diff --git a/README.md b/README.md index b5ab9ce..80da051 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ mkdir -p data cache media downloads ```bash cat > .env <<'EOF' # 固定版本;需要升级时改成新的 MediaStationGo-vX.Y.Z 后执行 docker compose pull && docker compose up -d -MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.16 +MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.17 MEDIASTATION_HTTP_PORT=18080 # 程序数据和缓存建议放在 MediaStationGo 部署目录下,便于备份和迁移。 @@ -307,7 +307,7 @@ vim docker-compose.yml # # 镜像版本: # 默认拉取 latest;如需固定版本,创建 .env 并写入: -# MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.16 +# MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.17 # # 路径映射总览: # /data 程序数据目录。保存 SQLite 数据库、JWT secret、系统配置等,必须持久化。 @@ -516,7 +516,7 @@ docker compose up -d ```bash cat > .env <<'EOF' -MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.16 +MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.17 MEDIASTATION_HTTP_PORT=18080 MEDIASTATION_DATA_DIR=./data MEDIASTATION_CACHE_DIR=./cache @@ -779,26 +779,26 @@ cd MediaStationGo | 平台 | 包名示例 | | --- | --- | -| Linux x86_64 | `MediaStationGo-v0.0.16-linux-amd64.tar.gz` | -| Linux ARM64 | `MediaStationGo-v0.0.16-linux-arm64.tar.gz` | -| Windows x86_64 | `MediaStationGo-v0.0.16-windows-amd64.zip` | -| macOS Intel | `MediaStationGo-v0.0.16-darwin-amd64.tar.gz` | -| macOS Apple Silicon | `MediaStationGo-v0.0.16-darwin-arm64.tar.gz` | +| Linux x86_64 | `MediaStationGo-v0.0.17-linux-amd64.tar.gz` | +| Linux ARM64 | `MediaStationGo-v0.0.17-linux-arm64.tar.gz` | +| Windows x86_64 | `MediaStationGo-v0.0.17-windows-amd64.zip` | +| macOS Intel | `MediaStationGo-v0.0.17-darwin-amd64.tar.gz` | +| macOS Apple Silicon | `MediaStationGo-v0.0.17-darwin-arm64.tar.gz` | 部署步骤: ```bash # Linux 示例 -tar -xzf MediaStationGo-v0.0.16-linux-amd64.tar.gz -cd MediaStationGo-v0.0.16-linux-amd64 +tar -xzf MediaStationGo-v0.0.17-linux-amd64.tar.gz +cd MediaStationGo-v0.0.17-linux-amd64 MEDIASTATION_APP_PORT=18080 ./mediastation-go ``` Windows: ```powershell -Expand-Archive .\MediaStationGo-v0.0.16-windows-amd64.zip -cd .\MediaStationGo-v0.0.16-windows-amd64 +Expand-Archive .\MediaStationGo-v0.0.17-windows-amd64.zip +cd .\MediaStationGo-v0.0.17-windows-amd64 $env:MEDIASTATION_APP_PORT = "18080" .\mediastation-go.exe ``` diff --git a/README_EN.md b/README_EN.md index 9722ee9..580a011 100644 --- a/README_EN.md +++ b/README_EN.md @@ -235,7 +235,7 @@ mkdir -p data cache media downloads ```bash cat > .env <<'EOF' -MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.16 +MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.17 MEDIASTATION_HTTP_PORT=18080 MEDIASTATION_DATA_DIR=./data MEDIASTATION_CACHE_DIR=./cache @@ -344,7 +344,7 @@ For production, pin a specific release tag instead of using `latest`. Recommende ```bash cat > .env <<'EOF' -MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.16 +MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.17 MEDIASTATION_HTTP_PORT=18080 MEDIASTATION_DATA_DIR=./data MEDIASTATION_CACHE_DIR=./cache @@ -593,25 +593,25 @@ Each release provides multi-platform archives: | Platform | Package example | | --- | --- | -| Linux x86_64 | `MediaStationGo-v0.0.16-linux-amd64.tar.gz` | -| Linux ARM64 | `MediaStationGo-v0.0.16-linux-arm64.tar.gz` | -| Windows x86_64 | `MediaStationGo-v0.0.16-windows-amd64.zip` | -| macOS Intel | `MediaStationGo-v0.0.16-darwin-amd64.tar.gz` | -| macOS Apple Silicon | `MediaStationGo-v0.0.16-darwin-arm64.tar.gz` | +| Linux x86_64 | `MediaStationGo-v0.0.17-linux-amd64.tar.gz` | +| Linux ARM64 | `MediaStationGo-v0.0.17-linux-arm64.tar.gz` | +| Windows x86_64 | `MediaStationGo-v0.0.17-windows-amd64.zip` | +| macOS Intel | `MediaStationGo-v0.0.17-darwin-amd64.tar.gz` | +| macOS Apple Silicon | `MediaStationGo-v0.0.17-darwin-arm64.tar.gz` | Linux example: ```bash -tar -xzf MediaStationGo-v0.0.16-linux-amd64.tar.gz -cd MediaStationGo-v0.0.16-linux-amd64 +tar -xzf MediaStationGo-v0.0.17-linux-amd64.tar.gz +cd MediaStationGo-v0.0.17-linux-amd64 MEDIASTATION_APP_PORT=18080 ./mediastation-go ``` Windows example: ```powershell -Expand-Archive .\MediaStationGo-v0.0.16-windows-amd64.zip -cd .\MediaStationGo-v0.0.16-windows-amd64 +Expand-Archive .\MediaStationGo-v0.0.17-windows-amd64.zip +cd .\MediaStationGo-v0.0.17-windows-amd64 $env:MEDIASTATION_APP_PORT = "18080" .\mediastation-go.exe ``` diff --git a/docker-compose.yml b/docker-compose.yml index 4c46c1e..0a6bcc5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,7 @@ # # 镜像版本: # 默认拉取 latest;如需固定版本,创建 .env 并写入: -# MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.16 +# MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.17 # # 路径映射总览: # /data 程序数据目录。保存 SQLite 数据库、JWT secret、系统配置等,必须持久化。 diff --git a/web/src/api/permission.ts b/web/src/api/permission.ts index e4bf457..5d543c8 100644 --- a/web/src/api/permission.ts +++ b/web/src/api/permission.ts @@ -2,6 +2,37 @@ import { api } from './client' import type { UserPermission } from '../types' +type PermissionPayload = { + permissions: Record + role: string + tier: string + is_super: boolean +} + +type ApiEnvelope = { + code?: number + message?: string + data?: T +} + +function unwrapPermissionsPayload(raw: unknown): PermissionPayload { + const payload = ( + raw && + typeof raw === 'object' && + 'data' in raw && + (raw as ApiEnvelope).data + ? (raw as ApiEnvelope).data + : raw + ) as Partial | null + + return { + permissions: payload?.permissions ?? {}, + role: payload?.role ?? '', + tier: payload?.tier ?? 'free', + is_super: payload?.is_super ?? false, + } +} + // 获取用户权限 export async function getUserPermissions(userId: string): Promise { const resp = await api.get(`/admin/users/${userId}/permissions`) @@ -29,10 +60,5 @@ export async function getMyPermissions(): Promise<{ is_super: boolean }> { const resp = await api.get('/auth/permissions') - return resp.data as unknown as { - permissions: Record - role: string - tier: string - is_super: boolean - } + return unwrapPermissionsPayload(resp.data) } diff --git a/web/src/api/permissions.ts b/web/src/api/permissions.ts index 671ecbf..e53cbb1 100644 --- a/web/src/api/permissions.ts +++ b/web/src/api/permissions.ts @@ -18,21 +18,36 @@ export interface UserPermission { updated_at: string } +type ApiEnvelope = { + code?: number + message?: string + data?: T +} + +function unwrap(raw: T | ApiEnvelope): T { + if (raw && typeof raw === 'object' && 'data' in raw && (raw as ApiEnvelope).data !== undefined) { + return (raw as ApiEnvelope).data as T + } + return raw as T +} + export const permissionsAPI = { // Caller's effective permissions; admins always get the all-true set. - mine: () => api.get('/auth/permissions').then((r) => r.data), + mine: () => api.get>('/auth/permissions').then((r) => unwrap(r.data)), // Admin endpoints get: (userID: string) => - api.get(`/admin/users/${userID}/permissions`).then((r) => r.data), + api + .get>(`/admin/users/${userID}/permissions`) + .then((r) => unwrap(r.data)), save: (userID: string, p: UserPermission) => api - .put(`/admin/users/${userID}/permissions`, p) - .then((r) => r.data), + .put>(`/admin/users/${userID}/permissions`, p) + .then((r) => unwrap(r.data)), reset: (userID: string) => api - .post(`/admin/users/${userID}/permissions/reset`) - .then((r) => r.data), + .post>(`/admin/users/${userID}/permissions/reset`) + .then((r) => unwrap(r.data)), } diff --git a/web/src/components/Layout.tsx b/web/src/components/Layout.tsx index c5516d4..6ded78d 100644 --- a/web/src/components/Layout.tsx +++ b/web/src/components/Layout.tsx @@ -47,13 +47,13 @@ export function Layout() { }, [location.pathname]) useEffect(() => { - if (user && !isPermissionLoading && Object.keys(permissions).length === 0) { + if (user && !isPermissionLoading && Object.keys(permissions ?? {}).length === 0) { fetchPermissions().catch(() => undefined) } }, [fetchPermissions, isPermissionLoading, permissions, user]) const isAdmin = user?.role === 'admin' - const can = (key: string) => isAdmin || isSuper || permissions[key] === true + const can = (key: string) => isAdmin || isSuper || (permissions ?? {})[key] === true const handleSearchSubmit = (e: React.FormEvent) => { e.preventDefault() diff --git a/web/src/stores/permissions.ts b/web/src/stores/permissions.ts index 5d025ae..d7f33c9 100644 --- a/web/src/stores/permissions.ts +++ b/web/src/stores/permissions.ts @@ -27,10 +27,10 @@ export const usePermissionStore = create((set, get) => ({ try { const result = await getMyPermissions() set({ - permissions: result.permissions, - role: result.role, - tier: result.tier, - isSuper: result.is_super, + permissions: result.permissions ?? {}, + role: result.role ?? '', + tier: result.tier ?? 'free', + isSuper: result.is_super ?? false, isLoading: false, }) } catch (err) { @@ -47,7 +47,7 @@ export const usePermissionStore = create((set, get) => ({ if (state.isSuper) { return true } - return state.permissions[key] === true + return (state.permissions ?? {})[key] === true }, clearPermissions: () => {