Update
This commit is contained in:
@@ -0,0 +1,476 @@
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user