KatelyaTV/src/lib/d1.db.ts

477 lines
12 KiB
TypeScript

/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import { AdminConfig } from './admin.types';
import { Favorite, IStorage, PlayRecord } from './types';
// 搜索历史最大条数
const SEARCH_HISTORY_LIMIT = 20;
// D1 数据库接口
interface D1Database {
prepare(sql: string): D1PreparedStatement;
exec(sql: string): Promise<D1ExecResult>;
batch(statements: D1PreparedStatement[]): Promise<D1Result[]>;
}
interface D1PreparedStatement {
bind(...values: any[]): D1PreparedStatement;
first<T = any>(colName?: string): Promise<T | null>;
run(): Promise<D1Result>;
all<T = any>(): Promise<D1Result<T>>;
}
interface D1Result<T = any> {
results: T[];
success: boolean;
error?: string;
meta: {
changed_db: boolean;
changes: number;
last_row_id: number;
duration: number;
};
}
interface D1ExecResult {
count: number;
duration: number;
}
// 获取全局D1数据库实例
function getD1Database(): D1Database {
return (process.env as any).DB as D1Database;
}
export class D1Storage implements IStorage {
private db: D1Database | null = null;
private async getDatabase(): Promise<D1Database> {
if (!this.db) {
this.db = getD1Database();
}
return this.db;
}
// 播放记录相关
async getPlayRecord(
userName: string,
key: string
): Promise<PlayRecord | null> {
try {
const db = await this.getDatabase();
const result = await db
.prepare('SELECT * FROM play_records WHERE username = ? AND key = ?')
.bind(userName, key)
.first<any>();
if (!result) return null;
return {
title: result.title,
source_name: result.source_name,
cover: result.cover,
year: result.year,
index: result.index_episode,
total_episodes: result.total_episodes,
play_time: result.play_time,
total_time: result.total_time,
save_time: result.save_time,
search_title: result.search_title || undefined,
};
} catch (err) {
console.error('Failed to get play record:', err);
throw err;
}
}
async setPlayRecord(
userName: string,
key: string,
record: PlayRecord
): Promise<void> {
try {
const db = await this.getDatabase();
await db
.prepare(
`
INSERT OR REPLACE INTO play_records
(username, key, title, source_name, cover, year, index_episode, total_episodes, play_time, total_time, save_time, search_title)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`
)
.bind(
userName,
key,
record.title,
record.source_name,
record.cover,
record.year,
record.index,
record.total_episodes,
record.play_time,
record.total_time,
record.save_time,
record.search_title || null
)
.run();
} catch (err) {
console.error('Failed to set play record:', err);
throw err;
}
}
async getAllPlayRecords(
userName: string
): Promise<Record<string, PlayRecord>> {
try {
const db = await this.getDatabase();
const result = await db
.prepare(
'SELECT * FROM play_records WHERE username = ? ORDER BY save_time DESC'
)
.bind(userName)
.all<any>();
const records: Record<string, PlayRecord> = {};
result.results.forEach((row: any) => {
records[row.key] = {
title: row.title,
source_name: row.source_name,
cover: row.cover,
year: row.year,
index: row.index_episode,
total_episodes: row.total_episodes,
play_time: row.play_time,
total_time: row.total_time,
save_time: row.save_time,
search_title: row.search_title || undefined,
};
});
return records;
} catch (err) {
console.error('Failed to get all play records:', err);
throw err;
}
}
async deletePlayRecord(userName: string, key: string): Promise<void> {
try {
const db = await this.getDatabase();
await db
.prepare('DELETE FROM play_records WHERE username = ? AND key = ?')
.bind(userName, key)
.run();
} catch (err) {
console.error('Failed to delete play record:', err);
throw err;
}
}
// 收藏相关
async getFavorite(userName: string, key: string): Promise<Favorite | null> {
try {
const db = await this.getDatabase();
const result = await db
.prepare('SELECT * FROM favorites WHERE username = ? AND key = ?')
.bind(userName, key)
.first<any>();
if (!result) return null;
return {
title: result.title,
source_name: result.source_name,
cover: result.cover,
year: result.year,
total_episodes: result.total_episodes,
save_time: result.save_time,
search_title: result.search_title,
};
} catch (err) {
console.error('Failed to get favorite:', err);
throw err;
}
}
async setFavorite(
userName: string,
key: string,
favorite: Favorite
): Promise<void> {
try {
const db = await this.getDatabase();
await db
.prepare(
`
INSERT OR REPLACE INTO favorites
(username, key, title, source_name, cover, year, total_episodes, save_time)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`
)
.bind(
userName,
key,
favorite.title,
favorite.source_name,
favorite.cover,
favorite.year,
favorite.total_episodes,
favorite.save_time
)
.run();
} catch (err) {
console.error('Failed to set favorite:', err);
throw err;
}
}
async getAllFavorites(userName: string): Promise<Record<string, Favorite>> {
try {
const db = await this.getDatabase();
const result = await db
.prepare(
'SELECT * FROM favorites WHERE username = ? ORDER BY save_time DESC'
)
.bind(userName)
.all<any>();
const favorites: Record<string, Favorite> = {};
result.results.forEach((row: any) => {
favorites[row.key] = {
title: row.title,
source_name: row.source_name,
cover: row.cover,
year: row.year,
total_episodes: row.total_episodes,
save_time: row.save_time,
search_title: row.search_title,
};
});
return favorites;
} catch (err) {
console.error('Failed to get all favorites:', err);
throw err;
}
}
async deleteFavorite(userName: string, key: string): Promise<void> {
try {
const db = await this.getDatabase();
await db
.prepare('DELETE FROM favorites WHERE username = ? AND key = ?')
.bind(userName, key)
.run();
} catch (err) {
console.error('Failed to delete favorite:', err);
throw err;
}
}
// 用户相关
async registerUser(userName: string, password: string): Promise<void> {
try {
const db = await this.getDatabase();
await db
.prepare('INSERT INTO users (username, password) VALUES (?, ?)')
.bind(userName, password)
.run();
} catch (err) {
console.error('Failed to register user:', err);
throw err;
}
}
async verifyUser(userName: string, password: string): Promise<boolean> {
try {
const db = await this.getDatabase();
const result = await db
.prepare('SELECT password FROM users WHERE username = ?')
.bind(userName)
.first<{ password: string }>();
return result?.password === password;
} catch (err) {
console.error('Failed to verify user:', err);
throw err;
}
}
async checkUserExist(userName: string): Promise<boolean> {
try {
const db = await this.getDatabase();
const result = await db
.prepare('SELECT 1 FROM users WHERE username = ?')
.bind(userName)
.first();
return result !== null;
} catch (err) {
console.error('Failed to check user existence:', err);
throw err;
}
}
async changePassword(userName: string, newPassword: string): Promise<void> {
try {
const db = await this.getDatabase();
await db
.prepare('UPDATE users SET password = ? WHERE username = ?')
.bind(newPassword, userName)
.run();
} catch (err) {
console.error('Failed to change password:', err);
throw err;
}
}
async deleteUser(userName: string): Promise<void> {
try {
const db = await this.getDatabase();
const statements = [
db.prepare('DELETE FROM users WHERE username = ?').bind(userName),
db
.prepare('DELETE FROM play_records WHERE username = ?')
.bind(userName),
db.prepare('DELETE FROM favorites WHERE username = ?').bind(userName),
db
.prepare('DELETE FROM search_history WHERE username = ?')
.bind(userName),
];
await db.batch(statements);
} catch (err) {
console.error('Failed to delete user:', err);
throw err;
}
}
// 搜索历史相关
async getSearchHistory(userName: string): Promise<string[]> {
try {
const db = await this.getDatabase();
const result = await db
.prepare(
'SELECT keyword FROM search_history WHERE username = ? ORDER BY created_at DESC LIMIT ?'
)
.bind(userName, SEARCH_HISTORY_LIMIT)
.all<{ keyword: string }>();
return result.results.map((row) => row.keyword);
} catch (err) {
console.error('Failed to get search history:', err);
throw err;
}
}
async addSearchHistory(userName: string, keyword: string): Promise<void> {
try {
const db = await this.getDatabase();
// 先删除可能存在的重复记录
await db
.prepare(
'DELETE FROM search_history WHERE username = ? AND keyword = ?'
)
.bind(userName, keyword)
.run();
// 添加新记录
await db
.prepare('INSERT INTO search_history (username, keyword) VALUES (?, ?)')
.bind(userName, keyword)
.run();
// 保持历史记录条数限制
await db
.prepare(
`
DELETE FROM search_history
WHERE username = ? AND id NOT IN (
SELECT id FROM search_history
WHERE username = ?
ORDER BY created_at DESC
LIMIT ?
)
`
)
.bind(userName, userName, SEARCH_HISTORY_LIMIT)
.run();
} catch (err) {
console.error('Failed to add search history:', err);
throw err;
}
}
async deleteSearchHistory(userName: string, keyword?: string): Promise<void> {
try {
const db = await this.getDatabase();
if (keyword) {
await db
.prepare(
'DELETE FROM search_history WHERE username = ? AND keyword = ?'
)
.bind(userName, keyword)
.run();
} else {
await db
.prepare('DELETE FROM search_history WHERE username = ?')
.bind(userName)
.run();
}
} catch (err) {
console.error('Failed to delete search history:', err);
throw err;
}
}
// 用户列表
async getAllUsers(): Promise<string[]> {
try {
const db = await this.getDatabase();
const result = await db
.prepare('SELECT username FROM users ORDER BY created_at ASC')
.all<{ username: string }>();
return result.results.map((row) => row.username);
} catch (err) {
console.error('Failed to get all users:', err);
throw err;
}
}
// 管理员配置相关
async getAdminConfig(): Promise<AdminConfig | null> {
try {
const db = await this.getDatabase();
const result = await db
.prepare('SELECT config FROM admin_config WHERE id = 1')
.first<{ config: string }>();
if (!result) return null;
return JSON.parse(result.config) as AdminConfig;
} catch (err) {
console.error('Failed to get admin config:', err);
throw err;
}
}
async setAdminConfig(config: AdminConfig): Promise<void> {
try {
const db = await this.getDatabase();
await db
.prepare(
'INSERT OR REPLACE INTO admin_config (id, config) VALUES (1, ?)'
)
.bind(JSON.stringify(config))
.run();
} catch (err) {
console.error('Failed to set admin config:', err);
throw err;
}
}
}