parent
6ada2c3fbd
commit
bb48e52a99
@ -35,6 +35,7 @@ jobs:
|
||||
docker run -d \
|
||||
--name chad-server \
|
||||
--network traefik \
|
||||
--volume /home/koptilnya/services/chad/database.db:/app/prisma/database.db
|
||||
-p 40000-40100:40000-40100/udp \
|
||||
--label "traefik.enable=true" \
|
||||
--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']
|
||||
PrimeInputText: typeof import('primevue/inputtext')['default']
|
||||
PrimeMenu: typeof import('primevue/menu')['default']
|
||||
PrimeMessage: typeof import('primevue/message')['default']
|
||||
PrimeSlider: typeof import('primevue/slider')['default']
|
||||
PrimeToast: typeof import('primevue/toast')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
|
||||
3
server/.gitignore
vendored
3
server/.gitignore
vendored
@ -15,3 +15,6 @@ node_modules
|
||||
|
||||
#!.yarn/cache
|
||||
.pnp.*
|
||||
|
||||
.env*
|
||||
*.db
|
||||
|
||||
@ -11,6 +11,8 @@ ENV PORT=80
|
||||
ENV CORS_ORIGIN=chad.koptilnya.xyz
|
||||
ENV ANNOUNCED_ADDRESS=91.144.171.182
|
||||
|
||||
RUN yarn db:deploy
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
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",
|
||||
"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",
|
||||
"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",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^5.1.0",
|
||||
"lucia": "^3.2.2",
|
||||
"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": {
|
||||
"@antfu/eslint-config": "^5.4.1",
|
||||
"@types/bcrypt": "^6",
|
||||
"@types/cookie-parser": "^1",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/ws": "^8",
|
||||
"eslint": "^9.36.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"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