Ungroup files #356

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2020-06-30 16:58:32 +02:00
parent b12f916e69
commit 15a5fd3c37
5 changed files with 102 additions and 14 deletions

View file

@ -48,7 +48,7 @@
</td> </td>
<td> <td>
<v-btn small depressed dark color="secondary-dark" class="ma-0 action-primary" <v-btn small depressed dark color="secondary-dark" class="ma-0 action-primary"
@click.stop.prevent="setPrimary(file)"> @click.stop.prevent="primary(file)">
<translate>Primary</translate> <translate>Primary</translate>
</v-btn> </v-btn>
<v-btn small depressed dark color="secondary-dark" class="ma-0 action-ungroup" <v-btn small depressed dark color="secondary-dark" class="ma-0 action-ungroup"
@ -173,10 +173,10 @@
this.$viewer.show([Thumb.fromFile(this.model, file)], 0); this.$viewer.show([Thumb.fromFile(this.model, file)], 0);
}, },
ungroup(file) { ungroup(file) {
this.model.ungroupFile(file.UID);
}, },
setPrimary(file) { primary(file) {
this.model.setPrimary(file.UID); this.model.primaryFile(file.UID);
}, },
formatTime(s) { formatTime(s) {
return DateTime.fromISO(s).toHTTP(); return DateTime.fromISO(s).toHTTP();

View file

@ -510,8 +510,12 @@ export class Photo extends RestModel {
return Api.put(this.getEntityResource(), {Private: this.Private}); return Api.put(this.getEntityResource(), {Private: this.Private});
} }
setPrimary(uid) { primaryFile(fileUID) {
return Api.post(this.getEntityResource() + "/primary/" + uid).then((r) => Promise.resolve(this.setValues(r.data))); return Api.post(`${this.getEntityResource()}/files/${fileUID}/primary`).then((r) => Promise.resolve(this.setValues(r.data)));
}
ungroupFile(fileUID) {
return Api.post(`${this.getEntityResource()}/files/${fileUID}/ungroup`).then((r) => Promise.resolve(this.setValues(r.data)));
} }
like() { like() {

View file

@ -294,12 +294,13 @@ func DislikePhoto(router *gin.RouterGroup) {
}) })
} }
// POST /api/v1/photos/:uid/primary/:file_uid // POST /api/v1/photos/:uid/files/:file_uid/primary
// //
// Parameters: // Parameters:
// uid: string PhotoUID as returned by the API // uid: string PhotoUID as returned by the API
func SetPhotoPrimary(router *gin.RouterGroup) { // file_uid: string File UID as returned by the API
router.POST("/photos/:uid/primary/:file_uid", func(c *gin.Context) { func PhotoFilePrimary(router *gin.RouterGroup) {
router.POST("/photos/:uid/files/:file_uid/primary", func(c *gin.Context) {
s := Auth(SessionID(c), acl.ResourcePhotos, acl.ActionUpdate) s := Auth(SessionID(c), acl.ResourcePhotos, acl.ActionUpdate)
if s.Invalid() { if s.Invalid() {
@ -330,3 +331,85 @@ func SetPhotoPrimary(router *gin.RouterGroup) {
c.JSON(http.StatusOK, p) c.JSON(http.StatusOK, p)
}) })
} }
// POST /api/v1/photos/:uid/files/:file_uid/ungroup
//
// Parameters:
// uid: string Photo UID as returned by the API
// file_uid: string File UID as returned by the API
func PhotoFileUngroup(router *gin.RouterGroup) {
router.POST("/photos/:uid/files/:file_uid/ungroup", func(c *gin.Context) {
s := Auth(SessionID(c), acl.ResourcePhotos, acl.ActionUpdate)
if s.Invalid() {
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
return
}
photoUID := c.Param("uid")
fileUID := c.Param("file_uid")
file, err := query.FileByUID(fileUID)
if err != nil {
log.Errorf("photo: %s (ungroup)", err)
c.AbortWithStatusJSON(http.StatusNotFound, ErrFileNotFound)
return
}
if file.FilePrimary {
log.Errorf("photo: can't ungroup primary files")
c.AbortWithStatusJSON(http.StatusBadRequest, ErrFormInvalid)
return
}
existingPhoto := *file.Photo
newPhoto := entity.NewPhoto()
if err := entity.UnscopedDb().Create(&newPhoto).Error; err != nil {
log.Errorf("photo: %s", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, ErrSaveFailed)
return
}
file.Photo = &newPhoto
file.PhotoID = newPhoto.ID
file.PhotoUID = newPhoto.PhotoUID
if err := file.Save(); err != nil {
log.Errorf("photo: %s", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, ErrSaveFailed)
return
}
fileName := photoprism.FileName(file.FileRoot, file.FileName)
f, err := photoprism.NewMediaFile(fileName)
if err != nil {
log.Errorf("photo: %s (ungroup)", err)
c.AbortWithStatusJSON(http.StatusNotFound, ErrFileNotFound)
return
}
if err := service.Index().MediaFile(f, photoprism.IndexOptions{Rescan: true}, existingPhoto.OriginalName).Error; err != nil {
log.Errorf("photo: %s", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, ErrSaveFailed)
return
}
PublishPhotoEvent(EntityCreated, file.PhotoUID, c)
PublishPhotoEvent(EntityUpdated, photoUID, c)
event.Success("file ungrouped")
p, err := query.PhotoPreloadByUID(photoUID)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
return
}
c.JSON(http.StatusOK, p)
})
}

View file

@ -114,8 +114,8 @@ func TestDislikePhoto(t *testing.T) {
func TestSetPhotoPrimary(t *testing.T) { func TestSetPhotoPrimary(t *testing.T) {
t.Run("existing photo", func(t *testing.T) { t.Run("existing photo", func(t *testing.T) {
app, router, _ := NewApiTest() app, router, _ := NewApiTest()
SetPhotoPrimary(router) PhotoFilePrimary(router)
r := PerformRequest(app, "POST", "/api/v1/photos/pt9jtdre2lvl0yh8/primary/ft1es39w45bnlqdw") r := PerformRequest(app, "POST", "/api/v1/photos/pt9jtdre2lvl0yh8/files/ft1es39w45bnlqdw/primary")
assert.Equal(t, http.StatusOK, r.Code) assert.Equal(t, http.StatusOK, r.Code)
GetFile(router) GetFile(router)
r2 := PerformRequest(app, "GET", "/api/v1/files/ocad9168fa6acc5c5c2965ddf6ec465ca42fd818") r2 := PerformRequest(app, "GET", "/api/v1/files/ocad9168fa6acc5c5c2965ddf6ec465ca42fd818")
@ -128,8 +128,8 @@ func TestSetPhotoPrimary(t *testing.T) {
t.Run("wrong photo uid", func(t *testing.T) { t.Run("wrong photo uid", func(t *testing.T) {
app, router, _ := NewApiTest() app, router, _ := NewApiTest()
SetPhotoPrimary(router) PhotoFilePrimary(router)
r := PerformRequest(app, "POST", "/api/v1/photos/xxx/primary/ft1es39w45bnlqdw") r := PerformRequest(app, "POST", "/api/v1/photos/xxx/files/ft1es39w45bnlqdw/primary")
val := gjson.Get(r.Body.String(), "error") val := gjson.Get(r.Body.String(), "error")
assert.Equal(t, "Photo not found", val.String()) assert.Equal(t, "Photo not found", val.String())
assert.Equal(t, http.StatusNotFound, r.Code) assert.Equal(t, http.StatusNotFound, r.Code)

View file

@ -55,7 +55,8 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
api.UpdatePhotoLabel(v1) api.UpdatePhotoLabel(v1)
api.GetMomentsTime(v1) api.GetMomentsTime(v1)
api.GetFile(v1) api.GetFile(v1)
api.SetPhotoPrimary(v1) api.PhotoFilePrimary(v1)
api.PhotoFileUngroup(v1)
api.GetLabels(v1) api.GetLabels(v1)
api.UpdateLabel(v1) api.UpdateLabel(v1)