feat(models,dao): danmaku prefs/docs tables and match source
This commit is contained in:
parent
5ce1f91a26
commit
3467e397e7
|
|
@ -0,0 +1,42 @@
|
|||
import { and, eq } from "drizzle-orm";
|
||||
import { danmakuDocs, type DanmakuDocRow } from "@app/models";
|
||||
import { db } from "@app/db";
|
||||
|
||||
export const danmakuDocsDao = {
|
||||
async get(userId: string, mediaItemId: string, source: string): Promise<DanmakuDocRow | null> {
|
||||
const [row] = await db
|
||||
.select()
|
||||
.from(danmakuDocs)
|
||||
.where(
|
||||
and(
|
||||
eq(danmakuDocs.userId, userId),
|
||||
eq(danmakuDocs.mediaItemId, mediaItemId),
|
||||
eq(danmakuDocs.source, source),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
return row ?? null;
|
||||
},
|
||||
async upsert(data: {
|
||||
userId: string;
|
||||
mediaItemId: string;
|
||||
source: string;
|
||||
xml: string;
|
||||
byteSize: number;
|
||||
}): Promise<DanmakuDocRow> {
|
||||
const existing = await danmakuDocsDao.get(data.userId, data.mediaItemId, data.source);
|
||||
if (existing) {
|
||||
const [row] = await db
|
||||
.update(danmakuDocs)
|
||||
.set({ xml: data.xml, byteSize: data.byteSize, updatedAt: new Date() })
|
||||
.where(eq(danmakuDocs.id, existing.id))
|
||||
.returning();
|
||||
if (!row) throw new Error("Failed to update danmaku doc");
|
||||
return row;
|
||||
}
|
||||
const rows = await db.insert(danmakuDocs).values(data).returning();
|
||||
const row = rows[0];
|
||||
if (!row) throw new Error("Failed to insert danmaku doc");
|
||||
return row;
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import { eq } from "drizzle-orm";
|
||||
import { danmakuPrefs, type DanmakuPrefsRow } from "@app/models";
|
||||
import { db } from "@app/db";
|
||||
|
||||
export const danmakuPrefsDao = {
|
||||
async getByUser(userId: string): Promise<DanmakuPrefsRow | null> {
|
||||
const [row] = await db
|
||||
.select()
|
||||
.from(danmakuPrefs)
|
||||
.where(eq(danmakuPrefs.userId, userId))
|
||||
.limit(1);
|
||||
return row ?? null;
|
||||
},
|
||||
async upsert(data: {
|
||||
userId: string;
|
||||
enabled: boolean;
|
||||
opacity: number;
|
||||
density: number;
|
||||
blockKeywords: string;
|
||||
blockTypes: string;
|
||||
}): Promise<DanmakuPrefsRow> {
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(danmakuPrefs)
|
||||
.where(eq(danmakuPrefs.userId, data.userId))
|
||||
.limit(1);
|
||||
if (existing[0]) {
|
||||
const [row] = await db
|
||||
.update(danmakuPrefs)
|
||||
.set({ ...data, updatedAt: new Date() })
|
||||
.where(eq(danmakuPrefs.id, existing[0].id))
|
||||
.returning();
|
||||
if (!row) throw new Error("Failed to update danmaku prefs");
|
||||
return row;
|
||||
}
|
||||
const rows = await db.insert(danmakuPrefs).values(data).returning();
|
||||
const row = rows[0];
|
||||
if (!row) throw new Error("Failed to insert danmaku prefs");
|
||||
return row;
|
||||
},
|
||||
};
|
||||
|
|
@ -11,3 +11,5 @@ export { mediaItemDao } from "./media-items.js";
|
|||
export type { MediaItemRow } from "./media-items.js";
|
||||
export { playbackProgressDao } from "./playback-progress.js";
|
||||
export { danmakuCacheDao } from "./danmaku-cache.js";
|
||||
export { danmakuPrefsDao } from "./danmaku-prefs.js";
|
||||
export { danmakuDocsDao } from "./danmaku-docs.js";
|
||||
|
|
|
|||
|
|
@ -280,13 +280,18 @@ export const mediaItemDao = {
|
|||
async setDanmakuMatch(
|
||||
id: string,
|
||||
userId: string,
|
||||
data: { episodeId: number; matchedHash?: string | null },
|
||||
data: {
|
||||
episodeId: number;
|
||||
matchedHash?: string | null;
|
||||
source?: "auto" | "manual" | "none";
|
||||
},
|
||||
): Promise<MediaItemRow | null> {
|
||||
const [row] = await db
|
||||
.update(mediaItems)
|
||||
.set({
|
||||
dandanplayEpisodeId: data.episodeId,
|
||||
matchedHash: data.matchedHash ?? null,
|
||||
danmakuMatchSource: data.source ?? "auto",
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(and(eq(mediaItems.id, id), eq(mediaItems.userId, userId)))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
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 danmakuDocs = sqliteTable(
|
||||
"danmaku_docs",
|
||||
{
|
||||
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" }),
|
||||
/** `open-network` | `local-xml` */
|
||||
source: text("source").notNull(),
|
||||
xml: text("xml").notNull(),
|
||||
byteSize: integer("byte_size").notNull().default(0),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.default(sql`(unixepoch())`),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.default(sql`(unixepoch())`),
|
||||
},
|
||||
(t) => [uniqueIndex("danmaku_docs_user_media_src_uq").on(t.userId, t.mediaItemId, t.source)],
|
||||
);
|
||||
export type DanmakuDocRow = typeof danmakuDocs.$inferSelect;
|
||||
export type NewDanmakuDocRow = typeof danmakuDocs.$inferInsert;
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { sql } from "drizzle-orm";
|
||||
import { integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
|
||||
import { users } from "./users.js";
|
||||
|
||||
export const danmakuPrefs = sqliteTable(
|
||||
"danmaku_prefs",
|
||||
{
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||
/** 整数百分比 0–100,API 层换算 0–1 小数 */
|
||||
opacity: integer("opacity").notNull().default(80),
|
||||
/** 整数百分比 10–100 */
|
||||
density: integer("density").notNull().default(100),
|
||||
blockKeywords: text("block_keywords").notNull().default("[]"),
|
||||
blockTypes: text("block_types").notNull().default("[]"),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.default(sql`(unixepoch())`),
|
||||
},
|
||||
(t) => [uniqueIndex("danmaku_prefs_user_uq").on(t.userId)],
|
||||
);
|
||||
export type DanmakuPrefsRow = typeof danmakuPrefs.$inferSelect;
|
||||
export type NewDanmakuPrefsRow = typeof danmakuPrefs.$inferInsert;
|
||||
|
|
@ -13,3 +13,7 @@ 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";
|
||||
export { danmakuPrefs } from "./danmaku-prefs.js";
|
||||
export type { DanmakuPrefsRow, NewDanmakuPrefsRow } from "./danmaku-prefs.js";
|
||||
export { danmakuDocs } from "./danmaku-docs.js";
|
||||
export type { DanmakuDocRow, NewDanmakuDocRow } from "./danmaku-docs.js";
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ export const mediaItems = sqliteTable(
|
|||
/** 弹弹play 弹幕库编号(文件识别成功后落库,弹幕拉取直达) */
|
||||
dandanplayEpisodeId: integer("dandanplay_episode_id"),
|
||||
matchedHash: text("matched_hash"),
|
||||
/** auto=高置信自动 | manual=手选 | none */
|
||||
danmakuMatchSource: text("danmaku_match_source").notNull().default("none"),
|
||||
scannedAt: integer("scanned_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.default(sql`(unixepoch())`),
|
||||
|
|
|
|||
Loading…
Reference in New Issue