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 Description
{album.Title}
+{selectedFile?.name || 'No image selected'}
+{currentPath.join(' / ')}
+{album.Title}
+No media found
+ )} + {media.length > 0 && ( +{m.Title}
+