diff --git a/graph/generated/generated.go b/graph/generated/generated.go index 8aa1d49..18f3311 100644 --- a/graph/generated/generated.go +++ b/graph/generated/generated.go @@ -73,7 +73,6 @@ type ComplexityRoot struct { RefreshKey func(childComplexity int) int Type func(childComplexity int) int UpdatedAt func(childComplexity int) int - User func(childComplexity int) int UserID func(childComplexity int) int } @@ -94,7 +93,6 @@ type ComplexityRoot struct { OrigName func(childComplexity int) int Tags func(childComplexity int) int UpdatedAt func(childComplexity int) int - User func(childComplexity int) int UserID func(childComplexity int) int } @@ -105,7 +103,6 @@ type ComplexityRoot struct { Mutation struct { CreateAlbum func(childComplexity int, input model.NewAlbum) int - CreateDevice func(childComplexity int, input model.NewDevice) int CreateMediaItem func(childComplexity int, input model.NewMediaItem) int CreateTag func(childComplexity int, input model.NewTag) int CreateUser func(childComplexity int, input model.NewUser) int @@ -146,16 +143,18 @@ type ComplexityRoot struct { } User struct { - AuthType func(childComplexity int) int - CreatedAt func(childComplexity int) int - Email func(childComplexity int) int - FirstName func(childComplexity int) int - ID func(childComplexity int) int - LastName func(childComplexity int) int - Password func(childComplexity int) int - Role func(childComplexity int) int - UpdatedAt func(childComplexity int) int - Username func(childComplexity int) int + AuthType func(childComplexity int) int + CreatedAt func(childComplexity int) int + Devices func(childComplexity int) int + Email func(childComplexity int) int + FirstName func(childComplexity int) int + ID func(childComplexity int) int + LastName func(childComplexity int) int + MediaItems func(childComplexity int) int + Password func(childComplexity int) int + Role func(childComplexity int) int + UpdatedAt func(childComplexity int) int + Username func(childComplexity int) int } UserResponse struct { @@ -166,7 +165,6 @@ type ComplexityRoot struct { type MutationResolver interface { CreateMediaItem(ctx context.Context, input model.NewMediaItem) (*model.MediaItem, error) - CreateDevice(ctx context.Context, input model.NewDevice) (*model.Device, error) CreateAlbum(ctx context.Context, input model.NewAlbum) (*model.Album, error) CreateTag(ctx context.Context, input model.NewTag) (*model.Tag, error) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) @@ -307,13 +305,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Device.UpdatedAt(childComplexity), true - case "Device.user": - if e.complexity.Device.User == nil { - break - } - - return e.complexity.Device.User(childComplexity), true - case "Device.userID": if e.complexity.Device.UserID == nil { break @@ -412,13 +403,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.MediaItem.UpdatedAt(childComplexity), true - case "MediaItem.user": - if e.complexity.MediaItem.User == nil { - break - } - - return e.complexity.MediaItem.User(childComplexity), true - case "MediaItem.userID": if e.complexity.MediaItem.UserID == nil { break @@ -452,18 +436,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.CreateAlbum(childComplexity, args["input"].(model.NewAlbum)), true - case "Mutation.createDevice": - if e.complexity.Mutation.CreateDevice == nil { - break - } - - args, err := ec.field_Mutation_createDevice_args(context.TODO(), rawArgs) - if err != nil { - return 0, false - } - - return e.complexity.Mutation.CreateDevice(childComplexity, args["input"].(model.NewDevice)), true - case "Mutation.createMediaItem": if e.complexity.Mutation.CreateMediaItem == nil { break @@ -723,6 +695,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.User.CreatedAt(childComplexity), true + case "User.devices": + if e.complexity.User.Devices == nil { + break + } + + return e.complexity.User.Devices(childComplexity), true + case "User.email": if e.complexity.User.Email == nil { break @@ -751,6 +730,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.User.LastName(childComplexity), true + case "User.mediaItems": + if e.complexity.User.MediaItems == nil { + break + } + + return e.complexity.User.MediaItems(childComplexity), true + case "User.password": if e.complexity.User.Password == nil { break @@ -998,6 +984,21 @@ input AuthTypeFilter { # -------------------- Object Definitions -------------------- # ------------------------------------------------------------ +type User { + id: ID! @meta(gorm: "primaryKey;not null") + createdAt: Time + updatedAt: Time + email: String! @meta(gorm: "not null;unique") + username: String! @meta(gorm: "not null;unique") + firstName: String + lastName: String + role: Role! @meta(gorm: "default:User;not null") + authType: AuthType! @meta(gorm: "default:Local;not null") + password: String @isPrivate + devices: [Device!] @meta(gorm: "foreignKey:UserID") + mediaItems: [MediaItem!] @meta(gorm: "foreignKey:UserID") +} + type Device { id: ID! @meta(gorm: "primaryKey;not null") createdAt: Time @@ -1005,21 +1006,7 @@ type Device { name: String! @meta(gorm: "not null") type: DeviceType! @meta(gorm: "default:Unknown;not null") userID: ID! @meta(gorm: "not null") - user: User! @meta(gorm: "foreignKey:ID;references:UserID;not null") - refreshKey: String @deprecated(reason: "Private Field") # @isPrivate -} - -type User { - id: ID! @meta(gorm: "primaryKey;not null") - createdAt: Time - updatedAt: Time - email: String! @meta(gorm: "not null;unique") - username: String! @meta(gorm: "not null;unique") - firstName: String - lastName: String - role: Role! @meta(gorm: "default:User;not null") - authType: AuthType! @meta(gorm: "default:Local;not null") - password: String @deprecated(reason: "Private Field") #@isPrivate + refreshKey: String @isPrivate } type MediaItem { @@ -1035,21 +1022,20 @@ type MediaItem { tags: [Tag] @meta(gorm: "many2many:media_tags") albums: [Album] @meta(gorm: "many2many:media_albums") userID: ID! @meta(gorm: "not null") - user: User! @meta(gorm: "foreignKey:ID;references:UserID;not null") } type Tag { - id: ID! @meta(gorm: "primaryKey;not null") + id: ID! @meta(gorm: "primaryKey;not null") createdAt: Time updatedAt: Time - name: String! @meta(gorm: "unique;not null") + name: String! @meta(gorm: "unique;not null") } type Album { - id: ID! @meta(gorm: "primaryKey;not null") + id: ID! @meta(gorm: "primaryKey;not null") createdAt: Time updatedAt: Time - name: String! @meta(gorm: "unique;not null") + name: String! @meta(gorm: "unique;not null") } # ------------------------------------------------------------ @@ -1131,10 +1117,6 @@ input NewUser { password: String } -input NewDevice { - name: String! -} - input NewMediaItem { file: Upload! tags: [ID!] @@ -1236,7 +1218,6 @@ type Query { type Mutation { createMediaItem(input: NewMediaItem!): MediaItem! @hasMinRole(role: User) - createDevice(input: NewDevice!): Device! @hasMinRole(role: User) createAlbum(input: NewAlbum!): Album! @hasMinRole(role: User) createTag(input: NewTag!): Tag! @hasMinRole(role: User) createUser(input: NewUser!): User! @hasMinRole(role: Admin) @@ -1294,21 +1275,6 @@ func (ec *executionContext) field_Mutation_createAlbum_args(ctx context.Context, return args, nil } -func (ec *executionContext) field_Mutation_createDevice_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { - var err error - args := map[string]interface{}{} - var arg0 model.NewDevice - if tmp, ok := rawArgs["input"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) - arg0, err = ec.unmarshalNNewDevice2reichardᚗioᚋimaginiᚋgraphᚋmodelᚐNewDevice(ctx, tmp) - if err != nil { - return nil, err - } - } - args["input"] = arg0 - return args, nil -} - func (ec *executionContext) field_Mutation_createMediaItem_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -2328,7 +2294,7 @@ func (ec *executionContext) _Device_userID(ctx context.Context, field graphql.Co return ec.marshalNID2string(ctx, field.Selections, res) } -func (ec *executionContext) _Device_user(ctx context.Context, field graphql.CollectedField, obj *model.Device) (ret graphql.Marshaler) { +func (ec *executionContext) _Device_refreshKey(ctx context.Context, field graphql.CollectedField, obj *model.Device) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -2347,17 +2313,13 @@ func (ec *executionContext) _Device_user(ctx context.Context, field graphql.Coll 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.User, nil + return obj.RefreshKey, nil } directive1 := func(ctx context.Context) (interface{}, error) { - gorm, err := ec.unmarshalOString2ᚖstring(ctx, "foreignKey:ID;references:UserID;not null") - if err != nil { - return nil, err + if ec.directives.IsPrivate == nil { + return nil, errors.New("directive isPrivate is not implemented") } - if ec.directives.Meta == nil { - return nil, errors.New("directive meta is not implemented") - } - return ec.directives.Meta(ctx, obj, directive0, gorm) + return ec.directives.IsPrivate(ctx, obj, directive0) } tmp, err := directive1(rctx) @@ -2367,45 +2329,10 @@ func (ec *executionContext) _Device_user(ctx context.Context, field graphql.Coll if tmp == nil { return nil, nil } - if data, ok := tmp.(*model.User); ok { + if data, ok := tmp.(*string); ok { return data, nil } - return nil, fmt.Errorf(`unexpected type %T from directive, should be *reichard.io/imagini/graph/model.User`, 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.(*model.User) - fc.Result = res - return ec.marshalNUser2ᚖreichardᚗioᚋimaginiᚋgraphᚋmodelᚐUser(ctx, field.Selections, res) -} - -func (ec *executionContext) _Device_refreshKey(ctx context.Context, field graphql.CollectedField, obj *model.Device) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "Device", - Field: field, - Args: nil, - IsMethod: false, - IsResolver: false, - } - - ctx = graphql.WithFieldContext(ctx, fc) - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.RefreshKey, nil + return nil, fmt.Errorf(`unexpected type %T from directive, should be *string`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -3053,65 +2980,6 @@ func (ec *executionContext) _MediaItem_userID(ctx context.Context, field graphql return ec.marshalNID2string(ctx, field.Selections, res) } -func (ec *executionContext) _MediaItem_user(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.User, nil - } - directive1 := func(ctx context.Context) (interface{}, error) { - gorm, err := ec.unmarshalOString2ᚖstring(ctx, "foreignKey:ID;references:UserID;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.(*model.User); ok { - return data, nil - } - return nil, fmt.Errorf(`unexpected type %T from directive, should be *reichard.io/imagini/graph/model.User`, 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.(*model.User) - fc.Result = res - return ec.marshalNUser2ᚖreichardᚗioᚋimaginiᚋgraphᚋmodelᚐUser(ctx, field.Selections, res) -} - func (ec *executionContext) _MediaItemResponse_data(ctx context.Context, field graphql.CollectedField, obj *model.MediaItemResponse) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -3245,72 +3113,6 @@ func (ec *executionContext) _Mutation_createMediaItem(ctx context.Context, field return ec.marshalNMediaItem2ᚖreichardᚗioᚋimaginiᚋgraphᚋmodelᚐMediaItem(ctx, field.Selections, res) } -func (ec *executionContext) _Mutation_createDevice(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "Mutation", - Field: field, - Args: nil, - IsMethod: true, - IsResolver: true, - } - - ctx = graphql.WithFieldContext(ctx, fc) - rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Mutation_createDevice_args(ctx, rawArgs) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - fc.Args = args - 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 ec.resolvers.Mutation().CreateDevice(rctx, args["input"].(model.NewDevice)) - } - directive1 := func(ctx context.Context) (interface{}, error) { - role, err := ec.unmarshalNRole2reichardᚗioᚋimaginiᚋgraphᚋmodelᚐRole(ctx, "User") - if err != nil { - return nil, err - } - if ec.directives.HasMinRole == nil { - return nil, errors.New("directive hasMinRole is not implemented") - } - return ec.directives.HasMinRole(ctx, nil, directive0, role) - } - - tmp, err := directive1(rctx) - if err != nil { - return nil, graphql.ErrorOnPath(ctx, err) - } - if tmp == nil { - return nil, nil - } - if data, ok := tmp.(*model.Device); ok { - return data, nil - } - return nil, fmt.Errorf(`unexpected type %T from directive, should be *reichard.io/imagini/graph/model.Device`, 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.(*model.Device) - fc.Result = res - return ec.marshalNDevice2ᚖreichardᚗioᚋimaginiᚋgraphᚋmodelᚐDevice(ctx, field.Selections, res) -} - func (ec *executionContext) _Mutation_createAlbum(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -5194,8 +4996,28 @@ func (ec *executionContext) _User_password(ctx context.Context, field graphql.Co ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Password, nil + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Password, nil + } + directive1 := func(ctx context.Context) (interface{}, error) { + if ec.directives.IsPrivate == nil { + return nil, errors.New("directive isPrivate is not implemented") + } + return ec.directives.IsPrivate(ctx, obj, directive0) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(*string); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be *string`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -5209,6 +5031,118 @@ func (ec *executionContext) _User_password(ctx context.Context, field graphql.Co return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } +func (ec *executionContext) _User_devices(ctx context.Context, field graphql.CollectedField, obj *model.User) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "User", + 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.Devices, nil + } + directive1 := func(ctx context.Context) (interface{}, error) { + gorm, err := ec.unmarshalOString2ᚖstring(ctx, "foreignKey:UserID") + 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.([]*model.Device); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be []*reichard.io/imagini/graph/model.Device`, tmp) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]*model.Device) + fc.Result = res + return ec.marshalODevice2ᚕᚖreichardᚗioᚋimaginiᚋgraphᚋmodelᚐDeviceᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _User_mediaItems(ctx context.Context, field graphql.CollectedField, obj *model.User) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "User", + 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.MediaItems, nil + } + directive1 := func(ctx context.Context) (interface{}, error) { + gorm, err := ec.unmarshalOString2ᚖstring(ctx, "foreignKey:UserID") + 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.([]*model.MediaItem); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be []*reichard.io/imagini/graph/model.MediaItem`, tmp) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]*model.MediaItem) + fc.Result = res + return ec.marshalOMediaItem2ᚕᚖreichardᚗioᚋimaginiᚋgraphᚋmodelᚐMediaItemᚄ(ctx, field.Selections, res) +} + func (ec *executionContext) _UserResponse_data(ctx context.Context, field graphql.CollectedField, obj *model.UserResponse) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -6931,26 +6865,6 @@ func (ec *executionContext) unmarshalInputNewAlbum(ctx context.Context, obj inte return it, nil } -func (ec *executionContext) unmarshalInputNewDevice(ctx context.Context, obj interface{}) (model.NewDevice, error) { - var it model.NewDevice - var asMap = obj.(map[string]interface{}) - - for k, v := range asMap { - switch k { - case "name": - var err error - - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name")) - it.Name, err = ec.unmarshalNString2string(ctx, v) - if err != nil { - return it, err - } - } - } - - return it, nil -} - func (ec *executionContext) unmarshalInputNewMediaItem(ctx context.Context, obj interface{}) (model.NewMediaItem, error) { var it model.NewMediaItem var asMap = obj.(map[string]interface{}) @@ -7610,11 +7524,6 @@ func (ec *executionContext) _Device(ctx context.Context, sel ast.SelectionSet, o if out.Values[i] == graphql.Null { invalids++ } - case "user": - out.Values[i] = ec._Device_user(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } case "refreshKey": out.Values[i] = ec._Device_refreshKey(ctx, field, obj) default: @@ -7707,11 +7616,6 @@ func (ec *executionContext) _MediaItem(ctx context.Context, sel ast.SelectionSet if out.Values[i] == graphql.Null { invalids++ } - case "user": - out.Values[i] = ec._MediaItem_user(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -7772,11 +7676,6 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } - case "createDevice": - out.Values[i] = ec._Mutation_createDevice(ctx, field) - if out.Values[i] == graphql.Null { - invalids++ - } case "createAlbum": out.Values[i] = ec._Mutation_createAlbum(ctx, field) if out.Values[i] == graphql.Null { @@ -8163,6 +8062,10 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj } case "password": out.Values[i] = ec._User_password(ctx, field, obj) + case "devices": + out.Values[i] = ec._User_devices(ctx, field, obj) + case "mediaItems": + out.Values[i] = ec._User_mediaItems(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -8641,11 +8544,6 @@ func (ec *executionContext) unmarshalNNewAlbum2reichardᚗioᚋimaginiᚋgraph return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) unmarshalNNewDevice2reichardᚗioᚋimaginiᚋgraphᚋmodelᚐNewDevice(ctx context.Context, v interface{}) (model.NewDevice, error) { - res, err := ec.unmarshalInputNewDevice(ctx, v) - return res, graphql.ErrorOnPath(ctx, err) -} - func (ec *executionContext) unmarshalNNewMediaItem2reichardᚗioᚋimaginiᚋgraphᚋmodelᚐNewMediaItem(ctx context.Context, v interface{}) (model.NewMediaItem, error) { res, err := ec.unmarshalInputNewMediaItem(ctx, v) return res, graphql.ErrorOnPath(ctx, err) @@ -9211,6 +9109,46 @@ func (ec *executionContext) marshalODevice2ᚕᚖreichardᚗioᚋimaginiᚋgraph return ret } +func (ec *executionContext) marshalODevice2ᚕᚖreichardᚗioᚋimaginiᚋgraphᚋmodelᚐDeviceᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.Device) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNDevice2ᚖreichardᚗioᚋimaginiᚋgraphᚋmodelᚐDevice(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + func (ec *executionContext) marshalODevice2ᚖreichardᚗioᚋimaginiᚋgraphᚋmodelᚐDevice(ctx context.Context, sel ast.SelectionSet, v *model.Device) graphql.Marshaler { if v == nil { return graphql.Null @@ -9523,6 +9461,46 @@ func (ec *executionContext) marshalOMediaItem2ᚕᚖreichardᚗioᚋimaginiᚋgr return ret } +func (ec *executionContext) marshalOMediaItem2ᚕᚖreichardᚗioᚋimaginiᚋgraphᚋmodelᚐMediaItemᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.MediaItem) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNMediaItem2ᚖreichardᚗioᚋimaginiᚋgraphᚋmodelᚐMediaItem(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + func (ec *executionContext) marshalOMediaItem2ᚖreichardᚗioᚋimaginiᚋgraphᚋmodelᚐMediaItem(ctx context.Context, sel ast.SelectionSet, v *model.MediaItem) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/graph/helpers.go b/graph/helpers.go new file mode 100644 index 0000000..e2b2c07 --- /dev/null +++ b/graph/helpers.go @@ -0,0 +1,74 @@ +package graph + +import ( + "net/http" + "strings" + "time" + + "github.com/dsoprea/go-exif/v3" + exifcommon "github.com/dsoprea/go-exif/v3/common" + "reichard.io/imagini/graph/model" +) + +func deriveDeviceType(r *http.Request) model.DeviceType { + userAgent := strings.ToLower(r.Header.Get("User-Agent")) + if strings.Contains(userAgent, "ios-imagini") { + return model.DeviceTypeIOs + } else if strings.Contains(userAgent, "android-imagini") { + return model.DeviceTypeAndroid + } else if strings.Contains(userAgent, "chrome") { + return model.DeviceTypeChrome + } else if strings.Contains(userAgent, "firefox") { + return model.DeviceTypeFirefox + } else if strings.Contains(userAgent, "msie") { + return model.DeviceTypeInternetExplorer + } else if strings.Contains(userAgent, "edge") { + return model.DeviceTypeEdge + } else if strings.Contains(userAgent, "safari") { + return model.DeviceTypeSafari + } + return model.DeviceTypeUnknown +} + +func mediaItemFromEXIFData(filePath string) (*model.MediaItem, error) { + rawExif, err := exif.SearchFileAndExtractExif(filePath) + entries, _, err := exif.GetFlatExifData(rawExif, nil) + + decLong := float64(1) + decLat := float64(1) + + mediaItem := &model.MediaItem{} + for _, v := range entries { + if v.TagName == "DateTimeOriginal" { + formattedTime, _ := time.Parse("2006:01:02 15:04:05", v.Formatted) + mediaItem.ExifDate = &formattedTime + } else if v.TagName == "GPSLatitude" { + latStruct := v.Value.([]exifcommon.Rational) + decLat *= deriveDecimalCoordinate( + latStruct[0].Numerator/latStruct[0].Denominator, + latStruct[1].Numerator/latStruct[1].Denominator, + float64(latStruct[2].Numerator)/float64(latStruct[2].Denominator), + ) + } else if v.TagName == "GPSLongitude" { + longStruct := v.Value.([]exifcommon.Rational) + decLong *= deriveDecimalCoordinate( + longStruct[0].Numerator/longStruct[0].Denominator, + longStruct[1].Numerator/longStruct[1].Denominator, + float64(longStruct[2].Numerator)/float64(longStruct[2].Denominator), + ) + } else if v.TagName == "GPSLatitudeRef" && v.Formatted == "S" { + decLat *= -1 + } else if v.TagName == "GPSLongitudeRef" && v.Formatted == "W" { + decLong *= -1 + } + } + + mediaItem.Latitude = &decLat + mediaItem.Longitude = &decLong + + return mediaItem, err +} + +func deriveDecimalCoordinate(degrees, minutes uint32, seconds float64) float64 { + return float64(degrees) + (float64(minutes) / 60) + (seconds / 3600) +} diff --git a/graph/model/models_gen.go b/graph/model/models_gen.go index d54d2f4..6b3fa1e 100644 --- a/graph/model/models_gen.go +++ b/graph/model/models_gen.go @@ -57,7 +57,6 @@ type Device struct { Name string `json:"name" gorm:"not null"` Type DeviceType `json:"type" gorm:"default:Unknown;not null"` UserID string `json:"userID" gorm:"not null"` - User *User `json:"user" gorm:"foreignKey:ID;references:UserID;not null"` RefreshKey *string `json:"refreshKey" ` } @@ -125,7 +124,6 @@ type MediaItem struct { Tags []*Tag `json:"tags" gorm:"many2many:media_tags"` Albums []*Album `json:"albums" gorm:"many2many:media_albums"` UserID string `json:"userID" gorm:"not null"` - User *User `json:"user" gorm:"foreignKey:ID;references:UserID;not null"` } type MediaItemFilter struct { @@ -152,10 +150,6 @@ type NewAlbum struct { Name string `json:"name" ` } -type NewDevice struct { - Name string `json:"name" ` -} - type NewMediaItem struct { File graphql.Upload `json:"file" ` Tags []string `json:"tags" ` @@ -239,16 +233,18 @@ type TimeFilter struct { } type User struct { - ID string `json:"id" gorm:"primaryKey;not null"` - CreatedAt *time.Time `json:"createdAt" ` - UpdatedAt *time.Time `json:"updatedAt" ` - Email string `json:"email" gorm:"not null;unique"` - Username string `json:"username" gorm:"not null;unique"` - FirstName *string `json:"firstName" ` - LastName *string `json:"lastName" ` - Role Role `json:"role" gorm:"default:User;not null"` - AuthType AuthType `json:"authType" gorm:"default:Local;not null"` - Password *string `json:"password" ` + ID string `json:"id" gorm:"primaryKey;not null"` + CreatedAt *time.Time `json:"createdAt" ` + UpdatedAt *time.Time `json:"updatedAt" ` + Email string `json:"email" gorm:"not null;unique"` + Username string `json:"username" gorm:"not null;unique"` + FirstName *string `json:"firstName" ` + LastName *string `json:"lastName" ` + Role Role `json:"role" gorm:"default:User;not null"` + AuthType AuthType `json:"authType" gorm:"default:Local;not null"` + Password *string `json:"password" ` + Devices []*Device `json:"devices" gorm:"foreignKey:UserID"` + MediaItems []*MediaItem `json:"mediaItems" gorm:"foreignKey:UserID"` } type UserFilter struct { diff --git a/graph/resolver.go b/graph/resolver.go index 4249b75..59a1c97 100644 --- a/graph/resolver.go +++ b/graph/resolver.go @@ -2,6 +2,7 @@ package graph import ( "reichard.io/imagini/internal/auth" + "reichard.io/imagini/internal/config" "reichard.io/imagini/internal/db" ) @@ -10,6 +11,7 @@ import ( // It serves as dependency injection for your app, add any dependencies you require here. type Resolver struct { - Auth *auth.AuthManager - DB *db.DBManager + Config *config.Config + Auth *auth.AuthManager + DB *db.DBManager } diff --git a/graph/schema.graphqls b/graph/schema.graphqls index 4d67510..2dc90a9 100644 --- a/graph/schema.graphqls +++ b/graph/schema.graphqls @@ -139,6 +139,21 @@ input AuthTypeFilter { # -------------------- Object Definitions -------------------- # ------------------------------------------------------------ +type User { + id: ID! @meta(gorm: "primaryKey;not null") + createdAt: Time + updatedAt: Time + email: String! @meta(gorm: "not null;unique") + username: String! @meta(gorm: "not null;unique") + firstName: String + lastName: String + role: Role! @meta(gorm: "default:User;not null") + authType: AuthType! @meta(gorm: "default:Local;not null") + password: String @isPrivate + devices: [Device!] @meta(gorm: "foreignKey:UserID") + mediaItems: [MediaItem!] @meta(gorm: "foreignKey:UserID") +} + type Device { id: ID! @meta(gorm: "primaryKey;not null") createdAt: Time @@ -146,23 +161,9 @@ type Device { name: String! @meta(gorm: "not null") type: DeviceType! @meta(gorm: "default:Unknown;not null") userID: ID! @meta(gorm: "not null") - user: User! @meta(gorm: "foreignKey:ID;references:UserID;not null") refreshKey: String @isPrivate } -type User { - id: ID! @meta(gorm: "primaryKey;not null") - createdAt: Time - updatedAt: Time - email: String! @meta(gorm: "not null;unique") - username: String! @meta(gorm: "not null;unique") - firstName: String - lastName: String - role: Role! @meta(gorm: "default:User;not null") - authType: AuthType! @meta(gorm: "default:Local;not null") - password: String @isPrivate -} - type MediaItem { id: ID! @meta(gorm: "primaryKey;not null") createdAt: Time @@ -176,21 +177,20 @@ type MediaItem { tags: [Tag] @meta(gorm: "many2many:media_tags") albums: [Album] @meta(gorm: "many2many:media_albums") userID: ID! @meta(gorm: "not null") - user: User! @meta(gorm: "foreignKey:ID;references:UserID;not null") } type Tag { - id: ID! @meta(gorm: "primaryKey;not null") + id: ID! @meta(gorm: "primaryKey;not null") createdAt: Time updatedAt: Time - name: String! @meta(gorm: "unique;not null") + name: String! @meta(gorm: "unique;not null") } type Album { - id: ID! @meta(gorm: "primaryKey;not null") + id: ID! @meta(gorm: "primaryKey;not null") createdAt: Time updatedAt: Time - name: String! @meta(gorm: "unique;not null") + name: String! @meta(gorm: "unique;not null") } # ------------------------------------------------------------ @@ -272,10 +272,6 @@ input NewUser { password: String } -input NewDevice { - name: String! -} - input NewMediaItem { file: Upload! tags: [ID!] @@ -377,7 +373,6 @@ type Query { type Mutation { createMediaItem(input: NewMediaItem!): MediaItem! @hasMinRole(role: User) - createDevice(input: NewDevice!): Device! @hasMinRole(role: User) createAlbum(input: NewAlbum!): Album! @hasMinRole(role: User) createTag(input: NewTag!): Tag! @hasMinRole(role: User) createUser(input: NewUser!): User! @hasMinRole(role: Admin) diff --git a/graph/schema.resolvers.go b/graph/schema.resolvers.go index 6f54faa..8d66e4e 100644 --- a/graph/schema.resolvers.go +++ b/graph/schema.resolvers.go @@ -4,22 +4,100 @@ package graph // will be copied through when generating and any unknown code will be moved to the end. import ( + "bytes" "context" + "errors" "fmt" + "io" "net/http" + "os" + "path" "strings" + "time" + "github.com/gabriel-vasile/mimetype" "github.com/google/uuid" "reichard.io/imagini/graph/generated" "reichard.io/imagini/graph/model" ) +// Done func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewMediaItem) (*model.MediaItem, error) { - panic(fmt.Errorf("not implemented")) -} + // Get Context + authContext := ctx.Value("auth").(*model.AuthContext) + accessToken := *authContext.AccessToken + userID, ok := accessToken.Get("sub") + if !ok { + return nil, errors.New("Upload Failed") + } -func (r *mutationResolver) CreateDevice(ctx context.Context, input model.NewDevice) (*model.Device, error) { - panic(fmt.Errorf("not implemented")) + // File header placeholder + fileHeader := make([]byte, 64) + + // Copy headers into the buffer + if _, err := input.File.File.Read(fileHeader); err != nil { + return nil, errors.New("Upload Failed") + } + + // Determine media type + fileMime := mimetype.Detect(fileHeader) + contentType := fileMime.String() + var isVideo bool + if strings.HasPrefix(contentType, "image/") { + isVideo = false + } else if strings.HasPrefix(contentType, "video/") { + isVideo = true + } else { + return nil, errors.New("Upload Failed") + } + + // Derive Folder & File Path + mediaItemID := uuid.New().String() + fileName := mediaItemID + fileMime.Extension() + folderPath := path.Join("/" + r.Config.DataPath + "/media/" + userID.(string)) + os.MkdirAll(folderPath, 0700) + filePath := path.Join(folderPath + "/" + fileName) + + // Create File + f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + 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") + } + + // Copy remaining file + _, err = io.Copy(f, input.File.File) + if err != nil { + return nil, errors.New("Upload Failed") + } + + // Create MediaItem From EXIF Data + mediaItem, err := mediaItemFromEXIFData(filePath) + if err != nil { + return nil, errors.New("Upload Failed") + } + + // Add Additional MediaItem Fields + mediaItem.ID = mediaItemID + mediaItem.UserID = userID.(string) + mediaItem.IsVideo = isVideo + mediaItem.FileName = fileName + mediaItem.OrigName = input.File.Filename + + // Create MediaItem in DB + err = r.DB.CreateMediaItem(mediaItem) + if err != nil { + return nil, errors.New("Upload Failed") + } + + // Success + return mediaItem, nil } func (r *mutationResolver) CreateAlbum(ctx context.Context, input model.NewAlbum) (*model.Album, error) { @@ -50,11 +128,17 @@ func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) } func (r *queryResolver) Login(ctx context.Context, user string, password string, deviceID *string) (*model.AuthResponse, error) { - // Set Cookie From Context + // Get Context authContext := ctx.Value("auth").(*model.AuthContext) resp := authContext.AuthResponse req := authContext.AuthRequest + // Clear All Cookies By Default + accessCookie := http.Cookie{Name: "AccessToken", Path: "/", HttpOnly: true, MaxAge: -1, Expires: time.Now().Add(-100 * time.Hour)} + refreshCookie := http.Cookie{Name: "RefreshToken", Path: "/", HttpOnly: true, MaxAge: -1, Expires: time.Now().Add(-100 * time.Hour)} + http.SetCookie(*resp, &accessCookie) + http.SetCookie(*resp, &refreshCookie) + // Do Login foundUser, success := r.Auth.AuthenticateUser(user, password) if !success { @@ -62,7 +146,7 @@ func (r *queryResolver) Login(ctx context.Context, user string, password string, } // Upsert Device - foundDevice := model.Device{} + foundDevice := model.Device{UserID: foundUser.ID} if deviceID != nil { parsedDeviceID, err := uuid.Parse(*deviceID) if err != nil { @@ -75,8 +159,6 @@ func (r *queryResolver) Login(ctx context.Context, user string, password string, } } else { foundDevice.Type = deriveDeviceType(req) - foundDevice.UserID = foundUser.ID - // TODO: foundDevice.User = &foundUser err := r.DB.CreateDevice(&foundDevice) if err != nil { return &model.AuthResponse{Result: model.AuthResultFailure}, nil @@ -94,18 +176,25 @@ func (r *queryResolver) Login(ctx context.Context, user string, password string, } // Set appropriate cookies - accessCookie := http.Cookie{Name: "AccessToken", Value: accessToken, Path: "/", HttpOnly: true} - refreshCookie := http.Cookie{Name: "RefreshToken", Value: refreshToken, Path: "/", HttpOnly: true} + accessCookie = http.Cookie{Name: "AccessToken", Value: accessToken, Path: "/", HttpOnly: true} + refreshCookie = http.Cookie{Name: "RefreshToken", Value: refreshToken, Path: "/", HttpOnly: true} http.SetCookie(*resp, &accessCookie) http.SetCookie(*resp, &refreshCookie) - // TODO: Prob bandaid - foundDevice.User = &foundUser return &model.AuthResponse{Result: model.AuthResultSuccess, Device: &foundDevice}, nil } func (r *queryResolver) Logout(ctx context.Context) (*model.AuthResponse, error) { - // panic(fmt.Errorf("not implemented")) + // Set Cookie From Context + authContext := ctx.Value("auth").(*model.AuthContext) + resp := authContext.AuthResponse + + // Clear All Cookies + accessCookie := http.Cookie{Name: "AccessToken", Path: "/", HttpOnly: true, MaxAge: -1, Expires: time.Now().Add(-100 * time.Hour)} + refreshCookie := http.Cookie{Name: "RefreshToken", Path: "/", HttpOnly: true, MaxAge: -1, Expires: time.Now().Add(-100 * time.Hour)} + http.SetCookie(*resp, &accessCookie) + http.SetCookie(*resp, &refreshCookie) + return &model.AuthResponse{Result: model.AuthResultSuccess}, nil } @@ -172,29 +261,3 @@ func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } type mutationResolver struct{ *Resolver } type queryResolver struct{ *Resolver } - -// !!! WARNING !!! -// The code below was going to be deleted when updating resolvers. It has been copied here so you have -// one last chance to move it out of harms way if you want. There are two reasons this happens: -// - When renaming or deleting a resolver the old code will be put in here. You can safely delete -// it when you're done. -// - You have helper methods in this file. Move them out to keep these resolver files clean. -func deriveDeviceType(r *http.Request) model.DeviceType { - userAgent := strings.ToLower(r.Header.Get("User-Agent")) - if strings.Contains(userAgent, "ios-imagini") { - return model.DeviceTypeIOs - } else if strings.Contains(userAgent, "android-imagini") { - return model.DeviceTypeAndroid - } else if strings.Contains(userAgent, "chrome") { - return model.DeviceTypeChrome - } else if strings.Contains(userAgent, "firefox") { - return model.DeviceTypeFirefox - } else if strings.Contains(userAgent, "msie") { - return model.DeviceTypeInternetExplorer - } else if strings.Contains(userAgent, "edge") { - return model.DeviceTypeEdge - } else if strings.Contains(userAgent, "safari") { - return model.DeviceTypeSafari - } - return model.DeviceTypeUnknown -} diff --git a/internal/api/media_item.go b/internal/api/media_item.go new file mode 100644 index 0000000..778f64e --- /dev/null +++ b/internal/api/media_item.go @@ -0,0 +1 @@ +package api diff --git a/internal/api/routes.go b/internal/api/routes.go index 2e342f2..265c9fc 100644 --- a/internal/api/routes.go +++ b/internal/api/routes.go @@ -12,8 +12,9 @@ func (api *API) registerRoutes() { // Set up Directives graphConfig := generated.Config{ Resolvers: &graph.Resolver{ - DB: api.DB, - Auth: api.Auth, + DB: api.DB, + Auth: api.Auth, + Config: api.Config, }, Directives: generated.DirectiveRoot{ Meta: api.metaDirective, diff --git a/internal/db/db.go b/internal/db/db.go index cda921c..4121c42 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -48,9 +48,28 @@ func NewMgr(c *config.Config) *DBManager { dbm.bootstrapDatabase() } + dbm.testFeatures() + return dbm } +func (dbm *DBManager) testFeatures() { + + // Get Devices By UserID + // var myDevices []model.Device + // dbm.db.Debug().Where(&model.Device{UserID: "97589354-cd42-40e2-bc5e-7ba6badf89fa"}).Find(&myDevices) + // fmt.Printf("Devices: %+v\n", myDevices) + + // Get User by DeviceID + // var myUser []model.User + // dbm.db.Debug().Model(&model.User{}). + // Select("users.*"). + // Joins("left join devices on users.id = devices.user_id"). + // Where("devices.id = ?", "4e9aa851-f25b-4330-91dc-c0f975e56fa1"). + // Find(&myUser) + // fmt.Printf("User: %+v\n", myUser) +} + func (dbm *DBManager) bootstrapDatabase() { log.Info("[query] Bootstrapping database.")