Skip to content

Commit

Permalink
feat(discord): enhance token refresh and streamline auth flow (#371)
Browse files Browse the repository at this point in the history
* feat(discord): enhance token refresh and role management

Implements robust Discord token refresh mechanism in auth flow and improves role management for Discord-connected users. Adds proper error handling for Discord API interactions and streamlines the account linking process.

* refactor(discord): streamline auth flow and directory structure

Reorganizes Discord integration files for better maintainability:
- Consolidates Discord connection UI components
- Removes redundant callback pages
- Updates environment configuration
- Refines Inngest function setup for Discord operations

* fix: bad line

---------

Co-authored-by: Vojta Holik <vojta@egghead.io>
  • Loading branch information
joelhooks and vojtaholik authored Jan 13, 2025
1 parent 32648d0 commit 44acce6
Show file tree
Hide file tree
Showing 11 changed files with 72 additions and 228 deletions.
160 changes: 0 additions & 160 deletions apps/ai-hero/src/app/discord/callback/page.tsx

This file was deleted.

27 changes: 0 additions & 27 deletions apps/ai-hero/src/app/discord/connect/page.tsx

This file was deleted.

32 changes: 25 additions & 7 deletions apps/ai-hero/src/app/discord/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
import { notFound, redirect } from 'next/navigation'
import { env } from '@/env.mjs'
import * as React from 'react'
import { headers } from 'next/headers'
import { getProviders } from '@/server/auth'

import { DiscordConnectButton } from './discord-connect-button'

export default async function Discord() {
if (env.NEXT_PUBLIC_DISCORD_INVITE_URL === undefined) {
console.error('Discord invite URL is not set')
return notFound()
}
redirect(env.NEXT_PUBLIC_DISCORD_INVITE_URL)
await headers()

const providers = getProviders()

const discordProvider = providers?.discord

return (
<main data-login-template="">
<h1 data-title="">
Join {process.env.NEXT_PUBLIC_PARTNER_FIRST_NAME}{' '}
{process.env.NEXT_PUBLIC_PARTNER_LAST_NAME}'s AI Hero Discord
</h1>

<div data-providers-container="">
{discordProvider ? (
<DiscordConnectButton discordProvider={discordProvider} />
) : null}
</div>
</main>
)
}
10 changes: 10 additions & 0 deletions apps/ai-hero/src/app/discord/redirect/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { notFound, redirect } from 'next/navigation'
import { env } from '@/env.mjs'

export default async function Discord() {
if (env.NEXT_PUBLIC_DISCORD_INVITE_URL === undefined) {
console.error('Discord invite URL is not set')
return notFound()
}
redirect(env.NEXT_PUBLIC_DISCORD_INVITE_URL)
}
2 changes: 2 additions & 0 deletions apps/ai-hero/src/env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export const env = createEnv({
DISCORD_BOT_TOKEN: z.string().optional(),
DISCORD_GUILD_ID: z.string().optional(),
DISCORD_MEMBER_ROLE_ID: z.string().optional(),
DISCORD_SUBSCRIBER_ROLE_ID: z.string().optional(),
DISCORD_PURCHASER_ROLE_ID: z.string().optional(),
DISCORD_CLIENT_ID: z.string().optional(),
CLOUDINARY_API_KEY: z.string(),
Expand Down Expand Up @@ -163,6 +164,7 @@ export const env = createEnv({
DISCORD_BOT_TOKEN: process.env.DISCORD_BOT_TOKEN,
DISCORD_GUILD_ID: process.env.DISCORD_GUILD_ID,
DISCORD_MEMBER_ROLE_ID: process.env.DISCORD_MEMBER_ROLE_ID,
DISCORD_SUBSCRIBER_ROLE_ID: process.env.DISCORD_SUBSCRIBER_ROLE_ID,
DISCORD_PURCHASER_ROLE_ID: process.env.DISCORD_PURCHASER_ROLE_ID,
CLOUDINARY_API_KEY: process.env.CLOUDINARY_API_KEY,
CLOUDINARY_API_SECRET: process.env.CLOUDINARY_API_SECRET,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@ import { env } from '@/env.mjs'
import { inngest } from '@/inngest/inngest.server'
import { DiscordError, DiscordMember } from '@/lib/discord'
import { fetchAsDiscordBot, fetchJsonAsDiscordBot } from '@/lib/discord-query'
import { getSubscriptionStatus } from '@/lib/subscriptions'
import { and, eq } from 'drizzle-orm'

import { NEW_PURCHASE_CREATED_EVENT } from '@coursebuilder/core/inngest/commerce/event-new-purchase-created'
import { NEW_SUBSCRIPTION_CREATED_EVENT } from '@coursebuilder/core/inngest/commerce/event-new-subscription-created'

export const addPurchaseRoleDiscord = inngest.createFunction(
export const addSubscriptionRoleDiscord = inngest.createFunction(
{
id: `add-purchase-role-discord`,
name: 'Add Purchase Role Discord',
id: `add-subscription-role-discord`,
name: 'Add Subscription Role Discord',
},
{ event: NEW_PURCHASE_CREATED_EVENT },
{ event: NEW_SUBSCRIPTION_CREATED_EVENT },
async ({ event, step }) => {
const user = await step.run('get user', async () => {
return db.query.users.findFirst({
where: eq(users.id, event.user.id),
with: {
accounts: true,
purchases: true,
},
})
})
Expand Down Expand Up @@ -48,8 +48,14 @@ export const addPurchaseRoleDiscord = inngest.createFunction(

await step.run('update basic discord roles for user', async () => {
if ('user' in discordMember) {
const { hasActiveSubscription } = await getSubscriptionStatus(user.id)
const roles = Array.from(
new Set([...discordMember.roles, env.DISCORD_MEMBER_ROLE_ID]),
new Set([
...discordMember.roles,
...(hasActiveSubscription
? [env.DISCORD_SUBSCRIBER_ROLE_ID]
: []),
]),
)

console.info('roles', { roles })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { UserSchema } from '@/ability'
import { db } from '@/db'
import { accounts, users } from '@/db/schema'
import { env } from '@/env.mjs'
import { OAUTH_PROVIDER_ACCOUNT_LINKED_EVENT } from '@/inngest/events/oauth-provider-account-linked'
import { inngest } from '@/inngest/inngest.server'
import { DiscordError, DiscordMember } from '@/lib/discord'
import { fetchAsDiscordBot, fetchJsonAsDiscordBot } from '@/lib/discord-query'
import { getSubscriptionStatus } from '@/lib/subscriptions'
import { and, eq } from 'drizzle-orm'

export const discordAccountLinked = inngest.createFunction(
Expand All @@ -20,13 +22,15 @@ export const discordAccountLinked = inngest.createFunction(
const { account, profile } = event.data

const user = await step.run('get user', async () => {
return db.query.users.findFirst({
where: eq(users.id, event.user.id),
with: {
accounts: true,
purchases: true,
},
})
return UserSchema.parse(
db.query.users.findFirst({
where: eq(users.id, event.user.id),
with: {
accounts: true,
purchases: true,
},
}),
)
})

if (!user) throw new Error('No user found')
Expand Down Expand Up @@ -75,22 +79,17 @@ export const discordAccountLinked = inngest.createFunction(

await step.run('update basic discord roles for user', async () => {
if ('user' in discordMember) {
const validPurchases = user.purchases.filter(
(purchase) =>
purchase.status === 'Valid' || purchase.status === 'Restricted',
)

const userHasPurchases = validPurchases.length > 0
const { hasActiveSubscription } = await getSubscriptionStatus(user.id)

const roles = Array.from(
new Set([
...discordMember.roles,
...(userHasPurchases ? [env.DISCORD_MEMBER_ROLE_ID] : []),
...(hasActiveSubscription
? [env.DISCORD_SUBSCRIBER_ROLE_ID]
: []),
]),
)

console.info('roles', { roles })

return await fetchAsDiscordBot(
`guilds/${env.DISCORD_GUILD_ID}/members/${discordAccount.providerAccountId}`,
{
Expand Down
4 changes: 2 additions & 2 deletions apps/ai-hero/src/inngest/inngest.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { imageResourceCreated } from '@/inngest/functions/cloudinary/image-resource-created'
import { addPurchasesConvertkit } from '@/inngest/functions/convertkit/add-purchased-convertkit'
import { addPurchaseRoleDiscord } from '@/inngest/functions/discord/add-purchase-role-discord'
import { addSubscriptionRoleDiscord } from '@/inngest/functions/discord/add-purchase-role-discord'
import { discordAccountLinked } from '@/inngest/functions/discord/discord-account-linked'
import { removePurchaseRoleDiscord } from '@/inngest/functions/discord/remove-purchase-role-discord'
import { emailSendBroadcast } from '@/inngest/functions/email-send-broadcast'
Expand Down Expand Up @@ -38,7 +38,7 @@ export const inngestConfig = {
getOrCreateConcept,
computeVideoSplitPoints,
discordAccountLinked,
addPurchaseRoleDiscord,
addSubscriptionRoleDiscord,
removePurchaseRoleDiscord,
postPurchaseWorkflow,
progressWasMade,
Expand Down
5 changes: 5 additions & 0 deletions apps/ai-hero/src/inngest/inngest.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ import {
RESOURCE_CHAT_REQUEST_EVENT,
ResourceChat,
} from '@coursebuilder/core/inngest/co-gardener/resource-chat'
import {
NEW_SUBSCRIPTION_CREATED_EVENT,
NewSubscriptionCreated,
} from '@coursebuilder/core/inngest/commerce/event-new-subscription-created'
import { createInngestMiddleware } from '@coursebuilder/core/inngest/create-inngest-middleware'
import {
STRIPE_CHECKOUT_SESSION_COMPLETED_EVENT,
Expand Down Expand Up @@ -86,6 +90,7 @@ export type Events = {
[SYNC_PURCHASE_TAGS_EVENT]: SyncPurchaseTags
[STRIPE_CHECKOUT_SESSION_COMPLETED_EVENT]: StripeCheckoutSessionCompleted
[CREATE_USER_ORGANIZATIONS_EVENT]: CreateUserOrganizations
[NEW_SUBSCRIPTION_CREATED_EVENT]: NewSubscriptionCreated
}

const callbackBase =
Expand Down
Loading

0 comments on commit 44acce6

Please sign in to comment.