feat: add api foundation and booking schema
This commit is contained in:
@@ -4,9 +4,29 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"dev": "tsc -p tsconfig.json --watch",
|
||||
"dev": "tsx watch src/main.ts",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||
"clean": "rimraf dist"
|
||||
"clean": "rimraf dist",
|
||||
"start": "node dist/main.js",
|
||||
"db:generate": "prisma generate",
|
||||
"db:migrate": "prisma migrate dev",
|
||||
"db:seed": "tsx prisma/seed.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^11.0.11",
|
||||
"@nestjs/core": "^11.0.11",
|
||||
"@nestjs/platform-fastify": "^11.0.11",
|
||||
"@prisma/client": "^6.2.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"fastify": "^5.2.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.5",
|
||||
"prisma": "^6.2.1",
|
||||
"tsx": "^4.19.2"
|
||||
}
|
||||
}
|
||||
|
||||
147
apps/api/prisma/schema.prisma
Normal file
147
apps/api/prisma/schema.prisma
Normal file
@@ -0,0 +1,147 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
enum BookingStatus {
|
||||
received
|
||||
contacting
|
||||
confirmed
|
||||
completed
|
||||
cancelled
|
||||
}
|
||||
|
||||
enum BookingRequestType {
|
||||
package
|
||||
private_chef
|
||||
}
|
||||
|
||||
enum ContactPreference {
|
||||
wechat
|
||||
phone
|
||||
none
|
||||
}
|
||||
|
||||
enum FulfillmentType {
|
||||
pickup
|
||||
delivery
|
||||
}
|
||||
|
||||
model BrandProfile {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
tagline String?
|
||||
description String?
|
||||
avatarUrl String?
|
||||
coverUrl String?
|
||||
hours String?
|
||||
address String?
|
||||
deliveryNote String?
|
||||
contactPhone String?
|
||||
contactWechat String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
services Service[]
|
||||
bookings Booking[]
|
||||
customerUsers CustomerUser[]
|
||||
tags Tag[]
|
||||
}
|
||||
|
||||
model Service {
|
||||
id String @id @default(uuid())
|
||||
brandId String
|
||||
brand BrandProfile @relation(fields: [brandId], references: [id])
|
||||
name String
|
||||
description String
|
||||
coverUrl String?
|
||||
priceText String?
|
||||
priceAmount Int?
|
||||
bookingEnabled Boolean @default(true)
|
||||
isEnabled Boolean @default(true)
|
||||
sortOrder Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
bookings Booking[]
|
||||
bookingItems BookingItem[]
|
||||
@@index([brandId, isEnabled, sortOrder])
|
||||
}
|
||||
|
||||
model CustomerUser {
|
||||
id String @id @default(uuid())
|
||||
brandId String
|
||||
brand BrandProfile @relation(fields: [brandId], references: [id])
|
||||
openid String
|
||||
lastLoginAt DateTime @default(now())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
bookings Booking[]
|
||||
@@unique([brandId, openid])
|
||||
}
|
||||
|
||||
model Booking {
|
||||
id String @id @default(uuid())
|
||||
bookingNo String @unique
|
||||
brandId String
|
||||
brand BrandProfile @relation(fields: [brandId], references: [id])
|
||||
customerUserId String
|
||||
customerUser CustomerUser @relation(fields: [customerUserId], references: [id])
|
||||
requestType BookingRequestType
|
||||
customerName String
|
||||
phone String?
|
||||
wechatId String?
|
||||
contactPreference ContactPreference
|
||||
fulfillmentType FulfillmentType?
|
||||
serviceId String?
|
||||
service Service? @relation(fields: [serviceId], references: [id], onDelete: SetNull)
|
||||
requestedDate DateTime?
|
||||
requestedTime String?
|
||||
deliveryAddress String?
|
||||
estimatedAmount Int?
|
||||
extraData Json?
|
||||
note String?
|
||||
adminNote String?
|
||||
status BookingStatus @default(received)
|
||||
isRead Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
items BookingItem[]
|
||||
tags BookingTag[]
|
||||
@@index([brandId, status, createdAt])
|
||||
@@index([customerUserId, createdAt])
|
||||
}
|
||||
|
||||
model BookingItem {
|
||||
id String @id @default(uuid())
|
||||
bookingId String
|
||||
booking Booking @relation(fields: [bookingId], references: [id], onDelete: Cascade)
|
||||
serviceId String?
|
||||
service Service? @relation(fields: [serviceId], references: [id], onDelete: SetNull)
|
||||
serviceNameSnapshot String
|
||||
unitPriceSnapshot Int
|
||||
quantity Int
|
||||
subtotalSnapshot Int
|
||||
}
|
||||
|
||||
model Tag {
|
||||
id String @id @default(uuid())
|
||||
brandId String
|
||||
brand BrandProfile @relation(fields: [brandId], references: [id])
|
||||
name String
|
||||
isPreset Boolean @default(false)
|
||||
isEnabled Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
bookings BookingTag[]
|
||||
@@unique([brandId, name])
|
||||
}
|
||||
|
||||
model BookingTag {
|
||||
bookingId String
|
||||
tagId String
|
||||
booking Booking @relation(fields: [bookingId], references: [id], onDelete: Cascade)
|
||||
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
|
||||
@@id([bookingId, tagId])
|
||||
}
|
||||
59
apps/api/prisma/seed.ts
Normal file
59
apps/api/prisma/seed.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const brand = await prisma.brandProfile.upsert({
|
||||
where: { id: "00000000-0000-0000-0000-000000000001" },
|
||||
update: {},
|
||||
create: {
|
||||
id: "00000000-0000-0000-0000-000000000001",
|
||||
name: "阿成家常菜",
|
||||
tagline: "现做家常套餐,也为小型聚餐定制一桌热饭菜",
|
||||
description: "每天认真做好一顿家常饭。",
|
||||
hours: "周一至周六 10:30-20:00",
|
||||
address: "大学城生活区东门附近",
|
||||
deliveryNote: "目前支持部分学生宿舍楼配送,无法送达时会主动联系。",
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.service.createMany({
|
||||
data: [
|
||||
{
|
||||
brandId: brand.id,
|
||||
name: "红烧肉双拼套餐",
|
||||
description: "红烧肉、时蔬、米饭",
|
||||
priceText: "¥28 / 份",
|
||||
priceAmount: 2800,
|
||||
sortOrder: 1,
|
||||
},
|
||||
{
|
||||
brandId: brand.id,
|
||||
name: "小炒鸡套餐",
|
||||
description: "现炒鸡块、时蔬、米饭",
|
||||
priceText: "¥25 / 份",
|
||||
priceAmount: 2500,
|
||||
sortOrder: 2,
|
||||
},
|
||||
{
|
||||
brandId: brand.id,
|
||||
name: "家庭聚餐私厨",
|
||||
description: "根据人数、地点和口味人工沟通报价",
|
||||
priceText: "面议",
|
||||
bookingEnabled: true,
|
||||
sortOrder: 3,
|
||||
},
|
||||
],
|
||||
skipDuplicates: true,
|
||||
});
|
||||
|
||||
for (const name of ["普通套餐", "私厨", "配送", "自取", "熟客", "重点跟进"]) {
|
||||
await prisma.tag.upsert({
|
||||
where: { brandId_name: { brandId: brand.id, name } },
|
||||
update: {},
|
||||
create: { brandId: brand.id, name, isPreset: true },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
main().finally(() => prisma.$disconnect());
|
||||
@@ -1,3 +1,35 @@
|
||||
export function healthMessage(): string {
|
||||
return "personal-brand-api";
|
||||
import "reflect-metadata";
|
||||
import { Controller, Get, Injectable, Module } from "@nestjs/common";
|
||||
import { NestFactory } from "@nestjs/core";
|
||||
import { FastifyAdapter } from "@nestjs/platform-fastify";
|
||||
import type { NestFastifyApplication } from "@nestjs/platform-fastify";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient {
|
||||
async onModuleDestroy(): Promise<void> {
|
||||
await this.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Controller("health")
|
||||
export class HealthController {
|
||||
@Get()
|
||||
check(): { status: string; service: string } {
|
||||
return { status: "ok", service: "personal-brand-api" };
|
||||
}
|
||||
}
|
||||
|
||||
@Module({ controllers: [HealthController], providers: [PrismaService], exports: [PrismaService] })
|
||||
export class AppModule {}
|
||||
|
||||
export async function createApp(): Promise<NestFastifyApplication> {
|
||||
const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter());
|
||||
app.enableCors();
|
||||
return app;
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== "test") {
|
||||
const app = await createApp();
|
||||
await app.listen({ port: Number(process.env.API_PORT ?? 3001), host: "0.0.0.0" });
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": { "outDir": "dist", "rootDir": "src" },
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user