mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-30 03:36:37 +08:00
feat: port remaining Vue UI surfaces with full backend support
Backend additions:
- New GORM models: NotifyChannel, PlayProfile (with PIN hashing).
- NotifyChannelService: multi-channel CRUD + unified dispatcher
supporting Telegram / Bark / WeChat / Webhook with optional
per-channel event filtering.
- PlayProfileService: per-user 'viewing personas' with
content-rating gates, library scoping, PIN protection, and
player defaults (autoplay, skip-intro, audio/subtitle prefs).
- New endpoints:
GET /admin/notify/channels list
POST /admin/notify/channels create
PUT /admin/notify/channels/:id update
DELETE /admin/notify/channels/:id delete
POST /admin/notify/channels/:id/test send test
GET /play-profiles list (admin: ?all=true)
POST /play-profiles create
PUT /play-profiles/:id update
DELETE /play-profiles/:id delete
GET /media/recent home page rail
GET /media/stats library composition
GET /watch-history list
GET /watch-history/stats aggregate
GET /watch-history/continue continue-watching rail
DELETE /watch-history[?media_id=] clear
DELETE /watch-history/:id remove one
GET /discover/sections available rails
GET /discover/feed?sections=a,b multi-section TMDb fetch
GET /system/info name/version/runtime
GET /system/status uptime/cpu/mem/disk
GET /system/scheduler read-only scheduler view
GET /stats/overview counts only
GET /stats/trend daily plays
GET /stats/top-content most played
GET /stats/libraries per-library size
GET /stats/monitor live hardware
Frontend additions:
- New API helpers: notify_channels, play_profiles, history,
stats_extra, media_extra, discover_extra, system.
- ProfileManagementPage (/play-profiles): full CRUD form with
PIN management, library scoping, language preferences.
- NotifyChannelsPage (/notify-channels, admin): typed config
forms per channel type + per-channel test action.
- SettingsPage (/settings, admin): grouped key/value editor
covering General, Organize/Scrape, Adult, qBittorrent.
- Layout sidebar links and App.tsx routes wired for all three.
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import { api } from './client'
|
||||
import type { DiscoverItem, DiscoverSection } from '../types'
|
||||
|
||||
// discoverExtraAPI wraps the Vue-style multi-section feed used by the
|
||||
// React DiscoverPage rails. Use the existing /discover/trending and
|
||||
// /discover/popular helpers for the simple cases.
|
||||
export const discoverExtraAPI = {
|
||||
sections: () =>
|
||||
api.get<{ sections: DiscoverSection[] }>('/discover/sections').then((r) => r.data.sections),
|
||||
|
||||
feed: (sectionKeys: string[]) =>
|
||||
api
|
||||
.get<Record<string, DiscoverItem[] | null>>('/discover/feed', {
|
||||
params: { sections: sectionKeys.join(',') },
|
||||
})
|
||||
.then((r) => r.data),
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { api } from './client'
|
||||
import type { HistoryItem, HistoryStats, Media } from '../types'
|
||||
|
||||
// historyAPI wraps /watch-history and /history. The two share storage on
|
||||
// the backend; we treat /watch-history as the rich admin/dashboard
|
||||
// surface and /history as the legacy resume-position write.
|
||||
export const historyAPI = {
|
||||
list: (limit = 50) =>
|
||||
api
|
||||
.get<HistoryItem[]>('/watch-history', { params: { limit } })
|
||||
.then((r) => r.data),
|
||||
|
||||
stats: () => api.get<HistoryStats>('/watch-history/stats').then((r) => r.data),
|
||||
|
||||
continueWatching: (limit = 10) =>
|
||||
api
|
||||
.get<{ history: HistoryItem; media: Media }[]>('/watch-history/continue', {
|
||||
params: { limit },
|
||||
})
|
||||
.then((r) => r.data),
|
||||
|
||||
clear: (mediaID?: string) =>
|
||||
api
|
||||
.delete('/watch-history', {
|
||||
params: mediaID ? { media_id: mediaID } : {},
|
||||
})
|
||||
.then((r) => r.data),
|
||||
|
||||
remove: (id: string) => api.delete(`/watch-history/${id}`).then((r) => r.data),
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { api } from './client'
|
||||
import type { Media } from '../types'
|
||||
|
||||
// Auxiliary media surfaces used by the home page rails and the admin
|
||||
// dashboard "library composition" card.
|
||||
export const mediaExtraAPI = {
|
||||
recent: (limit = 12) =>
|
||||
api.get<Media[]>('/media/recent', { params: { limit } }).then((r) => r.data),
|
||||
|
||||
stats: () =>
|
||||
api
|
||||
.get<{
|
||||
by_type: { movies: number; tv: number; anime: number; music: number; unscraped: number }
|
||||
total: number
|
||||
total_size: number
|
||||
total_seconds: number
|
||||
}>('/media/stats')
|
||||
.then((r) => r.data),
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { api } from './client'
|
||||
import type { NotifyChannel } from '../types'
|
||||
|
||||
// Payload accepted by create / update. `events` and `enabled` are optional.
|
||||
export interface NotifyChannelInput {
|
||||
name: string
|
||||
channel_type: NotifyChannel['channel_type']
|
||||
config: Record<string, string>
|
||||
events?: string[]
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
// notifyChannelsAPI wraps the admin /admin/notify/channels surface.
|
||||
export const notifyChannelsAPI = {
|
||||
list: () =>
|
||||
api.get<NotifyChannel[]>('/admin/notify/channels').then((r) => r.data),
|
||||
|
||||
create: (input: NotifyChannelInput) =>
|
||||
api.post<NotifyChannel>('/admin/notify/channels', input).then((r) => r.data),
|
||||
|
||||
update: (id: string, input: NotifyChannelInput) =>
|
||||
api.put<NotifyChannel>(`/admin/notify/channels/${id}`, input).then((r) => r.data),
|
||||
|
||||
remove: (id: string) =>
|
||||
api.delete(`/admin/notify/channels/${id}`).then((r) => r.data),
|
||||
|
||||
test: (id: string) =>
|
||||
api.post<{ message: string }>(`/admin/notify/channels/${id}/test`).then((r) => r.data),
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { api } from './client'
|
||||
import type { PlayProfile } from '../types'
|
||||
|
||||
// Payload accepted by create / update.
|
||||
export interface PlayProfileInput {
|
||||
user_id?: string
|
||||
name: string
|
||||
is_default: boolean
|
||||
content_rating_limit?: string
|
||||
allow_adult: boolean
|
||||
require_pin: boolean
|
||||
pin?: string
|
||||
preferred_subtitle_lang?: string
|
||||
preferred_audio_lang?: string
|
||||
autoplay_next: boolean
|
||||
skip_intro: boolean
|
||||
allowed_library_ids: string[]
|
||||
}
|
||||
|
||||
// playProfilesAPI wraps /play-profiles. The admin variant adds ?all=true.
|
||||
export const playProfilesAPI = {
|
||||
list: (all = false) =>
|
||||
api
|
||||
.get<PlayProfile[]>('/play-profiles', { params: all ? { all: 'true' } : {} })
|
||||
.then((r) => r.data),
|
||||
|
||||
create: (input: PlayProfileInput) =>
|
||||
api.post<PlayProfile>('/play-profiles', input).then((r) => r.data),
|
||||
|
||||
update: (id: string, input: PlayProfileInput) =>
|
||||
api.put<PlayProfile>(`/play-profiles/${id}`, input).then((r) => r.data),
|
||||
|
||||
remove: (id: string) =>
|
||||
api.delete(`/play-profiles/${id}`).then((r) => r.data),
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { api } from './client'
|
||||
import type { Hardware, Library, Media } from '../types'
|
||||
|
||||
// statsExtraAPI exposes the admin dashboard surfaces beyond /stats.
|
||||
export const statsExtraAPI = {
|
||||
overview: () =>
|
||||
api
|
||||
.get<{
|
||||
libraries: number
|
||||
media_count: number
|
||||
users_count: number
|
||||
total_size: number
|
||||
total_seconds: number
|
||||
generated_at: string
|
||||
}>('/stats/overview')
|
||||
.then((r) => r.data),
|
||||
|
||||
trend: (days = 14) =>
|
||||
api
|
||||
.get<{ trend: { day: string; count: number }[]; days: number }>('/stats/trend', {
|
||||
params: { days },
|
||||
})
|
||||
.then((r) => r.data),
|
||||
|
||||
topContent: (limit = 10) =>
|
||||
api
|
||||
.get<{
|
||||
items: { media: Media; play_count: number; last_played: string }[]
|
||||
}>('/stats/top-content', { params: { limit } })
|
||||
.then((r) => r.data),
|
||||
|
||||
libraries: () =>
|
||||
api
|
||||
.get<{
|
||||
libraries: { library: Library; item_count: number; total_size: number }[]
|
||||
}>('/stats/libraries')
|
||||
.then((r) => r.data),
|
||||
|
||||
monitor: () => api.get<Hardware>('/stats/monitor').then((r) => r.data),
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { api } from './client'
|
||||
|
||||
// systemAPI wraps the read-only /system/* metadata endpoints used by the
|
||||
// React shell (footer, admin dashboard, scheduler page).
|
||||
export const systemAPI = {
|
||||
info: () =>
|
||||
api
|
||||
.get<{
|
||||
name: string
|
||||
version: string
|
||||
go: string
|
||||
os: string
|
||||
arch: string
|
||||
data_dir: string
|
||||
cache_dir: string
|
||||
}>('/system/info')
|
||||
.then((r) => r.data),
|
||||
|
||||
status: () =>
|
||||
api
|
||||
.get<{
|
||||
uptime_seconds: number
|
||||
goroutines: number
|
||||
cpu_percent?: number
|
||||
memory_used?: number
|
||||
memory_total?: number
|
||||
disk_used?: number
|
||||
disk_total?: number
|
||||
}>('/system/status')
|
||||
.then((r) => r.data),
|
||||
|
||||
scheduler: () =>
|
||||
api
|
||||
.get<{ jobs: { name: string; cron: string; next_run?: string; last_run?: string }[] }>(
|
||||
'/system/scheduler',
|
||||
)
|
||||
.then((r) => r.data),
|
||||
}
|
||||
Reference in New Issue
Block a user