feat: Docker部署与CI/CD集成, 搜索栏修复, 上传目录改为data
This commit is contained in:
94
prisma/migrations/20251125075143_init/migration.sql
Normal file
94
prisma/migrations/20251125075143_init/migration.sql
Normal file
@@ -0,0 +1,94 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE `User` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`username` VARCHAR(50) NOT NULL,
|
||||
`password` VARCHAR(255) NULL,
|
||||
`avatarUrl` VARCHAR(500) NULL,
|
||||
`role` ENUM('USER', 'ADMIN', 'CREATOR') NOT NULL DEFAULT 'USER',
|
||||
`status` ENUM('ACTIVE', 'BANNED', 'FLAGGED') NOT NULL DEFAULT 'ACTIVE',
|
||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`lastLogin` DATETIME(3) NOT NULL,
|
||||
|
||||
UNIQUE INDEX `User_username_key`(`username`),
|
||||
INDEX `User_username_idx`(`username`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Material` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`title` VARCHAR(100) NOT NULL,
|
||||
`description` TEXT NOT NULL,
|
||||
`type` ENUM('CODE', 'ASSET_ZIP', 'VIDEO') NOT NULL,
|
||||
`contentUrl` VARCHAR(500) NULL,
|
||||
`codeSnippet` LONGTEXT NULL,
|
||||
`language` VARCHAR(20) NULL,
|
||||
`views` INTEGER NOT NULL DEFAULT 0,
|
||||
`downloads` INTEGER NOT NULL DEFAULT 0,
|
||||
`authorId` VARCHAR(191) NOT NULL,
|
||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updatedAt` DATETIME(3) NOT NULL,
|
||||
|
||||
INDEX `Material_type_idx`(`type`),
|
||||
INDEX `Material_authorId_idx`(`authorId`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Comment` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`content` VARCHAR(1000) NOT NULL,
|
||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`authorId` VARCHAR(191) NOT NULL,
|
||||
`materialId` VARCHAR(191) NOT NULL,
|
||||
|
||||
INDEX `Comment_materialId_idx`(`materialId`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Favorite` (
|
||||
`userId` VARCHAR(191) NOT NULL,
|
||||
`materialId` VARCHAR(191) NOT NULL,
|
||||
|
||||
PRIMARY KEY (`userId`, `materialId`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Tag` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(191) NOT NULL,
|
||||
|
||||
UNIQUE INDEX `Tag_name_key`(`name`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `_MaterialToTag` (
|
||||
`A` VARCHAR(191) NOT NULL,
|
||||
`B` INTEGER NOT NULL,
|
||||
|
||||
UNIQUE INDEX `_MaterialToTag_AB_unique`(`A`, `B`),
|
||||
INDEX `_MaterialToTag_B_index`(`B`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Material` ADD CONSTRAINT `Material_authorId_fkey` FOREIGN KEY (`authorId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Comment` ADD CONSTRAINT `Comment_authorId_fkey` FOREIGN KEY (`authorId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Comment` ADD CONSTRAINT `Comment_materialId_fkey` FOREIGN KEY (`materialId`) REFERENCES `Material`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Favorite` ADD CONSTRAINT `Favorite_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Favorite` ADD CONSTRAINT `Favorite_materialId_fkey` FOREIGN KEY (`materialId`) REFERENCES `Material`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `_MaterialToTag` ADD CONSTRAINT `_MaterialToTag_A_fkey` FOREIGN KEY (`A`) REFERENCES `Material`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `_MaterialToTag` ADD CONSTRAINT `_MaterialToTag_B_fkey` FOREIGN KEY (`B`) REFERENCES `Tag`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "mysql"
|
||||
99
prisma/schema.prisma
Normal file
99
prisma/schema.prisma
Normal file
@@ -0,0 +1,99 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "mysql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
enum UserRole {
|
||||
USER
|
||||
ADMIN
|
||||
CREATOR
|
||||
MANAGER
|
||||
}
|
||||
|
||||
enum UserStatus {
|
||||
ACTIVE
|
||||
BANNED
|
||||
FLAGGED
|
||||
}
|
||||
|
||||
enum MaterialType {
|
||||
CODE
|
||||
ASSET_ZIP
|
||||
VIDEO
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
username String @unique @db.VarChar(50)
|
||||
password String? @db.VarChar(255) // Made optional for guest/demo purposes if needed
|
||||
avatarUrl String? @db.VarChar(500)
|
||||
role UserRole @default(CREATOR)
|
||||
status UserStatus @default(ACTIVE)
|
||||
createdAt DateTime @default(now())
|
||||
lastLogin DateTime @updatedAt
|
||||
|
||||
materials Material[]
|
||||
comments Comment[]
|
||||
favorites Favorite[]
|
||||
|
||||
@@index([username])
|
||||
}
|
||||
|
||||
model Material {
|
||||
id String @id @default(uuid())
|
||||
title String @db.VarChar(100)
|
||||
description String @db.Text
|
||||
type MaterialType
|
||||
contentUrl String? @db.VarChar(500)
|
||||
codeSnippet String? @db.LongText
|
||||
language String? @db.VarChar(20)
|
||||
|
||||
views Int @default(0)
|
||||
downloads Int @default(0)
|
||||
|
||||
authorId String
|
||||
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
comments Comment[]
|
||||
favorites Favorite[]
|
||||
tags Tag[]
|
||||
|
||||
@@index([type])
|
||||
@@index([authorId])
|
||||
}
|
||||
|
||||
model Comment {
|
||||
id String @id @default(uuid())
|
||||
content String @db.VarChar(1000)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
authorId String
|
||||
author User @relation(fields: [authorId], references: [id])
|
||||
|
||||
materialId String
|
||||
material Material @relation(fields: [materialId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([materialId])
|
||||
}
|
||||
|
||||
model Favorite {
|
||||
userId String
|
||||
materialId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
material Material @relation(fields: [materialId], references: [id])
|
||||
|
||||
@@id([userId, materialId])
|
||||
}
|
||||
|
||||
model Tag {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
materials Material[]
|
||||
}
|
||||
81
prisma/seed.js
Normal file
81
prisma/seed.js
Normal file
@@ -0,0 +1,81 @@
|
||||
/* eslint-disable */
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function upsertUsers() {
|
||||
const usernames = ['SeedUser_Alpha', 'SeedUser_Beta', 'SeedUser_Gamma', 'SeedUser_Delta'];
|
||||
const authors = [];
|
||||
for (const username of usernames) {
|
||||
const user = await prisma.user.upsert({
|
||||
where: { username },
|
||||
update: {},
|
||||
create: {
|
||||
username,
|
||||
password: null,
|
||||
role: 'CREATOR',
|
||||
status: 'ACTIVE',
|
||||
avatarUrl: `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(username)}`,
|
||||
},
|
||||
});
|
||||
authors.push(user);
|
||||
}
|
||||
return authors;
|
||||
}
|
||||
|
||||
async function upsertTags() {
|
||||
const names = ['code', 'seed', 'demo'];
|
||||
const tags = [];
|
||||
for (const name of names) {
|
||||
const tag = await prisma.tag.upsert({
|
||||
where: { name },
|
||||
update: {},
|
||||
create: { name },
|
||||
});
|
||||
tags.push(tag);
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
async function seedMaterials(authors, tags) {
|
||||
const total = 40;
|
||||
for (let i = 1; i <= total; i++) {
|
||||
const title = `Seed Code #${String(i).padStart(2, '0')}`;
|
||||
const existing = await prisma.material.findFirst({ where: { title, type: 'CODE' } });
|
||||
if (existing) continue;
|
||||
|
||||
const author = authors[i % authors.length];
|
||||
const snippet = `export const seed${i} = () => ${i};`;
|
||||
await prisma.material.create({
|
||||
data: {
|
||||
title,
|
||||
description: 'Seeded code snippet for pagination demo.',
|
||||
type: 'CODE',
|
||||
codeSnippet: snippet,
|
||||
language: 'ts',
|
||||
authorId: author.id,
|
||||
tags: {
|
||||
connect: tags.map(t => ({ id: t.id })),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('[SEED] Starting...');
|
||||
const authors = await upsertUsers();
|
||||
const tags = await upsertTags();
|
||||
await seedMaterials(authors, tags);
|
||||
const count = await prisma.material.count({ where: { type: 'CODE' } });
|
||||
console.log(`[SEED] Done. CODE materials count: ${count}`);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
81
prisma/seed.ts
Normal file
81
prisma/seed.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { PrismaClient, UserRole, UserStatus, MaterialType } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function upsertUsers() {
|
||||
const usernames = ['SeedUser_Alpha', 'SeedUser_Beta', 'SeedUser_Gamma', 'SeedUser_Delta'];
|
||||
const authors = [] as Awaited<ReturnType<typeof prisma.user.upsert>>[];
|
||||
for (const username of usernames) {
|
||||
const user = await prisma.user.upsert({
|
||||
where: { username },
|
||||
update: {},
|
||||
create: {
|
||||
username,
|
||||
password: null,
|
||||
role: UserRole.CREATOR,
|
||||
status: UserStatus.ACTIVE,
|
||||
avatarUrl: `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(username)}`,
|
||||
},
|
||||
});
|
||||
authors.push(user);
|
||||
}
|
||||
return authors;
|
||||
}
|
||||
|
||||
async function upsertTags() {
|
||||
const names = ['code', 'seed', 'demo'];
|
||||
const tags = [] as Awaited<ReturnType<typeof prisma.tag.upsert>>[];
|
||||
for (const name of names) {
|
||||
const tag = await prisma.tag.upsert({
|
||||
where: { name },
|
||||
update: {},
|
||||
create: { name },
|
||||
});
|
||||
tags.push(tag);
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
async function seedMaterials(authors: Awaited<ReturnType<typeof upsertUsers>>, tags: Awaited<ReturnType<typeof upsertTags>>) {
|
||||
const total = 40;
|
||||
for (let i = 1; i <= total; i++) {
|
||||
const title = `Seed Code #${String(i).padStart(2, '0')}`;
|
||||
const existing = await prisma.material.findFirst({ where: { title, type: MaterialType.CODE } });
|
||||
if (existing) continue;
|
||||
|
||||
const author = authors[i % authors.length];
|
||||
const snippet = `export const seed${i} = () => ${i};`;
|
||||
await prisma.material.create({
|
||||
data: {
|
||||
title,
|
||||
description: 'Seeded code snippet for pagination demo.',
|
||||
type: MaterialType.CODE,
|
||||
codeSnippet: snippet,
|
||||
language: 'ts',
|
||||
authorId: author.id,
|
||||
tags: {
|
||||
connect: tags.map(t => ({ id: t.id })),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('[SEED] Starting...');
|
||||
const authors = await upsertUsers();
|
||||
const tags = await upsertTags();
|
||||
await seedMaterials(authors, tags);
|
||||
const count = await prisma.material.count({ where: { type: MaterialType.CODE } });
|
||||
console.log(`[SEED] Done. CODE materials count: ${count}`);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user