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