From 83119f6480721e14652718ef9cb30b757a125c3d Mon Sep 17 00:00:00 2001 From: Peter Sanchez Date: Wed, 13 Nov 2024 17:55:22 -0600 Subject: [PATCH] Moving getOrganizations call to use org slug versus db ID --- admin/routes.go | 34 +- api/graph/generated.go | 470 ++++++++++++------------- api/graph/schema.graphqls | 12 +- api/graph/schema.resolvers.go | 312 ++++++++-------- templates/admin_billing_list.html | 2 +- templates/admin_dashboard.html | 6 +- templates/admin_domains_list.html | 2 +- templates/admin_org_detail.html | 2 +- templates/admin_organization_list.html | 4 +- templates/admin_update_org_type.html | 6 +- templates/admin_user_detail.html | 4 +- 11 files changed, 415 insertions(+), 439 deletions(-) diff --git a/admin/routes.go b/admin/routes.go index 913417d..3f3d352 100644 --- a/admin/routes.go +++ b/admin/routes.go @@ -8,6 +8,7 @@ import ( "links/models" "net/http" "strconv" + "strings" "time" "git.sr.ht/~emersion/gqlclient" @@ -40,9 +41,9 @@ func (s *Service) RegisterRoutes() { s.Group.GET("/user/:id/lock", s.UserToggleLock).Name = s.RouteName("user_toggle_lock") s.Group.POST("/user/:id/lock", s.UserToggleLock).Name = s.RouteName("user_toggle_lock") s.Group.GET("/organization", s.OrgList).Name = s.RouteName("org_list") - s.Group.GET("/organization/:id", s.OrgDetail).Name = s.RouteName("org_detail") - s.Group.GET("/organization/:id/type", s.UpdateOrgType).Name = s.RouteName("update_org_type") - s.Group.POST("/organization/:id/type", s.UpdateOrgType).Name = s.RouteName("update_org_type") + s.Group.GET("/organization/:slug", s.OrgDetail).Name = s.RouteName("org_detail") + s.Group.GET("/organization/:slug/type", s.UpdateOrgType).Name = s.RouteName("update_org_type") + s.Group.POST("/organization/:slug/type", s.UpdateOrgType).Name = s.RouteName("update_org_type") s.Group.GET("/domain", s.DomainList).Name = s.RouteName("domain_list") s.Group.GET("/domain/:id", s.DomainDetail).Name = s.RouteName("domain_detail") s.Group.GET("/domain/add", s.DomainCreate).Name = s.RouteName("domain_create") @@ -237,8 +238,8 @@ func (s *Service) Dashboard(c echo.Context) error { } func (s *Service) UpdateOrgType(c echo.Context) error { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { + slug := strings.ToLower(c.Param("slug")) + if slug == "" { return echo.NotFoundHandler(c) } lt := localizer.GetSessionLocalizer(c) @@ -262,8 +263,8 @@ func (s *Service) UpdateOrgType(c echo.Context) error { } var result GraphQLResponse op := gqlclient.NewOperation( - `query GetOrganization($id: Int!) { - getOrganization(id: $id) { + `query GetOrganization($slug: String!) { + getOrganization(slug: $slug) { id name slug @@ -274,8 +275,8 @@ func (s *Service) UpdateOrgType(c echo.Context) error { } } }`) - op.Var("id", id) - err = links.Execute(c.Request().Context(), op, &result) + op.Var("slug", slug) + err := links.Execute(c.Request().Context(), op, &result) if err != nil { return err } @@ -308,6 +309,7 @@ func (s *Service) UpdateOrgType(c echo.Context) error { mutation UpdateAdminOrgType($orgSlug: String!, $orgType: Int!) { updateAdminOrgType(orgSlug: $orgSlug, orgType: $orgType) { id + slug } } `) @@ -318,7 +320,7 @@ func (s *Service) UpdateOrgType(c echo.Context) error { return err } return c.Redirect(http.StatusMovedPermanently, - c.Echo().Reverse(s.RouteName("org_detail"), resultUpdated.Org.ID)) + c.Echo().Reverse(s.RouteName("org_detail"), resultUpdated.Org.Slug)) } form.OrgType = result.Org.Settings.Billing.Status @@ -327,8 +329,8 @@ func (s *Service) UpdateOrgType(c echo.Context) error { } func (s *Service) OrgDetail(c echo.Context) error { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { + slug := strings.ToLower(c.Param("slug")) + if slug == "" { return echo.NotFoundHandler(c) } lt := localizer.GetSessionLocalizer(c) @@ -377,8 +379,8 @@ func (s *Service) OrgDetail(c echo.Context) error { } var result GraphQLResponse op := gqlclient.NewOperation( - `query GetOrganization($id: Int!) { - getOrganization(id: $id) { + `query GetOrganization($slug: String!) { + getOrganization(slug: $slug) { id name slug @@ -391,8 +393,8 @@ func (s *Service) OrgDetail(c echo.Context) error { } } }`) - op.Var("id", id) - err = links.Execute(c.Request().Context(), op, &result) + op.Var("slug", slug) + err := links.Execute(c.Request().Context(), op, &result) if err != nil { if graphError, ok := err.(*gqlclient.Error); ok { return links.ParseInputErrors(c, graphError, gobwebs.Map{}) diff --git a/api/graph/generated.go b/api/graph/generated.go index 165de96..d975068 100644 --- a/api/graph/generated.go +++ b/api/graph/generated.go @@ -376,7 +376,7 @@ type ComplexityRoot struct { GetOrgLink func(childComplexity int, hash string) int GetOrgLinks func(childComplexity int, input *model.GetLinkInput) int GetOrgMembers func(childComplexity int, orgSlug string) int - GetOrganization func(childComplexity int, id int) int + GetOrganization func(childComplexity int, slug string) int GetOrganizations func(childComplexity int, input *model.GetOrganizationsInput) int GetPaymentHistory func(childComplexity int, input *model.GetPaymentInput) int GetPopularLinks func(childComplexity int, input *model.PopularLinksInput) int @@ -477,10 +477,8 @@ type OrganizationResolver interface { type QueryResolver interface { Version(ctx context.Context) (*model.Version, error) Me(ctx context.Context) (*models.User, error) - GetUsers(ctx context.Context, input *model.GetUserInput) (*model.UserCursor, error) - GetUser(ctx context.Context, id int) (*models.User, error) GetOrganizations(ctx context.Context, input *model.GetOrganizationsInput) ([]*models.Organization, error) - GetOrganization(ctx context.Context, id int) (*models.Organization, error) + GetOrganization(ctx context.Context, slug string) (*models.Organization, error) GetPaymentHistory(ctx context.Context, input *model.GetPaymentInput) (*model.PaymentCursor, error) GetPopularLinks(ctx context.Context, input *model.PopularLinksInput) ([]*models.BaseURL, error) GetOrgLink(ctx context.Context, hash string) (*models.OrgLink, error) @@ -498,6 +496,8 @@ 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) + 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) GetAdminOrgStats(ctx context.Context, id int) (*model.OrganizationStats, error) GetAdminBillingStats(ctx context.Context, input *model.AdminBillingInput) (*model.AdminBillingStats, error) @@ -2302,7 +2302,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.GetOrganization(childComplexity, args["id"].(int)), true + return e.complexity.Query.GetOrganization(childComplexity, args["slug"].(string)), true case "Query.getOrganizations": if e.complexity.Query.GetOrganizations == nil { @@ -3551,15 +3551,15 @@ func (ec *executionContext) field_Query_getOrgMembers_args(ctx context.Context, func (ec *executionContext) field_Query_getOrganization_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["id"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) - arg0, err = ec.unmarshalNInt2int(ctx, tmp) + var arg0 string + if tmp, ok := rawArgs["slug"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("slug")) + arg0, err = ec.unmarshalNString2string(ctx, tmp) if err != nil { return nil, err } } - args["id"] = arg0 + args["slug"] = arg0 return args, nil } @@ -16024,194 +16024,6 @@ func (ec *executionContext) fieldContext_Query_me(_ context.Context, field graph 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 { - 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().GetUsers(rctx, fc.Args["input"].(*model.GetUserInput)) - } - directive1 := func(ctx context.Context) (interface{}, error) { - scope, err := ec.unmarshalNAccessScope2linksᚋapiᚋgraphᚋmodelᚐAccessScope(ctx, "PROFILE") - if err != nil { - return nil, err - } - kind, err := ec.unmarshalNAccessKind2linksᚋapiᚋgraphᚋmodelᚐAccessKind(ctx, "RW") - if err != nil { - return nil, err - } - if ec.directives.Access == nil { - return nil, 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.UserCursor); ok { - return data, nil - } - return nil, fmt.Errorf(`unexpected type %T from directive, should be *links/api/graph/model.UserCursor`, 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.UserCursor) - fc.Result = res - return ec.marshalNUserCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐUserCursor(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_Query_getUsers(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_UserCursor_result(ctx, field) - case "pageInfo": - return ec.fieldContext_UserCursor_pageInfo(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type UserCursor", 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_getUsers_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { - ec.Error(ctx, err) - return fc, err - } - return fc, nil -} - -func (ec *executionContext) _Query_getUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Query_getUser(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().GetUser(rctx, fc.Args["id"].(int)) - } - directive1 := func(ctx context.Context) (interface{}, error) { - scope, err := ec.unmarshalNAccessScope2linksᚋapiᚋgraphᚋmodelᚐAccessScope(ctx, "PROFILE") - if err != nil { - return nil, err - } - kind, err := ec.unmarshalNAccessKind2linksᚋapiᚋgraphᚋmodelᚐAccessKind(ctx, "RW") - if err != nil { - return nil, err - } - if ec.directives.Access == nil { - return nil, 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.User); ok { - return data, nil - } - return nil, fmt.Errorf(`unexpected type %T from directive, should be *links/models.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.(*models.User) - fc.Result = res - return ec.marshalNUser2ᚖlinksᚋmodelsᚐUser(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_Query_getUser(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_User_id(ctx, field) - case "name": - return ec.fieldContext_User_name(ctx, field) - case "email": - return ec.fieldContext_User_email(ctx, field) - case "createdOn": - return ec.fieldContext_User_createdOn(ctx, field) - case "isEmailVerified": - return ec.fieldContext_User_isEmailVerified(ctx, field) - case "isLocked": - return ec.fieldContext_User_isLocked(ctx, field) - case "lockReason": - return ec.fieldContext_User_lockReason(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type User", 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_getUser_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { - ec.Error(ctx, err) - return fc, err - } - return fc, nil -} - func (ec *executionContext) _Query_getOrganizations(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query_getOrganizations(ctx, field) if err != nil { @@ -16336,7 +16148,7 @@ func (ec *executionContext) _Query_getOrganization(ctx context.Context, field gr 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().GetOrganization(rctx, fc.Args["id"].(int)) + return ec.resolvers.Query().GetOrganization(rctx, fc.Args["slug"].(string)) } directive1 := func(ctx context.Context) (interface{}, error) { scope, err := ec.unmarshalNAccessScope2linksᚋapiᚋgraphᚋmodelᚐAccessScope(ctx, "ORGS") @@ -18110,6 +17922,178 @@ func (ec *executionContext) fieldContext_Query_getFeedFollowing(_ context.Contex 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 { + 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().GetUsers(rctx, fc.Args["input"].(*model.GetUserInput)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + if ec.directives.Admin == nil { + return nil, errors.New("directive admin is not implemented") + } + return ec.directives.Admin(ctx, nil, directive0) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(*model.UserCursor); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be *links/api/graph/model.UserCursor`, 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.UserCursor) + fc.Result = res + return ec.marshalNUserCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐUserCursor(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_getUsers(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_UserCursor_result(ctx, field) + case "pageInfo": + return ec.fieldContext_UserCursor_pageInfo(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type UserCursor", 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_getUsers_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Query_getUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_getUser(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().GetUser(rctx, fc.Args["id"].(int)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + if ec.directives.Admin == nil { + return nil, errors.New("directive admin is not implemented") + } + return ec.directives.Admin(ctx, nil, directive0) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(*models.User); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be *links/models.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.(*models.User) + fc.Result = res + return ec.marshalNUser2ᚖlinksᚋmodelsᚐUser(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_getUser(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_User_id(ctx, field) + case "name": + return ec.fieldContext_User_name(ctx, field) + case "email": + return ec.fieldContext_User_email(ctx, field) + case "createdOn": + return ec.fieldContext_User_createdOn(ctx, field) + case "isEmailVerified": + return ec.fieldContext_User_isEmailVerified(ctx, field) + case "isLocked": + return ec.fieldContext_User_isLocked(ctx, field) + case "lockReason": + return ec.fieldContext_User_lockReason(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type User", 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_getUser_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Query_getAdminOrganizations(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query_getAdminOrganizations(ctx, field) if err != nil { @@ -25893,50 +25877,6 @@ 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 "getUsers": - 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_getUsers(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 "getUser": - 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_getUser(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 "getOrganizations": field := field @@ -26349,6 +26289,50 @@ 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 "getUsers": + 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_getUsers(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 "getUser": + 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_getUser(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 "getAdminOrganizations": field := field diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls index c0c35da..2fb0f5f 100644 --- a/api/graph/schema.graphqls +++ b/api/graph/schema.graphqls @@ -678,17 +678,11 @@ type Query { "Returns the authenticated user." me: User! @access(scope: PROFILE, kind: RO) - "Returns an array of users" - getUsers(input: GetUserInput): UserCursor! @access(scope: PROFILE, kind: RW) - - "Returns a specific user" - getUser(id: Int!): User! @access(scope: PROFILE, kind: RW) - "Returns an array of organizations" getOrganizations(input: GetOrganizationsInput): [Organization!]! @access(scope: ORGS, kind: RO) "Returns a specific organization" - getOrganization(id: Int!): Organization @access(scope: ORGS, kind: RO) + getOrganization(slug: String!): Organization @access(scope: ORGS, kind: RO) "Returns payment history based on given input" getPaymentHistory(input: GetPaymentInput): PaymentCursor! @access(scope: BILLING, kind: RO) @@ -738,10 +732,12 @@ type Query { "Returns an array of organization links from the calling users feed (orgs they follow)" getFeed(input: GetFeedInput): OrgLinkCursor! @access(scope: PROFILE, kind: RO) - "REturns an array of organizations that the calling user follows" + "Returns an array of organizations that the calling user follows" getFeedFollowing: [Organization!]! @access(scope: PROFILE, kind: RO) "Admin only. Not open to public calls" + getUsers(input: GetUserInput): UserCursor! @admin + getUser(id: Int!): User! @admin getAdminOrganizations(input: GetAdminOrganizationsInput): OrganizationCursor! @admin getAdminOrgStats(id: Int!): OrganizationStats! @admin getAdminBillingStats(input: AdminBillingInput): AdminBillingStats! @admin diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index 694043d..19053ea 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -39,7 +39,7 @@ import ( "golang.org/x/image/draw" "golang.org/x/net/idna" "netlandish.com/x/gobwebs" - oauth2 "netlandish.com/x/gobwebs-oauth2" + "netlandish.com/x/gobwebs-oauth2" gaccounts "netlandish.com/x/gobwebs/accounts" "netlandish.com/x/gobwebs/crypto" "netlandish.com/x/gobwebs/database" @@ -4225,155 +4225,6 @@ func (r *queryResolver) Me(ctx context.Context) (*models.User, error) { return user, 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 { - input = &model.GetUserInput{} - } - 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 !user.IsSuperUser() { - validator.Error(lt.Translate("This user is not allowed to perform this action")). - WithCode(valid.ErrNotFoundCode) - return nil, nil - - } - opts := &database.FilterOptions{ - Filter: sq.And{ - sq.Eq{"o.org_type": models.OrgTypeUser}, - }, - OrderBy: "u.id DESC", - } - - if input.Search != nil && *input.Search != "" { - // We want to search for partial match - s := links.ParseSearch(*input.Search, true) - opts.Filter = sq.And{ - opts.Filter, - sq.Expr(`to_tsvector('simple', u.full_name || ' ' || u.email || ' ' || o.slug) - @@ websearch_to_tsquery('simple', ?)`, s), - } - - } - - if input.After != nil && input.Before != nil { - validator.Error(lt.Translate("You can not send both after and before cursors")). - WithCode(valid.ErrValidationGlobalCode) - return nil, nil - } - - numElements := model.PaginationDefault - var hasPrevPage bool - var hasNextPage bool - if input.After != nil { - opts.Filter = sq.And{ - opts.Filter, - sq.LtOrEq{"u.id": input.After.After}, - } - numElements = input.After.Limit - } else if input.Before != nil { - opts.Filter = sq.And{ - opts.Filter, - sq.GtOrEq{"u.id": input.Before.Before}, - } - opts.OrderBy = "u.id ASC" - 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 - users, err := models.GetUsers(ctx, opts) - if err != nil { - return nil, err - } - c := model.Cursor{Limit: numElements} - count := len(users) - if count > 0 { - // Checking for previous page - if input.Before != nil { - if int(users[0].ID) == input.Before.Before { - hasPrevPage = true - users = users[1:] - count-- - } - } else if input.After != nil { - if int(users[0].ID) == input.After.After { - hasPrevPage = true - users = users[1:] - count-- - } - } - if count == opts.Limit { - // No previous page - users = users[:count-1] - count-- - } - - if count > numElements { - hasNextPage = true - users = users[:count-1] - count-- - } - if count > 0 { - if input.Before != nil { - tmp := hasPrevPage - hasPrevPage = hasNextPage - hasNextPage = tmp - c.After = int(users[0].ID) - c.Before = int(users[count-1].ID) - } else { - c.After = int(users[count-1].ID) - c.Before = int(users[0].ID) - } - } else { - hasPrevPage = false - } - } - pageInfo := &model.PageInfo{ - Cursor: c, - HasNextPage: hasNextPage, - HasPrevPage: hasPrevPage, - } - return &model.UserCursor{Result: users, PageInfo: pageInfo}, nil -} - -// GetUser is the resolver for the ADMIN AREA getUser field. -func (r *queryResolver) GetUser(ctx context.Context, id int) (*models.User, 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 !user.IsSuperUser() { - validator.Error(lt.Translate("This user is not allowed to perform this action")). - WithCode(valid.ErrNotFoundCode) - return nil, nil - - } - - user, err := models.GetUser(ctx, id, false) - if err != nil { - return nil, err - } - - return user, nil -} - // GetOrganizations is the resolver for the getOrganizations field. func (r *queryResolver) GetOrganizations(ctx context.Context, input *model.GetOrganizationsInput) ([]*models.Organization, error) { tokenUser := oauth2.ForContext(ctx) @@ -4414,7 +4265,7 @@ func (r *queryResolver) GetOrganizations(ctx context.Context, input *model.GetOr } // GetOrganization is the resolver for the getOrganization field. -func (r *queryResolver) GetOrganization(ctx context.Context, id int) (*models.Organization, error) { +func (r *queryResolver) GetOrganization(ctx context.Context, slug string) (*models.Organization, error) { tokenUser := oauth2.ForContext(ctx) if tokenUser == nil { return nil, valid.ErrAuthorization @@ -4423,16 +4274,10 @@ func (r *queryResolver) GetOrganization(ctx context.Context, id int) (*models.Or lang := links.GetLangFromRequest(server.EchoForContext(ctx).Request(), user) lt := localizer.GetLocalizer(lang) opts := &database.FilterOptions{ - Filter: sq.Eq{"o.id": id}, + Filter: sq.Eq{"o.slug": strings.ToLower(slug)}, Limit: 1, } - if !user.IsSuperUser() { - // XXX Remove this? Not sure why we have this filter in place. - opts.Filter = sq.And{ - opts.Filter, - sq.Eq{"o.is_active": true}, - } - } + orgs, err := models.GetOrganizations(ctx, opts) if err != nil { return nil, err @@ -6243,6 +6088,155 @@ func (r *queryResolver) GetFeedFollowing(ctx context.Context) ([]*models.Organiz return orgs, 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 { + input = &model.GetUserInput{} + } + 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 !user.IsSuperUser() { + validator.Error(lt.Translate("This user is not allowed to perform this action")). + WithCode(valid.ErrNotFoundCode) + return nil, nil + + } + opts := &database.FilterOptions{ + Filter: sq.And{ + sq.Eq{"o.org_type": models.OrgTypeUser}, + }, + OrderBy: "u.id DESC", + } + + if input.Search != nil && *input.Search != "" { + // We want to search for partial match + s := links.ParseSearch(*input.Search, true) + opts.Filter = sq.And{ + opts.Filter, + sq.Expr(`to_tsvector('simple', u.full_name || ' ' || u.email || ' ' || o.slug) + @@ websearch_to_tsquery('simple', ?)`, s), + } + + } + + if input.After != nil && input.Before != nil { + validator.Error(lt.Translate("You can not send both after and before cursors")). + WithCode(valid.ErrValidationGlobalCode) + return nil, nil + } + + numElements := model.PaginationDefault + var hasPrevPage bool + var hasNextPage bool + if input.After != nil { + opts.Filter = sq.And{ + opts.Filter, + sq.LtOrEq{"u.id": input.After.After}, + } + numElements = input.After.Limit + } else if input.Before != nil { + opts.Filter = sq.And{ + opts.Filter, + sq.GtOrEq{"u.id": input.Before.Before}, + } + opts.OrderBy = "u.id ASC" + 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 + users, err := models.GetUsers(ctx, opts) + if err != nil { + return nil, err + } + c := model.Cursor{Limit: numElements} + count := len(users) + if count > 0 { + // Checking for previous page + if input.Before != nil { + if int(users[0].ID) == input.Before.Before { + hasPrevPage = true + users = users[1:] + count-- + } + } else if input.After != nil { + if int(users[0].ID) == input.After.After { + hasPrevPage = true + users = users[1:] + count-- + } + } + if count == opts.Limit { + // No previous page + users = users[:count-1] + count-- + } + + if count > numElements { + hasNextPage = true + users = users[:count-1] + count-- + } + if count > 0 { + if input.Before != nil { + tmp := hasPrevPage + hasPrevPage = hasNextPage + hasNextPage = tmp + c.After = int(users[0].ID) + c.Before = int(users[count-1].ID) + } else { + c.After = int(users[count-1].ID) + c.Before = int(users[0].ID) + } + } else { + hasPrevPage = false + } + } + pageInfo := &model.PageInfo{ + Cursor: c, + HasNextPage: hasNextPage, + HasPrevPage: hasPrevPage, + } + return &model.UserCursor{Result: users, PageInfo: pageInfo}, nil +} + +// GetUser is the resolver for the ADMIN AREA getUser field. +func (r *queryResolver) GetUser(ctx context.Context, id int) (*models.User, 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 !user.IsSuperUser() { + validator.Error(lt.Translate("This user is not allowed to perform this action")). + WithCode(valid.ErrNotFoundCode) + return nil, nil + + } + + user, err := models.GetUser(ctx, id, false) + if err != nil { + return nil, err + } + + return user, nil +} + // GetAdminOrganizations is the resolver for the ADMIN AREA getAdminOrganizations field. func (r *queryResolver) GetAdminOrganizations(ctx context.Context, input *model.GetAdminOrganizationsInput) (*model.OrganizationCursor, error) { if input == nil { diff --git a/templates/admin_billing_list.html b/templates/admin_billing_list.html index 61b62af..5b981f0 100644 --- a/templates/admin_billing_list.html +++ b/templates/admin_billing_list.html @@ -109,7 +109,7 @@ ${{formatAmt .AmountPaid}} ${{formatAmt .PaymentFee}} ${{formatAmt .AmountNet}} - {{.OrgSlug}} + {{.OrgSlug}} {{formatDate .CreatedOn}} {{end}} diff --git a/templates/admin_dashboard.html b/templates/admin_dashboard.html index 84a9ec8..9faf08f 100644 --- a/templates/admin_dashboard.html +++ b/templates/admin_dashboard.html @@ -82,8 +82,8 @@ {{ if .orgs }} {{range .orgs}} - {{.ID}} - {{.Name}} + {{.ID}} + {{.Name}} {{.Slug}} {{.OwnerName}} {{formatDate .CreatedOn}} @@ -137,7 +137,7 @@ {{$.pd.Data.list}} {{end}} - {{if .OrgID.Valid}}{{.OrgSlug.String}}{{else}}-{{end}} + {{if .OrgSlug.Valid}}{{.OrgSlug.String}}{{else}}-{{end}} {{if .IsActive}} diff --git a/templates/admin_domains_list.html b/templates/admin_domains_list.html index 97857ba..a7f3664 100644 --- a/templates/admin_domains_list.html +++ b/templates/admin_domains_list.html @@ -77,7 +77,7 @@ {{$.pd.Data.list}} {{end}} - {{if .OrgID.Valid}}{{.OrgSlug.String}}{{else}}-{{end}} + {{if .OrgSlug.Valid}}{{.OrgSlug.String}}{{else}}-{{end}} {{if .IsActive}} diff --git a/templates/admin_org_detail.html b/templates/admin_org_detail.html index 8922342..3a8eb6e 100644 --- a/templates/admin_org_detail.html +++ b/templates/admin_org_detail.html @@ -3,7 +3,7 @@

{{.org.Name}}

- {{.pd.Data.update_type}} + {{.pd.Data.update_type}} {{.pd.Data.back}}
diff --git a/templates/admin_organization_list.html b/templates/admin_organization_list.html index f0978fa..8c9fdcb 100644 --- a/templates/admin_organization_list.html +++ b/templates/admin_organization_list.html @@ -29,8 +29,8 @@ {{ if .orgs }} {{range .orgs}} - {{.ID}} - {{.Name}} + {{.ID}} + {{.Name}} {{.Slug}} {{.OwnerName}} {{formatDate .CreatedOn}} diff --git a/templates/admin_update_org_type.html b/templates/admin_update_org_type.html index 7eee423..1a3ec2d 100644 --- a/templates/admin_update_org_type.html +++ b/templates/admin_update_org_type.html @@ -3,11 +3,11 @@

{{.pd.Title}} {{.pd.Data.for}} {{.org.Name}}

- {{.pd.Data.back}} + {{.pd.Data.back}}
-
+ {{if .errors._global_ }} {{range .errors._global_}} @@ -29,7 +29,7 @@
diff --git a/templates/admin_user_detail.html b/templates/admin_user_detail.html index c1df845..a8aff88 100644 --- a/templates/admin_user_detail.html +++ b/templates/admin_user_detail.html @@ -71,8 +71,8 @@ {{range .orgs}} - {{.Name}} - {{.Slug}} + {{.Name}} + {{.Slug}} {{ .DisplayBillingStatus $.context }} {{if .IsActive}} -- 2.45.2