diff --git a/Cargo.lock b/Cargo.lock index 9eded7f..674672f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -709,7 +709,7 @@ dependencies = [ [[package]] name = "ushort" -version = "0.1.1" +version = "0.1.2" dependencies = [ "ctrlc", "include_dir", diff --git a/Cargo.toml b/Cargo.toml index ac52d24..c467e85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ushort" -version = "0.1.1" +version = "0.1.2" edition = "2021" rust-version = "1.86" description = "A compact, self-contained URL shortener" diff --git a/config.example.toml b/config.example.toml index a5049b7..b740f77 100644 --- a/config.example.toml +++ b/config.example.toml @@ -16,7 +16,3 @@ max_url_length = 2048 max_retention_days = 3650 rate_limit_requests = 60 rate_limit_window = 60 - -# Keep false when the embedded frontend should be served by ushort. -# Set true only when a separate web server serves the frontend files. -production = false diff --git a/deploy/docker-compose.prod.yml b/deploy/docker-compose.prod.yml index 023db53..6bef9de 100644 --- a/deploy/docker-compose.prod.yml +++ b/deploy/docker-compose.prod.yml @@ -1,6 +1,6 @@ services: ushort: - image: sodium/ushort:0.1.1 + image: sodium/ushort:0.1.2 container_name: ushort command: ["/app/config.toml"] ports: diff --git a/docs/api.md b/docs/api.md index 7f10ed2..1155973 100644 --- a/docs/api.md +++ b/docs/api.md @@ -6,7 +6,7 @@ when a query string follows the slash. ## Health and frontend -- `GET /` serves the embedded frontend when `production=false`. +- `GET /` serves the embedded frontend. - `GET /api/health` returns HTTP 200: ```json diff --git a/docs/configuration.md b/docs/configuration.md index 0946d77..46f6b3d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -24,9 +24,10 @@ ushort legacy-config.json | `max_retention_days` | no | `3650` | Maximum per-URL retention value. | | `rate_limit_requests` | no | `60` | Requests allowed per client/window. | | `rate_limit_window` | no | `60` | Sliding-window length in seconds. | -| `production` | no | `false` | Disable embedded frontend routes when a separate server provides them. | The legacy `short_length` option remains an alias for `min_short_length`. +The removed legacy `production` key is ignored when present; ushort always +serves its embedded frontend and static assets. ## Public URL layouts diff --git a/docs/deployment.md b/docs/deployment.md index 86599c5..e55f8e5 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -19,13 +19,12 @@ the server as `docker-compose.yml`. For repeatable releases, replace its image tag with the verified multi-architecture digest: ```yaml -image: docker.io/sodium/ushort:0.1.1@sha256: +image: docker.io/sodium/ushort:0.1.2@sha256: ``` -Set `db_path = "data/urlshort.db"` and normally keep `production = false` so -the executable serves its embedded frontend. Keep `config.toml` owned by the -container identity (`1001:1001`) with mode `0400`; the `data` directory must be -writable by the same identity. +Set `db_path = "data/urlshort.db"`. The executable always serves its embedded +frontend. Keep `config.toml` owned by the container identity (`1001:1001`) with +mode `0400`; the `data` directory must be writable by the same identity. ## Start and validate diff --git a/docs/frontend.md b/docs/frontend.md index bb1e7f3..4592abb 100644 --- a/docs/frontend.md +++ b/docs/frontend.md @@ -8,9 +8,10 @@ actions, API-key administration, sorting, pagination, theme selection, and locally hosted fonts. Relative asset URLs and the browser-derived API prefix allow the same files to work at either a subdomain root or a nested path. -Set `production = false` for the normal self-contained deployment. Setting it -to `true` disables the embedded frontend routes for installations that serve -those files separately. +The frontend and static assets are always available. The former `production` +toggle was removed because disabling embedded assets conflicts with ushort's +self-contained deployment model. Old configuration files containing that key +remain loadable; its value is ignored. The nginx examples apply `no-store` to the HTML entry point and `no-cache` to static assets. Embedded static requests do not consume the API rate-limit diff --git a/src/lib.rs b/src/lib.rs index 118e7ee..fbad276 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,6 @@ struct RawConfig { max_retention_days: i64, rate_limit_requests: usize, rate_limit_window: u64, - production: bool, } impl Default for RawConfig { @@ -49,7 +48,6 @@ impl Default for RawConfig { max_retention_days: 3650, rate_limit_requests: 60, rate_limit_window: 60, - production: false, } } } @@ -68,7 +66,6 @@ pub struct Config { pub max_retention_days: i64, pub rate_limit_requests: usize, pub rate_limit_window: u64, - pub production: bool, pub base_path: String, } @@ -152,7 +149,6 @@ impl Config { max_retention_days: raw.max_retention_days, rate_limit_requests: raw.rate_limit_requests, rate_limit_window: raw.rate_limit_window, - production: raw.production, base_path, }) } @@ -372,10 +368,7 @@ impl App { let raw_path = target_path(&request.target); let bare_base = !self.config.base_path.is_empty() && raw_path == self.config.base_path; - if matches!(request.method.as_str(), "GET" | "HEAD") - && !self.config.production - && !bare_base - { + if matches!(request.method.as_str(), "GET" | "HEAD") && !bare_base { if let Some(path) = local_path(raw_path, &self.config.base_path) { match path.as_str() { "/" | "/static" => return self.serve_static("index.html"), @@ -432,32 +425,14 @@ impl App { fn handle_get(&self, path: &str, request: &RequestData) -> ResponseData { match path { - "/" => { - if self.config.production { - health_response() - } else { - self.serve_static("index.html") - } - } + "/" => self.serve_static("index.html"), "/api/health" => health_response(), "/api/shorten" => self.handle_shorten(request), "/api/urls" => self.handle_list_urls(request), "/api/lookup" => self.handle_lookup(request), - "/static" => { - if self.config.production { - ResponseData::empty(404) - } else { - self.serve_static("index.html") - } - } + "/static" => self.serve_static("index.html"), _ if path.starts_with("/api/urls/") => self.handle_get_url(&path["/api/urls/".len()..]), - _ if path.starts_with("/static/") => { - if self.config.production { - ResponseData::empty(404) - } else { - self.serve_static(&path["/static/".len()..]) - } - } + _ if path.starts_with("/static/") => self.serve_static(&path["/static/".len()..]), _ => self.handle_redirect(path.trim_start_matches('/')), } } @@ -1477,19 +1452,32 @@ mod tests { } #[test] - fn production_flag_keeps_legacy_backend_static_behavior() { - let path = temp_path("production"); - let mut cfg = config(path.clone()); - cfg.production = true; - let app = App::new(cfg).unwrap(); + fn deprecated_production_key_is_ignored_and_frontend_remains_enabled() { + let path = temp_path("deprecated-production"); + let config_path = std::env::temp_dir().join(format!( + "ushort-deprecated-production-{}-{}.toml", + std::process::id(), + rand::random::() + )); + fs::write( + &config_path, + format!( + "base_url = \"https://example.test/s\"\n\ + api_key = \"secret\"\n\ + db_path = \"{}\"\n\ + production = true\n", + path.display() + ), + ) + .unwrap(); + let app = App::new(Config::load(&config_path).unwrap()).unwrap(); let root = app.handle(RequestData::new("GET", "/s/")); - assert_eq!( - root.body, - br#"{"status": "ok", "service": "url-shortener"}"# - ); + assert_eq!(root.status, 200); + assert_eq!(root.body, include_bytes!("../static/index.html")); let asset = app.handle(RequestData::new("GET", "/s/static/app.js")); - assert_eq!(asset.status, 404); - assert!(asset.body.is_empty()); + assert_eq!(asset.status, 200); + assert_eq!(asset.body, include_bytes!("../static/app.js")); + let _ = fs::remove_file(config_path); let _ = fs::remove_file(path); } diff --git a/src/main.rs b/src/main.rs index 22698c7..b72602e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,8 +49,6 @@ fn main() { "Rate limit : {} req/{}s per IP", app.config.rate_limit_requests, app.config.rate_limit_window ); - eprintln!("Production : {}", app.config.production); - for mut request in server.incoming_requests() { let maximum_body = app.max_request_body_bytes(); let declared_too_large = request