1
0

update: a more comprehensive prisma schema

This commit is contained in:
alikia2x (寒寒) 2026-02-04 14:39:15 +08:00
parent 3b6bed8c80
commit facb928dff
WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS.
GPG Key ID: 56209E0CCD8420C6
41 changed files with 617 additions and 317 deletions

View File

@ -40,6 +40,15 @@
<excludeFolder url="file://$MODULE_DIR$/packages/core/.turbo" />
<excludeFolder url="file://$MODULE_DIR$/packages/crawler/.turbo" />
<excludeFolder url="file://$MODULE_DIR$/packages/temp_frontend/.turbo" />
<excludeFolder url="file://$MODULE_DIR$/ml_new/datasets" />
<excludeFolder url="file://$MODULE_DIR$/ml_new/logs" />
<excludeFolder url="file://$MODULE_DIR$/packages/cf-worker/.wrangler" />
<excludeFolder url="file://$MODULE_DIR$/packages/core/logs" />
<excludeFolder url="file://$MODULE_DIR$/packages/docs/.react-router" />
<excludeFolder url="file://$MODULE_DIR$/packages/docs/.source" />
<excludeFolder url="file://$MODULE_DIR$/packages/temp_frontend/.react-router" />
<excludeFolder url="file://$MODULE_DIR$/packages/tracker/.react-router" />
<excludeFolder url="file://$MODULE_DIR$/packages/tracker/data" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />

View File

@ -1,17 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourcePerFileMappings">
<file url="file://$PROJECT_DIR$/packages/crawler/db/snapshot.ts" value="0d2dd3d3-bd27-4e5f-b0fa-ff14fb2a6bef" />
<file url="file://$PROJECT_DIR$/packages/crawler/mq/task/removeAllTimeoutSchedules.ts" value="0d2dd3d3-bd27-4e5f-b0fa-ff14fb2a6bef" />
<file url="file://$PROJECT_DIR$/queries/schedule_count.sql" value="0d2dd3d3-bd27-4e5f-b0fa-ff14fb2a6bef" />
<file url="file://$PROJECT_DIR$/queries/schedule_window.sql" value="0d2dd3d3-bd27-4e5f-b0fa-ff14fb2a6bef" />
<file url="file://$PROJECT_DIR$/queries/snapshots_count.sql" value="0d2dd3d3-bd27-4e5f-b0fa-ff14fb2a6bef" />
<file url="file://$PROJECT_DIR$/src/importSnapshots.ts" value="0d2dd3d3-bd27-4e5f-b0fa-ff14fb2a6bef" />
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/01377cfc-46b6-46b4-8d53-4f44f035c69a/console.sql" value="01377cfc-46b6-46b4-8d53-4f44f035c69a" />
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/0d2dd3d3-bd27-4e5f-b0fa-ff14fb2a6bef/console.sql" value="0d2dd3d3-bd27-4e5f-b0fa-ff14fb2a6bef" />
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/0d2dd3d3-bd27-4e5f-b0fa-ff14fb2a6bef/console_1.sql" value="0d2dd3d3-bd27-4e5f-b0fa-ff14fb2a6bef" />
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/0d2dd3d3-bd27-4e5f-b0fa-ff14fb2a6bef/console_3.sql" value="0d2dd3d3-bd27-4e5f-b0fa-ff14fb2a6bef" />
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/0d2dd3d3-bd27-4e5f-b0fa-ff14fb2a6bef/console_4.sql" value="0d2dd3d3-bd27-4e5f-b0fa-ff14fb2a6bef" />
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/a9e5ab87-9e98-49da-b47a-7da0255b6879/console_1.sql" value="a9e5ab87-9e98-49da-b47a-7da0255b6879" />
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/02601ccd-d0d3-42a7-9b8e-7596c6269559/console.sql" value="02601ccd-d0d3-42a7-9b8e-7596c6269559" />
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/388ec495-a827-4dfe-9fa2-219cf8dc32d2/console.sql" value="388ec495-a827-4dfe-9fa2-219cf8dc32d2" />
</component>
</project>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="db-tree-configuration">
<option name="data" value="----------------------------------------&#10;1:0:0d2dd3d3-bd27-4e5f-b0fa-ff14fb2a6bef&#10;2:0:3909a3cf-ec53-4749-8a31-9f90fec87ee1&#10;3:0:a8b3c7a5-107c-454d-bcdc-fa5d721a4df8&#10;4:0:c73d5a8a-cf9a-4e08-bc75-84af9a6f1ba9&#10;5:0:360f07a5-22e0-4aa5-8453-7b7e913f2fc7&#10;6:0:01377cfc-46b6-46b4-8d53-4f44f035c69a&#10;7:0:49463b66-d9a5-472e-b1a8-aa2c8758601c&#10;8:0:a9e5ab87-9e98-49da-b47a-7da0255b6879&#10;" />
<option name="data" value="----------------------------------------&#10;1:0:388ec495-a827-4dfe-9fa2-219cf8dc32d2&#10;2:0:02601ccd-d0d3-42a7-9b8e-7596c6269559&#10;" />
</component>
</project>

View File

@ -2,4 +2,4 @@ node_modules
# Keep environment variables out of version control
.env
/prisma/prisma
/prisma/generated

View File

@ -2,7 +2,7 @@
import { defineConfig, env } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
schema: "prisma/",
migrations: {
path: "prisma/migrations",
},

View File

@ -0,0 +1,6 @@
# Auth Schema
This schema handles Identity and Access Management (IAM).
- **User:** The central account entity.
- **Session:** Tracks active logins, device information. Used in authorization.

View File

@ -0,0 +1,13 @@
model Session {
id String @id
user User @relation(fields: [userId], references: [id])
userId Int @map("user_id")
ipAddress String? @map("ip_address")
userAgent String? @map("user_agent")
secretHash String @map("secret_hash")
createdAt DateTime @default(now()) @map("created_at")
lastVerifiedAt DateTime @map("last_verified_at")
@@map("session")
@@schema("auth")
}

View File

@ -0,0 +1,22 @@
model User {
id Int @id @default(autoincrement())
username String @unique
displayName String @map("display_name")
email String @unique
password String
changesMade History[]
role Role? @relation(fields: [roleId], references: [id])
roleId Int? @map("role_id")
sessions Session[]
artist Artist?
reputation Int @default(0)
reputationHistories ReputationHistory[]
posts Post[]
files File[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("user")
@@schema("auth")
}

View File

@ -0,0 +1,17 @@
# Core Schema
The `core` schema is the heart of the CVSA database. It functions as a structured encyclopedia for the Chinese Singing Voice Synthesis community.
## Design Philosophy
The data here is designed to be **objective and universal**. It models the relationships between music, creators, etc.
## Sub-domains
1. **Music Entities:** Songs, Albums, Lyrics, and Song Series.
2. **Talent:** Artists (producers/illustrators) and Singers (virtual characters).
3. **Classification:** A hierarchical Tag system for genre and thematic categorization.
## Localization
Most entities use `LocalizedField` (JSON) to support multi-language names and descriptions (e.g., Chinese, Japanese, and English).

View File

@ -0,0 +1,17 @@
model Album {
id Int @id @default(autoincrement())
name String?
/// [LocalizedField]
localizedNames Json? @map("localized_names")
description String?
/// [LocalizedField]
localizedDescriptions Json? @map("localized_descriptions")
songInAlbums SongInAlbum[]
coverUrl String? @map("cover_url")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("album")
@@schema("core")
}

View File

@ -0,0 +1,19 @@
model Artist {
id Int @id @default(autoincrement())
name String?
/// [LocalizedField]
localizedNames Json? @map("localized_names")
aliases String[]
description String?
/// [LocalizedField]
localizedDescriptions Json? @map("localized_descriptions")
artistSongRoles ArtistRole[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User? @relation(fields: [userId], references: [id])
userId Int? @unique @map("user_id")
deletedAt DateTime? @map("deleted_at")
@@map("artist")
@@schema("core")
}

View File

@ -0,0 +1,16 @@
model ArtistRole {
id Int @id @default(autoincrement())
role String
/// [LocalizedField]
localizedRoles Json? @map("localized_roles")
song Song @relation(fields: [songId], references: [id])
songId Int @map("song_id")
artist Artist @relation(fields: [artistId], references: [id])
artistId Int @map("artist_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("artist_role")
@@schema("core")
}

View File

@ -0,0 +1,44 @@
model ExternalLink {
id Int @id @default(autoincrement())
label String?
url String
platform Platform?
platformId String? @map("platform_id")
song Song @relation(fields: [songId], references: [id])
songId Int @map("song_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("external_link")
@@schema("core")
}
enum Platform {
YOUTUBE
NICONICO
BILIBILI
VOCALOID_WIKI
VOCALOID_LYRICS_WIKI
SOUNDCLOUD
NETEASE_MUSIC
QQ_MUSIC
FIVE_SING // 5sing.kugou.com
KUGOU
SPOTIFY
APPLE_MUSIC
YOUTUBE_MUSIC
WIKIPEDIA
BAIDU_BAIKE
PIXIV
WEIBO
TWITTER
VOCADB
MOEGIRLPEDIA
MUSICBRAINZ
VCPEDIA
XIAOHONGSHU // aka RedNote
@@map("third_party_platform")
@@schema("core")
}

View File

@ -0,0 +1,11 @@
model Group {
id Int @id @default(autoincrement())
name String
description String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("group")
@@schema("core")
}

View File

@ -0,0 +1,15 @@
model Lyrics {
id Int @id @default(autoincrement())
song Song @relation(fields: [songId], references: [id])
songId Int @map("song_id")
language String?
plainText String? @map("plain_text")
ttml String?
lrc String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("lyrics")
@@schema("core")
}

View File

@ -0,0 +1,20 @@
model Singer {
id Int @id @default(autoincrement())
name String?
avatarUrl String? @map("avatar_url")
/// [LocalizedField]
localizedNames Json? @map("localized_names")
description String?
/// [LocalizedField]
localizedDescriptions Json? @map("localized_descriptions")
singerSvsEngineVersions SingerSvsEngineVersion[]
singerSvsEngines SingerSvsEngine[]
voicebanks Voicebank[]
singerOfSongs SingerOfSong[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("singer")
@@schema("core")
}

View File

@ -0,0 +1,47 @@
model Song {
id Int @id @default(autoincrement())
type SongType?
originalVersion Song? @relation(name: "originalSong", fields: [originalSongId], references: [id])
originalSongId Int? @map("original_song_id")
derivations Song[] @relation(name: "originalSong")
name String?
duration Int?
lyrics Lyrics[]
songTags SongTag[]
/// [LocalizedField]
localizedNames Json? @map("localized_names")
description String?
/// [LocalizedField]
localizedDescriptions Json? @map("localized_descriptions")
songInAlbums SongInAlbum[]
bilibiliAid BigInt? @map("bilibili_aid")
bilibiliBvid String? @map("bilibili_bvid")
vocadbId Int? @map("vocadb_id")
vcpediaId Int? @map("vcpedia_id")
moegirlId Int? @map("moegirl_id")
externalLinks ExternalLink[]
coverUrl String? @map("cover_url")
artists ArtistRole[]
singerOfSongs SingerOfSong[]
publishedAt DateTime? @map("published_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
songInSeries SongInSeries[]
@@map("song")
@@schema("core")
}
enum SongType {
ORIGINAL
COVER
REMIX
REMASTER
MASHUP
INSTRUMENTAL
OTHERS
@@map("song_type")
@@schema("core")
}

View File

@ -0,0 +1,16 @@
model SongSeries {
id Int @id @default(autoincrement())
name String?
/// [LocalizedField]
localizedNames Json? @map("localized_names")
description String?
/// [LocalizedField]
localizedDescriptions Json? @map("localized_descriptions")
songInSeries SongInSeries[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("song_series")
@@schema("core")
}

View File

@ -0,0 +1,16 @@
model SvsEngine {
id Int @id @default(autoincrement())
name String
description String?
/// [LocalizedField]
localizedDescriptions Json? @map("localized_descriptions")
svsEngineVersions SvsEngineVersion[]
singerSvsEngines SingerSvsEngine[]
performances SingerOfSong[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("svs_engine")
@@schema("core")
}

View File

@ -0,0 +1,15 @@
model SvsEngineVersion {
id Int @id @default(autoincrement())
versionString String @map("version_string")
singerSvsEngineVersions SingerSvsEngineVersion[]
svsEngine SvsEngine @relation(fields: [svsEngineId], references: [id])
svsEngineId Int @map("svs_engine_id")
singerOfSongs SingerOfSong[]
voicebanks Voicebank[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("svs_engine_version")
@@schema("core")
}

View File

@ -0,0 +1,16 @@
model Tag {
id Int @id @default(autoincrement())
name String
/// [LocalizedField]
localizedNames Json? @map("localized_names")
songTags SongTag[]
parent Tag? @relation(name: "tagTree", fields: [parentId], references: [id])
parentId Int? @map("parent_id")
children Tag[] @relation(name: "tagTree")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("tag")
@@schema("core")
}

View File

@ -0,0 +1,18 @@
model Voicebank {
id Int @id @default(autoincrement())
singer Singer @relation(fields: [singerId], references: [id])
description String?
/// [LocalizedField]
localizedDescriptions Json? @map("localized_descriptions")
singerId Int @map("singer_id")
language String
svsEngineVersion SvsEngineVersion @relation(fields: [svsEngineVersionId], references: [id])
svsEngineVersionId Int @map("svs_engine_version_id")
singerOfSongs SingerOfSong[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("voicebank")
@@schema("core")
}

View File

@ -0,0 +1,6 @@
# Meta Schema
The `meta` schema manages system governance, auditing, and configuration.
- **Auditing:** The `History` table tracks every change made to the `core` entities (Create/Update/Delete) for rollback and accountability.
- **RBAC:** Implements Role-Based Access Control via `Role` and `Permission` entities.

View File

@ -0,0 +1,25 @@
model History {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int @map("user_id")
table String
objectId Int @map("object_id")
type ChangeType
comment String?
old Json?
new Json?
createdAt DateTime @default(now()) @map("created_at")
@@map("history")
@@schema("meta")
}
enum ChangeType {
CREATE
UPDATE
DELETE
RESTORE
@@map("change_type")
@@schema("meta")
}

View File

@ -0,0 +1,11 @@
model Permission {
id Int @id @default(autoincrement())
action String
roles RolePermission[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("permission")
@@schema("meta")
}

View File

@ -0,0 +1,14 @@
model Role {
id Int @id @default(autoincrement())
name String @unique
/// [LocalizedField]
localizedNames Json? @map("localized_names")
permissions RolePermission[]
users User[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("role")
@@schema("meta")
}

View File

@ -0,0 +1,11 @@
/*
NOTE:
This is an empty placeholder file to ensure the Prisma Language Server functions
correctly, serving as a temporary workaround.
See the link below for details:
https://github.com/prisma/prisma/discussions/24413#discussioncomment-9670600
This issue persists as of Prisma v7.3.0, which was the latest
version at the time of writing.
*/

View File

@ -0,0 +1,15 @@
# Platform Schema
This schema contains tables and logic specific to the **CVSA as a platform**.
## Purpose
While the `core` schema focuses on universal SVS (Singing Voice Synthesis) data that could theoretically be shared across different encyclopedia projects, the `platform` schema manages data unique to this specific implementation.
It includes:
- **User Activity:** Personal notes, bookmarks, and interaction history.
- **System State:** File storage metadata and upload tracking.
and so on.

View File

@ -0,0 +1,11 @@
model File {
id Int @id @default(autoincrement())
originalName String? @map("original_name")
path String?
user User @relation(fields: [userId], references: [id])
userId Int @map("user_id")
createdAt DateTime @default(now()) @map("created_at")
@@map("file")
@@schema("platform")
}

View File

@ -0,0 +1,12 @@
model Post {
id Int @id @default(autoincrement())
text String?
user User @relation(fields: [userId], references: [id])
userId Int @map("user_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("post")
@@schema("platform")
}

View File

@ -0,0 +1,13 @@
model ReputationHistory {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int @map("user_id")
actionType String @map("action_type")
points Int
sourceType Int @map("source_type")
sourceId Int @map("source_id")
createdAt DateTime @default(now()) @map("created_at")
@@map("reputation_history")
@@schema("platform")
}

View File

@ -0,0 +1,6 @@
# Relations
This directory contains join tables for Many-to-Many relationships.
To maintain a clean and modular Prisma schema, we separate core data models (in `/models`) from their association entities.
This structure helps prevent individual `.prisma` files from becoming too large and complex.

View File

@ -0,0 +1,22 @@
// Sometimes, we only know the SVS engine used in the song (e.g. VOCALOID, Synthesizer V, UTAU, etc.).
// In other cases, we know exactly which voicebank was used in the song. (e.g. Luo Tianyi VOCALOID 5 Chinese).
// That's why we need to have both SvsEngine and SvsEngineVersion.
model SingerOfSong {
id Int @id @default(autoincrement())
song Song @relation(fields: [songId], references: [id])
songId Int @map("song_id")
singer Singer @relation(fields: [singerId], references: [id])
singerId Int @map("singer_id")
voicebank Voicebank? @relation(fields: [voicebankId], references: [id])
voicebankId Int? @map("voicebank_id")
svsEngine SvsEngine? @relation(fields: [svsEngineId], references: [id])
svsEngineId Int? @map("svs_engine_id")
svsEngineVersion SvsEngineVersion? @relation(fields: [svsEngineVersionId], references: [id])
svsEngineVersionId Int? @map("svs_engine_version_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("singer_of_song")
@@schema("core")
}

View File

@ -0,0 +1,13 @@
model SingerSvsEngine {
id Int @id @default(autoincrement())
singer Singer @relation(fields: [singerId], references: [id])
singerId Int @map("singer_id")
svsEngine SvsEngine @relation(fields: [svsEngineId], references: [id])
svsEngineId Int @map("svs_engine_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("singer_svs_engine")
@@schema("core")
}

View File

@ -0,0 +1,13 @@
model SingerSvsEngineVersion {
id Int @id @default(autoincrement())
singer Singer @relation(fields: [singerId], references: [id])
singerId Int @map("singer_id")
svsEngineVersion SvsEngineVersion @relation(fields: [svsEngineVersionId], references: [id])
svsEngineVersionId Int @map("svs_engine_version_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("singer_svs_engine_version")
@@schema("core")
}

View File

@ -0,0 +1,15 @@
model SongInAlbum {
id Int @id @default(autoincrement())
song Song @relation(fields: [songId], references: [id])
songId Int @map("song_id")
album Album @relation(fields: [albumId], references: [id])
albumId Int @map("album_id")
trackNumber Int @map("track_number")
discNumber Int @default(1) @map("disc_number")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("song_in_album")
@@schema("core")
}

View File

@ -0,0 +1,14 @@
model SongInSeries {
id Int @id @default(autoincrement())
song Song @relation(fields: [songId], references: [id])
songId Int @map("song_id")
series SongSeries @relation(fields: [seriesId], references: [id])
seriesId Int @map("series_id")
order Int
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("song_in_series")
@@schema("core")
}

View File

@ -0,0 +1,13 @@
model SongTag {
id Int @id @default(autoincrement())
song Song @relation(fields: [songId], references: [id])
songId Int @map("song_id")
tag Tag @relation(fields: [tagId], references: [id])
tagId Int @map("tag_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("song_tag")
@@schema("core")
}

View File

@ -0,0 +1,10 @@
model RolePermission {
id Int @id @default(autoincrement())
role Role @relation(fields: [roleId], references: [id])
roleId Int @map("role_id")
permission Permission @relation(fields: [permissionId], references: [id])
permissionId Int @map("permission_id")
@@map("role_permission")
@@schema("meta")
}

View File

@ -0,0 +1,11 @@
/*
NOTE:
This is an empty placeholder file to ensure the Prisma Language Server functions
correctly, serving as a temporary workaround.
See the link below for details:
https://github.com/prisma/prisma/discussions/24413#discussioncomment-9670600
This issue persists as of Prisma v7.3.0, which was the latest
version at the time of writing.
*/

View File

@ -1,322 +1,60 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client"
output = "./prisma"
output = "./generated"
}
generator json {
provider = "prisma-json-types-generator"
provider = "prisma-json-types-generator"
}
datasource db {
provider = "postgresql"
schemas = ["core", "meta", "platform", "auth"]
}
// TODO
// Note:
// 1. All the IDs are 12-digit string generated via Nano ID with a custom 32-character alphabet.
// 2. Auth-related model has not yet been defined.
// 3. Indexes and unique constraints have not yet been considered in the design.
/*
# CVSA (Chinese Vocal Synthesis Archive) Database Schema
enum SongType {
ORIGINAL
COVER
REMIX
REMASTER
MASHUP
INSTRUMENTAL
OTHERS
## Overview
The CVSA is a structured encyclopedia and data platform dedicated to the Chinese Vocal Synthesis (SVS/VC) community.
It aims to bridge the gap between traditional wikis (like MediaWiki) and structured databases by providing:
- Automation: Fully automated song discovery, metadata extraction, and statistical tracking.
- Structured Data: Moving beyond flat text to rich, relational data for songs, artists, engines, and albums.
- Collaboration: A platform for contributors to provide descriptive content, translations, and corrections.
@@map("song_type")
}
## Schemas
The database is partitioned into several PostgreSQL schemas.
enum Platform {
YOUTUBE
NICONICO
BILIBILI
VOCALOID_WIKI
VOCALOID_LYRIC_SWIKI
SOUNDCLOUD
NETEASE_MUSIC
QQ_MUSIC
FIVE_SING // 5sing.kugou.com
KUGOU
SPOTIFY
APPLE_MUSIC
YOUTUBE_MUSIC
WIKIPEDIA
BAIDU_BAIKE
PIXIV
WEIBO
TWITTER
VOCADB
MOEGIRLPEDIA
MUSICBRAINZ
VCPEDIA
XIAOHONGSHU // aka RedNote
### A. [core] - The Encyclopedia
This is the heart of the project. It stores universal SVS data that remains objective across platforms.
- Entities: Songs, Singers (virtual characters), Artists (producers/illustrators),
SVS Engines (Vocaloid, Synthesizer V), Voicebanks, Albums, and Tags.
- Localization: Uses `LocalizedField` (JSON) to support multi-language metadata
(Simplified Chinese, Traditional Chinese, Japanese, English, etc.).
- Versioning: Supports tracking of specific SVS engine versions and voicebank iterations.
@@map("platform")
}
### B. [platform] - The Service Layer
Contains data specific to the CVSA website as a functional service.
- Features: User-uploaded files, personal notes, etc.
- Scope: Data here is specific to this implementation and not necessarily shared with other archives.
model Song {
id String @id
type SongType?
originalVersion Song? @relation(name: "originalSong", fields: [originalSongId], references: [id])
originalSongId String? @map("original_song_id")
derivations Song[] @relation(name: "originalSong")
name String?
duration Int?
lyrics Lyrics[]
songTags SongTag[]
/// [LocalizedField]
localizedNames Json?
description String?
/// [LocalizedField]
localizedDescriptions Json?
songInAlbums SongInAlbum[]
bilibiliAid BigInt? @map("bilibili_aid")
bilibiliBvid String? @map("bilibili_bvid")
vocadbId Int? @map("vocadb_id")
vcpediaId Int? @map("vcpedia_id")
moegirlId Int? @map("moegirl_id")
externalLinks ExternalLink[]
coverUrl String? @map("cover_url")
artists ArtistRole[]
singerOfSongs SingerOfSong[]
publishedAt DateTime? @map("published_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
### C. [auth] - Identity & Access
Handles user accounts, session persistence, and secure authentication.
- RBAC: Connects users to roles defined in the `meta` schema.
@@map("song")
}
### D. [meta] - Governance & Auditing
Manages the "data about data."
- Audit Logs: The `History` table tracks every mutation (Create/Update/Delete)
in the `core` schema for accountability and rollbacks.
- Permissions: Granular action-based permissions assigned to roles.
model Tag {
id String @id
name String
/// [LocalizedField]
localizedNames Json?
songTags SongTag[]
parent Tag? @relation(name: "tagTree", fields: [parentId], references: [id])
parentId String? @map("parent_id")
children Tag[] @relation(name: "tagTree")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
## 3. Technical Constraints & Design Patterns
- Soft Deletion: Most core entities implement a `deletedAt` timestamp for soft-deletion support.
- Modular Relations: Many-to-Many relations are extracted into separate files
within the `/relations` directory to keep entity definitions clean and prevent Prisma file bloat.
@@map("tag")
}
## 4. Current Status & TODO
- Indexing: Performance indexes and unique constraints beyond primary keys are still under design.
- Constraints: Complex business logic constraints (e.g., preventing circular tag parents) are handled at the application layer.
model Lyrics {
id String @id
song Song @relation(fields: [songId], references: [id])
songId String @map("song_id")
language String?
plaintext String?
ttml String?
lrc String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("lyrics")
}
model History {
id String @id
table String
objectId String @map("object_id")
old Json?
new Json?
createdAt DateTime @default(now()) @map("created_at")
@@map("history")
}
model ExternalLink {
id String @id
label String?
url String
platform Platform?
platformId String? @map("platform_id")
song Song @relation(fields: [songId], references: [id])
songId String @map("song_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("external_link")
}
// Sometimes, we only know the SVS engine (e.g. VOCALOID, Synthesizer V) used in the song.
// And in other cases, we know exactly the voicebank used in the song. (e.g. Luo Tianyi VOCALOID 5 Chinese)
// That's why we need to have both SvsEngine and SvsEngineVersion.
model SingerOfSong {
id String @id
song Song @relation(fields: [songId], references: [id])
songId String @map("song_id")
singer Singer @relation(fields: [singerId], references: [id])
singerId String @map("singer_id")
voicebank Voicebank? @relation(fields: [voicebankId], references: [id])
voicebankId String? @map("voicebank_id")
svsEngine SvsEngine? @relation(fields: [svsEngineId], references: [id])
svsEngineId String? @map("svs_engine_id")
svsEngineVersion SvsEngineVersion? @relation(fields: [svsEngineVersionId], references: [id])
svsEngineVersionId String? @map("svs_engine_version_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("singer_of_song")
}
model Voicebank {
id String @id
singer Singer @relation(fields: [singerId], references: [id])
description String?
/// [LocalizedField]
localizedDescriptions Json?
singerId String @map("singer_id")
language String
svsEngineVersion SvsEngineVersion @relation(fields: [svsEngineVersionId], references: [id])
svsEngineVersionId String @map("svs_engine_version_id")
singerOfSongs SingerOfSong[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("voicebank")
}
model Album {
id String @id
name String?
/// [LocalizedField]
localizedNames Json?
description String?
/// [LocalizedField]
localizedDescriptions Json?
songInAlbums SongInAlbum[]
coverUrl String? @map("cover_url")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("album")
}
model SongInAlbum {
song Song @relation(fields: [songId], references: [id])
songId String @map("song_id")
album Album @relation(fields: [albumId], references: [id])
albumId String @map("album_id")
trackNumber Int @map("track_number")
discNumber Int @default(1) @map("disc_number")
@@id([songId, albumId])
@@map("song_in_album")
}
model SongTag {
song Song @relation(fields: [songId], references: [id])
songId String @map("song_id")
tag Tag @relation(fields: [tagId], references: [id])
tagId String @map("tag_id")
@@id([songId, tagId])
@@map("song_tag")
}
model SingerSvsEngineVersion {
singer Singer @relation(fields: [singerId], references: [id])
singerId String @map("singer_id")
svsEngineVersion SvsEngineVersion @relation(fields: [svsEngineVersionId], references: [id])
svsEngineVersionId String @map("svs_engine_version_id")
@@id([singerId, svsEngineVersionId])
@@map("singer_svs_engine_version")
}
model SingerSvsEngine {
singer Singer @relation(fields: [singerId], references: [id])
singerId String @map("singer_id")
svsEngine SvsEngine @relation(fields: [svsEngineId], references: [id])
svsEngineId String @map("svs_engine_id")
@@id([singerId, svsEngineId])
@@map("singer_svs_engine")
}
model Singer {
id String @id
name String?
avatarUrl String? @map("avatar_url")
/// [LocalizedField]
localizedNames Json?
singerSvsEngineVersions SingerSvsEngineVersion[]
singerSvsEngines SingerSvsEngine[]
voicebanks Voicebank[]
singerOfSongs SingerOfSong[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("singer")
}
model SvsEngine {
id String @id
name String
description String?
/// [LocalizedField]
localizedDescriptions Json?
svsEngineVersions SvsEngineVersion[]
singerSvsEngines SingerSvsEngine[]
performances SingerOfSong[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("svs_engine")
}
model SvsEngineVersion {
id String @id
versionString String @map("version_string")
singerSvsEngineVersions SingerSvsEngineVersion[]
svsEngine SvsEngine @relation(fields: [svsEngineId], references: [id])
svsEngineId String @map("svs_engine_id")
singerOfSongs SingerOfSong[]
voicebanks Voicebank[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("svs_engine_version")
}
model Artist {
id String @id
name String?
/// [LocalizedField]
localizedNames Json?
description String?
/// [LocalizedField]
localizedDescriptions Json?
artistSongRoles ArtistRole[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("artist")
}
model ArtistRole {
id String @id
role String
/// [LocalizedField]
localizedRoles Json?
song Song @relation(fields: [songId], references: [id])
songId String @map("song_id")
artist Artist @relation(fields: [artistId], references: [id])
artistId String @map("artist_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("artist_role")
}
*/