feat(rm-server): POC-ready — autohydrate seed + Railway Dockerfile + $PORT bind#23
Merged
Merged
Conversation
…RT bind
Three pieces that together make redmine-rs deployable as a POC:
1) Autohydrate (rm-store::seed)
─────────────────────────────
New module `crates/rm-store/src/seed.rs` seeds a Redmine-shaped demo
corpus on first boot when the store is empty. Idempotent — subsequent
boots no-op so user-created rows are never duplicated.
pub fn seed_enabled_for(env: Option<&str>) -> bool # pure, testable
pub fn seed_enabled() -> bool # reads RM_SEED
pub async fn hydrate_demo_data(&Store) -> Result<usize, _>
pub async fn hydrate_demo_data_on_boot(&Store) -> Result<usize, _>
Demo corpus: ~50 rows shaped so the D2 filter/sort/paginate chrome has
real work to do — 27 issues (mixed subjects: "bug" matches some-not-all
for substring tests; spread alphabetically for visible sort), 4
statuses, 3 trackers, 4 priorities, 3 projects, 4 users, 3 roles, 2
news, 2 wiki pages, 5 time entries, 2 repositories, 3 queries, 1
relation.
`RM_SEED=off` (also `0`/`false`/`no`, case-insensitive) disables the
seed. Default-on for POC ergonomics.
The crate is #![forbid(unsafe_code)]; `std::env::set_var` is unsafe in
recent Rust. Tests target the pure `seed_enabled_for(Option<&str>)`
helper directly — same pattern as op-server #58.
2) $PORT bind (rm-server)
──────────────────────
`resolve_bind_addr(env_port, fallback)` honors $PORT for PaaS deploys
(Railway, Heroku, Cloud Run, Fly route their public 443/80 edge to
$PORT internally; the container must bind 0.0.0.0:$PORT). Falls back
to ServerConfig::bind for local. Whitespace-only $PORT treated as
unset; malformed $PORT yields a diagnostic error naming the bad value.
Mirrors op-server #58's same-named helper — both ports now land the
lance-graph CONSUMER_SCAN_TODO.md §B1 PaaS pattern.
3) Dockerfile (top-level) + .dockerignore
───────────────────────────────────────
Multi-stage build: rust:1.95-bookworm → debian:bookworm-slim. Ships
only the rm-server release binary + CA certs. Runs as non-root
(UID 10001). Default ENV PORT=3000, EXPOSE 3000; PaaS overrides
$PORT at deploy time. CMD uses exec-form so PID-1 is rm-server itself
and SIGTERM reaches axum's graceful-shutdown handler.
.dockerignore keeps target/, .git/, IDE noise, and the Dockerfile
itself out of the build context.
Also wires issues_form::router into rm-server's build_router_with — #22
created the module but didn't merge its route (oversight in that PR).
cargo +1.95 test --workspace clean: rm-store 52 (+6 new seed), rm-server
10 (+4 new bind), rm-handlers 89 unchanged, plus the rest. fmt --all
clean, clippy --all-targets --workspace -- -D warnings clean.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Three pieces that together make
redmine-rsdeployable as a POC —cargo run -p rm-serverlands on a populated browser (filter / sort / paginate actually has things to filter),docker build .produces a Railway-ready image, and$PORTis honored at the bind site.1. Autohydrate (
rm-store::seed)New module — seeds a Redmine-shaped demo corpus on first boot if the store is empty.
bugmatches some-not-all (substring tests have real partitions); alphabetically spread so sort by subject produces visibly-different page-1 vs page-2Idempotent. Empty store → seed runs (~52 rows). Anything already present → no-op. So a user creating an issue via the D1 form doesn't trigger a re-seed; production data is never touched.
Operator override.
RM_SEED=off(or0/false/no, case-insensitive) skips the seed. Default-on for POC ergonomics.#![forbid(unsafe_code)]-safe. The pureseed_enabled_for(Option<&str>)helper is testable directly; the env read happens at one boot-time call site only. Same pattern as op-server #58.2.
$PORTbind (rm-server)resolve_bind_addr(env_port, fallback) -> std::io::Result<SocketAddr>honors the PaaS pattern — Railway / Heroku / Cloud Run / Fly route their public 443/80 edge to$PORTinternally; the container must bind0.0.0.0:$PORT. Falls back toServerConfig::bindfor local. Whitespace-only treated as unset; malformed values yield a diagnostic naming the bad value.Mirrors op-server #58's same-named helper — both ports now land the lance-graph
CONSUMER_SCAN_TODO.md §B1PaaS pattern.3. Dockerfile +
.dockerignoreTop-level multi-stage build:
rust:1.95-bookworm(builder) —cargo build --release -p rm-serverdebian:bookworm-slim(runtime) — ships only the release binary + CA certsENV PORT=3000,EXPOSE 3000,CMD ["/usr/local/bin/rm-server"]exec-form so PID-1 is the binary itself and SIGTERM reaches axum's graceful-shutdown.dockerignorekeepstarget/,.git/, IDE noise, and the Dockerfile itself out of the build context.Also fixed
issues_form::routerfrom #22 wasn't merged intobuild_router_with(oversight in that PR). Now wired alongsideissues::router.Build
1.95.cargo test --workspace→ all green:seed_enabled_forcases × 4,fresh_store_gets_a_useful_corpus,second_call_is_idempotent_no_op,corpus_has_subject_variety_for_filter_testing)resolve_bind_addrcases)cargo fmt --all --checkclean.cargo clippy --all-targets --workspace -- -D warningsclean.Demo flow that works after this lands
This is the iconic Redmine demo loop, working end-to-end, against seeded data, in a Railway-deployable container.