diff --git a/backend/internal/routes/album.go b/backend/internal/routes/album.go index d3a58d8..d2a9829 100644 --- a/backend/internal/routes/album.go +++ b/backend/internal/routes/album.go @@ -25,6 +25,22 @@ func RegisterAlbumRoutes(rg *gin.RouterGroup) { } c.JSON(http.StatusOK, albums) }) + album.POST("/getAlbum", func(c *gin.Context) { + accessToken := c.GetHeader("Authorization") + var request struct { + ID string `json:"id"` + } + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + album, err := services.GetAlbum(request.ID, accessToken) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, album) + }) album.POST("/createAlbum", func(c *gin.Context) { accessToken := c.GetHeader("Authorization") if accessToken == "" { @@ -47,6 +63,28 @@ func RegisterAlbumRoutes(rg *gin.RouterGroup) { } c.JSON(http.StatusOK, result) }) + album.POST("/editAlbum", func(c *gin.Context) { + accessToken := c.GetHeader("Authorization") + if accessToken == "" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + return + } + var request struct { + ID string `json:"id"` + Properties map[string]interface{} `json:"properties"` + } + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + result, err := services.EditAlbum(accessToken, request.ID, request.Properties) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, result) + }) + album.POST("/getIDFromPath", func(c *gin.Context) { var request struct { Path string `json:"path"` diff --git a/backend/internal/services/album.go b/backend/internal/services/album.go index 9e7658d..c730926 100644 --- a/backend/internal/services/album.go +++ b/backend/internal/services/album.go @@ -50,7 +50,20 @@ func GetAlbumsInParent(parentID string, authToken string) ([]models.Album, error } func GetAlbum(id string, authToken string) (models.Album, error) { - // TODO: Add authentication + userID, err := ValidateAccessToken(authToken) + if err != nil { + return models.Album{}, err + } + if userID == "" { + return models.Album{}, fmt.Errorf("invalid access token") + } + accessLevel, err := CheckUserAlbumAccess(userID, id) + if err != nil { + return models.Album{}, err + } + if accessLevel < 0 { + return models.Album{}, fmt.Errorf("user does not have permission to view this album") + } album := models.Album{} result := db.GetDB().First(&album, "id = ?", id) if result.Error != nil { @@ -151,3 +164,45 @@ func IsAlbumPublic(albumID string) (bool, error) { } return !album.Private, nil } + +func EditAlbum(accessToken string, id string, properties map[string]interface{}) (models.Album, error) { + userID, err := ValidateAccessToken(accessToken) + if err != nil { + return models.Album{}, err + } + if userID == "" { + return models.Album{}, fmt.Errorf("invalid access token") + } + accessLevel, err := CheckUserAlbumAccess(userID, id) + if err != nil { + return models.Album{}, err + } + if accessLevel < 2 { + return models.Album{}, fmt.Errorf("user does not have permission to edit this album") + } + if properties["id"] != nil { + return models.Album{}, fmt.Errorf("cannot edit album ID") + } + if properties["private"] != nil { + return models.Album{}, fmt.Errorf("cannot edit album private status directly (use the dedicated endpoint for this)") + } + if properties["parent_id"] != nil { + return models.Album{}, fmt.Errorf("cannot edit album parent ID directly (use the dedicated endpoint for this)") + } + if properties["updated_at"] != nil { + return models.Album{}, fmt.Errorf("cannot edit album updatedAt") + } + if properties["created_at"] != nil { + return models.Album{}, fmt.Errorf("cannot edit album createdAt") + } + + album := models.Album{} + result := db.GetDB().Model(&album).Where("id = ?", id).Updates(properties).First(&album) + if result.Error != nil { + return models.Album{}, result.Error + } + if result.RowsAffected == 0 { + return models.Album{}, fmt.Errorf("failed to update album") + } + return album, nil +} diff --git a/frontend/src/gallery/components/AlbumEditModal.jsx b/frontend/src/gallery/components/AlbumEditModal.jsx new file mode 100644 index 0000000..c3c8ecf --- /dev/null +++ b/frontend/src/gallery/components/AlbumEditModal.jsx @@ -0,0 +1,53 @@ +import Modal from '../../components/Modal' +import { getServerUrl } from '../../hooks/getConstants' +import { useAccount } from '../../contexts/useAccount' +import { useState, useEffect } from 'react' +import { useNotifier } from '../../contexts/useNotifier' +import FilePicker from './FilePicker' +export default function AlbumEditModal({ open, onOpenChange, trigger, id, startTitle, startDescription, currentAlbum }) { + const { getAccessToken } = useAccount() + const [title, setTitle] = useState(startTitle || '') + const [description, setDescription] = useState(startDescription || '') + const { showError } = useNotifier() + const handleEditAlbum = async () => { + const response = await fetch(`${getServerUrl()}/api/albums/editAlbum`, { + method: 'POST', + headers: { + 'Authorization': getAccessToken(), + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + id: id, + properties: { + title: title, + description: description, + } + }) + }) + const data = await response.json() + if (data.error) { + showError(data.error) + } else { + onOpenChange(false) + } + } + useEffect(() => { + if (open) { + setTitle(startTitle || '') + setDescription(startDescription || '') + } + }, [open]) + return ( + +
+

Name

+ setTitle(e.target.value)} /> +

Description

+