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
|
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
|
# Set to true when serving over HTTPS
|
||||||
SECURE_COOKIE=false
|
SECURE_COOKIE=false
|
||||||
PRODUCTION=false
|
PRODUCTION=false
|
||||||
@@ -42,3 +45,17 @@ DB_MIN_CONNECTIONS=1
|
|||||||
|
|
||||||
# ── PostgreSQL (optional, uncomment db service in compose.yml first) ──────────
|
# ── PostgreSQL (optional, uncomment db service in compose.yml first) ──────────
|
||||||
# POSTGRES_PASSWORD=change-me
|
# 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}
|
- JWT_EXPIRY_HOURS=${JWT_EXPIRY_HOURS:-24}
|
||||||
- SECURE_COOKIE=${SECURE_COOKIE:-false}
|
- SECURE_COOKIE=${SECURE_COOKIE:-false}
|
||||||
- PRODUCTION=${PRODUCTION:-false}
|
- PRODUCTION=${PRODUCTION:-false}
|
||||||
|
- ALLOW_REGISTRATION=${ALLOW_REGISTRATION:-true}
|
||||||
- DB_MAX_CONNECTIONS=${DB_MAX_CONNECTIONS:-5}
|
- DB_MAX_CONNECTIONS=${DB_MAX_CONNECTIONS:-5}
|
||||||
- DB_MIN_CONNECTIONS=${DB_MIN_CONNECTIONS:-1}
|
- DB_MIN_CONNECTIONS=${DB_MIN_CONNECTIONS:-1}
|
||||||
# Jellyfin — all three required for schedule generation
|
# Jellyfin — all three required for schedule generation
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ pub struct Config {
|
|||||||
/// Whether the application is running in production mode
|
/// Whether the application is running in production mode
|
||||||
pub is_production: bool,
|
pub is_production: bool,
|
||||||
|
|
||||||
|
/// Whether new user registration is open. Set ALLOW_REGISTRATION=false to lock down.
|
||||||
|
pub allow_registration: bool,
|
||||||
|
|
||||||
// Jellyfin media provider
|
// Jellyfin media provider
|
||||||
pub jellyfin_base_url: Option<String>,
|
pub jellyfin_base_url: Option<String>,
|
||||||
pub jellyfin_api_key: Option<String>,
|
pub jellyfin_api_key: Option<String>,
|
||||||
@@ -100,6 +103,10 @@ impl Config {
|
|||||||
.map(|v| v.to_lowercase() == "production" || v == "1" || v == "true")
|
.map(|v| v.to_lowercase() == "production" || v == "1" || v == "true")
|
||||||
.unwrap_or(false);
|
.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_base_url = env::var("JELLYFIN_BASE_URL").ok();
|
||||||
let jellyfin_api_key = env::var("JELLYFIN_API_KEY").ok();
|
let jellyfin_api_key = env::var("JELLYFIN_API_KEY").ok();
|
||||||
let jellyfin_user_id = env::var("JELLYFIN_USER_ID").ok();
|
let jellyfin_user_id = env::var("JELLYFIN_USER_ID").ok();
|
||||||
@@ -123,6 +130,7 @@ impl Config {
|
|||||||
jwt_audience,
|
jwt_audience,
|
||||||
jwt_expiry_hours,
|
jwt_expiry_hours,
|
||||||
is_production,
|
is_production,
|
||||||
|
allow_registration,
|
||||||
jellyfin_base_url,
|
jellyfin_base_url,
|
||||||
jellyfin_api_key,
|
jellyfin_api_key,
|
||||||
jellyfin_user_id,
|
jellyfin_user_id,
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ async fn register(
|
|||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Json(payload): Json<RegisterRequest>,
|
Json(payload): Json<RegisterRequest>,
|
||||||
) -> Result<impl IntoResponse, ApiError> {
|
) -> 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 password_hash = infra::auth::hash_password(payload.password.as_ref());
|
||||||
|
|
||||||
let user = state
|
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::dto::ConfigResponse;
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
@@ -6,8 +8,8 @@ pub fn router() -> Router<AppState> {
|
|||||||
Router::new().route("/", get(get_config))
|
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 {
|
Json(ConfigResponse {
|
||||||
allow_registration: true, // Default to true for template
|
allow_registration: config.allow_registration,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ services:
|
|||||||
- JWT_EXPIRY_HOURS=24
|
- JWT_EXPIRY_HOURS=24
|
||||||
- SECURE_COOKIE=false # set to true when serving over HTTPS
|
- SECURE_COOKIE=false # set to true when serving over HTTPS
|
||||||
- PRODUCTION=false
|
- PRODUCTION=false
|
||||||
|
- ALLOW_REGISTRATION=true # set to false to disable new user registration
|
||||||
# Database pool
|
# Database pool
|
||||||
- DB_MAX_CONNECTIONS=5
|
- DB_MAX_CONNECTIONS=5
|
||||||
- DB_MIN_CONNECTIONS=1
|
- DB_MIN_CONNECTIONS=1
|
||||||
|
|||||||
Reference in New Issue
Block a user