chore: gate test helpers, code style, stricter clippy
This commit is contained in:
@@ -36,7 +36,7 @@ jobs:
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: clippy
|
||||
run: cargo clippy --all-targets -- -D warnings
|
||||
run: cargo clippy --workspace --all-targets -- -D warnings
|
||||
|
||||
- name: 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
|
||||
|
||||
- name: clippy
|
||||
run: cargo clippy --all-targets -- -D warnings
|
||||
run: cargo clippy --workspace --all-targets -- -D warnings
|
||||
|
||||
- name: 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"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
|
||||
[workspace.dependencies]
|
||||
tokio = { version = "1.0", features = ["macros", "net", "rt", "rt-multi-thread", "sync", "time"] }
|
||||
bytes = "1"
|
||||
|
||||
4
Makefile
4
Makefile
@@ -74,10 +74,10 @@ fmt-check:
|
||||
cargo fmt --check
|
||||
|
||||
clippy:
|
||||
cargo clippy -- -D warnings
|
||||
cargo clippy --workspace --all-targets -- -D warnings
|
||||
|
||||
test:
|
||||
cargo test
|
||||
cargo test --workspace
|
||||
|
||||
fix:
|
||||
cargo fmt
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "activitypub"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
k-ap = { version = "0.5.0", registry = "gitea" }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "adapter-common"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
domain = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "auth"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
@@ -9,7 +9,7 @@ domain = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde = { workspace = true }
|
||||
jsonwebtoken = "9"
|
||||
argon2 = { version = "0.5", features = ["std"] }
|
||||
rand_core = { version = "0.6", features = ["getrandom"] }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "event-payload"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
domain = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "event-publisher"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
domain = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "export"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
domain = { workspace = true }
|
||||
|
||||
@@ -15,5 +15,5 @@ webp = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
object-storage = { workspace = true }
|
||||
object_store = "0.11"
|
||||
object_store = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "importer"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[features]
|
||||
xlsx = ["dep:calamine"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "jellyfin"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
domain = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "metadata"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "nats"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-nats = "0.48.0"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "object-storage"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
domain = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "plex"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
domain = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "poster-sync"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
domain = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "postgres-event-queue"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "postgres", "macros", "chrono"] }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "postgres-federation"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
sqlx = { version = "0.8.6", features = [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "postgres-social"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
sqlx = { version = "0.8.6", features = [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "postgres"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
sqlx = { version = "0.8.6", features = [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "rss"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
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() {
|
||||
let adapter = RssAdapter::new("http://example.com".into());
|
||||
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!(
|
||||
"<link>http://example.com/movies/{}</link>",
|
||||
e.movie().id().value()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[package]
|
||||
name = "sqlite-event-queue"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "sqlite", "macros", "chrono"] }
|
||||
sqlx = { workspace = true, features = ["macros", "chrono"] }
|
||||
domain = { workspace = true }
|
||||
event-payload = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "sqlite-federation"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
sqlx = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "sqlite-social"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
sqlx = { workspace = true }
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
[package]
|
||||
name = "sqlite"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
sqlx = { version = "0.8.6", features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"uuid",
|
||||
"macros",
|
||||
] }
|
||||
sqlx = { workspace = true, features = ["macros"] }
|
||||
|
||||
adapter-common = { workspace = true }
|
||||
domain = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "template-askama"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
askama = { version = "0.16.0" }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "tmdb-enrichment"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
domain = { workspace = true }
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "api-types"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
utoipa = { version = "5.5.0", features = ["axum_extras", "uuid"] }
|
||||
domain = { path = "../domain" }
|
||||
domain = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "application"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "composition"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "domain"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
uuid = { workspace = true }
|
||||
|
||||
@@ -5,5 +5,5 @@ pub mod ports;
|
||||
pub mod services;
|
||||
pub mod value_objects;
|
||||
|
||||
#[cfg(any(test, feature = "test-helpers"))]
|
||||
#[cfg(feature = "test-helpers")]
|
||||
pub mod testing;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "presentation"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
description = "Self-hosted movie diary with REST API and ActivityPub federation"
|
||||
license = "MIT"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "server"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
description = "Self-hosted movie diary with REST API and ActivityPub federation"
|
||||
license = "MIT"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "tui"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
@@ -16,7 +16,7 @@ windows = ["dep:windows-native-keyring-store"]
|
||||
ratatui = "0.30.0"
|
||||
keyring-core = "1.0.0"
|
||||
directories = "6"
|
||||
csv = "1"
|
||||
csv = { workspace = true }
|
||||
|
||||
apple-native-keyring-store = { version = "1.0.0", optional = true, features = [
|
||||
"keychain",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "worker"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["sqlite", "sqlite-federation"]
|
||||
|
||||
Reference in New Issue
Block a user