# Bot-Side Report Handling Compatibility Assessment (Batch 2) ## 1. Bot Report Format Details **Location:** `crates/cgcx-bot/src/main.rs:1720-1730` The bot constructs the forwarded report message as follows (HTML parse mode): ```html [ NEW REPORT ] #{report_id} CXID: {cxid} Reporter: {reporter_id} Owner: {content.user_id} Uploaded: {YYYY-MM-DD HH:MM} Files: 1 ``` **Notes:** - `report_id` is the auto-incremented SQLite row ID returned by `ReportRepo::insert`. - `cxid` is extracted from the user’s message via `extract_cxid(text)`. - `reporter_id` is the Telegram `user_id` of the person reporting. - `content.user_id` is the owner/uploader of the reported content. - `Files: 1` is **hardcoded** to `1` regardless of actual file count. --- ## 2. Inline Keyboard Layout Details **Location:** `crates/cgcx-bot/src/main.rs:1732-1742` The inline keyboard is a 2×2 grid: | Row | Button Label | Callback Data | |-----|--------------|---------------| | 1 | `[ Rmv + Ban ]` | `v1:admin:delblk:{report_id}` | | 1 | `[ Delete Only ]` | `v1:admin:del:{report_id}` | | 2 | `[ Blacklist Only ]` | `v1:admin:blk:{report_id}` | | 2 | `[ Ignore ]` | `v1:admin:ign:{report_id}` | These callbacks are handled by `handle_admin_callback` (`main.rs:1745`), which: - Validates the user is an admin in the review group chat. - Looks up the `Report` by `report_id`. - Performs the requested moderation action and resolves the report. --- ## 3. `ReportRepo::insert` Signature & Behavior **Location:** `crates/cgcx-db/src/repos.rs:426-433` ```rust pub async fn insert( &self, content_id: &ContentId, reporter_user_id: i64, reason: &str, ) -> Result { let conn = self.conn.lock().await; conn.execute( "INSERT INTO reports (content_id, reporter_user_id, reason) VALUES (?1, ?2, ?3)", params![content_id.as_str(), reporter_user_id, reason], )?; Ok(conn.last_insert_rowid()) } ``` - Returns `last_insert_rowid()` (the generated `report_id`). - No additional validation inside the repo; the caller is responsible for ensuring `content_id` exists. --- ## 4. Compatibility Assessment for Web Reports (`reporter_user_id = 0`) ### ❌ Critical Issue: Foreign Key Constraint Violation **Schema:** `migrations/001_init.sql:39-50` ```sql CREATE TABLE reports ( id INTEGER PRIMARY KEY AUTOINCREMENT, content_id TEXT NOT NULL REFERENCES contents(id), reporter_user_id INTEGER NOT NULL REFERENCES users(id), reason TEXT NOT NULL, ... ); ``` **Foreign key enforcement is enabled:** - `cgcx-db/src/lib.rs:21` and `:33` both execute `PRAGMA foreign_keys = ON;`. **Impact:** - Passing `reporter_user_id = 0` to `ReportRepo::insert` will **fail with a foreign key constraint violation** because there is no user row with `id = 0`. - There is **no anonymous/web user seed** or special-case handling anywhere in the codebase. ### ⚠️ Secondary Issue: Reason Field Semantics **Location:** `crates/cgcx-bot/src/main.rs:1719` ```rust let report_id = report_repo.insert(&content_id, reporter_id, text).await?; ``` - In the bot flow, `text` is the raw user message (a cxid or share link). The bot stores this raw cxid/link as the `reason`. - A web report API would naturally accept separate `cxid` and `reason` fields. If the server replicates the bot behavior by passing the cxid as the reason, the database will contain a machine ID instead of a human-readable report reason. - **Recommendation:** The server should pass the user-supplied human reason (or a placeholder like `"Web report"`) to `ReportRepo::insert`, not the cxid. ### ⚠️ Tertiary Issue: Reporter Display - The report message displays `Reporter: {reporter_id}`. If `reporter_id = 0`, moderators will see `Reporter: 0`, which is indistinguishable from a real user and not user-friendly. - **Recommendation:** Consider creating a dedicated anonymous/web reporter user (e.g., `id = 0` or a negative sentinel) with a recognizable username, or adjust the report template to show `"Web"` / `"Anonymous"` when the reporter is not a Telegram user. --- ## 5. Recommendations 1. **Create an anonymous/web reporter user row** (e.g., `id = 0` or a dedicated negative ID) in the `users` table before any web report can be inserted, **OR** relax the `NOT NULL` / foreign-key constraint on `reporter_user_id` (requires migration). 2. **Update the server-side report endpoint** to accept a separate `reason` field and pass it to `ReportRepo::insert`, rather than mirroring the bot’s cxid-as-reason behavior. 3. **Align the report message template** for web reports so that the `Reporter:` line is meaningful (e.g., `"Reporter: Web"` or `"Reporter: Anonymous"`) instead of a raw numeric `0`. 4. **Optional:** Fix the hardcoded `Files: 1` in the bot template to use the actual file count from `ContentFileRepo::list_by_content`, so the server report and bot report are consistent. --- ## 6. Summary Table | Aspect | Bot Behavior | Web Report Compatibility | Risk | |--------|--------------|--------------------------|------| | Message format | HTML with hardcoded `Files: 1` | Server can replicate easily | Low (cosmetic) | | Keyboard layout | 2×2 grid with `v1:admin:*:{id}` | Fully compatible | None | | `ReportRepo::insert` | Accepts any `i64` for `reporter_user_id` | **Fails at runtime for `0`** | **High** | | `reason` field | Stores raw cxid/link | Misleading if replicated verbatim | Medium | | Reporter display | Raw numeric ID | `0` is uninformative | Low |