d1/sqlite支持
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -58,3 +58,8 @@ public/workbox-*.js.map
|
||||
|
||||
/.claude
|
||||
/.vscode
|
||||
# SQLite 开发数据库
|
||||
.data/
|
||||
*.db
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
|
||||
170
migrations/001_initial_schema.sql
Normal file
170
migrations/001_initial_schema.sql
Normal file
@@ -0,0 +1,170 @@
|
||||
-- ============================================
|
||||
-- MoonTV Plus - Cloudflare D1 数据库结构
|
||||
-- 版本: 1.0.0
|
||||
-- 创建时间: 2026-01-30
|
||||
-- ============================================
|
||||
|
||||
-- 1. 用户表
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
username TEXT PRIMARY KEY,
|
||||
password_hash TEXT NOT NULL,
|
||||
role TEXT NOT NULL CHECK(role IN ('owner', 'admin', 'user')),
|
||||
banned INTEGER DEFAULT 0,
|
||||
tags TEXT, -- JSON array: ["vip", "premium"]
|
||||
oidc_sub TEXT UNIQUE,
|
||||
enabled_apis TEXT, -- JSON array: ["api1", "api2"]
|
||||
created_at INTEGER NOT NULL,
|
||||
playrecord_migrated INTEGER DEFAULT 0,
|
||||
favorite_migrated INTEGER DEFAULT 0,
|
||||
skip_migrated INTEGER DEFAULT 0,
|
||||
last_movie_request_time INTEGER,
|
||||
email TEXT,
|
||||
email_notifications INTEGER DEFAULT 1
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_oidc_sub ON users(oidc_sub) WHERE oidc_sub IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_users_created_at ON users(created_at);
|
||||
|
||||
-- 2. 播放记录表
|
||||
CREATE TABLE IF NOT EXISTS play_records (
|
||||
username TEXT NOT NULL,
|
||||
key TEXT NOT NULL, -- format: "source+id" (e.g., "tmdb+12345")
|
||||
title TEXT NOT NULL,
|
||||
source_name TEXT NOT NULL,
|
||||
cover TEXT,
|
||||
year TEXT,
|
||||
episode_index INTEGER NOT NULL,
|
||||
total_episodes INTEGER NOT NULL,
|
||||
play_time INTEGER NOT NULL, -- 播放进度(秒)
|
||||
total_time INTEGER NOT NULL, -- 总时长(秒)
|
||||
save_time INTEGER NOT NULL, -- 保存时间戳
|
||||
search_title TEXT,
|
||||
PRIMARY KEY (username, key),
|
||||
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_play_records_save_time ON play_records(username, save_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_play_records_source ON play_records(username, source_name);
|
||||
|
||||
-- 3. 收藏表
|
||||
CREATE TABLE IF NOT EXISTS favorites (
|
||||
username TEXT NOT NULL,
|
||||
key TEXT NOT NULL, -- format: "source+id"
|
||||
source_name TEXT NOT NULL,
|
||||
total_episodes INTEGER NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
year TEXT,
|
||||
cover TEXT,
|
||||
save_time INTEGER NOT NULL,
|
||||
search_title TEXT,
|
||||
origin TEXT CHECK(origin IN ('vod', 'live')),
|
||||
is_completed INTEGER DEFAULT 0,
|
||||
vod_remarks TEXT,
|
||||
PRIMARY KEY (username, key),
|
||||
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_favorites_save_time ON favorites(username, save_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_favorites_source ON favorites(username, source_name);
|
||||
|
||||
-- 4. 搜索历史表
|
||||
CREATE TABLE IF NOT EXISTS search_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL,
|
||||
keyword TEXT NOT NULL,
|
||||
timestamp INTEGER NOT NULL,
|
||||
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE,
|
||||
UNIQUE(username, keyword)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_search_history_user_time ON search_history(username, timestamp DESC);
|
||||
|
||||
-- 5. 跳过配置表(片头片尾)
|
||||
CREATE TABLE IF NOT EXISTS skip_configs (
|
||||
username TEXT NOT NULL,
|
||||
key TEXT NOT NULL, -- format: "source+id"
|
||||
enable INTEGER NOT NULL DEFAULT 1,
|
||||
intro_time INTEGER NOT NULL DEFAULT 0, -- 片头时长(秒)
|
||||
outro_time INTEGER NOT NULL DEFAULT 0, -- 片尾时长(秒)
|
||||
PRIMARY KEY (username, key),
|
||||
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- 6. 弹幕过滤配置表
|
||||
CREATE TABLE IF NOT EXISTS danmaku_filter_configs (
|
||||
username TEXT PRIMARY KEY,
|
||||
rules TEXT NOT NULL, -- JSON array: [{"keyword": "xxx", "type": "normal", "enabled": true}]
|
||||
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- 7. 通知表
|
||||
CREATE TABLE IF NOT EXISTS notifications (
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT NOT NULL,
|
||||
type TEXT NOT NULL CHECK(type IN ('favorite_update', 'system', 'announcement', 'movie_request', 'request_fulfilled')),
|
||||
title TEXT NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
timestamp INTEGER NOT NULL,
|
||||
read INTEGER DEFAULT 0,
|
||||
metadata TEXT, -- JSON object
|
||||
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_notifications_user_time ON notifications(username, timestamp DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_notifications_user_read ON notifications(username, read, timestamp DESC);
|
||||
|
||||
-- 8. 求片请求表
|
||||
CREATE TABLE IF NOT EXISTS movie_requests (
|
||||
id TEXT PRIMARY KEY,
|
||||
tmdb_id INTEGER,
|
||||
title TEXT NOT NULL,
|
||||
year TEXT,
|
||||
media_type TEXT NOT NULL CHECK(media_type IN ('movie', 'tv')),
|
||||
season INTEGER,
|
||||
poster TEXT,
|
||||
overview TEXT,
|
||||
requested_by TEXT NOT NULL, -- JSON array: ["user1", "user2"]
|
||||
request_count INTEGER NOT NULL DEFAULT 1,
|
||||
status TEXT NOT NULL CHECK(status IN ('pending', 'fulfilled')) DEFAULT 'pending',
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
fulfilled_at INTEGER,
|
||||
fulfilled_source TEXT,
|
||||
fulfilled_id TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_movie_requests_status ON movie_requests(status, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_movie_requests_tmdb ON movie_requests(tmdb_id) WHERE tmdb_id IS NOT NULL;
|
||||
|
||||
-- 9. 用户求片关联表(用于快速查询用户的求片记录)
|
||||
CREATE TABLE IF NOT EXISTS user_movie_requests (
|
||||
username TEXT NOT NULL,
|
||||
request_id TEXT NOT NULL,
|
||||
PRIMARY KEY (username, request_id),
|
||||
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE,
|
||||
FOREIGN KEY (request_id) REFERENCES movie_requests(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_movie_requests_user ON user_movie_requests(username);
|
||||
|
||||
-- 10. 全局配置表(键值对存储)
|
||||
CREATE TABLE IF NOT EXISTS global_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- 11. 管理员配置表(单例)
|
||||
CREATE TABLE IF NOT EXISTS admin_config (
|
||||
id INTEGER PRIMARY KEY CHECK(id = 1), -- 确保只有一条记录
|
||||
config TEXT NOT NULL, -- JSON object
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- 12. 收藏更新检查时间表
|
||||
CREATE TABLE IF NOT EXISTS favorite_check_times (
|
||||
username TEXT PRIMARY KEY,
|
||||
last_check_time INTEGER NOT NULL,
|
||||
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE
|
||||
);
|
||||
@@ -35,7 +35,7 @@ const nextConfig = {
|
||||
],
|
||||
},
|
||||
|
||||
webpack(config) {
|
||||
webpack(config, { isServer }) {
|
||||
// Grab the existing rule that handles SVG imports
|
||||
const fileLoaderRule = config.module.rules.find((rule) =>
|
||||
rule.test?.test?.('.svg')
|
||||
@@ -71,6 +71,21 @@ const nextConfig = {
|
||||
crypto: false,
|
||||
};
|
||||
|
||||
// Exclude better-sqlite3 and D1 modules from client-side bundle
|
||||
if (!isServer) {
|
||||
config.externals = config.externals || [];
|
||||
config.externals.push({
|
||||
'better-sqlite3': 'commonjs better-sqlite3',
|
||||
});
|
||||
|
||||
config.resolve.alias = {
|
||||
...config.resolve.alias,
|
||||
'better-sqlite3': false,
|
||||
'@/lib/d1.db': false,
|
||||
'@/lib/d1-adapter': false,
|
||||
};
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
"gen:manifest": "node scripts/generate-manifest.js",
|
||||
"postbuild": "echo 'Build completed - sitemap generation disabled'",
|
||||
"prepare": "husky install",
|
||||
"watch-room:server": "node server/watch-room-standalone-server.js --port 3001 --auth YOUR_SECRET_KEY"
|
||||
"watch-room:server": "node server/watch-room-standalone-server.js --port 3001 --auth YOUR_SECRET_KEY",
|
||||
"init:sqlite": "node scripts/init-sqlite.js",
|
||||
"db:reset": "rm -f .data/moontv.db .data/moontv.db-shm .data/moontv.db-wal && node scripts/init-sqlite.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
@@ -36,6 +38,7 @@
|
||||
"anime4k-webgpu": "^1.0.0",
|
||||
"artplayer": "^5.3.0",
|
||||
"artplayer-plugin-danmuku": "^5.2.0",
|
||||
"better-sqlite3": "^12.6.2",
|
||||
"bs58": "^6.0.0",
|
||||
"cheerio": "^1.1.2",
|
||||
"clsx": "^2.0.0",
|
||||
@@ -64,6 +67,7 @@
|
||||
"react-window": "^2.2.3",
|
||||
"redis": "^4.6.7",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"server-only": "^0.0.1",
|
||||
"socket.io": "^4.8.1",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"swiper": "^11.2.8",
|
||||
@@ -81,6 +85,7 @@
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^15.0.7",
|
||||
"@types/better-sqlite3": "^7.6.11",
|
||||
"@types/bs58": "^5.0.0",
|
||||
"@types/he": "^1.2.3",
|
||||
"@types/node": "24.0.3",
|
||||
|
||||
196
pnpm-lock.yaml
generated
196
pnpm-lock.yaml
generated
@@ -47,6 +47,9 @@ importers:
|
||||
artplayer-plugin-danmuku:
|
||||
specifier: ^5.2.0
|
||||
version: 5.2.0
|
||||
better-sqlite3:
|
||||
specifier: ^12.6.2
|
||||
version: 12.6.2
|
||||
bs58:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
@@ -131,6 +134,9 @@ importers:
|
||||
remark-gfm:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
server-only:
|
||||
specifier: ^0.0.1
|
||||
version: 0.0.1
|
||||
socket.io:
|
||||
specifier: ^4.8.1
|
||||
version: 4.8.3
|
||||
@@ -177,6 +183,9 @@ importers:
|
||||
'@testing-library/react':
|
||||
specifier: ^15.0.7
|
||||
version: 15.0.7(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@types/better-sqlite3':
|
||||
specifier: ^7.6.11
|
||||
version: 7.6.13
|
||||
'@types/bs58':
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0
|
||||
@@ -3146,6 +3155,9 @@ packages:
|
||||
'@types/babel__traverse@7.28.0':
|
||||
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
|
||||
|
||||
'@types/better-sqlite3@7.6.13':
|
||||
resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==}
|
||||
|
||||
'@types/bs58@5.0.0':
|
||||
resolution: {integrity: sha512-cAw/jKBzo98m6Xz1X5ETqymWfIMbXbu6nK15W4LQYjeHJkVqSmM5PO8Bd9KVHQJ/F4rHcSso9LcjtgCW6TGu2w==}
|
||||
deprecated: This is a stub types definition. bs58 provides its own type definitions, so you do not need this installed.
|
||||
@@ -3986,6 +3998,9 @@ packages:
|
||||
base-x@5.0.1:
|
||||
resolution: {integrity: sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==}
|
||||
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
base64id@2.0.0:
|
||||
resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
|
||||
engines: {node: ^4.5.0 || >= 5.9}
|
||||
@@ -3994,6 +4009,10 @@ packages:
|
||||
resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==}
|
||||
hasBin: true
|
||||
|
||||
better-sqlite3@12.6.2:
|
||||
resolution: {integrity: sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==}
|
||||
engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x}
|
||||
|
||||
big.js@5.2.2:
|
||||
resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==}
|
||||
|
||||
@@ -4004,6 +4023,9 @@ packages:
|
||||
bindings@1.5.0:
|
||||
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
|
||||
|
||||
bl@4.1.0:
|
||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||
|
||||
blake3-wasm@2.1.5:
|
||||
resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==}
|
||||
|
||||
@@ -4047,6 +4069,9 @@ packages:
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
buffer@5.7.1:
|
||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||
|
||||
builtin-modules@3.3.0:
|
||||
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -4150,6 +4175,9 @@ packages:
|
||||
resolution: {integrity: sha512-mxIojEAQcuEvT/lyXq+jf/3cO/KoA6z4CeNDGGevTybECPOMFCnQy3OPahluUkbqgPNGw5Bi78UC7Po6Lhy+NA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
|
||||
chownr@1.1.4:
|
||||
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
||||
|
||||
chownr@2.0.0:
|
||||
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -4499,9 +4527,17 @@ packages:
|
||||
decode-named-character-reference@1.2.0:
|
||||
resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==}
|
||||
|
||||
decompress-response@6.0.0:
|
||||
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
dedent@0.7.0:
|
||||
resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==}
|
||||
|
||||
deep-extend@0.6.0:
|
||||
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
||||
deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
|
||||
@@ -4979,6 +5015,10 @@ packages:
|
||||
resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
expand-template@2.0.3:
|
||||
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
expect@27.5.1:
|
||||
resolution: {integrity: sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==}
|
||||
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
||||
@@ -5124,6 +5164,9 @@ packages:
|
||||
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
fs-constants@1.0.0:
|
||||
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
|
||||
|
||||
fs-extra@10.1.0:
|
||||
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -5217,6 +5260,9 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
github-from-package@0.0.0:
|
||||
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
|
||||
|
||||
glob-parent@5.1.2:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -5402,6 +5448,9 @@ packages:
|
||||
idb@7.1.1:
|
||||
resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
|
||||
|
||||
ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
|
||||
ignore@5.3.2:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
@@ -6397,6 +6446,10 @@ packages:
|
||||
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
mimic-response@3.1.0:
|
||||
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
min-document@2.19.2:
|
||||
resolution: {integrity: sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==}
|
||||
|
||||
@@ -6463,6 +6516,9 @@ packages:
|
||||
resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
mkdirp-classic@0.5.3:
|
||||
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
|
||||
|
||||
mkdirp@1.0.4:
|
||||
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -6520,6 +6576,9 @@ packages:
|
||||
engines: {node: ^18 || >=20}
|
||||
hasBin: true
|
||||
|
||||
napi-build-utils@2.0.0:
|
||||
resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
|
||||
|
||||
napi-postinstall@0.3.4:
|
||||
resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
|
||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||
@@ -6580,6 +6639,10 @@ packages:
|
||||
no-case@3.0.4:
|
||||
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
|
||||
|
||||
node-abi@3.87.0:
|
||||
resolution: {integrity: sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
node-domexception@1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
@@ -6961,6 +7024,11 @@ packages:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
prebuild-install@7.1.3:
|
||||
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
prelude-ls@1.1.2:
|
||||
resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -7110,6 +7178,10 @@ packages:
|
||||
resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
rc@1.2.8:
|
||||
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||
hasBin: true
|
||||
|
||||
react-dom@18.3.1:
|
||||
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
|
||||
peerDependencies:
|
||||
@@ -7384,6 +7456,9 @@ packages:
|
||||
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
server-only@0.0.1:
|
||||
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
|
||||
|
||||
set-function-length@1.2.2:
|
||||
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -7441,6 +7516,12 @@ packages:
|
||||
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
simple-concat@1.0.1:
|
||||
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
|
||||
|
||||
simple-get@4.0.1:
|
||||
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
|
||||
|
||||
sisteransi@1.0.5:
|
||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||
|
||||
@@ -7654,6 +7735,10 @@ packages:
|
||||
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strip-json-comments@2.0.1:
|
||||
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
strip-json-comments@3.1.1:
|
||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -7752,6 +7837,13 @@ packages:
|
||||
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tar-fs@2.1.4:
|
||||
resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==}
|
||||
|
||||
tar-stream@2.2.0:
|
||||
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tar@6.2.1:
|
||||
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -7944,6 +8036,9 @@ packages:
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
tunnel-agent@0.6.0:
|
||||
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
|
||||
|
||||
type-check@0.3.2:
|
||||
resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -12807,6 +12902,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/types': 7.28.5
|
||||
|
||||
'@types/better-sqlite3@7.6.13':
|
||||
dependencies:
|
||||
'@types/node': 24.0.3
|
||||
|
||||
'@types/bs58@5.0.0':
|
||||
dependencies:
|
||||
bs58: 6.0.0
|
||||
@@ -13888,10 +13987,17 @@ snapshots:
|
||||
|
||||
base-x@5.0.1: {}
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
base64id@2.0.0: {}
|
||||
|
||||
baseline-browser-mapping@2.9.11: {}
|
||||
|
||||
better-sqlite3@12.6.2:
|
||||
dependencies:
|
||||
bindings: 1.5.0
|
||||
prebuild-install: 7.1.3
|
||||
|
||||
big.js@5.2.2: {}
|
||||
|
||||
binary-extensions@2.3.0: {}
|
||||
@@ -13900,6 +14006,12 @@ snapshots:
|
||||
dependencies:
|
||||
file-uri-to-path: 1.0.0
|
||||
|
||||
bl@4.1.0:
|
||||
dependencies:
|
||||
buffer: 5.7.1
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
|
||||
blake3-wasm@2.1.5: {}
|
||||
|
||||
body-parser@2.2.2:
|
||||
@@ -13955,6 +14067,11 @@ snapshots:
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
buffer@5.7.1:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
|
||||
builtin-modules@3.3.0: {}
|
||||
|
||||
busboy@1.6.0:
|
||||
@@ -14065,6 +14182,8 @@ snapshots:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
|
||||
chownr@1.1.4: {}
|
||||
|
||||
chownr@2.0.0: {}
|
||||
|
||||
chownr@3.0.0: {}
|
||||
@@ -14386,8 +14505,14 @@ snapshots:
|
||||
dependencies:
|
||||
character-entities: 2.0.2
|
||||
|
||||
decompress-response@6.0.0:
|
||||
dependencies:
|
||||
mimic-response: 3.1.0
|
||||
|
||||
dedent@0.7.0: {}
|
||||
|
||||
deep-extend@0.6.0: {}
|
||||
|
||||
deep-is@0.1.4: {}
|
||||
|
||||
deepmerge@4.3.1: {}
|
||||
@@ -15059,6 +15184,8 @@ snapshots:
|
||||
|
||||
exit@0.1.2: {}
|
||||
|
||||
expand-template@2.0.3: {}
|
||||
|
||||
expect@27.5.1:
|
||||
dependencies:
|
||||
'@jest/types': 27.5.1
|
||||
@@ -15251,6 +15378,8 @@ snapshots:
|
||||
|
||||
fresh@2.0.0: {}
|
||||
|
||||
fs-constants@1.0.0: {}
|
||||
|
||||
fs-extra@10.1.0:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
@@ -15350,6 +15479,8 @@ snapshots:
|
||||
split2: 3.2.2
|
||||
through2: 4.0.2
|
||||
|
||||
github-from-package@0.0.0: {}
|
||||
|
||||
glob-parent@5.1.2:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
@@ -15579,6 +15710,8 @@ snapshots:
|
||||
|
||||
idb@7.1.1: {}
|
||||
|
||||
ieee754@1.2.1: {}
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
import-fresh@3.3.1:
|
||||
@@ -17123,6 +17256,8 @@ snapshots:
|
||||
|
||||
mimic-function@5.0.1: {}
|
||||
|
||||
mimic-response@3.1.0: {}
|
||||
|
||||
min-document@2.19.2:
|
||||
dependencies:
|
||||
dom-walk: 0.1.2
|
||||
@@ -17191,6 +17326,8 @@ snapshots:
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
|
||||
mkdirp-classic@0.5.3: {}
|
||||
|
||||
mkdirp@1.0.4: {}
|
||||
|
||||
mkdirp@3.0.1: {}
|
||||
@@ -17240,6 +17377,8 @@ snapshots:
|
||||
|
||||
nanoid@5.1.6: {}
|
||||
|
||||
napi-build-utils@2.0.0: {}
|
||||
|
||||
napi-postinstall@0.3.4: {}
|
||||
|
||||
natural-compare-lite@1.4.0: {}
|
||||
@@ -17310,6 +17449,10 @@ snapshots:
|
||||
lower-case: 2.0.2
|
||||
tslib: 2.8.1
|
||||
|
||||
node-abi@3.87.0:
|
||||
dependencies:
|
||||
semver: 7.7.3
|
||||
|
||||
node-domexception@1.0.0: {}
|
||||
|
||||
node-fetch@2.6.7:
|
||||
@@ -17649,6 +17792,21 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
prebuild-install@7.1.3:
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
expand-template: 2.0.3
|
||||
github-from-package: 0.0.0
|
||||
minimist: 1.2.8
|
||||
mkdirp-classic: 0.5.3
|
||||
napi-build-utils: 2.0.0
|
||||
node-abi: 3.87.0
|
||||
pump: 3.0.3
|
||||
rc: 1.2.8
|
||||
simple-get: 4.0.1
|
||||
tar-fs: 2.1.4
|
||||
tunnel-agent: 0.6.0
|
||||
|
||||
prelude-ls@1.1.2: {}
|
||||
|
||||
prelude-ls@1.2.1: {}
|
||||
@@ -17742,6 +17900,13 @@ snapshots:
|
||||
iconv-lite: 0.7.2
|
||||
unpipe: 1.0.0
|
||||
|
||||
rc@1.2.8:
|
||||
dependencies:
|
||||
deep-extend: 0.6.0
|
||||
ini: 1.3.8
|
||||
minimist: 1.2.8
|
||||
strip-json-comments: 2.0.1
|
||||
|
||||
react-dom@18.3.1(react@18.3.1):
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
@@ -18096,6 +18261,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
server-only@0.0.1: {}
|
||||
|
||||
set-function-length@1.2.2:
|
||||
dependencies:
|
||||
define-data-property: 1.1.4
|
||||
@@ -18193,6 +18360,14 @@ snapshots:
|
||||
|
||||
signal-exit@4.1.0: {}
|
||||
|
||||
simple-concat@1.0.1: {}
|
||||
|
||||
simple-get@4.0.1:
|
||||
dependencies:
|
||||
decompress-response: 6.0.0
|
||||
once: 1.4.0
|
||||
simple-concat: 1.0.1
|
||||
|
||||
sisteransi@1.0.5: {}
|
||||
|
||||
slash@3.0.0: {}
|
||||
@@ -18454,6 +18629,8 @@ snapshots:
|
||||
dependencies:
|
||||
min-indent: 1.0.1
|
||||
|
||||
strip-json-comments@2.0.1: {}
|
||||
|
||||
strip-json-comments@3.1.1: {}
|
||||
|
||||
strnum@1.1.2: {}
|
||||
@@ -18562,6 +18739,21 @@ snapshots:
|
||||
|
||||
tapable@2.3.0: {}
|
||||
|
||||
tar-fs@2.1.4:
|
||||
dependencies:
|
||||
chownr: 1.1.4
|
||||
mkdirp-classic: 0.5.3
|
||||
pump: 3.0.3
|
||||
tar-stream: 2.2.0
|
||||
|
||||
tar-stream@2.2.0:
|
||||
dependencies:
|
||||
bl: 4.1.0
|
||||
end-of-stream: 1.4.5
|
||||
fs-constants: 1.0.0
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
|
||||
tar@6.2.1:
|
||||
dependencies:
|
||||
chownr: 2.0.0
|
||||
@@ -18760,6 +18952,10 @@ snapshots:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
tunnel-agent@0.6.0:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
type-check@0.3.2:
|
||||
dependencies:
|
||||
prelude-ls: 1.1.2
|
||||
|
||||
62
scripts/init-sqlite.js
Normal file
62
scripts/init-sqlite.js
Normal file
@@ -0,0 +1,62 @@
|
||||
const Database = require('better-sqlite3');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
// SHA-256 加密密码(与 Redis 保持一致)
|
||||
function hashPassword(password) {
|
||||
return crypto.createHash('sha256').update(password).digest('hex');
|
||||
}
|
||||
|
||||
// 确保 .data 目录存在
|
||||
const dataDir = path.join(__dirname, '../.data');
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 创建数据库
|
||||
const dbPath = path.join(dataDir, 'moontv.db');
|
||||
const db = new Database(dbPath);
|
||||
|
||||
console.log('📦 Initializing SQLite database for development...');
|
||||
console.log('📍 Database location:', dbPath);
|
||||
|
||||
// 读取迁移脚本
|
||||
const migrationPath = path.join(__dirname, '../migrations/001_initial_schema.sql');
|
||||
if (!fs.existsSync(migrationPath)) {
|
||||
console.error('❌ Migration file not found:', migrationPath);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const sql = fs.readFileSync(migrationPath, 'utf8');
|
||||
|
||||
// 执行迁移
|
||||
try {
|
||||
db.exec(sql);
|
||||
console.log('✅ Database schema created successfully!');
|
||||
|
||||
// 创建默认管理员用户(可选)
|
||||
const username = process.env.USERNAME || 'admin';
|
||||
const password = process.env.PASSWORD || '123456789';
|
||||
const passwordHash = hashPassword(password);
|
||||
|
||||
const stmt = db.prepare(`
|
||||
INSERT OR IGNORE INTO users (username, password_hash, role, created_at, playrecord_migrated, favorite_migrated, skip_migrated)
|
||||
VALUES (?, ?, 'owner', ?, 1, 1, 1)
|
||||
`);
|
||||
|
||||
stmt.run(username, passwordHash, Date.now());
|
||||
console.log(`✅ Default admin user created: ${username}`);
|
||||
} catch (err) {
|
||||
console.error('❌ Migration failed:', err);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log('🎉 SQLite database initialized successfully!');
|
||||
console.log('');
|
||||
console.log('Next steps:');
|
||||
console.log('1. Set NEXT_PUBLIC_STORAGE_TYPE=d1 in .env');
|
||||
console.log('2. Run: npm run dev');
|
||||
170
src/lib/d1-adapter.ts
Normal file
170
src/lib/d1-adapter.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
/**
|
||||
* 统一的数据库适配器接口
|
||||
* 兼容 Cloudflare D1 和 better-sqlite3
|
||||
*
|
||||
* 注意:此模块仅在服务端使用,通过 webpack 配置排除客户端打包
|
||||
*/
|
||||
|
||||
// D1 PreparedStatement 接口
|
||||
export interface D1PreparedStatement {
|
||||
bind(...values: any[]): D1PreparedStatement;
|
||||
first<T = any>(colName?: string): Promise<T | null>;
|
||||
run<T = any>(): Promise<D1Result<T>>;
|
||||
all<T = any>(): Promise<D1Result<T>>;
|
||||
}
|
||||
|
||||
export interface D1Result<T = any> {
|
||||
results?: T[];
|
||||
success: boolean;
|
||||
meta?: any;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// 统一的数据库接口
|
||||
export interface DatabaseAdapter {
|
||||
prepare(query: string): D1PreparedStatement;
|
||||
batch?(statements: D1PreparedStatement[]): Promise<D1Result[]>;
|
||||
exec?(query: string): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cloudflare D1 适配器(生产环境)
|
||||
*/
|
||||
export class CloudflareD1Adapter implements DatabaseAdapter {
|
||||
constructor(private db: D1Database) {}
|
||||
|
||||
prepare(query: string): D1PreparedStatement {
|
||||
return this.db.prepare(query);
|
||||
}
|
||||
|
||||
async batch(statements: D1PreparedStatement[]): Promise<D1Result[]> {
|
||||
return this.db.batch(statements as any);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLite 适配器(开发环境)
|
||||
* 包装 better-sqlite3 以兼容 D1 API
|
||||
*/
|
||||
export class SQLiteAdapter implements DatabaseAdapter {
|
||||
private db: any; // better-sqlite3 Database
|
||||
|
||||
constructor(db: any) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
prepare(query: string): D1PreparedStatement {
|
||||
const stmt = this.db.prepare(query);
|
||||
return new SQLitePreparedStatement(stmt);
|
||||
}
|
||||
|
||||
batch(statements: D1PreparedStatement[]): Promise<D1Result[]> {
|
||||
// SQLite 使用事务模拟 batch
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const results: D1Result[] = [];
|
||||
const transaction = this.db.transaction(() => {
|
||||
for (const stmt of statements) {
|
||||
const result = (stmt as any).runSync();
|
||||
results.push(result);
|
||||
}
|
||||
});
|
||||
transaction();
|
||||
resolve(results);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
exec(query: string): void {
|
||||
this.db.exec(query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLite PreparedStatement 包装器
|
||||
* 将 better-sqlite3 API 转换为 D1 兼容 API
|
||||
*/
|
||||
class SQLitePreparedStatement implements D1PreparedStatement {
|
||||
private stmt: any;
|
||||
private params: any[] = [];
|
||||
|
||||
constructor(stmt: any) {
|
||||
this.stmt = stmt;
|
||||
}
|
||||
|
||||
bind(...values: any[]): D1PreparedStatement {
|
||||
this.params = values;
|
||||
return this;
|
||||
}
|
||||
|
||||
async first<T = any>(colName?: string): Promise<T | null> {
|
||||
try {
|
||||
const result = this.stmt.get(...this.params);
|
||||
if (!result) return null;
|
||||
if (colName) return result[colName] ?? null;
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error('SQLite first() error:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async run<T = any>(): Promise<D1Result<T>> {
|
||||
try {
|
||||
const info = this.stmt.run(...this.params);
|
||||
return {
|
||||
success: true,
|
||||
meta: {
|
||||
changes: info.changes,
|
||||
last_row_id: info.lastInsertRowid,
|
||||
},
|
||||
};
|
||||
} catch (err: any) {
|
||||
console.error('SQLite run() error:', err);
|
||||
return {
|
||||
success: false,
|
||||
error: err.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async all<T = any>(): Promise<D1Result<T>> {
|
||||
try {
|
||||
const results = this.stmt.all(...this.params);
|
||||
return {
|
||||
success: true,
|
||||
results: results || [],
|
||||
};
|
||||
} catch (err: any) {
|
||||
console.error('SQLite all() error:', err);
|
||||
return {
|
||||
success: false,
|
||||
error: err.message,
|
||||
results: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 同步版本(用于 batch)
|
||||
runSync(): D1Result {
|
||||
try {
|
||||
const info = this.stmt.run(...this.params);
|
||||
return {
|
||||
success: true,
|
||||
meta: {
|
||||
changes: info.changes,
|
||||
last_row_id: info.lastInsertRowid,
|
||||
},
|
||||
};
|
||||
} catch (err: any) {
|
||||
return {
|
||||
success: false,
|
||||
error: err.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
1414
src/lib/d1.db.ts
Normal file
1414
src/lib/d1.db.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,13 +6,14 @@ import { RedisStorage } from './redis.db';
|
||||
import { DanmakuFilterConfig,Favorite, IStorage, PlayRecord, SkipConfig } from './types';
|
||||
import { UpstashRedisStorage } from './upstash.db';
|
||||
|
||||
// storage type 常量: 'localstorage' | 'redis' | 'upstash',默认 'localstorage'
|
||||
// storage type 常量: 'localstorage' | 'redis' | 'upstash' | 'kvrocks' | 'd1',默认 'localstorage'
|
||||
const STORAGE_TYPE =
|
||||
(process.env.NEXT_PUBLIC_STORAGE_TYPE as
|
||||
| 'localstorage'
|
||||
| 'redis'
|
||||
| 'upstash'
|
||||
| 'kvrocks'
|
||||
| 'd1'
|
||||
| undefined) || 'localstorage';
|
||||
|
||||
// 创建存储实例
|
||||
@@ -24,12 +25,67 @@ function createStorage(): IStorage {
|
||||
return new UpstashRedisStorage();
|
||||
case 'kvrocks':
|
||||
return new KvrocksStorage();
|
||||
case 'd1':
|
||||
// D1Storage 只能在服务端使用,客户端会报错
|
||||
if (typeof window !== 'undefined') {
|
||||
throw new Error('D1Storage can only be used on the server side');
|
||||
}
|
||||
const adapter = getD1Adapter();
|
||||
// 动态导入 D1Storage 以避免客户端打包
|
||||
const { D1Storage } = require('./d1.db');
|
||||
return new D1Storage(adapter);
|
||||
case 'localstorage':
|
||||
default:
|
||||
return null as unknown as IStorage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 D1 适配器
|
||||
* 开发环境:使用 better-sqlite3
|
||||
* 生产环境:使用 Cloudflare D1
|
||||
*/
|
||||
function getD1Adapter(): any {
|
||||
// 动态导入适配器以避免客户端打包
|
||||
const { CloudflareD1Adapter, SQLiteAdapter } = require('./d1-adapter');
|
||||
|
||||
// 生产环境:Cloudflare Workers/Pages
|
||||
if (typeof process !== 'undefined' && (process as any).env?.DB) {
|
||||
console.log('Using Cloudflare D1 database');
|
||||
return new CloudflareD1Adapter((process as any).env.DB);
|
||||
}
|
||||
|
||||
// 开发环境:better-sqlite3
|
||||
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
|
||||
try {
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const dbPath = path.join(process.cwd(), '.data', 'moontv.db');
|
||||
|
||||
// 检查数据库文件是否存在
|
||||
if (!fs.existsSync(dbPath)) {
|
||||
console.error('❌ SQLite database not found. Please run: npm run init:sqlite');
|
||||
throw new Error('SQLite database not initialized');
|
||||
}
|
||||
|
||||
const db = new Database(dbPath);
|
||||
db.pragma('journal_mode = WAL'); // 启用 WAL 模式提升性能
|
||||
|
||||
console.log('Using SQLite database (development mode)');
|
||||
console.log('Database location:', dbPath);
|
||||
|
||||
return new SQLiteAdapter(db);
|
||||
} catch (err) {
|
||||
console.error('Failed to initialize SQLite:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('D1 database not available. Set NEXT_PUBLIC_STORAGE_TYPE to another option or configure D1.');
|
||||
}
|
||||
|
||||
// 单例存储实例
|
||||
let storageInstance: IStorage | null = null;
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { TOKEN_CONFIG } from './token-config';
|
||||
|
||||
// Re-export TOKEN_CONFIG for backward compatibility
|
||||
export { TOKEN_CONFIG };
|
||||
|
||||
// Lazy import to avoid Edge Runtime issues in middleware
|
||||
let getStorage: (() => any) | null = null;
|
||||
|
||||
@@ -11,13 +16,6 @@ async function loadStorage() {
|
||||
return getStorage();
|
||||
}
|
||||
|
||||
// Token 配置
|
||||
export const TOKEN_CONFIG = {
|
||||
ACCESS_TOKEN_AGE: 4 * 60 * 60 * 1000, // 4 小时
|
||||
REFRESH_TOKEN_AGE: 60 * 24 * 60 * 60 * 1000, // 60 天
|
||||
RENEWAL_THRESHOLD: 10 * 60 * 1000, // 剩余 10 分钟时自动续期
|
||||
};
|
||||
|
||||
interface TokenData {
|
||||
token: string;
|
||||
deviceInfo: string;
|
||||
|
||||
8
src/lib/token-config.ts
Normal file
8
src/lib/token-config.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// Token 配置常量
|
||||
// 这个文件不依赖任何服务端模块,可以在客户端安全使用
|
||||
|
||||
export const TOKEN_CONFIG = {
|
||||
ACCESS_TOKEN_AGE: 4 * 60 * 60 * 1000, // 4 小时
|
||||
REFRESH_TOKEN_AGE: 60 * 24 * 60 * 60 * 1000, // 60 天
|
||||
RENEWAL_THRESHOLD: 10 * 60 * 1000, // 剩余 10 分钟时自动续期
|
||||
};
|
||||
Reference in New Issue
Block a user