feat: add support for user registration toggle and Traefik integration in Docker setup
This commit is contained in:
17
.env.example
17
.env.example
@@ -13,6 +13,9 @@ COOKIE_SECRET=change-me-must-be-at-least-64-characters-long-for-production!!
|
||||
|
||||
JWT_EXPIRY_HOURS=24
|
||||
|
||||
# Set to false to disable new user registration (existing users can still log in)
|
||||
ALLOW_REGISTRATION=true
|
||||
|
||||
# Set to true when serving over HTTPS
|
||||
SECURE_COOKIE=false
|
||||
PRODUCTION=false
|
||||
@@ -42,3 +45,17 @@ DB_MIN_CONNECTIONS=1
|
||||
|
||||
# ── PostgreSQL (optional, uncomment db service in compose.yml first) ──────────
|
||||
# POSTGRES_PASSWORD=change-me
|
||||
|
||||
# ── Traefik (only needed with compose.traefik.yml) ────────────────────────────
|
||||
# External Docker network Traefik is attached to
|
||||
TRAEFIK_NETWORK=traefik_proxy
|
||||
# Traefik entrypoint (usually websecure for HTTPS, web for HTTP)
|
||||
TRAEFIK_ENTRYPOINT=websecure
|
||||
# Cert resolver defined in your Traefik static config
|
||||
TRAEFIK_CERT_RESOLVER=letsencrypt
|
||||
# Public hostnames routed by Traefik
|
||||
FRONTEND_HOST=tv.example.com
|
||||
BACKEND_HOST=tv-api.example.com
|
||||
# When using Traefik, update these to the public URLs:
|
||||
# NEXT_PUBLIC_API_URL=https://tv-api.example.com/api/v1
|
||||
# CORS_ALLOWED_ORIGINS=https://tv.example.com
|
||||
|
||||
49
compose.traefik.yml
Normal file
49
compose.traefik.yml
Normal file
@@ -0,0 +1,49 @@
|
||||
# Traefik integration overlay.
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f compose.yml -f compose.traefik.yml up -d --build
|
||||
#
|
||||
# Assumes Traefik is already running on your host with an external Docker
|
||||
# network. Add these variables to your .env (see .env.example):
|
||||
#
|
||||
# TRAEFIK_NETWORK name of the external Traefik network (default: traefik_proxy)
|
||||
# TRAEFIK_ENTRYPOINT Traefik entrypoint name (default: websecure)
|
||||
# TRAEFIK_CERT_RESOLVER cert resolver name for TLS (default: letsencrypt)
|
||||
# FRONTEND_HOST public hostname for the frontend e.g. tv.example.com
|
||||
# BACKEND_HOST public hostname for the backend API e.g. tv-api.example.com
|
||||
#
|
||||
# Remember: NEXT_PUBLIC_API_URL in .env must be the *public* backend URL,
|
||||
# e.g. https://tv-api.example.com/api/v1, and you must rebuild after changing it.
|
||||
|
||||
services:
|
||||
|
||||
backend:
|
||||
ports: [] # Traefik handles ingress; no direct port exposure needed
|
||||
networks:
|
||||
- default
|
||||
- traefik
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=${TRAEFIK_NETWORK:-traefik_proxy}"
|
||||
- "traefik.http.routers.ktv-backend.rule=Host(`${BACKEND_HOST}`)"
|
||||
- "traefik.http.routers.ktv-backend.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
- "traefik.http.routers.ktv-backend.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt}"
|
||||
- "traefik.http.services.ktv-backend.loadbalancer.server.port=3000"
|
||||
|
||||
frontend:
|
||||
ports: []
|
||||
networks:
|
||||
- default
|
||||
- traefik
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=${TRAEFIK_NETWORK:-traefik_proxy}"
|
||||
- "traefik.http.routers.ktv-frontend.rule=Host(`${FRONTEND_HOST}`)"
|
||||
- "traefik.http.routers.ktv-frontend.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
- "traefik.http.routers.ktv-frontend.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt}"
|
||||
- "traefik.http.services.ktv-frontend.loadbalancer.server.port=3001"
|
||||
|
||||
networks:
|
||||
traefik:
|
||||
external: true
|
||||
name: ${TRAEFIK_NETWORK:-traefik_proxy}
|
||||
@@ -18,6 +18,7 @@ services:
|
||||
- JWT_EXPIRY_HOURS=${JWT_EXPIRY_HOURS:-24}
|
||||
- SECURE_COOKIE=${SECURE_COOKIE:-false}
|
||||
- PRODUCTION=${PRODUCTION:-false}
|
||||
- ALLOW_REGISTRATION=${ALLOW_REGISTRATION:-true}
|
||||
- DB_MAX_CONNECTIONS=${DB_MAX_CONNECTIONS:-5}
|
||||
- DB_MIN_CONNECTIONS=${DB_MIN_CONNECTIONS:-1}
|
||||
# Jellyfin — all three required for schedule generation
|
||||
|
||||
@@ -32,6 +32,9 @@ pub struct Config {
|
||||
/// Whether the application is running in production mode
|
||||
pub is_production: bool,
|
||||
|
||||
/// Whether new user registration is open. Set ALLOW_REGISTRATION=false to lock down.
|
||||
pub allow_registration: bool,
|
||||
|
||||
// Jellyfin media provider
|
||||
pub jellyfin_base_url: Option<String>,
|
||||
pub jellyfin_api_key: Option<String>,
|
||||
@@ -100,6 +103,10 @@ impl Config {
|
||||
.map(|v| v.to_lowercase() == "production" || v == "1" || v == "true")
|
||||
.unwrap_or(false);
|
||||
|
||||
let allow_registration = env::var("ALLOW_REGISTRATION")
|
||||
.map(|v| !(v == "false" || v == "0"))
|
||||
.unwrap_or(true);
|
||||
|
||||
let jellyfin_base_url = env::var("JELLYFIN_BASE_URL").ok();
|
||||
let jellyfin_api_key = env::var("JELLYFIN_API_KEY").ok();
|
||||
let jellyfin_user_id = env::var("JELLYFIN_USER_ID").ok();
|
||||
@@ -123,6 +130,7 @@ impl Config {
|
||||
jwt_audience,
|
||||
jwt_expiry_hours,
|
||||
is_production,
|
||||
allow_registration,
|
||||
jellyfin_base_url,
|
||||
jellyfin_api_key,
|
||||
jellyfin_user_id,
|
||||
|
||||
@@ -73,6 +73,10 @@ async fn register(
|
||||
State(state): State<AppState>,
|
||||
Json(payload): Json<RegisterRequest>,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
if !state.config.allow_registration {
|
||||
return Err(ApiError::Forbidden("Registration is disabled".to_string()));
|
||||
}
|
||||
|
||||
let password_hash = infra::auth::hash_password(payload.password.as_ref());
|
||||
|
||||
let user = state
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use axum::{Json, Router, routing::get};
|
||||
use axum::{Json, Router, extract::State, routing::get};
|
||||
use std::sync::Arc;
|
||||
use crate::config::Config;
|
||||
use crate::dto::ConfigResponse;
|
||||
use crate::state::AppState;
|
||||
|
||||
@@ -6,8 +8,8 @@ pub fn router() -> Router<AppState> {
|
||||
Router::new().route("/", get(get_config))
|
||||
}
|
||||
|
||||
async fn get_config() -> Json<ConfigResponse> {
|
||||
async fn get_config(State(config): State<Arc<Config>>) -> Json<ConfigResponse> {
|
||||
Json(ConfigResponse {
|
||||
allow_registration: true, // Default to true for template
|
||||
allow_registration: config.allow_registration,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ services:
|
||||
- JWT_EXPIRY_HOURS=24
|
||||
- SECURE_COOKIE=false # set to true when serving over HTTPS
|
||||
- PRODUCTION=false
|
||||
- ALLOW_REGISTRATION=true # set to false to disable new user registration
|
||||
# Database pool
|
||||
- DB_MAX_CONNECTIONS=5
|
||||
- DB_MIN_CONNECTIONS=1
|
||||
|
||||
Reference in New Issue
Block a user