From 89fddc3295e76c2d34e3e4c8e3558a1a2e15a56f Mon Sep 17 00:00:00 2001 From: Carl Vitullo Date: Fri, 25 Jul 2025 19:30:02 -0400 Subject: [PATCH 01/19] Add user_threads database table and model for persistent modlog threading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create migration for user_threads table (user_id, guild_id, thread_id, created_at) - Add unique composite index on user_id, guild_id - Implement userThreads.server.ts model with CRUD operations - Generate updated TypeScript database types - Update notes with comprehensive refactor plan This supports the modlog refactor to use persistent user threads instead of per-message threads. πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .gitignore | 1 + app/db.d.ts | 8 ++ app/models/userThreads.server.ts | 69 +++++++++ migrations/20250725192908_user_threads.ts | 22 +++ notes/2025-07-25_1-track-changes.md | 11 ++ ...025-07-25_1_database-structure-analysis.md | 136 ++++++++++++++++++ notes/2025-07-25_2-modlog-refactor-plan.md | 55 +++++++ 7 files changed, 302 insertions(+) create mode 100644 app/models/userThreads.server.ts create mode 100644 migrations/20250725192908_user_threads.ts create mode 100644 notes/2025-07-25_1-track-changes.md create mode 100644 notes/2025-07-25_1_database-structure-analysis.md create mode 100644 notes/2025-07-25_2-modlog-refactor-plan.md diff --git a/.gitignore b/.gitignore index 8449a5d..878637b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ k8s-context tsconfig.tsbuildinfo .react-router tailwind.css +userInfoCache.json diff --git a/app/db.d.ts b/app/db.d.ts index 261c70d..9039fe2 100644 --- a/app/db.d.ts +++ b/app/db.d.ts @@ -61,6 +61,13 @@ export interface Users { id: string; } +export interface UserThreads { + created_at: Generated; + guild_id: string; + thread_id: string; + user_id: string; +} + export interface DB { channel_info: ChannelInfo; guild_subscriptions: GuildSubscriptions; @@ -68,5 +75,6 @@ export interface DB { message_stats: MessageStats; sessions: Sessions; tickets_config: TicketsConfig; + user_threads: UserThreads; users: Users; } diff --git a/app/models/userThreads.server.ts b/app/models/userThreads.server.ts new file mode 100644 index 0000000..9792781 --- /dev/null +++ b/app/models/userThreads.server.ts @@ -0,0 +1,69 @@ +import type { DB } from "#~/db.server"; +import db from "#~/db.server"; +import { log, trackPerformance } from "#~/helpers/observability"; + +export type UserThread = DB["user_threads"]; + +export async function getUserThread(userId: string, guildId: string): Promise { + return trackPerformance( + "getUserThread", + async () => { + log("debug", "UserThread", "Fetching user thread", { userId, guildId }); + + const thread = await db + .selectFrom("user_threads") + .selectAll() + .where("user_id", "=", userId) + .where("guild_id", "=", guildId) + .executeTakeFirst(); + + log("debug", "UserThread", thread ? "Found user thread" : "No user thread found", { userId, guildId, threadId: thread?.thread_id }); + return thread; + }, + { userId, guildId } + ); +} + +export async function createUserThread(userId: string, guildId: string, threadId: string): Promise { + return trackPerformance( + "createUserThread", + async () => { + log("info", "UserThread", "Creating user thread", { userId, guildId, threadId }); + + const userThread = { + user_id: userId, + guild_id: guildId, + thread_id: threadId, + created_at: new Date().toISOString(), + }; + + await db + .insertInto("user_threads") + .values(userThread) + .execute(); + + log("info", "UserThread", "Created user thread", { userId, guildId, threadId }); + return userThread; + }, + { userId, guildId, threadId } + ); +} + +export async function updateUserThread(userId: string, guildId: string, threadId: string): Promise { + return trackPerformance( + "updateUserThread", + async () => { + log("info", "UserThread", "Updating user thread", { userId, guildId, threadId }); + + await db + .updateTable("user_threads") + .set({ thread_id: threadId }) + .where("user_id", "=", userId) + .where("guild_id", "=", guildId) + .execute(); + + log("info", "UserThread", "Updated user thread", { userId, guildId, threadId }); + }, + { userId, guildId, threadId } + ); +} \ No newline at end of file diff --git a/migrations/20250725192908_user_threads.ts b/migrations/20250725192908_user_threads.ts new file mode 100644 index 0000000..c83f370 --- /dev/null +++ b/migrations/20250725192908_user_threads.ts @@ -0,0 +1,22 @@ +import type { Kysely } from "kysely"; + +export async function up(db: Kysely): Promise { + await db.schema + .createTable("user_threads") + .addColumn("user_id", "text", (c) => c.notNull()) + .addColumn("guild_id", "text", (c) => c.notNull()) + .addColumn("thread_id", "text", (c) => c.notNull()) + .addColumn("created_at", "datetime", (c) => c.defaultTo("CURRENT_TIMESTAMP").notNull()) + .execute(); + + await db.schema + .createIndex("user_threads_pk") + .on("user_threads") + .columns(["user_id", "guild_id"]) + .unique() + .execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable("user_threads").execute(); +} \ No newline at end of file diff --git a/notes/2025-07-25_1-track-changes.md b/notes/2025-07-25_1-track-changes.md new file mode 100644 index 0000000..804690f --- /dev/null +++ b/notes/2025-07-25_1-track-changes.md @@ -0,0 +1,11 @@ +Currently we post a new message to the designated "mod log" channel for every new message reported, logging who it was and creating a thread to discuss the message. Historically I've used the search box to track history, e.g. mentions: in:mod-log, but recently that's been breaking sometimes (perhaps as they optimize/break search indices). + +We could instead move to a model where the entire moderation history for a user is captured in a single thread. This actually works really well for a couple of other benefits, like making this history easily discoverable by programmatic means without storing a complete duplicate of the records in our own database. We could store a thread lookup alongside a user record, and use that to retrieve message history for summary. + +let's not do this but capturing it +This change would mean a database record like + +user_id | guild_id | thread_id | created_at | expires (?) +Behavior within the thread would be similar to now, with a thread consisting of member reports and moderator actions. We should add in logs for timeouts as well, and try to capture kicks/bans not initiated through Euno features. + +Outside of the thread, we should post a new top-level message linking back to the thread when any new reports come in. Perhaps it would make sense to keep the same "new report" message format, and forward it back to the top level channel. diff --git a/notes/2025-07-25_1_database-structure-analysis.md b/notes/2025-07-25_1_database-structure-analysis.md new file mode 100644 index 0000000..23a7e2f --- /dev/null +++ b/notes/2025-07-25_1_database-structure-analysis.md @@ -0,0 +1,136 @@ +# Database Structure Analysis - 2025-07-25 + +## Overview +This codebase uses **Kysely** as the TypeScript query builder with **SQLite** as the database engine. The project follows a clear pattern for database management with migrations, models, and type definitions. + +## Key Files and Structure + +### Database Configuration +- **`/app/db.server.ts`** - Main database connection using Kysely with SQLite dialect +- **`/kysely.config.ts`** - Migration configuration using kysely-ctl +- **`/app/db.d.ts`** - Auto-generated TypeScript type definitions for all tables + +### Models Directory +All database models are in `/app/models/` with `.server.ts` suffix: +- `user.server.ts` - User management functions +- `guilds.server.ts` - Discord guild management +- `activity.server.ts` - Message analytics and statistics +- `session.server.ts` - Session management +- `stripe.server.ts` - Payment integration +- `subscriptions.server.ts` - Subscription management +- `discord.server.ts` - Discord bot specific functions + +### Migrations Directory +Located at `/migrations/` with timestamp-prefixed files following pattern `YYYYMMDDHHMMSS_description.ts` + +## Database Schema (Current Tables) + +Based on `/app/db.d.ts`, current tables are: + +1. **users** + - `id` (uuid, primary key) + - `email` (text, nullable) + - `externalId` (text, not null) - Discord user ID + - `authProvider` (text, defaults to "discord") + +2. **sessions** + - `id` (uuid, primary key) + - `data` (json) + - `expires` (datetime) + +3. **guilds** + - `id` (text, primary key) - Discord guild ID + - `settings` (json) - Guild configuration + +4. **message_stats** + - `author_id` (text) + - `channel_id` (text) + - `channel_category` (text, nullable) + - `guild_id` (text) + - `message_id` (text, nullable) + - `recipient_id` (text, nullable) + - `sent_at` (number) - Unix timestamp + - `word_count`, `char_count`, `react_count` (numbers) + - `code_stats`, `link_stats` (json) + +5. **channel_info** + - `id` (text, nullable) + - `name` (text, nullable) + - `category` (text, nullable) + +6. **tickets_config** + - `message_id` (text, primary key) + - `channel_id` (text, nullable) + - `role_id` (text) + +7. **guild_subscriptions** + - `guild_id` (text, primary key) + - `stripe_customer_id`, `stripe_subscription_id` (text, nullable) + - `product_tier` (text, defaults to "free") + - `status` (text, defaults to "active") + - `current_period_end` (datetime, nullable) + - `created_at`, `updated_at` (datetime, auto-generated) + +## Patterns to Follow for New Tables + +### Migration Pattern +```typescript +import type { Kysely } from "kysely"; + +export async function up(db: Kysely): Promise { + await db.schema + .createTable("table_name") + .addColumn("id", "uuid", (c) => c.primaryKey().notNull()) + .addColumn("created_at", "datetime", (c) => c.defaultTo("CURRENT_TIMESTAMP")) + // ... other columns + .execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable("table_name").execute(); +} +``` + +### Model Pattern +```typescript +import type { DB } from "#~/db.server"; +import db from "#~/db.server"; +import { log, trackPerformance } from "#~/helpers/observability"; + +export type TableType = DB["table_name"]; + +export async function getById(id: string) { + return trackPerformance( + "getById", + async () => { + log("debug", "TableName", "Fetching by ID", { id }); + + const result = await db + .selectFrom("table_name") + .selectAll() + .where("id", "=", id) + .executeTakeFirst(); + + log("debug", "TableName", result ? "Found" : "Not found", { id }); + return result; + }, + { id } + ); +} +``` + +### Key Patterns +1. **Observability**: All database operations wrapped with `trackPerformance()` and include `log()` calls +2. **Type Safety**: Export table types from models using `DB["table_name"]` +3. **Async/Await**: All database operations are async +4. **Error Handling**: SQLite errors caught and handled appropriately +5. **Naming**: Snake_case for database columns, camelCase for TypeScript +6. **Primary Keys**: UUIDs for user-related tables, natural keys (like Discord IDs) for external entities + +## For user_threads Table +Based on patterns, a user_threads table should: +- Use `user_id` (uuid) referencing users.id +- Use `thread_id` (text) as Discord thread ID +- Include `created_at`, `updated_at` timestamps +- Follow the established model pattern with observability +- Have a corresponding migration file with timestamp prefix \ No newline at end of file diff --git a/notes/2025-07-25_2-modlog-refactor-plan.md b/notes/2025-07-25_2-modlog-refactor-plan.md new file mode 100644 index 0000000..de1ea57 --- /dev/null +++ b/notes/2025-07-25_2-modlog-refactor-plan.md @@ -0,0 +1,55 @@ +# ModLog Refactor Plan - User-Based Threading + +## Current System Analysis + +### Issues with Current Implementation +- Creates new threads per message via `makeLogThread()` (modLog.ts:43-47) +- Uses message-based caching with `queryReportCache()` +- Thread naming is date-based: `${user.username} – ${format(message.createdAt, "P")}` +- Search history tracking via Discord search is unreliable +- Creates channel clutter with many threads per user + +### Key Functions to Refactor +- `reportUser()`: Main function handling report logic (modLog.ts:50-166) +- `makeLogThread()`: Thread creation logic (modLog.ts:43-47) +- `constructLog()`: Message formatting for initial reports (modLog.ts:181-241) +- Cache system in `reportCache.js` for user-based lookup + +## New System Design + +### Database Schema +```sql +user_threads ( + user_id TEXT, + guild_id TEXT, + thread_id TEXT, + created_at DATETIME, + PRIMARY KEY (user_id, guild_id) +) +``` + +### Threading Strategy +1. **One thread per user per guild** - persistent across all reports +2. **Thread naming**: `${user.username} Moderation History` +3. **Top-level notifications**: New message in mod-log channel linking to user thread +4. **Thread content**: All reports, actions, timeouts, kicks/bans for that user + +### Notification Flow +1. New report comes in +2. Lookup/create persistent user thread +3. Post report details in user thread +4. Post notification in main mod-log channel linking to thread +5. Update any escalation controls in user thread + +## Integration Points +- **Report feature**: Uses `reportUser()` directly +- **Track feature**: Uses `reportUser()` via track commands +- **Automod**: Calls `reportUser()` for automated reports +- **Escalate**: Uses threads created by `reportUser()` + +## Benefits +- Consolidated user history in single discoverable thread +- Programmatic access via thread lookup table +- Reduced channel clutter +- Better historical context for moderation decisions +- More reliable than Discord search \ No newline at end of file From 70f61b5cc7b96d9b241fdc712e3eaf8c85c48b41 Mon Sep 17 00:00:00 2001 From: Carl Vitullo Date: Fri, 25 Jul 2025 19:45:10 -0400 Subject: [PATCH 02/19] Refactor modLog to use persistent user threads instead of per-message threads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major changes: - Replace makeLogThread() with getOrCreateUserThread() for persistent threads - Update reportUser() to lookup/create user threads from database - Change thread naming from date-based to "Username Moderation History" - Post notifications in main channel that link to user threads - Send detailed reports + individual messages to user threads - Update both cached and new report flows to use persistent threading This consolidates all moderation history for a user into a single discoverable thread per guild, reducing channel clutter and improving historical context. πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- app/helpers/modLog.ts | 85 +++++++++++++++---- app/models/userThreads.server.ts | 79 +++++++++++------ migrations/20250725192908_user_threads.ts | 10 ++- notes/2025-07-04_1.md | 31 +++++-- ...4_1_authorization-architecture-analysis.md | 29 +++++-- ...2_major-features-observability-analysis.md | 49 ++++++++++- ..._3_observability-implementation-summary.md | 41 +++++++-- ...025-07-25_1_database-structure-analysis.md | 28 ++++-- notes/2025-07-25_2-modlog-refactor-plan.md | 17 ++-- 9 files changed, 291 insertions(+), 78 deletions(-) diff --git a/app/helpers/modLog.ts b/app/helpers/modLog.ts index 7d656fe..37c6c3e 100644 --- a/app/helpers/modLog.ts +++ b/app/helpers/modLog.ts @@ -26,6 +26,11 @@ import { } from "#~/helpers/discord"; import { truncateMessage } from "#~/helpers/string"; import { escalationControls } from "#~/helpers/escalate"; +import { + getUserThread, + createUserThread, + updateUserThread, +} from "#~/models/userThreads.server"; const ReadableReasons: Record = { [ReportReasons.anonReport]: "Reported anonymously", @@ -40,12 +45,57 @@ interface Reported { latestReport?: Message; } -const makeLogThread = (message: Message, user: User) => { +const makeUserThread = (message: Message, user: User) => { return message.startThread({ - name: `${user.username} – ${format(message.createdAt, "P")}`, + name: `${user.username} Moderation History`, }); }; +const getOrCreateUserThread = async (message: Message, user: User) => { + const { guild } = message; + if (!guild) throw new Error("Message has no guild"); + + // Check if we already have a thread for this user + const existingThread = await getUserThread(user.id, guild.id); + + if (existingThread) { + try { + // Verify the thread still exists and is accessible + const thread = await guild.channels.fetch(existingThread.thread_id); + if (thread?.isThread()) { + return thread; + } + } catch (error) { + console.log( + "[getOrCreateUserThread] Existing thread not accessible, will create new one:", + error, + ); + } + } + + // Create new thread and store in database + const { modLog: modLogId } = await fetchSettings(guild.id, [SETTINGS.modLog]); + const modLog = await guild.channels.fetch(modLogId); + if (!modLog || modLog.type !== ChannelType.GuildText) { + throw new Error("Invalid mod log channel"); + } + + // Create a placeholder message to start the thread from + const placeholder = await modLog.send( + `**${user.username}** Moderation History`, + ); + const thread = await makeUserThread(placeholder, user); + + // Store or update the thread reference + if (existingThread) { + await updateUserThread(user.id, guild.id, thread.id); + } else { + await createUserThread(user.id, guild.id, thread.id); + } + + return thread; +}; + // const warningMessages = new (); export const reportUser = async ({ reason, @@ -74,11 +124,9 @@ export const reportUser = async ({ // If we already logged for ~ this message, post to the existing thread const { logMessage: cachedMessage, logs } = cached; - let thread = cachedMessage.thread; - if (!thread || !cachedMessage.hasThread) { - thread = await makeLogThread(cachedMessage, message.author); - await escalationControls(message, thread, modRoleId); - } + // Get or create persistent user thread + const thread = await getOrCreateUserThread(message, message.author); + await escalationControls(message, thread, modRoleId); if (cached.logs.some((l) => l.message.id === message.id)) { // If we've already logged exactly this message, don't log it again as a @@ -140,25 +188,30 @@ export const reportUser = async ({ console.log("[reportUser]", "new message reported"); + // Get or create persistent user thread first + const thread = await getOrCreateUserThread(message, message.author); + await escalationControls(message, thread, modRoleId); + + // Post notification in main channel linking to user thread + const notificationMessage = await modLog.send({ + content: `New report for <@${message.author.id}> - see discussion in <#${thread.id}>`, + }); + trackReport(notificationMessage, newReport); + + // Send detailed report info to the user thread const logBody = await constructLog({ extra, logs: newLogs, previousWarnings: queryCacheMetadata(message), staff, }); - const warningMessage = await modLog.send(logBody); - trackReport(warningMessage, newReport); - - const thread = await makeLogThread(warningMessage, message.author); - await escalationControls(message, thread, modRoleId); - - const firstReportMessage = makeReportMessage(newReport); - const latestReport = await thread.send(firstReportMessage); + await thread.send(logBody); + const latestReport = await thread.send(makeReportMessage(newReport)); return { warnings: 1, - message: warningMessage, + message: notificationMessage, latestReport, thread, allReportedMessages: newLogs, diff --git a/app/models/userThreads.server.ts b/app/models/userThreads.server.ts index 9792781..b8d1032 100644 --- a/app/models/userThreads.server.ts +++ b/app/models/userThreads.server.ts @@ -4,66 +4,91 @@ import { log, trackPerformance } from "#~/helpers/observability"; export type UserThread = DB["user_threads"]; -export async function getUserThread(userId: string, guildId: string): Promise { +export async function getUserThread(userId: string, guildId: string) { return trackPerformance( "getUserThread", async () => { log("debug", "UserThread", "Fetching user thread", { userId, guildId }); - + const thread = await db .selectFrom("user_threads") .selectAll() .where("user_id", "=", userId) .where("guild_id", "=", guildId) .executeTakeFirst(); - - log("debug", "UserThread", thread ? "Found user thread" : "No user thread found", { userId, guildId, threadId: thread?.thread_id }); + + log( + "debug", + "UserThread", + thread ? "Found user thread" : "No user thread found", + { userId, guildId, threadId: thread?.thread_id }, + ); return thread; }, - { userId, guildId } + { userId, guildId }, ); } -export async function createUserThread(userId: string, guildId: string, threadId: string): Promise { +export async function createUserThread( + userId: string, + guildId: string, + threadId: string, +): Promise { return trackPerformance( "createUserThread", async () => { - log("info", "UserThread", "Creating user thread", { userId, guildId, threadId }); - - const userThread = { - user_id: userId, - guild_id: guildId, - thread_id: threadId, - created_at: new Date().toISOString(), - }; + log("info", "UserThread", "Creating user thread", { + userId, + guildId, + threadId, + }); await db .insertInto("user_threads") - .values(userThread) + .values({ + user_id: userId, + guild_id: guildId, + thread_id: threadId, + }) .execute(); - - log("info", "UserThread", "Created user thread", { userId, guildId, threadId }); - return userThread; + + log("info", "UserThread", "Created user thread", { + userId, + guildId, + threadId, + }); }, - { userId, guildId, threadId } + { userId, guildId, threadId }, ); } -export async function updateUserThread(userId: string, guildId: string, threadId: string): Promise { +export async function updateUserThread( + userId: string, + guildId: string, + threadId: string, +): Promise { return trackPerformance( - "updateUserThread", + "updateUserThread", async () => { - log("info", "UserThread", "Updating user thread", { userId, guildId, threadId }); - + log("info", "UserThread", "Updating user thread", { + userId, + guildId, + threadId, + }); + await db .updateTable("user_threads") .set({ thread_id: threadId }) .where("user_id", "=", userId) .where("guild_id", "=", guildId) .execute(); - - log("info", "UserThread", "Updated user thread", { userId, guildId, threadId }); + + log("info", "UserThread", "Updated user thread", { + userId, + guildId, + threadId, + }); }, - { userId, guildId, threadId } + { userId, guildId, threadId }, ); -} \ No newline at end of file +} diff --git a/migrations/20250725192908_user_threads.ts b/migrations/20250725192908_user_threads.ts index c83f370..abbaf5f 100644 --- a/migrations/20250725192908_user_threads.ts +++ b/migrations/20250725192908_user_threads.ts @@ -4,11 +4,13 @@ export async function up(db: Kysely): Promise { await db.schema .createTable("user_threads") .addColumn("user_id", "text", (c) => c.notNull()) - .addColumn("guild_id", "text", (c) => c.notNull()) + .addColumn("guild_id", "text", (c) => c.notNull()) .addColumn("thread_id", "text", (c) => c.notNull()) - .addColumn("created_at", "datetime", (c) => c.defaultTo("CURRENT_TIMESTAMP").notNull()) + .addColumn("created_at", "datetime", (c) => + c.defaultTo("CURRENT_TIMESTAMP").notNull(), + ) .execute(); - + await db.schema .createIndex("user_threads_pk") .on("user_threads") @@ -19,4 +21,4 @@ export async function up(db: Kysely): Promise { export async function down(db: Kysely): Promise { await db.schema.dropTable("user_threads").execute(); -} \ No newline at end of file +} diff --git a/notes/2025-07-04_1.md b/notes/2025-07-04_1.md index 0b3d178..f529fa3 100644 --- a/notes/2025-07-04_1.md +++ b/notes/2025-07-04_1.md @@ -1,9 +1,11 @@ # Observability Integration Plan - July 4, 2025 ## Project Overview + Systematic integration of observability practices across all major features of the Discord bot application, following the established pattern from the subscription service. ## Current State + - βœ… Observability helpers in `app/helpers/observability.ts` - βœ… Business analytics in `app/helpers/metrics.ts` (Amplitude) - βœ… Subscription service fully instrumented as reference @@ -11,6 +13,7 @@ Systematic integration of observability practices across all major features of t - βœ… Sentry integration for error tracking ## Three-Layer Architecture + 1. **Operational Observability** - System health, performance, debugging 2. **Business Analytics** - User behavior, product insights via Amplitude 3. **Infrastructure Monitoring** - Error tracking, stability via Sentry @@ -18,30 +21,39 @@ Systematic integration of observability practices across all major features of t ## Implementation Phases ### Phase 1: Critical Business Logic (High Priority) + - Discord Bot Gateway & Event Handling -- Message Activity Tracking System +- Message Activity Tracking System - Discord Commands System ### Phase 2: User Experience (Medium Priority) + - User Management & Authentication - Analytics & Dashboard - Guild/Server Management ### Phase 3: Advanced Features (Lower Priority) + - Moderation & Automoderation - Database Operations ## Key Patterns to Follow + ```typescript // Dual-track approach: operational + business analytics -await trackPerformance("operationName", async () => { - log("info", "ServiceName", "Operation description", context); - // ... business logic - // Business analytics tracking -}, { contextData }); +await trackPerformance( + "operationName", + async () => { + log("info", "ServiceName", "Operation description", context); + // ... business logic + // Business analytics tracking + }, + { contextData }, +); ``` ## Success Metrics + - Comprehensive logging across all major features - Performance tracking on critical paths - Business analytics for user behavior @@ -51,18 +63,21 @@ await trackPerformance("operationName", async () => { ## Implementation Progress ### βœ… Phase 1 Completed: Critical Business Logic + - **Discord Bot Gateway & Event Handling**: Added comprehensive logging for bot lifecycle, connection events, error handling, and business analytics - **Message Activity Tracking System**: Enhanced all message processing operations with performance tracking and detailed logging - **Discord Commands System**: Added observability to setup, report, and force-ban commands with success/failure tracking -### 🚧 Phase 2 In Progress: User Experience +### 🚧 Phase 2 In Progress: User Experience + - **User Management & Authentication**: Enhanced user model operations with performance tracking and structured logging - **Analytics & Dashboard**: Pending - **Guild/Server Management**: Pending ### πŸ“ Key Patterns Established + - Dual-track observability: operational + business analytics - Performance tracking with `trackPerformance()` wrapper - Structured logging with consistent context - Business analytics events via Amplitude -- Error tracking with full context \ No newline at end of file +- Error tracking with full context diff --git a/notes/2025-07-04_1_authorization-architecture-analysis.md b/notes/2025-07-04_1_authorization-architecture-analysis.md index da815ea..b5946ab 100644 --- a/notes/2025-07-04_1_authorization-architecture-analysis.md +++ b/notes/2025-07-04_1_authorization-architecture-analysis.md @@ -3,27 +3,30 @@ ## Current Authentication System ### Session Management + - **Dual Session Architecture**: Uses both cookie-based and database-based sessions - Cookie session: Stores minimal data directly in encrypted cookie - Database session: Stores sensitive data (Discord tokens) in database with session ID -- **Session Models**: +- **Session Models**: - `CookieSession`: Lightweight, stores basic info - `DbSession`: Secure storage for Discord tokens and OAuth state - **Security**: Discord tokens never stored in cookies, only in database ### OAuth Flow + - **Discord OAuth Integration**: Complete OAuth2 flow with multiple modes - User flow: Standard user authentication - Bot flow: Bot installation with permissions - Signup flow: New user registration - **State Management**: Uses UUID-based state parameter for CSRF protection -- **Scopes**: +- **Scopes**: - User: `identify email guilds guilds.members.read` - Bot: `identify email guilds guilds.members.read bot applications.commands` ### Current Authorization Patterns #### User Authentication + - **User Model**: Basic user entity with Discord external ID - **Session Functions**: - `getUser()`: Get current user from session @@ -31,17 +34,19 @@ - `requireUserId()`: Get user ID with authentication check #### Guild-Based Authorization + - **Guild Permissions**: Discord permission-based authorization - **Permission Mapping**: - `MOD`: ModerateMembers permission - - `ADMIN`: Administrator permission + - `ADMIN`: Administrator permission - `MANAGER`: ManageChannels, ManageGuild, or ManageRoles permissions - `MANAGE_CHANNELS`: ManageChannels permission - `MANAGE_GUILD`: ManageGuild permission - `MANAGE_ROLES`: ManageRoles permission #### Guild Access Control -- **Guild Filtering**: + +- **Guild Filtering**: - Users can only access guilds where they have `MANAGER` level permissions - Bot must be installed in guild for management access - **Authorization Array**: Each guild has `authz` array with user's permissions @@ -50,6 +55,7 @@ ### Database Schema #### Users Table + ```sql - id: UUID (primary key) - email: text @@ -58,6 +64,7 @@ ``` #### Sessions Table + ```sql - id: UUID (primary key) - data: JSON (session data) @@ -65,12 +72,14 @@ ``` #### Guilds Table + ```sql - id: string (Discord guild ID) - settings: JSON (guild configuration) ``` #### Guild Subscriptions Table + ```sql - guild_id: string (primary key) - stripe_customer_id: text @@ -85,16 +94,19 @@ ### Current Access Control Mechanisms #### Route-Level Protection + - **Auth Layout**: `__auth.tsx` enforces authentication for protected routes - **User Requirement**: Routes under auth layout require valid user session - **Guild Context**: Guild-specific routes include guild ID in URL parameters #### Permission Checking + - **Discord Permissions**: Leverages Discord's native permission system - **Guild Membership**: Validates user membership and permissions in target guild - **Bot Presence**: Requires bot installation for management features #### Subscription-Based Access + - **Product Tiers**: Free vs. Paid tiers - **Feature Gating**: `SubscriptionService.hasFeature()` (currently returns false) - **Subscription Status**: Active/inactive subscription management @@ -102,26 +114,31 @@ ### Current Gaps and Limitations #### Missing Role-Based Access Control + - No internal role system beyond Discord permissions - No fine-grained feature permissions - No user role management interface #### Limited Authorization Middleware + - No declarative permission decorators - No middleware for route-level permission checks - Manual permission validation in loaders #### No Audit Trail + - No logging of permission changes - No access attempt tracking - Limited observability for authorization decisions #### Feature Flag System + - Subscription service exists but feature checking is stubbed - No granular feature control - No A/B testing or gradual rollouts #### Guild-Level Permissions + - Guild settings stored as JSON blob - No structured permission model for guild features - No delegation of permissions to non-admin users @@ -134,15 +151,17 @@ 4. **Feature Access**: Check subscription tier β†’ Validate feature access (stubbed) ### Strengths + - Secure Discord integration with proper token handling - Caching layer for performance - Subscription management foundation - Clean separation of cookie vs. database sessions ### Areas for Extension + - Role-based access control system - Permission middleware and decorators - Feature flag implementation - Audit logging - Guild-level permission delegation -- Fine-grained resource access control \ No newline at end of file +- Fine-grained resource access control diff --git a/notes/2025-07-04_2_major-features-observability-analysis.md b/notes/2025-07-04_2_major-features-observability-analysis.md index 6892eae..3197bed 100644 --- a/notes/2025-07-04_2_major-features-observability-analysis.md +++ b/notes/2025-07-04_2_major-features-observability-analysis.md @@ -1,35 +1,43 @@ # Major Features Analysis for Observability Integration ## Overview + This document identifies the major functional areas of the Discord bot application that would benefit from observability integration, providing detailed analysis of each feature with file locations and integration opportunities. ## Core Application Architecture ### 1. **Discord Bot Gateway & Event Handling** + **Primary Files:** + - `/app/discord/gateway.ts` - Main Discord bot initialization - `/app/discord/client.server.ts` - Discord client setup - `/app/server.ts` - Express server with Discord webhooks **Key Features:** + - Discord bot lifecycle management - Event handling for messages, reactions, threads - WebSocket connection management - Webhook signature verification **Observability Opportunities:** + - Bot uptime and connection health - Event processing rates and latency - WebSocket connection stability - Webhook validation success/failure rates ### 2. **Message Activity Tracking System** + **Primary Files:** + - `/app/discord/activityTracker.ts` - Real-time message processing - `/app/models/activity.server.ts` - Analytics queries and reports - `/app/helpers/messageParsing.ts` - Message content analysis **Key Features:** + - Real-time message statistics collection - Code block detection and analysis - Link extraction and tracking @@ -38,6 +46,7 @@ This document identifies the major functional areas of the Discord bot applicati - User participation metrics **Observability Opportunities:** + - Message processing throughput - Channel activity patterns - User engagement trends @@ -45,63 +54,77 @@ This document identifies the major functional areas of the Discord bot applicati - Message parsing performance ### 3. **User Management & Authentication** + **Primary Files:** + - `/app/models/user.server.ts` - User CRUD operations - `/app/models/session.server.ts` - Session management - `/app/routes/discord-oauth.tsx` - OAuth flow - `/app/routes/auth.tsx` - Authentication routes **Key Features:** + - Discord OAuth integration - User registration and login - Session management - User profile management **Observability Opportunities:** + - Login success/failure rates - Session duration patterns - OAuth conversion rates - User retention metrics ### 4. **Guild/Server Management** + **Primary Files:** + - `/app/models/guilds.server.ts` - Guild configuration - `/app/discord/onboardGuild.ts` - Guild onboarding - `/app/commands/setup.ts` - Initial guild setup **Key Features:** + - Guild registration and configuration - Settings management (JSON-based) - Moderator role assignment - Channel configuration **Observability Opportunities:** + - Guild onboarding completion rates - Configuration change frequency - Active guild counts - Setup command success rates ### 5. **Subscription & Payment System** + **Primary Files:** + - `/app/models/subscriptions.server.ts` - Subscription management (already has observability!) - `/app/models/stripe.server.ts` - Payment processing (stub) - `/app/routes/upgrade.tsx` - Upgrade interface - `/app/routes/payment.success.tsx` - Payment confirmation **Key Features:** + - Free/paid tier management - Subscription lifecycle tracking - Payment processing integration - Feature access control **Observability Opportunities:** + - Subscription conversion rates - Payment success/failure rates - Churn analysis - Feature usage by tier ### 6. **Discord Commands System** + **Primary Files:** + - `/app/commands/setup.ts` - Guild configuration - `/app/commands/report.ts` - Message reporting - `/app/commands/track.tsx` - Activity tracking @@ -109,25 +132,30 @@ This document identifies the major functional areas of the Discord bot applicati - `/app/commands/setupTickets.ts` - Support tickets **Key Features:** + - Slash command handling - Context menu commands - Permission-based access - Command registration and deployment **Observability Opportunities:** + - Command usage frequencies - Command execution success rates - User interaction patterns - Permission validation metrics ### 7. **Moderation & Automoderation** + **Primary Files:** + - `/app/discord/automod.ts` - Automated moderation - `/app/helpers/modLog.ts` - Moderation logging - `/app/helpers/isSpam.ts` - Spam detection - `/app/helpers/escalate.tsx` - Escalation handling **Key Features:** + - Automated spam detection - User reporting system - Moderation action logging @@ -135,18 +163,22 @@ This document identifies the major functional areas of the Discord bot applicati - Staff role verification **Observability Opportunities:** + - Spam detection accuracy - Moderation action frequencies - False positive rates - Response time metrics ### 8. **Analytics & Dashboard** + **Primary Files:** + - `/app/routes/__auth/dashboard.tsx` - Main analytics dashboard - `/app/routes/__auth/sh-user.tsx` - User-specific analytics - `/app/models/activity.server.ts` - Complex query builders **Key Features:** + - Top participant analysis - Message statistics breakdowns - User engagement scoring @@ -154,42 +186,51 @@ This document identifies the major functional areas of the Discord bot applicati - CSV export functionality **Observability Opportunities:** + - Dashboard load times - Query performance metrics - Export usage patterns - User engagement with analytics ### 9. **Database Operations** + **Primary Files:** + - `/app/db.server.ts` - Database connection - `/app/db.d.ts` - Database schema types - `/migrations/` - Database migrations **Key Features:** + - SQLite database with Kysely ORM - Complex analytical queries - Message statistics storage - User and guild data management **Observability Opportunities:** + - Database query performance - Connection pool health - Migration success rates - Storage usage patterns ### 10. **API Routes & Web Interface** + **Primary Files:** + - `/app/routes/` - Various route handlers - `/app/routes/__auth.tsx` - Authentication layout - `/app/routes/healthcheck.tsx` - Health monitoring **Key Features:** + - Protected route handling - Health check endpoints - Form submission processing - Redirect management **Observability Opportunities:** + - Route response times - Error rates by endpoint - Form submission success rates @@ -198,12 +239,14 @@ This document identifies the major functional areas of the Discord bot applicati ## Existing Observability Infrastructure ### Current Implementation + - **Structured Logging**: `/app/helpers/observability.ts` provides logging utilities - **Performance Tracking**: `trackPerformance()` function for timing operations - **Sentry Integration**: `/app/helpers/sentry.server.ts` for error tracking - **Pino HTTP**: Express middleware for request logging ### Already Instrumented + - **Subscription Service**: Fully instrumented with logging and performance tracking - **Error Handling**: Sentry integration for exception tracking - **Basic Request Logging**: HTTP request/response logging via pino @@ -211,18 +254,21 @@ This document identifies the major functional areas of the Discord bot applicati ## Recommended Observability Priorities ### High Priority (Core Business Logic) + 1. **Message Processing Pipeline** - Activity tracking throughput and reliability 2. **Discord Bot Health** - Connection stability and event processing 3. **User Authentication Flow** - OAuth success rates and session management 4. **Command Execution** - Usage patterns and error rates ### Medium Priority (Feature Usage) + 1. **Analytics Dashboard** - Query performance and user engagement 2. **Moderation System** - Effectiveness and accuracy metrics 3. **Guild Management** - Configuration and onboarding success 4. **Payment Processing** - Transaction success and conversion rates ### Low Priority (Operational) + 1. **Database Performance** - Query optimization and connection health 2. **API Response Times** - General web interface performance 3. **Export Features** - CSV generation and download patterns @@ -230,10 +276,11 @@ This document identifies the major functional areas of the Discord bot applicati ## Integration Approach Each major feature area can be enhanced with: + - **Structured logging** using the existing `log()` function - **Performance tracking** using the existing `trackPerformance()` wrapper - **Custom metrics** for business-specific KPIs - **Error tracking** via Sentry integration - **Health checks** for system components -The application already has a solid foundation with the observability helpers and Sentry integration, making it straightforward to add comprehensive monitoring to each feature area. \ No newline at end of file +The application already has a solid foundation with the observability helpers and Sentry integration, making it straightforward to add comprehensive monitoring to each feature area. diff --git a/notes/2025-07-04_3_observability-implementation-summary.md b/notes/2025-07-04_3_observability-implementation-summary.md index dfbe6af..23ea021 100644 --- a/notes/2025-07-04_3_observability-implementation-summary.md +++ b/notes/2025-07-04_3_observability-implementation-summary.md @@ -1,13 +1,15 @@ # Observability Integration Implementation Summary - July 4, 2025 ## Overview + Successfully completed comprehensive observability integration across all major Discord bot application features, implementing a three-layer observability architecture following industry best practices. ## Architecture Implemented ### Three-Layer Observability Stack + 1. **Operational Observability** - System health, performance, debugging via structured logging -2. **Business Analytics** - User behavior, product insights via Amplitude integration +2. **Business Analytics** - User behavior, product insights via Amplitude integration 3. **Infrastructure Monitoring** - Error tracking, stability via Sentry integration ## Features Enhanced @@ -15,18 +17,21 @@ Successfully completed comprehensive observability integration across all major ### βœ… Phase 1: Critical Business Logic (Complete) #### 1. Discord Bot Gateway & Event Handling + - **Files Enhanced**: `app/discord/gateway.ts`, `app/discord/client.server.ts` - **Added**: Comprehensive logging for bot lifecycle, connection events, startup performance tracking - **Business Analytics**: Bot startup events, reconnection tracking, error events - **Key Metrics**: Bot uptime, connection stability, guild/user counts #### 2. Message Activity Tracking System + - **Files Enhanced**: `app/discord/activityTracker.ts` - **Added**: Performance tracking for all message processing operations, detailed logging for channel caching, reaction tracking - **Business Analytics**: Message tracking events already existed, enhanced with additional context - **Key Metrics**: Message processing throughput, channel activity, user engagement #### 3. Discord Commands System + - **Files Enhanced**: `app/commands/setup.ts`, `app/commands/report.ts`, `app/commands/force-ban.ts` - **Added**: Command execution tracking, success/failure logging, business analytics for command usage - **Business Analytics**: Command execution events, setup completion tracking, report submission events @@ -35,16 +40,19 @@ Successfully completed comprehensive observability integration across all major ### βœ… Phase 2: User Experience (Complete) #### 4. User Management & Authentication + - **Files Enhanced**: `app/models/user.server.ts` - **Added**: Performance tracking for all user database operations, structured logging for user lifecycle - **Key Metrics**: User lookup performance, authentication success rates, user creation events #### 5. Analytics & Dashboard + - **Files Enhanced**: `app/routes/__auth/dashboard.tsx` - **Added**: Dashboard access logging, performance tracking for complex queries - **Key Metrics**: Dashboard load times, query performance, user engagement with analytics #### 6. Guild/Server Management + - **Files Enhanced**: `app/models/guilds.server.ts` - **Added**: Guild registration tracking, settings management logging, error handling - **Key Metrics**: Guild onboarding success rates, configuration changes @@ -52,6 +60,7 @@ Successfully completed comprehensive observability integration across all major ### βœ… Business Analytics Infrastructure #### Enhanced Metrics System + - **File Enhanced**: `app/helpers/metrics.ts` - **Added**: Command tracking events, bot lifecycle events, comprehensive Discord event tracking - **Events Added**: @@ -64,15 +73,21 @@ Successfully completed comprehensive observability integration across all major ## Technical Implementation Details ### Performance Tracking Pattern + ```typescript -await trackPerformance("operationName", async () => { - log("info", "ServiceName", "Operation description", context); - // ... business logic - // Business analytics tracking -}, { contextData }); +await trackPerformance( + "operationName", + async () => { + log("info", "ServiceName", "Operation description", context); + // ... business logic + // Business analytics tracking + }, + { contextData }, +); ``` ### Structured Logging Pattern + ```typescript log("level", "ServiceName", "Message", { contextKey: "value", @@ -81,6 +96,7 @@ log("level", "ServiceName", "Message", { ``` ### Business Analytics Pattern + ```typescript // Operational tracking commandStats.commandExecuted(interaction, "commandName", success); @@ -91,24 +107,28 @@ commandStats.setupCompleted(interaction, settings); ## Observability Capabilities Added ### 1. System Health Monitoring + - Bot connection status and reconnection events - Gateway error tracking and alerting - Performance metrics for all critical operations - Database operation performance tracking ### 2. User Behavior Analytics + - Command usage patterns and frequencies - Guild setup completion rates - User engagement with dashboard features - Message activity and participation metrics ### 3. Operational Debugging + - Comprehensive error logging with stack traces - Structured context for all operations - Performance bottleneck identification - User authentication flow tracking ### 4. Business Intelligence + - Bot adoption metrics (guild counts, user engagement) - Feature usage analytics (command popularity, setup success) - User journey tracking (onboarding, feature discovery) @@ -117,18 +137,21 @@ commandStats.setupCompleted(interaction, settings); ## Impact & Benefits ### For Development Team + - **Faster Debugging**: Structured logs with comprehensive context - **Performance Insights**: Timing data for all critical operations - **Error Visibility**: Detailed error tracking with Sentry integration - **Code Quality**: Consistent observability patterns across codebase ### For Product Team + - **User Behavior Insights**: Comprehensive analytics via Amplitude - **Feature Adoption Tracking**: Command usage and setup completion rates - **Performance Monitoring**: Dashboard load times and query performance - **Business Metrics**: Guild growth, user engagement, feature popularity ### For Operations Team + - **System Health**: Real-time bot status and connection monitoring - **Alert-Ready**: Structured data ready for monitoring dashboards - **Incident Response**: Detailed context for debugging production issues @@ -137,12 +160,14 @@ commandStats.setupCompleted(interaction, settings); ## Files Modified/Created ### New Files + - `app/helpers/metrics.ts` - Enhanced business analytics - `notes/2025-07-04_1.md` - Implementation plan - `notes/2025-07-04_2_major-features-observability-analysis.md` - Feature analysis - `notes/2025-07-04_3_observability-implementation-summary.md` - This summary ### Enhanced Files + - `app/discord/gateway.ts` - Bot lifecycle observability - `app/discord/client.server.ts` - Client connection tracking - `app/discord/activityTracker.ts` - Message processing observability @@ -156,11 +181,13 @@ commandStats.setupCompleted(interaction, settings); ## Next Steps & Recommendations ### Immediate (Production Ready) + 1. **Configure Monitoring Dashboards**: Use structured log data for operational dashboards 2. **Set Up Alerting**: Configure alerts for error rates, performance thresholds 3. **Business Analytics Review**: Analyze Amplitude data for product insights ### Future Enhancements + 1. **Distributed Tracing**: Add trace IDs for request correlation across services 2. **Custom Metrics**: Integration with Prometheus/Grafana for infrastructure metrics 3. **Advanced Analytics**: Enhanced user journey tracking and cohort analysis @@ -170,4 +197,4 @@ commandStats.setupCompleted(interaction, settings); Successfully implemented enterprise-grade observability across the entire Discord bot application. The three-layer architecture provides comprehensive visibility into system health, user behavior, and business metrics. The consistent patterns established make it easy to extend observability to new features and maintain high operational standards. -The implementation follows industry best practices and provides a solid foundation for production operations, enabling proactive monitoring, quick debugging, and data-driven product decisions. \ No newline at end of file +The implementation follows industry best practices and provides a solid foundation for production operations, enabling proactive monitoring, quick debugging, and data-driven product decisions. diff --git a/notes/2025-07-25_1_database-structure-analysis.md b/notes/2025-07-25_1_database-structure-analysis.md index 23a7e2f..7799279 100644 --- a/notes/2025-07-25_1_database-structure-analysis.md +++ b/notes/2025-07-25_1_database-structure-analysis.md @@ -1,17 +1,21 @@ # Database Structure Analysis - 2025-07-25 ## Overview + This codebase uses **Kysely** as the TypeScript query builder with **SQLite** as the database engine. The project follows a clear pattern for database management with migrations, models, and type definitions. ## Key Files and Structure ### Database Configuration + - **`/app/db.server.ts`** - Main database connection using Kysely with SQLite dialect - **`/kysely.config.ts`** - Migration configuration using kysely-ctl - **`/app/db.d.ts`** - Auto-generated TypeScript type definitions for all tables ### Models Directory + All database models are in `/app/models/` with `.server.ts` suffix: + - `user.server.ts` - User management functions - `guilds.server.ts` - Discord guild management - `activity.server.ts` - Message analytics and statistics @@ -21,6 +25,7 @@ All database models are in `/app/models/` with `.server.ts` suffix: - `discord.server.ts` - Discord bot specific functions ### Migrations Directory + Located at `/migrations/` with timestamp-prefixed files following pattern `YYYYMMDDHHMMSS_description.ts` ## Database Schema (Current Tables) @@ -28,21 +33,25 @@ Located at `/migrations/` with timestamp-prefixed files following pattern `YYYYM Based on `/app/db.d.ts`, current tables are: 1. **users** + - `id` (uuid, primary key) - `email` (text, nullable) - `externalId` (text, not null) - Discord user ID - `authProvider` (text, defaults to "discord") 2. **sessions** + - `id` (uuid, primary key) - `data` (json) - `expires` (datetime) 3. **guilds** + - `id` (text, primary key) - Discord guild ID - `settings` (json) - Guild configuration 4. **message_stats** + - `author_id` (text) - `channel_id` (text) - `channel_category` (text, nullable) @@ -54,11 +63,13 @@ Based on `/app/db.d.ts`, current tables are: - `code_stats`, `link_stats` (json) 5. **channel_info** + - `id` (text, nullable) - `name` (text, nullable) - `category` (text, nullable) 6. **tickets_config** + - `message_id` (text, primary key) - `channel_id` (text, nullable) - `role_id` (text) @@ -74,6 +85,7 @@ Based on `/app/db.d.ts`, current tables are: ## Patterns to Follow for New Tables ### Migration Pattern + ```typescript import type { Kysely } from "kysely"; @@ -81,7 +93,9 @@ export async function up(db: Kysely): Promise { await db.schema .createTable("table_name") .addColumn("id", "uuid", (c) => c.primaryKey().notNull()) - .addColumn("created_at", "datetime", (c) => c.defaultTo("CURRENT_TIMESTAMP")) + .addColumn("created_at", "datetime", (c) => + c.defaultTo("CURRENT_TIMESTAMP"), + ) // ... other columns .execute(); } @@ -92,6 +106,7 @@ export async function down(db: Kysely): Promise { ``` ### Model Pattern + ```typescript import type { DB } from "#~/db.server"; import db from "#~/db.server"; @@ -104,22 +119,23 @@ export async function getById(id: string) { "getById", async () => { log("debug", "TableName", "Fetching by ID", { id }); - + const result = await db .selectFrom("table_name") .selectAll() .where("id", "=", id) .executeTakeFirst(); - + log("debug", "TableName", result ? "Found" : "Not found", { id }); return result; }, - { id } + { id }, ); } ``` ### Key Patterns + 1. **Observability**: All database operations wrapped with `trackPerformance()` and include `log()` calls 2. **Type Safety**: Export table types from models using `DB["table_name"]` 3. **Async/Await**: All database operations are async @@ -128,9 +144,11 @@ export async function getById(id: string) { 6. **Primary Keys**: UUIDs for user-related tables, natural keys (like Discord IDs) for external entities ## For user_threads Table + Based on patterns, a user_threads table should: + - Use `user_id` (uuid) referencing users.id - Use `thread_id` (text) as Discord thread ID - Include `created_at`, `updated_at` timestamps - Follow the established model pattern with observability -- Have a corresponding migration file with timestamp prefix \ No newline at end of file +- Have a corresponding migration file with timestamp prefix diff --git a/notes/2025-07-25_2-modlog-refactor-plan.md b/notes/2025-07-25_2-modlog-refactor-plan.md index de1ea57..7663282 100644 --- a/notes/2025-07-25_2-modlog-refactor-plan.md +++ b/notes/2025-07-25_2-modlog-refactor-plan.md @@ -3,13 +3,15 @@ ## Current System Analysis ### Issues with Current Implementation + - Creates new threads per message via `makeLogThread()` (modLog.ts:43-47) -- Uses message-based caching with `queryReportCache()` +- Uses message-based caching with `queryReportCache()` - Thread naming is date-based: `${user.username} – ${format(message.createdAt, "P")}` - Search history tracking via Discord search is unreliable - Creates channel clutter with many threads per user ### Key Functions to Refactor + - `reportUser()`: Main function handling report logic (modLog.ts:50-166) - `makeLogThread()`: Thread creation logic (modLog.ts:43-47) - `constructLog()`: Message formatting for initial reports (modLog.ts:181-241) @@ -18,10 +20,11 @@ ## New System Design ### Database Schema + ```sql user_threads ( user_id TEXT, - guild_id TEXT, + guild_id TEXT, thread_id TEXT, created_at DATETIME, PRIMARY KEY (user_id, guild_id) @@ -29,12 +32,14 @@ user_threads ( ``` ### Threading Strategy + 1. **One thread per user per guild** - persistent across all reports -2. **Thread naming**: `${user.username} Moderation History` +2. **Thread naming**: `${user.username} Moderation History` 3. **Top-level notifications**: New message in mod-log channel linking to user thread 4. **Thread content**: All reports, actions, timeouts, kicks/bans for that user ### Notification Flow + 1. New report comes in 2. Lookup/create persistent user thread 3. Post report details in user thread @@ -42,14 +47,16 @@ user_threads ( 5. Update any escalation controls in user thread ## Integration Points + - **Report feature**: Uses `reportUser()` directly -- **Track feature**: Uses `reportUser()` via track commands +- **Track feature**: Uses `reportUser()` via track commands - **Automod**: Calls `reportUser()` for automated reports - **Escalate**: Uses threads created by `reportUser()` ## Benefits + - Consolidated user history in single discoverable thread - Programmatic access via thread lookup table - Reduced channel clutter - Better historical context for moderation decisions -- More reliable than Discord search \ No newline at end of file +- More reliable than Discord search From e5dd37189ec9ace5d1b72b45a3aab9594836c591 Mon Sep 17 00:00:00 2001 From: Carl Vitullo Date: Fri, 25 Jul 2025 19:47:15 -0400 Subject: [PATCH 03/19] Complete modLog refactor implementation and documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add comprehensive completion notes documenting all changes - Verify TypeScript compliance and linting passes - Confirm API compatibility with all existing integrations - Document benefits and production deployment considerations The refactor successfully transitions from per-message threads to persistent user-based threads while maintaining full backward compatibility. πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../2025-07-25_3-modlog-refactor-complete.md | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 notes/2025-07-25_3-modlog-refactor-complete.md diff --git a/notes/2025-07-25_3-modlog-refactor-complete.md b/notes/2025-07-25_3-modlog-refactor-complete.md new file mode 100644 index 0000000..64e9d06 --- /dev/null +++ b/notes/2025-07-25_3-modlog-refactor-complete.md @@ -0,0 +1,57 @@ +# ModLog Refactor Implementation Complete - 2025-07-25 + +## Summary + +Successfully refactored the moderation logging system from per-message threads to persistent user-based threads. This consolidates all moderation history for each user into a single thread per guild, improving discoverability and reducing channel clutter. + +## Key Changes Implemented + +### Database Layer +- **Migration**: `20250725192908_user_threads.ts` - Creates user_threads table with unique composite key (user_id, guild_id) +- **Model**: `app/models/userThreads.server.ts` - CRUD operations with full observability integration +- **Schema**: user_id, guild_id, thread_id, created_at with unique constraint + +### Core Logic Changes +- **Thread Creation**: `makeUserThread()` creates threads with "Username Moderation History" naming +- **Thread Management**: `getOrCreateUserThread()` handles lookup/creation with database persistence +- **Notification System**: Main channel posts link to user thread instead of full content +- **Report Flow**: Detailed reports posted in user threads, notifications in main channel + +### Integration Points Verified +All existing features maintain compatibility through the `reportUser()` API: +- **Report Command**: Anonymous reporting via context menu +- **Track Command**: Staff message tracking +- **Automod**: Spam detection and auto-moderation +- **Escalate System**: All resolution types (restrict, kick, ban, warning, timeout) + +## Benefits Achieved + +1. **Consolidated History**: All user moderation actions in single persistent thread +2. **Improved Discoverability**: Database lookup instead of Discord search +3. **Reduced Clutter**: One thread per user instead of per message +4. **Better Context**: Historical view of all user interactions +5. **Programmatic Access**: Thread IDs stored for easy API access + +## Technical Quality + +- βœ… **Type Safety**: Full TypeScript integration with generated DB types +- βœ… **Observability**: All operations include performance tracking and logging +- βœ… **Error Handling**: Graceful fallbacks for missing/inaccessible threads +- βœ… **Backward Compatibility**: Existing API preserved, internal implementation changed +- βœ… **Database Integrity**: Unique constraints and proper indexing + +## Files Modified + +- `app/helpers/modLog.ts` - Core refactoring with new threading logic +- `app/models/userThreads.server.ts` - New database model +- `migrations/20250725192908_user_threads.ts` - Database schema +- `app/db.d.ts` - Updated type definitions + +## Next Steps for Production + +1. **Testing**: Verify functionality in development environment +2. **Migration**: Run migration in production (backward compatible) +3. **Monitoring**: Watch observability logs for thread access patterns +4. **Cleanup**: Eventually clean up old per-message threads (manual process) + +The refactor maintains full API compatibility while delivering significant UX improvements for moderators. \ No newline at end of file From 0d0af266b549011c3f178feef8c1dec6fc6f7041 Mon Sep 17 00:00:00 2001 From: Carl Vitullo Date: Fri, 25 Jul 2025 19:54:02 -0400 Subject: [PATCH 04/19] Implement freestanding threads and improve message flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: - Switch from message-based threads to freestanding private threads using channel.threads.create() - Remove placeholder message creation, threads are now truly independent - Add visual connection between warning message and moderator controls with footer - Update constructLog to include "Moderator controls follow below" indicator - Maintain proper thread flow: detailed report β†’ moderator controls β†’ individual report This creates cleaner threads without the initial placeholder message and provides better visual connection between the warning content and moderation actions. πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- app/helpers/modLog.ts | 23 +++++++++++-------- .../2025-07-25_3-modlog-refactor-complete.md | 10 +++++--- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/app/helpers/modLog.ts b/app/helpers/modLog.ts index 37c6c3e..c0fee98 100644 --- a/app/helpers/modLog.ts +++ b/app/helpers/modLog.ts @@ -4,6 +4,7 @@ import type { User, APIEmbed, AnyThreadChannel, + TextChannel, } from "discord.js"; import { MessageType, ChannelType } from "discord.js"; import { format, formatDistanceToNowStrict, differenceInHours } from "date-fns"; @@ -45,9 +46,10 @@ interface Reported { latestReport?: Message; } -const makeUserThread = (message: Message, user: User) => { - return message.startThread({ +const makeUserThread = (channel: TextChannel, user: User) => { + return channel.threads.create({ name: `${user.username} Moderation History`, + type: ChannelType.PrivateThread, }); }; @@ -80,11 +82,8 @@ const getOrCreateUserThread = async (message: Message, user: User) => { throw new Error("Invalid mod log channel"); } - // Create a placeholder message to start the thread from - const placeholder = await modLog.send( - `**${user.username}** Moderation History`, - ); - const thread = await makeUserThread(placeholder, user); + // Create freestanding private thread + const thread = await makeUserThread(modLog, user); // Store or update the thread reference if (existingThread) { @@ -126,7 +125,6 @@ export const reportUser = async ({ // Get or create persistent user thread const thread = await getOrCreateUserThread(message, message.author); - await escalationControls(message, thread, modRoleId); if (cached.logs.some((l) => l.message.id === message.id)) { // If we've already logged exactly this message, don't log it again as a @@ -190,7 +188,6 @@ export const reportUser = async ({ // Get or create persistent user thread first const thread = await getOrCreateUserThread(message, message.author); - await escalationControls(message, thread, modRoleId); // Post notification in main channel linking to user thread const notificationMessage = await modLog.send({ @@ -206,7 +203,10 @@ export const reportUser = async ({ staff, }); + // Send combined detailed report with moderator controls await thread.send(logBody); + await escalationControls(message, thread, modRoleId); + const latestReport = await thread.send(makeReportMessage(newReport)); return { @@ -287,7 +287,10 @@ const constructLog = async ({ return { content: truncateMessage(`${preface} ${extra}${reportedMessage} -${warnings.join("\n")}`).trim(), +${warnings.join("\n")} + +--- +**Moderator controls follow below** ⬇️`).trim(), embeds: embeds.length === 0 ? undefined : embeds, allowedMentions: { roles: [moderator] }, }; diff --git a/notes/2025-07-25_3-modlog-refactor-complete.md b/notes/2025-07-25_3-modlog-refactor-complete.md index 64e9d06..48d3072 100644 --- a/notes/2025-07-25_3-modlog-refactor-complete.md +++ b/notes/2025-07-25_3-modlog-refactor-complete.md @@ -7,20 +7,24 @@ Successfully refactored the moderation logging system from per-message threads t ## Key Changes Implemented ### Database Layer + - **Migration**: `20250725192908_user_threads.ts` - Creates user_threads table with unique composite key (user_id, guild_id) - **Model**: `app/models/userThreads.server.ts` - CRUD operations with full observability integration - **Schema**: user_id, guild_id, thread_id, created_at with unique constraint ### Core Logic Changes + - **Thread Creation**: `makeUserThread()` creates threads with "Username Moderation History" naming -- **Thread Management**: `getOrCreateUserThread()` handles lookup/creation with database persistence +- **Thread Management**: `getOrCreateUserThread()` handles lookup/creation with database persistence - **Notification System**: Main channel posts link to user thread instead of full content - **Report Flow**: Detailed reports posted in user threads, notifications in main channel ### Integration Points Verified + All existing features maintain compatibility through the `reportUser()` API: + - **Report Command**: Anonymous reporting via context menu -- **Track Command**: Staff message tracking +- **Track Command**: Staff message tracking - **Automod**: Spam detection and auto-moderation - **Escalate System**: All resolution types (restrict, kick, ban, warning, timeout) @@ -54,4 +58,4 @@ All existing features maintain compatibility through the `reportUser()` API: 3. **Monitoring**: Watch observability logs for thread access patterns 4. **Cleanup**: Eventually clean up old per-message threads (manual process) -The refactor maintains full API compatibility while delivering significant UX improvements for moderators. \ No newline at end of file +The refactor maintains full API compatibility while delivering significant UX improvements for moderators. From 39a5182e759aeaea525880b862acf9283d30fae5 Mon Sep 17 00:00:00 2001 From: Carl Vitullo Date: Sat, 26 Jul 2025 11:25:29 -0400 Subject: [PATCH 05/19] Update Discord to 14.21 --- package-lock.json | 132 +++++++++++++++++++++++++++++----------------- package.json | 2 +- 2 files changed, 84 insertions(+), 50 deletions(-) diff --git a/package-lock.json b/package-lock.json index b619167..63fa59c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "date-fns": "^4.1.0", "discord-api-types": "0.37.97", "discord-interactions": "^4.1.0", - "discord.js": "^14.16.0", + "discord.js": "^14.21.0", "dotenv": "^16.0.1", "express": "^4.18.1", "isbot": "^5", @@ -725,26 +725,35 @@ } }, "node_modules/@discordjs/builders": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.9.0.tgz", - "integrity": "sha512-0zx8DePNVvQibh5ly5kCEei5wtPBIUbSoE9n+91Rlladz4tgtFbJ36PZMxxZrTEOQ7AHMZ/b0crT/0fCy6FTKg==", + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.11.2.tgz", + "integrity": "sha512-F1WTABdd8/R9D1icJzajC4IuLyyS8f3rTOz66JsSI3pKvpCAtsMBweu8cyNYsIyvcrKAVn9EPK+Psoymq+XC0A==", "license": "Apache-2.0", "dependencies": { - "@discordjs/formatters": "^0.5.0", + "@discordjs/formatters": "^0.6.1", "@discordjs/util": "^1.1.1", "@sapphire/shapeshift": "^4.0.0", - "discord-api-types": "0.37.97", + "discord-api-types": "^0.38.1", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" }, "engines": { - "node": ">=18" + "node": ">=16.11.0" }, "funding": { "url": "https://github.com/discordjs/discord.js?sponsor" } }, + "node_modules/@discordjs/builders/node_modules/discord-api-types": { + "version": "0.38.17", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.17.tgz", + "integrity": "sha512-/fCx5jdUoR2hBFcj77Qx7Tmx1Ub8V/QpyS6uorjFvxRLcJJ348QxMFml9QW/eXh3i46eO4Ve8qGVepStpInEPg==", + "license": "MIT", + "workspaces": [ + "scripts/actions/documentation" + ] + }, "node_modules/@discordjs/collection": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", @@ -758,24 +767,33 @@ } }, "node_modules/@discordjs/formatters": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.5.0.tgz", - "integrity": "sha512-98b3i+Y19RFq1Xke4NkVY46x8KjJQjldHUuEbCqMvp1F5Iq9HgnGpu91jOi/Ufazhty32eRsKnnzS8n4c+L93g==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.1.tgz", + "integrity": "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg==", "license": "Apache-2.0", "dependencies": { - "discord-api-types": "0.37.97" + "discord-api-types": "^0.38.1" }, "engines": { - "node": ">=18" + "node": ">=16.11.0" }, "funding": { "url": "https://github.com/discordjs/discord.js?sponsor" } }, + "node_modules/@discordjs/formatters/node_modules/discord-api-types": { + "version": "0.38.17", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.17.tgz", + "integrity": "sha512-/fCx5jdUoR2hBFcj77Qx7Tmx1Ub8V/QpyS6uorjFvxRLcJJ348QxMFml9QW/eXh3i46eO4Ve8qGVepStpInEPg==", + "license": "MIT", + "workspaces": [ + "scripts/actions/documentation" + ] + }, "node_modules/@discordjs/rest": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.4.0.tgz", - "integrity": "sha512-Xb2irDqNcq+O8F0/k/NaDp7+t091p+acb51iA4bCKfIn+WFWd6HrNvcsSbMMxIR9NjcMZS6NReTKygqiQN+ntw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.5.1.tgz", + "integrity": "sha512-Tg9840IneBcbrAjcGaQzHUJWFNq1MMWZjTdjJ0WS/89IffaNKc++iOvffucPxQTF/gviO9+9r8kEPea1X5J2Dw==", "license": "Apache-2.0", "dependencies": { "@discordjs/collection": "^2.1.1", @@ -783,10 +801,10 @@ "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", - "discord-api-types": "0.37.97", + "discord-api-types": "^0.38.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", - "undici": "6.19.8" + "undici": "6.21.3" }, "engines": { "node": ">=18" @@ -795,6 +813,15 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, + "node_modules/@discordjs/rest/node_modules/discord-api-types": { + "version": "0.38.17", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.17.tgz", + "integrity": "sha512-/fCx5jdUoR2hBFcj77Qx7Tmx1Ub8V/QpyS6uorjFvxRLcJJ348QxMFml9QW/eXh3i46eO4Ve8qGVepStpInEPg==", + "license": "MIT", + "workspaces": [ + "scripts/actions/documentation" + ] + }, "node_modules/@discordjs/util": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz", @@ -808,20 +835,20 @@ } }, "node_modules/@discordjs/ws": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.1.1.tgz", - "integrity": "sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", + "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", "license": "Apache-2.0", "dependencies": { "@discordjs/collection": "^2.1.0", - "@discordjs/rest": "^2.3.0", + "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", - "discord-api-types": "0.37.83", + "discord-api-types": "^0.38.1", "tslib": "^2.6.2", - "ws": "^8.16.0" + "ws": "^8.17.0" }, "engines": { "node": ">=16.11.0" @@ -831,10 +858,13 @@ } }, "node_modules/@discordjs/ws/node_modules/discord-api-types": { - "version": "0.37.83", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.83.tgz", - "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==", - "license": "MIT" + "version": "0.38.17", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.17.tgz", + "integrity": "sha512-/fCx5jdUoR2hBFcj77Qx7Tmx1Ub8V/QpyS6uorjFvxRLcJJ348QxMFml9QW/eXh3i46eO4Ve8qGVepStpInEPg==", + "license": "MIT", + "workspaces": [ + "scripts/actions/documentation" + ] }, "node_modules/@esbuild/aix-ppc64": { "version": "0.23.1", @@ -2996,9 +3026,9 @@ "peer": true }, "node_modules/@types/ws": { - "version": "8.5.13", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", - "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -4792,23 +4822,24 @@ } }, "node_modules/discord.js": { - "version": "14.16.3", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.16.3.tgz", - "integrity": "sha512-EPCWE9OkA9DnFFNrO7Kl1WHHDYFXu3CNVFJg63bfU7hVtjZGyhShwZtSBImINQRWxWP2tgo2XI+QhdXx28r0aA==", + "version": "14.21.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.21.0.tgz", + "integrity": "sha512-U5w41cEmcnSfwKYlLv5RJjB8Joa+QJyRwIJz5i/eg+v2Qvv6EYpCRhN9I2Rlf0900LuqSDg8edakUATrDZQncQ==", "license": "Apache-2.0", "dependencies": { - "@discordjs/builders": "^1.9.0", + "@discordjs/builders": "^1.11.2", "@discordjs/collection": "1.5.3", - "@discordjs/formatters": "^0.5.0", - "@discordjs/rest": "^2.4.0", + "@discordjs/formatters": "^0.6.1", + "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.1", - "@discordjs/ws": "1.1.1", + "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", - "discord-api-types": "0.37.100", + "discord-api-types": "^0.38.1", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", + "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", - "undici": "6.19.8" + "undici": "6.21.3" }, "engines": { "node": ">=18" @@ -4837,10 +4868,13 @@ } }, "node_modules/discord.js/node_modules/discord-api-types": { - "version": "0.37.100", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.100.tgz", - "integrity": "sha512-a8zvUI0GYYwDtScfRd/TtaNBDTXwP5DiDVX7K5OmE+DRT57gBqKnwtOC5Ol8z0mRW8KQfETIgiB8U0YZ9NXiCA==", - "license": "MIT" + "version": "0.38.17", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.17.tgz", + "integrity": "sha512-/fCx5jdUoR2hBFcj77Qx7Tmx1Ub8V/QpyS6uorjFvxRLcJJ348QxMFml9QW/eXh3i46eO4Ve8qGVepStpInEPg==", + "license": "MIT", + "workspaces": [ + "scripts/actions/documentation" + ] }, "node_modules/dlv": { "version": "1.1.3", @@ -12108,9 +12142,9 @@ } }, "node_modules/undici": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", - "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==", + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", "license": "MIT", "engines": { "node": ">=18.17" @@ -13175,9 +13209,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index d8e9064..8050684 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "date-fns": "^4.1.0", "discord-api-types": "0.37.97", "discord-interactions": "^4.1.0", - "discord.js": "^14.16.0", + "discord.js": "^14.21.0", "dotenv": "^16.0.1", "express": "^4.18.1", "isbot": "^5", From 09c0ed36f4a0cc6a0e0944eb7d64893882848613 Mon Sep 17 00:00:00 2001 From: Carl Vitullo Date: Sat, 26 Jul 2025 11:26:13 -0400 Subject: [PATCH 06/19] Simplify userThread model trackPerformance will issue named logs at start/end, so the included logs are redundant. Less code! --- app/models/userThreads.server.ts | 44 ++++++-------------------------- 1 file changed, 8 insertions(+), 36 deletions(-) diff --git a/app/models/userThreads.server.ts b/app/models/userThreads.server.ts index b8d1032..5782470 100644 --- a/app/models/userThreads.server.ts +++ b/app/models/userThreads.server.ts @@ -8,8 +8,6 @@ export async function getUserThread(userId: string, guildId: string) { return trackPerformance( "getUserThread", async () => { - log("debug", "UserThread", "Fetching user thread", { userId, guildId }); - const thread = await db .selectFrom("user_threads") .selectAll() @@ -34,30 +32,17 @@ export async function createUserThread( guildId: string, threadId: string, ): Promise { - return trackPerformance( + await trackPerformance( "createUserThread", - async () => { - log("info", "UserThread", "Creating user thread", { - userId, - guildId, - threadId, - }); - - await db + () => + db .insertInto("user_threads") .values({ user_id: userId, guild_id: guildId, thread_id: threadId, }) - .execute(); - - log("info", "UserThread", "Created user thread", { - userId, - guildId, - threadId, - }); - }, + .execute(), { userId, guildId, threadId }, ); } @@ -67,28 +52,15 @@ export async function updateUserThread( guildId: string, threadId: string, ): Promise { - return trackPerformance( + await trackPerformance( "updateUserThread", - async () => { - log("info", "UserThread", "Updating user thread", { - userId, - guildId, - threadId, - }); - - await db + () => + db .updateTable("user_threads") .set({ thread_id: threadId }) .where("user_id", "=", userId) .where("guild_id", "=", guildId) - .execute(); - - log("info", "UserThread", "Updated user thread", { - userId, - guildId, - threadId, - }); - }, + .execute(), { userId, guildId, threadId }, ); } From b10e555feed4f02364fc86ec169ccd7d941f49d1 Mon Sep 17 00:00:00 2001 From: Carl Vitullo Date: Sat, 26 Jul 2025 11:26:31 -0400 Subject: [PATCH 07/19] Fix deprecation warning --- app/commands/demo.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/commands/demo.ts b/app/commands/demo.ts index 803ec23..0176544 100644 --- a/app/commands/demo.ts +++ b/app/commands/demo.ts @@ -8,7 +8,7 @@ export const command = new SlashCommandBuilder() export const handler = async (interaction: CommandInteraction) => { await interaction.reply({ - ephemeral: true, + flags: "Ephemeral", content: "ok", }); }; From c15c4261e171140a14837001d6cf10a875db8657 Mon Sep 17 00:00:00 2001 From: Carl Vitullo Date: Sat, 26 Jul 2025 11:29:31 -0400 Subject: [PATCH 08/19] Memory! --- CLAUDE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CLAUDE.md b/CLAUDE.md index ae7f3d3..1f66970 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,6 +2,7 @@ - You should periodically jot down your thoughts in `/notes`, especially if it will help you remember important implementation details later. - Your notes must be named consistently with a date prefix in the format YYYY-MM-DD followed by a sequence in the format \_X where x is a monotonically increasing integer. +- You must commit periodically, running `npm run validate` first. - You expect to be able to access VS Code. If you can't, prompt me about it. - This project uses sqlite, so you can inspect the database yourself. You can make your own dummy data, but don't do anything destructive, and make sure to describe how to reverse any DB changes. - You can curl this website, it's running locally at http://localhost:3000. You are not able to access areas behind authentication without data from me. From 3d5d9d724cf9858f4a715b1782e06ea6aa872585 Mon Sep 17 00:00:00 2001 From: Carl Vitullo Date: Sat, 26 Jul 2025 15:23:21 -0400 Subject: [PATCH 09/19] Fix some "Unknown Message" logs that turned out to come from deletion events --- app/discord/activityTracker.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/discord/activityTracker.ts b/app/discord/activityTracker.ts index 6d5ff96..5b0cde5 100644 --- a/app/discord/activityTracker.ts +++ b/app/discord/activityTracker.ts @@ -144,6 +144,9 @@ export async function startActivityTracking(client: Client) { }); client.on(Events.MessageDelete, async (msg) => { + if (msg.system || msg.author?.bot) { + return; + } await trackPerformance( "processMessageDelete", async () => { From 16633fa5b73a50bed245048b3fa00c281e6e6181 Mon Sep 17 00:00:00 2001 From: Carl Vitullo Date: Sat, 26 Jul 2025 15:23:38 -0400 Subject: [PATCH 10/19] Refine chat experience for updated Track command --- app/helpers/escalate.tsx | 1 - app/helpers/modLog.ts | 61 +++++++++++++++------------------------- 2 files changed, 23 insertions(+), 39 deletions(-) diff --git a/app/helpers/escalate.tsx b/app/helpers/escalate.tsx index 1aee30d..c226a1e 100644 --- a/app/helpers/escalate.tsx +++ b/app/helpers/escalate.tsx @@ -26,7 +26,6 @@ export async function escalationControls( ) { reacord.createChannelMessage(thread.id).render( <> - Moderator controls