chore: gate test helpers, code style, stricter clippy
This commit is contained in:
@@ -36,7 +36,7 @@ jobs:
|
|||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
|
|
||||||
- name: clippy
|
- name: clippy
|
||||||
run: cargo clippy --all-targets -- -D warnings
|
run: cargo clippy --workspace --all-targets -- -D warnings
|
||||||
|
|
||||||
- name: test
|
- name: test
|
||||||
run: cargo test
|
run: cargo test
|
||||||
|
|||||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
|
|
||||||
- name: clippy
|
- name: clippy
|
||||||
run: cargo clippy --all-targets -- -D warnings
|
run: cargo clippy --workspace --all-targets -- -D warnings
|
||||||
|
|
||||||
- name: test
|
- name: test
|
||||||
run: cargo test
|
run: cargo test
|
||||||
|
|||||||
283
CODE_STYLE.md
Normal file
283
CODE_STYLE.md
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
# Code Style Guide
|
||||||
|
|
||||||
|
This document defines the coding standards for this project. All contributors (human and agent) must follow these rules. When in doubt, prefer clarity and correctness over cleverness and speed.
|
||||||
|
|
||||||
|
## Philosophy
|
||||||
|
|
||||||
|
- Code reads like prose
|
||||||
|
- Do things properly — no shortcuts
|
||||||
|
- SOLID principles — always, no exceptions
|
||||||
|
- KISS — simplest solution that solves the problem **correctly and properly**. KISS means no unnecessary complexity, NOT skipping proper architecture. If the design calls for a trait, a newtype, or a pattern — that IS the simple solution. When in doubt, follow the architecture (CONTEXT.md, ADRs) over perceived simplicity.
|
||||||
|
- DRY — one source of truth for every piece of knowledge
|
||||||
|
- Use design patterns where they fit (Strategy, Observer, Builder, etc.) — name them in code so intent is clear
|
||||||
|
- Single responsibility everywhere: files, functions, types, modules
|
||||||
|
- Single point of entry, single control flow — no parallel data/control paths
|
||||||
|
- Open/Closed — extend via new types, don't modify existing ones
|
||||||
|
- Depend on abstractions (traits), not concretions
|
||||||
|
- Keep things as private as possible
|
||||||
|
- Zero magic numbers, strings, or unnamed constants
|
||||||
|
- Newtypes for self-documenting code
|
||||||
|
- Descriptive variable names — `review_rating`, not `rr`. `watched_on`, not `wd`. `poster_url`, not `pu`. No abbreviations unless universally understood (e.g., `id`, `url`, `i` for a trivial loop index).
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
### Module Organization
|
||||||
|
|
||||||
|
Deep module hierarchy, grouped by concern. No flat file dumps. Each file is its own small, enclosed thing.
|
||||||
|
|
||||||
|
```
|
||||||
|
crates/domain/src/
|
||||||
|
├── models/
|
||||||
|
│ ├── review.rs
|
||||||
|
│ ├── diary_entry.rs
|
||||||
|
│ └── movie.rs
|
||||||
|
├── value_objects/
|
||||||
|
│ ├── movie_id.rs
|
||||||
|
│ ├── rating.rs
|
||||||
|
│ └── watched_on.rs
|
||||||
|
├── ports/
|
||||||
|
│ ├── review_repository.rs
|
||||||
|
│ └── event_publisher.rs
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Size
|
||||||
|
|
||||||
|
Small files with a single concern. If a file grows past ~100-150 lines, it's probably doing too much — split it.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
Never in the same file as production code. Use cases keep their tests in a sibling `tests/` module, one file per use case:
|
||||||
|
|
||||||
|
```
|
||||||
|
crates/application/src/diary/log_review.rs
|
||||||
|
crates/application/src/diary/tests/log_review.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Naming
|
||||||
|
|
||||||
|
### Conventions
|
||||||
|
|
||||||
|
- Files and modules: `snake_case`
|
||||||
|
- Types: `PascalCase` with semantic suffixes for clarity and navigation
|
||||||
|
- Functions and methods: `snake_case`, verb-first, descriptive
|
||||||
|
|
||||||
|
### Type Suffixes
|
||||||
|
|
||||||
|
| Suffix | Usage | Example |
|
||||||
|
|---|---|---|
|
||||||
|
| (none) | Value objects, entities | `Review`, `Movie`, `DiaryEntry` |
|
||||||
|
| `Id` | Identity newtypes | `MovieId`, `ReviewId`, `UserId` |
|
||||||
|
| `Config` | Configuration data | `ServerConfig`, `StorageConfig` |
|
||||||
|
| `Service` | Domain services | `LocalSocialService` |
|
||||||
|
| `Error` | Error types | `DomainError`, `AuthError` |
|
||||||
|
| `UseCase` | Application use cases | `LogReviewUseCase`, `GetDiaryUseCase` |
|
||||||
|
| `Strategy` | Strategy trait/impls | `RatingAggregationStrategy` |
|
||||||
|
|
||||||
|
Ports are named for the capability they provide (`ReviewRepository`, `EventPublisher`); their implementations are named for the technology (`SqliteReviewRepository`).
|
||||||
|
|
||||||
|
### Newtypes
|
||||||
|
|
||||||
|
Use newtypes everywhere a primitive carries domain meaning:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// YES
|
||||||
|
struct MovieId(Uuid);
|
||||||
|
struct ReviewId(Uuid);
|
||||||
|
struct Rating(u8);
|
||||||
|
struct PosterUrl(Url);
|
||||||
|
|
||||||
|
// NO
|
||||||
|
type MovieId = String;
|
||||||
|
fn log_review(movie: &str, rating: u8, watched_on: &str) -> ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Newtypes are self-documenting and prevent accidental misuse (can't pass a `MovieId` where a `ReviewId` is expected).
|
||||||
|
|
||||||
|
## Functions and Methods
|
||||||
|
|
||||||
|
### Single Responsibility
|
||||||
|
|
||||||
|
Each function does ONE thing. Compose small functions into larger behaviors.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// YES
|
||||||
|
fn resolve_movie(...) -> Movie { ... }
|
||||||
|
fn build_review(movie: &Movie, draft: ReviewDraft) -> Review { ... }
|
||||||
|
fn record_diary_entry(review: &Review) -> DiaryEntry { ... }
|
||||||
|
|
||||||
|
// NO
|
||||||
|
fn log_review(...) -> DiaryEntry {
|
||||||
|
// 80 lines doing movie resolution, review construction and diary recording
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Visibility
|
||||||
|
|
||||||
|
Default to private. Only `pub` what the module's consumers actually need. Use `pub(crate)` for intra-crate sharing that shouldn't leak outside.
|
||||||
|
|
||||||
|
### impl Blocks
|
||||||
|
|
||||||
|
Split by concern:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Construction
|
||||||
|
impl Review {
|
||||||
|
pub fn new(movie: MovieId, rating: Rating, watched_on: WatchedOn) -> Self { ... }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queries
|
||||||
|
impl Review {
|
||||||
|
pub fn is_rewatch(&self) -> bool { ... }
|
||||||
|
pub fn rating(&self) -> Rating { ... }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutations
|
||||||
|
impl Review {
|
||||||
|
pub fn revise_rating(&mut self, rating: Rating) { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Domain and Application: `thiserror`
|
||||||
|
|
||||||
|
Typed, matchable error enums. Every error variant is meaningful.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum DiaryError {
|
||||||
|
#[error("movie {0} not found")]
|
||||||
|
MovieNotFound(MovieId),
|
||||||
|
#[error("review rejected: {0}")]
|
||||||
|
ReviewRejected(String),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adapters and Bootstrap: `anyhow`
|
||||||
|
|
||||||
|
Pragmatic error handling. Context strings for debugging.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn fetch_poster_blob(poster_url: &PosterUrl) -> anyhow::Result<Bytes> {
|
||||||
|
let data = http_client.get(poster_url)
|
||||||
|
.context("failed to fetch poster")?;
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Never `unwrap` or `expect` in production code
|
||||||
|
|
||||||
|
Errors propagate with `?` and bubble up to a single handler. No scattered panic points.
|
||||||
|
|
||||||
|
`unwrap` is allowed **only** in test code.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Secrets (API keys, JWT secrets) and deployment settings live in environment variables, read once at startup into a typed config struct.
|
||||||
|
|
||||||
|
Zero magic numbers or strings. Everything named, everything explained by its name.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// YES
|
||||||
|
const MAX_REVIEW_BODY_LENGTH: usize = 10_000;
|
||||||
|
|
||||||
|
// NO
|
||||||
|
if body.len() > 10_000 { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Constants are only for true invariants that never change. Tunable values belong in config.
|
||||||
|
|
||||||
|
## Comments
|
||||||
|
|
||||||
|
Comments only where a name, test or ADR cannot carry the reason. A comment is noise that drifts out of date while the code moves on, so prefer to put the reason somewhere that cannot rot silently:
|
||||||
|
|
||||||
|
- **A name.** Rename the function, the variable, or the type until the reason is visible in the code.
|
||||||
|
- **A test.** A constraint worth a comment is worth a test that fails when someone breaks it. `media_is_resolved_before_the_cascade_runs` outlives any note explaining why the order matters.
|
||||||
|
- **An ADR.** Architectural reasoning belongs in `docs/adr/`, where it is versioned and discoverable.
|
||||||
|
|
||||||
|
When none of those can carry it, a short comment stating the reason (never restating the code) is acceptable.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Use good crates when they exist — don't reinvent
|
||||||
|
- Prefer crates with minimal transitive dependencies
|
||||||
|
- Aim for small memory footprint and executable size
|
||||||
|
- Every dependency that has a `[workspace.dependencies]` entry is declared `workspace = true`; crates inherit `edition` from `[workspace.package]`
|
||||||
|
- Domain crate: zero external dependencies beyond `thiserror`, `serde` and small value-type crates
|
||||||
|
- Application crate: depends only on domain
|
||||||
|
- Adapters: free to pull in platform crates (`axum`, `sqlx`, `nats`, etc.)
|
||||||
|
- Bootstrap: wiring only
|
||||||
|
|
||||||
|
## Trait Objects for Ports
|
||||||
|
|
||||||
|
Ports are `dyn` behind `Arc`. Use cases hold their dependencies as trait objects:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct Deps {
|
||||||
|
pub reviews: Arc<dyn ReviewRepository>,
|
||||||
|
pub events: Arc<dyn EventPublisher>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Prefer a closed enum over `dyn` only where exhaustiveness is the point — when adding a variant must force every consumer to handle it. That is a decision about compiler-enforced coverage, not about dispatch cost.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Test Helpers Are Gated
|
||||||
|
|
||||||
|
`domain::testing` (in-memory fakes and noops) compiles only with the `test-helpers` feature, which crates enable in their `[dev-dependencies]`. Production builds must never contain fakes. `application::test_helpers` is `#[cfg(test)]`.
|
||||||
|
|
||||||
|
### Coverage Targets
|
||||||
|
|
||||||
|
| Crate | Approach | Target |
|
||||||
|
|---|---|---|
|
||||||
|
| Domain | TDD (red-green-refactor) | 100% |
|
||||||
|
| Application | TDD | High coverage |
|
||||||
|
| Adapters | Integration tests where practical | Best effort |
|
||||||
|
| Bootstrap | No tests | — |
|
||||||
|
|
||||||
|
### Fakes Must Not Be Kinder Than Production
|
||||||
|
|
||||||
|
A fake that is more forgiving than the real adapter turns a test into a lie: it passes whichever way the code is written, which is worse than having no test. Two rules follow.
|
||||||
|
|
||||||
|
**A fake must reproduce the constraints the database enforces.** SQLite removes dependent rows through `ON DELETE CASCADE`, so an in-memory store must cascade the same way, and enforce the same uniqueness constraints. Without that, an ordering requirement such as "resolve the poster before deleting the `DiaryEntry`" could not be tested at all.
|
||||||
|
|
||||||
|
**A fake must be able to fail.** Every port whose failure is meant to degrade rather than propagate needs a fake that refuses (a poster fetcher that errors, an event publisher that rejects). A best-effort path with no failing test is a path that has never run.
|
||||||
|
|
||||||
|
When a claim cannot be observed through the fake, move the test to where the real thing runs rather than asserting it against the fake's own behaviour.
|
||||||
|
|
||||||
|
### Test Naming
|
||||||
|
|
||||||
|
Tests read as specifications:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[tokio::test]
|
||||||
|
async fn logging_review_creates_diary_entry() { ... }
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn rewatch_review_keeps_original_entry() { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Control Flow
|
||||||
|
|
||||||
|
### Single Point of Entry
|
||||||
|
|
||||||
|
One path through the code. No parallel flows that do similar things in different places.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// YES — one function handles movie resolution
|
||||||
|
let movie = movie_resolver.resolve(movie_id, requester);
|
||||||
|
|
||||||
|
// NO — movie resolution duplicated in two places
|
||||||
|
// diary_handler.rs: resolves movie
|
||||||
|
// import_handler.rs: also resolves movie with slightly different logic
|
||||||
|
```
|
||||||
|
|
||||||
|
### Single Call, Cascading Changes
|
||||||
|
|
||||||
|
When something changes, it should cascade from one point. Don't require updating multiple files for a single logical change.
|
||||||
|
|
||||||
|
### No God Objects
|
||||||
|
|
||||||
|
If a struct has more than 5-6 fields, question whether it's doing too much. If a function takes more than 4-5 parameters, consider grouping them into a dedicated type.
|
||||||
@@ -40,6 +40,9 @@ members = [
|
|||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
tokio = { version = "1.0", features = ["macros", "net", "rt", "rt-multi-thread", "sync", "time"] }
|
tokio = { version = "1.0", features = ["macros", "net", "rt", "rt-multi-thread", "sync", "time"] }
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -74,10 +74,10 @@ fmt-check:
|
|||||||
cargo fmt --check
|
cargo fmt --check
|
||||||
|
|
||||||
clippy:
|
clippy:
|
||||||
cargo clippy -- -D warnings
|
cargo clippy --workspace --all-targets -- -D warnings
|
||||||
|
|
||||||
test:
|
test:
|
||||||
cargo test
|
cargo test --workspace
|
||||||
|
|
||||||
fix:
|
fix:
|
||||||
cargo fmt
|
cargo fmt
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "activitypub"
|
name = "activitypub"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
k-ap = { version = "0.5.0", registry = "gitea" }
|
k-ap = { version = "0.5.0", registry = "gitea" }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "adapter-common"
|
name = "adapter-common"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
domain = { workspace = true }
|
domain = { workspace = true }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "auth"
|
name = "auth"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait = { workspace = true }
|
async-trait = { workspace = true }
|
||||||
@@ -9,7 +9,7 @@ domain = { workspace = true }
|
|||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { workspace = true }
|
||||||
jsonwebtoken = "9"
|
jsonwebtoken = "9"
|
||||||
argon2 = { version = "0.5", features = ["std"] }
|
argon2 = { version = "0.5", features = ["std"] }
|
||||||
rand_core = { version = "0.6", features = ["getrandom"] }
|
rand_core = { version = "0.6", features = ["getrandom"] }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "event-payload"
|
name = "event-payload"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
domain = { workspace = true }
|
domain = { workspace = true }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "event-publisher"
|
name = "event-publisher"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
domain = { workspace = true }
|
domain = { workspace = true }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "export"
|
name = "export"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
domain = { workspace = true }
|
domain = { workspace = true }
|
||||||
|
|||||||
@@ -15,5 +15,5 @@ webp = "0.3"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
object-storage = { workspace = true }
|
object-storage = { workspace = true }
|
||||||
object_store = "0.11"
|
object_store = { workspace = true }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "importer"
|
name = "importer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
xlsx = ["dep:calamine"]
|
xlsx = ["dep:calamine"]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "jellyfin"
|
name = "jellyfin"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
domain = { workspace = true }
|
domain = { workspace = true }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "metadata"
|
name = "metadata"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nats"
|
name = "nats"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-nats = "0.48.0"
|
async-nats = "0.48.0"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "object-storage"
|
name = "object-storage"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
domain = { workspace = true }
|
domain = { workspace = true }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "plex"
|
name = "plex"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
domain = { workspace = true }
|
domain = { workspace = true }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "poster-sync"
|
name = "poster-sync"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
domain = { workspace = true }
|
domain = { workspace = true }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "postgres-event-queue"
|
name = "postgres-event-queue"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "postgres", "macros", "chrono"] }
|
sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "postgres", "macros", "chrono"] }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "postgres-federation"
|
name = "postgres-federation"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sqlx = { version = "0.8.6", features = [
|
sqlx = { version = "0.8.6", features = [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "postgres-social"
|
name = "postgres-social"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sqlx = { version = "0.8.6", features = [
|
sqlx = { version = "0.8.6", features = [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "postgres"
|
name = "postgres"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sqlx = { version = "0.8.6", features = [
|
sqlx = { version = "0.8.6", features = [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rss"
|
name = "rss"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rss-feed = { package = "rss", version = "2", features = ["atom"] }
|
rss-feed = { package = "rss", version = "2", features = ["atom"] }
|
||||||
|
|||||||
@@ -60,7 +60,9 @@ fn channel_links_are_absolute_with_atom_self() {
|
|||||||
fn item_links_to_absolute_movie_page() {
|
fn item_links_to_absolute_movie_page() {
|
||||||
let adapter = RssAdapter::new("http://example.com".into());
|
let adapter = RssAdapter::new("http://example.com".into());
|
||||||
let e = entry(false);
|
let e = entry(false);
|
||||||
let xml = adapter.render_feed(&[e.clone()], &channel("Feed")).unwrap();
|
let xml = adapter
|
||||||
|
.render_feed(std::slice::from_ref(&e), &channel("Feed"))
|
||||||
|
.unwrap();
|
||||||
let expected = format!(
|
let expected = format!(
|
||||||
"<link>http://example.com/movies/{}</link>",
|
"<link>http://example.com/movies/{}</link>",
|
||||||
e.movie().id().value()
|
e.movie().id().value()
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sqlite-event-queue"
|
name = "sqlite-event-queue"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "sqlite", "macros", "chrono"] }
|
sqlx = { workspace = true, features = ["macros", "chrono"] }
|
||||||
domain = { workspace = true }
|
domain = { workspace = true }
|
||||||
event-payload = { workspace = true }
|
event-payload = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sqlite-federation"
|
name = "sqlite-federation"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sqlx = { workspace = true }
|
sqlx = { workspace = true }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sqlite-social"
|
name = "sqlite-social"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sqlx = { workspace = true }
|
sqlx = { workspace = true }
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sqlite"
|
name = "sqlite"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sqlx = { version = "0.8.6", features = [
|
sqlx = { workspace = true, features = ["macros"] }
|
||||||
"runtime-tokio-rustls",
|
|
||||||
"sqlite",
|
|
||||||
"uuid",
|
|
||||||
"macros",
|
|
||||||
] }
|
|
||||||
|
|
||||||
adapter-common = { workspace = true }
|
adapter-common = { workspace = true }
|
||||||
domain = { workspace = true }
|
domain = { workspace = true }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "template-askama"
|
name = "template-askama"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
askama = { version = "0.16.0" }
|
askama = { version = "0.16.0" }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tmdb-enrichment"
|
name = "tmdb-enrichment"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
domain = { workspace = true }
|
domain = { workspace = true }
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "api-types"
|
name = "api-types"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
utoipa = { version = "5.5.0", features = ["axum_extras", "uuid"] }
|
utoipa = { version = "5.5.0", features = ["axum_extras", "uuid"] }
|
||||||
domain = { path = "../domain" }
|
domain = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "application"
|
name = "application"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait = { workspace = true }
|
async-trait = { workspace = true }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "composition"
|
name = "composition"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "domain"
|
name = "domain"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ pub mod ports;
|
|||||||
pub mod services;
|
pub mod services;
|
||||||
pub mod value_objects;
|
pub mod value_objects;
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-helpers"))]
|
#[cfg(feature = "test-helpers")]
|
||||||
pub mod testing;
|
pub mod testing;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "presentation"
|
name = "presentation"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
description = "Self-hosted movie diary with REST API and ActivityPub federation"
|
description = "Self-hosted movie diary with REST API and ActivityPub federation"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "server"
|
name = "server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
description = "Self-hosted movie diary with REST API and ActivityPub federation"
|
description = "Self-hosted movie diary with REST API and ActivityPub federation"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tui"
|
name = "tui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
@@ -16,7 +16,7 @@ windows = ["dep:windows-native-keyring-store"]
|
|||||||
ratatui = "0.30.0"
|
ratatui = "0.30.0"
|
||||||
keyring-core = "1.0.0"
|
keyring-core = "1.0.0"
|
||||||
directories = "6"
|
directories = "6"
|
||||||
csv = "1"
|
csv = { workspace = true }
|
||||||
|
|
||||||
apple-native-keyring-store = { version = "1.0.0", optional = true, features = [
|
apple-native-keyring-store = { version = "1.0.0", optional = true, features = [
|
||||||
"keychain",
|
"keychain",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "worker"
|
name = "worker"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["sqlite", "sqlite-federation"]
|
default = ["sqlite", "sqlite-federation"]
|
||||||
|
|||||||
Reference in New Issue
Block a user