✨ tweak: database init implementation#96
Conversation
Overview
Labels (5 changes)
-org.opencontainers.image.created=2025-09-24T10:57:17Z
+org.opencontainers.image.created=2025-10-01T10:18:15Z
org.opencontainers.image.description=Turn Discord servers into comprehensive support ticket hubs with real-time bidirectional communication — powered by Unthread.io.
-org.opencontainers.image.licenses=AGPL-3.0
-org.opencontainers.image.revision=94752732455cb612bee529a58e25dcfc52c755f5
+org.opencontainers.image.revision=dcb9a08c30072bc036db6b8a3785f611241bff78
org.opencontainers.image.source=https://github.com/wgtechlabs/unthread-discord-bot
org.opencontainers.image.title=Unthread Discord Bot
-org.opencontainers.image.url=https://github.com/wgtechlabs/unthread-discord-bot
-org.opencontainers.image.version=1.0.4Packages and Vulnerabilities (14 package changes and 0 vulnerability changes)
Changes for packages of type
|
| Package | Versionwgtechlabs/unthread-discord-bot:latest |
Versionwgtechlabs/unthread-discord-bot:pr-96-dcb9a08 |
|
|---|---|---|---|
| ➖ | node | 22.16.0 |
Changes for packages of type github (1 changes)
| Package | Versionwgtechlabs/unthread-discord-bot:latest |
Versionwgtechlabs/unthread-discord-bot:pr-96-dcb9a08 |
|
|---|---|---|---|
| ➕ | node | 22.16.0 |
🩹 Patch Image Built SuccessfullyTest this patch with Docker: # Docker Hub
docker pull wgtechlabs/unthread-discord-bot:patch-ac484b3
# GitHub Container Registry
docker pull ghcr.io/wgtechlabs/unthread-discord-bot:patch-ac484b3Deploy to Railway/Cloud for testing:
|
📝 WalkthroughWalkthroughAt a glance: container images were updated in Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Jarvis out — the summary is compiled and precise. Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (6)
src/sdk/bots-brain/UnifiedStorage.ts (1)
181-191: Consolidate Redis "error" handling to avoid duplicate listeners and noisy logsYou attach two 'error' listeners (constructor and initializeConnection). They will both fire, duplicating logs and work. Suggest: keep a single structured handler; also add 'end'/'reconnecting' to flip connected=false proactively.
Apply this diff to consolidate and harden:
@@ - // Add error event handler to prevent crashes (Railway optimization) - this.client.on('error', (error: Error) => { - this.connected = false; - LogEngine.error('Redis L2 cache connection error (non-fatal):', { - error: error.message, - code: (error as NodeJS.ErrnoException).code, - errorType: 'L2_cache_error', - }); - // Don't throw - graceful degradation to L1 + L3 only - }); + // Single non-fatal error handler (+ connection lifecycle) + this.client.on('error', (error: Error) => { + this.connected = false; + LogEngine.error('Redis L2 cache connection error (non-fatal):', { + error: error.message, + code: (error as NodeJS.ErrnoException).code, + errorType: 'L2_cache_error', + }); + }); + this.client.on('end', () => { + this.connected = false; + LogEngine.warn('Redis L2 cache connection closed'); + }); + this.client.on('reconnecting', () => { + this.connected = false; + LogEngine.warn('Redis L2 cache reconnecting...'); + }); @@ - this.client.on('error', (error: Error) => { - this.connected = false; - LogEngine.error('Redis L2 cache error:', error); - }); + // 'error' handled in constructor; avoid duplicate listenersAlso applies to: 202-205
docker-compose.yml (1)
141-141: Second Redis 8 instance: mirror the validationSame compatibility check for webhook Redis; consider aligning persistence and healthcheck intervals if workload differs.
src/sdk/bots-brain/BotsStore.ts (4)
865-879: Timeout via Promise.race doesn’t cancel the underlying workThe 2‑minute guard is good, but Promise.race won’t abort performSchemaCheck; it will keep running in the background. Consider a simple reentrancy guard to prevent overlapping runs, and/or use server‑side timeouts (lock_timeout, idle_in_transaction_session_timeout) for defensive limits.
Example minimal guard:
@@ - private async ensureSchema(): Promise<void> { + private schemaInitInProgress = false; + private async ensureSchema(): Promise<void> { + if (this.schemaInitInProgress) { + LogEngine.warn('Schema operation already in progress; skipping reentry'); + return; + } + this.schemaInitInProgress = true; try { @@ - } + } catch (error) { @@ - } + } finally { + this.schemaInitInProgress = false; + }
924-942: Duplicate JSDoc blocks — please consolidateThere are two consecutive “Initialize database schema from schema.sql file” comment blocks; keep one to avoid drift.
891-922: Verify presence of cleanup_expired_cache function in performSchemaCheckCertainly, Sir. Extend performSchemaCheck to query pg_proc for cleanup_expired_cache and include missingFuncs in the initialization check to prevent latent runtime errors.
@@ private async performSchemaCheck(): Promise<void> { - const requiredTables = ['customers', 'thread_ticket_mappings', 'storage_cache']; + const requiredTables = ['customers', 'thread_ticket_mappings', 'storage_cache']; + // verify cleanup_expired_cache function exists + const funcs = await withDbClient(this.pool, c => + c.query(`SELECT proname FROM pg_proc WHERE proname = 'cleanup_expired_cache'`) + ); + const missingFuncs = funcs.rows.length === 0 ? ['cleanup_expired_cache'] : []; @@ - if (missingTables.length > 0) { + if (missingTables.length > 0 || missingFuncs.length > 0) { LogEngine.info('Database tables missing - setting up automatically...', { - missing: missingTables, + missingTables, + missingFuncs, }); await this.initializeSchema(); }
1019-1164: Optional resilience tweaks for parseSchemaStatements
- Restrict the dollar-tag regex to valid PG identifiers by using
/^\$([A-Za-z_][A-Za-z0-9_]*)?\$/instead of/^\$([^$]*)\$/.- Add unit tests covering nested
$$, named tags (e.g.$tag$...$tag$),DOblocks, and function bodies with embedded semicolons.- const match = schema.slice(i).match(/^\$([^$]*)\$/); + const match = schema.slice(i).match(/^\$([A-Za-z_][A-Za-z0-9_]*)?\$/);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
docker-compose.yml(3 hunks)src/sdk/bots-brain/BotsStore.ts(4 hunks)src/sdk/bots-brain/UnifiedStorage.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/sdk/bots-brain/BotsStore.ts (1)
src/config/logger.ts (1)
LogEngine(33-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build Patch Images
🔇 Additional comments (3)
src/sdk/bots-brain/BotsStore.ts (1)
967-969: Per‑statement execution: LGTMExecuting statements individually with a local 60s statement_timeout is a sensible guard for managed PG. Proceed.
docker-compose.yml (2)
121-121: Redis 8 upgrade: client compatibility confirmed; persistence defaults reviewed
- node-redis v5.8.2 supports Redis 8.0.x (github.com)
- Default RDB snapshots (save 900 1, save 300 10, save 60 10000) remain active – disable or tune via
--save ""or custom config if only AOF is desired (docs.serv00.com)
97-97: Postgres image swap — init hooks and pg_isready: verified.
- /docker-entrypoint-initdb.d scripts are installed and executed on first boot (repo copies init-ssl.sh into that dir and the wrapper ultimately invokes the official docker-entrypoint). (github.com)
- pg_isready is available (image is built FROM the official Postgres base and pg_isready is the standard readiness utility used for healthchecks). (github.com)
File: docker-compose.yml (line 97) — no change required.
…tweak/db-init-cmd-deploy
🩹 Patch Image Built SuccessfullyTest this patch with Docker: # Docker Hub
docker pull wgtechlabs/unthread-discord-bot:patch-dcb9a08
# GitHub Container Registry
docker pull ghcr.io/wgtechlabs/unthread-discord-bot:patch-dcb9a08Deploy to Railway/Cloud for testing:
|
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
src/utils/commandDeployment.ts (2)
133-146: Type casting approach follows discord.js patterns but lacks runtime guards.The type assertion on line 134 assumes the client has a
commandsproperty with a specific structure. While this is a common pattern in discord.js applications (commands are typically attached to the client instance during initialization), the code doesn't verify thatcommand.dataexists or thattoJSON()is callable.If a command in the collection lacks the
dataproperty ortoJSONmethod, the.map()call on line 141 would throw a runtime error.Consider adding a runtime guard:
// Convert local commands collection to JSON array -const localCommands = Array.from(extendedClient.commands.values()).map(command => command.data.toJSON()); +const localCommands = Array.from(extendedClient.commands.values()) + .filter(command => command.data && typeof command.data.toJSON === 'function') + .map(command => command.data.toJSON());Alternatively, verify that all commands loaded into the collection during initialization have the correct structure, which would make this guard unnecessary.
#!/bin/bash # Description: Verify command loading patterns to check if structure validation exists # Search for command loading and collection initialization rg -nP --type=ts -C5 'client\.commands\s*=|commands\.set\(' # Search for command file imports and structure ast-grep --pattern $'export default { data: $$$, $$$ }'
148-155: REST operations are correctly structured.The Discord REST client initialization and API calls follow discord.js patterns. The use of
Routes.applicationGuildCommandsdeploys commands to a specific guild, which is appropriate for most bot configurations (guild commands update instantly vs. global commands which can take up to an hour).One consideration: The REST client doesn't have an explicit timeout configured. The discord.js REST client has default timeouts (15s for most operations), but for critical startup operations, you may want to configure this explicitly to align with your retry strategy.
If you want explicit control over timeouts to align with the 2s/4s/6s retry delays:
// Initialize Discord REST client -const rest = new REST().setToken(DISCORD_BOT_TOKEN); +const rest = new REST({ timeout: 10000 }).setToken(DISCORD_BOT_TOKEN);This would ensure the REST operation fails within 10 seconds, allowing the retry mechanism to proceed predictably.
src/events/ready.ts (1)
108-126: Sir, ensure graceful shutdown precedesprocess.exit(1)
I observe SIGTERM/SIGINT handlers in src/index.ts that gracefully stop the webhook consumer, but the directprocess.exit(1)in ready.ts bypasses them. Consider invoking your cleanup routines (for example,await webhookConsumer.stop()) or programmatically emitting SIGTERM to trigger those handlers before exiting.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/events/ready.ts(3 hunks)src/utils/commandDeployment.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/events/ready.ts (1)
src/utils/commandDeployment.ts (1)
deployCommandsIfNeeded(122-189)
src/utils/commandDeployment.ts (1)
src/config/logger.ts (1)
LogEngine(33-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build Patch Images
🔇 Additional comments (8)
src/events/ready.ts (2)
1-56: Documentation architecture is exemplary, sir.The comprehensive JSDoc header provides excellent context for troubleshooting, performance characteristics, and usage patterns. The addition of command deployment considerations to the documentation aligns perfectly with the new functionality.
62-63: Import statements are properly organized.The new utility imports are clean and purposeful, bringing in exactly what's needed for the command deployment enhancement.
src/utils/commandDeployment.ts (6)
1-14: Module documentation is clear and purposeful.The header effectively communicates the smart deployment strategy and rate-limiting prevention benefits.
34-55: Normalization logic is sound for comparison purposes.The function correctly strips Discord-managed metadata while preserving developer-controlled configuration. The shallow copy approach is appropriate since the result is immediately stringified for comparison.
The comment on lines 48-50 about preserving order is helpful context. JSON.stringify will serialize the options array in order, so the shallow copy maintains the parameter sequence as intended.
126-131: Environment variable validation is appropriate.The guard clause correctly returns
falsewhen required configuration is missing, preventing deployment attempts in misconfigured environments. The warning message is descriptive.
160-182: Deployment decision logic is excellent.The conditional deployment with detailed logging provides excellent observability. The use of
rest.put()for atomic command replacement ensures consistency—removed commands are properly cleaned up, and there's no intermediate state where commands are partially deployed.The verbose change logging (lines 162-168) will be invaluable for debugging and monitoring in production.
184-188: Error handling with rethrow is correct for retry integration.The catch block logs the error for immediate visibility while rethrowing it, allowing the upstream retry mechanism in
ready.tsto handle retry attempts. This is the appropriate pattern for this architecture.
64-111: Approval granted: comparison logic and type assertions validated
Thenameproperty is guaranteed bySlashCommandBuilder().toJSON()and the upstream loading checks in index.ts and deploy_commands.ts, so no additional guards are required.
|
Okay, tested and it's working properly on Railway. I'm merging this to the main branch. I'm bypassing the dev branch. |
Summary by CodeRabbit
New Features
Bug Fixes
Chores