feat: align with upstream 1.5.9.9

This commit is contained in:
2026-09-12 18:21:11 +08:00
commit 34f0679117
805 changed files with 376275 additions and 0 deletions
@@ -0,0 +1,132 @@
DO $$ BEGIN
CREATE TYPE "public"."BlacklistType" AS ENUM('SONG', 'KEYWORD');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;--> statement-breakpoint
CREATE TABLE "NotificationSettings" (
"id" serial PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
"userId" integer NOT NULL,
"enabled" boolean DEFAULT true NOT NULL,
"songRequestEnabled" boolean DEFAULT true NOT NULL,
"songVotedEnabled" boolean DEFAULT true NOT NULL,
"songPlayedEnabled" boolean DEFAULT true NOT NULL,
"refreshInterval" integer DEFAULT 60 NOT NULL,
"songVotedThreshold" integer DEFAULT 1 NOT NULL,
CONSTRAINT "NotificationSettings_userId_unique" UNIQUE("userId")
);
--> statement-breakpoint
CREATE TABLE "Notification" (
"id" serial PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
"type" text NOT NULL,
"message" text NOT NULL,
"read" boolean DEFAULT false NOT NULL,
"userId" integer NOT NULL,
"songId" integer
);
--> statement-breakpoint
CREATE TABLE "PlayTime" (
"id" serial PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
"name" text NOT NULL,
"startTime" text,
"endTime" text,
"enabled" boolean DEFAULT true NOT NULL,
"description" text
);
--> statement-breakpoint
CREATE TABLE "Schedule" (
"id" serial PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
"songId" integer NOT NULL,
"playDate" timestamp NOT NULL,
"played" boolean DEFAULT false NOT NULL,
"sequence" integer DEFAULT 1 NOT NULL,
"playTimeId" integer
);
--> statement-breakpoint
CREATE TABLE "Semester" (
"id" serial PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
"name" text NOT NULL,
"isActive" boolean DEFAULT false NOT NULL,
CONSTRAINT "Semester_name_unique" UNIQUE("name")
);
--> statement-breakpoint
CREATE TABLE "SongBlacklist" (
"id" serial PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
"type" "BlacklistType" NOT NULL,
"value" text NOT NULL,
"reason" text,
"isActive" boolean DEFAULT true NOT NULL,
"createdBy" integer
);
--> statement-breakpoint
CREATE TABLE "Song" (
"id" serial PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
"title" text NOT NULL,
"artist" text NOT NULL,
"requesterId" integer NOT NULL,
"played" boolean DEFAULT false NOT NULL,
"playedAt" timestamp,
"semester" text,
"preferredPlayTimeId" integer,
"cover" text,
"musicPlatform" text,
"musicId" text
);
--> statement-breakpoint
CREATE TABLE "SystemSettings" (
"id" serial PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
"enablePlayTimeSelection" boolean DEFAULT false NOT NULL,
"siteTitle" text,
"siteLogoUrl" text,
"schoolLogoHomeUrl" text,
"schoolLogoPrintUrl" text,
"siteDescription" text,
"submissionGuidelines" text,
"icpNumber" text,
"enableSubmissionLimit" boolean DEFAULT false NOT NULL,
"dailySubmissionLimit" integer,
"weeklySubmissionLimit" integer,
"showBlacklistKeywords" boolean DEFAULT false NOT NULL,
"hideStudentInfo" boolean DEFAULT true NOT NULL
);
--> statement-breakpoint
CREATE TABLE "User" (
"id" serial PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
"username" text NOT NULL,
"name" text,
"grade" text,
"class" text,
"role" text DEFAULT 'USER' NOT NULL,
"password" text NOT NULL,
"lastLogin" timestamp,
"lastLoginIp" text,
"passwordChangedAt" timestamp,
"forcePasswordChange" boolean DEFAULT true NOT NULL,
"meowNickname" text,
"meowBoundAt" timestamp,
CONSTRAINT "User_username_unique" UNIQUE("username")
);
--> statement-breakpoint
CREATE TABLE "Vote" (
"id" serial PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"songId" integer NOT NULL,
"userId" integer NOT NULL
);
@@ -0,0 +1,14 @@
-- 为排期表添加草稿支持
-- 添加草稿状态标识字段
ALTER TABLE "Schedule" ADD COLUMN IF NOT EXISTS "isDraft" boolean DEFAULT false NOT NULL;
-- 添加发布时间字段(发布后记录发布时间)
ALTER TABLE "Schedule" ADD COLUMN IF NOT EXISTS "publishedAt" timestamp;
-- 为现有排期设置为已发布状态(因为现有的都应该是已发布的)
UPDATE "Schedule" SET "isDraft" = false, "publishedAt" = "updatedAt" WHERE "isDraft" IS NULL;
-- 创建索引以优化查询性能
CREATE INDEX IF NOT EXISTS "idx_schedule_is_draft" ON "Schedule" ("isDraft");
CREATE INDEX IF NOT EXISTS "idx_schedule_published_at" ON "Schedule" ("publishedAt");
CREATE INDEX IF NOT EXISTS "idx_schedule_draft_date" ON "Schedule" ("isDraft", "playDate");
@@ -0,0 +1,55 @@
-- 创建用户状态枚举类型
DO $$ BEGIN
CREATE TYPE "public"."user_status" AS ENUM('active', 'withdrawn');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;--> statement-breakpoint
-- 添加用户状态相关字段到用户表
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "status" "user_status" DEFAULT 'active' NOT NULL;--> statement-breakpoint
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "statusChangedAt" timestamp DEFAULT now();--> statement-breakpoint
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "statusChangedBy" integer;--> statement-breakpoint
-- 创建用户状态变更日志表
CREATE TABLE IF NOT EXISTS "user_status_logs" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" integer NOT NULL,
"old_status" "user_status",
"new_status" "user_status" NOT NULL,
"reason" text,
"operator_id" integer,
"created_at" timestamp DEFAULT now() NOT NULL
);--> statement-breakpoint
-- 创建索引以提高查询性能
CREATE INDEX IF NOT EXISTS "idx_user_status" ON "User" USING btree ("status");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "idx_user_status_logs_user_id" ON "user_status_logs" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "idx_user_status_logs_created_at" ON "user_status_logs" USING btree ("created_at" DESC);--> statement-breakpoint
-- 添加外键约束
DO $$ BEGIN
ALTER TABLE "User" ADD CONSTRAINT "User_statusChangedBy_User_id_fk" FOREIGN KEY ("statusChangedBy") REFERENCES "User"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "user_status_logs" ADD CONSTRAINT "user_status_logs_user_id_User_id_fk" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "user_status_logs" ADD CONSTRAINT "user_status_logs_operator_id_User_id_fk" FOREIGN KEY ("operator_id") REFERENCES "User"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;--> statement-breakpoint
-- 为现有用户初始化状态变更日志
INSERT INTO "user_status_logs" ("user_id", "old_status", "new_status", "reason", "created_at")
SELECT "id", NULL, 'active', '系统迁移初始化', "createdAt"
FROM "User"
WHERE NOT EXISTS (
SELECT 1
FROM "user_status_logs"
WHERE "user_status_logs"."user_id" = "User"."id"
AND "user_status_logs"."reason" = '系统迁移初始化'
);
@@ -0,0 +1 @@
ALTER TABLE "Semester" DROP CONSTRAINT "Semester_name_unique";
@@ -0,0 +1,68 @@
-- 创建API Keys表
CREATE TABLE IF NOT EXISTS "api_keys" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
"name" varchar(255) NOT NULL,
"description" text,
"key_hash" varchar(255) NOT NULL,
"key_prefix" varchar(10) NOT NULL,
"is_active" boolean DEFAULT true NOT NULL,
"expires_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"last_used_at" timestamp with time zone,
"created_by_user_id" integer NOT NULL,
"usage_count" integer DEFAULT 0 NOT NULL,
CONSTRAINT "api_keys_key_hash_unique" UNIQUE("key_hash")
);--> statement-breakpoint
-- 创建API Key权限表
CREATE TABLE IF NOT EXISTS "api_key_permissions" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
"api_key_id" uuid NOT NULL,
"permission" varchar(100) NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "api_key_permissions_api_key_id_permission_unique" UNIQUE("api_key_id", "permission")
);--> statement-breakpoint
-- 创建API访问日志表
CREATE TABLE IF NOT EXISTS "api_logs" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
"api_key_id" uuid,
"endpoint" varchar(500) NOT NULL,
"method" varchar(10) NOT NULL,
"ip_address" inet NOT NULL,
"user_agent" text,
"status_code" integer NOT NULL,
"response_time_ms" integer NOT NULL,
"request_body" text,
"response_body" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"error_message" text
);--> statement-breakpoint
-- 创建外键约束
DO $$ BEGIN
ALTER TABLE "api_keys" ADD CONSTRAINT "api_keys_created_by_user_id_User_id_fk" FOREIGN KEY ("created_by_user_id") REFERENCES "User"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "api_key_permissions" ADD CONSTRAINT "api_key_permissions_api_key_id_api_keys_id_fk" FOREIGN KEY ("api_key_id") REFERENCES "api_keys"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "api_logs" ADD CONSTRAINT "api_logs_api_key_id_api_keys_id_fk" FOREIGN KEY ("api_key_id") REFERENCES "api_keys"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;--> statement-breakpoint
-- 创建索引
CREATE INDEX IF NOT EXISTS "idx_api_keys_key_hash" ON "api_keys"("key_hash");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "idx_api_keys_created_by" ON "api_keys"("created_by_user_id");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "idx_api_keys_active" ON "api_keys"("is_active") WHERE "is_active" = true;--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "idx_api_key_permissions_api_key_id" ON "api_key_permissions"("api_key_id");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "idx_api_logs_api_key_id" ON "api_logs"("api_key_id");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "idx_api_logs_created_at" ON "api_logs"("created_at" DESC);--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "idx_api_logs_status_code" ON "api_logs"("status_code");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "idx_api_logs_endpoint" ON "api_logs"("endpoint");
@@ -0,0 +1,2 @@
ALTER TABLE "NotificationSettings" DROP CONSTRAINT "NotificationSettings_userId_unique";--> statement-breakpoint
ALTER TABLE "User" DROP CONSTRAINT "User_username_unique";
@@ -0,0 +1,20 @@
-- 添加用户邮箱字段
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "email" text;
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "emailVerified" boolean DEFAULT false NOT NULL;
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "emailVerifiedAt" timestamp;
-- 添加系统设置SMTP配置字段
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "smtpEnabled" boolean DEFAULT false NOT NULL;
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "smtpHost" text;
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "smtpPort" integer DEFAULT 587;
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "smtpSecure" boolean DEFAULT false NOT NULL;
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "smtpUsername" text;
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "smtpPassword" text;
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "smtpFromEmail" text;
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "smtpFromName" text;
-- 添加通知设置邮件通知字段
ALTER TABLE "NotificationSettings" ADD COLUMN IF NOT EXISTS "emailSongRequestEnabled" boolean DEFAULT true NOT NULL;
ALTER TABLE "NotificationSettings" ADD COLUMN IF NOT EXISTS "emailSongVotedEnabled" boolean DEFAULT true NOT NULL;
ALTER TABLE "NotificationSettings" ADD COLUMN IF NOT EXISTS "emailSongPlayedEnabled" boolean DEFAULT true NOT NULL;
ALTER TABLE "NotificationSettings" ADD COLUMN IF NOT EXISTS "emailSystemNoticeEnabled" boolean DEFAULT true NOT NULL;
@@ -0,0 +1,10 @@
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "smtpEnabled" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "smtpHost" text;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "smtpPort" integer DEFAULT 587;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "smtpSecure" boolean DEFAULT false;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "smtpUsername" text;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "smtpPassword" text;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "smtpFromEmail" text;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "smtpFromName" text DEFAULT '校园广播站';--> statement-breakpoint
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "email" text;--> statement-breakpoint
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "emailVerified" boolean DEFAULT false;
@@ -0,0 +1,10 @@
CREATE TABLE "EmailTemplate" (
"id" serial PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
"key" varchar(100) NOT NULL,
"name" varchar(200) NOT NULL,
"subject" varchar(300) NOT NULL,
"html" text NOT NULL,
"updatedByUserId" integer
);
@@ -0,0 +1,2 @@
-- 添加歌曲播放地址字段
ALTER TABLE "Song" ADD COLUMN IF NOT EXISTS "playUrl" text;
@@ -0,0 +1,3 @@
ALTER TABLE "Schedule" ADD COLUMN IF NOT EXISTS "isDraft" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "Schedule" ADD COLUMN IF NOT EXISTS "publishedAt" timestamp;--> statement-breakpoint
ALTER TABLE "Song" ADD COLUMN IF NOT EXISTS "playUrl" text;
@@ -0,0 +1,42 @@
CREATE TYPE "public"."collaborator_status" AS ENUM ('PENDING', 'ACCEPTED', 'REJECTED');--> statement-breakpoint
CREATE TABLE "collaboration_logs"
(
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"collaborator_id" uuid NOT NULL,
"action" varchar(50) NOT NULL,
"operator_id" integer NOT NULL,
"ip_address" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "RequestTime"
(
"id" serial PRIMARY KEY NOT NULL,
"createdAt" timestamp(6) DEFAULT now() NOT NULL,
"updatedAt" timestamp(6) DEFAULT now() NOT NULL,
"name" text NOT NULL,
"startTime" timestamp NOT NULL,
"endTime" timestamp NOT NULL,
"enabled" boolean DEFAULT true NOT NULL,
"description" text,
"expected" bigint DEFAULT 0 NOT NULL,
"accepted" bigint DEFAULT 0 NOT NULL,
"past" boolean DEFAULT false NOT NULL
);
--> statement-breakpoint
CREATE TABLE "song_collaborators"
(
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"song_id" integer NOT NULL,
"user_id" integer NOT NULL,
"status" "collaborator_status" DEFAULT 'PENDING' NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "Song"
ADD COLUMN "hitRequestId" integer;--> statement-breakpoint
ALTER TABLE "SystemSettings"
ADD COLUMN "enableRequestTimeLimitation" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "SystemSettings"
ADD COLUMN "forceBlockAllRequests" boolean DEFAULT false NOT NULL;
@@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS "song_replay_requests" (
"id" serial PRIMARY KEY NOT NULL,
"song_id" integer NOT NULL,
"user_id" integer NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "song_replay_requests_song_id_user_id_unique" UNIQUE("song_id","user_id")
);
ALTER TABLE "SystemSettings" ADD COLUMN "enableReplayRequests" boolean DEFAULT false NOT NULL;
@@ -0,0 +1,2 @@
CREATE TYPE "public"."replay_request_status" AS ENUM('PENDING', 'FULFILLED', 'REJECTED');--> statement-breakpoint
ALTER TABLE "song_replay_requests" ADD COLUMN "status" "replay_request_status" DEFAULT 'PENDING' NOT NULL;
@@ -0,0 +1,11 @@
CREATE TABLE "UserIdentity" (
"id" serial PRIMARY KEY NOT NULL,
"userId" integer NOT NULL,
"provider" text NOT NULL,
"providerUserId" text NOT NULL,
"providerUsername" text,
"createdAt" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "UserIdentity_provider_providerUserId_unique" UNIQUE("provider","providerUserId")
);
--> statement-breakpoint
ALTER TABLE "UserIdentity" ADD CONSTRAINT "UserIdentity_userId_User_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE no action;
@@ -0,0 +1 @@
ALTER TABLE "SystemSettings" ADD COLUMN "monthlySubmissionLimit" integer;
@@ -0,0 +1,2 @@
ALTER TABLE "SystemSettings" ADD COLUMN "gonganNumber" text;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "gonganLink" text;
@@ -0,0 +1 @@
ALTER TABLE "SystemSettings" DROP COLUMN "gonganLink";
@@ -0,0 +1 @@
ALTER TABLE "SystemSettings" ADD COLUMN "enableCollaborativeSubmission" boolean DEFAULT true NOT NULL;
@@ -0,0 +1,3 @@
ALTER TABLE "Song" ADD COLUMN "submissionNote" text;
ALTER TABLE "Song" ADD COLUMN "submissionNotePublic" boolean DEFAULT false NOT NULL;
ALTER TABLE "SystemSettings" ADD COLUMN "enableSubmissionRemarks" boolean DEFAULT false NOT NULL;
@@ -0,0 +1,14 @@
ALTER TABLE "SystemSettings"
ADD COLUMN "customOAuthEnabled" boolean DEFAULT false NOT NULL,
ADD COLUMN "customOAuthDisplayName" text,
ADD COLUMN "customOAuthAuthorizeUrl" text,
ADD COLUMN "customOAuthTokenUrl" text,
ADD COLUMN "customOAuthUserInfoUrl" text,
ADD COLUMN "customOAuthScope" text,
ADD COLUMN "customOAuthClientId" text,
ADD COLUMN "customOAuthClientSecret" text,
ADD COLUMN "customOAuthUserIdField" text,
ADD COLUMN "customOAuthUsernameField" text,
ADD COLUMN "customOAuthNameField" text,
ADD COLUMN "customOAuthEmailField" text,
ADD COLUMN "customOAuthAvatarField" text;
@@ -0,0 +1,15 @@
ALTER TABLE "SystemSettings"
ADD COLUMN "oauthRedirectUri" text,
ADD COLUMN "oauthStateSecret" text,
ADD COLUMN "oauthProviders" text DEFAULT '[]',
ADD COLUMN "githubOAuthEnabled" boolean DEFAULT false NOT NULL,
ADD COLUMN "githubClientId" text,
ADD COLUMN "githubClientSecret" text,
ADD COLUMN "casdoorOAuthEnabled" boolean DEFAULT false NOT NULL,
ADD COLUMN "casdoorServerUrl" text,
ADD COLUMN "casdoorClientId" text,
ADD COLUMN "casdoorClientSecret" text,
ADD COLUMN "casdoorOrganizationName" text,
ADD COLUMN "googleOAuthEnabled" boolean DEFAULT false NOT NULL,
ADD COLUMN "googleClientId" text,
ADD COLUMN "googleClientSecret" text;
@@ -0,0 +1 @@
ALTER TYPE "public"."user_status" ADD VALUE IF NOT EXISTS 'graduate';
@@ -0,0 +1 @@
ALTER TABLE "SystemSettings" ADD COLUMN "showBeianIcon" boolean DEFAULT false NOT NULL;
@@ -0,0 +1 @@
ALTER TABLE "SystemSettings" ADD COLUMN "allowOAuthRegistration" boolean DEFAULT false NOT NULL;
@@ -0,0 +1,2 @@
ALTER TABLE "SystemSettings" ADD COLUMN "captchaEnabled" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "captchaMaxFailures" integer DEFAULT 3 NOT NULL;
@@ -0,0 +1,3 @@
ALTER TABLE "SystemSettings" ADD COLUMN "captchaProvider" text DEFAULT 'graphic' NOT NULL;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "turnstileSiteKey" text;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "turnstileSecretKey" text;
@@ -0,0 +1,3 @@
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "instance_id" text;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN IF NOT EXISTS "telemetryEnabled" boolean DEFAULT true NOT NULL;--> statement-breakpoint
ALTER TYPE "user_status" ADD VALUE IF NOT EXISTS 'graduate';
@@ -0,0 +1,97 @@
DO $$ BEGIN
CREATE TYPE "public"."card_code_status" AS ENUM('AVAILABLE','LOCKED','REDEEMED','INVALID');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "CardCode" (
"id" serial PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
"code" text NOT NULL,
"status" "card_code_status" DEFAULT 'AVAILABLE' NOT NULL,
"lockedBy" integer,
"lockedAt" timestamp,
"redeemedBy" integer,
"redeemedAt" timestamp,
"note" text
);--> statement-breakpoint
ALTER TABLE "Song" ADD COLUMN IF NOT EXISTS "cardCodeId" integer;--> statement-breakpoint
ALTER TABLE "SystemSettings"
ADD COLUMN IF NOT EXISTS "enableCardCodeRequests" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "SystemSettings"
ADD COLUMN IF NOT EXISTS "requireCardCodeForRequests" boolean DEFAULT false NOT NULL;--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "CardCode_code_unique" ON "CardCode" ("code");--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "CardCode"
ADD CONSTRAINT "CardCode_lockedBy_User_id_fk"
FOREIGN KEY ("lockedBy") REFERENCES "public"."User"("id")
ON DELETE SET NULL ON UPDATE NO ACTION;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "CardCode"
ADD CONSTRAINT "CardCode_redeemedBy_User_id_fk"
FOREIGN KEY ("redeemedBy") REFERENCES "public"."User"("id")
ON DELETE SET NULL ON UPDATE NO ACTION;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Song"
ADD CONSTRAINT "Song_cardCodeId_CardCode_id_fk"
FOREIGN KEY ("cardCodeId") REFERENCES "public"."CardCode"("id")
ON DELETE SET NULL ON UPDATE NO ACTION;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "CardCodeRedeemLog" (
"id" serial PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"cardCodeId" integer NOT NULL,
"codeSnapshot" text NOT NULL,
"redeemedBy" integer NOT NULL,
"redeemedAt" timestamp DEFAULT now() NOT NULL,
"source" text DEFAULT 'UNKNOWN' NOT NULL,
"songId" integer
);--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "CardCodeRedeemLog_cardCodeId_idx" ON "CardCodeRedeemLog" ("cardCodeId");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "CardCodeRedeemLog_redeemedBy_idx" ON "CardCodeRedeemLog" ("redeemedBy");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "CardCodeRedeemLog_redeemedAt_idx" ON "CardCodeRedeemLog" ("redeemedAt");--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "CardCodeRedeemLog"
ADD CONSTRAINT "CardCodeRedeemLog_cardCodeId_CardCode_id_fk"
FOREIGN KEY ("cardCodeId") REFERENCES "public"."CardCode"("id")
ON DELETE RESTRICT ON UPDATE NO ACTION;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "CardCodeRedeemLog"
ADD CONSTRAINT "CardCodeRedeemLog_redeemedBy_User_id_fk"
FOREIGN KEY ("redeemedBy") REFERENCES "public"."User"("id")
ON DELETE RESTRICT ON UPDATE NO ACTION;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "CardCodeRedeemLog"
ADD CONSTRAINT "CardCodeRedeemLog_songId_Song_id_fk"
FOREIGN KEY ("songId") REFERENCES "public"."Song"("id")
ON DELETE SET NULL ON UPDATE NO ACTION;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
@@ -0,0 +1,18 @@
ALTER TABLE "CardCodeRedeemLog"
ALTER COLUMN "cardCodeId" DROP NOT NULL;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "CardCodeRedeemLog"
DROP CONSTRAINT IF EXISTS "CardCodeRedeemLog_cardCodeId_CardCode_id_fk";
EXCEPTION
WHEN undefined_object THEN null;
END $$;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "CardCodeRedeemLog"
ADD CONSTRAINT "CardCodeRedeemLog_cardCodeId_CardCode_id_fk"
FOREIGN KEY ("cardCodeId") REFERENCES "public"."CardCode"("id")
ON DELETE SET NULL ON UPDATE NO ACTION;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
@@ -0,0 +1,2 @@
CREATE INDEX "card_code_redeem_log_card_code_id_idx" ON "CardCodeRedeemLog" USING btree ("cardCodeId");--> statement-breakpoint
CREATE INDEX "song_card_code_id_idx" ON "Song" USING btree ("cardCodeId");
@@ -0,0 +1 @@
ALTER TABLE "SystemSettings" ADD COLUMN "enableCardCodeLimitBypass" boolean DEFAULT false NOT NULL;
@@ -0,0 +1,6 @@
ALTER TABLE "SystemSettings"
ADD COLUMN IF NOT EXISTS "aggregateOAuthEnabled" boolean DEFAULT false NOT NULL,
ADD COLUMN IF NOT EXISTS "aggregateOAuthAppId" text,
ADD COLUMN IF NOT EXISTS "aggregateOAuthAppKey" text,
ADD COLUMN IF NOT EXISTS "aggregateOAuthLoginType" text DEFAULT 'qq',
ADD COLUMN IF NOT EXISTS "aggregateOAuthEndpoint" text DEFAULT 'https://a.idcfx.net/connect.php';
@@ -0,0 +1,22 @@
CREATE INDEX "schedule_published_song_idx" ON "Schedule" USING btree ("isDraft","songId","playDate");--> statement-breakpoint
CREATE INDEX "schedule_published_date_idx" ON "Schedule" USING btree ("isDraft","playDate");--> statement-breakpoint
CREATE INDEX "song_collaborators_song_status_idx" ON "song_collaborators" USING btree ("song_id","status");--> statement-breakpoint
CREATE INDEX "song_collaborators_user_status_idx" ON "song_collaborators" USING btree ("user_id","status");--> statement-breakpoint
CREATE INDEX "song_replay_requests_user_status_song_idx" ON "song_replay_requests" USING btree ("user_id","status","song_id");--> statement-breakpoint
CREATE INDEX "song_semester_created_at_idx" ON "Song" USING btree ("semester","createdAt");--> statement-breakpoint
CREATE INDEX "song_requester_id_idx" ON "Song" USING btree ("requesterId");--> statement-breakpoint
CREATE INDEX "vote_user_song_idx" ON "Vote" USING btree ("userId","songId");--> statement-breakpoint
-- 保留每个用户对每首歌曲最早的一条投票,清理旧版本并发产生的重复记录。
WITH ranked_votes AS (
SELECT
"id",
ROW_NUMBER() OVER (PARTITION BY "songId", "userId" ORDER BY "id") AS duplicate_rank
FROM "Vote"
)
DELETE FROM "Vote"
WHERE "id" IN (
SELECT "id"
FROM ranked_votes
WHERE duplicate_rank > 1
);--> statement-breakpoint
ALTER TABLE "Vote" ADD CONSTRAINT "vote_song_user_unique" UNIQUE("songId","userId");
@@ -0,0 +1,38 @@
-- 重播申请无感化:扩展申请元数据、排期绑定申请、放开唯一约束
-- 1. song_replay_requests 新增提交元数据字段(复用 songs 字段语义)
ALTER TABLE "song_replay_requests" ADD COLUMN IF NOT EXISTS "preferred_play_time_id" integer;
ALTER TABLE "song_replay_requests" ADD COLUMN IF NOT EXISTS "submission_note" text;
ALTER TABLE "song_replay_requests" ADD COLUMN IF NOT EXISTS "submission_note_public" boolean DEFAULT false NOT NULL;
-- 2. Schedule 新增重播申请绑定字段(无外键约束,历史数据可为 NULL)
ALTER TABLE "Schedule" ADD COLUMN IF NOT EXISTS "replay_request_id" integer;
-- 3. 放开 (song_id, user_id) 全量唯一约束,允许同一用户对同一首歌提交多条重播申请
ALTER TABLE "song_replay_requests" DROP CONSTRAINT IF EXISTS "song_replay_requests_song_id_user_id_unique";
-- 同一用户对同一首歌同时最多保留一条待处理申请
CREATE UNIQUE INDEX IF NOT EXISTS "song_replay_requests_pending_song_user_unique"
ON "song_replay_requests" ("song_id", "user_id")
WHERE "status" = 'PENDING';
-- 4. 尽力回填历史重播排期的申请绑定:
-- 每首歌取最新一条已履行申请,只绑定申请创建后最早发布的一条排期(一对一)
UPDATE "Schedule" sch
SET "replay_request_id" = m.request_id
FROM (
SELECT DISTINCT ON (rr.id) rr.id AS request_id, s.id AS schedule_id
FROM (
SELECT DISTINCT ON ("song_id") id, "song_id", "created_at"
FROM "song_replay_requests"
WHERE "status" = 'FULFILLED'
ORDER BY "song_id", "created_at" DESC
) rr
JOIN "Schedule" s ON s."songId" = rr."song_id"
AND s."replay_request_id" IS NULL
AND s."isDraft" = false
AND s."publishedAt" IS NOT NULL
AND s."publishedAt" >= rr."created_at"
ORDER BY rr.id, s."publishedAt" ASC, s.id ASC
) m
WHERE sch.id = m.schedule_id;
@@ -0,0 +1,27 @@
CREATE TABLE "PasswordAuditLog" (
"id" serial PRIMARY KEY NOT NULL,
"userId" integer NOT NULL,
"actorId" integer,
"action" text NOT NULL,
"success" boolean NOT NULL,
"ipAddress" text,
"userAgent" text,
"failureReason" text,
"createdAt" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "PasswordRateLimit" (
"key" text PRIMARY KEY NOT NULL,
"count" integer NOT NULL,
"resetAt" timestamp with time zone NOT NULL
);
--> statement-breakpoint
ALTER TABLE "User" ALTER COLUMN "forcePasswordChange" SET DEFAULT false;--> statement-breakpoint
-- 回填存量数据:旧默认值 true 无法与管理员显式重置区分,统一清零,
-- 避免上线当天存量用户在开关关闭时仍被硬门控锁进改密页;
-- 此后强制标记仅由管理员显式重置产生,首次登录强制改密由系统开关控制。
UPDATE "User" SET "forcePasswordChange" = false WHERE "forcePasswordChange" = true;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "forcePasswordChangeOnFirstLogin" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "User" ADD COLUMN "tokenVersion" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
CREATE INDEX "PasswordAuditLog_user_created_idx" ON "PasswordAuditLog" USING btree ("userId","createdAt");--> statement-breakpoint
CREATE INDEX "PasswordRateLimit_reset_idx" ON "PasswordRateLimit" USING btree ("resetAt");
@@ -0,0 +1,10 @@
ALTER TABLE "Notification" ADD COLUMN "batchId" text;--> statement-breakpoint
ALTER TABLE "Notification" ADD COLUMN "source" text DEFAULT 'SYSTEM' NOT NULL;--> statement-breakpoint
ALTER TABLE "Notification" ADD COLUMN "senderId" integer;--> statement-breakpoint
ALTER TABLE "Notification" ADD COLUMN "senderName" text;--> statement-breakpoint
ALTER TABLE "Notification" ADD COLUMN "senderUsername" text;--> statement-breakpoint
ALTER TABLE "Notification" ADD COLUMN "title" text;--> statement-breakpoint
ALTER TABLE "Notification" ADD COLUMN "important" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "Notification" ADD COLUMN "userDeleted" boolean DEFAULT false NOT NULL;--> statement-breakpoint
CREATE INDEX "notification_user_important_read_created_idx" ON "Notification" USING btree ("userId","userDeleted","important","read","createdAt");--> statement-breakpoint
CREATE INDEX "notification_batch_id_idx" ON "Notification" USING btree ("batchId");
@@ -0,0 +1,14 @@
CREATE TABLE "BackupHistory" (
"id" serial PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"filename" text NOT NULL,
"totalRecords" integer DEFAULT 0 NOT NULL,
"backupSize" integer DEFAULT 0 NOT NULL,
"methods" text NOT NULL,
"success" boolean DEFAULT false NOT NULL,
"triggeredBy" text
);
--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "autoBackupEnabled" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "autoBackupConfig" text;--> statement-breakpoint
CREATE INDEX "backup_history_created_at_idx" ON "BackupHistory" USING btree ("createdAt");
@@ -0,0 +1,5 @@
ALTER TABLE "SystemSettings" ADD COLUMN "enabledPlatforms" text DEFAULT '["netease","tencent","bilibili","migu"]';--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "platformOrder" text DEFAULT '["netease","tencent","bilibili","migu"]';--> statement-breakpoint
-- 回填已有行的默认值
UPDATE "SystemSettings" SET "enabledPlatforms" = '["netease","tencent","bilibili","migu"]' WHERE "enabledPlatforms" IS NULL;
UPDATE "SystemSettings" SET "platformOrder" = '["netease","tencent","bilibili","migu"]' WHERE "platformOrder" IS NULL;
@@ -0,0 +1,21 @@
-- 歌曲时长
ALTER TABLE "Song" ADD COLUMN "durationSeconds" integer;
-- 自动排期备选池表(管理员手动维护的候选歌曲池)
CREATE TABLE IF NOT EXISTS "ScheduleSongPool" (
"id" serial PRIMARY KEY,
"createdAt" timestamp DEFAULT now() NOT NULL,
"songId" integer NOT NULL REFERENCES "Song" ("id") ON DELETE CASCADE,
"addedBy" integer REFERENCES "User" ("id") ON DELETE SET NULL,
CONSTRAINT "schedule_song_pool_song_unique" UNIQUE ("songId")
);
-- 音源平台默认排序
ALTER TABLE "SystemSettings" ALTER COLUMN "enabledPlatforms" SET DEFAULT '["netease","tencent","bilibili","migu"]';
ALTER TABLE "SystemSettings" ALTER COLUMN "platformOrder" SET DEFAULT '["netease","tencent","bilibili","migu"]';
-- 重复投稿限制字段(同一首歌/同一歌手进入排期后的禁止再次投稿时间窗口)
ALTER TABLE "SystemSettings" ADD COLUMN "enableSubmissionRestriction" boolean DEFAULT false NOT NULL;
ALTER TABLE "SystemSettings" ADD COLUMN "submissionRestrictionScope" text DEFAULT 'all' NOT NULL;
ALTER TABLE "SystemSettings" ADD COLUMN "sameSongRestrictionHours" integer;
ALTER TABLE "SystemSettings" ADD COLUMN "sameArtistRestrictionHours" integer;
@@ -0,0 +1,2 @@
ALTER TABLE "SystemSettings" ADD COLUMN "defaultTheme" text DEFAULT 'System' NOT NULL;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "enabledThemes" text DEFAULT '["System","ClassicDark","ClassicLight","ModernLight"]' NOT NULL;
@@ -0,0 +1,25 @@
CREATE TABLE IF NOT EXISTS "auth_sessions" (
"id" uuid PRIMARY KEY NOT NULL,
"user_id" integer NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"last_active_at" timestamp with time zone DEFAULT now() NOT NULL,
"expires_at" timestamp with time zone NOT NULL,
"ip_address" text,
"user_agent" text,
"browser" text,
"operating_system" text,
"device" text,
"login_method" text DEFAULT 'password' NOT NULL,
"revoked_at" timestamp with time zone,
"revoked_reason" text
);
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "auth_sessions" ADD CONSTRAINT "auth_sessions_user_id_User_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "auth_sessions_user_active_idx" ON "auth_sessions" USING btree ("user_id","revoked_at","expires_at");
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "auth_sessions_last_active_idx" ON "auth_sessions" USING btree ("last_active_at");
@@ -0,0 +1,4 @@
ALTER TABLE "SystemSettings" ADD COLUMN "scheduleDaysBeforeEnabled" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "scheduleDaysBefore" integer DEFAULT 1 NOT NULL;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "scheduleDaysAfterEnabled" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "scheduleDaysAfter" integer DEFAULT 1 NOT NULL;
@@ -0,0 +1,3 @@
ALTER TABLE "UserIdentity" ADD COLUMN "avatar" text;--> statement-breakpoint
ALTER TABLE "User" ADD COLUMN "avatarProvider" text;--> statement-breakpoint
ALTER TABLE "User" ADD COLUMN "avatarProviderUserId" text;
@@ -0,0 +1 @@
ALTER TABLE "SystemSettings" ALTER COLUMN "aggregateOAuthEndpoint" DROP DEFAULT;
@@ -0,0 +1,22 @@
ALTER TYPE "public"."user_status" ADD VALUE 'pending' BEFORE 'withdrawn';--> statement-breakpoint
ALTER TYPE "public"."user_status" ADD VALUE 'rejected';--> statement-breakpoint
CREATE TABLE "GradeClass" (
"id" serial PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
"grade" text NOT NULL,
"class" text NOT NULL,
CONSTRAINT "GradeClass_grade_class_unique" UNIQUE("grade","class")
);
--> statement-breakpoint
ALTER TABLE "song_replay_requests" ADD COLUMN "submission_note_public_status" text;--> statement-breakpoint
ALTER TABLE "Song" ADD COLUMN "submissionNotePublicStatus" text;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "allowRegister" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "registerRequiresApproval" boolean DEFAULT true NOT NULL;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "oauthRegisterRequiresApproval" boolean DEFAULT true NOT NULL;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "registerEmailRequired" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "submissionNoteRequiresApproval" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "user_status_logs" ADD COLUMN "username" text;--> statement-breakpoint
ALTER TABLE "user_status_logs" ADD COLUMN "name" text;--> statement-breakpoint
ALTER TABLE "User" ADD COLUMN "remark" text;--> statement-breakpoint
CREATE UNIQUE INDEX "User_username_unique" ON "User" USING btree ("username");
@@ -0,0 +1 @@
ALTER TABLE "SystemSettings" ADD COLUMN "registerRequiresGradeClass" boolean DEFAULT false NOT NULL;
@@ -0,0 +1,2 @@
ALTER TYPE "public"."BlacklistType" ADD VALUE 'LANGUAGE';--> statement-breakpoint
ALTER TYPE "public"."BlacklistType" ADD VALUE 'GENRE';
@@ -0,0 +1,2 @@
ALTER TABLE "SystemSettings" ADD COLUMN "statisticsCodeEnabled" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "SystemSettings" ADD COLUMN "statisticsCode" text;
@@ -0,0 +1,791 @@
{
"id": "7431b15e-7067-4715-87c6-cc4c83712e0b",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.NotificationSettings": {
"name": "NotificationSettings",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"userId": {
"name": "userId",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"enabled": {
"name": "enabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"songRequestEnabled": {
"name": "songRequestEnabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"songVotedEnabled": {
"name": "songVotedEnabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"songPlayedEnabled": {
"name": "songPlayedEnabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"refreshInterval": {
"name": "refreshInterval",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 60
},
"songVotedThreshold": {
"name": "songVotedThreshold",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 1
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"NotificationSettings_userId_unique": {
"name": "NotificationSettings_userId_unique",
"nullsNotDistinct": false,
"columns": [
"userId"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.Notification": {
"name": "Notification",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"message": {
"name": "message",
"type": "text",
"primaryKey": false,
"notNull": true
},
"read": {
"name": "read",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"userId": {
"name": "userId",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"songId": {
"name": "songId",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.PlayTime": {
"name": "PlayTime",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"startTime": {
"name": "startTime",
"type": "text",
"primaryKey": false,
"notNull": false
},
"endTime": {
"name": "endTime",
"type": "text",
"primaryKey": false,
"notNull": false
},
"enabled": {
"name": "enabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.Schedule": {
"name": "Schedule",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"songId": {
"name": "songId",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"playDate": {
"name": "playDate",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"played": {
"name": "played",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"sequence": {
"name": "sequence",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 1
},
"playTimeId": {
"name": "playTimeId",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.Semester": {
"name": "Semester",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"isActive": {
"name": "isActive",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"Semester_name_unique": {
"name": "Semester_name_unique",
"nullsNotDistinct": false,
"columns": [
"name"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.SongBlacklist": {
"name": "SongBlacklist",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"type": {
"name": "type",
"type": "BlacklistType",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
},
"reason": {
"name": "reason",
"type": "text",
"primaryKey": false,
"notNull": false
},
"isActive": {
"name": "isActive",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"createdBy": {
"name": "createdBy",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.Song": {
"name": "Song",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": true
},
"artist": {
"name": "artist",
"type": "text",
"primaryKey": false,
"notNull": true
},
"requesterId": {
"name": "requesterId",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"played": {
"name": "played",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"playedAt": {
"name": "playedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"semester": {
"name": "semester",
"type": "text",
"primaryKey": false,
"notNull": false
},
"preferredPlayTimeId": {
"name": "preferredPlayTimeId",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"cover": {
"name": "cover",
"type": "text",
"primaryKey": false,
"notNull": false
},
"musicPlatform": {
"name": "musicPlatform",
"type": "text",
"primaryKey": false,
"notNull": false
},
"musicId": {
"name": "musicId",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.SystemSettings": {
"name": "SystemSettings",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"enablePlayTimeSelection": {
"name": "enablePlayTimeSelection",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"siteTitle": {
"name": "siteTitle",
"type": "text",
"primaryKey": false,
"notNull": false
},
"siteLogoUrl": {
"name": "siteLogoUrl",
"type": "text",
"primaryKey": false,
"notNull": false
},
"schoolLogoHomeUrl": {
"name": "schoolLogoHomeUrl",
"type": "text",
"primaryKey": false,
"notNull": false
},
"schoolLogoPrintUrl": {
"name": "schoolLogoPrintUrl",
"type": "text",
"primaryKey": false,
"notNull": false
},
"siteDescription": {
"name": "siteDescription",
"type": "text",
"primaryKey": false,
"notNull": false
},
"submissionGuidelines": {
"name": "submissionGuidelines",
"type": "text",
"primaryKey": false,
"notNull": false
},
"icpNumber": {
"name": "icpNumber",
"type": "text",
"primaryKey": false,
"notNull": false
},
"enableSubmissionLimit": {
"name": "enableSubmissionLimit",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"dailySubmissionLimit": {
"name": "dailySubmissionLimit",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"weeklySubmissionLimit": {
"name": "weeklySubmissionLimit",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"showBlacklistKeywords": {
"name": "showBlacklistKeywords",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"hideStudentInfo": {
"name": "hideStudentInfo",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.User": {
"name": "User",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"grade": {
"name": "grade",
"type": "text",
"primaryKey": false,
"notNull": false
},
"class": {
"name": "class",
"type": "text",
"primaryKey": false,
"notNull": false
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'USER'"
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": true
},
"lastLogin": {
"name": "lastLogin",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"lastLoginIp": {
"name": "lastLoginIp",
"type": "text",
"primaryKey": false,
"notNull": false
},
"passwordChangedAt": {
"name": "passwordChangedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"forcePasswordChange": {
"name": "forcePasswordChange",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"meowNickname": {
"name": "meowNickname",
"type": "text",
"primaryKey": false,
"notNull": false
},
"meowBoundAt": {
"name": "meowBoundAt",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"User_username_unique": {
"name": "User_username_unique",
"nullsNotDistinct": false,
"columns": [
"username"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.Vote": {
"name": "Vote",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"songId": {
"name": "songId",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"userId": {
"name": "userId",
"type": "integer",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {
"public.BlacklistType": {
"name": "BlacklistType",
"schema": "public",
"values": [
"SONG",
"KEYWORD"
]
}
},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
@@ -0,0 +1,783 @@
{
"id": "5412e543-4a9c-416f-87b1-b8a405a19172",
"prevId": "7431b15e-7067-4715-87c6-cc4c83712e0b",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.NotificationSettings": {
"name": "NotificationSettings",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"userId": {
"name": "userId",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"enabled": {
"name": "enabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"songRequestEnabled": {
"name": "songRequestEnabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"songVotedEnabled": {
"name": "songVotedEnabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"songPlayedEnabled": {
"name": "songPlayedEnabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"refreshInterval": {
"name": "refreshInterval",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 60
},
"songVotedThreshold": {
"name": "songVotedThreshold",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 1
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"NotificationSettings_userId_unique": {
"name": "NotificationSettings_userId_unique",
"nullsNotDistinct": false,
"columns": [
"userId"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.Notification": {
"name": "Notification",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"message": {
"name": "message",
"type": "text",
"primaryKey": false,
"notNull": true
},
"read": {
"name": "read",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"userId": {
"name": "userId",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"songId": {
"name": "songId",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.PlayTime": {
"name": "PlayTime",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"startTime": {
"name": "startTime",
"type": "text",
"primaryKey": false,
"notNull": false
},
"endTime": {
"name": "endTime",
"type": "text",
"primaryKey": false,
"notNull": false
},
"enabled": {
"name": "enabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.Schedule": {
"name": "Schedule",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"songId": {
"name": "songId",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"playDate": {
"name": "playDate",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"played": {
"name": "played",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"sequence": {
"name": "sequence",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 1
},
"playTimeId": {
"name": "playTimeId",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.Semester": {
"name": "Semester",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"isActive": {
"name": "isActive",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.SongBlacklist": {
"name": "SongBlacklist",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"type": {
"name": "type",
"type": "BlacklistType",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
},
"reason": {
"name": "reason",
"type": "text",
"primaryKey": false,
"notNull": false
},
"isActive": {
"name": "isActive",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"createdBy": {
"name": "createdBy",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.Song": {
"name": "Song",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": true
},
"artist": {
"name": "artist",
"type": "text",
"primaryKey": false,
"notNull": true
},
"requesterId": {
"name": "requesterId",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"played": {
"name": "played",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"playedAt": {
"name": "playedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"semester": {
"name": "semester",
"type": "text",
"primaryKey": false,
"notNull": false
},
"preferredPlayTimeId": {
"name": "preferredPlayTimeId",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"cover": {
"name": "cover",
"type": "text",
"primaryKey": false,
"notNull": false
},
"musicPlatform": {
"name": "musicPlatform",
"type": "text",
"primaryKey": false,
"notNull": false
},
"musicId": {
"name": "musicId",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.SystemSettings": {
"name": "SystemSettings",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"enablePlayTimeSelection": {
"name": "enablePlayTimeSelection",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"siteTitle": {
"name": "siteTitle",
"type": "text",
"primaryKey": false,
"notNull": false
},
"siteLogoUrl": {
"name": "siteLogoUrl",
"type": "text",
"primaryKey": false,
"notNull": false
},
"schoolLogoHomeUrl": {
"name": "schoolLogoHomeUrl",
"type": "text",
"primaryKey": false,
"notNull": false
},
"schoolLogoPrintUrl": {
"name": "schoolLogoPrintUrl",
"type": "text",
"primaryKey": false,
"notNull": false
},
"siteDescription": {
"name": "siteDescription",
"type": "text",
"primaryKey": false,
"notNull": false
},
"submissionGuidelines": {
"name": "submissionGuidelines",
"type": "text",
"primaryKey": false,
"notNull": false
},
"icpNumber": {
"name": "icpNumber",
"type": "text",
"primaryKey": false,
"notNull": false
},
"enableSubmissionLimit": {
"name": "enableSubmissionLimit",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"dailySubmissionLimit": {
"name": "dailySubmissionLimit",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"weeklySubmissionLimit": {
"name": "weeklySubmissionLimit",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"showBlacklistKeywords": {
"name": "showBlacklistKeywords",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"hideStudentInfo": {
"name": "hideStudentInfo",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.User": {
"name": "User",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"grade": {
"name": "grade",
"type": "text",
"primaryKey": false,
"notNull": false
},
"class": {
"name": "class",
"type": "text",
"primaryKey": false,
"notNull": false
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'USER'"
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": true
},
"lastLogin": {
"name": "lastLogin",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"lastLoginIp": {
"name": "lastLoginIp",
"type": "text",
"primaryKey": false,
"notNull": false
},
"passwordChangedAt": {
"name": "passwordChangedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"forcePasswordChange": {
"name": "forcePasswordChange",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"meowNickname": {
"name": "meowNickname",
"type": "text",
"primaryKey": false,
"notNull": false
},
"meowBoundAt": {
"name": "meowBoundAt",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"User_username_unique": {
"name": "User_username_unique",
"nullsNotDistinct": false,
"columns": [
"username"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.Vote": {
"name": "Vote",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"songId": {
"name": "songId",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"userId": {
"name": "userId",
"type": "integer",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {
"public.BlacklistType": {
"name": "BlacklistType",
"schema": "public",
"values": [
"SONG",
"KEYWORD"
]
}
},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
@@ -0,0 +1,767 @@
{
"id": "b7406ed0-51d0-4217-92f0-0131b12db197",
"prevId": "5412e543-4a9c-416f-87b1-b8a405a19172",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.NotificationSettings": {
"name": "NotificationSettings",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"userId": {
"name": "userId",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"enabled": {
"name": "enabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"songRequestEnabled": {
"name": "songRequestEnabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"songVotedEnabled": {
"name": "songVotedEnabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"songPlayedEnabled": {
"name": "songPlayedEnabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"refreshInterval": {
"name": "refreshInterval",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 60
},
"songVotedThreshold": {
"name": "songVotedThreshold",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 1
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.Notification": {
"name": "Notification",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"message": {
"name": "message",
"type": "text",
"primaryKey": false,
"notNull": true
},
"read": {
"name": "read",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"userId": {
"name": "userId",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"songId": {
"name": "songId",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.PlayTime": {
"name": "PlayTime",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"startTime": {
"name": "startTime",
"type": "text",
"primaryKey": false,
"notNull": false
},
"endTime": {
"name": "endTime",
"type": "text",
"primaryKey": false,
"notNull": false
},
"enabled": {
"name": "enabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.Schedule": {
"name": "Schedule",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"songId": {
"name": "songId",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"playDate": {
"name": "playDate",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"played": {
"name": "played",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"sequence": {
"name": "sequence",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 1
},
"playTimeId": {
"name": "playTimeId",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.Semester": {
"name": "Semester",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"isActive": {
"name": "isActive",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.SongBlacklist": {
"name": "SongBlacklist",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"type": {
"name": "type",
"type": "BlacklistType",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
},
"reason": {
"name": "reason",
"type": "text",
"primaryKey": false,
"notNull": false
},
"isActive": {
"name": "isActive",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"createdBy": {
"name": "createdBy",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.Song": {
"name": "Song",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": true
},
"artist": {
"name": "artist",
"type": "text",
"primaryKey": false,
"notNull": true
},
"requesterId": {
"name": "requesterId",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"played": {
"name": "played",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"playedAt": {
"name": "playedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"semester": {
"name": "semester",
"type": "text",
"primaryKey": false,
"notNull": false
},
"preferredPlayTimeId": {
"name": "preferredPlayTimeId",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"cover": {
"name": "cover",
"type": "text",
"primaryKey": false,
"notNull": false
},
"musicPlatform": {
"name": "musicPlatform",
"type": "text",
"primaryKey": false,
"notNull": false
},
"musicId": {
"name": "musicId",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.SystemSettings": {
"name": "SystemSettings",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"enablePlayTimeSelection": {
"name": "enablePlayTimeSelection",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"siteTitle": {
"name": "siteTitle",
"type": "text",
"primaryKey": false,
"notNull": false
},
"siteLogoUrl": {
"name": "siteLogoUrl",
"type": "text",
"primaryKey": false,
"notNull": false
},
"schoolLogoHomeUrl": {
"name": "schoolLogoHomeUrl",
"type": "text",
"primaryKey": false,
"notNull": false
},
"schoolLogoPrintUrl": {
"name": "schoolLogoPrintUrl",
"type": "text",
"primaryKey": false,
"notNull": false
},
"siteDescription": {
"name": "siteDescription",
"type": "text",
"primaryKey": false,
"notNull": false
},
"submissionGuidelines": {
"name": "submissionGuidelines",
"type": "text",
"primaryKey": false,
"notNull": false
},
"icpNumber": {
"name": "icpNumber",
"type": "text",
"primaryKey": false,
"notNull": false
},
"enableSubmissionLimit": {
"name": "enableSubmissionLimit",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"dailySubmissionLimit": {
"name": "dailySubmissionLimit",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"weeklySubmissionLimit": {
"name": "weeklySubmissionLimit",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"showBlacklistKeywords": {
"name": "showBlacklistKeywords",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"hideStudentInfo": {
"name": "hideStudentInfo",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.User": {
"name": "User",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"grade": {
"name": "grade",
"type": "text",
"primaryKey": false,
"notNull": false
},
"class": {
"name": "class",
"type": "text",
"primaryKey": false,
"notNull": false
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'USER'"
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": true
},
"lastLogin": {
"name": "lastLogin",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"lastLoginIp": {
"name": "lastLoginIp",
"type": "text",
"primaryKey": false,
"notNull": false
},
"passwordChangedAt": {
"name": "passwordChangedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"forcePasswordChange": {
"name": "forcePasswordChange",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"meowNickname": {
"name": "meowNickname",
"type": "text",
"primaryKey": false,
"notNull": false
},
"meowBoundAt": {
"name": "meowBoundAt",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.Vote": {
"name": "Vote",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"songId": {
"name": "songId",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"userId": {
"name": "userId",
"type": "integer",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {
"public.BlacklistType": {
"name": "BlacklistType",
"schema": "public",
"values": [
"SONG",
"KEYWORD"
]
}
},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+349
View File
@@ -0,0 +1,349 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1756291495622,
"tag": "0000_friendly_garia",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1756291496622,
"tag": "20250102000001_add_schedule_draft_support",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1756291497622,
"tag": "20250113000001_add_user_status_management",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1756299330141,
"tag": "20250827125530_little_james_howlett",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1756339201000,
"tag": "20250828000001_add_api_framework_tables",
"breakpoints": true
},
{
"idx": 5,
"version": "7",
"when": 1756368610365,
"tag": "20250828081010_careful_shinobi_shaw",
"breakpoints": true
},
{
"idx": 6,
"version": "7",
"when": 1757548801000,
"tag": "20250911000001_add_smtp_email_fields",
"breakpoints": true
},
{
"idx": 7,
"version": "7",
"when": 1757772919975,
"tag": "20250913141519_clumsy_maria_hill",
"breakpoints": true
},
{
"idx": 8,
"version": "7",
"when": 1757819341115,
"tag": "20250914030901_majestic_black_widow",
"breakpoints": true
},
{
"idx": 9,
"version": "7",
"when": 1759363201000,
"tag": "20251002000001_add_song_play_url",
"breakpoints": true
},
{
"idx": 10,
"version": "7",
"when": 1759395147253,
"tag": "20251002085227_deep_silver_sable",
"breakpoints": true
},
{
"idx": 11,
"version": "7",
"when": 1767777840967,
"tag": "20260107092400_add_collaboration_and_request_time",
"breakpoints": true
},
{
"idx": 12,
"version": "7",
"when": 1768042908810,
"tag": "20260110000001_add_replay_requests",
"breakpoints": true
},
{
"idx": 13,
"version": "7",
"when": 1769766215307,
"tag": "20260130094335_add_replay_request_status",
"breakpoints": true
},
{
"idx": 14,
"version": "7",
"when": 1770895837437,
"tag": "20260212113037_add_user_identities_table",
"breakpoints": true
},
{
"idx": 15,
"version": "7",
"when": 1772187630689,
"tag": "20260227102030_add_monthly_submission_limit",
"breakpoints": true
},
{
"idx": 16,
"version": "7",
"when": 1772889825480,
"tag": "20260307132345_add_gongan_filing",
"breakpoints": true
},
{
"idx": 17,
"version": "7",
"when": 1772890398452,
"tag": "20260307133318_remove_gongan_link",
"breakpoints": true
},
{
"idx": 18,
"version": "7",
"when": 1773923154561,
"tag": "20260319122554_add_collaborative_submission_toggle",
"breakpoints": true
},
{
"idx": 19,
"version": "7",
"when": 1773927000000,
"tag": "20260319133000_add_submission_remarks",
"breakpoints": true
},
{
"idx": 20,
"version": "7",
"when": 1774700000000,
"tag": "20260328120000_add_custom_oauth_provider",
"breakpoints": true
},
{
"idx": 21,
"version": "7",
"when": 1774700600000,
"tag": "20260328121000_add_oauth_system_settings_columns",
"breakpoints": true
},
{
"idx": 22,
"version": "7",
"when": 1775136952492,
"tag": "20260402133552_remarkable_star_brand",
"breakpoints": true
},
{
"idx": 23,
"version": "7",
"when": 1775310943895,
"tag": "20260404135543_add_show_beian_icon",
"breakpoints": true
},
{
"idx": 24,
"version": "7",
"when": 1775393214709,
"tag": "20260405124654_add_allow_oauth_registration",
"breakpoints": true
},
{
"idx": 25,
"version": "7",
"when": 1778311885032,
"tag": "20260509073125_add_captcha_settings",
"breakpoints": true
},
{
"idx": 26,
"version": "7",
"when": 1778318043510,
"tag": "20260509091403_add_turnstile_support",
"breakpoints": true
},
{
"idx": 27,
"version": "7",
"when": 1779603809404,
"tag": "20260524062329_add_instance_id_and_telemetry_toggle",
"breakpoints": true
},
{
"idx": 28,
"version": "7",
"when": 1781481660000,
"tag": "20260614000100_add_card_code_request_system",
"breakpoints": true
},
{
"idx": 29,
"version": "7",
"when": 1782432001000,
"tag": "20260626000100_add_card_code_auto_delete",
"breakpoints": true
},
{
"idx": 30,
"version": "7",
"when": 1782527721417,
"tag": "20260627023521_add_card_code_foreign_key_indexes",
"breakpoints": true
},
{
"idx": 31,
"version": "7",
"when": 1783690428462,
"tag": "20260710133348_add_card_code_limit_bypass",
"breakpoints": true
},
{
"idx": 32,
"version": "7",
"when": 1784419260000,
"tag": "20260718000100_add_aggregate_oauth_provider",
"breakpoints": true
},
{
"idx": 33,
"version": "7",
"when": 1784530957225,
"tag": "20260720070237_minimize-redis-add-song-query-indexes",
"breakpoints": true
},
{
"idx": 34,
"version": "7",
"when": 1785196801000,
"tag": "20260727000100_replay_request_seamless",
"breakpoints": true
},
{
"idx": 35,
"version": "7",
"when": 1785239348895,
"tag": "20260728114908_add_password_security",
"breakpoints": true
},
{
"idx": 36,
"version": "7",
"when": 1785492048291,
"tag": "20260731100048_notification-announcement",
"breakpoints": true
},
{
"idx": 37,
"version": "7",
"when": 1785578400000,
"tag": "20260801000000_add_auto_backup",
"breakpoints": true
},
{
"idx": 38,
"version": "7",
"when": 1786269600000,
"tag": "20260809000000_add_platform_management",
"breakpoints": true
},
{
"idx": 39,
"version": "7",
"when": 1786809600000,
"tag": "20260816000000_add_schedule_song_pool",
"breakpoints": true
},
{
"idx": 40,
"version": "7",
"when": 1787483779816,
"tag": "20260823000000_theme-settings",
"breakpoints": true
},
{
"idx": 41,
"version": "7",
"when": 1787497559680,
"tag": "20260823230554_add_auth_sessions",
"breakpoints": true
},
{
"idx": 42,
"version": "7",
"when": 1787587786102,
"tag": "20260824160946_add_schedule_visibility_range",
"breakpoints": true
},
{
"idx": 43,
"version": "7",
"when": 1787653675610,
"tag": "20260825102755_add-oauth-avatar",
"breakpoints": true
},
{
"idx": 44,
"version": "7",
"when": 1787655239368,
"tag": "20260825105359_drop_aggregate_oauth_endpoint_default",
"breakpoints": true
},
{
"idx": 45,
"version": "7",
"when": 1787715174545,
"tag": "20260826033254_add_registration_review_grade_class_and_note_review",
"breakpoints": true
},
{
"idx": 46,
"version": "7",
"when": 1787825301370,
"tag": "20260827100821_add_register_requires_grade_class",
"breakpoints": true
},
{
"idx": 47,
"version": "7",
"when": 1787880865098,
"tag": "20260828013425_add_song_type_blacklist",
"breakpoints": true
},
{
"idx": 48,
"version": "7",
"when": 1787902641410,
"tag": "20260828073721_add_statistics_code",
"breakpoints": true
}
]
}
+13
View File
@@ -0,0 +1,13 @@
import { relations } from "drizzle-orm/relations";
import { user, userIdentity } from "./schema";
export const userIdentityRelations = relations(userIdentity, ({one}) => ({
user: one(user, {
fields: [userIdentity.userId],
references: [user.id]
}),
}));
export const userRelations = relations(user, ({many}) => ({
userIdentities: many(userIdentity),
}));
+415
View File
@@ -0,0 +1,415 @@
import { pgTable, serial, timestamp, text, boolean, integer, uuid, varchar, unique, uniqueIndex, index, bigint, foreignKey, pgEnum } from "drizzle-orm/pg-core"
import { sql } from "drizzle-orm"
export const blacklistType = pgEnum("BlacklistType", ['SONG', 'KEYWORD'])
export const cardCodeStatus = pgEnum("card_code_status", ['AVAILABLE', 'LOCKED', 'REDEEMED', 'INVALID'])
export const collaboratorStatus = pgEnum("collaborator_status", ['PENDING', 'ACCEPTED', 'REJECTED'])
export const replayRequestStatus = pgEnum("replay_request_status", ['PENDING', 'FULFILLED', 'REJECTED'])
export const userStatus = pgEnum("user_status", ['active', 'withdrawn', 'graduate'])
export const semester = pgTable("Semester", {
id: serial().primaryKey().notNull(),
createdAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
updatedAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
name: text().notNull(),
isActive: boolean().default(false).notNull(),
});
export const notification = pgTable("Notification", {
id: serial().primaryKey().notNull(),
createdAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
updatedAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
type: text().notNull(),
message: text().notNull(),
read: boolean().default(false).notNull(),
userId: integer().notNull(),
songId: integer(),
});
export const playTime = pgTable("PlayTime", {
id: serial().primaryKey().notNull(),
createdAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
updatedAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
name: text().notNull(),
startTime: text(),
endTime: text(),
enabled: boolean().default(true).notNull(),
description: text(),
});
export const songBlacklist = pgTable("SongBlacklist", {
id: serial().primaryKey().notNull(),
createdAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
updatedAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
type: blacklistType().notNull(),
value: text().notNull(),
reason: text(),
isActive: boolean().default(true).notNull(),
createdBy: integer(),
});
export const schedule = pgTable("Schedule", {
id: serial().primaryKey().notNull(),
createdAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
updatedAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
songId: integer().notNull(),
playDate: timestamp({ mode: 'string' }).notNull(),
played: boolean().default(false).notNull(),
sequence: integer().default(1).notNull(),
playTimeId: integer(),
isDraft: boolean().default(false).notNull(),
publishedAt: timestamp({ mode: 'string' }),
replayRequestId: integer(),
});
export const scheduleSongPool = pgTable("ScheduleSongPool", {
id: serial().primaryKey().notNull(),
createdAt: timestamp().defaultNow().notNull(),
songId: integer().notNull(),
addedBy: integer(),
});
export const notificationSettings = pgTable("NotificationSettings", {
id: serial().primaryKey().notNull(),
createdAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
updatedAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
userId: integer().notNull(),
enabled: boolean().default(true).notNull(),
songRequestEnabled: boolean().default(true).notNull(),
songVotedEnabled: boolean().default(true).notNull(),
songPlayedEnabled: boolean().default(true).notNull(),
refreshInterval: integer().default(60).notNull(),
songVotedThreshold: integer().default(1).notNull(),
});
export const apiLogs = pgTable("api_logs", {
id: uuid().defaultRandom().primaryKey().notNull(),
apiKeyId: uuid("api_key_id"),
endpoint: varchar({ length: 500 }).notNull(),
method: varchar({ length: 10 }).notNull(),
ipAddress: text("ip_address").notNull(),
userAgent: text("user_agent"),
statusCode: integer("status_code").notNull(),
responseTimeMs: integer("response_time_ms").notNull(),
requestBody: text("request_body"),
responseBody: text("response_body"),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(),
errorMessage: text("error_message"),
});
export const song = pgTable("Song", {
id: serial().primaryKey().notNull(),
createdAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
updatedAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
title: text().notNull(),
artist: text().notNull(),
requesterId: integer().notNull(),
played: boolean().default(false).notNull(),
playedAt: timestamp({ mode: 'string' }).defaultNow(),
semester: text(),
preferredPlayTimeId: integer(),
cover: text(),
musicPlatform: text(),
musicId: text(),
durationSeconds: integer(),
playUrl: text(),
hitRequestId: integer(),
submissionNote: text(),
submissionNotePublic: boolean().default(false).notNull(),
cardCodeId: integer(),
});
export const vote = pgTable("Vote", {
id: serial().primaryKey().notNull(),
createdAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
songId: integer().notNull(),
userId: integer().notNull(),
});
export const apiKeyPermissions = pgTable("api_key_permissions", {
id: uuid().defaultRandom().primaryKey().notNull(),
apiKeyId: uuid("api_key_id").notNull(),
permission: varchar({ length: 100 }).notNull(),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(),
});
export const userStatusLogs = pgTable("user_status_logs", {
id: serial().primaryKey().notNull(),
userId: integer("user_id").notNull(),
oldStatus: userStatus("old_status"),
newStatus: userStatus("new_status").notNull(),
reason: text(),
operatorId: integer("operator_id"),
createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
});
export const apiKeys = pgTable("api_keys", {
id: uuid().defaultRandom().primaryKey().notNull(),
name: varchar({ length: 255 }).notNull(),
description: text(),
keyHash: varchar("key_hash", { length: 255 }).notNull(),
keyPrefix: varchar("key_prefix", { length: 10 }).notNull(),
isActive: boolean("is_active").default(true).notNull(),
expiresAt: timestamp("expires_at", { withTimezone: true, mode: 'string' }),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(),
lastUsedAt: timestamp("last_used_at", { withTimezone: true, mode: 'string' }),
createdByUserId: integer("created_by_user_id").notNull(),
usageCount: integer("usage_count").default(0).notNull(),
}, (table) => [
unique("api_keys_key_hash_unique").on(table.keyHash),
]);
export const user = pgTable("User", {
id: serial().primaryKey().notNull(),
createdAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
updatedAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
username: text().notNull(),
name: text(),
grade: text(),
class: text(),
role: text().default('USER').notNull(),
password: text().notNull(),
lastLogin: timestamp({ mode: 'string' }),
lastLoginIp: text(),
passwordChangedAt: timestamp({ mode: 'string' }),
forcePasswordChange: boolean().default(false).notNull(),
tokenVersion: integer().default(0).notNull(),
meowNickname: text(),
meowBoundAt: timestamp({ mode: 'string' }),
status: userStatus().default('active').notNull(),
statusChangedAt: timestamp({ mode: 'string' }).defaultNow(),
statusChangedBy: integer(),
email: text(),
emailVerified: boolean().default(false),
});
export const passwordAuditLog = pgTable("PasswordAuditLog", {
id: serial().primaryKey().notNull(),
userId: integer().notNull(),
actorId: integer(),
action: text().notNull(),
success: boolean().notNull(),
ipAddress: text(),
userAgent: text(),
failureReason: text(),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).defaultNow().notNull(),
}, (table) => [
index("PasswordAuditLog_user_created_idx").on(table.userId, table.createdAt),
]);
export const passwordRateLimit = pgTable("PasswordRateLimit", {
key: text().primaryKey().notNull(),
count: integer().notNull(),
resetAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
}, (table) => [
index("PasswordRateLimit_reset_idx").on(table.resetAt),
]);
export const emailTemplate = pgTable("EmailTemplate", {
id: serial().primaryKey().notNull(),
createdAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
updatedAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
key: varchar({ length: 100 }).notNull(),
name: varchar({ length: 200 }).notNull(),
subject: varchar({ length: 300 }).notNull(),
html: text().notNull(),
updatedByUserId: integer(),
});
export const collaborationLogs = pgTable("collaboration_logs", {
id: uuid().defaultRandom().primaryKey().notNull(),
collaboratorId: uuid("collaborator_id").notNull(),
action: varchar({ length: 50 }).notNull(),
operatorId: integer("operator_id").notNull(),
ipAddress: text("ip_address"),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(),
});
export const requestTime = pgTable("RequestTime", {
id: serial().primaryKey().notNull(),
createdAt: timestamp({ precision: 6, mode: 'string' }).defaultNow().notNull(),
updatedAt: timestamp({ precision: 6, mode: 'string' }).defaultNow().notNull(),
name: text().notNull(),
startTime: timestamp({ mode: 'string' }).notNull(),
endTime: timestamp({ mode: 'string' }).notNull(),
enabled: boolean().default(true).notNull(),
description: text(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
expected: bigint({ mode: "number" }).default(0).notNull(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
accepted: bigint({ mode: "number" }).default(0).notNull(),
past: boolean().default(false).notNull(),
});
export const songCollaborators = pgTable("song_collaborators", {
id: uuid().defaultRandom().primaryKey().notNull(),
songId: integer("song_id").notNull(),
userId: integer("user_id").notNull(),
status: collaboratorStatus().default('PENDING').notNull(),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(),
});
export const songReplayRequests = pgTable("song_replay_requests", {
id: serial().primaryKey().notNull(),
songId: integer("song_id").notNull(),
userId: integer("user_id").notNull(),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(),
status: replayRequestStatus().default('PENDING').notNull(),
preferredPlayTimeId: integer("preferred_play_time_id"),
submissionNote: text("submission_note"),
submissionNotePublic: boolean("submission_note_public").default(false).notNull(),
}, (table) => [
uniqueIndex("song_replay_requests_pending_song_user_unique")
.on(table.songId, table.userId)
.where(sql`${table.status} = 'PENDING'`),
]);
export const userIdentity = pgTable("UserIdentity", {
id: serial().primaryKey().notNull(),
userId: integer().notNull(),
provider: text().notNull(),
providerUserId: text().notNull(),
providerUsername: text(),
createdAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
}, (table) => [
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
name: "UserIdentity_userId_User_id_fk"
}).onDelete("cascade"),
unique("UserIdentity_provider_providerUserId_unique").on(table.provider, table.providerUserId),
]);
export const cardCode = pgTable("CardCode", {
id: serial().primaryKey().notNull(),
createdAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
updatedAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
code: text().notNull(),
status: cardCodeStatus().default('AVAILABLE').notNull(),
lockedBy: integer(),
lockedAt: timestamp({ mode: 'string' }),
redeemedBy: integer(),
redeemedAt: timestamp({ mode: 'string' }),
note: text(),
}, (table) => [
foreignKey({
columns: [table.lockedBy],
foreignColumns: [user.id],
name: "CardCode_lockedBy_User_id_fk"
}).onDelete("set null"),
foreignKey({
columns: [table.redeemedBy],
foreignColumns: [user.id],
name: "CardCode_redeemedBy_User_id_fk"
}).onDelete("set null"),
unique("CardCode_code_unique").on(table.code),
]);
export const cardCodeRedeemLog = pgTable("CardCodeRedeemLog", {
id: serial().primaryKey().notNull(),
createdAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
cardCodeId: integer(),
codeSnapshot: text().notNull(),
redeemedBy: integer().notNull(),
redeemedAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
source: text().default('UNKNOWN').notNull(),
songId: integer(),
}, (table) => [
foreignKey({
columns: [table.cardCodeId],
foreignColumns: [cardCode.id],
name: "CardCodeRedeemLog_cardCodeId_CardCode_id_fk"
}).onDelete("set null"),
foreignKey({
columns: [table.redeemedBy],
foreignColumns: [user.id],
name: "CardCodeRedeemLog_redeemedBy_User_id_fk"
}).onDelete("restrict"),
foreignKey({
columns: [table.songId],
foreignColumns: [song.id],
name: "CardCodeRedeemLog_songId_Song_id_fk"
}).onDelete("set null"),
]);
export const systemSettings = pgTable("SystemSettings", {
id: serial().primaryKey().notNull(),
createdAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
updatedAt: timestamp({ mode: 'string' }).defaultNow().notNull(),
enablePlayTimeSelection: boolean().default(false).notNull(),
siteTitle: text(),
siteLogoUrl: text(),
schoolLogoHomeUrl: text(),
schoolLogoPrintUrl: text(),
siteDescription: text(),
submissionGuidelines: text(),
icpNumber: text(),
enableSubmissionLimit: boolean().default(false).notNull(),
dailySubmissionLimit: integer(),
weeklySubmissionLimit: integer(),
showBlacklistKeywords: boolean().default(false).notNull(),
hideStudentInfo: boolean().default(true).notNull(),
smtpEnabled: boolean().default(false).notNull(),
smtpHost: text(),
smtpPort: integer().default(587),
smtpSecure: boolean().default(false),
smtpUsername: text(),
smtpPassword: text(),
smtpFromEmail: text(),
smtpFromName: text().default('校园广播站'),
enableRequestTimeLimitation: boolean().default(false).notNull(),
forceBlockAllRequests: boolean().default(false).notNull(),
forcePasswordChangeOnFirstLogin: boolean().default(false).notNull(),
enableReplayRequests: boolean().default(false).notNull(),
monthlySubmissionLimit: integer(),
gonganNumber: text(),
enableCollaborativeSubmission: boolean().default(true).notNull(),
enableSubmissionRemarks: boolean().default(false).notNull(),
customOauthEnabled: boolean().default(false).notNull(),
customOauthDisplayName: text(),
customOauthAuthorizeUrl: text(),
customOauthTokenUrl: text(),
customOauthUserInfoUrl: text(),
customOauthScope: text(),
customOauthClientId: text(),
customOauthClientSecret: text(),
customOauthUserIdField: text(),
customOauthUsernameField: text(),
customOauthNameField: text(),
customOauthEmailField: text(),
customOauthAvatarField: text(),
oauthRedirectUri: text(),
oauthStateSecret: text(),
oauthProviders: text().default('[]'),
githubOauthEnabled: boolean().default(false).notNull(),
githubClientId: text(),
githubClientSecret: text(),
casdoorOauthEnabled: boolean().default(false).notNull(),
casdoorServerUrl: text(),
casdoorClientId: text(),
casdoorClientSecret: text(),
casdoorOrganizationName: text(),
googleOauthEnabled: boolean().default(false).notNull(),
googleClientId: text(),
googleClientSecret: text(),
aggregateOauthEnabled: boolean("aggregateOAuthEnabled").default(false).notNull(),
aggregateOauthAppId: text("aggregateOAuthAppId"),
aggregateOauthAppKey: text("aggregateOAuthAppKey"),
aggregateOauthLoginType: text("aggregateOAuthLoginType").default('qq'),
aggregateOauthEndpoint: text("aggregateOAuthEndpoint").default('https://a.idcfx.net/connect.php'),
showBeianIcon: boolean().default(false).notNull(),
allowOauthRegistration: boolean().default(false).notNull(),
captchaEnabled: boolean().default(false).notNull(),
captchaMaxFailures: integer().default(3).notNull(),
captchaProvider: text().default('graphic').notNull(),
turnstileSiteKey: text(),
turnstileSecretKey: text(),
instanceId: text("instance_id"),
telemetryEnabled: boolean().default(true).notNull(),
enableCardCodeRequests: boolean().default(false).notNull(),
requireCardCodeForRequests: boolean().default(false).notNull(),
});