diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-28 23:31:31 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-28 23:31:31 +0300 |
| commit | 5b5978dabf2011a64720998cd03cbb01c706475d (patch) | |
| tree | 80ad17b97cc6b6d8d614ca4f3bc34237e33f16a4 | |
| parent | d0d2b9f47b371afc94c2cdc58977e388ed30e224 (diff) | |
feat(k9): Define domain model structs (Media, User, Set, Tag, Session, Share, Note) in internal/model/ with zero external deps
| -rw-r--r-- | internal/model/media.go | 140 | ||||
| -rw-r--r-- | internal/model/media_test.go | 27 | ||||
| -rw-r--r-- | internal/model/model.go | 2 |
3 files changed, 167 insertions, 2 deletions
diff --git a/internal/model/media.go b/internal/model/media.go new file mode 100644 index 0000000..cae1c8d --- /dev/null +++ b/internal/model/media.go @@ -0,0 +1,140 @@ +// Package model defines the core domain entities. +package model + +import "time" + +// MediaType distinguishes between video and audio files. +type MediaType string + +const ( + MediaTypeVideo MediaType = "video" + MediaTypeAudio MediaType = "audio" +) + +// Role defines the level of access a user has to a set. +type Role string + +const ( + RoleOwner Role = "owner" + RoleViewer Role = "viewer" +) + +// User represents an application account. +type User struct { + ID int64 + Username string + PasswordHash string + IsAdmin bool + CreatedAt time.Time +} + +// Set represents a top-level media collection (a directory under MEDIA_ROOT). +type Set struct { + ID int64 + Name string + RootPath string + CoverThumbnailPath string + CreatedAt time.Time +} + +// SetPermission grants a user access to a set. +type SetPermission struct { + SetID int64 + UserID int64 + Role Role + CreatedAt time.Time +} + +// Media represents a single audio or video file within a set. +type Media struct { + ID int64 + SetID int64 + RelPath string + FileName string + AbsPath string + Type MediaType + Duration float64 + Codec string + Resolution string + Bitrate int + FileSizeBytes int64 + ThumbnailPath string + PlayCount int + DeletedAt *time.Time + CreatedAt time.Time +} + +// Tag is a label that can be attached to media items. +type Tag struct { + ID int64 + Name string +} + +// Session is an authenticated browser session. +type Session struct { + ID string + UserID int64 + ExpiresAt time.Time + CreatedAt time.Time +} + +// Share is a time-bounded public link to a media item. +type Share struct { + Token string + MediaID int64 + CreatedBy int64 + CreatedAt time.Time + ExpiresAt time.Time + MaxUses *int + UsedCount int +} + +// Note is a per-user, per-media text note. +type Note struct { + ID int64 + MediaID int64 + UserID int64 + Content string + CreatedAt time.Time + UpdatedAt time.Time +} + +// PlaybackProgress stores the last known playback position. +type PlaybackProgress struct { + UserID int64 + MediaID int64 + PositionSeconds float64 + UpdatedAt time.Time +} + +// PlaybackAccumulator tracks deltas for the 60-second playback counter rule. +type PlaybackAccumulator struct { + SessionID string + MediaID int64 + LastPosition float64 + AccumulatedSeconds float64 + Counted bool + UpdatedAt time.Time +} + +// Favorite records that a user has favorited a media item. +type Favorite struct { + UserID int64 + MediaID int64 + CreatedAt time.Time +} + +// MediaTag is the join table between media and tags. +type MediaTag struct { + MediaID int64 + TagID int64 +} + +// Metadata holds extracted file properties from ffprobe and os.Stat. +type Metadata struct { + Duration float64 + Codec string + Resolution string + Bitrate int + FileSizeBytes int64 +} diff --git a/internal/model/media_test.go b/internal/model/media_test.go new file mode 100644 index 0000000..49d877c --- /dev/null +++ b/internal/model/media_test.go @@ -0,0 +1,27 @@ +package model + +import ( + "testing" + "time" +) + +func TestStructsInstantiate(t *testing.T) { + now := time.Now() + _ = User{ID: 1, Username: "u", PasswordHash: "h", IsAdmin: true, CreatedAt: now} + _ = Set{ID: 1, Name: "s", RootPath: "/r", CreatedAt: now} + _ = Media{ID: 1, SetID: 1, RelPath: "r", FileName: "f", AbsPath: "a", Type: MediaTypeVideo, Duration: 1, DeletedAt: &now, CreatedAt: now} + _ = Tag{ID: 1, Name: "t"} + _ = Session{ID: "s", UserID: 1, ExpiresAt: now, CreatedAt: now} + max := 1 + _ = Share{Token: "t", MediaID: 1, CreatedBy: 1, CreatedAt: now, ExpiresAt: now, MaxUses: &max} + _ = Note{ID: 1, MediaID: 1, UserID: 1, Content: "c", CreatedAt: now, UpdatedAt: now} + _ = PlaybackProgress{UserID: 1, MediaID: 1, PositionSeconds: 1, UpdatedAt: now} + _ = PlaybackAccumulator{SessionID: "s", MediaID: 1, UpdatedAt: now} + _ = Favorite{UserID: 1, MediaID: 1, CreatedAt: now} + _ = MediaTag{MediaID: 1, TagID: 1} + _ = SetPermission{SetID: 1, UserID: 1, Role: RoleOwner, CreatedAt: now} + _ = Metadata{Duration: 1, Codec: "c", Resolution: "r", Bitrate: 1, FileSizeBytes: 1} + if MediaTypeVideo != "video" || MediaTypeAudio != "audio" || RoleOwner != "owner" || RoleViewer != "viewer" { + t.Fatal("constants mismatch") + } +} diff --git a/internal/model/model.go b/internal/model/model.go deleted file mode 100644 index 8061bf6..0000000 --- a/internal/model/model.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package model defines the core domain entities. -package model |
