Lazy Loading & Pagination, Alpha Channel -> White Conversion, Add Height & Width to DB & API
This commit is contained in:
@@ -87,6 +87,7 @@ type ComplexityRoot struct {
|
||||
CreatedAt func(childComplexity int) int
|
||||
ExifDate func(childComplexity int) int
|
||||
FileName func(childComplexity int) int
|
||||
Height func(childComplexity int) int
|
||||
ID func(childComplexity int) int
|
||||
IsVideo func(childComplexity int) int
|
||||
Latitude func(childComplexity int) int
|
||||
@@ -95,6 +96,7 @@ type ComplexityRoot struct {
|
||||
Tags func(childComplexity int) int
|
||||
UpdatedAt func(childComplexity int) int
|
||||
UserID func(childComplexity int) int
|
||||
Width func(childComplexity int) int
|
||||
}
|
||||
|
||||
MediaItemResponse struct {
|
||||
@@ -363,6 +365,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
|
||||
return e.complexity.MediaItem.FileName(childComplexity), true
|
||||
|
||||
case "MediaItem.height":
|
||||
if e.complexity.MediaItem.Height == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.MediaItem.Height(childComplexity), true
|
||||
|
||||
case "MediaItem.id":
|
||||
if e.complexity.MediaItem.ID == nil {
|
||||
break
|
||||
@@ -419,6 +428,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
|
||||
return e.complexity.MediaItem.UserID(childComplexity), true
|
||||
|
||||
case "MediaItem.width":
|
||||
if e.complexity.MediaItem.Width == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.MediaItem.Width(childComplexity), true
|
||||
|
||||
case "MediaItemResponse.data":
|
||||
if e.complexity.MediaItemResponse.Data == nil {
|
||||
break
|
||||
@@ -1019,6 +1035,8 @@ type MediaItem {
|
||||
isVideo: Boolean! @meta(gorm: "default:false;not null")
|
||||
fileName: String! @meta(gorm: "not null")
|
||||
origName: String! @meta(gorm: "not null")
|
||||
width: Int! @meta(gorm: "not null")
|
||||
height: Int! @meta(gorm: "not null")
|
||||
tags: [Tag] @meta(gorm: "many2many:media_tags;foreignKey:ID,UserID;References:ID")
|
||||
albums: [Album] @meta(gorm: "many2many:media_albums;foreignKey:ID,UserID;Refrences:ID")
|
||||
userID: ID! @meta(gorm: "not null")
|
||||
@@ -2937,6 +2955,124 @@ func (ec *executionContext) _MediaItem_origName(ctx context.Context, field graph
|
||||
return ec.marshalNString2string(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MediaItem_width(ctx context.Context, field graphql.CollectedField, obj *model.MediaItem) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "MediaItem",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
directive0 := func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.Width, nil
|
||||
}
|
||||
directive1 := func(ctx context.Context) (interface{}, error) {
|
||||
gorm, err := ec.unmarshalOString2ᚖstring(ctx, "not null")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ec.directives.Meta == nil {
|
||||
return nil, errors.New("directive meta is not implemented")
|
||||
}
|
||||
return ec.directives.Meta(ctx, obj, directive0, gorm)
|
||||
}
|
||||
|
||||
tmp, err := directive1(rctx)
|
||||
if err != nil {
|
||||
return nil, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
if tmp == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if data, ok := tmp.(int); ok {
|
||||
return data, nil
|
||||
}
|
||||
return nil, fmt.Errorf(`unexpected type %T from directive, should be int`, tmp)
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(int)
|
||||
fc.Result = res
|
||||
return ec.marshalNInt2int(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MediaItem_height(ctx context.Context, field graphql.CollectedField, obj *model.MediaItem) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "MediaItem",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
directive0 := func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.Height, nil
|
||||
}
|
||||
directive1 := func(ctx context.Context) (interface{}, error) {
|
||||
gorm, err := ec.unmarshalOString2ᚖstring(ctx, "not null")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ec.directives.Meta == nil {
|
||||
return nil, errors.New("directive meta is not implemented")
|
||||
}
|
||||
return ec.directives.Meta(ctx, obj, directive0, gorm)
|
||||
}
|
||||
|
||||
tmp, err := directive1(rctx)
|
||||
if err != nil {
|
||||
return nil, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
if tmp == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if data, ok := tmp.(int); ok {
|
||||
return data, nil
|
||||
}
|
||||
return nil, fmt.Errorf(`unexpected type %T from directive, should be int`, tmp)
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(int)
|
||||
fc.Result = res
|
||||
return ec.marshalNInt2int(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MediaItem_tags(ctx context.Context, field graphql.CollectedField, obj *model.MediaItem) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@@ -7615,6 +7751,16 @@ func (ec *executionContext) _MediaItem(ctx context.Context, sel ast.SelectionSet
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "width":
|
||||
out.Values[i] = ec._MediaItem_width(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "height":
|
||||
out.Values[i] = ec._MediaItem_height(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "tags":
|
||||
out.Values[i] = ec._MediaItem_tags(ctx, field, obj)
|
||||
case "albums":
|
||||
|
||||
@@ -112,6 +112,11 @@ func mediaItemFromEXIFData(filePath string) (*model.MediaItem, error) {
|
||||
mediaItem.Latitude = &decLat
|
||||
mediaItem.Longitude = &decLong
|
||||
|
||||
// Gross
|
||||
if err != nil && err.Error() == "no exif data" {
|
||||
return mediaItem, nil
|
||||
}
|
||||
|
||||
return mediaItem, err
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,8 @@ type MediaItem struct {
|
||||
IsVideo bool `json:"isVideo" gorm:"default:false;not null"`
|
||||
FileName string `json:"fileName" gorm:"not null"`
|
||||
OrigName string `json:"origName" gorm:"not null"`
|
||||
Width int `json:"width" gorm:"not null"`
|
||||
Height int `json:"height" gorm:"not null"`
|
||||
Tags []*Tag `json:"tags" gorm:"many2many:media_tags;foreignKey:ID,UserID;References:ID"`
|
||||
Albums []*Album `json:"albums" gorm:"many2many:media_albums;foreignKey:ID,UserID;Refrences:ID"`
|
||||
UserID string `json:"userID" gorm:"not null"`
|
||||
|
||||
@@ -158,6 +158,8 @@ type MediaItem {
|
||||
isVideo: Boolean! @meta(gorm: "default:false;not null")
|
||||
fileName: String! @meta(gorm: "not null")
|
||||
origName: String! @meta(gorm: "not null")
|
||||
width: Int! @meta(gorm: "not null")
|
||||
height: Int! @meta(gorm: "not null")
|
||||
tags: [Tag] @meta(gorm: "many2many:media_tags;foreignKey:ID,UserID;References:ID")
|
||||
albums: [Album] @meta(gorm: "many2many:media_albums;foreignKey:ID,UserID;Refrences:ID")
|
||||
userID: ID! @meta(gorm: "not null")
|
||||
|
||||
@@ -14,8 +14,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/davidbyttow/govips/v2/vips"
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"reichard.io/imagini/graph/generated"
|
||||
"reichard.io/imagini/graph/model"
|
||||
)
|
||||
@@ -32,6 +34,7 @@ func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewM
|
||||
|
||||
// Copy headers into the buffer
|
||||
if _, err := input.File.File.Read(fileHeader); err != nil {
|
||||
log.Error("[upload] Failed to read file header:", err)
|
||||
return nil, errors.New("Upload Failed")
|
||||
}
|
||||
|
||||
@@ -42,8 +45,12 @@ func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewM
|
||||
if strings.HasPrefix(contentType, "image/") {
|
||||
isVideo = false
|
||||
} else if strings.HasPrefix(contentType, "video/") {
|
||||
isVideo = true
|
||||
// TODO
|
||||
log.Error("[upload] Video unsupported at this time")
|
||||
return nil, errors.New("Upload Failed")
|
||||
// isVideo = true
|
||||
} else {
|
||||
log.Error("[upload] File is neither an image or video")
|
||||
return nil, errors.New("Upload Failed")
|
||||
}
|
||||
|
||||
@@ -55,32 +62,42 @@ func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewM
|
||||
filePath := path.Join(folderPath + "/" + fileName)
|
||||
|
||||
// Create File
|
||||
f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0600)
|
||||
f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
log.Error("[upload] Unable to open file handle:", err)
|
||||
return nil, errors.New("Upload Failed")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Copy header to file
|
||||
_, err = io.Copy(f, bytes.NewReader(fileHeader))
|
||||
if err != nil {
|
||||
return nil, errors.New("Upload Failed")
|
||||
}
|
||||
// Concat File
|
||||
concatFile := io.MultiReader(bytes.NewReader(fileHeader), input.File.File)
|
||||
|
||||
// Copy remaining file
|
||||
_, err = io.Copy(f, input.File.File)
|
||||
// Copy file
|
||||
_, err = io.Copy(f, concatFile)
|
||||
if err != nil {
|
||||
log.Error("[upload] Unable to copy file:", err)
|
||||
return nil, errors.New("Upload Failed")
|
||||
}
|
||||
|
||||
// Create MediaItem From EXIF Data
|
||||
mediaItem, err := mediaItemFromEXIFData(filePath)
|
||||
if err != nil {
|
||||
log.Error("[upload] Unable to extract EXIF data:", err)
|
||||
return nil, errors.New("Upload Failed")
|
||||
}
|
||||
|
||||
// Use Vips for Width & Height
|
||||
f.Seek(0, io.SeekStart)
|
||||
image, err := vips.NewImageFromReader(f)
|
||||
if err != nil {
|
||||
log.Error("[upload] Unable to extract dimension data:", err)
|
||||
return nil, errors.New("Upload Failed")
|
||||
}
|
||||
|
||||
// Add Additional MediaItem Fields
|
||||
mediaItem.ID = mediaItemID
|
||||
mediaItem.Width = image.Width()
|
||||
mediaItem.Height = image.Height()
|
||||
mediaItem.UserID = userID
|
||||
mediaItem.IsVideo = isVideo
|
||||
mediaItem.FileName = fileName
|
||||
@@ -89,6 +106,7 @@ func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewM
|
||||
// Create MediaItem in DB
|
||||
err = r.DB.CreateMediaItem(mediaItem)
|
||||
if err != nil {
|
||||
log.Error("[upload] Unable to populate create file in DB:", err)
|
||||
return nil, errors.New("Upload Failed")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user