feat(server): add Prisma schema with all 8 data models

- User, CardType, Membership, WeekTemplate, TimeSlot, Booking, Order, StudioConfig
- PrismaService and global PrismaModule
- snake_case column mapping with camelCase TypeScript fields
- Proper indexes and unique constraints
This commit is contained in:
richarjiang
2026-04-02 12:01:21 +08:00
parent 90b54d1138
commit e653580155
5 changed files with 232 additions and 0 deletions

View File

@@ -0,0 +1,204 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ===== Enums =====
enum UserRole {
MEMBER
ADMIN
}
enum CardTypeCategory {
TIMES
DURATION
TRIAL
}
enum MembershipStatus {
ACTIVE
EXPIRED
USED_UP
}
enum TimeSlotStatus {
OPEN
FULL
CLOSED
}
enum TimeSlotSource {
TEMPLATE
MANUAL
}
enum BookingStatus {
CONFIRMED
CANCELLED
COMPLETED
NO_SHOW
}
enum OrderStatus {
PENDING
PAID
REFUNDED
}
// ===== Models =====
model User {
id String @id @default(uuid())
openid String @unique
unionid String?
phone String?
nickname String @default("")
avatarUrl String? @map("avatar_url")
role UserRole @default(MEMBER)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
memberships Membership[]
bookings Booking[]
orders Order[]
@@map("users")
}
model CardType {
id String @id @default(uuid())
name String
type CardTypeCategory
totalTimes Int? @map("total_times")
durationDays Int @map("duration_days")
price Decimal @db.Decimal(10, 0)
originalPrice Decimal? @map("original_price") @db.Decimal(10, 0)
description String?
isActive Boolean @default(true) @map("is_active")
sortOrder Int @default(0) @map("sort_order")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
memberships Membership[]
orders Order[]
@@map("card_types")
}
model Membership {
id String @id @default(uuid())
userId String @map("user_id")
cardTypeId String @map("card_type_id")
remainingTimes Int? @map("remaining_times")
startDate DateTime @map("start_date")
expireDate DateTime @map("expire_date")
status MembershipStatus @default(ACTIVE)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id])
cardType CardType @relation(fields: [cardTypeId], references: [id])
bookings Booking[]
@@index([userId])
@@index([status])
@@map("memberships")
}
model WeekTemplate {
id String @id @default(uuid())
dayOfWeek Int @map("day_of_week")
startTime String @map("start_time")
endTime String @map("end_time")
capacity Int @default(1)
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
timeSlots TimeSlot[]
@@map("week_templates")
}
model TimeSlot {
id String @id @default(uuid())
date DateTime @db.Date
startTime String @map("start_time")
endTime String @map("end_time")
capacity Int @default(1)
bookedCount Int @default(0) @map("booked_count")
status TimeSlotStatus @default(OPEN)
source TimeSlotSource @default(TEMPLATE)
templateId String? @map("template_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
template WeekTemplate? @relation(fields: [templateId], references: [id])
bookings Booking[]
@@unique([date, startTime, endTime])
@@index([date])
@@index([status])
@@map("time_slots")
}
model Booking {
id String @id @default(uuid())
userId String @map("user_id")
timeSlotId String @map("time_slot_id")
membershipId String @map("membership_id")
status BookingStatus @default(CONFIRMED)
cancelledAt DateTime? @map("cancelled_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id])
timeSlot TimeSlot @relation(fields: [timeSlotId], references: [id])
membership Membership @relation(fields: [membershipId], references: [id])
@@unique([userId, timeSlotId])
@@index([userId])
@@index([status])
@@map("bookings")
}
model Order {
id String @id @default(uuid())
userId String @map("user_id")
cardTypeId String @map("card_type_id")
orderNo String @unique @map("order_no")
amount Decimal @db.Decimal(10, 0)
status OrderStatus @default(PENDING)
wxTransactionId String? @map("wx_transaction_id")
paidAt DateTime? @map("paid_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id])
cardType CardType @relation(fields: [cardTypeId], references: [id])
@@index([userId])
@@index([status])
@@map("orders")
}
model StudioConfig {
id String @id @default(uuid())
name String
logo String?
bannerUrl String? @map("banner_url")
address String @default("")
phone String @default("")
latitude Decimal? @db.Decimal(10, 7)
longitude Decimal? @db.Decimal(10, 7)
cancelHoursLimit Int @default(2) @map("cancel_hours_limit")
photos Json @default("[]")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("studio_config")
}

View File

@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config'
import { AppController } from './app.controller'
import { PrismaModule } from './prisma/prisma.module'
@Module({
imports: [
@@ -8,6 +9,7 @@ import { AppController } from './app.controller'
isGlobal: true,
envFilePath: ['.env.local', '.env'],
}),
PrismaModule,
],
controllers: [AppController],
})

View File

@@ -0,0 +1,9 @@
import { Global, Module } from '@nestjs/common'
import { PrismaService } from './prisma.service'
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}

View File

@@ -0,0 +1,16 @@
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'
import { PrismaClient } from '@prisma/client'
@Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
async onModuleInit() {
await this.$connect()
}
async onModuleDestroy() {
await this.$disconnect()
}
}

File diff suppressed because one or more lines are too long