diff --git a/frontend/src/component/p-album-toolbar.vue b/frontend/src/component/p-album-toolbar.vue
index 6b5a0c70d..dabc26267 100644
--- a/frontend/src/component/p-album-toolbar.vue
+++ b/frontend/src/component/p-album-toolbar.vue
@@ -39,7 +39,7 @@
view_column
-
+
{{ searchExpanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}
@@ -48,7 +48,7 @@
flat
color="secondary-light"
v-show="searchExpanded">
-
+
+
+
+
+
@@ -146,6 +157,7 @@
],
},
labels: {
+ description: this.$gettext("Description"),
search: this.$gettext("Search"),
view: this.$gettext("View"),
country: this.$gettext("Country"),
@@ -154,15 +166,28 @@
name: this.$gettext("Album Name"),
},
titleRule: v => v.length <= 25 || this.$gettext("Title too long"),
+ growDesc: false,
};
},
methods: {
+ expand() {
+ this.searchExpanded = !this.searchExpanded;
+ this.growDesc = !this.growDesc;
+ },
+ updateAlbum() {
+ this.album.update()
+ },
dropdownChange() {
this.filterChange();
if (window.innerWidth < 600) {
this.searchExpanded = false;
}
+
+ if (this.filter.order !== this.album.AlbumOrder) {
+ this.album.AlbumOrder = this.filter.order;
+ this.updateAlbum()
+ }
},
setView(name) {
this.settings.view = name;
diff --git a/frontend/src/pages/album/photos.vue b/frontend/src/pages/album/photos.vue
index a7bb19c97..61a657f4b 100644
--- a/frontend/src/pages/album/photos.vue
+++ b/frontend/src/pages/album/photos.vue
@@ -271,9 +271,12 @@
});
},
findAlbum() {
- this.model.find(this.uuid).then(m => {
+ return this.model.find(this.uuid).then(m => {
this.model = m;
+ this.filter.order = m.AlbumOrder;
window.document.title = `PhotoPrism: ${this.model.AlbumName}`;
+
+ return Promise.resolve(this.model)
});
},
onAlbumsUpdated(ev, data) {
@@ -347,8 +350,7 @@
},
},
created() {
- this.findAlbum();
- this.search();
+ this.findAlbum().then(() => this.search());
this.subscriptions.push(Event.subscribe("albums.updated", (ev, data) => this.onAlbumsUpdated(ev, data)));
this.subscriptions.push(Event.subscribe("photos", (ev, data) => this.onUpdate(ev, data)));
diff --git a/internal/api/account.go b/internal/api/account.go
index 4b73bfd0b..260b00351 100644
--- a/internal/api/account.go
+++ b/internal/api/account.go
@@ -214,19 +214,22 @@ func UpdateAccount(router *gin.RouterGroup, conf *config.Config) {
f, err := form.NewAccount(m)
if err != nil {
- c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": txt.UcFirst(err.Error())})
+ log.Error(err)
+ c.AbortWithStatusJSON(http.StatusInternalServerError, ErrSaveFailed)
return
}
// 2) Update form with values from request
if err := c.BindJSON(&f); err != nil {
- c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": txt.UcFirst(err.Error())})
+ log.Error(err)
+ c.AbortWithStatusJSON(http.StatusBadRequest, ErrFormInvalid)
return
}
// 3) Save model with values from form
if err := m.Save(f, conf.Db()); err != nil {
- c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": txt.UcFirst(err.Error())})
+ log.Error(err)
+ c.AbortWithStatusJSON(http.StatusInternalServerError, ErrSaveFailed)
return
}
diff --git a/internal/api/album.go b/internal/api/album.go
index 0d22cdc95..3efc263ea 100644
--- a/internal/api/album.go
+++ b/internal/api/album.go
@@ -117,30 +117,41 @@ func UpdateAlbum(router *gin.RouterGroup, conf *config.Config) {
return
}
- var f form.Album
+ db := conf.Db()
+ uuid := c.Param("uuid")
+ q := query.New(db)
- if err := c.BindJSON(&f); err != nil {
- c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": txt.UcFirst(err.Error())})
- return
- }
-
- id := c.Param("uuid")
- q := query.New(conf.Db())
-
- m, err := q.AlbumByUUID(id)
+ m, err := q.AlbumByUUID(uuid)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, ErrAlbumNotFound)
return
}
- m.Rename(f.AlbumName)
- conf.Db().Save(&m)
+ f, err := form.NewAlbum(m)
+
+ if err != nil {
+ log.Error(err)
+ c.AbortWithStatusJSON(http.StatusInternalServerError, ErrSaveFailed)
+ return
+ }
+
+ if err := c.BindJSON(&f); err != nil {
+ log.Error(err)
+ c.AbortWithStatusJSON(http.StatusBadRequest, ErrFormInvalid)
+ return
+ }
+
+ if err := m.Save(f, conf.Db()); err != nil {
+ log.Error(err)
+ c.AbortWithStatusJSON(http.StatusInternalServerError, ErrSaveFailed)
+ return
+ }
event.Publish("config.updated", event.Data(conf.ClientConfig()))
event.Success("album saved")
- PublishAlbumEvent(EntityUpdated, id, c, q)
+ PublishAlbumEvent(EntityUpdated, uuid, c, q)
c.JSON(http.StatusOK, m)
})
diff --git a/internal/api/errors.go b/internal/api/errors.go
index 5a0bfef8e..90b83e4e8 100644
--- a/internal/api/errors.go
+++ b/internal/api/errors.go
@@ -18,5 +18,7 @@ var (
ErrPhotoNotFound = gin.H{"code": http.StatusNotFound, "error": "Photo not found"}
ErrLabelNotFound = gin.H{"code": http.StatusNotFound, "error": "Label not found"}
ErrUnexpectedError = gin.H{"code": http.StatusInternalServerError, "error": "Unexpected error"}
- ErrSaveFailed = gin.H{"code": http.StatusInternalServerError, "error": "Save failed - database error?"}
+ ErrSaveFailed = gin.H{"code": http.StatusInternalServerError, "error": "Changes could not be saved"}
+ ErrFormInvalid = gin.H{"code": http.StatusBadRequest, "error": "Changes could not be saved"}
+ ErrFeatureDisabled = gin.H{"code": http.StatusForbidden, "error": "Feature disabled"}
)
diff --git a/internal/api/import.go b/internal/api/import.go
index d78e88db8..c71d67d35 100644
--- a/internal/api/import.go
+++ b/internal/api/import.go
@@ -21,13 +21,13 @@ import (
// POST /api/v1/import*
func StartImport(router *gin.RouterGroup, conf *config.Config) {
router.POST("/import/*path", func(c *gin.Context) {
- if conf.ReadOnly() || !conf.Settings().Features.Import {
- c.AbortWithStatusJSON(http.StatusForbidden, ErrReadOnly)
+ if Unauthorized(c, conf) {
+ c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
return
}
- if Unauthorized(c, conf) {
- c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
+ if conf.ReadOnly() || !conf.Settings().Features.Import {
+ c.AbortWithStatusJSON(http.StatusForbidden, ErrFeatureDisabled)
return
}
diff --git a/internal/api/photo.go b/internal/api/photo.go
index 9b43665bd..198af6384 100644
--- a/internal/api/photo.go
+++ b/internal/api/photo.go
@@ -12,7 +12,6 @@ import (
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/pkg/fs"
- "github.com/photoprism/photoprism/pkg/txt"
)
// GET /api/v1/photos/:uuid
@@ -46,8 +45,9 @@ func UpdatePhoto(router *gin.RouterGroup, conf *config.Config) {
return
}
+ db := conf.Db()
uuid := c.Param("uuid")
- q := query.New(conf.Db())
+ q := query.New(db)
m, err := q.PhotoByUUID(uuid)
@@ -61,19 +61,22 @@ func UpdatePhoto(router *gin.RouterGroup, conf *config.Config) {
f, err := form.NewPhoto(m)
if err != nil {
- c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": txt.UcFirst(err.Error())})
+ log.Error(err)
+ c.AbortWithStatusJSON(http.StatusInternalServerError, ErrSaveFailed)
return
}
// 2) Update form with values from request
if err := c.BindJSON(&f); err != nil {
- c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": txt.UcFirst(err.Error())})
+ log.Error(err)
+ c.AbortWithStatusJSON(http.StatusBadRequest, ErrFormInvalid)
return
}
// 3) Save model with values from form
- if err := entity.SavePhotoForm(m, f, conf.Db(), conf.GeoCodingApi()); err != nil {
- c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": txt.UcFirst(err.Error())})
+ if err := entity.SavePhotoForm(m, f, db, conf.GeoCodingApi()); err != nil {
+ log.Error(err)
+ c.AbortWithStatusJSON(http.StatusInternalServerError, ErrSaveFailed)
return
}
diff --git a/internal/api/zip.go b/internal/api/zip.go
index 9a64afbb0..f96f85b15 100644
--- a/internal/api/zip.go
+++ b/internal/api/zip.go
@@ -28,6 +28,11 @@ func CreateZip(router *gin.RouterGroup, conf *config.Config) {
return
}
+ if !conf.Settings().Features.Download {
+ c.AbortWithStatusJSON(http.StatusForbidden, ErrFeatureDisabled)
+ return
+ }
+
var f form.Selection
start := time.Now()
diff --git a/internal/entity/album.go b/internal/entity/album.go
index 02361cca6..5d873b34a 100644
--- a/internal/entity/album.go
+++ b/internal/entity/album.go
@@ -6,7 +6,9 @@ import (
"github.com/gosimple/slug"
"github.com/jinzhu/gorm"
+ "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/pkg/rnd"
+ "github.com/ulule/deepcopier"
)
// Album represents a photo album
@@ -63,3 +65,16 @@ func (m *Album) Rename(albumName string) {
m.AlbumName = strings.TrimSpace(albumName)
m.AlbumSlug = slug.Make(m.AlbumName)
}
+
+// Save updates the entity using form data and stores it in the database.
+func (m *Album) Save(f form.Album, db *gorm.DB) error {
+ if err := deepcopier.Copy(m).From(f); err != nil {
+ return err
+ }
+
+ if f.AlbumName != "" {
+ m.Rename(f.AlbumName)
+ }
+
+ return db.Save(m).Error
+}
diff --git a/internal/form/album.go b/internal/form/album.go
index 1ede2b0a4..7849ebd5d 100644
--- a/internal/form/album.go
+++ b/internal/form/album.go
@@ -1,12 +1,19 @@
package form
+import "github.com/ulule/deepcopier"
+
// Album represents an album edit form.
type Album struct {
AlbumName string `json:"AlbumName"`
AlbumDescription string `json:"AlbumDescription"`
AlbumNotes string `json:"AlbumNotes"`
- AlbumFavorite bool `json:"AlbumFavorite"`
- AlbumPublic bool `json:"AlbumPublic"`
AlbumOrder string `json:"AlbumOrder"`
AlbumTemplate string `json:"AlbumTemplate"`
+ AlbumFavorite bool `json:"AlbumFavorite"`
+}
+
+func NewAlbum(m interface{}) (f Album, err error) {
+ err = deepcopier.Copy(m).To(&f)
+
+ return f, err
}