feat: initial chat and abstract auth away from next

This commit is contained in:
2025-03-23 00:14:14 +01:00
parent fbdb43e6df
commit f2c0abbcdc
28 changed files with 250 additions and 50 deletions

28
apps/chat/.gitignore vendored Normal file
View File

@@ -0,0 +1,28 @@
# dev
.yarn/
!.yarn/releases
.vscode/*
!.vscode/launch.json
!.vscode/*.code-snippets
.idea/workspace.xml
.idea/usage.statistics.xml
.idea/shelf
# deps
node_modules/
# env
.env
.env.production
# logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# misc
.DS_Store

8
apps/chat/README.md Normal file
View File

@@ -0,0 +1,8 @@
```
npm install
npm run dev
```
```
open http://localhost:3000
```

19
apps/chat/package.json Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "@hctv/chat",
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts"
},
"dependencies": {
"@hctv/db": "*",
"@hono/node-server": "^1.14.0",
"@hono/node-ws": "^1.1.0",
"@oslojs/encoding": "^1.1.0",
"hono": "^4.7.5"
},
"devDependencies": {
"@types/node": "^20.11.17",
"tsx": "^4.7.1"
}
}

15
apps/chat/src/3d.txt Normal file
View File

@@ -0,0 +1,15 @@
,---, ___
,--.' | ,--.'|_
| | : | | :,'
: : : : : ' : .---.
: | |,--. ,---. .;__,' / /. ./|
| : ' | / \| | | .-' . ' |
| | /' : / / ':__,'| : /___/ \: |
' : | | |. ' / ' : |__. \ ' .
| | ' | :' ; :__ | | '.'|\ \ '
| : :_:,'' | '.'| ; : ; \ \
| | ,' | : : | , / \ \ |
`--'' \ \ / ---`-' '---"
`----'
This is hctv's chat backend. There's not much here, so go back to where you came from :)

27
apps/chat/src/index.ts Normal file
View File

@@ -0,0 +1,27 @@
import { serve } from '@hono/node-server'
import { createNodeWebSocket } from '@hono/node-ws'
import { Hono } from 'hono'
import { readFile } from 'node:fs/promises'
const threed = await readFile('./src/3d.txt', 'utf-8')
const app = new Hono()
const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app })
app.get('/', async (c) => {
return c.text(threed)
})
app.get(
'/ws',
upgradeWebSocket((c) => ({
// https://hono.dev/helpers/websocket
}))
)
serve({
fetch: app.fetch,
port: 8000,
}, (info) => {
console.log(`Server is running on http://localhost:${info.port}`)
})

14
apps/chat/tsconfig.json Normal file
View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"strict": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"types": [
"node"
],
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx",
}
}

View File

@@ -25,6 +25,14 @@ const nextConfig = {
reactStrictMode: false,
output: 'standalone',
outputFileTracingRoot: path.join(__dirname, '../../'),
async rewrites() {
return [
{
source: '/api/chat/:path*',
destination: `http://localhost:8000/:path*`,
},
];
}
};
export default nextConfig;

View File

@@ -14,6 +14,7 @@
"check-types": "tsc --noEmit"
},
"dependencies": {
"@hctv/auth": "*",
"@hctv/db": "*",
"@hookform/resolvers": "^3.9.1",
"@livekit/components-react": "^2.7.0",

View File

@@ -1,4 +1,4 @@
import { validateRequest } from "@/lib/auth";
import { validateRequest } from '@/lib/auth/validate';
import fsP from 'fs/promises';
import fs from 'fs';

View File

@@ -1,4 +1,4 @@
import { validateRequest } from "@/lib/auth";
import { validateRequest } from '@/lib/auth/validate';
import prisma from '@hctv/db';
import { NextRequest } from "next/server";

View File

@@ -1,4 +1,4 @@
import { lucia } from '@/lib/auth';
import { lucia } from '@hctv/auth';
import prisma from '@hctv/db';
import { resolveUserPersonalChannel } from '@/lib/db/resolve';
import type { WebSocket } from 'ws';

View File

@@ -1,4 +1,4 @@
import { validateRequest } from '@/lib/auth';
import { validateRequest } from '@/lib/auth/validate';
import prisma from '@hctv/db';
import { NextRequest } from 'next/server';

View File

@@ -1,4 +1,4 @@
import { validateRequest } from '@/lib/auth';
import { validateRequest } from '@/lib/auth/validate';
import prisma from '@hctv/db';
import type { NextRequest } from 'next/server';

View File

@@ -1,4 +1,4 @@
import { validateRequest } from '@/lib/auth';
import { validateRequest } from '@/lib/auth/validate';
import { redirect, RedirectType } from 'next/navigation';
export default async function Layout({ children }: { children: React.ReactNode }) {

View File

@@ -1,4 +1,4 @@
import { slack, lucia } from '@/lib/auth';
import { slack, lucia } from '@hctv/auth';
import { cookies as nextCookies } from 'next/headers';
import { decodeIdToken, OAuth2RequestError } from 'arctic';
import { generateIdFromEntropySize } from 'lucia';

View File

@@ -1,5 +1,5 @@
import { generateState } from "arctic";
import { slack } from "@/lib/auth";
import { slack } from '@hctv/auth';
import { cookies } from "next/headers";
export async function GET(): Promise<Response> {

View File

@@ -1,4 +1,4 @@
import { validateRequest } from "@/lib/auth";
import { validateRequest } from '@/lib/auth/validate';
import { redirect } from "next/navigation";
import OnboardingClient from "./page.client";

View File

@@ -1,7 +1,7 @@
import LandingPage from '@/components/app/LandingPage/LandingPage';
import { Card, CardContent } from '@/components/ui/card';
import ConfusedDino from '@/components/ui/confuseddino';
import { validateRequest } from '@/lib/auth';
import { validateRequest } from '@/lib/auth/validate';
import prisma from '@hctv/db';
import { Avatar, AvatarImage, AvatarFallback } from '@radix-ui/react-avatar';
import Image from 'next/image';

View File

@@ -3,7 +3,7 @@ import { Inter } from 'next/font/google';
import './globals.css';
import Navbar from '@/components/app/NavBar/NavBar';
import { SessionProvider } from '@/lib/providers/SessionProvider';
import { validateRequest } from '@/lib/auth';
import { validateRequest } from '@/lib/auth/validate';
import { Toaster } from '@/components/ui/sonner';
import { ThemeProvider } from '@/lib/providers/ThemeProvider';
import { SidebarProvider } from '@/components/ui/sidebar';

View File

@@ -1,4 +1,4 @@
import { validateRequest } from '@/lib/auth';
import { validateRequest } from '@/lib/auth/validate';
import prisma from '@hctv/db';
import EditLivestreamDialog from './dialog';

View File

@@ -1,8 +1,6 @@
import { PrismaAdapter } from '@lucia-auth/adapter-prisma';
import { Lucia } from 'lucia';
import prisma from '@hctv/db';
import { cache } from 'react';
import { cookies } from 'next/headers';
import { Slack } from 'arctic';
const adapter = new PrismaAdapter(prisma.session, prisma.user);
@@ -26,33 +24,6 @@ export const lucia = new Lucia(adapter, {
};
},
});
export const validateRequest = cache(async () => {
const sessionId = (await cookies()).get(lucia.sessionCookieName)?.value ?? null;
if (!sessionId)
return {
user: null,
session: null,
};
const { user, session } = await lucia.validateSession(sessionId);
try {
if (session && session.fresh) {
const sessionCookie = lucia.createSessionCookie(session.id);
(await cookies()).set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
}
if (!session) {
const sessionCookie = lucia.createBlankSessionCookie();
(await cookies()).set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
}
} catch {
// Next.js throws error attempting to set cookies when rendering page
}
return {
user,
session,
};
});
declare module 'lucia' {
interface Register {

View File

@@ -1,4 +1,4 @@
import { validateRequest } from ".";
import { validateRequest } from '@/lib/auth/validate';
import prisma from '@hctv/db';
export async function getPersonalChannel(id?: string) {
@@ -15,4 +15,4 @@ export async function getPersonalChannel(id?: string) {
return null;
}
return db.personalChannel;
}
}

View File

@@ -0,0 +1,31 @@
import { cookies } from "next/headers";
import { cache } from "react";
import { lucia } from '@hctv/auth';
export const validateRequest = cache(async () => {
const sessionId = (await cookies()).get(lucia.sessionCookieName)?.value ?? null;
if (!sessionId)
return {
user: null,
session: null,
};
const { user, session } = await lucia.validateSession(sessionId);
try {
if (session && session.fresh) {
const sessionCookie = lucia.createSessionCookie(session.id);
(await cookies()).set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
}
if (!session) {
const sessionCookie = lucia.createBlankSessionCookie();
(await cookies()).set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
}
} catch {
// Next.js throws error attempting to set cookies when rendering page
}
return {
user,
session,
};
});

View File

@@ -1,7 +1,7 @@
'use server';
import { revalidatePath } from 'next/cache';
import { validateRequest } from '../auth';
import { validateRequest } from '@/lib/auth/validate';
import prisma from '@hctv/db';
import zodVerify from '../zodVerify';
import { onboardSchema, streamInfoEditSchema } from './zod';

View File

@@ -0,0 +1,12 @@
{
"name": "@hctv/auth",
"version": "0.0.0",
"exports": {
".": "./src/index.ts"
},
"dependencies": {
"@hctv/db": "*",
"@lucia-auth/adapter-prisma": "^4.0.1",
"arctic": "^3.1.1"
}
}

View File

@@ -0,0 +1,39 @@
import { PrismaAdapter } from '@lucia-auth/adapter-prisma';
import { Lucia } from 'lucia';
import prisma from '@hctv/db';
import { Slack } from 'arctic';
const adapter = new PrismaAdapter(prisma.session, prisma.user);
export const slack = new Slack(process.env.SLACK_ID!, process.env.SLACK_SECRET!, process.env.SLACK_REDIRECT_URI!);
export const lucia = new Lucia(adapter, {
sessionCookie: {
// this sets cookies with super long expiration
// since Next.js doesn't allow Lucia to extend cookie expiration when rendering pages
expires: false,
attributes: {
// set to `true` when using HTTPS
secure: process.env.NODE_ENV === 'production',
},
},
getUserAttributes: (attributes) => {
return {
slack_id: attributes.slack_id,
pfpUrl: attributes.pfpUrl,
hasOnboarded: attributes.hasOnboarded,
};
},
});
declare module 'lucia' {
interface Register {
Lucia: typeof lucia;
DatabaseUserAttributes: DatabaseUserAttributes;
}
}
interface DatabaseUserAttributes {
slack_id: string;
pfpUrl: string;
hasOnboarded: boolean;
}

View File

@@ -22,7 +22,7 @@
"cache": false
},
"dd": {
"persistent": true,
"persistent": false,
"cache": false
},
"db:generate": {

View File

@@ -455,6 +455,18 @@
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.9.tgz#50dea3616bc8191fb8e112283b49eaff03e78429"
integrity sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==
"@hono/node-server@^1.14.0":
version "1.14.0"
resolved "https://registry.yarnpkg.com/@hono/node-server/-/node-server-1.14.0.tgz#b64f7e6b10dbc0fa0642e8a0a1c03c2abf2b391c"
integrity sha512-YUCxJwgHRKSqjrdTk9e4VMGKN27MK5r4+MGPyZTgKH+IYbK+KtYbHeOcPGJ91KGGD6RIQiz2dAHxvjauNhOS8g==
"@hono/node-ws@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@hono/node-ws/-/node-ws-1.1.0.tgz#aefb18f31a2e67d71787a5838110f156fa819183"
integrity sha512-uHaz1EPguJqsUmA+Jmhdi/DTRAMs2Fvcy7qno9E48rlK3WBtyGQw4u4DKlc+o18Nh1DGz2oA1n9hCzEyhVBeLw==
dependencies:
ws "^8.17.0"
"@hookform/resolvers@^3.9.1":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.10.0.tgz#7bfd18113daca4e57e27e1205b7d5a2d371aa59a"
@@ -1478,7 +1490,7 @@
dependencies:
undici-types "~6.20.0"
"@types/node@^20":
"@types/node@^20", "@types/node@^20.11.17":
version "20.17.25"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.25.tgz#3135ad0af2b46a7689aa5ffb3ecafe1f50171a29"
integrity sha512-bT+r2haIlplJUYtlZrEanFHdPIZTeiMeh/fSOEbOOfWf9uTn+lg8g0KU6Q3iMgjd9FLuuMAgfCNSkjUbxL6E3Q==
@@ -2549,7 +2561,7 @@ esbuild-register@3.6.0:
dependencies:
debug "^4.3.4"
"esbuild@>=0.12 <1":
"esbuild@>=0.12 <1", esbuild@~0.25.0:
version "0.25.1"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.1.tgz#a16b8d070b6ad4871935277bda6ccfe852e3fa2f"
integrity sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==
@@ -2959,7 +2971,7 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsevents@2.3.3, fsevents@~2.3.2:
fsevents@2.3.3, fsevents@~2.3.2, fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
@@ -3039,7 +3051,7 @@ get-symbol-description@^1.1.0:
es-errors "^1.3.0"
get-intrinsic "^1.2.6"
get-tsconfig@^4.10.0:
get-tsconfig@^4.10.0, get-tsconfig@^4.7.5:
version "4.10.0"
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.10.0.tgz#403a682b373a823612475a4c2928c7326fc0f6bb"
integrity sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==
@@ -3176,6 +3188,11 @@ hls.js@^1.5.11:
resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-1.5.20.tgz#7eb23bb5e2595311d4e2761038ca6882673de7e2"
integrity sha512-uu0VXUK52JhihhnN/MVVo1lvqNNuhoxkonqgO3IpjvQiGpJBdIXMGkofjQb/j9zvV7a1SW8U9g1FslWx/1HOiQ==
hono@^4.7.5:
version "4.7.5"
resolved "https://registry.yarnpkg.com/hono/-/hono-4.7.5.tgz#5e28b48384971c16e011fe6f7b95668655d4b855"
integrity sha512-fDOK5W2C1vZACsgLONigdZTRZxuBqFtcKh7bUQ5cVSbwI2RWjloJDcgFOVzbQrlI6pCmhlTsVYZ7zpLj4m4qMQ==
htmlparser2@^9.1.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-9.1.0.tgz#cdb498d8a75a51f739b61d3f718136c369bc8c23"
@@ -5068,6 +5085,16 @@ tslib@2.8.1, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.8.
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
tsx@^4.7.1:
version "4.19.3"
resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.19.3.tgz#2bdbcb87089374d933596f8645615142ed727666"
integrity sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==
dependencies:
esbuild "~0.25.0"
get-tsconfig "^4.7.5"
optionalDependencies:
fsevents "~2.3.3"
turbo-darwin-64@2.4.4:
version "2.4.4"
resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-2.4.4.tgz#2508d9b3b358bb91e8745be3e62284621a2b8721"
@@ -5392,7 +5419,7 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
ws@^8.18.1:
ws@^8.17.0, ws@^8.18.1:
version "8.18.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb"
integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==