This commit is contained in:
2026-10-07 01:43:28 +07:00
parent 494b3fc471
commit e0caa0b409
23 changed files with 5264 additions and 963 deletions
+26
View File
@@ -0,0 +1,26 @@
package app
import (
"errors"
"evening_detective_server/internal/services/playlist_service"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func playlistErrorStatus(err error, fallback string) error {
switch {
case errors.Is(err, playlist_service.ErrInvalidName),
errors.Is(err, playlist_service.ErrInvalidTrack):
return status.Error(codes.InvalidArgument, err.Error())
case errors.Is(err, playlist_service.ErrPlaylistNotFound),
errors.Is(err, playlist_service.ErrTrackNotFound),
errors.Is(err, playlist_service.ErrGameNotFound),
errors.Is(err, playlist_service.ErrScenarioNotFound):
return status.Error(codes.NotFound, err.Error())
case errors.Is(err, playlist_service.ErrPlaylistInUse):
return status.Error(codes.FailedPrecondition, err.Error())
default:
return status.Error(codes.Internal, fallback)
}
}
+161
View File
@@ -0,0 +1,161 @@
package app
import (
"context"
"evening_detective_server/internal/modules/roles"
proto "evening_detective_server/proto"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func canManagePlaylists(ctx context.Context) bool {
claims, err := claimsFromContext(ctx)
return err == nil && claims != nil &&
(roles.HasRole(claims, roles.Author) || roles.HasRole(claims, roles.Organizer))
}
func (s *server) GetPlaylists(ctx context.Context, _ *proto.GetPlaylistsReq) (*proto.GetPlaylistsRsp, error) {
if !canManagePlaylists(ctx) {
return nil, status.Error(codes.PermissionDenied, "permission denied")
}
items, err := s.playlistService.List(ctx)
if err != nil {
return nil, playlistErrorStatus(err, "не удалось получить плейлисты")
}
result := &proto.GetPlaylistsRsp{}
for _, item := range items {
result.Playlists = append(result.Playlists, mapPlaylist(item))
}
return result, nil
}
func (s *server) CreatePlaylist(ctx context.Context, req *proto.CreatePlaylistReq) (*proto.CreatePlaylistRsp, error) {
if !canManagePlaylists(ctx) {
return nil, status.Error(codes.PermissionDenied, "permission denied")
}
item, err := s.playlistService.Create(ctx, req.GetName())
if err != nil {
return nil, playlistErrorStatus(err, "не удалось создать плейлист")
}
return &proto.CreatePlaylistRsp{Playlist: mapPlaylist(item)}, nil
}
func (s *server) UpdatePlaylist(ctx context.Context, req *proto.UpdatePlaylistReq) (*proto.UpdatePlaylistRsp, error) {
if !canManagePlaylists(ctx) {
return nil, status.Error(codes.PermissionDenied, "permission denied")
}
item, err := s.playlistService.Update(ctx, int(req.GetID()), req.GetName())
if err != nil {
return nil, playlistErrorStatus(err, "не удалось изменить плейлист")
}
return &proto.UpdatePlaylistRsp{Playlist: mapPlaylist(item)}, nil
}
func (s *server) DeletePlaylist(ctx context.Context, req *proto.DeletePlaylistReq) (*proto.DeletePlaylistRsp, error) {
if !canManagePlaylists(ctx) {
return nil, status.Error(codes.PermissionDenied, "permission denied")
}
if err := s.playlistService.Delete(ctx, int(req.GetID())); err != nil {
return nil, playlistErrorStatus(err, "не удалось удалить плейлист")
}
return &proto.DeletePlaylistRsp{}, nil
}
func (s *server) UploadPlaylistTrack(ctx context.Context, req *proto.UploadPlaylistTrackReq) (*proto.UploadPlaylistTrackRsp, error) {
if !canManagePlaylists(ctx) {
return nil, status.Error(codes.PermissionDenied, "permission denied")
}
if err := s.playlistService.UploadTrack(
ctx,
int(req.GetPlaylistID()),
req.GetName(),
req.GetFilename(),
req.GetData(),
); err != nil {
return nil, playlistErrorStatus(err, "не удалось загрузить MP3")
}
item, err := s.playlistService.Get(ctx, int(req.GetPlaylistID()))
if err != nil {
return nil, playlistErrorStatus(err, "не удалось получить плейлист")
}
return &proto.UploadPlaylistTrackRsp{Playlist: mapPlaylist(item)}, nil
}
func (s *server) DeletePlaylistTrack(ctx context.Context, req *proto.DeletePlaylistTrackReq) (*proto.DeletePlaylistTrackRsp, error) {
if !canManagePlaylists(ctx) {
return nil, status.Error(codes.PermissionDenied, "permission denied")
}
if err := s.playlistService.DeleteTrack(
ctx,
int(req.GetPlaylistID()),
int(req.GetTrackID()),
); err != nil {
return nil, playlistErrorStatus(err, "не удалось удалить трек")
}
return &proto.DeletePlaylistTrackRsp{}, nil
}
func (s *server) SetScenarioPlaylist(ctx context.Context, req *proto.SetScenarioPlaylistReq) (*proto.SetScenarioPlaylistRsp, error) {
claims, err := claimsFromContext(ctx)
if err != nil {
return nil, err
}
if !roles.HasRole(claims, roles.Author) {
return nil, status.Error(codes.PermissionDenied, "permission denied")
}
if err := s.playlistService.SetScenario(
ctx,
int(req.GetID()),
int(req.GetPlaylistID()),
); err != nil {
return nil, playlistErrorStatus(err, "не удалось назначить плейлист сценарию")
}
return &proto.SetScenarioPlaylistRsp{}, nil
}
func (s *server) GetGameMusic(ctx context.Context, req *proto.GetGameMusicReq) (*proto.GetGameMusicRsp, error) {
if !canManageScreens(ctx) {
return nil, status.Error(codes.PermissionDenied, "permission denied")
}
state, err := s.playlistService.GetGame(ctx, int(req.GetGameID()))
if err != nil {
return nil, playlistErrorStatus(err, "не удалось получить музыку игры")
}
playlists, err := s.playlistService.List(ctx)
if err != nil {
return nil, playlistErrorStatus(err, "не удалось получить плейлисты")
}
result := &proto.GetGameMusicRsp{State: mapMusicState(state)}
for _, item := range playlists {
result.Playlists = append(result.Playlists, mapPlaylist(item))
}
if state.ScenarioID != nil {
result.ScenarioPlaylistId = int32(*state.ScenarioID)
}
if state.GameID != nil {
result.GamePlaylistId = int32(*state.GameID)
}
return result, nil
}
func (s *server) SetGameMusic(ctx context.Context, req *proto.SetGameMusicReq) (*proto.SetGameMusicRsp, error) {
if !canManageScreens(ctx) {
return nil, status.Error(codes.PermissionDenied, "permission denied")
}
state, err := s.playlistService.SetGame(
ctx,
int(req.GetGameID()),
int(req.GetPlaylistID()),
int(req.GetTrackIndex()),
req.GetPositionMs(),
req.GetVolume(),
req.GetLoop(),
int32(req.GetPlayback()),
)
if err != nil {
return nil, playlistErrorStatus(err, "не удалось изменить музыку игры")
}
return &proto.SetGameMusicRsp{State: mapMusicState(state)}, nil
}
+40
View File
@@ -0,0 +1,40 @@
package app
import (
"evening_detective_server/internal/services/playlist_service"
proto "evening_detective_server/proto"
)
func mapPlaylist(value *playlist_service.Playlist) *proto.Playlist {
if value == nil {
return nil
}
result := &proto.Playlist{
ID: int32(value.ID),
Name: value.Name,
}
for _, track := range value.Tracks {
result.Tracks = append(result.Tracks, &proto.PlaylistTrack{
ID: int32(track.ID),
Name: track.Name,
Filename: track.Filename,
Position: int32(track.Position),
})
}
return result
}
func mapMusicState(value *playlist_service.MusicState) *proto.MusicState {
if value == nil {
return nil
}
return &proto.MusicState{
Playlist: mapPlaylist(value.Playlist),
TrackIndex: int32(value.TrackIndex),
PositionMs: value.PositionMs,
Volume: value.Volume,
Loop: value.Loop,
Playback: proto.MusicPlaybackState(value.Playback),
StateVersion: value.Version,
}
}
+41
View File
@@ -49,6 +49,42 @@ func (s *server) GetScreenState(ctx context.Context, req *proto.GetScreenStateRe
return &proto.GetScreenStateRsp{State: state}, nil
}
func (s *server) AdvanceScreenMusic(ctx context.Context, req *proto.AdvanceScreenMusicReq) (*proto.AdvanceScreenMusicRsp, error) {
if req == nil || req.ScreenID == "" || req.AccessToken == "" {
return nil, status.Error(codes.InvalidArgument, "некорректные данные экрана")
}
id, err := strconv.Atoi(req.ScreenID)
if err != nil || id <= 0 {
return nil, status.Error(codes.InvalidArgument, "некорректный идентификатор экрана")
}
session, err := s.screenService.Get(ctx, id, hashScreenToken(req.AccessToken))
if errors.Is(err, screen_service.ErrScreenNotFound) {
return nil, status.Error(codes.Unauthenticated, "недействительная сессия экрана")
}
if err != nil || session.GameID == nil {
return nil, status.Error(codes.FailedPrecondition, "экран не привязан к игре")
}
state, err := s.playlistService.GetGame(ctx, *session.GameID)
if err != nil || state.Playlist == nil || len(state.Playlist.Tracks) == 0 {
return &proto.AdvanceScreenMusicRsp{State: mapMusicState(state)}, nil
}
next := state.TrackIndex + 1
playback := state.Playback
if next >= len(state.Playlist.Tracks) {
if state.Loop {
next = 0
} else {
next = len(state.Playlist.Tracks) - 1
playback = 1
}
}
updated, err := s.playlistService.SetGame(ctx, *session.GameID, state.Playlist.ID, next, 0, state.Volume, state.Loop, playback)
if err != nil {
return nil, status.Error(codes.Internal, "не удалось переключить трек")
}
return &proto.AdvanceScreenMusicRsp{State: mapMusicState(updated)}, nil
}
func (s *server) GetGameScreens(ctx context.Context, req *proto.GetGameScreensReq) (*proto.GetGameScreensRsp, error) {
if !canManageScreens(ctx) {
return nil, status.Error(codes.PermissionDenied, "permission denied")
@@ -201,6 +237,11 @@ func (s *server) enrichScreenState(ctx context.Context, session *screen_service.
if session.GameID == nil {
return
}
if s.playlistService != nil {
if music, err := s.playlistService.GetGame(ctx, *session.GameID); err == nil {
state.Music = mapMusicState(music)
}
}
game, err := s.gameService.GetGameMedia(ctx, *session.GameID)
if err != nil {
return
+4
View File
@@ -8,6 +8,7 @@ import (
"evening_detective_server/internal/services/gameplay_service"
"evening_detective_server/internal/services/map_service"
"evening_detective_server/internal/services/participation_service"
"evening_detective_server/internal/services/playlist_service"
"evening_detective_server/internal/services/scenarios_service"
"evening_detective_server/internal/services/screen_plan_service"
"evening_detective_server/internal/services/screen_service"
@@ -33,6 +34,7 @@ type server struct {
ticketService *ticket_service.TicketService
participationService *participation_service.ParticipationService
screenService *screen_service.ScreenService
playlistService *playlist_service.Service
screenPlanService *screen_plan_service.ScreenPlanService
mapService *map_service.MapService
}
@@ -51,6 +53,7 @@ func NewServer(
ticketService *ticket_service.TicketService,
participationService *participation_service.ParticipationService,
screenService *screen_service.ScreenService,
playlistService *playlist_service.Service,
screenPlanService *screen_plan_service.ScreenPlanService,
mapService *map_service.MapService,
) proto.EveningDetectiveServerServer {
@@ -67,6 +70,7 @@ func NewServer(
ticketService: ticketService,
participationService: participationService,
screenService: screenService,
playlistService: playlistService,
screenPlanService: screenPlanService,
mapService: mapService,
}
+19 -13
View File
@@ -27,20 +27,26 @@ func mapScenario(o *scenarios_service.Scenario) *proto.Scenario {
if o.PublishedAt != nil {
publishedAt = timestamppb.New(*o.PublishedAt)
}
var playlistID *int32
if o.PlaylistID != nil {
value := int32(*o.PlaylistID)
playlistID = &value
}
return &proto.Scenario{
ID: int32(o.ID),
Name: o.Name,
Description: o.Description,
Image: o.Image,
Story: mapStory(o.Story),
Author: mapUser(o.Author),
Status: o.Status,
UpdatedAt: timestamppb.New(o.UpdatedAt),
CreatedAt: timestamppb.New(o.CreatedAt),
PublishedAt: publishedAt,
AverageRating: o.AverageRating,
ReviewsCount: o.ReviewsCount,
Difficulty: o.Difficulty,
ID: int32(o.ID),
Name: o.Name,
Description: o.Description,
Image: o.Image,
Story: mapStory(o.Story),
Author: mapUser(o.Author),
Status: o.Status,
UpdatedAt: timestamppb.New(o.UpdatedAt),
CreatedAt: timestamppb.New(o.CreatedAt),
PublishedAt: publishedAt,
AverageRating: o.AverageRating,
ReviewsCount: o.ReviewsCount,
Difficulty: o.Difficulty,
MusicPlaylistId: playlistID,
}
}