generated from VLADIMIR/template
up
This commit is contained in:
@@ -146,3 +146,8 @@ func (s *FileService) DownloadFile(
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// DeleteFile removes a stored file.
|
||||
func (s *FileService) DeleteFile(ctx context.Context, filename string) error {
|
||||
return s.fileStorage.Delete(ctx, filename)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package playlist_service
|
||||
|
||||
import "evening_detective_server/internal/repos/playlists_repo"
|
||||
|
||||
func mapPlaylist(value *playlists_repo.Playlist) *Playlist {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
result := &Playlist{ID: value.ID, Name: value.Name}
|
||||
for _, track := range value.Tracks {
|
||||
result.Tracks = append(result.Tracks, &Track{
|
||||
ID: track.ID,
|
||||
Name: track.Name,
|
||||
Filename: track.Filename,
|
||||
Position: track.Position,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func mapMusicState(value *playlists_repo.MusicState) *MusicState {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
return &MusicState{
|
||||
Playlist: mapPlaylist(value.Playlist),
|
||||
TrackIndex: value.TrackIndex,
|
||||
PositionMs: value.PositionMs,
|
||||
Volume: value.Volume,
|
||||
Loop: value.Loop,
|
||||
Playback: value.Playback,
|
||||
Version: value.Version,
|
||||
StartedAt: value.StartedAt,
|
||||
ScenarioID: value.ScenarioID,
|
||||
GameID: value.GameID,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package playlist_service
|
||||
|
||||
import "time"
|
||||
|
||||
// Track describes an MP3 track in a playlist.
|
||||
type Track struct {
|
||||
ID int
|
||||
Name string
|
||||
Filename string
|
||||
Position int
|
||||
}
|
||||
|
||||
// Playlist describes a reusable playlist.
|
||||
type Playlist struct {
|
||||
ID int
|
||||
Name string
|
||||
Tracks []*Track
|
||||
}
|
||||
|
||||
// MusicState describes the synchronized music state of a game.
|
||||
type MusicState struct {
|
||||
Playlist *Playlist
|
||||
TrackIndex int
|
||||
PositionMs int64
|
||||
Volume int32
|
||||
Loop bool
|
||||
Playback int32
|
||||
Version int64
|
||||
StartedAt *time.Time
|
||||
ScenarioID *int
|
||||
GameID *int
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package playlist_service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"evening_detective_server/internal/modules/file_storage"
|
||||
"evening_detective_server/internal/repos/playlists_repo"
|
||||
"evening_detective_server/internal/services/file_service"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidName = errors.New("некорректное имя плейлиста")
|
||||
ErrInvalidTrack = errors.New("некорректный трек")
|
||||
ErrPlaylistNotFound = errors.New("плейлист не найден")
|
||||
ErrPlaylistInUse = errors.New("плейлист используется")
|
||||
ErrTrackNotFound = errors.New("трек не найден")
|
||||
ErrGameNotFound = errors.New("игра не найдена")
|
||||
ErrScenarioNotFound = errors.New("сценарий не найден")
|
||||
)
|
||||
|
||||
// Service implements playlist and game music use cases.
|
||||
type Service struct {
|
||||
repo *playlists_repo.Repo
|
||||
files *file_service.FileService
|
||||
}
|
||||
|
||||
// New creates a playlist service.
|
||||
func New(repo *playlists_repo.Repo, files *file_service.FileService) *Service {
|
||||
return &Service{repo: repo, files: files}
|
||||
}
|
||||
|
||||
// List returns all playlists.
|
||||
func (s *Service) List(ctx context.Context) ([]*Playlist, error) {
|
||||
items, err := s.repo.List(ctx)
|
||||
if err != nil {
|
||||
return nil, mapError(err)
|
||||
}
|
||||
result := make([]*Playlist, 0, len(items))
|
||||
for _, item := range items {
|
||||
result = append(result, mapPlaylist(item))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Get returns a playlist by ID.
|
||||
func (s *Service) Get(ctx context.Context, id int) (*Playlist, error) {
|
||||
item, err := s.repo.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, mapError(err)
|
||||
}
|
||||
return mapPlaylist(item), nil
|
||||
}
|
||||
|
||||
// Create validates and creates a playlist.
|
||||
func (s *Service) Create(ctx context.Context, name string) (*Playlist, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" || len(name) > 255 {
|
||||
return nil, ErrInvalidName
|
||||
}
|
||||
item, err := s.repo.Create(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mapPlaylist(item), nil
|
||||
}
|
||||
|
||||
// Update validates and updates a playlist.
|
||||
func (s *Service) Update(ctx context.Context, id int, name string) (*Playlist, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
if id <= 0 || name == "" || len(name) > 255 {
|
||||
return nil, ErrInvalidName
|
||||
}
|
||||
item, err := s.repo.Update(ctx, id, name)
|
||||
if err != nil {
|
||||
return nil, mapError(err)
|
||||
}
|
||||
return mapPlaylist(item), nil
|
||||
}
|
||||
|
||||
// Delete removes a playlist.
|
||||
func (s *Service) Delete(ctx context.Context, id int) error {
|
||||
return mapError(s.repo.Delete(ctx, id))
|
||||
}
|
||||
|
||||
// UploadTrack stores an MP3 file and adds it to a playlist.
|
||||
func (s *Service) UploadTrack(ctx context.Context, playlistID int, name, filename string, data []byte) error {
|
||||
if playlistID <= 0 || strings.TrimSpace(name) == "" || len(data) == 0 || !strings.HasSuffix(strings.ToLower(filename), ".mp3") {
|
||||
return ErrInvalidTrack
|
||||
}
|
||||
if _, err := s.repo.Get(ctx, playlistID); err != nil {
|
||||
return mapError(err)
|
||||
}
|
||||
stored, fileType, err := s.files.UploadFile(ctx, filename, data)
|
||||
if err != nil || fileType != "audio" || !strings.HasSuffix(strings.ToLower(stored), ".mp3") {
|
||||
return ErrInvalidTrack
|
||||
}
|
||||
if err := s.repo.AddTrack(ctx, playlistID, strings.TrimSpace(name), stored); err != nil {
|
||||
_ = s.files.DeleteFile(ctx, stored)
|
||||
return mapError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteTrack removes a track and its stored file.
|
||||
func (s *Service) DeleteTrack(ctx context.Context, playlistID, trackID int) error {
|
||||
filename, err := s.repo.DeleteTrack(ctx, playlistID, trackID)
|
||||
if err != nil {
|
||||
return mapError(err)
|
||||
}
|
||||
if err := s.files.DeleteFile(ctx, filename); err != nil && !errors.Is(err, file_storage.ErrFileNotFound) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetScenario assigns a playlist to a scenario.
|
||||
func (s *Service) SetScenario(ctx context.Context, scenarioID, playlistID int) error {
|
||||
return mapError(s.repo.SetScenario(ctx, scenarioID, playlistID))
|
||||
}
|
||||
|
||||
// SetGame updates the music state for a game.
|
||||
func (s *Service) SetGame(
|
||||
ctx context.Context,
|
||||
gameID, playlistID, trackIndex int,
|
||||
positionMs int64,
|
||||
volume int32,
|
||||
loop bool,
|
||||
playback int32,
|
||||
) (*MusicState, error) {
|
||||
if trackIndex < 0 || positionMs < 0 || volume < 0 || volume > 100 || playback < 1 || playback > 3 {
|
||||
return nil, ErrInvalidTrack
|
||||
}
|
||||
state, err := s.repo.SetMusic(ctx, gameID, playlistID, trackIndex, positionMs, volume, loop, playback)
|
||||
if err != nil {
|
||||
return nil, mapError(err)
|
||||
}
|
||||
return mapMusicState(state), nil
|
||||
}
|
||||
|
||||
// GetGame returns the music state for a game.
|
||||
func (s *Service) GetGame(ctx context.Context, gameID int) (*MusicState, error) {
|
||||
state, err := s.repo.GetMusic(ctx, gameID)
|
||||
if err != nil {
|
||||
return nil, mapError(err)
|
||||
}
|
||||
return mapMusicState(state), nil
|
||||
}
|
||||
|
||||
func mapError(err error) error {
|
||||
switch {
|
||||
case errors.Is(err, playlists_repo.ErrPlaylistNotFound):
|
||||
return ErrPlaylistNotFound
|
||||
case errors.Is(err, playlists_repo.ErrPlaylistInUse):
|
||||
return ErrPlaylistInUse
|
||||
case errors.Is(err, playlists_repo.ErrTrackNotFound):
|
||||
return ErrTrackNotFound
|
||||
case errors.Is(err, playlists_repo.ErrGameNotFound):
|
||||
return ErrGameNotFound
|
||||
case errors.Is(err, playlists_repo.ErrScenarioNotFound):
|
||||
return ErrScenarioNotFound
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ type Scenario struct {
|
||||
Name string
|
||||
Description *string
|
||||
Image *string
|
||||
PlaylistID *int
|
||||
Story *storytelling.Story
|
||||
Author *users_service.User
|
||||
Status string
|
||||
|
||||
@@ -34,6 +34,7 @@ func MapScenarioLight(
|
||||
Name: o.Name,
|
||||
Description: o.Description,
|
||||
Image: mapImage(o.Image, domain),
|
||||
PlaylistID: o.PlaylistID,
|
||||
Story: nil,
|
||||
Author: mapAuthor(o.Author),
|
||||
Status: o.Status,
|
||||
|
||||
Reference in New Issue
Block a user