Peter Sanchez: 1 Adding GraphQL calls for audit logs. 7 files changed, 1529 insertions(+), 375 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.code.netlandish.com/~netlandish/links-dev/patches/108/mbox | git am -3Learn more about email & git
GraphQL api version bump Changelog-added: GraphQL calls for audit logs Changelog-changed: GraphQL api version: minor version bump --- This is the first step in adding auditlog to the user interface. Just misisng now is a few handlers for cases where they are visible. api/gqlgen.yml | 3 + api/graph/generated.go | 1171 ++++++++++++++++++++++++++++----- api/graph/model/models_gen.go | 15 + api/graph/schema.graphqls | 28 + api/graph/schema.resolvers.go | 681 ++++++++++++------- helpers.go | 4 + templates/restricted.html | 2 +- 7 files changed, 1529 insertions(+), 375 deletions(-) diff --git a/api/gqlgen.yml b/api/gqlgen.yml index 0c5c4f0..090a18f 100644 --- a/api/gqlgen.yml +++ b/api/gqlgen.yml @@ -64,3 +64,6 @@ models: - github.com/99designs/gqlgen/graphql.Int - github.com/99designs/gqlgen/graphql.Int64 - github.com/99designs/gqlgen/graphql.Int32 + AuditLog: + model: + - netlandish.com/x/gobwebs-auditlog.AuditLog diff --git a/api/graph/generated.go b/api/graph/generated.go index 633087f..1ebec44 100644 --- a/api/graph/generated.go +++ b/api/graph/generated.go @@ -19,6 +19,7 @@ import ( "github.com/99designs/gqlgen/graphql/introspection" gqlparser "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" + "netlandish.com/x/gobwebs-auditlog" ) // region ************************** generated!.gotpl ************************** @@ -41,6 +42,7 @@ type Config struct { } type ResolverRoot interface { + AuditLog() AuditLogResolver BillingSettings() BillingSettingsResolver Domain() DomainResolver Mutation() MutationResolver @@ -93,6 +95,20 @@ type ComplexityRoot struct { Title func(childComplexity int) int } + AuditLog struct { + CreatedOn func(childComplexity int) int + Details func(childComplexity int) int + EventType func(childComplexity int) int + IPAddress func(childComplexity int) int + Metadata func(childComplexity int) int + UserID func(childComplexity int) int + } + + AuditLogCursor struct { + PageInfo func(childComplexity int) int + Result func(childComplexity int) int + } + BaseURL struct { Counter func(childComplexity int) int CreatedOn func(childComplexity int) int @@ -369,6 +385,7 @@ type ComplexityRoot struct { GetAdminDomains func(childComplexity int, input *model.GetAdminDomainInput) int GetAdminOrgStats func(childComplexity int, id int) int GetAdminOrganizations func(childComplexity int, input *model.GetAdminOrganizationsInput) int + GetAuditLogs func(childComplexity int, input *model.AuditLogInput) int GetDomain func(childComplexity int, id int) int GetDomains func(childComplexity int, orgSlug *string, service *model.DomainService) int GetFeed func(childComplexity int, input *model.GetFeedInput) int @@ -437,6 +454,9 @@ type ComplexityRoot struct { } } +type AuditLogResolver interface { + Metadata(ctx context.Context, obj *auditlog.AuditLog) (map[string]interface{}, error) +} type BillingSettingsResolver interface { Status(ctx context.Context, obj *models.BillingSettings) (model.OrgBillingStatus, error) } @@ -449,6 +469,7 @@ type DomainResolver interface { } type MutationResolver interface { AddOrganization(ctx context.Context, input model.OrganizationInput) (*models.Organization, error) + UpdateOrganization(ctx context.Context, input *model.UpdateOrganizationInput) (*models.Organization, error) AddLink(ctx context.Context, input *model.LinkInput) (*models.OrgLink, error) UpdateLink(ctx context.Context, input *model.UpdateLinkInput) (*models.OrgLink, error) DeleteLink(ctx context.Context, hash string) (*model.DeletePayload, error) @@ -459,7 +480,6 @@ type MutationResolver interface { Register(ctx context.Context, input *model.RegisterInput) (*models.User, error) CompleteRegister(ctx context.Context, input *model.CompleteRegisterInput) (*models.User, error) UpdateProfile(ctx context.Context, input *model.ProfileInput) (*models.User, error) - UpdateOrganization(ctx context.Context, input *model.UpdateOrganizationInput) (*models.Organization, error) AddDomain(ctx context.Context, input model.DomainInput) (*models.Domain, error) DeleteDomain(ctx context.Context, id int) (*model.DeletePayload, error) AddLinkShort(ctx context.Context, input *model.LinkShortInput) (*models.LinkShort, error) @@ -521,6 +541,7 @@ type QueryResolver interface { Analytics(ctx context.Context, input model.AnalyticsInput) (*model.Analytics, error) GetFeed(ctx context.Context, input *model.GetFeedInput) (*model.OrgLinkCursor, error) GetFeedFollowing(ctx context.Context) ([]*models.Organization, error) + GetAuditLogs(ctx context.Context, input *model.AuditLogInput) (*model.AuditLogCursor, error) GetUsers(ctx context.Context, input *model.GetUserInput) (*model.UserCursor, error) GetUser(ctx context.Context, id int) (*models.User, error) GetAdminOrganizations(ctx context.Context, input *model.GetAdminOrganizationsInput) (*model.OrganizationCursor, error) @@ -691,6 +712,62 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Analytics.Title(childComplexity), true + case "AuditLog.createdOn": + if e.complexity.AuditLog.CreatedOn == nil { + break + } + + return e.complexity.AuditLog.CreatedOn(childComplexity), true + + case "AuditLog.details": + if e.complexity.AuditLog.Details == nil { + break + } + + return e.complexity.AuditLog.Details(childComplexity), true + + case "AuditLog.eventType": + if e.complexity.AuditLog.EventType == nil { + break + } + + return e.complexity.AuditLog.EventType(childComplexity), true + + case "AuditLog.ipAddress": + if e.complexity.AuditLog.IPAddress == nil { + break + } + + return e.complexity.AuditLog.IPAddress(childComplexity), true + + case "AuditLog.metadata": + if e.complexity.AuditLog.Metadata == nil { + break + } + + return e.complexity.AuditLog.Metadata(childComplexity), true + + case "AuditLog.userId": + if e.complexity.AuditLog.UserID == nil { + break + } + + return e.complexity.AuditLog.UserID(childComplexity), true + + case "AuditLogCursor.pageInfo": + if e.complexity.AuditLogCursor.PageInfo == nil { + break + } + + return e.complexity.AuditLogCursor.PageInfo(childComplexity), true + + case "AuditLogCursor.result": + if e.complexity.AuditLogCursor.Result == nil { + break + } + + return e.complexity.AuditLogCursor.Result(childComplexity), true + case "BaseURL.counter": if e.complexity.BaseURL.Counter == nil { break @@ -2192,6 +2269,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.GetAdminOrganizations(childComplexity, args["input"].(*model.GetAdminOrganizationsInput)), true + case "Query.getAuditLogs": + if e.complexity.Query.GetAuditLogs == nil { + break + } + + args, err := ec.field_Query_getAuditLogs_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.GetAuditLogs(childComplexity, args["input"].(*model.AuditLogInput)), true + case "Query.getDomain": if e.complexity.Query.GetDomain == nil { break @@ -2630,6 +2719,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputAdminBillingInput, ec.unmarshalInputAdminDomainInput, ec.unmarshalInputAnalyticsInput, + ec.unmarshalInputAuditLogInput, ec.unmarshalInputCompleteRegisterInput, ec.unmarshalInputDomainInput, ec.unmarshalInputGetAdminDomainInput, @@ -4134,6 +4224,38 @@ func (ec *executionContext) field_Query_getAdminOrganizations_argsInput( return zeroVal, nil } +func (ec *executionContext) field_Query_getAuditLogs_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + arg0, err := ec.field_Query_getAuditLogs_argsInput(ctx, rawArgs) + if err != nil { + return nil, err + } + args["input"] = arg0 + return args, nil +} +func (ec *executionContext) field_Query_getAuditLogs_argsInput( + ctx context.Context, + rawArgs map[string]interface{}, +) (*model.AuditLogInput, error) { + // We won't call the directive if the argument is null. + // Set call_argument_directives_with_null to true to call directives + // even if the argument is null. + _, ok := rawArgs["input"] + if !ok { + var zeroVal *model.AuditLogInput + return zeroVal, nil + } + + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) + if tmp, ok := rawArgs["input"]; ok { + return ec.unmarshalOAuditLogInput2ᚖlinksᚋapiᚋgraphᚋmodelᚐAuditLogInput(ctx, tmp) + } + + var zeroVal *model.AuditLogInput + return zeroVal, nil +} + func (ec *executionContext) field_Query_getDomain_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -5949,6 +6071,371 @@ func (ec *executionContext) fieldContext_Analytics_qrList(_ context.Context, fie return fc, nil } +func (ec *executionContext) _AuditLog_userId(ctx context.Context, field graphql.CollectedField, obj *auditlog.AuditLog) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_AuditLog_userId(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 + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.UserID, nil + }) + 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) fieldContext_AuditLog_userId(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "AuditLog", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Int does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _AuditLog_ipAddress(ctx context.Context, field graphql.CollectedField, obj *auditlog.AuditLog) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_AuditLog_ipAddress(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 + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.IPAddress, nil + }) + 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.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_AuditLog_ipAddress(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "AuditLog", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _AuditLog_eventType(ctx context.Context, field graphql.CollectedField, obj *auditlog.AuditLog) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_AuditLog_eventType(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 + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.EventType, nil + }) + 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.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_AuditLog_eventType(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "AuditLog", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _AuditLog_details(ctx context.Context, field graphql.CollectedField, obj *auditlog.AuditLog) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_AuditLog_details(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 + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Details, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalOString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_AuditLog_details(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "AuditLog", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _AuditLog_metadata(ctx context.Context, field graphql.CollectedField, obj *auditlog.AuditLog) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_AuditLog_metadata(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 + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.AuditLog().Metadata(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(map[string]interface{}) + fc.Result = res + return ec.marshalOMap2map(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_AuditLog_metadata(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "AuditLog", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Map does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _AuditLog_createdOn(ctx context.Context, field graphql.CollectedField, obj *auditlog.AuditLog) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_AuditLog_createdOn(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 + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.CreatedOn, nil + }) + 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.(time.Time) + fc.Result = res + return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_AuditLog_createdOn(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "AuditLog", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Time does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _AuditLogCursor_result(ctx context.Context, field graphql.CollectedField, obj *model.AuditLogCursor) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_AuditLogCursor_result(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 + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Result, nil + }) + 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.([]*auditlog.AuditLog) + fc.Result = res + return ec.marshalNAuditLog2ᚕᚖnetlandishᚗcomᚋxᚋgobwebsᚑauditlogᚐAuditLog(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_AuditLogCursor_result(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "AuditLogCursor", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "userId": + return ec.fieldContext_AuditLog_userId(ctx, field) + case "ipAddress": + return ec.fieldContext_AuditLog_ipAddress(ctx, field) + case "eventType": + return ec.fieldContext_AuditLog_eventType(ctx, field) + case "details": + return ec.fieldContext_AuditLog_details(ctx, field) + case "metadata": + return ec.fieldContext_AuditLog_metadata(ctx, field) + case "createdOn": + return ec.fieldContext_AuditLog_createdOn(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type AuditLog", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _AuditLogCursor_pageInfo(ctx context.Context, field graphql.CollectedField, obj *model.AuditLogCursor) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_AuditLogCursor_pageInfo(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 + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.PageInfo, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*model.PageInfo) + fc.Result = res + return ec.marshalOPageInfo2ᚖlinksᚋapiᚋgraphᚋmodelᚐPageInfo(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_AuditLogCursor_pageInfo(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "AuditLogCursor", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "cursor": + return ec.fieldContext_PageInfo_cursor(ctx, field) + case "hasNextPage": + return ec.fieldContext_PageInfo_hasNextPage(ctx, field) + case "hasPrevPage": + return ec.fieldContext_PageInfo_hasPrevPage(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type PageInfo", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _BaseURL_id(ctx context.Context, field graphql.CollectedField, obj *models.BaseURL) (ret graphql.Marshaler) { fc, err := ec.fieldContext_BaseURL_id(ctx, field) if err != nil { @@ -10334,7 +10821,120 @@ func (ec *executionContext) _Mutation_addOrganization(ctx context.Context, field return ec.marshalNOrganization2ᚖlinksᚋmodelsᚐOrganization(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_Mutation_addOrganization(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Mutation_addOrganization(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + 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_Organization_id(ctx, field) + case "ownerId": + return ec.fieldContext_Organization_ownerId(ctx, field) + case "orgType": + return ec.fieldContext_Organization_orgType(ctx, field) + case "name": + return ec.fieldContext_Organization_name(ctx, field) + case "slug": + return ec.fieldContext_Organization_slug(ctx, field) + case "image": + return ec.fieldContext_Organization_image(ctx, field) + case "timezone": + return ec.fieldContext_Organization_timezone(ctx, field) + case "settings": + return ec.fieldContext_Organization_settings(ctx, field) + case "isActive": + return ec.fieldContext_Organization_isActive(ctx, field) + case "createdOn": + return ec.fieldContext_Organization_createdOn(ctx, field) + case "updatedOn": + return ec.fieldContext_Organization_updatedOn(ctx, field) + case "ownerName": + return ec.fieldContext_Organization_ownerName(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Organization", 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_Mutation_addOrganization_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Mutation_updateOrganization(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_updateOrganization(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 + } + }() + 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().UpdateOrganization(rctx, fc.Args["input"].(*model.UpdateOrganizationInput)) + } + + directive1 := func(ctx context.Context) (interface{}, error) { + scope, err := ec.unmarshalNAccessScope2linksᚋapiᚋgraphᚋmodelᚐAccessScope(ctx, "ORGS") + if err != nil { + var zeroVal *models.Organization + return zeroVal, err + } + kind, err := ec.unmarshalNAccessKind2linksᚋapiᚋgraphᚋmodelᚐAccessKind(ctx, "RW") + if err != nil { + var zeroVal *models.Organization + return zeroVal, err + } + if ec.directives.Access == nil { + var zeroVal *models.Organization + return zeroVal, errors.New("directive access is not implemented") + } + return ec.directives.Access(ctx, nil, directive0, scope, kind) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(*models.Organization); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be *links/models.Organization`, 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.(*models.Organization) + fc.Result = res + return ec.marshalNOrganization2ᚖlinksᚋmodelsᚐOrganization(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_updateOrganization(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Mutation", Field: field, @@ -10377,7 +10977,7 @@ func (ec *executionContext) fieldContext_Mutation_addOrganization(ctx context.Co } }() ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Mutation_addOrganization_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + if fc.Args, err = ec.field_Mutation_updateOrganization_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { ec.Error(ctx, err) return fc, err } @@ -11438,119 +12038,6 @@ func (ec *executionContext) fieldContext_Mutation_updateProfile(ctx context.Cont return fc, nil } -func (ec *executionContext) _Mutation_updateOrganization(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Mutation_updateOrganization(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 - } - }() - 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().UpdateOrganization(rctx, fc.Args["input"].(*model.UpdateOrganizationInput)) - } - - directive1 := func(ctx context.Context) (interface{}, error) { - scope, err := ec.unmarshalNAccessScope2linksᚋapiᚋgraphᚋmodelᚐAccessScope(ctx, "ORGS") - if err != nil { - var zeroVal *models.Organization - return zeroVal, err - } - kind, err := ec.unmarshalNAccessKind2linksᚋapiᚋgraphᚋmodelᚐAccessKind(ctx, "RW") - if err != nil { - var zeroVal *models.Organization - return zeroVal, err - } - if ec.directives.Access == nil { - var zeroVal *models.Organization - return zeroVal, errors.New("directive access is not implemented") - } - return ec.directives.Access(ctx, nil, directive0, scope, kind) - } - - tmp, err := directive1(rctx) - if err != nil { - return nil, graphql.ErrorOnPath(ctx, err) - } - if tmp == nil { - return nil, nil - } - if data, ok := tmp.(*models.Organization); ok { - return data, nil - } - return nil, fmt.Errorf(`unexpected type %T from directive, should be *links/models.Organization`, 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.(*models.Organization) - fc.Result = res - return ec.marshalNOrganization2ᚖlinksᚋmodelsᚐOrganization(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_Mutation_updateOrganization(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "Mutation", - 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_Organization_id(ctx, field) - case "ownerId": - return ec.fieldContext_Organization_ownerId(ctx, field) - case "orgType": - return ec.fieldContext_Organization_orgType(ctx, field) - case "name": - return ec.fieldContext_Organization_name(ctx, field) - case "slug": - return ec.fieldContext_Organization_slug(ctx, field) - case "image": - return ec.fieldContext_Organization_image(ctx, field) - case "timezone": - return ec.fieldContext_Organization_timezone(ctx, field) - case "settings": - return ec.fieldContext_Organization_settings(ctx, field) - case "isActive": - return ec.fieldContext_Organization_isActive(ctx, field) - case "createdOn": - return ec.fieldContext_Organization_createdOn(ctx, field) - case "updatedOn": - return ec.fieldContext_Organization_updatedOn(ctx, field) - case "ownerName": - return ec.fieldContext_Organization_ownerName(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type Organization", 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_Mutation_updateOrganization_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { - ec.Error(ctx, err) - return fc, err - } - return fc, nil -} - func (ec *executionContext) _Mutation_addDomain(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Mutation_addDomain(ctx, field) if err != nil { @@ -19682,6 +20169,99 @@ func (ec *executionContext) fieldContext_Query_getFeedFollowing(_ context.Contex return fc, nil } +func (ec *executionContext) _Query_getAuditLogs(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_getAuditLogs(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 + } + }() + 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().GetAuditLogs(rctx, fc.Args["input"].(*model.AuditLogInput)) + } + + directive1 := func(ctx context.Context) (interface{}, error) { + scope, err := ec.unmarshalNAccessScope2linksᚋapiᚋgraphᚋmodelᚐAccessScope(ctx, "PROFILE") + if err != nil { + var zeroVal *model.AuditLogCursor + return zeroVal, err + } + kind, err := ec.unmarshalNAccessKind2linksᚋapiᚋgraphᚋmodelᚐAccessKind(ctx, "RO") + if err != nil { + var zeroVal *model.AuditLogCursor + return zeroVal, err + } + if ec.directives.Access == nil { + var zeroVal *model.AuditLogCursor + return zeroVal, errors.New("directive access is not implemented") + } + return ec.directives.Access(ctx, nil, directive0, scope, kind) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(*model.AuditLogCursor); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be *links/api/graph/model.AuditLogCursor`, 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.AuditLogCursor) + fc.Result = res + return ec.marshalNAuditLogCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐAuditLogCursor(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_getAuditLogs(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 "result": + return ec.fieldContext_AuditLogCursor_result(ctx, field) + case "pageInfo": + return ec.fieldContext_AuditLogCursor_pageInfo(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type AuditLogCursor", 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_getAuditLogs_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Query_getUsers(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query_getUsers(ctx, field) if err != nil { @@ -23690,62 +24270,124 @@ func (ec *executionContext) unmarshalInputAdminDomainInput(ctx context.Context, return it, nil } -func (ec *executionContext) unmarshalInputAnalyticsInput(ctx context.Context, obj interface{}) (model.AnalyticsInput, error) { - var it model.AnalyticsInput +func (ec *executionContext) unmarshalInputAnalyticsInput(ctx context.Context, obj interface{}) (model.AnalyticsInput, error) { + var it model.AnalyticsInput + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"id", "orgSlug", "type", "interval", "dateStart", "dateEnd"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "id": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + data, err := ec.unmarshalNInt2int(ctx, v) + if err != nil { + return it, err + } + it.ID = data + case "orgSlug": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("orgSlug")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.OrgSlug = data + case "type": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("type")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.Type = data + case "interval": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("interval")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.Interval = data + case "dateStart": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("dateStart")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.DateStart = data + case "dateEnd": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("dateEnd")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.DateEnd = data + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputAuditLogInput(ctx context.Context, obj interface{}) (model.AuditLogInput, error) { + var it model.AuditLogInput asMap := map[string]interface{}{} for k, v := range obj.(map[string]interface{}) { asMap[k] = v } - fieldsInOrder := [...]string{"id", "orgSlug", "type", "interval", "dateStart", "dateEnd"} + fieldsInOrder := [...]string{"userId", "orgSlug", "listingId", "after", "before", "limit"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { continue } switch k { - case "id": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) - data, err := ec.unmarshalNInt2int(ctx, v) + case "userId": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("userId")) + data, err := ec.unmarshalOInt2ᚖint(ctx, v) if err != nil { return it, err } - it.ID = data + it.UserID = data case "orgSlug": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("orgSlug")) - data, err := ec.unmarshalNString2string(ctx, v) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) if err != nil { return it, err } it.OrgSlug = data - case "type": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("type")) - data, err := ec.unmarshalNString2string(ctx, v) + case "listingId": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("listingId")) + data, err := ec.unmarshalOInt2ᚖint(ctx, v) if err != nil { return it, err } - it.Type = data - case "interval": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("interval")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) + it.ListingID = data + case "after": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after")) + data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v) if err != nil { return it, err } - it.Interval = data - case "dateStart": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("dateStart")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) + it.After = data + case "before": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before")) + data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v) if err != nil { return it, err } - it.DateStart = data - case "dateEnd": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("dateEnd")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) + it.Before = data + case "limit": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("limit")) + data, err := ec.unmarshalOInt2ᚖint(ctx, v) if err != nil { return it, err } - it.DateEnd = data + it.Limit = data } } @@ -25726,6 +26368,136 @@ func (ec *executionContext) _Analytics(ctx context.Context, sel ast.SelectionSet return out } +var auditLogImplementors = []string{"AuditLog"} + +func (ec *executionContext) _AuditLog(ctx context.Context, sel ast.SelectionSet, obj *auditlog.AuditLog) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, auditLogImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("AuditLog") + case "userId": + out.Values[i] = ec._AuditLog_userId(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "ipAddress": + out.Values[i] = ec._AuditLog_ipAddress(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "eventType": + out.Values[i] = ec._AuditLog_eventType(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "details": + out.Values[i] = ec._AuditLog_details(ctx, field, obj) + case "metadata": + field := field + + innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._AuditLog_metadata(ctx, field, obj) + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "createdOn": + out.Values[i] = ec._AuditLog_createdOn(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var auditLogCursorImplementors = []string{"AuditLogCursor"} + +func (ec *executionContext) _AuditLogCursor(ctx context.Context, sel ast.SelectionSet, obj *model.AuditLogCursor) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, auditLogCursorImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("AuditLogCursor") + case "result": + out.Values[i] = ec._AuditLogCursor_result(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "pageInfo": + out.Values[i] = ec._AuditLogCursor_pageInfo(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var baseURLImplementors = []string{"BaseURL"} func (ec *executionContext) _BaseURL(ctx context.Context, sel ast.SelectionSet, obj *models.BaseURL) graphql.Marshaler { @@ -26803,6 +27575,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { out.Invalids++ } + case "updateOrganization": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_updateOrganization(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } case "addLink": out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { return ec._Mutation_addLink(ctx, field) @@ -26873,13 +27652,6 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { out.Invalids++ } - case "updateOrganization": - out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { - return ec._Mutation_updateOrganization(ctx, field) - }) - if out.Values[i] == graphql.Null { - out.Invalids++ - } case "addDomain": out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { return ec._Mutation_addDomain(ctx, field) @@ -28525,6 +29297,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 "getAuditLogs": + 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_getAuditLogs(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 "getUsers": field := field @@ -29463,6 +30257,58 @@ func (ec *executionContext) unmarshalNAnalyticsInput2linksᚋapiᚋgraphᚋmodel return res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) marshalNAuditLog2ᚕᚖnetlandishᚗcomᚋxᚋgobwebsᚑauditlogᚐAuditLog(ctx context.Context, sel ast.SelectionSet, v []*auditlog.AuditLog) graphql.Marshaler { + 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.marshalOAuditLog2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑauditlogᚐAuditLog(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + return ret +} + +func (ec *executionContext) marshalNAuditLogCursor2linksᚋapiᚋgraphᚋmodelᚐAuditLogCursor(ctx context.Context, sel ast.SelectionSet, v model.AuditLogCursor) graphql.Marshaler { + return ec._AuditLogCursor(ctx, sel, &v) +} + +func (ec *executionContext) marshalNAuditLogCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐAuditLogCursor(ctx context.Context, sel ast.SelectionSet, v *model.AuditLogCursor) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._AuditLogCursor(ctx, sel, v) +} + func (ec *executionContext) marshalNBaseURL2ᚕᚖlinksᚋmodelsᚐBaseURL(ctx context.Context, sel ast.SelectionSet, v []*models.BaseURL) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup @@ -30737,6 +31583,21 @@ func (ec *executionContext) marshalOAnalyticData2ᚖlinksᚋapiᚋgraphᚋmodel return ec._AnalyticData(ctx, sel, v) } +func (ec *executionContext) marshalOAuditLog2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑauditlogᚐAuditLog(ctx context.Context, sel ast.SelectionSet, v *auditlog.AuditLog) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._AuditLog(ctx, sel, v) +} + +func (ec *executionContext) unmarshalOAuditLogInput2ᚖlinksᚋapiᚋgraphᚋmodelᚐAuditLogInput(ctx context.Context, v interface{}) (*model.AuditLogInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputAuditLogInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) marshalOBaseURL2ᚖlinksᚋmodelsᚐBaseURL(ctx context.Context, sel ast.SelectionSet, v *models.BaseURL) graphql.Marshaler { if v == nil { return graphql.Null @@ -31041,6 +31902,22 @@ func (ec *executionContext) marshalOListingLink2ᚖlinksᚋmodelsᚐListingLink( return ec._ListingLink(ctx, sel, v) } +func (ec *executionContext) unmarshalOMap2map(ctx context.Context, v interface{}) (map[string]interface{}, error) { + if v == nil { + return nil, nil + } + res, err := graphql.UnmarshalMap(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOMap2map(ctx context.Context, sel ast.SelectionSet, v map[string]interface{}) graphql.Marshaler { + if v == nil { + return graphql.Null + } + res := graphql.MarshalMap(v) + return res +} + func (ec *executionContext) unmarshalOMemberInput2ᚖlinksᚋapiᚋgraphᚋmodelᚐMemberInput(ctx context.Context, v interface{}) (*model.MemberInput, error) { if v == nil { return nil, nil diff --git a/api/graph/model/models_gen.go b/api/graph/model/models_gen.go index 3052017..d78ce72 100644 --- a/api/graph/model/models_gen.go +++ b/api/graph/model/models_gen.go @@ -10,6 +10,7 @@ import ( "time" "github.com/99designs/gqlgen/graphql" + "netlandish.com/x/gobwebs-auditlog" ) type AddListingInput struct { @@ -105,6 +106,20 @@ type AnalyticsInput struct { DateEnd *string `json:"dateEnd,omitempty"` } +type AuditLogCursor struct { + Result []*auditlog.AuditLog `json:"result"` + PageInfo *PageInfo `json:"pageInfo,omitempty"` +} + +type AuditLogInput struct { + UserID *int `json:"userId,omitempty"` + OrgSlug *string `json:"orgSlug,omitempty"` + ListingID *int `json:"listingId,omitempty"` + After *Cursor `json:"after,omitempty"` + Before *Cursor `json:"before,omitempty"` + Limit *int `json:"limit,omitempty"` +} + type CompleteRegisterInput struct { Name string `json:"name"` Username string `json:"username"` diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls index 79f72eb..2f54459 100644 --- a/api/graph/schema.graphqls +++ b/api/graph/schema.graphqls @@ -4,6 +4,7 @@ scalar Time scalar Upload scalar Cursor +scalar Map """ This is used to decorate fields which are only accessible by superuser. @@ -304,6 +305,15 @@ type Domain { updatedOn: Time! @access(scope: DOMAINS, kind: RO) } +type AuditLog { + userId: Int! + ipAddress: String! + eventType: String! + details: String + metadata: Map + createdOn: Time! +} + """ This is used in various cursor objects for pagination """ @@ -372,6 +382,12 @@ type ListingLinkCursor { pageInfo: PageInfo } +type AuditLogCursor { + result: [AuditLog]! + pageInfo: PageInfo +} + + type AnalyticData { key: String! val: Int! @@ -734,6 +750,15 @@ type FollowPayload { message: String! } +input AuditLogInput { + userId: Int + orgSlug: String + listingId: Int + after: Cursor + before: Cursor + limit: Int +} + type Query { "Returns API version information." version: Version! @@ -798,6 +823,9 @@ type Query { "Returns an array of organizations that the calling user follows" getFeedFollowing: [Organization!]! @access(scope: PROFILE, kind: RO) + "Returns audit log entries for given parameters" + getAuditLogs(input: AuditLogInput): AuditLogCursor! @access(scope: PROFILE, kind: RO) + # # Admin only. Not open to public calls # diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index 60ca18d..7bdc796 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -39,6 +39,7 @@ import ( "golang.org/x/image/draw" "golang.org/x/net/idna" "netlandish.com/x/gobwebs" + auditlog "netlandish.com/x/gobwebs-auditlog" oauth2 "netlandish.com/x/gobwebs-oauth2" gaccounts "netlandish.com/x/gobwebs/accounts" gcore "netlandish.com/x/gobwebs/core" @@ -51,6 +52,11 @@ import ( "netlandish.com/x/gobwebs/validate" ) +// Metadata is the resolver for the metadata field. +func (r *auditLogResolver) Metadata(ctx context.Context, obj *auditlog.AuditLog) (map[string]interface{}, error) { + return obj.Metadata, nil +} + // Status is the resolver for the status field. func (r *billingSettingsResolver) Status(ctx context.Context, obj *models.BillingSettings) (model.OrgBillingStatus, error) { return model.OrgBillingStatus(obj.Status), nil @@ -199,6 +205,200 @@ func (r *mutationResolver) AddOrganization(ctx context.Context, input model.Orga return org, nil } +// UpdateOrganization is the resolver for the updateOrganization field. +func (r *mutationResolver) UpdateOrganization(ctx context.Context, input *model.UpdateOrganizationInput) (*models.Organization, error) { + tokenUser := oauth2.ForContext(ctx) + if tokenUser == nil { + return nil, valid.ErrAuthorization + } + user := tokenUser.User.(*models.User) + lang := links.GetLangFromRequest(server.EchoForContext(ctx).Request(), user) + lt := localizer.GetLocalizer(lang) + + validator := valid.New(ctx) + validator.Expect(input.CurrentSlug != "", "%s", lt.Translate("CurrentSlug is required")). + WithField("currentSlug"). + WithCode(valid.ErrValidationCode) + validator.Expect(input.Name != "", "%s", lt.Translate("Name is required")). + WithField("name"). + WithCode(valid.ErrValidationCode) + validator.Expect(len(input.Name) < 150, "%s", lt.Translate("Name may not exceed 150 characters")). + WithField("name"). + WithCode(valid.ErrValidationCode) + validator.Expect(input.Slug != "", "%s", lt.Translate("Slug is required")). + WithField("slug"). + WithCode(valid.ErrValidationCode) + validator.Expect(len(input.Slug) < 150, "%s", lt.Translate("Slug may not exceed 150 characters")). + WithField("slug"). + WithCode(valid.ErrValidationCode) + + if !validator.Ok() { + return nil, nil + } + + org, err := user.GetOrgsSlug(ctx, models.OrgUserPermissionAdminWrite, input.CurrentSlug) + if err != nil { + return nil, err + } + if org == nil { + validator.Error( + "%s", lt.Translate("Organization Not Found")). + WithField("name"). + WithCode(valid.ErrNotFoundCode) + return nil, nil + } + + // If the org name changed, validate it + var ( + opts *database.FilterOptions + orgs []*models.Organization + ) + if input.Name != org.Name { + opts = &database.FilterOptions{ + Filter: sq.And{ + sq.Eq{"o.name": input.Name}, + sq.Eq{"o.owner_id": user.ID}, + }, + Limit: 1, + } + orgs, err = models.GetOrganizations(ctx, opts) + if err != nil { + return nil, err + } + + if len(orgs) > 0 { + validator.Error( + "%s", lt.Translate("This organization name is already registered")). + WithField("name"). + WithCode(valid.ErrValidationCode) + return nil, nil + } + + } + + // If the org slug changed, validate it + slug := links.Slugify(input.Slug) + if slug != org.Slug { + b := &gaccounts.BlacklistValidator{} + if !b.UsernameSafePlus(links.InvalidSlugs, slug) { + validator.Error("%s", lt.Translate("This slug can not be used. Please chose another one")). + WithField("slug"). + WithCode(valid.ErrValidationCode) + return nil, nil + } + + opts = &database.FilterOptions{ + Filter: sq.Eq{"o.slug": slug}, + Limit: 1, + } + orgs, err = models.GetOrganizations(ctx, opts) + if err != nil { + return nil, err + } + + if len(orgs) > 0 { + validator.Error( + "%s", lt.Translate("This organization slug is already registered")). + WithField("slug"). + WithCode(valid.ErrValidationCode) + return nil, nil + } + } + + org.Name = input.Name + org.Slug = slug + + // If the param was sent and if it is true + if input.DeleteImg != nil && *input.DeleteImg { + org.Image = "" + } else if input.Image != nil { + path, err := links.StoreImage(ctx, input.Image) + if err != nil { + return nil, err + } + org.Image = path + } + + // The org is going to be disabled or enabled + if input.IsActive != nil { + if org.OrgType == models.OrgTypeUser { + validator.Error("%s", lt.Translate("You are not allowed to enable/disabled user type organization")). + WithField("isActive"). + WithCode(valid.ErrValidationCode) + return nil, nil + } + + if *input.IsActive { + // If we want re-enable it, we have to check the limit of free orgs + opts := &database.FilterOptions{ + Filter: sq.And{ + sq.NotEq{"o.id": org.ID}, + sq.Eq{"o.owner_id": user.ID}, + sq.Eq{"o.is_active": true}, + sq.Eq{"(o.settings->'billing'->>'status')": models.BillingStatusFree}, + }, + } + + freeOrgs, err := models.GetOrganizations(ctx, opts) + if err != nil { + return nil, err + } + if len(freeOrgs) > 1 { + validator.Error("%s", lt.Translate("You are not allowed to have more than two free organizations. Please upgrade")). + WithCode(valid.ErrValidationGlobalCode) + return nil, nil + } + } else if org.Settings.Billing.Status == models.BillingStatusPersonal || + org.Settings.Billing.Status == models.BillingStatusBusiness { + validator.Error("%s", lt.Translate("This organization has an active subscription. Please cancel it first")). + WithCode(valid.ErrValidationGlobalCode) + return nil, nil + } + org.IsActive = *input.IsActive + } + + if input.DefaultPerm != nil { + if org.Settings.Billing.Status == models.BillingStatusFree && + string(*input.DefaultPerm) == models.OrgLinkVisibilityPrivate { + validator.Error("%s", lt.Translate( + "Free organizations can not use Private permission. Please upgrade to use "+ + "Private permission")). + WithField("defaultPerm"). + WithCode(valid.ErrValidationCode) + return nil, nil + } + if !models.ValidateLinkVisibility(string(*input.DefaultPerm)) { + validator.Error("%s", lt.Translate("Invalid default permission value")). + WithField("defaultPerm"). + WithCode(valid.ErrValidationCode) + return nil, nil + } + org.Settings.DefaultPerm = string(*input.DefaultPerm) + } + + err = org.Store(ctx) + if err != nil { + return nil, err + } + + c := server.EchoForContext(ctx) + mdata := make(map[string]any) + mdata["org_id"] = org.ID + err = models.RecordAuditLog( + ctx, + int(user.ID), + c.RealIP(), + models.LOG_ORG_UPDATED, + fmt.Sprintf("Updated organization '%s' (%d)", org.Slug, org.ID), + mdata, + ) + if err != nil { + return nil, err + } + + return org, nil +} + // AddLink is the resolver for the addLink field. func (r *mutationResolver) AddLink(ctx context.Context, input *model.LinkInput) (*models.OrgLink, error) { tokenUser := oauth2.ForContext(ctx) @@ -1660,257 +1860,63 @@ func (r *mutationResolver) UpdateProfile(ctx context.Context, input *model.Profi user.Settings.Account.Timezone = input.Timezone err := user.Store(ctx) - if err != nil { - return nil, err - } - - opts := &database.FilterOptions{ - Filter: sq.And{ - sq.Eq{"o.owner_id": user.ID}, - sq.Eq{"o.org_type": models.OrgTypeUser}, - }, - Limit: 1, - } - - personalOrgs, err := models.GetOrganizations(ctx, opts) - if err != nil { - return nil, err - } - - if len(personalOrgs) == 0 { - return nil, fmt.Errorf("%s", lt.Translate("No personal organization found for this user")) - } - personalOrg := personalOrgs[0] - var updateOrg bool - if input.DeleteImg != nil && *input.DeleteImg { - personalOrg.Image = "" - updateOrg = true - } else if input.Image != nil { - path, err := links.StoreImage(ctx, input.Image) - if err != nil { - return nil, err - } - personalOrg.Image = path - updateOrg = true - } - - if updateOrg { - err = personalOrg.Store(ctx) - if err != nil { - return nil, err - } - } - - c := server.EchoForContext(ctx) - mdata := make(map[string]any) - mdata["org_id"] = personalOrg.ID - err = models.RecordAuditLog( - ctx, - int(user.ID), - c.RealIP(), - models.LOG_PROFILE_UPDATED, - fmt.Sprintf("Updated profile"), - mdata, - ) - if err != nil { - return nil, err - } - - return user, nil -} - -// UpdateOrganization is the resolver for the updateOrganization field. -func (r *mutationResolver) UpdateOrganization(ctx context.Context, input *model.UpdateOrganizationInput) (*models.Organization, error) { - tokenUser := oauth2.ForContext(ctx) - if tokenUser == nil { - return nil, valid.ErrAuthorization - } - user := tokenUser.User.(*models.User) - lang := links.GetLangFromRequest(server.EchoForContext(ctx).Request(), user) - lt := localizer.GetLocalizer(lang) - - validator := valid.New(ctx) - validator.Expect(input.CurrentSlug != "", "%s", lt.Translate("CurrentSlug is required")). - WithField("currentSlug"). - WithCode(valid.ErrValidationCode) - validator.Expect(input.Name != "", "%s", lt.Translate("Name is required")). - WithField("name"). - WithCode(valid.ErrValidationCode) - validator.Expect(len(input.Name) < 150, "%s", lt.Translate("Name may not exceed 150 characters")). - WithField("name"). - WithCode(valid.ErrValidationCode) - validator.Expect(input.Slug != "", "%s", lt.Translate("Slug is required")). - WithField("slug"). - WithCode(valid.ErrValidationCode) - validator.Expect(len(input.Slug) < 150, "%s", lt.Translate("Slug may not exceed 150 characters")). - WithField("slug"). - WithCode(valid.ErrValidationCode) - - if !validator.Ok() { - return nil, nil - } - - org, err := user.GetOrgsSlug(ctx, models.OrgUserPermissionAdminWrite, input.CurrentSlug) - if err != nil { - return nil, err - } - if org == nil { - validator.Error( - "%s", lt.Translate("Organization Not Found")). - WithField("name"). - WithCode(valid.ErrNotFoundCode) - return nil, nil - } - - // If the org name changed, validate it - var ( - opts *database.FilterOptions - orgs []*models.Organization - ) - if input.Name != org.Name { - opts = &database.FilterOptions{ - Filter: sq.And{ - sq.Eq{"o.name": input.Name}, - sq.Eq{"o.owner_id": user.ID}, - }, - Limit: 1, - } - orgs, err = models.GetOrganizations(ctx, opts) - if err != nil { - return nil, err - } - - if len(orgs) > 0 { - validator.Error( - "%s", lt.Translate("This organization name is already registered")). - WithField("name"). - WithCode(valid.ErrValidationCode) - return nil, nil - } - - } - - // If the org slug changed, validate it - slug := links.Slugify(input.Slug) - if slug != org.Slug { - b := &gaccounts.BlacklistValidator{} - if !b.UsernameSafePlus(links.InvalidSlugs, slug) { - validator.Error("%s", lt.Translate("This slug can not be used. Please chose another one")). - WithField("slug"). - WithCode(valid.ErrValidationCode) - return nil, nil - } - - opts = &database.FilterOptions{ - Filter: sq.Eq{"o.slug": slug}, - Limit: 1, - } - orgs, err = models.GetOrganizations(ctx, opts) - if err != nil { - return nil, err - } + if err != nil { + return nil, err + } - if len(orgs) > 0 { - validator.Error( - "%s", lt.Translate("This organization slug is already registered")). - WithField("slug"). - WithCode(valid.ErrValidationCode) - return nil, nil - } + opts := &database.FilterOptions{ + Filter: sq.And{ + sq.Eq{"o.owner_id": user.ID}, + sq.Eq{"o.org_type": models.OrgTypeUser}, + }, + Limit: 1, } - org.Name = input.Name - org.Slug = slug + personalOrgs, err := models.GetOrganizations(ctx, opts) + if err != nil { + return nil, err + } - // If the param was sent and if it is true + if len(personalOrgs) == 0 { + return nil, fmt.Errorf("%s", lt.Translate("No personal organization found for this user")) + } + personalOrg := personalOrgs[0] + var updateOrg bool if input.DeleteImg != nil && *input.DeleteImg { - org.Image = "" + personalOrg.Image = "" + updateOrg = true } else if input.Image != nil { path, err := links.StoreImage(ctx, input.Image) if err != nil { return nil, err } - org.Image = path - } - - // The org is going to be disabled or enabled - if input.IsActive != nil { - if org.OrgType == models.OrgTypeUser { - validator.Error("%s", lt.Translate("You are not allowed to enable/disabled user type organization")). - WithField("isActive"). - WithCode(valid.ErrValidationCode) - return nil, nil - } - - if *input.IsActive { - // If we want re-enable it, we have to check the limit of free orgs - opts := &database.FilterOptions{ - Filter: sq.And{ - sq.NotEq{"o.id": org.ID}, - sq.Eq{"o.owner_id": user.ID}, - sq.Eq{"o.is_active": true}, - sq.Eq{"(o.settings->'billing'->>'status')": models.BillingStatusFree}, - }, - } - - freeOrgs, err := models.GetOrganizations(ctx, opts) - if err != nil { - return nil, err - } - if len(freeOrgs) > 1 { - validator.Error("%s", lt.Translate("You are not allowed to have more than two free organizations. Please upgrade")). - WithCode(valid.ErrValidationGlobalCode) - return nil, nil - } - } else if org.Settings.Billing.Status == models.BillingStatusPersonal || - org.Settings.Billing.Status == models.BillingStatusBusiness { - validator.Error("%s", lt.Translate("This organization has an active subscription. Please cancel it first")). - WithCode(valid.ErrValidationGlobalCode) - return nil, nil - } - org.IsActive = *input.IsActive + personalOrg.Image = path + updateOrg = true } - if input.DefaultPerm != nil { - if org.Settings.Billing.Status == models.BillingStatusFree && - string(*input.DefaultPerm) == models.OrgLinkVisibilityPrivate { - validator.Error("%s", lt.Translate( - "Free organizations can not use Private permission. Please upgrade to use "+ - "Private permission")). - WithField("defaultPerm"). - WithCode(valid.ErrValidationCode) - return nil, nil - } - if !models.ValidateLinkVisibility(string(*input.DefaultPerm)) { - validator.Error("%s", lt.Translate("Invalid default permission value")). - WithField("defaultPerm"). - WithCode(valid.ErrValidationCode) - return nil, nil + if updateOrg { + err = personalOrg.Store(ctx) + if err != nil { + return nil, err } - org.Settings.DefaultPerm = string(*input.DefaultPerm) - } - - err = org.Store(ctx) - if err != nil { - return nil, err } c := server.EchoForContext(ctx) mdata := make(map[string]any) - mdata["org_id"] = org.ID + mdata["org_id"] = personalOrg.ID err = models.RecordAuditLog( ctx, int(user.ID), c.RealIP(), - models.LOG_ORG_UPDATED, - fmt.Sprintf("Updated organization '%s' (%d)", org.Slug, org.ID), + models.LOG_PROFILE_UPDATED, + fmt.Sprintf("Updated profile"), mdata, ) if err != nil { return nil, err } - return org, nil + return user, nil } // AddDomain is the resolver for the addDomain field. @@ -4740,8 +4746,8 @@ func (r *qRCodeResolver) CodeType(ctx context.Context, obj *models.QRCode) (mode func (r *queryResolver) Version(ctx context.Context) (*model.Version, error) { return &model.Version{ Major: 0, - Minor: 1, - Patch: 2, + Minor: 2, + Patch: 0, DeprecationDate: nil, }, nil } @@ -6170,7 +6176,7 @@ func (r *queryResolver) Analytics(ctx context.Context, input model.AnalyticsInpu if !org.CanRead(ctx, user) { validator.Error("%s", lt.Translate("Access Restricted")). - WithCode(valid.ErrRestrictedCode) + WithCode(valid.ErrValidationCode) return nil, nil } @@ -6629,6 +6635,223 @@ func (r *queryResolver) GetFeedFollowing(ctx context.Context) ([]*models.Organiz return orgs, nil } +// GetAuditLog is the resolver for the getAuditLog field. +func (r *queryResolver) GetAuditLogs(ctx context.Context, input *model.AuditLogInput) (*model.AuditLogCursor, error) { + tokenUser := oauth2.ForContext(ctx) + if tokenUser == nil { + return nil, valid.ErrAuthorization + } + user := tokenUser.User.(*models.User) + lang := links.GetLangFromRequest(server.EchoForContext(ctx).Request(), user) + lt := localizer.GetLocalizer(lang) + + validator := valid.New(ctx) + if input.After != nil && input.Before != nil { + validator.Error("%s", lt.Translate("You can not send both after and before cursors")). + WithCode(valid.ErrValidationGlobalCode) + return nil, nil + } + + var org *models.Organization + var err error + opts := &database.FilterOptions{ + Filter: sq.And{}, + } + + if input.OrgSlug != nil && *input.OrgSlug != "" { + if !user.IsSuperUser() { + org, err = user.GetOrgsSlug(ctx, models.OrgUserPermissionAdminWrite, *input.OrgSlug) + if err != nil { + return nil, err + } + if org == nil { + validator.Error("%s", lt.Translate("Access denied.")). + WithCode(valid.ErrValidationCode) + return nil, nil + } + } else { + orgOpts := &database.FilterOptions{ + Filter: sq.Eq{"o.slug": strings.ToLower(*input.OrgSlug)}, + } + orgs, err := models.GetOrganizations(ctx, orgOpts) + if err != nil { + return nil, err + } + if len(orgs) == 0 { + validator.Error("%s", lt.Translate("Organization not found.")). + WithField("orgSlug"). + WithCode(valid.ErrNotFoundCode) + return nil, nil + } + org = orgs[0] + } + opts.Filter = sq.And{ + opts.Filter, + sq.Eq{"(al.metadata->>'org_id')": org.ID}, + } + } + + // Verify listing permisison + if input.ListingID != nil && *input.ListingID > 0 { + var listing *models.Listing + lopts := &database.FilterOptions{ + Filter: sq.Eq{"l.id": *input.ListingID}, + } + listings, err := models.GetListings(ctx, lopts) + if err != nil { + return nil, err + } + + if len(listings) == 0 { + validator.Error("%s", lt.Translate("Invalid listing ID provided")). + WithField("listingId"). + WithCode(valid.ErrNotFoundCode) + return nil, nil + } + listing = listings[0] + + if org != nil { + // Verify listing belongs specified org + if listing.OrgID != org.ID { + validator.Error("%s", lt.Translate("Invalid listing for specified organization")). + WithField("listingId"). + WithCode(valid.ErrValidationCode) + return nil, nil + } + } else { + // No org speciifed. Let's fetch associated org to verify user permissions + org, err = user.GetOrgsID(ctx, models.OrgUserPermissionAdminWrite, listing.OrgID) + if err != nil { + return nil, err + } + if org == nil { + validator.Error("%s", lt.Translate("Access denied.")). + WithField("listingId"). + WithCode(valid.ErrValidationCode) + return nil, nil + } + opts.Filter = sq.And{ + opts.Filter, + sq.Eq{"(al.metadata->>'org_id')": org.ID}, + } + } + opts.Filter = sq.And{ + opts.Filter, + sq.Eq{"(al.metadata->>'list_id')": listing.ID}, + } + } + + if !validator.Ok() { + return nil, nil + } + + var userId int + if org == nil && !user.IsSuperUser() { + userId = int(user.ID) + } + + if input.UserID != nil && *input.UserID > 0 { + if user.IsSuperUser() { + userId = *input.UserID + } else if *input.UserID != int(user.ID) { + if org != nil { + // If the given user was never part of the organization, no records would be returned. + // However if a user is removed from an organization we still want to be able to + // view their audit logs + userId = *input.UserID + } + } + } + + if userId > 0 { + opts.Filter = sq.And{ + opts.Filter, + sq.Eq{"al.user_id": userId}, + } + } + + numElements := model.PaginationDefault + var hasPrevPage, hasNextPage bool + if input.After != nil { + opts.Filter = sq.And{ + opts.Filter, + sq.Expr("al.id <= ?", input.After.After), + } + numElements = input.After.Limit + } else if input.Before != nil { + opts.Filter = sq.And{ + opts.Filter, + sq.Expr("ol.id >= ?", input.Before.Before), + } + numElements = input.Before.Limit + } + + // If limit specifically set, it overrides any cursor limit + if input.Limit != nil && *input.Limit > 0 { + numElements = *input.Limit + } + if numElements > model.PaginationMax { + numElements = model.PaginationMax + } + opts.Limit = numElements + 2 + + alogs, err := auditlog.GetAuditLogs(ctx, opts) + if err != nil { + return nil, err + } + + c := model.Cursor{Limit: numElements} + count := len(alogs) + if count > 0 { + // Checking for previous page + if input.Before != nil { + if alogs[0].ID == input.Before.Before { + hasPrevPage = true + alogs = alogs[1:] + count-- + } + } else if input.After != nil { + if alogs[0].ID == input.After.After { + hasPrevPage = true + alogs = alogs[1:] + count-- + } + } + if count == opts.Limit { + // No previous page + alogs = alogs[:count-1] + count-- + } + + if count > numElements { + hasNextPage = true + alogs = alogs[:count-1] + count-- + } + if count > 0 { + if input.Before != nil { + tmp := hasPrevPage + hasPrevPage = hasNextPage + hasNextPage = tmp + c.After = alogs[0].ID + c.Before = alogs[count-1].ID + } else { + c.After = alogs[count-1].ID + c.Before = alogs[0].ID + } + } else { + hasPrevPage = false + } + } + + pageInfo := &model.PageInfo{ + Cursor: c, + HasNextPage: hasNextPage, + HasPrevPage: hasPrevPage, + } + return &model.AuditLogCursor{Result: alogs, PageInfo: pageInfo}, nil +} + // GetUsers is the resolver for the ADMIN AREA getUsers field. func (r *queryResolver) GetUsers(ctx context.Context, input *model.GetUserInput) (*model.UserCursor, error) { if input == nil { @@ -7224,6 +7447,9 @@ func (r *userResolver) ID(ctx context.Context, obj *models.User) (int, error) { return int(obj.ID), nil } +// AuditLog returns AuditLogResolver implementation. +func (r *Resolver) AuditLog() AuditLogResolver { return &auditLogResolver{r} } + // BillingSettings returns BillingSettingsResolver implementation. func (r *Resolver) BillingSettings() BillingSettingsResolver { return &billingSettingsResolver{r} } @@ -7253,6 +7479,7 @@ func (r *Resolver) Query() QueryResolver { return &queryResolver{r} } // User returns UserResolver implementation. func (r *Resolver) User() UserResolver { return &userResolver{r} } +type auditLogResolver struct{ *Resolver } type billingSettingsResolver struct{ *Resolver } type domainResolver struct{ *Resolver } type mutationResolver struct{ *Resolver } diff --git a/helpers.go b/helpers.go index c917207..a5a8d47 100644 --- a/helpers.go +++ b/helpers.go @@ -94,6 +94,7 @@ func ParseInputErrors(c echo.Context, graphError *gqlclient.Error, fMap gobwebs. } if ext.Code == valid.ErrRestrictedCode { + c.Set("pass_msg", graphError.Message) return RenderRestrictedTemplate(c) } @@ -552,6 +553,9 @@ func RenderRestrictedTemplate(c echo.Context) error { gmap := gobwebs.Map{ "pd": pd, } + if pass_msg, ok := c.Get("pass_msg").(string); ok { + gmap["pass_msg"] = pass_msg + } curSlug := PullOrgSlug(c) if curSlug != "" { gmap["orgSlug"] = curSlug diff --git a/templates/restricted.html b/templates/restricted.html index f27d2c5..02876ef 100644 --- a/templates/restricted.html +++ b/templates/restricted.html @@ -1,7 +1,7 @@ {{template "base" .}} {{ define "title" }}{{ .pd.Title }}{{ end }} <section class="card shadow-card"> - <p class="text-center">{{.pd.Data.message}}</p> + <p class="text-center">{{ if .pass_msg }}{{ .pass_msg }}: {{ end }}{{.pd.Data.message}}</p> {{if .orgSlug}} <p class="text-center"><a href="{{reverse "billing:create_subscription" .orgSlug}}">{{.pd.Data.continue}}</a></p> {{end}} -- 2.47.2
Applied. To git@git.code.netlandish.com:~netlandish/links 4780cdb..fd727f9 master -> master