feat(models): mounts/media_items/playback_progress/danmaku_cache

This commit is contained in:
noelorin 2026-09-25 02:40:32 +08:00
parent 3143a9c7d0
commit f94aabe8a5
5 changed files with 123 additions and 0 deletions

View File

@ -0,0 +1,20 @@
import { sql } from "drizzle-orm";
import { integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
export const danmakuCache = sqliteTable(
"danmaku_cache",
{
id: text("id")
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
matchKey: text("match_key").notNull(),
payload: text("payload").notNull(),
source: text("source").notNull().default("open-network"),
expiresAt: integer("expires_at", { mode: "timestamp" }),
createdAt: integer("created_at", { mode: "timestamp" })
.notNull()
.default(sql`(unixepoch())`),
},
(t) => [uniqueIndex("danmaku_match_key_uq").on(t.matchKey)],
);
export type DanmakuCacheRow = typeof danmakuCache.$inferSelect;

View File

@ -5,3 +5,11 @@ export { sessions } from "./sessions.js";
export type { Session, NewSession } from "./sessions.js";
export { passwordResets } from "./password-resets.js";
export type { PasswordReset, NewPasswordReset } from "./password-resets.js";
export { mounts } from "./mounts.js";
export type { Mount, NewMount } from "./mounts.js";
export { mediaItems } from "./media-items.js";
export type { MediaItem, NewMediaItem } from "./media-items.js";
export { playbackProgress } from "./playback-progress.js";
export type { PlaybackProgress } from "./playback-progress.js";
export { danmakuCache } from "./danmaku-cache.js";
export type { DanmakuCacheRow } from "./danmaku-cache.js";

View File

@ -0,0 +1,37 @@
import { sql } from "drizzle-orm";
import { integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
import { users } from "./users.js";
import { mounts } from "./mounts.js";
export const mediaItems = sqliteTable(
"media_items",
{
id: text("id")
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
userId: text("user_id")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
mountId: text("mount_id").references(() => mounts.id, { onDelete: "set null" }),
path: text("path").notNull(),
rawName: text("raw_name").notNull(),
title: text("title").notNull(),
size: integer("size").notNull().default(0),
mime: text("mime"),
bangumiId: integer("bangumi_id"),
epNumber: integer("ep_number"),
scrapeStatus: text("scrape_status").notNull().default("pending"),
scrapedAt: integer("scraped_at", { mode: "timestamp" }),
posterUrl: text("poster_url"),
matchedHash: text("matched_hash"),
scannedAt: integer("scanned_at", { mode: "timestamp" })
.notNull()
.default(sql`(unixepoch())`),
updatedAt: integer("updated_at", { mode: "timestamp" })
.notNull()
.default(sql`(unixepoch())`),
},
(t) => [uniqueIndex("media_user_path_uq").on(t.userId, t.path)],
);
export type MediaItem = typeof mediaItems.$inferSelect;
export type NewMediaItem = typeof mediaItems.$inferInsert;

View File

@ -0,0 +1,32 @@
import { sql } from "drizzle-orm";
import { integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
import { users } from "./users.js";
export const mounts = sqliteTable(
"mounts",
{
id: text("id")
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
userId: text("user_id")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
name: text("name").notNull(),
type: text("type").notNull().default("webdav"),
baseUrl: text("base_url").notNull(),
username: text("username"),
/** AES-GCM 密文(base64),永不回传前端 */
secretEnc: text("secret_enc").notNull().default(""),
rootPath: text("root_path").notNull().default("/"),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
createdAt: integer("created_at", { mode: "timestamp" })
.notNull()
.default(sql`(unixepoch())`),
updatedAt: integer("updated_at", { mode: "timestamp" })
.notNull()
.default(sql`(unixepoch())`),
},
(t) => [uniqueIndex("mounts_user_name_uq").on(t.userId, t.name)],
);
export type Mount = typeof mounts.$inferSelect;
export type NewMount = typeof mounts.$inferInsert;

View File

@ -0,0 +1,26 @@
import { sql } from "drizzle-orm";
import { integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
import { users } from "./users.js";
import { mediaItems } from "./media-items.js";
export const playbackProgress = sqliteTable(
"playback_progress",
{
id: text("id")
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
userId: text("user_id")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
mediaItemId: text("media_item_id")
.notNull()
.references(() => mediaItems.id, { onDelete: "cascade" }),
positionMs: integer("position_ms").notNull().default(0),
durationMs: integer("duration_ms").notNull().default(0),
updatedAt: integer("updated_at", { mode: "timestamp" })
.notNull()
.default(sql`(unixepoch())`),
},
(t) => [uniqueIndex("progress_user_media_uq").on(t.userId, t.mediaItemId)],
);
export type PlaybackProgress = typeof playbackProgress.$inferSelect;