Fix TypeScript errors: Update User type system across all storage implementations

This commit is contained in:
katelya
2025-09-05 15:59:55 +08:00
parent d83e2c6f42
commit 142c780b50
9 changed files with 574 additions and 22 deletions
+1 -1
View File
@@ -1 +1 @@
if(!self.define){let e,t={};const s=(s,n)=>(s=new URL(s+".js",n).href,t[s]||new Promise(t=>{if("document"in self){const e=document.createElement("script");e.src=s,e.onload=t,document.head.appendChild(e)}else e=s,importScripts(s),t()}).then(()=>{let e=t[s];if(!e)throw new Error(`Module ${s} didnt register its module`);return e}));self.define=(n,c)=>{const a=e||("document"in self?document.currentScript.src:"")||location.href;if(t[a])return;let i={};const u=e=>s(e,a),r={module:{uri:a},exports:i,require:u};t[a]=Promise.all(n.map(e=>r[e]||u(e))).then(e=>(c(...e),i))}}define(["./workbox-e9849328"],function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/app-build-manifest.json",revision:"a7fbfbfdafe57c95e97e8d3cfd839328"},{url:"/_next/static/UMt0xPAGCnkut4ut2VU-9/_buildManifest.js",revision:"046380ae5bc74b46b6d5eac3eed65355"},{url:"/_next/static/UMt0xPAGCnkut4ut2VU-9/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/110-442e7067e441a607.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/154-211e189482cc0258.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/29-2acace5e289d422b.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/459-0617417b9c423d5b.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/51b697cb-24a59f0c53e2e105.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/682-e64c4c7808d3e611.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/711-9ae080cb4f6a9355.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/900-c7c9e505cc903ead.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/998-f22ebd15e7bac0f0.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/app/_not-found/page-4dc7d52fd5d943cc.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/app/admin/page-7f30b4abb7bde63b.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/app/config/page-578e0487b53650a4.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/app/douban/page-6b5d567834ba726e.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/app/layout-41b89aac912b9c2b.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/app/login/page-bbafa236e79f06c3.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/app/page-a401624afa29aef0.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/app/play/page-7d5c45316b8afbc6.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/app/search/page-ce5b70ae0b349207.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/app/settings/page-95f3174527d62558.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/app/tvbox/page-443d4dd8e3c842b3.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/app/warning/page-11cba4cf9332a238.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/c72274ce-06682d6fc8197e6d.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/da9543df-bf6da1a431d8604f.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/framework-6e06c675866dc992.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/main-6c3022dbb9d044ff.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/main-app-dbd320e104e1a5dc.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/pages/_app-792b631a362c29e1.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/pages/_error-9fde6601392a2a99.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-17170f1d90853b2d.js",revision:"UMt0xPAGCnkut4ut2VU-9"},{url:"/_next/static/css/23100062f5d4aac0.css",revision:"23100062f5d4aac0"},{url:"/_next/static/css/275ed64cc4367444.css",revision:"275ed64cc4367444"},{url:"/_next/static/css/355e679a11861bb0.css",revision:"355e679a11861bb0"},{url:"/_next/static/media/26a46d62cd723877-s.woff2",revision:"befd9c0fdfa3d8a645d5f95717ed6420"},{url:"/_next/static/media/55c55f0601d81cf3-s.woff2",revision:"43828e14271c77b87e3ed582dbff9f74"},{url:"/_next/static/media/581909926a08bbc8-s.woff2",revision:"f0b86e7c24f455280b8df606b89af891"},{url:"/_next/static/media/8e9860b6e62d6359-s.woff2",revision:"01ba6c2a184b8cba08b0d57167664d75"},{url:"/_next/static/media/97e0cb1ae144a2a9-s.woff2",revision:"e360c61c5bd8d90639fd4503c829c2dc"},{url:"/_next/static/media/df0a9ae256c0569c-s.woff2",revision:"d54db44de5ccb18886ece2fda72bdfe0"},{url:"/_next/static/media/e4af272ccee01ff0-s.p.woff2",revision:"65850a373e258f1c897a2b3d75eb74de"},{url:"/favicon.ico",revision:"c5de6e56c5664adda146825f75ea6ecf"},{url:"/icons/icon-192x192.png",revision:"4a56c090828a1ad254c903c7aec0389d"},{url:"/icons/icon-256x256.png",revision:"f6409eb1a001f754121e3a8281c0319c"},{url:"/icons/icon-384x384.png",revision:"f6efc3e357b9ffdf4e0d8c14b2ed0ac1"},{url:"/icons/icon-512x512.png",revision:"9c008cbbeb6a576fe07bb1284a83f4d2"},{url:"/logo.png",revision:"40de611b143c47c6291c7bdad2c959ca"},{url:"/manifest.json",revision:"7bd3dabc1cfbfe40f09577efca223d31"},{url:"/robots.txt",revision:"e2b2cd8514443456bc6fb9d77b3b1f3e"},{url:"/screenshot1.png",revision:"10572bfcea54dc93ac4c5f7c9057fc98"},{url:"/screenshot2.png",revision:"f815a8990973a221899976867365c239"},{url:"/screenshot3.png",revision:"49709e96345dfeeab1d8083821d4b44e"},{url:"/screenshot4.png",revision:"a76c751e41e37556048a487e4f8b8b1c"},{url:"/wechat.jpg",revision:"d0f601311802667cd6ca5a37dc69bfa7"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:t,event:s,state:n})=>t&&"opaqueredirect"===t.type?new Response(t.body,{status:200,statusText:"OK",headers:t.headers}):t}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp4)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.NetworkFirst({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;const t=e.pathname;return!t.startsWith("/api/auth/")&&!!t.startsWith("/api/")},new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;return!e.pathname.startsWith("/api/")},new e.NetworkFirst({cacheName:"others",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>!(self.origin===e.origin),new e.NetworkFirst({cacheName:"cross-origin",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:3600})]}),"GET")});
if(!self.define){let e,s={};const n=(n,t)=>(n=new URL(n+".js",t).href,s[n]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=s,document.head.appendChild(e)}else e=n,importScripts(n),s()}).then(()=>{let e=s[n];if(!e)throw new Error(`Module ${n} didnt register its module`);return e}));self.define=(t,c)=>{const a=e||("document"in self?document.currentScript.src:"")||location.href;if(s[a])return;let i={};const r=e=>n(e,a),o={module:{uri:a},exports:i,require:r};s[a]=Promise.all(t.map(e=>o[e]||r(e))).then(e=>(c(...e),i))}}define(["./workbox-e9849328"],function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/app-build-manifest.json",revision:"da3e7f135771549b628a65338b86353c"},{url:"/_next/static/BTk6Xg2zuGHYv0RDM8tZg/_buildManifest.js",revision:"046380ae5bc74b46b6d5eac3eed65355"},{url:"/_next/static/BTk6Xg2zuGHYv0RDM8tZg/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/110-442e7067e441a607.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/154-211e189482cc0258.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/29-2acace5e289d422b.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/459-0617417b9c423d5b.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/51b697cb-24a59f0c53e2e105.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/682-e64c4c7808d3e611.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/711-9ae080cb4f6a9355.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/900-c7c9e505cc903ead.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/998-f22ebd15e7bac0f0.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/app/_not-found/page-4dc7d52fd5d943cc.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/app/admin/page-7f30b4abb7bde63b.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/app/config/page-578e0487b53650a4.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/app/douban/page-6b5d567834ba726e.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/app/layout-41b89aac912b9c2b.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/app/login/page-bbafa236e79f06c3.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/app/page-a401624afa29aef0.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/app/play/page-6297f80eaa080bf5.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/app/search/page-cafa7a89158278cb.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/app/settings/page-d73b9df16c781bd2.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/app/tvbox/page-443d4dd8e3c842b3.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/app/warning/page-11cba4cf9332a238.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/c72274ce-06682d6fc8197e6d.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/da9543df-bf6da1a431d8604f.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/framework-6e06c675866dc992.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/main-283be9c351146029.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/main-app-dbd320e104e1a5dc.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/pages/_app-792b631a362c29e1.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/pages/_error-9fde6601392a2a99.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-17170f1d90853b2d.js",revision:"BTk6Xg2zuGHYv0RDM8tZg"},{url:"/_next/static/css/23100062f5d4aac0.css",revision:"23100062f5d4aac0"},{url:"/_next/static/css/275ed64cc4367444.css",revision:"275ed64cc4367444"},{url:"/_next/static/css/89b466011e0abf7c.css",revision:"89b466011e0abf7c"},{url:"/_next/static/media/26a46d62cd723877-s.woff2",revision:"befd9c0fdfa3d8a645d5f95717ed6420"},{url:"/_next/static/media/55c55f0601d81cf3-s.woff2",revision:"43828e14271c77b87e3ed582dbff9f74"},{url:"/_next/static/media/581909926a08bbc8-s.woff2",revision:"f0b86e7c24f455280b8df606b89af891"},{url:"/_next/static/media/8e9860b6e62d6359-s.woff2",revision:"01ba6c2a184b8cba08b0d57167664d75"},{url:"/_next/static/media/97e0cb1ae144a2a9-s.woff2",revision:"e360c61c5bd8d90639fd4503c829c2dc"},{url:"/_next/static/media/df0a9ae256c0569c-s.woff2",revision:"d54db44de5ccb18886ece2fda72bdfe0"},{url:"/_next/static/media/e4af272ccee01ff0-s.p.woff2",revision:"65850a373e258f1c897a2b3d75eb74de"},{url:"/favicon.ico",revision:"c5de6e56c5664adda146825f75ea6ecf"},{url:"/icons/icon-192x192.png",revision:"4a56c090828a1ad254c903c7aec0389d"},{url:"/icons/icon-256x256.png",revision:"f6409eb1a001f754121e3a8281c0319c"},{url:"/icons/icon-384x384.png",revision:"f6efc3e357b9ffdf4e0d8c14b2ed0ac1"},{url:"/icons/icon-512x512.png",revision:"9c008cbbeb6a576fe07bb1284a83f4d2"},{url:"/logo.png",revision:"40de611b143c47c6291c7bdad2c959ca"},{url:"/manifest.json",revision:"7bd3dabc1cfbfe40f09577efca223d31"},{url:"/robots.txt",revision:"e2b2cd8514443456bc6fb9d77b3b1f3e"},{url:"/screenshot1.png",revision:"10572bfcea54dc93ac4c5f7c9057fc98"},{url:"/screenshot2.png",revision:"f815a8990973a221899976867365c239"},{url:"/screenshot3.png",revision:"49709e96345dfeeab1d8083821d4b44e"},{url:"/screenshot4.png",revision:"a76c751e41e37556048a487e4f8b8b1c"},{url:"/wechat.jpg",revision:"d0f601311802667cd6ca5a37dc69bfa7"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:s,event:n,state:t})=>s&&"opaqueredirect"===s.type?new Response(s.body,{status:200,statusText:"OK",headers:s.headers}):s}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp4)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.NetworkFirst({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;const s=e.pathname;return!s.startsWith("/api/auth/")&&!!s.startsWith("/api/")},new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;return!e.pathname.startsWith("/api/")},new e.NetworkFirst({cacheName:"others",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>!(self.origin===e.origin),new e.NetworkFirst({cacheName:"cross-origin",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:3600})]}),"GET")});
+155
View File
@@ -0,0 +1,155 @@
/* eslint-disable no-console, @typescript-eslint/no-explicit-any */
import { NextRequest, NextResponse } from 'next/server';
import { getStorage } from '@/lib/db';
import { User } from '@/lib/types';
export const runtime = 'edge';
// 检查是否为站长账户
function isOwnerAccount(username: string): boolean {
const ownerUsername = process.env.USERNAME || 'admin';
return username === ownerUsername;
}
export async function GET(request: NextRequest) {
try {
// 从Authorization头获取当前用户
const auth = request.headers.get('Authorization')?.replace('Bearer ', '');
if (!auth) {
return NextResponse.json({ error: '需要认证' }, { status: 401 });
}
const currentUsername = decodeURIComponent(auth);
// 检查是否为站长账户
if (!isOwnerAccount(currentUsername)) {
return NextResponse.json({ error: '权限不足' }, { status: 403 });
}
// 获取所有用户及其设置
const storage = getStorage();
const users: User[] = await storage.getAllUsers();
const usersWithSettings = await Promise.all(
users.map(async (user) => {
const settings = await storage.getUserSettings(user.username);
return {
username: user.username,
role: user.role || 'user',
created_at: user.created_at,
filter_adult_content: settings?.filter_adult_content ?? true,
can_disable_filter: settings?.can_disable_filter ?? true,
managed_by_admin: settings?.managed_by_admin ?? false,
last_filter_change: settings?.last_filter_change
};
})
);
return NextResponse.json({
users: usersWithSettings,
total: usersWithSettings.length
});
} catch (error) {
console.error('获取用户列表失败:', error);
return NextResponse.json({ error: '获取用户列表失败' }, { status: 500 });
}
}
export async function POST(request: NextRequest) {
try {
// 从Authorization头获取当前用户
const auth = request.headers.get('Authorization')?.replace('Bearer ', '');
if (!auth) {
return NextResponse.json({ error: '需要认证' }, { status: 401 });
}
const currentUsername = decodeURIComponent(auth);
// 检查是否为站长账户
if (!isOwnerAccount(currentUsername)) {
return NextResponse.json({ error: '权限不足' }, { status: 403 });
}
const storage = getStorage();
const { action, username, settings } = await request.json();
switch (action) {
case 'update_settings': {
// 更新用户设置
const currentSettings = await storage.getUserSettings(username);
const newSettings = {
...currentSettings,
...settings,
last_filter_change: new Date().toISOString()
};
await storage.setUserSettings(username, newSettings);
return NextResponse.json({
success: true,
message: `已更新用户 ${username} 的设置`
});
}
case 'force_filter': {
// 强制开启某用户的成人内容过滤
const currentSettings = await storage.getUserSettings(username) || {
filter_adult_content: true,
theme: 'auto' as const,
language: 'zh-CN',
auto_play: false,
video_quality: 'auto'
};
await storage.setUserSettings(username, {
...currentSettings,
filter_adult_content: true,
can_disable_filter: false,
managed_by_admin: true,
last_filter_change: new Date().toISOString()
});
return NextResponse.json({
success: true,
message: `已强制开启用户 ${username} 的成人内容过滤`
});
}
case 'allow_disable': {
// 允许用户自己管理过滤设置
const existingSettings = await storage.getUserSettings(username) || {
filter_adult_content: true,
theme: 'auto' as const,
language: 'zh-CN',
auto_play: false,
video_quality: 'auto'
};
await storage.setUserSettings(username, {
...existingSettings,
filter_adult_content: existingSettings.filter_adult_content ?? true,
theme: existingSettings.theme || 'auto',
language: existingSettings.language || 'zh-CN',
auto_play: existingSettings.auto_play ?? false,
video_quality: existingSettings.video_quality || 'auto',
can_disable_filter: true,
managed_by_admin: false,
last_filter_change: new Date().toISOString()
});
return NextResponse.json({
success: true,
message: `已允许用户 ${username} 自己管理过滤设置`
});
}
default:
return NextResponse.json({ error: '未知操作' }, { status: 400 });
}
} catch (error) {
console.error('用户管理操作失败:', error);
return NextResponse.json({ error: '操作失败' }, { status: 500 });
}
}
+288
View File
@@ -0,0 +1,288 @@
/* eslint-disable no-console, @typescript-eslint/no-explicit-any */
'use client';
import { useEffect, useState } from 'react';
// 临时内联认证函数,避免导入问题
function getAuthInfoFromBrowserCookie(): {
password?: string;
username?: string;
signature?: string;
timestamp?: number;
} | null {
if (typeof window === 'undefined') {
return null;
}
const cookies = document.cookie.split(';');
const authCookie = cookies.find(cookie => cookie.trim().startsWith('auth='));
if (!authCookie) {
return null;
}
try {
const cookieValue = authCookie.split('=')[1];
const decoded = decodeURIComponent(cookieValue);
const authData = JSON.parse(decoded);
return authData;
} catch (error) {
console.error('Failed to parse auth cookie:', error);
return null;
}
}
interface UserInfo {
username: string;
role: string;
created_at: string;
filter_adult_content: boolean;
can_disable_filter: boolean;
managed_by_admin: boolean;
last_filter_change?: string;
}
export default function UserManagement() {
const [users, setUsers] = useState<UserInfo[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [currentUser, setCurrentUser] = useState<string | null>(null);
useEffect(() => {
// 获取当前用户信息
const authInfo = getAuthInfoFromBrowserCookie();
if (authInfo?.username) {
setCurrentUser(authInfo.username);
loadUsers();
} else {
setError('未登录或权限不足');
setLoading(false);
}
}, []);
const loadUsers = async () => {
try {
const authInfo = getAuthInfoFromBrowserCookie();
if (!authInfo?.username) {
throw new Error('未获取到用户认证信息');
}
const response = await fetch('/api/admin/users', {
headers: {
'Authorization': `Bearer ${encodeURIComponent(authInfo.username)}`,
'Cache-Control': 'no-cache'
}
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || '获取用户列表失败');
}
const data = await response.json();
setUsers(data.users || []);
} catch (err) {
setError(err instanceof Error ? err.message : '未知错误');
console.error('加载用户列表失败:', err);
} finally {
setLoading(false);
}
};
const updateUserSettings = async (username: string, action: string, settings?: any) => {
try {
const authInfo = getAuthInfoFromBrowserCookie();
if (!authInfo?.username) {
throw new Error('未获取到用户认证信息');
}
const response = await fetch('/api/admin/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${encodeURIComponent(authInfo.username)}`
},
body: JSON.stringify({
action,
username,
settings
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || '操作失败');
}
const data = await response.json();
alert(data.message || '操作成功');
// 重新加载用户列表
await loadUsers();
} catch (err) {
const errorMsg = err instanceof Error ? err.message : '未知错误';
alert(`操作失败: ${errorMsg}`);
console.error('用户管理操作失败:', err);
}
};
const handleForceFilter = (username: string) => {
if (confirm(`确定要强制开启用户 ${username} 的成人内容过滤功能吗?`)) {
updateUserSettings(username, 'force_filter');
}
};
const handleAllowDisable = (username: string) => {
if (confirm(`确定要允许用户 ${username} 自己管理过滤设置吗?`)) {
updateUserSettings(username, 'allow_disable');
}
};
if (loading) {
return (
<div className="flex items-center justify-center h-40">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-green-500"></div>
</div>
);
}
if (error) {
return (
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md p-4">
<div className="text-red-600 dark:text-red-400">{error}</div>
<button
onClick={loadUsers}
className="mt-2 text-sm text-red-600 dark:text-red-400 underline hover:no-underline"
>
</button>
</div>
);
}
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-xl font-bold text-gray-800 dark:text-gray-200">
</h2>
<div className="text-sm text-gray-600 dark:text-gray-400">
{users.length}
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-900">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
</th>
</tr>
</thead>
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
{users.map((user) => (
<tr key={user.username} className="hover:bg-gray-50 dark:hover:bg-gray-700">
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">
{user.username}
</div>
{user.username === currentUser && (
<span className="ml-2 px-2 py-1 text-xs bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 rounded-full">
</span>
)}
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
user.role === 'owner'
? 'bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200'
: 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300'
}`}>
{user.role === 'owner' ? '站长' : '用户'}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
user.filter_adult_content
? 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200'
: 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200'
}`}>
{user.filter_adult_content ? '已开启' : '已关闭'}
</span>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{user.managed_by_admin ? (
<span className="text-orange-600 dark:text-orange-400"></span>
) : user.can_disable_filter ? (
<span className="text-green-600 dark:text-green-400"></span>
) : (
<span className="text-gray-600 dark:text-gray-400"></span>
)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
{user.role !== 'owner' && user.username !== currentUser && (
<div className="flex space-x-2">
{!user.filter_adult_content || !user.managed_by_admin ? (
<button
onClick={() => handleForceFilter(user.username)}
className="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300"
>
</button>
) : null}
{user.managed_by_admin || !user.can_disable_filter ? (
<button
onClick={() => handleAllowDisable(user.username)}
className="text-green-600 hover:text-green-900 dark:text-green-400 dark:hover:text-green-300"
>
</button>
) : null}
</div>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
{users.length === 0 && (
<div className="text-center py-12 text-gray-500 dark:text-gray-400">
</div>
)}
</div>
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md p-4">
<h3 className="text-sm font-medium text-blue-800 dark:text-blue-200 mb-2">
</h3>
<ul className="text-sm text-blue-600 dark:text-blue-300 space-y-1">
<li> <strong></strong></li>
<li> <strong></strong></li>
<li> </li>
<li> </li>
</ul>
</div>
</div>
);
}
+11 -5
View File
@@ -1,7 +1,7 @@
/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import { AdminConfig } from './admin.types';
import { EpisodeSkipConfig, Favorite, IStorage, PlayRecord, UserSettings } from './types';
import { EpisodeSkipConfig, Favorite, IStorage, PlayRecord, User, UserSettings } from './types';
// 搜索历史最大条数
const SEARCH_HISTORY_LIMIT = 20;
@@ -428,14 +428,20 @@ export class D1Storage implements IStorage {
}
// 用户列表
async getAllUsers(): Promise<string[]> {
async getAllUsers(): Promise<User[]> {
try {
const db = await this.getDatabase();
const result = await db
.prepare('SELECT username FROM users ORDER BY created_at ASC')
.all<{ username: string }>();
.prepare('SELECT username, created_at FROM users ORDER BY created_at ASC')
.all<{ username: string; created_at: string }>();
return result.results.map((row) => row.username);
const ownerUsername = process.env.USERNAME || 'admin';
return result.results.map((row) => ({
username: row.username,
role: row.username === ownerUsername ? 'owner' : 'user',
created_at: row.created_at
}));
} catch (err) {
console.error('Failed to get all users:', err);
throw err;
+26 -4
View File
@@ -3,7 +3,7 @@
import { createClient, RedisClientType } from 'redis';
import { AdminConfig } from './admin.types';
import { EpisodeSkipConfig, Favorite, IStorage, PlayRecord, UserSettings } from './types';
import { EpisodeSkipConfig, Favorite, IStorage, PlayRecord, User, UserSettings } from './types';
// 搜索历史最大条数
const SEARCH_HISTORY_LIMIT = 20;
@@ -266,9 +266,31 @@ export class KvrocksStorage implements IStorage {
});
}
async getAllUsers(): Promise<string[]> {
const users = await withRetry(() => this.client.sMembers(this.userListKey()));
return ensureStringArray(users);
async getAllUsers(): Promise<User[]> {
const usernames = await withRetry(() => this.client.sMembers(this.userListKey()));
const ownerUsername = process.env.USERNAME || 'admin';
const users = await Promise.all(
usernames.map(async (username) => {
let created_at = '';
try {
const userData = await this.getUser(username);
if (userData?.created_at) {
created_at = new Date(userData.created_at).toISOString();
}
} catch (err) {
// 忽略错误,使用空字符串
}
return {
username,
role: username === ownerUsername ? 'owner' : 'user',
created_at
};
})
);
return users;
}
async registerUser(userName: string, password: string): Promise<void> {
+25 -5
View File
@@ -1,6 +1,6 @@
/* eslint-disable no-console */
import { AdminConfig } from './admin.types';
import { EpisodeSkipConfig, Favorite, IStorage, PlayRecord, UserSettings } from './types';
import { EpisodeSkipConfig, Favorite, IStorage, PlayRecord, User, UserSettings } from './types';
/**
* LocalStorage 存储实现
@@ -341,18 +341,38 @@ export class LocalStorage implements IStorage {
}
// ---------- 管理员功能 ----------
async getAllUsers(): Promise<string[]> {
async getAllUsers(): Promise<User[]> {
if (typeof window === 'undefined') return [];
try {
const users: string[] = [];
const users: User[] = [];
const prefix = 'katelyatv_user_';
const ownerUsername = process.env.USERNAME || 'admin';
for (let i = 0; i < localStorage.length; i++) {
const storageKey = localStorage.key(i);
if (storageKey && storageKey.startsWith(prefix)) {
const userName = storageKey.replace(prefix, '');
users.push(userName);
const username = storageKey.replace(prefix, '');
// 尝试获取用户创建时间
let created_at = '';
try {
const userDataStr = localStorage.getItem(storageKey);
if (userDataStr) {
const userData = JSON.parse(userDataStr);
if (userData.created_at) {
created_at = new Date(userData.created_at).toISOString();
}
}
} catch (err) {
// 忽略解析错误
}
users.push({
username,
role: username === ownerUsername ? 'owner' : 'user',
created_at
});
}
}
+30 -3
View File
@@ -3,7 +3,7 @@
import { createClient, RedisClientType } from 'redis';
import { AdminConfig } from './admin.types';
import { EpisodeSkipConfig, Favorite, IStorage, PlayRecord, UserSettings } from './types';
import { EpisodeSkipConfig, Favorite, IStorage, PlayRecord, User, UserSettings } from './types';
// 搜索历史最大条数
const SEARCH_HISTORY_LIMIT = 20;
@@ -302,14 +302,41 @@ export class RedisStorage implements IStorage {
}
// ---------- 获取全部用户 ----------
async getAllUsers(): Promise<string[]> {
async getAllUsers(): Promise<User[]> {
const keys = await withRetry(() => this.client.keys('u:*:pwd'));
return keys
const ownerUsername = process.env.USERNAME || 'admin';
const usernames = keys
.map((k) => {
const match = k.match(/^u:(.+?):pwd$/);
return match ? ensureString(match[1]) : undefined;
})
.filter((u): u is string => typeof u === 'string');
// 获取用户创建时间并构造 User 对象
const users = await Promise.all(
usernames.map(async (username) => {
// 尝试获取用户创建时间,如果没有则使用空字符串
const createdAtKey = `u:${username}:created_at`;
let created_at = '';
try {
const timestamp = await withRetry(() => this.client.get(createdAtKey));
if (timestamp) {
created_at = new Date(parseInt(timestamp)).toISOString();
}
} catch (err) {
// 忽略错误,使用空字符串
}
return {
username,
role: username === ownerUsername ? 'owner' : 'user',
created_at
};
})
);
return users;
}
// ---------- 管理员配置 ----------
+8 -1
View File
@@ -42,6 +42,13 @@ export interface Favorite {
search_title: string; // 搜索时使用的标题
}
// 用户数据结构
export interface User {
username: string;
role?: string;
created_at?: string;
}
// 存储接口
export interface IStorage {
// 播放记录相关
@@ -87,7 +94,7 @@ export interface IStorage {
deleteSkipConfig(userName: string, key: string): Promise<void>;
// 用户列表
getAllUsers(): Promise<string[]>;
getAllUsers(): Promise<User[]>;
// 管理员配置相关
getAdminConfig(): Promise<AdminConfig | null>;
+30 -3
View File
@@ -3,7 +3,7 @@
import { Redis } from '@upstash/redis';
import { AdminConfig } from './admin.types';
import { EpisodeSkipConfig, Favorite, IStorage, PlayRecord, UserSettings } from './types';
import { EpisodeSkipConfig, Favorite, IStorage, PlayRecord, User, UserSettings } from './types';
// 搜索历史最大条数
const SEARCH_HISTORY_LIMIT = 20;
@@ -244,14 +244,41 @@ export class UpstashRedisStorage implements IStorage {
}
// ---------- 获取全部用户 ----------
async getAllUsers(): Promise<string[]> {
async getAllUsers(): Promise<User[]> {
const keys = await withRetry(() => this.client.keys('u:*:pwd'));
return keys
const ownerUsername = process.env.USERNAME || 'admin';
const usernames = keys
.map((k) => {
const match = k.match(/^u:(.+?):pwd$/);
return match ? ensureString(match[1]) : undefined;
})
.filter((u): u is string => typeof u === 'string');
// 获取用户创建时间并构造 User 对象
const users = await Promise.all(
usernames.map(async (username) => {
// 尝试获取用户创建时间,如果没有则使用空字符串
const createdAtKey = `u:${username}:created_at`;
let created_at = '';
try {
const timestamp = await withRetry(() => this.client.get(createdAtKey));
if (timestamp && typeof timestamp === 'number') {
created_at = new Date(timestamp).toISOString();
}
} catch (err) {
// 忽略错误,使用空字符串
}
return {
username,
role: username === ownerUsername ? 'owner' : 'user',
created_at
};
})
);
return users;
}
// ---------- 管理员配置 ----------