parent
6ada2c3fbd
commit
bb48e52a99
@ -35,6 +35,7 @@ jobs:
|
|||||||
docker run -d \
|
docker run -d \
|
||||||
--name chad-server \
|
--name chad-server \
|
||||||
--network traefik \
|
--network traefik \
|
||||||
|
--volume /home/koptilnya/services/chad/database.db:/app/prisma/database.db
|
||||||
-p 40000-40100:40000-40100/udp \
|
-p 40000-40100:40000-40100/udp \
|
||||||
--label "traefik.enable=true" \
|
--label "traefik.enable=true" \
|
||||||
--label "traefik.http.routers.chad-server.rule=Host(\`api.koptilnya.xyz\`) && PathPrefix(\`/chad\`)" \
|
--label "traefik.http.routers.chad-server.rule=Host(\`api.koptilnya.xyz\`) && PathPrefix(\`/chad\`)" \
|
||||||
|
|||||||
1
client/app/components.d.ts
vendored
1
client/app/components.d.ts
vendored
@ -17,7 +17,6 @@ declare module 'vue' {
|
|||||||
PrimeFloatLabel: typeof import('primevue/floatlabel')['default']
|
PrimeFloatLabel: typeof import('primevue/floatlabel')['default']
|
||||||
PrimeInputText: typeof import('primevue/inputtext')['default']
|
PrimeInputText: typeof import('primevue/inputtext')['default']
|
||||||
PrimeMenu: typeof import('primevue/menu')['default']
|
PrimeMenu: typeof import('primevue/menu')['default']
|
||||||
PrimeMessage: typeof import('primevue/message')['default']
|
|
||||||
PrimeSlider: typeof import('primevue/slider')['default']
|
PrimeSlider: typeof import('primevue/slider')['default']
|
||||||
PrimeToast: typeof import('primevue/toast')['default']
|
PrimeToast: typeof import('primevue/toast')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
|
|||||||
3
server/.gitignore
vendored
3
server/.gitignore
vendored
@ -15,3 +15,6 @@ node_modules
|
|||||||
|
|
||||||
#!.yarn/cache
|
#!.yarn/cache
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
.env*
|
||||||
|
*.db
|
||||||
|
|||||||
@ -11,6 +11,8 @@ ENV PORT=80
|
|||||||
ENV CORS_ORIGIN=chad.koptilnya.xyz
|
ENV CORS_ORIGIN=chad.koptilnya.xyz
|
||||||
ENV ANNOUNCED_ADDRESS=91.144.171.182
|
ENV ANNOUNCED_ADDRESS=91.144.171.182
|
||||||
|
|
||||||
|
RUN yarn db:deploy
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
CMD ["yarn", "start"]
|
CMD ["yarn", "start"]
|
||||||
|
|||||||
20
server/auth/lucia.ts
Normal file
20
server/auth/lucia.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { PrismaAdapter } from '@lucia-auth/adapter-prisma'
|
||||||
|
import { Lucia } from 'lucia'
|
||||||
|
import prisma from '../prisma/client'
|
||||||
|
|
||||||
|
export const auth = new Lucia<object, { username: string, displayName: string }>(new PrismaAdapter(prisma.session, prisma.user))
|
||||||
|
|
||||||
|
// export const auth = new Lucia({
|
||||||
|
// adapter: new PrismaAdapter(prisma.session, prisma.user),
|
||||||
|
// env: process.env.NODE_ENV === 'production' ? 'PROD' : 'DEV',
|
||||||
|
// middleware: req => ({
|
||||||
|
// headers: req.headers,
|
||||||
|
// }),
|
||||||
|
// transformUserData: user => ({
|
||||||
|
// id: user.id,
|
||||||
|
// username: user.username,
|
||||||
|
// displayName: user.username,
|
||||||
|
// }),
|
||||||
|
// })
|
||||||
|
|
||||||
|
export type Auth = typeof auth
|
||||||
@ -1,19 +1,33 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "ts-node --transpile-only index.ts"
|
"start": "ts-node --transpile-only server.ts",
|
||||||
|
"db:deploy": "npx prisma migrate deploy && npx prisma generate"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.10.3",
|
"packageManager": "yarn@4.10.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@lucia-auth/adapter-prisma": "^4.0.1",
|
||||||
|
"@prisma/client": "^6.17.0",
|
||||||
|
"@trpc/server": "^11.6.0",
|
||||||
|
"bcrypt": "^6.0.0",
|
||||||
"consola": "^3.4.2",
|
"consola": "^3.4.2",
|
||||||
|
"cookie-parser": "^1.4.7",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
"lucia": "^3.2.2",
|
||||||
"mediasoup": "^3.19.3",
|
"mediasoup": "^3.19.3",
|
||||||
"socket.io": "^4.8.1"
|
"prisma": "^6.17.0",
|
||||||
|
"socket.io": "^4.8.1",
|
||||||
|
"ws": "^8.18.3",
|
||||||
|
"zod": "^4.1.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "^5.4.1",
|
"@antfu/eslint-config": "^5.4.1",
|
||||||
|
"@types/bcrypt": "^6",
|
||||||
|
"@types/cookie-parser": "^1",
|
||||||
"@types/express": "^5.0.3",
|
"@types/express": "^5.0.3",
|
||||||
|
"@types/ws": "^8",
|
||||||
"eslint": "^9.36.0",
|
"eslint": "^9.36.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
|
|||||||
7
server/prisma/client.ts
Normal file
7
server/prisma/client.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
|
||||||
|
const instance = new PrismaClient({
|
||||||
|
log: ['query', 'error', 'warn'],
|
||||||
|
})
|
||||||
|
|
||||||
|
export default instance
|
||||||
29
server/prisma/schema.prisma
Normal file
29
server/prisma/schema.prisma
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
datasource db {
|
||||||
|
provider = "sqlite"
|
||||||
|
url = "file:./database.db"
|
||||||
|
}
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
username String @unique
|
||||||
|
password String
|
||||||
|
displayName String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
Session Session[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Session {
|
||||||
|
id String @id
|
||||||
|
userId String
|
||||||
|
expiresAt DateTime
|
||||||
|
|
||||||
|
user User @relation(references: [id], fields: [userId], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@index([userId])
|
||||||
|
}
|
||||||
21
server/server.ts
Normal file
21
server/server.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { createExpressMiddleware } from '@trpc/server/adapters/express'
|
||||||
|
import cookieParser from 'cookie-parser'
|
||||||
|
import express from 'express'
|
||||||
|
import { createContext } from './trpc/context'
|
||||||
|
import { appRouter } from './trpc/routers'
|
||||||
|
|
||||||
|
const app = express()
|
||||||
|
app.use(cookieParser())
|
||||||
|
app.use(express.json())
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
'/trpc',
|
||||||
|
createExpressMiddleware({
|
||||||
|
router: appRouter,
|
||||||
|
createContext,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
app.listen(process.env.PORT || 4000, () => {
|
||||||
|
console.log('✅ Server running')
|
||||||
|
})
|
||||||
23
server/trpc/context.ts
Normal file
23
server/trpc/context.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import type { CreateExpressContextOptions } from '@trpc/server/adapters/express'
|
||||||
|
import { auth } from '../auth/lucia'
|
||||||
|
|
||||||
|
export async function createContext({ res, req }: CreateExpressContextOptions) {
|
||||||
|
const sessionId = auth.readSessionCookie(req.headers.cookie ?? '')
|
||||||
|
|
||||||
|
if (!sessionId)
|
||||||
|
return { res, req }
|
||||||
|
|
||||||
|
const { session, user } = await auth.validateSession(sessionId)
|
||||||
|
|
||||||
|
if (session && session.fresh) {
|
||||||
|
res.appendHeader('Set-Cookie', auth.createSessionCookie(session.id).serialize())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
res.appendHeader('Set-Cookie', auth.createBlankSessionCookie().serialize())
|
||||||
|
}
|
||||||
|
|
||||||
|
return { res, req, session, user }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Context = Awaited<ReturnType<typeof createContext>>
|
||||||
15
server/trpc/router.ts
Normal file
15
server/trpc/router.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import type { Context } from './context'
|
||||||
|
import { initTRPC } from '@trpc/server'
|
||||||
|
|
||||||
|
const t = initTRPC.context<Context>().create()
|
||||||
|
|
||||||
|
export const router = t.router
|
||||||
|
|
||||||
|
export const publicProcedure = t.procedure
|
||||||
|
|
||||||
|
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
|
||||||
|
if (!ctx.session?.fresh)
|
||||||
|
throw new Error('UNAUTHORIZED')
|
||||||
|
|
||||||
|
return next({ ctx: { ...ctx } })
|
||||||
|
})
|
||||||
74
server/trpc/routers/auth.ts
Normal file
74
server/trpc/routers/auth.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { TRPCError } from '@trpc/server'
|
||||||
|
import bcrypt from 'bcrypt'
|
||||||
|
import { z } from 'zod'
|
||||||
|
import { auth } from '../../auth/lucia'
|
||||||
|
import client from '../../prisma/client'
|
||||||
|
import { protectedProcedure, publicProcedure, router } from '../router'
|
||||||
|
|
||||||
|
export const authRouter = router({
|
||||||
|
register: publicProcedure
|
||||||
|
.input(z.object({ username: z.string().min(1), password: z.string().min(6) }))
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const hashed = await bcrypt.hash(input.password, 10)
|
||||||
|
const user = await client.user.create({
|
||||||
|
data: {
|
||||||
|
username: input.username,
|
||||||
|
password: hashed,
|
||||||
|
displayName: input.username,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const session = await auth.createSession(user.id, {})
|
||||||
|
const cookie = auth.createSessionCookie(session.id)
|
||||||
|
|
||||||
|
ctx.res.setHeader('Set-Cookie', cookie.serialize())
|
||||||
|
|
||||||
|
return { user }
|
||||||
|
}),
|
||||||
|
|
||||||
|
login: publicProcedure
|
||||||
|
.input(z.object({ username: z.string().min(1), password: z.string() }))
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const user = await client.user.findFirst({
|
||||||
|
where: {
|
||||||
|
username: input.username,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'NOT_FOUND',
|
||||||
|
message: 'Incorrect username or password',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const validPassword = await bcrypt.compare(input.password, user.password)
|
||||||
|
|
||||||
|
if (!validPassword) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'NOT_FOUND',
|
||||||
|
message: 'Incorrect username or password',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = await auth.createSession(user.id, {})
|
||||||
|
const cookie = auth.createSessionCookie(session.id)
|
||||||
|
|
||||||
|
ctx.res.setHeader('Set-Cookie', cookie.serialize())
|
||||||
|
|
||||||
|
return { user }
|
||||||
|
}),
|
||||||
|
|
||||||
|
me: protectedProcedure.query(({ ctx }) => {
|
||||||
|
return ctx.user
|
||||||
|
}),
|
||||||
|
|
||||||
|
logout: publicProcedure.mutation(async ({ ctx }) => {
|
||||||
|
if (ctx.session)
|
||||||
|
await auth.invalidateSession(ctx.session.id)
|
||||||
|
|
||||||
|
ctx.res.setHeader('Set-Cookie', auth.createBlankSessionCookie().serialize())
|
||||||
|
|
||||||
|
return true
|
||||||
|
}),
|
||||||
|
})
|
||||||
8
server/trpc/routers/index.ts
Normal file
8
server/trpc/routers/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { router } from '../router'
|
||||||
|
import { authRouter } from './auth'
|
||||||
|
|
||||||
|
export const appRouter = router({
|
||||||
|
auth: authRouter,
|
||||||
|
})
|
||||||
|
|
||||||
|
export type AppRouter = typeof appRouter
|
||||||
1027
server/yarn.lock
1027
server/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user