package photos import ( "context" "encoding/json" "errors" "galo/internal/httpx" "galo/internal/models" "galo/internal/roles" ) // editablePhoto loads a photo and checks that the user may change its // editing parameters: the uploader of the photo or an album owner/admin. func (s *Service) editablePhoto(ctx context.Context, photoID, userID string) (*models.Photo, error) { p, err := s.store.GetByID(ctx, photoID, userID) if err != nil { if errors.Is(err, ErrNotFound) { return nil, httpx.NotFound("photo_not_found", "photo not found") } return nil, err } if p.DeletedAt != nil { return nil, httpx.NotFound("photo_not_found", "photo not found") } _, role, err := s.albums.Access(ctx, p.AlbumID, userID) if err != nil { return nil, err } own := p.UploaderID == userID if !(roles.CanDeleteAnyPhoto(role) || (own && roles.CanDeleteOwnPhoto(role))) { return nil, httpx.Forbidden("forbidden", "you cannot edit this photo") } return p, nil } // EditPhoto stores new editing parameters for a photo and schedules the // render of the edited version. The original object is never modified, so the // edit can always be reset. func (s *Service) EditPhoto(ctx context.Context, photoID, userID string, params EditParams) (*models.Photo, error) { p, err := s.editablePhoto(ctx, photoID, userID) if err != nil { return nil, err } if p.Kind != KindImage { return nil, httpx.BadRequest("invalid_request", "only images can be edited") } params = params.Normalized() if params.IsEmpty() { return s.ResetEdit(ctx, photoID, userID) } if err := s.store.SetEditParams(ctx, photoID, params.JSON()); err != nil { return nil, err } if err := s.store.EnqueueEditJob(ctx, photoID); err != nil { return nil, err } // Рендер начинается сразу, а не в конце цикла воркера. if s.wakeEdit != nil { s.wakeEdit() } return s.publishEdit(ctx, photoID, userID) } // ResetEdit removes the editing parameters of a photo together with the // edited version, returning the photo to its original look. func (s *Service) ResetEdit(ctx context.Context, photoID, userID string) (*models.Photo, error) { if _, err := s.editablePhoto(ctx, photoID, userID); err != nil { return nil, err } if err := s.store.ClearEditParams(ctx, photoID); err != nil { return nil, err } if err := s.store.DeleteEditJob(ctx, photoID); err != nil { return nil, err } if err := s.store.DeleteObjectBindings(ctx, photoID, EditRoles()...); err != nil { return nil, err } return s.publishEdit(ctx, photoID, userID) } // publishEdit reloads a photo with its current editing state and notifies the // album subscribers. func (s *Service) publishEdit(ctx context.Context, photoID, userID string) (*models.Photo, error) { p, err := s.store.GetByID(ctx, photoID, userID) if err != nil { if errors.Is(err, ErrNotFound) { return nil, httpx.NotFound("photo_not_found", "photo not found") } return nil, err } if err := s.attachURLs(ctx, p); err != nil { return nil, err } s.hub.Publish(p.AlbumID, "photo.updated", p) return p, nil } // EditParamsOf returns the editing parameters stored on a photo. func EditParamsOf(p *models.Photo) EditParams { if p == nil || len(p.EditParams) == 0 { return EditParams{} } data, err := json.Marshal(p.EditParams) if err != nil { return EditParams{} } params, err := ParseEditParams(data) if err != nil { return EditParams{} } return params }