Skip to content

✨ tweak: database init implementation#96

Merged
warengonzaga merged 7 commits intomainfrom
tweak/database-init
Oct 1, 2025
Merged

✨ tweak: database init implementation#96
warengonzaga merged 7 commits intomainfrom
tweak/database-init

Conversation

@warengonzaga
Copy link
Member

@warengonzaga warengonzaga commented Sep 30, 2025

Summary by CodeRabbit

  • New Features

    • Automatic, retrying deployment of Discord slash commands with smart detection to only update when needed.
  • Bug Fixes

    • Improved startup reliability with timeouts and safer schema initialization to avoid long-running operations.
    • Enhanced Redis resilience: non-fatal error handling prevents crashes and allows graceful fallback.
  • Chores

    • Upgraded Postgres service to an SSL-enabled v17 image.
    • Upgraded Redis services to version 8 for improved performance and compatibility.

Copilot AI review requested due to automatic review settings September 30, 2025 18:02
@github-actions
Copy link

github-actions bot commented Sep 30, 2025

Overview

Image reference wgtechlabs/unthread-discord-bot:latest wgtechlabs/unthread-discord-bot:pr-96-dcb9a08
- digest cafd84e7eb3a 72b9376dc356
- tag latest pr-96-dcb9a08
- provenance 9475273 dcb9a08
- vulnerabilities critical: 0 high: 1 medium: 2 low: 3 critical: 0 high: 1 medium: 2 low: 3
- platform linux/amd64 linux/amd64
- size 61 MB 74 MB (+13 MB)
- packages 354 359 (+5)
Base Image node:22.16-alpine3.21
also known as:
22-alpine
22-alpine3.21
jod-alpine
jod-alpine3.21
lts-alpine
lts-alpine3.21
node:22-alpine
also known as:
22-alpine3.21
jod-alpine
jod-alpine3.21
lts-alpine
lts-alpine3.21
- vulnerabilities critical: 0 high: 1 medium: 2 low: 3 critical: 0 high: 1 medium: 2 low: 3
Labels (5 changes)
  • - 3 removed
  • ± 2 changed
  • 3 unchanged
-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.4
Packages and Vulnerabilities (14 package changes and 0 vulnerability changes)
  • ➕ 6 packages added
  • ➖ 1 packages removed
  • ♾️ 7 packages changed
  • 315 packages unchanged
Changes for packages of type apk (12 changes)
Package Version
wgtechlabs/unthread-discord-bot:latest
Version
wgtechlabs/unthread-discord-bot:pr-96-dcb9a08
alpine-base 3.21.4-r0
♾️ alpine-release 3.21.3-r0 3.21.4-r0
♾️ busybox 1.37.0-r12 1.37.0-r13
♾️ busybox-binsh 1.37.0-r12 1.37.0-r13
ca-certificates 20250619-r0
♾️ ca-certificates-bundle 20241121-r1 20250619-r0
gcc 14.2.0-r4
♾️ libcrypto3 3.3.3-r0 3.3.4-r0
♾️ libssl3 3.3.3-r0 3.3.4-r0
critical: 0 high: 1 medium: 0 low: 0
Removed vulnerabilities (1):
  • high : CVE--2025--9230
openssl 3.3.4-r0
critical: 0 high: 1 medium: 0 low: 0
Added vulnerabilities (1):
  • high : CVE--2025--9230
pax-utils 1.3.8-r1
♾️ ssl_client 1.37.0-r12 1.37.0-r13
Changes for packages of type generic (1 changes)
Package Version
wgtechlabs/unthread-discord-bot:latest
Version
wgtechlabs/unthread-discord-bot:pr-96-dcb9a08
node 22.16.0
Changes for packages of type github (1 changes)
Package Version
wgtechlabs/unthread-discord-bot:latest
Version
wgtechlabs/unthread-discord-bot:pr-96-dcb9a08
node 22.16.0

@github-actions
Copy link

🩹 Patch Image Built Successfully

Test 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-ac484b3

Deploy to Railway/Cloud for testing:

  • Image: wgtechlabs/unthread-discord-bot:patch-ac484b3
  • SHA: ac484b3665618b9852de7b532003b5d011749f07
  • Built: 2025-09-30T18:02:34Z

⚡ Hotfix ready for validation in production-like environment!

@warengonzaga warengonzaga changed the title database init implementation ✨ tweak: database init implementation Sep 30, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 30, 2025

📝 Walkthrough

Walkthrough

At a glance: container images were updated in docker-compose.yml; BotsStore gained robust schema checking, per-statement schema initialization, and timeouts; UnifiedStorage adds non-fatal Redis error handling; startup now deploys Discord commands with retry via a new commandDeployment utility.

Changes

Cohort / File(s) Summary of Changes
Container image updates
docker-compose.yml
Updated images: postgres-platformghcr.io/railwayapp-templates/postgres-ssl:17; redis-platform, redis-webhookredis:8-alpine. No other service config changes.
Bots schema management overhaul
src/sdk/bots-brain/BotsStore.ts
Added performSchemaCheck; ensureSchema now uses a 2-minute timeout wrapper. New initializeSchema, executeSchemaStatements, and parseSchemaStatements run schema.sql as parsed, per-statement executions with transaction semantics, 60s local statement_timeout, improved logging and error handling.
Resilient Redis handling
src/sdk/bots-brain/UnifiedStorage.ts
Added a non-fatal Redis error handler in constructor that marks connection as disconnected, logs via LogEngine, and avoids throwing to allow graceful degradation.
Discord command deployment at startup
src/events/ready.ts, src/utils/commandDeployment.ts
New deployCommandsIfNeeded(client) utility added; ready event now calls it with withRetry (3 attempts, 2s base delay). On repeated failure, logs critical error and exits; deployment skips when env/client commands absent; compares and deploys only when diffs exist.

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)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “✨ tweak: database init implementation” accurately reflects the schema initialization enhancements introduced in this pull request and remains concise and clear, referring to a genuine aspect of the changeset. Although it does not mention ancillary updates such as docker-compose service version upgrades, Redis error handling improvements, and command deployment utilities, it satisfies the criteria for a partially related title.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch tweak/database-init

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 logs

You 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 listeners

Also applies to: 202-205

docker-compose.yml (1)

141-141: Second Redis 8 instance: mirror the validation

Same 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 work

The 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 consolidate

There 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 performSchemaCheck

Certainly, 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$), DO blocks, 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

📥 Commits

Reviewing files that changed from the base of the PR and between 44890fe and 3d7b600.

📒 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: LGTM

Executing 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.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@github-actions
Copy link

github-actions bot commented Oct 1, 2025

🩹 Patch Image Built Successfully

Test 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-dcb9a08

Deploy to Railway/Cloud for testing:

  • Image: wgtechlabs/unthread-discord-bot:patch-dcb9a08
  • SHA: dcb9a08c30072bc036db6b8a3785f611241bff78
  • Built: 2025-10-01T10:18:17Z

⚡ Hotfix ready for validation in production-like environment!

@warengonzaga warengonzaga self-assigned this Oct 1, 2025
@warengonzaga warengonzaga added hacktoberfest-accepted Hacktoberfest accepted (PRs) deployment Deployment and infrastructure-related (Issues/PRs) api API integration and external services (Issues/PRs) labels Oct 1, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 commands property 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 that command.data exists or that toJSON() is callable.

If a command in the collection lacks the data property or toJSON method, 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.applicationGuildCommands deploys 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 precedes process.exit(1)
I observe SIGTERM/SIGINT handlers in src/index.ts that gracefully stop the webhook consumer, but the direct process.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

📥 Commits

Reviewing files that changed from the base of the PR and between 3d7b600 and 246da06.

📒 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 false when 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.ts to handle retry attempts. This is the appropriate pattern for this architecture.


64-111: Approval granted: comparison logic and type assertions validated
The name property is guaranteed by SlashCommandBuilder().toJSON() and the upstream loading checks in index.ts and deploy_commands.ts, so no additional guards are required.

@warengonzaga
Copy link
Member Author

Okay, tested and it's working properly on Railway. I'm merging this to the main branch. I'm bypassing the dev branch.

@warengonzaga warengonzaga merged commit 4d0e4de into main Oct 1, 2025
7 checks passed
@warengonzaga warengonzaga deleted the tweak/database-init branch October 1, 2025 10:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api API integration and external services (Issues/PRs) deployment Deployment and infrastructure-related (Issues/PRs) hacktoberfest-accepted Hacktoberfest accepted (PRs)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants