diff --git a/go.mod b/go.mod index 0cdcc78ba..a99e467d1 100644 --- a/go.mod +++ b/go.mod @@ -66,6 +66,7 @@ require ( github.com/uber/jaeger-client-go v2.15.0+incompatible // indirect github.com/uber/jaeger-lib v1.5.0 // indirect github.com/ugorji/go v1.1.7 // indirect + github.com/ulule/deepcopier v0.0.0-20200117111125-792cfb847af8 github.com/unrolled/render v0.0.0-20181210145518-4c664cb3ad2f // indirect github.com/urfave/cli v1.20.0 go.uber.org/atomic v1.4.0 // indirect diff --git a/go.sum b/go.sum index 7d217395e..ec0b9e5e9 100644 --- a/go.sum +++ b/go.sum @@ -126,7 +126,6 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/open-location-code v0.0.0-20191230190541-a6eb95b4d2f9 h1:MOOYh4mJsm+TXOgXRXT1E0g4FHrl41qhvUqXd/EPgFg= github.com/google/open-location-code/go v0.0.0-20191230190541-a6eb95b4d2f9 h1:6ILzS4n0F17S38XvOB1BcyzB+0BtVzU77EyuMtkMffo= github.com/google/open-location-code/go v0.0.0-20191230190541-a6eb95b4d2f9/go.mod h1:eJfRN6aj+kR/rnua/rw9jAgYhqoMHldQkdTi+sePRKk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -150,6 +149,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v0.0.0-20160910222444-6b7015e65d36/ github.com/grpc-ecosystem/grpc-gateway v1.4.1/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.5.1 h1:3scN4iuXkNOyP98jF55Lv8a9j1o/IwvnDIZ0LHJK1nk= github.com/grpc-ecosystem/grpc-gateway v1.5.1/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/guregu/null v3.4.0+incompatible h1:a4mw37gBO7ypcBlTJeZGuMpSxxFTV9qFfFKgWxQSGaM= +github.com/guregu/null v3.4.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -183,6 +184,8 @@ github.com/leandro-lugaresi/hub v1.1.0 h1:yHYA0WsMYaJd+I6J24nYlCP2CFD4RTnhaHCRmK github.com/leandro-lugaresi/hub v1.1.0/go.mod h1:IVKrfZTYfU1SbWCGQMHNGYdW4j1Pl7Cg8gr6sSeT/84= github.com/lib/pq v1.1.0 h1:/5u4a+KGJptBRqGzPvYQL9p0d/tPR4S31+Tnzj9lEO4= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09mS6CXfO4= github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= github.com/machinebox/progress v0.2.0/go.mod h1:hl4FywxSjfmkmCrersGhmJH7KwuKl+Ueq9BXkOny+iE= @@ -328,6 +331,8 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ulule/deepcopier v0.0.0-20200117111125-792cfb847af8 h1:iCslye9fWwp1ExDu06BcKM8/gjDRAlX9DBL+T8CjuWU= +github.com/ulule/deepcopier v0.0.0-20200117111125-792cfb847af8/go.mod h1:wUZg40sMUnY+1FU5F9rZwwCruLb8h1bHF8HzI09kgok= github.com/unrolled/render v0.0.0-20171102162132-65450fb6b2d3/go.mod h1:tu82oB5W2ykJRVioYsB+IQKcft7ryBr7w12qMBUPyXg= github.com/unrolled/render v0.0.0-20181210145518-4c664cb3ad2f h1:+feYJlxPM00jEkdybexHiwIIOVuClwTEbh1WLiNr0mk= github.com/unrolled/render v0.0.0-20181210145518-4c664cb3ad2f/go.mod h1:tu82oB5W2ykJRVioYsB+IQKcft7ryBr7w12qMBUPyXg= @@ -351,8 +356,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200117160349-530e935923ad h1:Jh8cai0fqIK+f6nG0UgPW5wFk8wmiMhM3AyciDBdtQg= -golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b h1:VHyIDlv3XkfCa5/a81uzaoDkHH4rr81Z62g+xlnO8uM= diff --git a/internal/api/photo.go b/internal/api/photo.go index ae55da1ec..6a222e245 100644 --- a/internal/api/photo.go +++ b/internal/api/photo.go @@ -5,13 +5,14 @@ import ( "net/http" "path" + "github.com/gin-gonic/gin" "github.com/photoprism/photoprism/internal/config" + "github.com/photoprism/photoprism/internal/entity" "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" - - "github.com/gin-gonic/gin" ) // GET /api/v1/photos/:uuid @@ -55,12 +56,26 @@ func UpdatePhoto(router *gin.RouterGroup, conf *config.Config) { return } - if err := c.BindJSON(&m); err != nil { + // TODO: Proof-of-concept for form handling - might need refactoring + // 1) Init form with model values + f, err := form.NewPhoto(m) + + if err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": txt.UcFirst(err.Error())}) return } - conf.Db().Save(&m) + // 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())}) + return + } + + // 3) Save model with values from form + if err := entity.SavePhoto(m, f, conf.Db()); err != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": txt.UcFirst(err.Error())}) + return + } PublishPhotoEvent(EntityUpdated, id, c, q) diff --git a/internal/entity/photo.go b/internal/entity/photo.go index 38743efa4..563aed7c4 100644 --- a/internal/entity/photo.go +++ b/internal/entity/photo.go @@ -5,8 +5,10 @@ import ( "time" "github.com/jinzhu/gorm" + "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/pkg/rnd" "github.com/photoprism/photoprism/pkg/txt" + "github.com/ulule/deepcopier" ) // Photo represents a photo that can have multiple image or sidecar files. @@ -34,9 +36,9 @@ type Photo struct { PhotoExposure string `gorm:"type:varbinary(64);" json:"PhotoExposure"` CameraID uint `gorm:"index:idx_photos_camera_lens;" json:"CameraID"` LensID uint `gorm:"index:idx_photos_camera_lens;" json:"LensID"` - LocationID string `gorm:"type:varbinary(16);index;" json:"LocationID"` - PlaceID string `gorm:"type:varbinary(16);index;" json:"PlaceID"` AccountID uint `json:"AccountID"` + PlaceID string `gorm:"type:varbinary(16);index;" json:"PlaceID"` + LocationID string `gorm:"type:varbinary(16);index;" json:"LocationID"` LocationEstimated bool `json:"LocationEstimated"` PhotoCountry string `gorm:"index:idx_photos_country_year_month;" json:"PhotoCountry"` PhotoYear int `gorm:"index:idx_photos_country_year_month;"` @@ -61,6 +63,15 @@ type Photo struct { DeletedAt *time.Time `sql:"index"` } +// SavePhoto updates a model using form data and persists it in the database. +func SavePhoto(model Photo, form form.Photo, db *gorm.DB) error { + if err := deepcopier.Copy(&model).From(form); err != nil { + return err + } + + return db.Save(&model).Error +} + func (m *Photo) BeforeCreate(scope *gorm.Scope) error { if err := scope.SetColumn("PhotoUUID", rnd.PPID('p')); err != nil { return err diff --git a/internal/form/album.go b/internal/form/album.go index 9b0c6876c..1ede2b0a4 100644 --- a/internal/form/album.go +++ b/internal/form/album.go @@ -1,5 +1,6 @@ package form +// Album represents an album edit form. type Album struct { AlbumName string `json:"AlbumName"` AlbumDescription string `json:"AlbumDescription"` diff --git a/internal/form/label.go b/internal/form/label.go index 9dc7e854e..6bf747079 100644 --- a/internal/form/label.go +++ b/internal/form/label.go @@ -1,5 +1,6 @@ package form +// Label represents a label edit form. type Label struct { LabelName string `json:"LabelName"` LabelUncertainty int `json:"LabelUncertainty"` diff --git a/internal/form/photo.go b/internal/form/photo.go new file mode 100644 index 000000000..aae6e4d95 --- /dev/null +++ b/internal/form/photo.go @@ -0,0 +1,45 @@ +package form + +import ( + "time" + + "github.com/ulule/deepcopier" +) + +// Photo represents a photo edit form. +type Photo struct { + TakenAt time.Time `json:"TakenAt"` + PhotoTitle string `json:"PhotoTitle"` + PhotoDescription string `json:"PhotoDescription"` + PhotoNotes string `json:"PhotoNotes"` + PhotoArtist string `json:"PhotoArtist"` + PhotoCopyright string `json:"PhotoCopyright"` + PhotoFavorite bool `json:"PhotoFavorite"` + PhotoPrivate bool `json:"PhotoPrivate"` + PhotoNSFW bool `json:"PhotoNSFW"` + PhotoStory bool `json:"PhotoStory"` + PhotoLat float64 `json:"PhotoLat"` + PhotoLng float64 `json:"PhotoLng"` + PhotoAltitude int `json:"PhotoAltitude"` + PhotoFocalLength int `json:"PhotoFocalLength"` + PhotoIso int `json:"PhotoIso"` + PhotoFNumber float64 `json:"PhotoFNumber"` + PhotoExposure string `json:"PhotoExposure"` + CameraID uint `json:"CameraID"` + LensID uint `json:"LensID"` + LocationID string `json:"LocationID"` + PlaceID string `json:"PlaceID"` + PhotoCountry string `json:"PhotoCountry"` + TimeZone string `json:"TimeZone"` + TakenAtLocal time.Time `json:"TakenAtLocal"` + ModifiedTitle bool `json:"ModifiedTitle"` + ModifiedDetails bool `json:"ModifiedDetails"` + ModifiedLocation bool `json:"ModifiedLocation"` + ModifiedDate bool `json:"ModifiedDate"` +} + +func NewPhoto(m interface{}) (f Photo, err error) { + err = deepcopier.Copy(m).To(&f) + + return f, err +} diff --git a/pkg/txt/clip.go b/pkg/txt/clip.go index d269d12dc..37c442e35 100644 --- a/pkg/txt/clip.go +++ b/pkg/txt/clip.go @@ -11,7 +11,7 @@ func Clip(s string, size int) string { runes := []rune(s) if len(runes) > size { - s = string(runes[0:size-1]) + s = string(runes[0 : size-1]) } return s diff --git a/pkg/txt/slug.go b/pkg/txt/slug.go index 21f340c66..4dd7b796c 100644 --- a/pkg/txt/slug.go +++ b/pkg/txt/slug.go @@ -10,4 +10,3 @@ func SlugToTitle(s string) string { return Title(strings.Join(Words(s), " ")) } -