feat: photo rename and viewer delete with confirmation

Backend:
- add PATCH /api/photos/:id to rename original_filename (uploader/owner/admin)
- emit photo.updated SSE event

Frontend:
- inline filename editor in the right panel
- viewer: back button (arrow_back) on the left, delete on the right
- delete confirmed via a modal dialog; filmstrip updates after deletion
This commit is contained in:
iu
2026-10-10 18:50:16 +03:00
parent 1f0e909ffd
commit aa396bdb85
9 changed files with 296 additions and 13 deletions
+18
View File
@@ -93,6 +93,24 @@ func (h *Handler) Get(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"photo": p})
}
type renameRequest struct {
OriginalFilename string `json:"original_filename" binding:"required"`
}
func (h *Handler) Rename(c *gin.Context) {
var req renameRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.RespondError(c, httpx.BadRequest("invalid_request", "original_filename is required"))
return
}
p, err := h.svc.Rename(c.Request.Context(), c.Param("id"), auth.UserID(c), req.OriginalFilename)
if err != nil {
httpx.RespondError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"photo": p})
}
func (h *Handler) Download(c *gin.Context) {
url, err := h.svc.Download(c.Request.Context(), c.Param("id"), auth.UserID(c))
if err != nil {
+34
View File
@@ -250,6 +250,40 @@ func (s *Service) Download(ctx context.Context, photoID, userID string) (string,
return url, nil
}
func (s *Service) Rename(ctx context.Context, photoID, userID, filename 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
}
_, 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 rename this photo")
}
name := strings.TrimSpace(filename)
if name == "" {
return nil, httpx.BadRequest("invalid_filename", "filename is required")
}
if len(name) > 255 {
name = name[:255]
}
if err := s.store.UpdateFilename(ctx, photoID, name); err != nil {
return nil, err
}
p.OriginalFilename = name
if err := s.attachURLs(ctx, p); err != nil {
return nil, err
}
s.hub.Publish(p.AlbumID, "photo.updated", p)
return p, nil
}
func (s *Service) Delete(ctx context.Context, photoID, userID string) error {
p, err := s.store.GetByID(ctx, photoID, userID)
if err != nil {
+11
View File
@@ -127,3 +127,14 @@ func (s *Store) SetCoverIfEmpty(ctx context.Context, albumID, photoID string) {
`UPDATE albums SET cover_photo_id = $2 WHERE id = $1 AND cover_photo_id IS NULL`,
albumID, photoID)
}
func (s *Store) UpdateFilename(ctx context.Context, id, filename string) error {
tag, err := s.pool.Exec(ctx, `UPDATE photos SET original_filename = $2 WHERE id = $1`, id, filename)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return ErrNotFound
}
return nil
}