create project
This commit is contained in:
34
backend/Dockerfile
Normal file
34
backend/Dockerfile
Normal file
@@ -0,0 +1,34 @@
|
||||
# Stage 1: 依赖安装 + 构建
|
||||
FROM node:20-alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
# 先装依赖(缓存最大化)
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN corepack enable && pnpm install --frozen-lockfile
|
||||
|
||||
# Prisma 客户端生成
|
||||
COPY prisma/ ./prisma/
|
||||
RUN npx prisma generate
|
||||
|
||||
# 复制源码并构建
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# Stage 2: 最小运行时镜像
|
||||
FROM node:20-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# Standalone output 包含所有必要运行时文件
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/prisma ./prisma
|
||||
COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma
|
||||
COPY --from=builder /app/node_modules/prisma ./node_modules/prisma
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
5
backend/next-env.d.ts
vendored
Normal file
5
backend/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
|
||||
6
backend/next.config.mjs
Normal file
6
backend/next.config.mjs
Normal file
@@ -0,0 +1,6 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
output: "standalone",
|
||||
};
|
||||
export default nextConfig;
|
||||
32
backend/package.json
Normal file
32
backend/package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"db:migrate": "prisma migrate dev",
|
||||
"db:seed": "tsx prisma/seed.ts",
|
||||
"db:studio": "prisma studio"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "^14.2.0",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0",
|
||||
"prisma": "^5.14.0",
|
||||
"@prisma/client": "^5.14.0",
|
||||
"zod": "^3.23.0",
|
||||
"jose": "^5.3.0",
|
||||
"bcryptjs": "^2.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.4.0",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/react": "^18.3.0",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/bcryptjs": "^2.4.0",
|
||||
"tsx": "^4.11.0"
|
||||
}
|
||||
}
|
||||
748
backend/pnpm-lock.yaml
generated
Normal file
748
backend/pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,748 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@prisma/client':
|
||||
specifier: ^5.14.0
|
||||
version: 5.22.0(prisma@5.22.0)
|
||||
bcryptjs:
|
||||
specifier: ^2.4.3
|
||||
version: 2.4.3
|
||||
jose:
|
||||
specifier: ^5.3.0
|
||||
version: 5.10.0
|
||||
next:
|
||||
specifier: ^14.2.0
|
||||
version: 14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
prisma:
|
||||
specifier: ^5.14.0
|
||||
version: 5.22.0
|
||||
react:
|
||||
specifier: ^18.3.0
|
||||
version: 18.3.1
|
||||
react-dom:
|
||||
specifier: ^18.3.0
|
||||
version: 18.3.1(react@18.3.1)
|
||||
zod:
|
||||
specifier: ^3.23.0
|
||||
version: 3.25.76
|
||||
devDependencies:
|
||||
'@types/bcryptjs':
|
||||
specifier: ^2.4.0
|
||||
version: 2.4.6
|
||||
'@types/node':
|
||||
specifier: ^20.0.0
|
||||
version: 20.19.41
|
||||
'@types/react':
|
||||
specifier: ^18.3.0
|
||||
version: 18.3.28
|
||||
'@types/react-dom':
|
||||
specifier: ^18.3.0
|
||||
version: 18.3.7(@types/react@18.3.28)
|
||||
tsx:
|
||||
specifier: ^4.11.0
|
||||
version: 4.21.0
|
||||
typescript:
|
||||
specifier: ^5.4.0
|
||||
version: 5.9.3
|
||||
|
||||
packages:
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.7':
|
||||
resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.27.7':
|
||||
resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.27.7':
|
||||
resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.27.7':
|
||||
resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.7':
|
||||
resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.27.7':
|
||||
resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.27.7':
|
||||
resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.27.7':
|
||||
resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.7':
|
||||
resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.7':
|
||||
resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.7':
|
||||
resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.27.7':
|
||||
resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.27.7':
|
||||
resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.7':
|
||||
resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.7':
|
||||
resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@esbuild/sunos-x64@0.27.7':
|
||||
resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.27.7':
|
||||
resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.27.7':
|
||||
resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@next/env@14.2.35':
|
||||
resolution: {integrity: sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==}
|
||||
|
||||
'@next/swc-darwin-arm64@14.2.33':
|
||||
resolution: {integrity: sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-darwin-x64@14.2.33':
|
||||
resolution: {integrity: sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-linux-arm64-gnu@14.2.33':
|
||||
resolution: {integrity: sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-arm64-musl@14.2.33':
|
||||
resolution: {integrity: sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-x64-gnu@14.2.33':
|
||||
resolution: {integrity: sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-x64-musl@14.2.33':
|
||||
resolution: {integrity: sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@14.2.33':
|
||||
resolution: {integrity: sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@next/swc-win32-ia32-msvc@14.2.33':
|
||||
resolution: {integrity: sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@next/swc-win32-x64-msvc@14.2.33':
|
||||
resolution: {integrity: sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@prisma/client@5.22.0':
|
||||
resolution: {integrity: sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==}
|
||||
engines: {node: '>=16.13'}
|
||||
peerDependencies:
|
||||
prisma: '*'
|
||||
peerDependenciesMeta:
|
||||
prisma:
|
||||
optional: true
|
||||
|
||||
'@prisma/debug@5.22.0':
|
||||
resolution: {integrity: sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==}
|
||||
|
||||
'@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2':
|
||||
resolution: {integrity: sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==}
|
||||
|
||||
'@prisma/engines@5.22.0':
|
||||
resolution: {integrity: sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==}
|
||||
|
||||
'@prisma/fetch-engine@5.22.0':
|
||||
resolution: {integrity: sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==}
|
||||
|
||||
'@prisma/get-platform@5.22.0':
|
||||
resolution: {integrity: sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==}
|
||||
|
||||
'@swc/counter@0.1.3':
|
||||
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
||||
|
||||
'@swc/helpers@0.5.5':
|
||||
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
|
||||
|
||||
'@types/bcryptjs@2.4.6':
|
||||
resolution: {integrity: sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==}
|
||||
|
||||
'@types/node@20.19.41':
|
||||
resolution: {integrity: sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==}
|
||||
|
||||
'@types/prop-types@15.7.15':
|
||||
resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
|
||||
|
||||
'@types/react-dom@18.3.7':
|
||||
resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
|
||||
peerDependencies:
|
||||
'@types/react': ^18.0.0
|
||||
|
||||
'@types/react@18.3.28':
|
||||
resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==}
|
||||
|
||||
bcryptjs@2.4.3:
|
||||
resolution: {integrity: sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==}
|
||||
|
||||
busboy@1.6.0:
|
||||
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
|
||||
engines: {node: '>=10.16.0'}
|
||||
|
||||
caniuse-lite@1.0.30001792:
|
||||
resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==}
|
||||
|
||||
client-only@0.0.1:
|
||||
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
||||
|
||||
csstype@3.2.3:
|
||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||
|
||||
esbuild@0.27.7:
|
||||
resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
get-tsconfig@4.14.0:
|
||||
resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==}
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
jose@5.10.0:
|
||||
resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
loose-envify@1.4.0:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
hasBin: true
|
||||
|
||||
nanoid@3.3.12:
|
||||
resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
next@14.2.35:
|
||||
resolution: {integrity: sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==}
|
||||
engines: {node: '>=18.17.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.1.0
|
||||
'@playwright/test': ^1.41.2
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
sass: ^1.3.0
|
||||
peerDependenciesMeta:
|
||||
'@opentelemetry/api':
|
||||
optional: true
|
||||
'@playwright/test':
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
postcss@8.4.31:
|
||||
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
prisma@5.22.0:
|
||||
resolution: {integrity: sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==}
|
||||
engines: {node: '>=16.13'}
|
||||
hasBin: true
|
||||
|
||||
react-dom@18.3.1:
|
||||
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
|
||||
peerDependencies:
|
||||
react: ^18.3.1
|
||||
|
||||
react@18.3.1:
|
||||
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
resolve-pkg-maps@1.0.0:
|
||||
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||
|
||||
scheduler@0.23.2:
|
||||
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
streamsearch@1.1.0:
|
||||
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
styled-jsx@5.1.1:
|
||||
resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': '*'
|
||||
babel-plugin-macros: '*'
|
||||
react: '>= 16.8.0 || 17.x.x || ^18.0.0-0'
|
||||
peerDependenciesMeta:
|
||||
'@babel/core':
|
||||
optional: true
|
||||
babel-plugin-macros:
|
||||
optional: true
|
||||
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
tsx@4.21.0:
|
||||
resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
zod@3.25.76:
|
||||
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@next/env@14.2.35': {}
|
||||
|
||||
'@next/swc-darwin-arm64@14.2.33':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-x64@14.2.33':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-gnu@14.2.33':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-musl@14.2.33':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-gnu@14.2.33':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-musl@14.2.33':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-arm64-msvc@14.2.33':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-ia32-msvc@14.2.33':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-x64-msvc@14.2.33':
|
||||
optional: true
|
||||
|
||||
'@prisma/client@5.22.0(prisma@5.22.0)':
|
||||
optionalDependencies:
|
||||
prisma: 5.22.0
|
||||
|
||||
'@prisma/debug@5.22.0': {}
|
||||
|
||||
'@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2': {}
|
||||
|
||||
'@prisma/engines@5.22.0':
|
||||
dependencies:
|
||||
'@prisma/debug': 5.22.0
|
||||
'@prisma/engines-version': 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2
|
||||
'@prisma/fetch-engine': 5.22.0
|
||||
'@prisma/get-platform': 5.22.0
|
||||
|
||||
'@prisma/fetch-engine@5.22.0':
|
||||
dependencies:
|
||||
'@prisma/debug': 5.22.0
|
||||
'@prisma/engines-version': 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2
|
||||
'@prisma/get-platform': 5.22.0
|
||||
|
||||
'@prisma/get-platform@5.22.0':
|
||||
dependencies:
|
||||
'@prisma/debug': 5.22.0
|
||||
|
||||
'@swc/counter@0.1.3': {}
|
||||
|
||||
'@swc/helpers@0.5.5':
|
||||
dependencies:
|
||||
'@swc/counter': 0.1.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@types/bcryptjs@2.4.6': {}
|
||||
|
||||
'@types/node@20.19.41':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
'@types/prop-types@15.7.15': {}
|
||||
|
||||
'@types/react-dom@18.3.7(@types/react@18.3.28)':
|
||||
dependencies:
|
||||
'@types/react': 18.3.28
|
||||
|
||||
'@types/react@18.3.28':
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.15
|
||||
csstype: 3.2.3
|
||||
|
||||
bcryptjs@2.4.3: {}
|
||||
|
||||
busboy@1.6.0:
|
||||
dependencies:
|
||||
streamsearch: 1.1.0
|
||||
|
||||
caniuse-lite@1.0.30001792: {}
|
||||
|
||||
client-only@0.0.1: {}
|
||||
|
||||
csstype@3.2.3: {}
|
||||
|
||||
esbuild@0.27.7:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.27.7
|
||||
'@esbuild/android-arm': 0.27.7
|
||||
'@esbuild/android-arm64': 0.27.7
|
||||
'@esbuild/android-x64': 0.27.7
|
||||
'@esbuild/darwin-arm64': 0.27.7
|
||||
'@esbuild/darwin-x64': 0.27.7
|
||||
'@esbuild/freebsd-arm64': 0.27.7
|
||||
'@esbuild/freebsd-x64': 0.27.7
|
||||
'@esbuild/linux-arm': 0.27.7
|
||||
'@esbuild/linux-arm64': 0.27.7
|
||||
'@esbuild/linux-ia32': 0.27.7
|
||||
'@esbuild/linux-loong64': 0.27.7
|
||||
'@esbuild/linux-mips64el': 0.27.7
|
||||
'@esbuild/linux-ppc64': 0.27.7
|
||||
'@esbuild/linux-riscv64': 0.27.7
|
||||
'@esbuild/linux-s390x': 0.27.7
|
||||
'@esbuild/linux-x64': 0.27.7
|
||||
'@esbuild/netbsd-arm64': 0.27.7
|
||||
'@esbuild/netbsd-x64': 0.27.7
|
||||
'@esbuild/openbsd-arm64': 0.27.7
|
||||
'@esbuild/openbsd-x64': 0.27.7
|
||||
'@esbuild/openharmony-arm64': 0.27.7
|
||||
'@esbuild/sunos-x64': 0.27.7
|
||||
'@esbuild/win32-arm64': 0.27.7
|
||||
'@esbuild/win32-ia32': 0.27.7
|
||||
'@esbuild/win32-x64': 0.27.7
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
get-tsconfig@4.14.0:
|
||||
dependencies:
|
||||
resolve-pkg-maps: 1.0.0
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
jose@5.10.0: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
loose-envify@1.4.0:
|
||||
dependencies:
|
||||
js-tokens: 4.0.0
|
||||
|
||||
nanoid@3.3.12: {}
|
||||
|
||||
next@14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@next/env': 14.2.35
|
||||
'@swc/helpers': 0.5.5
|
||||
busboy: 1.6.0
|
||||
caniuse-lite: 1.0.30001792
|
||||
graceful-fs: 4.2.11
|
||||
postcss: 8.4.31
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
styled-jsx: 5.1.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 14.2.33
|
||||
'@next/swc-darwin-x64': 14.2.33
|
||||
'@next/swc-linux-arm64-gnu': 14.2.33
|
||||
'@next/swc-linux-arm64-musl': 14.2.33
|
||||
'@next/swc-linux-x64-gnu': 14.2.33
|
||||
'@next/swc-linux-x64-musl': 14.2.33
|
||||
'@next/swc-win32-arm64-msvc': 14.2.33
|
||||
'@next/swc-win32-ia32-msvc': 14.2.33
|
||||
'@next/swc-win32-x64-msvc': 14.2.33
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
postcss@8.4.31:
|
||||
dependencies:
|
||||
nanoid: 3.3.12
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
prisma@5.22.0:
|
||||
dependencies:
|
||||
'@prisma/engines': 5.22.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
react-dom@18.3.1(react@18.3.1):
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
react: 18.3.1
|
||||
scheduler: 0.23.2
|
||||
|
||||
react@18.3.1:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
|
||||
scheduler@0.23.2:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
streamsearch@1.1.0: {}
|
||||
|
||||
styled-jsx@5.1.1(react@18.3.1):
|
||||
dependencies:
|
||||
client-only: 0.0.1
|
||||
react: 18.3.1
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
tsx@4.21.0:
|
||||
dependencies:
|
||||
esbuild: 0.27.7
|
||||
get-tsconfig: 4.14.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
zod@3.25.76: {}
|
||||
60
backend/prisma/schema.prisma
Normal file
60
backend/prisma/schema.prisma
Normal file
@@ -0,0 +1,60 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
phone String @unique
|
||||
passwordHash String
|
||||
nickname String
|
||||
email String?
|
||||
familyIds String[]
|
||||
activeFamilyId String?
|
||||
joinDate DateTime @default(now())
|
||||
createdAt DateTime @default(now())
|
||||
bills Bill[]
|
||||
familiesCreated Family[] @relation("FamilyCreator")
|
||||
}
|
||||
|
||||
model Family {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
inviteCode String @unique
|
||||
createdBy String
|
||||
creator User @relation("FamilyCreator", fields: [createdBy], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
members FamilyMember[]
|
||||
bills Bill[]
|
||||
}
|
||||
|
||||
model FamilyMember {
|
||||
id String @id @default(cuid())
|
||||
familyId String
|
||||
family Family @relation(fields: [familyId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
nickname String
|
||||
role String
|
||||
joinedAt DateTime @default(now())
|
||||
@@unique([familyId, userId])
|
||||
}
|
||||
|
||||
model Bill {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
familyId String?
|
||||
family Family? @relation(fields: [familyId], references: [id])
|
||||
type String
|
||||
amount Float
|
||||
category String
|
||||
note String?
|
||||
emoji String?
|
||||
time String
|
||||
date DateTime
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
75
backend/prisma/seed.ts
Normal file
75
backend/prisma/seed.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const passwordHash = await bcrypt.hash('123456', 10);
|
||||
|
||||
const user = await prisma.user.upsert({
|
||||
where: { phone: '13800138000' },
|
||||
update: {},
|
||||
create: {
|
||||
phone: '13800138000',
|
||||
passwordHash,
|
||||
nickname: '小明',
|
||||
joinDate: new Date('2026-03-01'),
|
||||
},
|
||||
});
|
||||
|
||||
const family = await prisma.family.upsert({
|
||||
where: { id: 'f_preset_home' },
|
||||
update: {},
|
||||
create: {
|
||||
id: 'f_preset_home',
|
||||
name: '小明的家',
|
||||
inviteCode: 'ABCDEF',
|
||||
createdBy: user.id,
|
||||
members: {
|
||||
create: {
|
||||
userId: user.id,
|
||||
nickname: '小明',
|
||||
role: 'owner',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
familyIds: [family.id],
|
||||
activeFamilyId: family.id,
|
||||
},
|
||||
});
|
||||
|
||||
const sampleBills = [
|
||||
{ type: 'expense' as const, amount: 32, category: '餐饮', emoji: '🍜', note: '午餐-兰州拉面', time: '12:30', date: new Date('2026-05-01') },
|
||||
{ type: 'expense' as const, amount: 15, category: '交通', emoji: '🚇', note: '地铁通勤', time: '08:45', date: new Date('2026-05-02') },
|
||||
{ type: 'expense' as const, amount: 128, category: '购物', emoji: '🛒', note: '超市采购', time: '18:20', date: new Date('2026-05-03') },
|
||||
{ type: 'expense' as const, amount: 45, category: '餐饮', emoji: '☕', note: '星巴克咖啡', time: '15:00', date: new Date('2026-05-03') },
|
||||
{ type: 'income' as const, amount: 15000, category: '工资', emoji: '💰', note: '5月工资', time: '10:00', date: new Date('2026-05-05') },
|
||||
{ type: 'expense' as const, amount: 200, category: '居住', emoji: '🏠', note: '物业费', time: '09:00', date: new Date('2026-05-06') },
|
||||
{ type: 'expense' as const, amount: 88, category: '餐饮', emoji: '🍣', note: '和朋友聚餐', time: '19:30', date: new Date('2026-05-07') },
|
||||
{ type: 'expense' as const, amount: 56, category: '交通', emoji: '🚕', note: '打车回家', time: '22:15', date: new Date('2026-05-08') },
|
||||
];
|
||||
|
||||
for (const bill of sampleBills) {
|
||||
await prisma.bill.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
familyId: family.id,
|
||||
...bill,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log('✅ 种子数据已创建:');
|
||||
console.log(' 预设账号: 13800138000 / 123456');
|
||||
console.log(' 家庭: 小明的家');
|
||||
console.log(' 账单: 8 条示例');
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => { console.error(e); process.exit(1); })
|
||||
.finally(() => prisma.$disconnect());
|
||||
44
backend/src/lib/auth.ts
Normal file
44
backend/src/lib/auth.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { SignJWT, jwtVerify } from 'jose';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
const secret = new TextEncoder().encode(process.env.JWT_SECRET || 'fallback-secret');
|
||||
|
||||
export async function signToken(payload: { userId: string; phone: string }) {
|
||||
return new SignJWT(payload)
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setExpirationTime('7d')
|
||||
.sign(secret);
|
||||
}
|
||||
|
||||
export async function verifyToken(token: string) {
|
||||
try {
|
||||
const { payload } = await jwtVerify(token, secret);
|
||||
return payload as { userId: string; phone: string };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export interface AuthenticatedRequest extends NextApiRequest {
|
||||
user?: { userId: string; phone: string };
|
||||
}
|
||||
|
||||
export function withAuth(
|
||||
handler: (req: AuthenticatedRequest, res: NextApiResponse) => Promise<void>
|
||||
) {
|
||||
return async (req: AuthenticatedRequest, res: NextApiResponse) => {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
res.status(401).json({ success: false, error: '未提供认证令牌' });
|
||||
return;
|
||||
}
|
||||
const token = authHeader.slice(7);
|
||||
const payload = await verifyToken(token);
|
||||
if (!payload) {
|
||||
res.status(401).json({ success: false, error: '令牌无效或已过期' });
|
||||
return;
|
||||
}
|
||||
req.user = payload;
|
||||
return handler(req, res);
|
||||
};
|
||||
}
|
||||
19
backend/src/lib/invite-code.ts
Normal file
19
backend/src/lib/invite-code.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
const CHARS = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
||||
|
||||
function randomChar(): string {
|
||||
return CHARS[Math.floor(Math.random() * CHARS.length)];
|
||||
}
|
||||
|
||||
export function generateInviteCode(): string {
|
||||
return Array.from({ length: 6 }, () => randomChar()).join('');
|
||||
}
|
||||
|
||||
export async function createUniqueInviteCode(
|
||||
checkExists: (code: string) => Promise<boolean>
|
||||
): Promise<string> {
|
||||
let code: string;
|
||||
do {
|
||||
code = generateInviteCode();
|
||||
} while (await checkExists(code));
|
||||
return code;
|
||||
}
|
||||
7
backend/src/lib/prisma.ts
Normal file
7
backend/src/lib/prisma.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
|
||||
|
||||
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
|
||||
9
backend/src/lib/response.ts
Normal file
9
backend/src/lib/response.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { NextApiResponse } from 'next';
|
||||
|
||||
export function success<T>(res: NextApiResponse, data: T, status = 200) {
|
||||
return res.status(status).json({ success: true, data });
|
||||
}
|
||||
|
||||
export function error(res: NextApiResponse, message: string, status = 400) {
|
||||
return res.status(status).json({ success: false, error: message });
|
||||
}
|
||||
41
backend/src/pages/api/auth/login.ts
Normal file
41
backend/src/pages/api/auth/login.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { signToken } from '@/lib/auth';
|
||||
import { success, error } from '@/lib/response';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { z } from 'zod';
|
||||
|
||||
const schema = z.object({
|
||||
phone: z.string().regex(/^1\d{10}$/),
|
||||
password: z.string().min(1),
|
||||
});
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method !== 'POST') return error(res, 'Method not allowed', 405);
|
||||
|
||||
const parsed = schema.safeParse(req.body);
|
||||
if (!parsed.success) return error(res, parsed.error.errors[0].message);
|
||||
|
||||
const { phone, password } = parsed.data;
|
||||
const user = await prisma.user.findUnique({ where: { phone } });
|
||||
if (!user) return error(res, '手机号未注册');
|
||||
|
||||
const valid = await bcrypt.compare(password, user.passwordHash);
|
||||
if (!valid) return error(res, '密码错误');
|
||||
|
||||
const token = await signToken({ userId: user.id, phone: user.phone });
|
||||
|
||||
return success(res, {
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
phone: user.phone,
|
||||
nickname: user.nickname,
|
||||
email: user.email,
|
||||
familyIds: user.familyIds,
|
||||
activeFamilyId: user.activeFamilyId,
|
||||
joinDate: user.joinDate.toISOString().slice(0, 7),
|
||||
createdAt: user.createdAt.toISOString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
10
backend/src/pages/api/auth/logout.ts
Normal file
10
backend/src/pages/api/auth/logout.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { success } from '@/lib/response';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method !== 'POST') {
|
||||
res.status(405).json({ success: false, error: 'Method not allowed' });
|
||||
return;
|
||||
}
|
||||
return success(res, { message: '已登出' });
|
||||
}
|
||||
45
backend/src/pages/api/auth/register.ts
Normal file
45
backend/src/pages/api/auth/register.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { signToken } from '@/lib/auth';
|
||||
import { success, error } from '@/lib/response';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { z } from 'zod';
|
||||
|
||||
const schema = z.object({
|
||||
phone: z.string().regex(/^1\d{10}$/, '手机号格式不正确'),
|
||||
password: z.string().min(6, '密码至少6位'),
|
||||
nickname: z.string().min(1, '昵称不能为空').max(20),
|
||||
});
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method !== 'POST') return error(res, 'Method not allowed', 405);
|
||||
|
||||
const parsed = schema.safeParse(req.body);
|
||||
if (!parsed.success) return error(res, parsed.error.errors[0].message);
|
||||
|
||||
const { phone, password, nickname } = parsed.data;
|
||||
|
||||
const existing = await prisma.user.findUnique({ where: { phone } });
|
||||
if (existing) return error(res, '该手机号已注册');
|
||||
|
||||
const passwordHash = await bcrypt.hash(password, 10);
|
||||
const user = await prisma.user.create({
|
||||
data: { phone, passwordHash, nickname, joinDate: new Date() },
|
||||
});
|
||||
|
||||
const token = await signToken({ userId: user.id, phone: user.phone });
|
||||
|
||||
return success(res, {
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
phone: user.phone,
|
||||
nickname: user.nickname,
|
||||
email: user.email,
|
||||
familyIds: user.familyIds,
|
||||
activeFamilyId: user.activeFamilyId,
|
||||
joinDate: user.joinDate.toISOString().slice(0, 7),
|
||||
createdAt: user.createdAt.toISOString(),
|
||||
},
|
||||
}, 201);
|
||||
}
|
||||
56
backend/src/pages/api/bills/index.ts
Normal file
56
backend/src/pages/api/bills/index.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { withAuth, AuthenticatedRequest } from '@/lib/auth';
|
||||
import { success, error } from '@/lib/response';
|
||||
import { z } from 'zod';
|
||||
|
||||
const createSchema = z.object({
|
||||
type: z.enum(['expense', 'income']),
|
||||
amount: z.number().positive(),
|
||||
category: z.string().min(1),
|
||||
note: z.string().optional().default(''),
|
||||
emoji: z.string().optional().default('📝'),
|
||||
time: z.string().regex(/^\d{2}:\d{2}$/),
|
||||
date: z.string(),
|
||||
familyId: z.string().nullable().optional().default(null),
|
||||
});
|
||||
|
||||
async function handler(req: AuthenticatedRequest, res: NextApiResponse) {
|
||||
if (req.method === 'GET') {
|
||||
const { userId, familyId } = req.query;
|
||||
|
||||
const where: any = { userId: req.user!.userId };
|
||||
if (familyId) {
|
||||
where.familyId = familyId as string;
|
||||
} else if (!userId) {
|
||||
where.familyId = null;
|
||||
}
|
||||
|
||||
const bills = await prisma.bill.findMany({
|
||||
where,
|
||||
orderBy: { date: 'desc' },
|
||||
take: 100,
|
||||
});
|
||||
|
||||
return success(res, bills);
|
||||
}
|
||||
|
||||
if (req.method === 'POST') {
|
||||
const parsed = createSchema.safeParse(req.body);
|
||||
if (!parsed.success) return error(res, parsed.error.errors[0].message);
|
||||
|
||||
const bill = await prisma.bill.create({
|
||||
data: {
|
||||
userId: req.user!.userId,
|
||||
...parsed.data,
|
||||
date: new Date(parsed.data.date),
|
||||
},
|
||||
});
|
||||
|
||||
return success(res, bill, 201);
|
||||
}
|
||||
|
||||
return error(res, 'Method not allowed', 405);
|
||||
}
|
||||
|
||||
export default withAuth(handler);
|
||||
19
backend/src/pages/api/families/[id].ts
Normal file
19
backend/src/pages/api/families/[id].ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { withAuth, AuthenticatedRequest } from '@/lib/auth';
|
||||
import { success, error } from '@/lib/response';
|
||||
|
||||
async function handler(req: AuthenticatedRequest, res: NextApiResponse) {
|
||||
if (req.method !== 'GET') return error(res, 'Method not allowed', 405);
|
||||
|
||||
const { id } = req.query;
|
||||
const family = await prisma.family.findUnique({
|
||||
where: { id: id as string },
|
||||
include: { members: true },
|
||||
});
|
||||
|
||||
if (!family) return error(res, '家庭不存在', 404);
|
||||
return success(res, family);
|
||||
}
|
||||
|
||||
export default withAuth(handler);
|
||||
64
backend/src/pages/api/families/index.ts
Normal file
64
backend/src/pages/api/families/index.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { withAuth, AuthenticatedRequest } from '@/lib/auth';
|
||||
import { success, error } from '@/lib/response';
|
||||
import { createUniqueInviteCode } from '@/lib/invite-code';
|
||||
import { z } from 'zod';
|
||||
|
||||
const createSchema = z.object({ name: z.string().min(1).max(30) });
|
||||
|
||||
async function handler(req: AuthenticatedRequest, res: NextApiResponse) {
|
||||
if (req.method === 'GET') {
|
||||
const user = await prisma.user.findUnique({ where: { id: req.user!.userId } });
|
||||
if (!user) return error(res, '用户不存在', 404);
|
||||
|
||||
const families = await prisma.family.findMany({
|
||||
where: { id: { in: user.familyIds } },
|
||||
include: { members: true },
|
||||
});
|
||||
return success(res, families);
|
||||
}
|
||||
|
||||
if (req.method === 'POST') {
|
||||
const parsed = createSchema.safeParse(req.body);
|
||||
if (!parsed.success) return error(res, parsed.error.errors[0].message);
|
||||
|
||||
const inviteCode = await createUniqueInviteCode(async (code) => {
|
||||
const existing = await prisma.family.findUnique({ where: { inviteCode: code } });
|
||||
return !!existing;
|
||||
});
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { id: req.user!.userId } });
|
||||
if (!user) return error(res, '用户不存在', 404);
|
||||
|
||||
const family = await prisma.family.create({
|
||||
data: {
|
||||
name: parsed.data.name,
|
||||
inviteCode,
|
||||
createdBy: req.user!.userId,
|
||||
members: {
|
||||
create: {
|
||||
userId: req.user!.userId,
|
||||
nickname: user.nickname,
|
||||
role: 'owner',
|
||||
},
|
||||
},
|
||||
},
|
||||
include: { members: true },
|
||||
});
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: req.user!.userId },
|
||||
data: {
|
||||
familyIds: { push: family.id },
|
||||
activeFamilyId: family.id,
|
||||
},
|
||||
});
|
||||
|
||||
return success(res, family, 201);
|
||||
}
|
||||
|
||||
return error(res, 'Method not allowed', 405);
|
||||
}
|
||||
|
||||
export default withAuth(handler);
|
||||
48
backend/src/pages/api/families/join.ts
Normal file
48
backend/src/pages/api/families/join.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { withAuth, AuthenticatedRequest } from '@/lib/auth';
|
||||
import { success, error } from '@/lib/response';
|
||||
import { z } from 'zod';
|
||||
|
||||
const schema = z.object({ inviteCode: z.string().length(6) });
|
||||
|
||||
async function handler(req: AuthenticatedRequest, res: NextApiResponse) {
|
||||
if (req.method !== 'POST') return error(res, 'Method not allowed', 405);
|
||||
|
||||
const parsed = schema.safeParse(req.body);
|
||||
if (!parsed.success) return error(res, '邀请码格式不正确');
|
||||
|
||||
const family = await prisma.family.findUnique({
|
||||
where: { inviteCode: parsed.data.inviteCode },
|
||||
include: { members: true },
|
||||
});
|
||||
|
||||
if (!family) return error(res, '邀请码无效');
|
||||
if (family.members.some(m => m.userId === req.user!.userId)) {
|
||||
return error(res, '你已是该家庭成员');
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { id: req.user!.userId } });
|
||||
if (!user) return error(res, '用户不存在', 404);
|
||||
|
||||
await prisma.familyMember.create({
|
||||
data: {
|
||||
familyId: family.id,
|
||||
userId: req.user!.userId,
|
||||
nickname: user.nickname,
|
||||
role: 'member',
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: req.user!.userId },
|
||||
data: {
|
||||
familyIds: [...user.familyIds, family.id],
|
||||
activeFamilyId: family.id,
|
||||
},
|
||||
});
|
||||
|
||||
return success(res, { message: '加入成功', family });
|
||||
}
|
||||
|
||||
export default withAuth(handler);
|
||||
34
backend/src/pages/api/families/leave.ts
Normal file
34
backend/src/pages/api/families/leave.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { withAuth, AuthenticatedRequest } from '@/lib/auth';
|
||||
import { success, error } from '@/lib/response';
|
||||
import { z } from 'zod';
|
||||
|
||||
const schema = z.object({ familyId: z.string() });
|
||||
|
||||
async function handler(req: AuthenticatedRequest, res: NextApiResponse) {
|
||||
if (req.method !== 'POST') return error(res, 'Method not allowed', 405);
|
||||
|
||||
const parsed = schema.safeParse(req.body);
|
||||
if (!parsed.success) return error(res, '参数错误');
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { id: req.user!.userId } });
|
||||
if (!user) return error(res, '用户不存在', 404);
|
||||
|
||||
await prisma.familyMember.deleteMany({
|
||||
where: { familyId: parsed.data.familyId, userId: req.user!.userId },
|
||||
});
|
||||
|
||||
const newFamilyIds = user.familyIds.filter(id => id !== parsed.data.familyId);
|
||||
await prisma.user.update({
|
||||
where: { id: req.user!.userId },
|
||||
data: {
|
||||
familyIds: newFamilyIds,
|
||||
activeFamilyId: newFamilyIds.length > 0 ? newFamilyIds[0] : null,
|
||||
},
|
||||
});
|
||||
|
||||
return success(res, { message: '已退出家庭' });
|
||||
}
|
||||
|
||||
export default withAuth(handler);
|
||||
10
backend/src/pages/api/hello.ts
Normal file
10
backend/src/pages/api/hello.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
type Data = { name: string };
|
||||
|
||||
export default function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<Data>,
|
||||
) {
|
||||
res.status(200).json({ name: 'Family Accounting API' });
|
||||
}
|
||||
25
backend/src/pages/api/user/active-family.ts
Normal file
25
backend/src/pages/api/user/active-family.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { withAuth, AuthenticatedRequest } from '@/lib/auth';
|
||||
import { success, error } from '@/lib/response';
|
||||
import { z } from 'zod';
|
||||
|
||||
const schema = z.object({
|
||||
familyId: z.string().nullable(),
|
||||
});
|
||||
|
||||
async function handler(req: AuthenticatedRequest, res: NextApiResponse) {
|
||||
if (req.method !== 'PUT') return error(res, 'Method not allowed', 405);
|
||||
|
||||
const parsed = schema.safeParse(req.body);
|
||||
if (!parsed.success) return error(res, parsed.error.errors[0].message);
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: req.user!.userId },
|
||||
data: { activeFamilyId: parsed.data.familyId },
|
||||
});
|
||||
|
||||
return success(res, { message: '切换成功' });
|
||||
}
|
||||
|
||||
export default withAuth(handler);
|
||||
25
backend/src/pages/api/user/email.ts
Normal file
25
backend/src/pages/api/user/email.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { withAuth, AuthenticatedRequest } from '@/lib/auth';
|
||||
import { success, error } from '@/lib/response';
|
||||
import { z } from 'zod';
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email('邮箱格式不正确'),
|
||||
});
|
||||
|
||||
async function handler(req: AuthenticatedRequest, res: NextApiResponse) {
|
||||
if (req.method !== 'PUT') return error(res, 'Method not allowed', 405);
|
||||
|
||||
const parsed = schema.safeParse(req.body);
|
||||
if (!parsed.success) return error(res, parsed.error.errors[0].message);
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: req.user!.userId },
|
||||
data: { email: parsed.data.email },
|
||||
});
|
||||
|
||||
return success(res, { message: '邮箱绑定成功' });
|
||||
}
|
||||
|
||||
export default withAuth(handler);
|
||||
24
backend/src/pages/api/user/profile.ts
Normal file
24
backend/src/pages/api/user/profile.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { withAuth, AuthenticatedRequest } from '@/lib/auth';
|
||||
import { success, error } from '@/lib/response';
|
||||
|
||||
async function handler(req: AuthenticatedRequest, res: NextApiResponse) {
|
||||
if (req.method !== 'GET') return error(res, 'Method not allowed', 405);
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { id: req.user!.userId } });
|
||||
if (!user) return error(res, '用户不存在', 404);
|
||||
|
||||
return success(res, {
|
||||
id: user.id,
|
||||
phone: user.phone,
|
||||
nickname: user.nickname,
|
||||
email: user.email,
|
||||
familyIds: user.familyIds,
|
||||
activeFamilyId: user.activeFamilyId,
|
||||
joinDate: user.joinDate.toISOString().slice(0, 7),
|
||||
createdAt: user.createdAt.toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
export default withAuth(handler);
|
||||
21
backend/tsconfig.json
Normal file
21
backend/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [{ "name": "next" }],
|
||||
"paths": { "@/*": ["./src/*"] }
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user