diff --git a/README.md b/README.md index 93b2502..d673acb 100644 --- a/README.md +++ b/README.md @@ -88,8 +88,9 @@ Open `http://localhost:3000`. The HTTP server and background worker start togeth Hexagonal (Ports & Adapters) with Domain-Driven Design: ``` -api-types — shared REST API request/response DTOs (Serialize/Deserialize + utoipa schemas); used by presentation and tui -domain — pure types and trait definitions, no external deps +api-types — shared REST API request/response DTOs (Serialize/Deserialize + utoipa schemas) + HtmlPageContext; used by presentation, tui, and template adapters +infra-wiring — shared infrastructure types (DbPool, EventBusBackend, AppConfig) used by both presentation and worker binaries +domain — pure types and CQRS port traits (MovieCommand/MovieQuery, WatchEventCommand/WatchEventQuery, PersonCommand/PersonQuery, SearchCommand/SearchPort, ImageFetcher, RssFeedRenderer), no external deps except serde application — use cases (commands + queries), business logic orchestration; handlers delegate here for all domain logic presentation — Axum HTTP router, OpenAPI spec assembly, Swagger UI + Scalar serving, composition root for the HTTP process worker — standalone worker binary (event consumer, poster sync, federation) diff --git a/architecture.mmd b/architecture.mmd index d36d5a5..23949b6 100644 --- a/architecture.mmd +++ b/architecture.mmd @@ -59,8 +59,8 @@ graph TB M_SEARCH["SearchQuery,
SearchResults"] end subgraph Ports["Port Traits (Interfaces)"] - P_REPOS["MovieRepository
ReviewRepository
DiaryRepository
UserRepository
WatchlistRepository
WatchEventRepository
WebhookTokenRepository
ImportSessionRepository
MovieProfileRepository
WrapUpRepository
GoalRepository
UserSettingsRepository
MovieDeduplicator"] - P_SERVICES["AuthService
MetadataClient
PosterFetcherClient
ObjectStorage
EventPublisher
EventConsumer
PasswordHasher
DiaryExporter
DocumentParser"] + P_REPOS["MovieCommand / MovieQuery
ReviewRepository
DiaryRepository / StatsRepository
UserRepository
WatchlistRepository
WatchEventCommand / WatchEventQuery
WebhookTokenRepository
ImportSessionRepository
MovieProfileRepository
WrapUpRepository
GoalRepository
UserSettingsRepository
MovieDeduplicator"] + P_SERVICES["AuthService
MetadataClient
PosterFetcherClient
ImageFetcher
ObjectStorage
EventPublisher
EventConsumer
PasswordHasher
DiaryExporter
DocumentParser
RssFeedRenderer"] P_SEARCH["SearchPort
SearchCommand
PersonQuery
PersonCommand"] P_FEDERATION["SocialQueryPort
LocalApContentQuery
RemoteWatchlistRepository
RemoteGoalRepository"] end @@ -72,8 +72,12 @@ graph TB VO["Value Objects
MovieId, UserId, Rating,
WatchMedium, Email, Username,
Password, ...
"] end - subgraph ApiTypes["api-types (0 domain deps)"] - DTO["DTOs
MovieDto, ReviewDto,
FeedEntryDto, UserSummaryDto,
CastMemberDto, ...
"] + subgraph ApiTypes["api-types"] + DTO["DTOs
MovieDto, ReviewDto,
FeedEntryDto, UserSummaryDto,
HtmlPageContext, ...
"] + end + + subgraph InfraWiring["infra-wiring"] + IW["DbPool, EventBusBackend,
AppConfig
Shared infra types"] end subgraph Adapters["Adapters (implement Port Traits)"] diff --git a/crates/presentation/src/errors.rs b/crates/presentation/src/errors.rs index 968aec8..073151a 100644 --- a/crates/presentation/src/errors.rs +++ b/crates/presentation/src/errors.rs @@ -36,6 +36,13 @@ impl From for ApiError { impl IntoResponse for ApiError { fn into_response(self) -> Response { - domain_error_response(self.0) + let status = domain_error_status(&self.0); + match &self.0 { + DomainError::InfrastructureError(_) => { + tracing::error!("Internal error: {:?}", self.0); + (status, axum::Json(serde_json::json!({"error": "internal server error"}))).into_response() + } + _ => (status, axum::Json(serde_json::json!({"error": self.0.to_string()}))).into_response(), + } } } diff --git a/movies-diary.insomnia.json b/movies-diary.insomnia.json index e23b94f..b552def 100644 --- a/movies-diary.insomnia.json +++ b/movies-diary.insomnia.json @@ -355,6 +355,32 @@ "settingRebuildPath": true, "settingFollowRedirects": "global" }, + { + "_id": "req_reviews_edit", + "_type": "request", + "parentId": "fld_reviews", + "modified": 1749340800000, + "created": 1749340800000, + "name": "Edit Review", + "method": "PATCH", + "url": "{{ base_url }}/api/v1/reviews/:id", + "description": "Partial update — all fields optional", + "headers": [{ "name": "Content-Type", "value": "application/json" }], + "parameters": [], + "authentication": { "type": "bearer", "token": "{{ token }}" }, + "body": { + "mimeType": "application/json", + "text": "{\n \"rating\": 9,\n \"comment\": \"Updated thoughts\",\n \"watch_medium\": \"cinema\"\n}" + }, + "metaSortKey": -3, + "isPrivate": false, + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global" + }, { "_id": "fld_people",