feat: add Dockerfile, .dockerignore, and README; remove common crate
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
target/
|
||||||
|
.git/
|
||||||
|
.env
|
||||||
|
*.db
|
||||||
|
*.db-shm
|
||||||
|
*.db-wal
|
||||||
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -389,13 +389,6 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "common"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -611,7 +604,6 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"chrono",
|
"chrono",
|
||||||
"common",
|
|
||||||
"email_address",
|
"email_address",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"crates/adapters/auth", "crates/adapters/event-publisher",
|
"crates/adapters/auth",
|
||||||
"crates/adapters/metadata", "crates/adapters/poster-fetcher", "crates/adapters/poster-storage",
|
"crates/adapters/event-publisher",
|
||||||
|
"crates/adapters/metadata",
|
||||||
|
"crates/adapters/poster-fetcher",
|
||||||
|
"crates/adapters/poster-storage",
|
||||||
"crates/adapters/rss",
|
"crates/adapters/rss",
|
||||||
"crates/adapters/sqlite",
|
"crates/adapters/sqlite",
|
||||||
"crates/adapters/template-askama",
|
"crates/adapters/template-askama",
|
||||||
"crates/application",
|
"crates/application",
|
||||||
"crates/common",
|
|
||||||
"crates/domain",
|
"crates/domain",
|
||||||
"crates/presentation",
|
"crates/presentation",
|
||||||
]
|
]
|
||||||
@@ -34,7 +36,6 @@ reqwest = { version = "0.13", features = ["json", "query"] }
|
|||||||
object_store = { version = "0.11", features = ["aws"] }
|
object_store = { version = "0.11", features = ["aws"] }
|
||||||
|
|
||||||
domain = { path = "crates/domain" }
|
domain = { path = "crates/domain" }
|
||||||
common = { path = "crates/common" }
|
|
||||||
application = { path = "crates/application" }
|
application = { path = "crates/application" }
|
||||||
presentation = { path = "crates/presentation" }
|
presentation = { path = "crates/presentation" }
|
||||||
auth = { path = "crates/adapters/auth" }
|
auth = { path = "crates/adapters/auth" }
|
||||||
|
|||||||
57
Dockerfile
Normal file
57
Dockerfile
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# ----- build -----
|
||||||
|
FROM rust:slim-bookworm AS builder
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends sqlite3 && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Cache dependency compilation separately from source
|
||||||
|
COPY Cargo.toml Cargo.lock ./
|
||||||
|
COPY crates/adapters/auth/Cargo.toml crates/adapters/auth/Cargo.toml
|
||||||
|
COPY crates/adapters/event-publisher/Cargo.toml crates/adapters/event-publisher/Cargo.toml
|
||||||
|
COPY crates/adapters/metadata/Cargo.toml crates/adapters/metadata/Cargo.toml
|
||||||
|
COPY crates/adapters/poster-fetcher/Cargo.toml crates/adapters/poster-fetcher/Cargo.toml
|
||||||
|
COPY crates/adapters/poster-storage/Cargo.toml crates/adapters/poster-storage/Cargo.toml
|
||||||
|
COPY crates/adapters/rss/Cargo.toml crates/adapters/rss/Cargo.toml
|
||||||
|
COPY crates/adapters/sqlite/Cargo.toml crates/adapters/sqlite/Cargo.toml
|
||||||
|
COPY crates/adapters/template-askama/Cargo.toml crates/adapters/template-askama/Cargo.toml
|
||||||
|
COPY crates/application/Cargo.toml crates/application/Cargo.toml
|
||||||
|
COPY crates/domain/Cargo.toml crates/domain/Cargo.toml
|
||||||
|
COPY crates/presentation/Cargo.toml crates/presentation/Cargo.toml
|
||||||
|
|
||||||
|
# Stub every crate so cargo can resolve and fetch deps
|
||||||
|
RUN find crates -name "Cargo.toml" | sed 's|/Cargo.toml||' | \
|
||||||
|
xargs -I{} sh -c 'mkdir -p {}/src && echo "fn main(){}" > {}/src/main.rs && echo "" > {}/src/lib.rs'
|
||||||
|
|
||||||
|
RUN cargo fetch
|
||||||
|
|
||||||
|
# Now copy real sources (invalidates cache only on source changes)
|
||||||
|
COPY crates ./crates
|
||||||
|
|
||||||
|
# sqlx macros verify queries at compile time; create a real DB from migrations
|
||||||
|
RUN sqlite3 /build/dev.db \
|
||||||
|
< crates/adapters/sqlite/migrations/0001_initial.sql && \
|
||||||
|
sqlite3 /build/dev.db \
|
||||||
|
< crates/adapters/sqlite/migrations/0002_users.sql
|
||||||
|
|
||||||
|
ENV DATABASE_URL=sqlite:///build/dev.db
|
||||||
|
|
||||||
|
RUN cargo build --release -p presentation
|
||||||
|
|
||||||
|
# ----- runtime -----
|
||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=builder /build/target/release/presentation ./presentation
|
||||||
|
COPY static ./static
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
ENV RUST_LOG=presentation=info,tower_http=info
|
||||||
|
|
||||||
|
CMD ["./presentation"]
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Gabriel Kaszewski
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
86
README.md
Normal file
86
README.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# Movies Diary
|
||||||
|
|
||||||
|
A self-hosted, server-side rendered movie logging system. Built in Rust — no JavaScript, no SPA, just HTML forms and an RSS feed. Designed to run as a lightweight widget embedded on a personal site.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Log movies with a TMDB/OMDb ID and a 0–5 rating
|
||||||
|
- Immutable append-only viewing ledger (tracks re-watches)
|
||||||
|
- Background poster fetching and storage (S3-compatible)
|
||||||
|
- RSS/Atom feed for public subscription
|
||||||
|
- JWT authentication via cookie (HTML) or Bearer token (REST API)
|
||||||
|
- Zero JavaScript
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Hexagonal (Ports & Adapters) with Domain-Driven Design:
|
||||||
|
|
||||||
|
```
|
||||||
|
domain — pure types and trait definitions, no external deps
|
||||||
|
common — shared error types
|
||||||
|
application — use cases / business logic orchestration
|
||||||
|
presentation — Axum HTTP router, wires all adapters together
|
||||||
|
adapters/
|
||||||
|
auth — JWT issuance and validation (Argon2 passwords)
|
||||||
|
sqlite — SQLite repository via sqlx
|
||||||
|
metadata — OMDb HTTP client
|
||||||
|
poster-fetcher — downloads poster images
|
||||||
|
poster-storage — uploads posters to S3-compatible storage
|
||||||
|
template-askama — Askama HTML rendering
|
||||||
|
rss — RSS/Atom feed generation
|
||||||
|
event-publisher — async event channel for background poster sync
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Rust (stable, 2024 edition)
|
||||||
|
- SQLite
|
||||||
|
- An S3-compatible object store (e.g. MinIO) for poster storage
|
||||||
|
- An [OMDb API key](https://www.omdbapi.com/apikey.aspx)
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Copy and fill in the following (e.g. in a `.env` file):
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Database
|
||||||
|
DATABASE_URL=sqlite://movies.db
|
||||||
|
|
||||||
|
# Authentication
|
||||||
|
JWT_SECRET=change-me
|
||||||
|
JWT_TTL_SECONDS=86400
|
||||||
|
|
||||||
|
# OMDb metadata
|
||||||
|
OMDB_API_KEY=your-key
|
||||||
|
|
||||||
|
# Poster storage (S3-compatible)
|
||||||
|
MINIO_ENDPOINT=http://localhost:9000
|
||||||
|
MINIO_BUCKET=posters
|
||||||
|
MINIO_REGION=us-east-1
|
||||||
|
MINIO_ACCESS_KEY_ID=minioadmin
|
||||||
|
MINIO_SECRET_ACCESS_KEY=minioadmin
|
||||||
|
|
||||||
|
# Optional
|
||||||
|
ALLOW_REGISTRATION=false
|
||||||
|
POSTER_FETCH_TIMEOUT_SECONDS=10
|
||||||
|
EVENT_CHANNEL_BUFFER=32
|
||||||
|
RUST_LOG=presentation=debug,tower_http=debug
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run -p presentation
|
||||||
|
```
|
||||||
|
|
||||||
|
Server listens on `0.0.0.0:3000`.
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License. See [LICENSE](LICENSE).
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "common"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
thiserror = { workspace = true }
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
pub mod errors;
|
|
||||||
@@ -10,5 +10,4 @@ async-trait = { workspace = true }
|
|||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
common = { workspace = true }
|
|
||||||
email_address = "0.2.9"
|
email_address = "0.2.9"
|
||||||
|
|||||||
Reference in New Issue
Block a user