Push updates: Add event types for photos & albums

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2020-01-30 18:19:26 +01:00
parent 11c3ed70e3
commit ceb7d258fe
11 changed files with 187 additions and 44 deletions

View file

@ -262,21 +262,74 @@
onCount() {
this.dirty = true;
},
onPhotosUpdated(ev, data) {
onPhotos(ev, data) {
if (!data || !data.entities) {
console.warn("onPhotosUpdated(): no entities found in event data");
console.warn("onPhotos(): no entities found in event data");
return
}
for (let i = 0; i < data.entities.length; i++) {
const values = data.entities[i];
const model = this.results.find((m) => m.ID === values.ID);
const type = ev.split('.')[1];
for (let key in values) {
if (values.hasOwnProperty(key)) {
model[key] = values[key];
console.log("onPhotos(): ", ev, type, data);
switch (type) {
case 'updated':
for (let i = 0; i < data.entities.length; i++) {
const values = data.entities[i];
const model = this.results.find((m) => m.ID === values.ID);
for (let key in values) {
if (values.hasOwnProperty(key)) {
model[key] = values[key];
}
}
}
}
break;
case 'restored':
if(this.context === "archive") {
this.dirty = false;
for (let i = 0; i < data.entities.length; i++) {
const uuid = data.entities[i];
const index = this.results.findIndex((m) => m.PhotoUUID === uuid);
if (index >= 0) {
this.results.splice(index, 1);
}
}
} else {
this.dirty = true;
}
break;
case 'archived':
if(this.context === "photos") {
this.dirty = false;
for (let i = 0; i < data.entities.length; i++) {
const uuid = data.entities[i];
const index = this.results.findIndex((m) => m.PhotoUUID === uuid);
if (index >= 0) {
this.results.splice(index, 1);
}
}
} else {
this.dirty = true;
}
break;
case 'created':
if(this.order === "imported" && JSON.stringify(this.filter) === "{}") {
this.dirty = false;
for (let i = 0; i < data.entities.length; i++) {
const values = data.entities[i];
const index = this.results.findIndex((m) => m.ID === values.ID);
if(index === -1) {
this.results.unshift(new Photo(values));
}
}
}
break;
default:
console.warn("unexpected event type", ev);
}
}
},
@ -285,7 +338,7 @@
this.uploadSubId = Event.subscribe("import.completed", (ev, data) => this.onImportCompleted(ev, data));
this.countSubId = Event.subscribe("count.photos", (ev, data) => this.onCount(ev, data));
this.modelSubId = Event.subscribe("photos.updated", (ev, data) => this.onPhotosUpdated(ev, data));
this.modelSubId = Event.subscribe("photos", (ev, data) => this.onPhotos(ev, data));
},
destroyed() {
Event.unsubscribe(this.uploadSubId);

View file

@ -86,6 +86,7 @@ func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
return
}
q := query.New(conf.OriginalsPath(), conf.Db())
m := entity.NewAlbum(f.AlbumName)
m.AlbumFavorite = f.AlbumFavorite
@ -107,6 +108,8 @@ func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
event.Publish("config.updated", event.Data(conf.ClientConfig()))
PublishAlbumEvent(EntityCreated, m.AlbumUUID, c, q)
c.JSON(http.StatusOK, m)
})
}
@ -142,6 +145,8 @@ func UpdateAlbum(router *gin.RouterGroup, conf *config.Config) {
event.Publish("config.updated", event.Data(conf.ClientConfig()))
event.Success(fmt.Sprintf("album \"%s\" saved", m.AlbumName))
PublishAlbumEvent(EntityUpdated, id, c, q)
c.JSON(http.StatusOK, m)
})
}
@ -164,6 +169,8 @@ func DeleteAlbum(router *gin.RouterGroup, conf *config.Config) {
return
}
PublishAlbumEvent(EntityDeleted, id, c, q)
conf.Db().Delete(&m)
event.Publish("config.updated", event.Data(conf.ClientConfig()))
@ -184,9 +191,10 @@ func LikeAlbum(router *gin.RouterGroup, conf *config.Config) {
return
}
id := c.Param("uuid")
q := query.New(conf.OriginalsPath(), conf.Db())
album, err := q.FindAlbumByUUID(c.Param("uuid"))
album, err := q.FindAlbumByUUID(id)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, ErrAlbumNotFound)
@ -197,6 +205,7 @@ func LikeAlbum(router *gin.RouterGroup, conf *config.Config) {
conf.Db().Save(&album)
event.Publish("config.updated", event.Data(conf.ClientConfig()))
PublishAlbumEvent(EntityUpdated, id, c, q)
c.JSON(http.StatusOK, http.Response{})
})
@ -213,8 +222,9 @@ func DislikeAlbum(router *gin.RouterGroup, conf *config.Config) {
return
}
id := c.Param("uuid")
q := query.New(conf.OriginalsPath(), conf.Db())
album, err := q.FindAlbumByUUID(c.Param("uuid"))
album, err := q.FindAlbumByUUID(id)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, ErrAlbumNotFound)
@ -225,6 +235,7 @@ func DislikeAlbum(router *gin.RouterGroup, conf *config.Config) {
conf.Db().Save(&album)
event.Publish("config.updated", event.Data(conf.ClientConfig()))
PublishAlbumEvent(EntityUpdated, id, c, q)
c.JSON(http.StatusOK, http.Response{})
})

View file

@ -48,6 +48,10 @@ func BatchPhotosArchive(router *gin.RouterGroup, conf *config.Config) {
event.Publish("config.updated", event.Data(conf.ClientConfig()))
event.Publish("photos.archived", event.Data{
"entities": f.Photos,
})
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("photos archived in %d s", elapsed)})
})
}
@ -86,6 +90,10 @@ func BatchPhotosRestore(router *gin.RouterGroup, conf *config.Config) {
event.Publish("config.updated", event.Data(conf.ClientConfig()))
event.Publish("photos.restored", event.Data{
"entities": f.Photos,
})
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("photos restored in %d s", elapsed)})
})
}
@ -120,6 +128,10 @@ func BatchAlbumsDelete(router *gin.RouterGroup, conf *config.Config) {
event.Publish("config.updated", event.Data(conf.ClientConfig()))
event.Publish("albums.deleted", event.Data{
"entities": f.Albums,
})
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("albums deleted")})
})
}

63
internal/api/event.go Normal file
View file

@ -0,0 +1,63 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/query"
)
type EntityEvent string
const (
EntityUpdated EntityEvent = "updated"
EntityCreated EntityEvent = "created"
EntityDeleted EntityEvent = "deleted"
)
func PublishPhotoEvent(e EntityEvent, uuid string, c *gin.Context, q *query.Repo) {
f := form.PhotoSearch{ID: uuid}
result, err := q.Photos(f)
if err != nil {
log.Error(err)
c.AbortWithStatusJSON(http.StatusInternalServerError, ErrUnexpectedError)
return
}
event.Publish("photos."+string(e), event.Data{
"entities": result,
})
}
func PublishAlbumEvent(e EntityEvent, uuid string, c *gin.Context, q *query.Repo) {
f := form.AlbumSearch{ID: uuid}
result, err := q.Albums(f)
if err != nil {
log.Error(err)
c.AbortWithStatusJSON(http.StatusInternalServerError, ErrUnexpectedError)
return
}
event.Publish("albums."+string(e), event.Data{
"entities": result,
})
}
func PublishLabelEvent(e EntityEvent, uuid string, c *gin.Context, q *query.Repo) {
f := form.LabelSearch{ID: uuid}
result, err := q.Labels(f)
if err != nil {
log.Error(err)
c.AbortWithStatusJSON(http.StatusInternalServerError, ErrUnexpectedError)
return
}
event.Publish("labels."+string(e), event.Data{
"entities": result,
})
}

View file

@ -7,7 +7,6 @@ import (
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/txt"
@ -15,21 +14,6 @@ import (
"github.com/gin-gonic/gin"
)
func PublishPhotoUpdate(uuid string, c *gin.Context, q *query.Repo) {
f := form.PhotoSearch{ID: uuid}
result, err := q.Photos(f)
if err != nil {
log.Error(err)
c.AbortWithStatusJSON(http.StatusInternalServerError, ErrUnexpectedError)
return
}
event.Publish("photos.updated", event.Data{
"entities": result,
})
}
// GET /api/v1/photos/:uuid
//
// Parameters:
@ -61,10 +45,10 @@ func UpdatePhoto(router *gin.RouterGroup, conf *config.Config) {
return
}
uuid := c.Param("uuid")
id := c.Param("uuid")
q := query.New(conf.OriginalsPath(), conf.Db())
m, err := q.FindPhotoByUUID(uuid)
m, err := q.FindPhotoByUUID(id)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
@ -78,11 +62,11 @@ func UpdatePhoto(router *gin.RouterGroup, conf *config.Config) {
conf.Db().Save(&m)
PublishPhotoUpdate(uuid, c, q)
PublishPhotoEvent(EntityUpdated, id, c, q)
event.Success("photo saved")
p, err := q.PreloadPhotoByUUID(uuid)
p, err := q.PreloadPhotoByUUID(id)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
@ -138,9 +122,9 @@ func LikePhoto(router *gin.RouterGroup, conf *config.Config) {
return
}
uuid := c.Param("uuid")
id := c.Param("uuid")
q := query.New(conf.OriginalsPath(), conf.Db())
m, err := q.FindPhotoByUUID(uuid)
m, err := q.FindPhotoByUUID(id)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
@ -154,7 +138,7 @@ func LikePhoto(router *gin.RouterGroup, conf *config.Config) {
"count": 1,
})
PublishPhotoUpdate(uuid, c, q)
PublishPhotoEvent(EntityUpdated, id, c, q)
c.JSON(http.StatusOK, gin.H{"photo": m})
})
@ -171,9 +155,9 @@ func DislikePhoto(router *gin.RouterGroup, conf *config.Config) {
return
}
uuid := c.Param("uuid")
id := c.Param("uuid")
q := query.New(conf.OriginalsPath(), conf.Db())
m, err := q.FindPhotoByUUID(uuid)
m, err := q.FindPhotoByUUID(id)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
@ -187,7 +171,7 @@ func DislikePhoto(router *gin.RouterGroup, conf *config.Config) {
"count": -1,
})
PublishPhotoUpdate(uuid, c, q)
PublishPhotoEvent(EntityUpdated, id, c, q)
c.JSON(http.StatusOK, gin.H{"photo": m})
})

View file

@ -73,7 +73,7 @@ func wsReader(ws *websocket.Conn, connId string, conf *config.Config) {
func wsWriter(ws *websocket.Conn, connId string) {
pingTicker := time.NewTicker(15 * time.Second)
s := event.Subscribe("log.*", "notify.*", "index.*", "upload.*", "import.*", "config.*", "count.*", "photos.*", "albums.*")
s := event.Subscribe("log.*", "notify.*", "index.*", "upload.*", "import.*", "config.*", "count.*", "photos.*", "albums.*", "labels.*")
defer func() {
pingTicker.Stop()

View file

@ -3,6 +3,7 @@ package form
// AlbumSearch represents search form fields for "/api/v1/albums".
type AlbumSearch struct {
Query string `form:"q"`
ID string `form:"id"`
Slug string `form:"slug"`
Name string `form:"name"`
Favorites bool `form:"favorites"`

View file

@ -3,6 +3,7 @@ package form
// PhotoSearch represents search form fields for "/api/v1/labels".
type LabelSearch struct {
Query string `form:"q"`
ID string `form:"id"`
Slug string `form:"slug"`
Name string `form:"name"`
All bool `form:"all"`

View file

@ -54,18 +54,26 @@ func (s *Repo) Albums(f form.AlbumSearch) (results []AlbumResult, err error) {
return results, err
}
defer log.Debug(capture.Time(time.Now(), fmt.Sprintf("search: %+v", f)))
defer log.Debug(capture.Time(time.Now(), fmt.Sprintf("albums: %+v", f)))
q := s.db.NewScope(nil).DB()
// q.LogMode(true)
q = q.Table("albums").
Select(`albums.*, COUNT(photos_albums.album_uuid) AS album_count`).
Joins("LEFT JOIN photos_albums ON photos_albums.album_uuid = albums.album_uuid").
Where("albums.deleted_at IS NULL").
Group("albums.id")
if f.ID != "" {
q = q.Where("albums.album_uuid = ?", f.ID)
if result := q.Scan(&results); result.Error != nil {
return results, result.Error
}
return results, nil
}
if f.Query != "" {
likeString := "%" + strings.ToLower(f.Query) + "%"
q = q.Where("LOWER(albums.album_name) LIKE ?", likeString)

View file

@ -91,7 +91,7 @@ func (s *Repo) Labels(f form.LabelSearch) (results []LabelResult, err error) {
return results, err
}
defer log.Debug(capture.Time(time.Now(), fmt.Sprintf("search: %+v", f)))
defer log.Debug(capture.Time(time.Now(), fmt.Sprintf("labels: %+v", f)))
q := s.db.NewScope(nil).DB()
@ -102,6 +102,16 @@ func (s *Repo) Labels(f form.LabelSearch) (results []LabelResult, err error) {
Where("labels.deleted_at IS NULL").
Group("labels.id")
if f.ID != "" {
q = q.Where("labels.label_uuid = ?", f.ID)
if result := q.Scan(&results); result.Error != nil {
return results, result.Error
}
return results, nil
}
if f.Query != "" {
var labelIds []uint
var categories []entity.Category

View file

@ -104,7 +104,7 @@ func (s *Repo) Photos(f form.PhotoSearch) (results []PhotoResult, err error) {
return results, err
}
defer log.Debug(capture.Time(time.Now(), fmt.Sprintf("search: %+v", f)))
defer log.Debug(capture.Time(time.Now(), fmt.Sprintf("photos: %+v", f)))
q := s.db.NewScope(nil).DB()