diff --git a/go.mod b/go.mod index ffc0ec8..a9d9bd5 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/ory/dockertest/v3 v3.10.0 github.com/sendgrid/rest v2.6.9+incompatible github.com/sendgrid/sendgrid-go v3.14.0+incompatible + github.com/thanhpk/randstr v1.0.6 github.com/vektah/gqlparser/v2 v2.5.11 golang.org/x/crypto v0.21.0 ) @@ -63,7 +64,6 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/stretchr/testify v1.8.4 // indirect - github.com/thanhpk/randstr v1.0.6 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect diff --git a/graph/event.graphql b/graph/event.graphql index 278af46..b2221d6 100644 --- a/graph/event.graphql +++ b/graph/event.graphql @@ -1,5 +1,6 @@ extend type Query { - allEvents(filter: AllEventsFilter!): [Event!]! @isAuthenticated + allEvents(filter: AllEventsFilter!): [Event!]! + myEvents(userId: Int!): [Event!]! } extend type Mutation { diff --git a/graph/generated.go b/graph/generated.go index c422108..1dc0ead 100644 --- a/graph/generated.go +++ b/graph/generated.go @@ -213,6 +213,7 @@ type ComplexityRoot struct { GoogleOAuth func(childComplexity int, accessToken string, ipAddress *string) int Login func(childComplexity int, email string, password string, ipAddress *string) int Me func(childComplexity int) int + MyEvents func(childComplexity int, userID int) int } UpdatedByUser struct { @@ -256,6 +257,7 @@ type MutationResolver interface { type QueryResolver interface { GetAllCountries(ctx context.Context) ([]*gmodel.Country, error) AllEvents(ctx context.Context, filter gmodel.AllEventsFilter) ([]*gmodel.Event, error) + MyEvents(ctx context.Context, userID int) ([]*gmodel.Event, error) Login(ctx context.Context, email string, password string, ipAddress *string) (*gmodel.Auth, error) GoogleOAuth(ctx context.Context, accessToken string, ipAddress *string) (*gmodel.Auth, error) Me(ctx context.Context) (*gmodel.User, error) @@ -1172,6 +1174,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Me(childComplexity), true + case "Query.myEvents": + if e.complexity.Query.MyEvents == nil { + break + } + + args, err := ec.field_Query_myEvents_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.MyEvents(childComplexity, args["userId"].(int)), true + case "UpdatedByUser.active": if e.complexity.UpdatedByUser.Active == nil { break @@ -1629,6 +1643,21 @@ func (ec *executionContext) field_Query_login_args(ctx context.Context, rawArgs return args, nil } +func (ec *executionContext) field_Query_myEvents_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 int + if tmp, ok := rawArgs["userId"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("userId")) + arg0, err = ec.unmarshalNInt2int(ctx, tmp) + if err != nil { + return nil, err + } + } + args["userId"] = arg0 + return args, nil +} + func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -7288,28 +7317,97 @@ func (ec *executionContext) _Query_allEvents(ctx context.Context, field graphql. } }() 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.Query().AllEvents(rctx, fc.Args["filter"].(gmodel.AllEventsFilter)) + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().AllEvents(rctx, fc.Args["filter"].(gmodel.AllEventsFilter)) + }) + 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") } - directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.IsAuthenticated == nil { - return nil, errors.New("directive isAuthenticated is not implemented") + return graphql.Null + } + res := resTmp.([]*gmodel.Event) + fc.Result = res + return ec.marshalNEvent2ᚕᚖgithubᚗcomᚋstadioᚑappᚋstadioᚑbackendᚋgraphᚋgmodelᚐEventᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_allEvents(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Event_id(ctx, field) + case "createdAt": + return ec.fieldContext_Event_createdAt(ctx, field) + case "updatedAt": + return ec.fieldContext_Event_updatedAt(ctx, field) + case "name": + return ec.fieldContext_Event_name(ctx, field) + case "description": + return ec.fieldContext_Event_description(ctx, field) + case "type": + return ec.fieldContext_Event_type(ctx, field) + case "startDate": + return ec.fieldContext_Event_startDate(ctx, field) + case "endDate": + return ec.fieldContext_Event_endDate(ctx, field) + case "locationId": + return ec.fieldContext_Event_locationId(ctx, field) + case "location": + return ec.fieldContext_Event_location(ctx, field) + case "locationInstanceId": + return ec.fieldContext_Event_locationInstanceId(ctx, field) + case "createdById": + return ec.fieldContext_Event_createdById(ctx, field) + case "createdBy": + return ec.fieldContext_Event_createdBy(ctx, field) + case "updatedById": + return ec.fieldContext_Event_updatedById(ctx, field) + case "updatedBy": + return ec.fieldContext_Event_updatedBy(ctx, field) + case "approved": + return ec.fieldContext_Event_approved(ctx, field) } - return ec.directives.IsAuthenticated(ctx, nil, directive0) + return nil, fmt.Errorf("no field named %q was found under type Event", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_allEvents_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} - tmp, err := directive1(rctx) - if err != nil { - return nil, graphql.ErrorOnPath(ctx, err) - } - if tmp == nil { - return nil, nil - } - if data, ok := tmp.([]*gmodel.Event); ok { - return data, nil +func (ec *executionContext) _Query_myEvents(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_myEvents(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null } - return nil, fmt.Errorf(`unexpected type %T from directive, should be []*github.com/stadio-app/stadio-backend/graph/gmodel.Event`, tmp) + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().MyEvents(rctx, fc.Args["userId"].(int)) }) if err != nil { ec.Error(ctx, err) @@ -7326,7 +7424,7 @@ func (ec *executionContext) _Query_allEvents(ctx context.Context, field graphql. return ec.marshalNEvent2ᚕᚖgithubᚗcomᚋstadioᚑappᚋstadioᚑbackendᚋgraphᚋgmodelᚐEventᚄ(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_Query_allEvents(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Query_myEvents(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Query", Field: field, @@ -7377,7 +7475,7 @@ func (ec *executionContext) fieldContext_Query_allEvents(ctx context.Context, fi } }() ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Query_allEvents_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + if fc.Args, err = ec.field_Query_myEvents_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { ec.Error(ctx, err) return fc, err } @@ -11865,6 +11963,28 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "myEvents": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_myEvents(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) case "login": field := field diff --git a/graph/resolver/event.resolvers.go b/graph/resolver/event.resolvers.go index 2fe67b7..9046edd 100644 --- a/graph/resolver/event.resolvers.go +++ b/graph/resolver/event.resolvers.go @@ -9,6 +9,7 @@ import ( "github.com/stadio-app/stadio-backend/graph" "github.com/stadio-app/stadio-backend/graph/gmodel" + "github.com/stadio-app/stadio-backend/utils" ) // CreateEvent is the resolver for the createEvent field. @@ -27,11 +28,16 @@ func (r *queryResolver) AllEvents(ctx context.Context, filter gmodel.AllEventsFi if err != nil { return nil, err } - event_ptrs := make([]*gmodel.Event, len(events)) - for i := range events { - event_ptrs[i] = &events[i] + return utils.PointersOf(events).([]*gmodel.Event), nil +} + +// MyEvents is the resolver for the myEvents field. +func (r *queryResolver) MyEvents(ctx context.Context, userID int) ([]*gmodel.Event, error) { + events, err := r.Service.MyEvents(ctx, int64(userID)) + if err != nil { + return nil, err } - return event_ptrs, nil + return utils.PointersOf(events).([]*gmodel.Event), nil } // Mutation returns graph.MutationResolver implementation. diff --git a/graph/resolver/user.resolvers.go b/graph/resolver/user.resolvers.go index f19d5cf..4d676dc 100644 --- a/graph/resolver/user.resolvers.go +++ b/graph/resolver/user.resolvers.go @@ -66,7 +66,7 @@ func (r *mutationResolver) UpdateProfile(ctx context.Context, input gmodel.Updat if input.Avatar != nil && updated_user.Avatar != nil { _, err := r.Service.GraphImageUpload(ctx, *input.Avatar, uploader.UploadParams{ PublicID: *updated_user.Avatar, - Tags: []string{"USER_PROFILE"}, + Tags: []string{"USER_PROFILE"}, }) if err != nil { return nil, fmt.Errorf("could not upload avatar to CDN") diff --git a/services/event_service.go b/services/event_service.go index 08e5432..27ff500 100644 --- a/services/event_service.go +++ b/services/event_service.go @@ -40,15 +40,15 @@ func (service Service) CreateEvent(ctx context.Context, user gmodel.User, input table.Event.UpdatedByID, ). MODEL(model.Event{ - LocationID: &input.LocationID, - Name: input.Name, - Description: input.Description, - Type: &input.Type, - StartDate: input.StartDate, - EndDate: input.EndDate, + LocationID: &input.LocationID, + Name: input.Name, + Description: input.Description, + Type: &input.Type, + StartDate: input.StartDate, + EndDate: input.EndDate, LocationInstanceID: &location_instances[0].ID, - CreatedByID: &user.ID, - UpdatedByID: &user.ID, + CreatedByID: &user.ID, + UpdatedByID: &user.ID, }).RETURNING(table.Event.AllColumns) var new_event gmodel.EventShallow if err := qb.QueryContext(ctx, db, &new_event); err != nil { @@ -91,7 +91,7 @@ func (service Service) FindAllEvents(ctx context.Context, filter gmodel.AllEvent INNER_JOIN(table.Location, table.Location.ID.EQ(table.Event.LocationID)). INNER_JOIN(table.Address, table.Address.ID.EQ(table.Location.AddressID)). INNER_JOIN(table.Country, table.Country.Code.EQ(table.Address.CountryCode)). - LEFT_JOIN(table.LocationImage, table.LocationImage.LocationID.EQ(table.Location.ID)). + LEFT_JOIN(table.LocationImage, table.LocationImage.LocationID.EQ(table.Location.ID)). LEFT_JOIN(created_by_user_table, created_by_user_table.ID.EQ(table.Event.CreatedByID)). LEFT_JOIN(updated_by_user_table, updated_by_user_table.ID.EQ(table.Event.CreatedByID)), ). @@ -123,3 +123,37 @@ func (service Service) FindAllEvents(ctx context.Context, filter gmodel.AllEvent } return events, nil } + +func (service Service) MyEvents(ctx context.Context, user_id int64) ([]gmodel.Event, error) { + query := table.Event. + SELECT( + table.Event.AllColumns, + table.Participant.AllColumns, + table.Location.AllColumns, + table.Address.AllColumns, + table.Country.Name, + table.LocationImage.AllColumns, + table.User.ID.AS("created_by_user_id"), + table.User.Name.AS("created_by_name"), + table.User.Avatar.AS("created_by_avatar"), + ).FROM( + table.Event. + INNER_JOIN(table.Location, table.Location.ID.EQ(table.Event.LocationID)). + INNER_JOIN(table.Address, table.Address.ID.EQ(table.Location.AddressID)). + INNER_JOIN(table.Country, table.Country.Code.EQ(table.Address.CountryCode)). + LEFT_JOIN(table.Participant, table.Participant.EventID.EQ(table.Event.ID)). + LEFT_JOIN(table.LocationImage, table.LocationImage.LocationID.EQ(table.Location.ID)). + LEFT_JOIN(table.User, table.User.ID.EQ(table.Event.CreatedByID)), + ).WHERE( + table.Participant.UserID.EQ(postgres.Int64(user_id)), + ).ORDER_BY( + table.Event.StartDate.ASC(), + ) + + db := service.DbOrTxQueryable() + var events []gmodel.Event + if err := query.QueryContext(ctx, db, &events); err != nil { + return nil, err + } + return events, nil +} diff --git a/setup/server_setup.go b/setup/server_setup.go index a58a123..8e9dcc0 100644 --- a/setup/server_setup.go +++ b/setup/server_setup.go @@ -21,9 +21,9 @@ const GRAPH_ENDPOINT string = "/graphql" func NewServer(db_conn *sql.DB, router *chi.Mux) *types.ServerBase { server := types.ServerBase{ - DB: db_conn, - Router: router, - StructValidator: validator.New(), + DB: db_conn, + Router: router, + StructValidator: validator.New(), MigrationDirectory: "./database/migrations", } @@ -50,11 +50,11 @@ func NewServer(db_conn *sql.DB, router *chi.Mux) *types.ServerBase { sendgrid_client := sendgrid.NewSendClient(tokens.SendGrid.ApiKey) service := services.Service{ - DB: server.DB, + DB: server.DB, StructValidator: server.StructValidator, - Tokens: &tokens, - Cloudinary: cloudinary, - Sendgrid: sendgrid_client, + Tokens: &tokens, + Cloudinary: cloudinary, + Sendgrid: sendgrid_client, } // TODO: only show in dev environment @@ -64,7 +64,7 @@ func NewServer(db_conn *sql.DB, router *chi.Mux) *types.ServerBase { c := graph.Config{} c.Resolvers = &gresolver.Resolver{ AppContext: server, - Service: service, + Service: service, } c.Directives.IsAuthenticated = service.IsAuthenticatedDirective graphql_handler := handler.NewDefaultServer(graph.NewExecutableSchema(c)) diff --git a/utils/types.go b/utils/types.go new file mode 100644 index 0000000..48be737 --- /dev/null +++ b/utils/types.go @@ -0,0 +1,12 @@ +package utils + +import "reflect" + +func PointersOf(v interface{}) interface{} { + in := reflect.ValueOf(v) + out := reflect.MakeSlice(reflect.SliceOf(reflect.PointerTo(in.Type().Elem())), in.Len(), in.Len()) + for i := 0; i < in.Len(); i++ { + out.Index(i).Set(in.Index(i).Addr()) + } + return out.Interface() +}