diff --git a/client/.env.example b/client/.env.example index 181ec32..b19b25a 100644 --- a/client/.env.example +++ b/client/.env.example @@ -1,17 +1,8 @@ DUMMY_DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public" -# Vercel -POSTGRES_URL= -POSTGRES_PRISMA_URL= -POSTGRES_URL_NO_SSL= -POSTGRES_URL_NON_POOLING= -POSTGRES_USER= -POSTGRES_HOST= -POSTGRES_PASSWORD= -POSTGRES_DATABASE= +# Prisma PULSE_API_KEY= OPTIMIZELY_API_KEY= -CRON_SECRET= # Neon DATABASE_PRISMA_URL= @@ -19,11 +10,18 @@ DATABASE_URL= DATABASE_URL_UNPOOLED= ID= HOST= +NEON_API= # Optional -NEON_API= VERCEL_TOKEN= # UploadThing UPLOADTHING_TOKEN= NEXT_PUBLIC_UPLOADTHING_APP_ID= + +# Other +CRON_SECRET= + +# Stream +NEXT_PUBLIC_STREAM_KEY= +STREAM_SECRET= diff --git a/client/.npmcheckrc.json b/client/.npmcheckrc.json index 36f9c93..0639d61 100644 --- a/client/.npmcheckrc.json +++ b/client/.npmcheckrc.json @@ -6,7 +6,6 @@ "prettier", "prettier-plugin-packagejson", "prettier-plugin-tailwindcss", - "stream-chat", "stream-chat-react" ] } diff --git a/client/src/app/(main)/messages/page.tsx b/client/src/app/(main)/messages/page.tsx new file mode 100644 index 0000000..e69de29 diff --git a/client/src/app/api/get-token/route.ts b/client/src/app/api/get-token/route.ts new file mode 100644 index 0000000..9cf6312 --- /dev/null +++ b/client/src/app/api/get-token/route.ts @@ -0,0 +1,21 @@ +import { validateRequest } from '@/auth' +import { streamServerClient } from '@/lib' +import type { NextRequest } from 'next/server' + +export async function GET(req: NextRequest) { + try { + const { user } = await validateRequest() + + if (!user) { + return Response.json({ error: 'Unauthorized' }, { status: 401 }) + } + const expirationTime = Math.floor(Date.now() / 1000) + 60 * 60 // 1 hour + const issuedAt = Math.floor(Date.now() / 1000) - 60 // current time in seconds + const token = streamServerClient.createToken(user.id, expirationTime, issuedAt) + + return Response.json({ token }) + } catch (error) { + console.error(error) + return Response.json({ error: 'Internal server error' }, { status: 500 }) + } +} diff --git a/client/src/app/api/posts/for-you/route.ts b/client/src/app/api/posts/for-you/route.ts index 34096ea..a3726a1 100644 --- a/client/src/app/api/posts/for-you/route.ts +++ b/client/src/app/api/posts/for-you/route.ts @@ -1,7 +1,7 @@ import { validateRequest } from '@/auth' import { prisma } from '@/lib' import { getPostDataInclude, PostsPage } from '@/types' -import { NextRequest } from 'next/server' +import type { NextRequest } from 'next/server' export const GET = async (req: NextRequest) => { try { diff --git a/client/src/hooks/index.ts b/client/src/hooks/index.ts index f7ff3a9..a62181f 100644 --- a/client/src/hooks/index.ts +++ b/client/src/hooks/index.ts @@ -8,3 +8,4 @@ export { default as useCreateCommentMutation } from './useCreateCommentMutation' export { useUploadThing } from '@/lib/uploadthing' export { default as useDeleteCommentMutation } from './useDeleteCommentMutation' export { default as useDeleteNotificationMutation } from './useDeleteNotificationMutation' +export { default as useInitializeChatClient } from './useInitializeChatClient' diff --git a/client/src/hooks/useInitializeChatClient.ts b/client/src/hooks/useInitializeChatClient.ts new file mode 100644 index 0000000..1e3acb8 --- /dev/null +++ b/client/src/hooks/useInitializeChatClient.ts @@ -0,0 +1,41 @@ +import { useEffect, useState } from 'react' +import { kyInstance } from '@/lib/ky' +import { useSession } from '.' +import { StreamChat } from 'stream-chat' + +const useInitializeChatClient = () => { + const { user } = useSession() + const [chatClient, setChatClient] = useState(null) + + useEffect(() => { + const client = StreamChat.getInstance(process.env.NEXT_PUBLIC_STREAM_KEY as string) + client + .connectUser( + { + id: user.id, + username: user.userName, + name: user.displayName, + image: user.avatarUrl + }, + async () => + kyInstance + .get('/api/get-token') + .json<{ token: string }>() + .then((data) => data.token) + ) + .catch((err) => console.error('Failed to connect to chat client', err)) + .then(() => setChatClient(client)) + + return () => { + setChatClient(null) + client + .disconnectUser() + .catch((err) => console.error('Failed to disconnect user', err)) + .then(() => console.log('Connection closed')) + } + }, [user.id, user.userName, user.displayName, user.avatarUrl]) + + return chatClient +} + +export default useInitializeChatClient diff --git a/client/src/lib/index.ts b/client/src/lib/index.ts index b1b39a4..c42e7fe 100644 --- a/client/src/lib/index.ts +++ b/client/src/lib/index.ts @@ -1,3 +1,4 @@ -export { cn, formatRelativeDate } from './utils' +export * from './utils' export { default as kyInstance } from './ky' export { default as prisma } from './prisma' +export { default as streamServerClient } from './stream' diff --git a/client/src/lib/stream.ts b/client/src/lib/stream.ts new file mode 100644 index 0000000..0f86579 --- /dev/null +++ b/client/src/lib/stream.ts @@ -0,0 +1,8 @@ +import { StreamChat } from 'stream-chat' + +const streamServerClient = StreamChat.getInstance( + process.env.NEXT_PUBLIC_STREAM_KEY as string, + process.env.STREAM_SECRET as string +) + +export default streamServerClient