classDiagram direction TB class Base { +ID: string +CreatedAt: time.Time +UpdatedAt: time.Time +DeletedAt: gorm.DeletedAt +BeforeCreate(db: *gorm.DB) error } class User { -Base +Username: string +PasswordHash: string +Role: string +Tier: string +Email: string +AvatarURL: string +Nickname: string +IsActive: bool +ForcePasswordReset: bool +LastLoginAt: *time.Time } class UserPermission { +ID: string +UserID: string +CanViewDashboard: bool +CanPlayMedia: bool +CanCast: bool +CanExternalPlayer: bool +CanFavorite: bool +CanViewHistory: bool +CanEditMedia: bool +CanRescrape: bool +CanUseAI: bool +CanCaptureFrames: bool +CanManageDownloads: bool +CanViewDiscover: bool +CanManageSubscriptions: bool +CanManageSites: bool +CanUseAIAssistant: bool +CanManageUsers: bool +CanManageFiles: bool +CanManageStrm: bool +CanAccessSettings: bool } class RefreshToken { +ID: string +UserID: string +TokenHash: string +ExpiresAt: time.Time +CreatedAt: time.Time +Revoked: bool } class Library { -Base +Name: string +Path: string +Type: string +Enabled: bool +ScanIntervalMin: int +MetadataLanguage: string +AdultContent: bool +PreferNFO: bool +EnableWatch: bool +MinFileSizeMB: int } class Media { -Base +LibraryID: string +SeriesID: string +Title: string +OriginalName: string +Path: string +SizeBytes: int64 +DurationSec: int +Width: int +Height: int +VideoCodec: string +AudioCodec: string +Container: string +PosterURL: string +BackdropURL: string +Overview: string +Rating: float32 +Year: int +SeasonNum: int +EpisodeNum: int +ScrapeStatus: string +TMDbID: int +BangumiID: int +DoubanID: int +NSFW: bool +FileHash: string +IsDuplicate: bool +DuplicateOfID: string +STRMURL: string +Resolution: string +AudioChannels: int +HdrFormat: string +FrameRate: float32 +ColorSpace: string +BitDepth: int +Genres: string } class Series { -Base +LibraryID: string +Title: string +PosterURL: string +BackdropURL: string +Overview: string +Rating: float32 +Year: int +TMDbID: int +BangumiID: int } class SubtitleTrack { -Base +MediaID: string +Language: string +LanguageName: string +Path: string +Source: string +Codec: string +IsInternal: bool +StreamIdx: int } class PlaybackHistory { -Base +UserID: string +MediaID: string +PositionMs: int64 +DurationMs: int64 +WatchedAt: time.Time +PlayedAt: time.Time +Completed: bool +DeviceType: string +IPAddress: string } class Favorite { -Base +UserID: string +MediaID: string } class Playlist { -Base +UserID: string +Name: string +Description: string +IsPublic: bool +CoverURL: string } class PlaylistItem { -Base +PlaylistID: string +MediaID: string +Position: int } class DownloadClient { -Base +Name: string +Type: string +Host: string +Port: int +Username: string +Password: string +Enabled: bool +Category: string } class DownloadTask { -Base +ClientID: string +UserID: string +Source: string +URL: string +SavePath: string +InfoHash: string +Status: string +Progress: float32 +TotalSize: int64 +SpeedDown: int64 +SpeedUp: int64 +Message: string } class Site { -Base +Name: string +BaseURL: string +SiteType: string +AuthType: string +Cookie: string +APIKey: string +AuthHeader: string +UserAgent: string +RSSURL: string +TimeoutSec: int +Priority: int +UseProxy: bool +RateLimit: int +Enabled: bool +LoginStatus: string +UploadBytes: int64 +DownloadBytes: int64 } class Subscription { -Base +UserID: string +Name: string +FeedURL: string +Filter: string +Enabled: bool +LastRunAt: *time.Time +TMDbID: int +MediaType: string +Year: int +QualityFilter: []byte +MinSizeMB: int +MaxSizeMB: int +ExcludeKeys: string +IncludeKeys: string +TotalDownloaded: int +Status: string } class NotifyChannel { -Base +Name: string +ChannelType: string +Enabled: bool +Config: string +EncryptedConfig: string +Events: string } class ApiConfig { +ID: string +Provider: string +APIKey: string +BaseURL: string +Extra: string +Enabled: bool +Description: string +LastTestedAt: *time.Time +TestResult: string +UpdatedAt: time.Time } class STRMRecord { -Base +MediaID: string +URL: string +Protocol: string } class Setting { +Key: string +Value: string +UpdatedAt: time.Time } class AccessLog { -Base +UserID: string +Action: string +Target: string +IP: string +Detail: string } User "1" -- "1..1" --> UserPermission : has User "1" -- "*" --> RefreshToken : issues Library "1" -- "1..*" --> Media : contains Media "*..*" -- "..1" --> Series : belongs_to Media "1" -- "1..*" --> SubtitleTrack : has_tracks User "1" -- "0..*" --> PlaybackHistory : records User "1" -- "0..*" --> Favorite : marks User "1" -- "0..*" --> Playlist : owns Playlist "1" -- "1..*" --> PlaylistItem : contains DownloadClient "1" -- "0..*" --> DownloadTask : manages Site "1" -- "0..*" --> Subscription : feeds_into Subscription "1" -- "0..*" --> DownloadTask : queues Media "1" -- "0..1" --> STRMRecord : has_strm