~netlandish/links

fd727f96864b9d4bb0652bfba75037592a88dacf — Peter Sanchez 9 days ago 4780cdb
Adding GraphQL calls for audit logs.

GraphQL api version bump

Changelog-added: GraphQL calls for audit logs
Changelog-changed: GraphQL api version: minor version bump
M api/gqlgen.yml => api/gqlgen.yml +3 -0
@@ 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

M api/graph/generated.go => api/graph/generated.go +1024 -147
@@ 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)


@@ 28526,6 29298,28 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
			}

			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

M api/graph/model/models_gen.go => api/graph/model/models_gen.go +15 -0
@@ 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"`

M api/graph/schema.graphqls => api/graph/schema.graphqls +28 -0
@@ 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
    #

M api/graph/schema.resolvers.go => api/graph/schema.resolvers.go +454 -227
@@ 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 }

M helpers.go => helpers.go +4 -0
@@ 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

M templates/restricted.html => templates/restricted.html +1 -1
@@ 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}}

Do not follow this link