feat: add Prisma schema (MySQL) and Docker Compose for db/redis

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
WilandClaude Sonnet 5 committed 2026-10-29 10:58:42 -05:00
1 parent 6948fa61f2
commit d5c9aa0be9
9 files changed
+1835 -9174

No files matched your search

+1542
View File
File diff suppressed because it is too large. Load diff
+23
View File
@@ -0,0 +1,23 @@
services:
db:
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-devpassword}
MYSQL_DATABASE: meet
ports:
- "3306:3306"
volumes:
- db_data:/var/lib/mysql
redis:
image: redis:7-alpine
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
db_data:
redis_data:
-9174
View File
File diff suppressed because it is too large. Load diff
+3
View File
@@ -11,6 +11,8 @@
"dependencies": { "dependencies": {
"@base-ui/react": "^1.9.0", "@base-ui/react": "^1.9.0",
"@hookform/resolvers": "^6.0.2", "@hookform/resolvers": "^6.0.2",
"@prisma/adapter-mariadb": "^7.10.1",
"@prisma/client": "^7.10.1",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"cn": "^0.5.0", "cn": "^0.5.0",
"lucide-react": "^1.64.0", "lucide-react": "^1.64.0",
@@ -32,6 +34,7 @@
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "16.4.3", "eslint-config-next": "16.4.3",
"prettier": "^3.9.10", "prettier": "^3.9.10",
"prisma": "7.10.1",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5"
} }
+10
View File
@@ -0,0 +1,10 @@
import path from "node:path";
import "dotenv/config";
import { defineConfig, env } from "@prisma/config";
export default defineConfig({
schema: path.join("prisma", "schema.prisma"),
datasource: {
url: env("DATABASE_URL"),
},
});
@@ -0,0 +1,101 @@
-- CreateTable
CREATE TABLE `users` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`email` VARCHAR(191) NOT NULL,
`password` VARCHAR(191) NOT NULL,
`display_name` VARCHAR(191) NOT NULL,
`role` ENUM('admin', 'user') NOT NULL DEFAULT 'user',
`email_verified_at` DATETIME(3) NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` DATETIME(3) NOT NULL,
UNIQUE INDEX `users_email_key`(`email`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `meetings` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`uuid` VARCHAR(191) NOT NULL,
`title` VARCHAR(191) NOT NULL,
`type` ENUM('instant', 'scheduled') NOT NULL,
`status` ENUM('scheduled', 'active', 'ended') NOT NULL DEFAULT 'scheduled',
`scheduled_at` DATETIME(3) NULL,
`host_user_id` INTEGER NOT NULL,
`started_at` DATETIME(3) NULL,
`ended_at` DATETIME(3) NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` DATETIME(3) NOT NULL,
UNIQUE INDEX `meetings_uuid_key`(`uuid`),
INDEX `meetings_uuid_idx`(`uuid`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `meeting_participants` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`meeting_id` INTEGER NOT NULL,
`user_id` INTEGER NULL,
`guest_name` VARCHAR(191) NULL,
`identity` VARCHAR(191) NOT NULL,
`role` ENUM('host', 'co_host', 'participant') NOT NULL DEFAULT 'participant',
`status` ENUM('pending', 'admitted', 'denied', 'left') NOT NULL DEFAULT 'pending',
`requested_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`admitted_at` DATETIME(3) NULL,
`left_at` DATETIME(3) NULL,
INDEX `meeting_participants_meeting_id_status_idx`(`meeting_id`, `status`),
UNIQUE INDEX `meeting_participants_meeting_id_identity_key`(`meeting_id`, `identity`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `notes` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`meeting_id` INTEGER NOT NULL,
`user_id` INTEGER NOT NULL,
`content` TEXT NOT NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` DATETIME(3) NOT NULL,
UNIQUE INDEX `notes_meeting_id_user_id_key`(`meeting_id`, `user_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `recordings` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`meeting_id` INTEGER NOT NULL,
`egress_id` VARCHAR(191) NOT NULL,
`status` ENUM('in_progress', 'processing', 'completed', 'failed') NOT NULL DEFAULT 'in_progress',
`storage_path` VARCHAR(191) NULL,
`file_size_bytes` BIGINT NULL,
`duration_seconds` INTEGER NULL,
`started_by_user_id` INTEGER NOT NULL,
`started_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`completed_at` DATETIME(3) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `meetings` ADD CONSTRAINT `meetings_host_user_id_fkey` FOREIGN KEY (`host_user_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `meeting_participants` ADD CONSTRAINT `meeting_participants_meeting_id_fkey` FOREIGN KEY (`meeting_id`) REFERENCES `meetings`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `meeting_participants` ADD CONSTRAINT `meeting_participants_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `notes` ADD CONSTRAINT `notes_meeting_id_fkey` FOREIGN KEY (`meeting_id`) REFERENCES `meetings`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `notes` ADD CONSTRAINT `notes_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `recordings` ADD CONSTRAINT `recordings_meeting_id_fkey` FOREIGN KEY (`meeting_id`) REFERENCES `meetings`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `recordings` ADD CONSTRAINT `recordings_started_by_user_id_fkey` FOREIGN KEY (`started_by_user_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+3
View File
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "mysql"
+136
View File
@@ -0,0 +1,136 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
}
enum UserRole {
admin
user
}
enum MeetingType {
instant
scheduled
}
enum MeetingStatus {
scheduled
active
ended
}
enum ParticipantRole {
host
co_host
participant
}
enum ParticipantStatus {
pending
admitted
denied
left
}
enum RecordingStatus {
in_progress
processing
completed
failed
}
model User {
id Int @id @default(autoincrement())
email String @unique
password String
displayName String @map("display_name")
role UserRole @default(user)
emailVerifiedAt DateTime? @map("email_verified_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
hostedMeetings Meeting[] @relation("MeetingHost")
meetingParticipant MeetingParticipant[]
notes Note[]
startedRecordings Recording[] @relation("RecordingStartedBy")
@@map("users")
}
model Meeting {
id Int @id @default(autoincrement())
uuid String @unique
title String
type MeetingType
status MeetingStatus @default(scheduled)
scheduledAt DateTime? @map("scheduled_at")
hostUserId Int @map("host_user_id")
startedAt DateTime? @map("started_at")
endedAt DateTime? @map("ended_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
host User @relation("MeetingHost", fields: [hostUserId], references: [id])
participants MeetingParticipant[]
notes Note[]
recordings Recording[]
@@index([uuid])
@@map("meetings")
}
model MeetingParticipant {
id Int @id @default(autoincrement())
meetingId Int @map("meeting_id")
userId Int? @map("user_id")
guestName String? @map("guest_name")
identity String
role ParticipantRole @default(participant)
status ParticipantStatus @default(pending)
requestedAt DateTime @default(now()) @map("requested_at")
admittedAt DateTime? @map("admitted_at")
leftAt DateTime? @map("left_at")
meeting Meeting @relation(fields: [meetingId], references: [id])
user User? @relation(fields: [userId], references: [id])
@@unique([meetingId, identity])
@@index([meetingId, status])
@@map("meeting_participants")
}
model Note {
id Int @id @default(autoincrement())
meetingId Int @map("meeting_id")
userId Int @map("user_id")
content String @db.Text
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
meeting Meeting @relation(fields: [meetingId], references: [id])
user User @relation(fields: [userId], references: [id])
@@unique([meetingId, userId])
@@map("notes")
}
model Recording {
id Int @id @default(autoincrement())
meetingId Int @map("meeting_id")
egressId String @map("egress_id")
status RecordingStatus @default(in_progress)
storagePath String? @map("storage_path")
fileSizeBytes BigInt? @map("file_size_bytes")
durationSeconds Int? @map("duration_seconds")
startedByUserId Int @map("started_by_user_id")
startedAt DateTime @default(now()) @map("started_at")
completedAt DateTime? @map("completed_at")
meeting Meeting @relation(fields: [meetingId], references: [id])
startedBy User @relation("RecordingStartedBy", fields: [startedByUserId], references: [id])
@@map("recordings")
}
+17
View File
@@ -0,0 +1,17 @@
import { PrismaClient } from "@prisma/client";
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
function createPrismaClient() {
const adapter = new PrismaMariaDb(process.env.DATABASE_URL!);
return new PrismaClient({ adapter });
}
export const prisma = globalForPrisma.prisma ?? createPrismaClient();
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prisma = prisma;
}