diff --git a/ROADMAP.md b/ROADMAP.md deleted file mode 100644 index e08269c..0000000 --- a/ROADMAP.md +++ /dev/null @@ -1,11 +0,0 @@ -## DISCLAIMER; not for AI-simply for the dev, kimi can ignore this - -- replace all em dashes with normal dashes (-) -- on the home page, when hovering over the top part that says "cg", expand into "cannibal girls" with "annibal" "irls" expanded parts being shown with the same slightly green gradient used elsewhere -- do not make the software univerally tied to cg.cx, possibly switch to other cg.xx.xx or cg.xxx/cg.xx domain -- add other temp files from ./data dir to .gitignore -- resolve all issues that i might discover later -- add sign-in/sign-up on web -- add direct upload on web -- add extreme stability & error handling, so neither bot nor server can crash anymore -- do not expose API endpoints, ip's ect. in web/telegram diff --git a/config/default.example.toml b/config/default.example.toml index 79417d6..c7646fb 100644 --- a/config/default.example.toml +++ b/config/default.example.toml @@ -6,6 +6,10 @@ # Example: CGCX_TELEGRAM__BOT_TOKEN=your_token # ============================================================================ +# Path to the SQLite database file. Relative paths are resolved from the +# current working directory. Both the server and bot MUST use the same file. +database_path = "data/db.sqlite" + # ---------------------------------------------------------------------------- # Content settings # ---------------------------------------------------------------------------- diff --git a/crates/cgcx-bot/src/main.rs b/crates/cgcx-bot/src/main.rs index c0aa138..06a0702 100644 --- a/crates/cgcx-bot/src/main.rs +++ b/crates/cgcx-bot/src/main.rs @@ -150,9 +150,12 @@ async fn run_bot() { let config = Arc::new(Config::load().expect("Failed to load config")); - tokio::fs::create_dir_all("data").await.ok(); - - let db = Arc::new(Database::open("data/db.sqlite").expect("Failed to open database")); + let db_path = std::path::PathBuf::from(&config.database_path); + if let Some(parent) = db_path.parent() { + tokio::fs::create_dir_all(parent).await.ok(); + } + let db = Arc::new(Database::open(&db_path).expect("Failed to open database")); + info!("Bot database opened at: {:?}", std::fs::canonicalize(&db_path).unwrap_or_else(|_| db_path.clone())); db.run_migrations().await.expect("Failed to run migrations"); let storage = Arc::new(CgcxStorage::new(config.storage.paths.clone())); @@ -741,10 +744,11 @@ async fn finalize_upload( } } - let content_id = ContentId::generate(); + let mut content_id = ContentId::generate(); let repo = ContentRepo::new(ctx.db.conn()); let mut attempts = 0; while repo.get(&content_id).await?.is_some() && attempts < 5 { + content_id = ContentId::generate(); attempts += 1; } diff --git a/crates/cgcx-config/src/lib.rs b/crates/cgcx-config/src/lib.rs index f772bdb..c695e28 100644 --- a/crates/cgcx-config/src/lib.rs +++ b/crates/cgcx-config/src/lib.rs @@ -14,6 +14,12 @@ pub struct Config { pub rate_limiting: RateLimitConfig, pub logging: LoggingConfig, pub frontend: FrontendConfig, + #[serde(default = "default_db_path")] + pub database_path: String, +} + +fn default_db_path() -> String { + "data/db.sqlite".to_string() } #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/crates/cgcx-server/src/main.rs b/crates/cgcx-server/src/main.rs index 94b24d7..8fc19c3 100644 --- a/crates/cgcx-server/src/main.rs +++ b/crates/cgcx-server/src/main.rs @@ -137,9 +137,12 @@ async fn main() -> cgcx_core::Result<()> { let config = Arc::new(Config::load()?); config.validate()?; - tokio::fs::create_dir_all("data").await.ok(); - - let db = Arc::new(Database::open("data/db.sqlite")?); + let db_path = std::path::PathBuf::from(&config.database_path); + if let Some(parent) = db_path.parent() { + tokio::fs::create_dir_all(parent).await.ok(); + } + let db = Arc::new(Database::open(&db_path)?); + info!("Server database opened at: {:?}", std::fs::canonicalize(&db_path).unwrap_or_else(|_| db_path.clone())); db.run_migrations().await?; let storage = Arc::new(Storage::new(config.storage.paths.clone())); @@ -186,7 +189,7 @@ async fn main() -> cgcx_core::Result<()> { .expect("invalid password rate limit config"); let password_route = Router::new() - .route("/api/content/{cxid}/verify-password", post(verify_password)) + .route("/api/content/:cxid/verify-password", post(verify_password)) .layer(tower_governor::GovernorLayer { config: Arc::new(password_governor_conf), }); @@ -231,8 +234,8 @@ async fn main() -> cgcx_core::Result<()> { let app = Router::new() .route("/api/health", get(health)) - .route("/api/content/{cxid}", get(get_metadata)) - .route("/api/content/{cxid}/file/{file_idx}", get(serve_file)) + .route("/api/content/:cxid", get(get_metadata)) + .route("/api/content/:cxid/file/:file_idx", get(serve_file)) .merge(password_route) .nest_service("/assets", static_service) .fallback(fallback) @@ -283,7 +286,7 @@ async fn fallback(uri: axum::http::Uri) -> Response { let path = uri.path(); tracing::info!("fallback: path={}", path); if path.starts_with("/api/") { - return (StatusCode::NOT_FOUND, Json(serde_json::json!({"error": "Not found"}))).into_response(); + return (StatusCode::NOT_FOUND, Json(serde_json::json!({"error": "Not found", "source": "fallback"}))).into_response(); } match tokio::fs::read_to_string("frontend/dist/index.html").await { Ok(html) => (StatusCode::OK, [(header::CONTENT_TYPE, "text/html")], html).into_response(),