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 }