Skip to content

[feat]: add discord auto-join#404

Merged
apsinghdev merged 3 commits into
mainfrom
feat/discord-integration
Jun 18, 2026
Merged

[feat]: add discord auto-join#404
apsinghdev merged 3 commits into
mainfrom
feat/discord-integration

Conversation

@apsinghdev

@apsinghdev apsinghdev commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Discord community integration for paying members, including an OAuth connect flow and automated Pro Guild joining.
    • Account dashboard now displays subscription start and end dates.
    • Added a Discord community status indicator (connected/joined) and automated handling during scheduled subscription expirations.
    • Added Discord user ID storage with unique mapping to prevent duplicate associations.
  • UI Enhancements

    • Checkout join flow now redirects to an authorization URL from the backend and shows improved join progress/success messaging.

@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
opensox-website Ready Ready Preview, Comment Jun 18, 2026 8:26am

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 814a3280-dc54-46c4-8874-a1121e454429

📥 Commits

Reviewing files that changed from the base of the PR and between 21505ba and 548275a.

📒 Files selected for processing (1)
  • apps/api/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api/src/index.ts

📝 Walkthrough

Walkthrough

Adds end-to-end Discord OAuth 2.0 integration and Pro Guild membership automation. A new discordService handles token exchange, user profile fetching, account persistence, and bot-authenticated guild membership. New API routes cover OAuth connect URL, community status, OAuth callback, and an overhauled cron subscription-expiry job. The web account page and checkout confirmation are updated to use the new OAuth redirect flow.

Changes

Discord OAuth & Pro Guild Automation

Layer / File(s) Summary
DB schema, migration, and environment config
apps/api/prisma/schema.prisma, apps/api/prisma/migrations/.../migration.sql, apps/api/.env.example
Adds optional unique discordUserId field to User model with migration, and documents all Discord/cron env vars in the example config.
HMAC-signed Discord OAuth state utility
apps/api/src/utils/discord-oauth-state.ts
Implements createDiscordOAuthState and verifyDiscordOAuthState with HMAC-SHA256, base64url encoding, timing-safe comparison, and max-age freshness enforcement.
Discord service: OAuth, persistence, and guild membership
apps/api/src/services/discord.service.ts
New discordService object covering automation feature gating, OAuth URL construction, code-for-token exchange, user profile fetch, account upsert with discordUserId linking, and bot-authenticated Pro Guild add/check/remove with timeout-wrapped HTTP calls.
API route handlers: Discord endpoints and cron job
apps/api/src/index.ts
Adds imports for new service/utilities; updates /join-community for automation mode; adds /discord/connect-url, /discord/community-status, /auth/discord/callback; overhauls /internal/jobs/expire-subscriptions with cron secret validation and Discord member removal on expiry.
Post-payment Pro Guild sync
apps/api/src/services/payment.service.ts
After subscription creation, conditionally fetches Discord account and adds user to Pro Guild, silently swallowing errors to preserve the payment flow.
Account page Discord UI
apps/web/src/app/(main)/dashboard/account/page.tsx
AccountPage gains Discord state hooks, connect-URL fetch-and-redirect handler, community-status effect, and AccountPageContent renders join button, subscription dates, and error/success messages.
Checkout confirmation join flow update
apps/web/src/components/checkout/checkout-confirmation.tsx
Updates join handler to call /discord/connect-url and redirect via data.authUrl, adds successMessage state, and updates button labels and error handling.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant WebApp as Web (Account/Checkout)
  participant APIServer as API Server
  participant discordService as Discord Service
  participant DiscordOAuth as Discord OAuth
  participant PrismaDB as Database

  User->>WebApp: Click "Join Pro Community"
  WebApp->>APIServer: GET /discord/connect-url (Bearer token)
  APIServer->>discordService: buildAuthorizationUrl(state)
  APIServer-->>WebApp: { authUrl }
  WebApp->>DiscordOAuth: Redirect to authUrl
  User->>DiscordOAuth: Authorize app
  DiscordOAuth->>APIServer: GET /auth/discord/callback?code=&state=
  APIServer->>discordService: exchangeCodeForToken(code)
  discordService->>DiscordOAuth: POST /oauth2/token
  DiscordOAuth-->>discordService: token response
  discordService->>DiscordOAuth: GET /users/@me
  DiscordOAuth-->>discordService: user profile
  discordService->>PrismaDB: upsert account + update user.discordUserId
  discordService->>DiscordOAuth: PUT /guilds/{guildId}/members/{userId} (bot)
  DiscordOAuth-->>discordService: 201/204
  APIServer-->>WebApp: Redirect to success URL ?discord=joined
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • apsinghdev/opensox#370: Both PRs modify the community-join endpoint and web join handler; this PR extends that flow with full Discord OAuth automation and Pro Guild management on top of the base invite-URL behavior.

Poem

🐇 Hoppin' through OAuth gates so grand,
A Discord guild invite in hand,
HMAC'd state to keep things right,
Bot tokens gleaming in the night,
The Pro Guild waits — just click "Join" today,
This bunny wired the whole relay! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'add discord auto-join' clearly and concisely summarizes the main change: implementing Discord automatic join functionality across the entire changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/discord-integration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (5)
apps/api/src/services/payment.service.ts (1)

343-356: ⚡ Quick win

Add structured context to Discord sync error log.

Per coding guidelines, error logs should include context (userId, endpoint, timestamp) for debugging. The current log message lacks the user identifier.

♻️ Proposed fix
       } catch (error) {
-        // don't fail payment flow if discord sync fails
-        console.error("Failed to sync Discord membership after payment:", error);
+        // don't fail payment flow if discord sync fails
+        console.error(
+          JSON.stringify({
+            level: "error",
+            service: "paymentService",
+            operation: "createSubscription",
+            event: "discord_sync_failed",
+            userId,
+            subscriptionId: subscription.id,
+            timestamp: new Date().toISOString(),
+            errorMessage: error instanceof Error ? error.message : String(error),
+          })
+        );
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/services/payment.service.ts` around lines 343 - 356, The error
log in the catch block within the Discord sync section lacks necessary debugging
context. Update the console.error call to include the userId parameter (which is
available in scope) along with the error message to provide proper context for
troubleshooting Discord membership sync failures. Include the userId in the log
message formatted with the error details to aid debugging.

Source: Coding guidelines

apps/api/src/index.ts (2)

370-398: ⚖️ Poor tradeoff

Sequential updates without transaction risk partial state on failure.

If the job fails midway (e.g., after expiring some subscriptions but before completing the loop), some subscriptions will be marked EXPIRED while others remain ACTIVE despite being past endDate. Consider wrapping the batch in a transaction, or at minimum, ensure the job can be safely re-run (idempotent).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/index.ts` around lines 370 - 398, The for loop iterating over
expiredSubscriptions performs sequential updates to subscription status without
a transaction, risking inconsistent state if the job fails midway. Wrap the
entire for loop in a database transaction using
prismaModule.prisma.$transaction() to make all subscription status updates and
Discord removal operations atomic, ensuring either all subscriptions are
processed or none are, making the job safely re-entrant if it needs to be
retried on failure.

380-386: ⚡ Quick win

Avoid as any; narrow with a proper type.

The coding guidelines require avoiding any and using type guards for dynamic data. Define an interface for the user with the optional discordUserId field, or use a type guard to narrow the type safely.

♻️ Proposed fix
+interface UserWithDiscord {
+  discordUserId?: string | null;
+}
+
 for (const subscription of expiredSubscriptions) {
   await prismaModule.prisma.subscription.update({
     where: { id: subscription.id },
     data: { status: SUBSCRIPTION_STATUS.EXPIRED },
   });

   if (!discordService.isAutomationEnabled()) {
     continue;
   }

-  const discordUserId = (subscription.user as any).discordUserId as
-    | string
-    | null
-    | undefined;
+  const discordUserId = (subscription.user as UserWithDiscord).discordUserId;
   if (!discordUserId) {
     continue;
   }

Alternatively, if discordUserId is defined in the Prisma User model schema (as indicated by the migration in this PR stack), the type should already be available from the generated Prisma types. Verify that the include: { user: true } relation returns the correct typed user object.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/index.ts` around lines 380 - 386, Replace the `as any` type
assertion when accessing `discordUserId` from `subscription.user` with proper
typing. First, verify that the Prisma User model includes the `discordUserId`
field (as indicated by the migration) and that the subscription query properly
includes the user relation. If the Prisma types are correctly generated, remove
the `as any` cast and directly access `subscription.user.discordUserId` to rely
on the proper type from the generated Prisma types. If the field is optional or
conditionally present, define a type guard or interface that properly represents
the user object with the optional `discordUserId` field instead of using `any`.

Source: Coding guidelines

apps/web/src/components/checkout/checkout-confirmation.tsx (1)

20-20: 💤 Low value

successMessage state is never set and renders nothing.

The state is initialized to null, cleared in the handler, but never assigned a meaningful value. On successful API response, the component redirects away via window.location.href before any success message could be shown. This results in dead code.

Either remove the unused state and JSX, or if a success message is needed for a different flow, set it appropriately before redirect.

🧹 Remove unused successMessage state
  const [error, setError] = useState<string | null>(null);
-  const [successMessage, setSuccessMessage] = useState<string | null>(null);
  const [isJoining, setIsJoining] = useState(false);
    setIsJoining(true);
    setError(null);
-    setSuccessMessage(null);
              {error && <p className="text-error-text text-sm mt-2">{error}</p>}
-              {successMessage && (
-                <p className="text-success-text text-sm mt-2">{successMessage}</p>
-              )}

Also applies to: 39-39, 130-132

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/checkout/checkout-confirmation.tsx` at line 20,
Remove the unused successMessage state from the checkout-confirmation component
since it is initialized to null and never assigned a meaningful value before the
component redirects via window.location.href. Delete the successMessage state
declaration (the useState hook), remove any JSX that renders the successMessage,
and remove any code that clears or attempts to set this state variable across
all locations (lines 20, 39, and 130-132 as indicated in the comment).
apps/api/src/services/discord.service.ts (1)

114-121: ⚡ Quick win

replace any with prisma input types on persistence payloads.

using any here removes compile-time guarantees on a critical write path.

as per coding guidelines, "avoid any type; use unknown for truly dynamic data and narrow with type guards".

Also applies to: 154-154

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/services/discord.service.ts` around lines 114 - 121, The
`updateData` and `createData` variables are declared with `any` type, which
removes compile-time type safety on these critical database write operations.
Replace the `any` type annotations with the appropriate Prisma input types for
the Discord account/token entity being updated and created. Determine the
correct Prisma model name from your schema and use its corresponding CreateInput
and UpdateInput types instead of `any`. This fix should be applied to both
occurrences mentioned in the comment.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/index.ts`:
- Around line 269-330: The OAuth callback endpoint at "/auth/discord/callback"
is missing rate limiting protection that other Discord endpoints have. Add the
apiLimiter middleware to this route definition (similar to how it's applied to
the "/discord/connect-url" and "/discord/community-status" endpoints) to prevent
attackers from brute-forcing state tokens or flooding the Discord API with
excessive token exchange requests. Apply the middleware to the app.get call for
the callback endpoint to enforce the rate limit on all incoming requests to this
endpoint.

In `@apps/api/src/services/discord.service.ts`:
- Around line 71-77: All fetch calls to the Discord API endpoints in this
service (the oauth2/token endpoint at lines 71-77 and other Discord API calls at
lines 88-93, 172-184, 195-203, and 220-228) are missing timeout configuration,
which can cause requests to hang indefinitely if Discord is slow or
unresponsive. Add a timeout signal or abort controller to each fetch call to
enforce a reasonable timeout duration (typically a few seconds for external API
calls). Apply this consistently across all Discord API calls in the file to
prevent request handlers from being pinned by slow/hung Discord services.
- Around line 141-155: The account upsert and user update operations are not
atomic, creating a race condition where the account could be reassigned to a new
user while the old user still references the discord ID. Wrap both the account
upsert and user update operations in a prisma.$transaction call to ensure
atomicity. Additionally, add validation logic before the transaction to check if
the discordUserId is already assigned to a different user and block the
reassignment if it would create a cross-user conflict, ensuring data consistency
between the account and user tables.

In `@apps/api/src/utils/discord-oauth-state.ts`:
- Around line 3-8: The getStateSecret function currently uses a hardcoded
fallback string "discord-oauth-state-fallback" which creates a security
vulnerability by allowing forged state values when environment variables are
missing. Remove the hardcoded fallback and instead throw an error if both
DISCORD_OAUTH_STATE_SECRET and JWT_SECRET environment variables are undefined.
This ensures the application fails closed (safely) rather than proceeding with a
predictable secret that could be exploited.

---

Nitpick comments:
In `@apps/api/src/index.ts`:
- Around line 370-398: The for loop iterating over expiredSubscriptions performs
sequential updates to subscription status without a transaction, risking
inconsistent state if the job fails midway. Wrap the entire for loop in a
database transaction using prismaModule.prisma.$transaction() to make all
subscription status updates and Discord removal operations atomic, ensuring
either all subscriptions are processed or none are, making the job safely
re-entrant if it needs to be retried on failure.
- Around line 380-386: Replace the `as any` type assertion when accessing
`discordUserId` from `subscription.user` with proper typing. First, verify that
the Prisma User model includes the `discordUserId` field (as indicated by the
migration) and that the subscription query properly includes the user relation.
If the Prisma types are correctly generated, remove the `as any` cast and
directly access `subscription.user.discordUserId` to rely on the proper type
from the generated Prisma types. If the field is optional or conditionally
present, define a type guard or interface that properly represents the user
object with the optional `discordUserId` field instead of using `any`.

In `@apps/api/src/services/discord.service.ts`:
- Around line 114-121: The `updateData` and `createData` variables are declared
with `any` type, which removes compile-time type safety on these critical
database write operations. Replace the `any` type annotations with the
appropriate Prisma input types for the Discord account/token entity being
updated and created. Determine the correct Prisma model name from your schema
and use its corresponding CreateInput and UpdateInput types instead of `any`.
This fix should be applied to both occurrences mentioned in the comment.

In `@apps/api/src/services/payment.service.ts`:
- Around line 343-356: The error log in the catch block within the Discord sync
section lacks necessary debugging context. Update the console.error call to
include the userId parameter (which is available in scope) along with the error
message to provide proper context for troubleshooting Discord membership sync
failures. Include the userId in the log message formatted with the error details
to aid debugging.

In `@apps/web/src/components/checkout/checkout-confirmation.tsx`:
- Line 20: Remove the unused successMessage state from the checkout-confirmation
component since it is initialized to null and never assigned a meaningful value
before the component redirects via window.location.href. Delete the
successMessage state declaration (the useState hook), remove any JSX that
renders the successMessage, and remove any code that clears or attempts to set
this state variable across all locations (lines 20, 39, and 130-132 as indicated
in the comment).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4a9d6e29-f0ab-46f3-91a8-d308ebb56a09

📥 Commits

Reviewing files that changed from the base of the PR and between da5e3c3 and a0d9882.

📒 Files selected for processing (9)
  • apps/api/.env.example
  • apps/api/prisma/migrations/20260617090052_add_discord_user_id/migration.sql
  • apps/api/prisma/schema.prisma
  • apps/api/src/index.ts
  • apps/api/src/services/discord.service.ts
  • apps/api/src/services/payment.service.ts
  • apps/api/src/utils/discord-oauth-state.ts
  • apps/web/src/app/(main)/dashboard/account/page.tsx
  • apps/web/src/components/checkout/checkout-confirmation.tsx

Comment thread apps/api/src/index.ts Outdated
Comment thread apps/api/src/services/discord.service.ts Outdated
Comment thread apps/api/src/services/discord.service.ts Outdated
Comment thread apps/api/src/utils/discord-oauth-state.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
apps/api/src/index.ts (1)

188-188: ⚡ Quick win

remove any from typed api paths.

line 188 (error: any) and lines 380-383 (subscription.user as any) bypass type safety and can hide contract drift. use unknown for caught errors and strongly type the included user shape.

as per coding guidelines, "apps/api/src/**/*.ts: avoid any type; use unknown for truly dynamic data and narrow with type guards."

proposed fix
-  } catch (error: any) {
+  } catch (error: unknown) {
-      const expiredSubscriptions = await prismaModule.prisma.subscription.findMany({
+      const expiredSubscriptions = await prismaModule.prisma.subscription.findMany({
         where: {
           status: SUBSCRIPTION_STATUS.ACTIVE,
           endDate: { lt: new Date() },
         },
         include: {
-          user: true,
+          user: { select: { discordUserId: true } },
         },
         take: 500,
       });
@@
-        const discordUserId = (subscription.user as any).discordUserId as
-          | string
-          | null
-          | undefined;
+        const discordUserId = subscription.user.discordUserId;

Also applies to: 347-354, 380-383

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/index.ts` at line 188, Replace the `any` type annotation in the
catch block (error: any) with `unknown` to maintain type safety for error
handling. Additionally, remove the `as any` type assertion from
subscription.user and replace it with explicit typing that properly defines the
user shape instead of bypassing type checking. Apply the same changes throughout
the file where similar `any` annotations appear in catch blocks and type
assertions, following the coding guideline to avoid `any` and use `unknown` for
dynamic data that requires type narrowing.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/index.ts`:
- Around line 337-340: The CRON_SECRET validation is currently being performed
lazily within an endpoint handler rather than at application startup, which
violates the fail-fast principle for required environment variables. Move the
CRON_SECRET check (currently checking if !cronSecret) from inside the endpoint
handler to the startup/initialization code at the top level of index.ts,
ensuring the application fails immediately during startup if this required
environment variable is missing. This aligns with the guideline that required
environment variables must be validated during startup in index.ts.
- Around line 347-398: The subscription status is being updated to
SUBSCRIPTION_STATUS.EXPIRED before the Discord removal attempt is made, and
subsequent runs only query for ACTIVE subscriptions. When Discord removal fails
in the catch block, the subscription remains marked as EXPIRED and becomes
invisible to future runs, preventing retry attempts. Fix this by moving the
subscription status update for the EXPIRED status to occur only after a
successful Discord removal (or when Discord automation is disabled). Keep the
subscription in ACTIVE status if Discord removal fails so the next run can retry
the operation.
- Around line 209-217: The verifyToken(token) call on line 210 (and line 242 in
the similar route) currently allows token verification failures to fall into the
outer catch block, which returns a generic 500 error. Instead, add a dedicated
try-catch block around the verifyToken(token) call that catches token
verification errors and returns a 401 status code with an appropriate error
message like "Unauthorized" or "Invalid token", while allowing other unexpected
errors to be caught by the existing outer catch block that returns 500. This
ensures authentication failures are properly distinguished from internal server
errors.

---

Nitpick comments:
In `@apps/api/src/index.ts`:
- Line 188: Replace the `any` type annotation in the catch block (error: any)
with `unknown` to maintain type safety for error handling. Additionally, remove
the `as any` type assertion from subscription.user and replace it with explicit
typing that properly defines the user shape instead of bypassing type checking.
Apply the same changes throughout the file where similar `any` annotations
appear in catch blocks and type assertions, following the coding guideline to
avoid `any` and use `unknown` for dynamic data that requires type narrowing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 93e13375-6677-4697-80c4-dd012ad422ea

📥 Commits

Reviewing files that changed from the base of the PR and between a0d9882 and 21505ba.

📒 Files selected for processing (9)
  • apps/api/.env.example
  • apps/api/prisma/migrations/20260617090052_add_discord_user_id/migration.sql
  • apps/api/prisma/schema.prisma
  • apps/api/src/index.ts
  • apps/api/src/services/discord.service.ts
  • apps/api/src/services/payment.service.ts
  • apps/api/src/utils/discord-oauth-state.ts
  • apps/web/src/app/(main)/dashboard/account/page.tsx
  • apps/web/src/components/checkout/checkout-confirmation.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/api/prisma/migrations/20260617090052_add_discord_user_id/migration.sql
  • apps/api/src/services/payment.service.ts
  • apps/web/src/app/(main)/dashboard/account/page.tsx
  • apps/web/src/components/checkout/checkout-confirmation.tsx
  • apps/api/src/services/discord.service.ts

Comment thread apps/api/src/index.ts
Comment thread apps/api/src/index.ts Outdated
Comment thread apps/api/src/index.ts
@apsinghdev
apsinghdev merged commit b8456ef into main Jun 18, 2026
3 checks passed
@apsinghdev
apsinghdev deleted the feat/discord-integration branch June 18, 2026 09:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant