diff --git a/apps/comms/internal/app/graph/generated/generated.go b/apps/comms/internal/app/graph/generated/generated.go index 420a45ef0..a2dacc18f 100644 --- a/apps/comms/internal/app/graph/generated/generated.go +++ b/apps/comms/internal/app/graph/generated/generated.go @@ -115,9 +115,10 @@ type ComplexityRoot struct { } Mutation struct { - CommsMarkNotificationAsRead func(childComplexity int, id repos.ID) int - CommsUpdateNotificationConfig func(childComplexity int, config entities.NotificationConf) int - CommsUpdateSubscriptionConfig func(childComplexity int, config entities.Subscription, id repos.ID) int + CommsMarkAllNotificationAsRead func(childComplexity int) int + CommsMarkNotificationAsRead func(childComplexity int, id repos.ID) int + CommsUpdateNotificationConfig func(childComplexity int, config entities.NotificationConf) int + CommsUpdateSubscriptionConfig func(childComplexity int, config entities.Subscription, id repos.ID) int } Notification struct { @@ -206,6 +207,7 @@ type MutationResolver interface { CommsUpdateNotificationConfig(ctx context.Context, config entities.NotificationConf) (*entities.NotificationConf, error) CommsUpdateSubscriptionConfig(ctx context.Context, config entities.Subscription, id repos.ID) (*entities.Subscription, error) CommsMarkNotificationAsRead(ctx context.Context, id repos.ID) (*types.Notification, error) + CommsMarkAllNotificationAsRead(ctx context.Context) (bool, error) } type NotificationResolver interface { Content(ctx context.Context, obj *types.Notification) (*model.GithubComKloudliteAPIAppsCommsTypesNotifyContent, error) @@ -467,6 +469,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.MatchFilter.Regex(childComplexity), true + case "Mutation.comms_markAllNotificationAsRead": + if e.complexity.Mutation.CommsMarkAllNotificationAsRead == nil { + break + } + + return e.complexity.Mutation.CommsMarkAllNotificationAsRead(childComplexity), true + case "Mutation.comms_markNotificationAsRead": if e.complexity.Mutation.CommsMarkNotificationAsRead == nil { break @@ -994,6 +1003,7 @@ type Mutation { comms_updateSubscriptionConfig(config: SubscriptionIn!, id: ID!): Subscription comms_markNotificationAsRead(id: ID!): Notification @isLoggedInAndVerified @hasAccount + comms_markAllNotificationAsRead: Boolean! @isLoggedInAndVerified @hasAccount } `, BuiltIn: false}, {Name: "../struct-to-graphql/common-types.graphqls", Input: `type Github__com___kloudlite___api___apps___comms___internal___domain___entities__Email @shareable { @@ -2876,6 +2886,76 @@ func (ec *executionContext) fieldContext_Mutation_comms_markNotificationAsRead(c return fc, nil } +func (ec *executionContext) _Mutation_comms_markAllNotificationAsRead(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_comms_markAllNotificationAsRead(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().CommsMarkAllNotificationAsRead(rctx) + } + directive1 := func(ctx context.Context) (interface{}, error) { + if ec.directives.IsLoggedInAndVerified == nil { + return nil, errors.New("directive isLoggedInAndVerified is not implemented") + } + return ec.directives.IsLoggedInAndVerified(ctx, nil, directive0) + } + directive2 := func(ctx context.Context) (interface{}, error) { + if ec.directives.HasAccount == nil { + return nil, errors.New("directive hasAccount is not implemented") + } + return ec.directives.HasAccount(ctx, nil, directive1) + } + + tmp, err := directive2(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(bool); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be bool`, 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.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_comms_markAllNotificationAsRead(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) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Notification_accountName(ctx context.Context, field graphql.CollectedField, obj *types.Notification) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Notification_accountName(ctx, field) if err != nil { @@ -8086,6 +8166,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { return ec._Mutation_comms_markNotificationAsRead(ctx, field) }) + case "comms_markAllNotificationAsRead": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_comms_markAllNotificationAsRead(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } diff --git a/apps/comms/internal/app/graph/schema.graphqls b/apps/comms/internal/app/graph/schema.graphqls index f7ded99fb..4b16b11aa 100644 --- a/apps/comms/internal/app/graph/schema.graphqls +++ b/apps/comms/internal/app/graph/schema.graphqls @@ -12,4 +12,5 @@ type Mutation { comms_updateSubscriptionConfig(config: SubscriptionIn!, id: ID!): Subscription comms_markNotificationAsRead(id: ID!): Notification @isLoggedInAndVerified @hasAccount + comms_markAllNotificationAsRead: Boolean! @isLoggedInAndVerified @hasAccount } diff --git a/apps/comms/internal/app/graph/schema.resolvers.go b/apps/comms/internal/app/graph/schema.resolvers.go index c1d9f286b..bc631eef4 100644 --- a/apps/comms/internal/app/graph/schema.resolvers.go +++ b/apps/comms/internal/app/graph/schema.resolvers.go @@ -6,11 +6,13 @@ package graph import ( "context" + + "github.com/kloudlite/api/pkg/errors" + "github.com/kloudlite/api/apps/comms/internal/app/graph/generated" "github.com/kloudlite/api/apps/comms/internal/app/graph/model" "github.com/kloudlite/api/apps/comms/internal/domain/entities" "github.com/kloudlite/api/apps/comms/types" - "github.com/kloudlite/api/pkg/errors" fn "github.com/kloudlite/api/pkg/functions" "github.com/kloudlite/api/pkg/repos" ) @@ -45,6 +47,20 @@ func (r *mutationResolver) CommsMarkNotificationAsRead(ctx context.Context, id r return r.Domain.MarkNotificationAsRead(cc, id) } +// CommsMarkAllNotificationAsRead is the resolver for the comms_markAllNotificationAsRead field. +func (r *mutationResolver) CommsMarkAllNotificationAsRead(ctx context.Context) (bool, error) { + cc, err := toCommsContext(ctx) + if err != nil { + return false, errors.NewE(err) + } + + if err := r.Domain.MarkAllNotificationsAsRead(cc); err != nil { + return false, errors.NewE(err) + } + + return true, nil +} + // CommsListNotifications is the resolver for the comms_listNotifications field. func (r *queryResolver) CommsListNotifications(ctx context.Context, pagination *repos.CursorPagination) (*model.NotificationPaginatedRecords, error) { cc, err := toCommsContext(ctx) diff --git a/apps/comms/internal/domain/api.go b/apps/comms/internal/domain/api.go index 78a7f8f6a..201d1e355 100644 --- a/apps/comms/internal/domain/api.go +++ b/apps/comms/internal/domain/api.go @@ -11,6 +11,7 @@ import ( type Domain interface { ListNotifications(ctx CommsContext, pagination repos.CursorPagination) (*repos.PaginatedRecord[*types.Notification], error) MarkNotificationAsRead(ctx CommsContext, id repos.ID) (*types.Notification, error) + MarkAllNotificationsAsRead(ctx CommsContext) error GetNotificationConfig(ctx CommsContext) (*entities.NotificationConf, error) UpdateNotificationConfig(ctx CommsContext, config entities.NotificationConf) (*entities.NotificationConf, error) diff --git a/apps/comms/internal/domain/notification.go b/apps/comms/internal/domain/notification.go index 3442d497c..ecf6fb675 100644 --- a/apps/comms/internal/domain/notification.go +++ b/apps/comms/internal/domain/notification.go @@ -3,6 +3,7 @@ package domain import ( "context" + field_constants "github.com/kloudlite/api/apps/comms/internal/domain/entities/field-constants" "github.com/kloudlite/api/apps/comms/types" iamT "github.com/kloudlite/api/apps/iam/types" "github.com/kloudlite/api/common/fields" @@ -73,6 +74,33 @@ func (d *Impl) MarkNotificationAsRead(ctx CommsContext, id repos.ID) (*types.Not return n, nil } +func (d *Impl) MarkAllNotificationsAsRead(ctx CommsContext) error { + co, err := d.iamClient.Can(ctx, &iam.CanIn{ + UserId: string(ctx.UserId), + ResourceRefs: []string{iamT.NewResourceRef(ctx.AccountName, iamT.ResourceAccount, ctx.AccountName)}, + Action: string(iamT.UpdateAccount), + }) + + if err != nil { + return err + } + + if !co.Status { + return errors.NewE(errors.Newf("user %s does not have permission to update account %s", ctx.UserId, ctx.AccountName)) + } + + if err := d.notificationRepo.UpdateMany(ctx, repos.Filter{ + fields.AccountName: ctx.AccountName, + }, map[string]any{ + field_constants.NotificationRead: true, + }); err != nil { + return err + } + + return nil + +} + func (d *Impl) Notify(ctx context.Context, notification *types.Notification) error { _, err := d.notificationRepo.Create(ctx, notification) if err != nil { diff --git a/apps/comms/internal/env/env.go b/apps/comms/internal/env/env.go index 4ccada7d2..ae95fb592 100644 --- a/apps/comms/internal/env/env.go +++ b/apps/comms/internal/env/env.go @@ -32,6 +32,8 @@ type Env struct { CommsDBUri string `env:"MONGO_URI" required:"true"` CommsDBName string `env:"MONGO_DB_NAME" required:"true"` + + Port uint16 `env:"HTTP_PORT" required:"true"` } func LoadEnv() (*Env, error) { diff --git a/apps/comms/internal/framework/framework.go b/apps/comms/internal/framework/framework.go index e2fa8194f..ccaa1326d 100644 --- a/apps/comms/internal/framework/framework.go +++ b/apps/comms/internal/framework/framework.go @@ -61,6 +61,17 @@ var Module = fx.Module( return httpServer.NewServer(httpServer.ServerArgs{Logger: logger, CorsAllowOrigins: &corsOrigins, IsDev: e.IsDev}) }), + fx.Invoke(func(lf fx.Lifecycle, server httpServer.Server, ev *env.Env) { + lf.Append(fx.Hook{ + OnStart: func(context.Context) error { + return server.Listen(fmt.Sprintf(":%d", ev.Port)) + }, + OnStop: func(context.Context) error { + return server.Close() + }, + }) + }), + fx.Provide( func(ev *env.Env, jc *nats.JetstreamClient) (kv.Repo[*common.AuthSession], error) { cxt := context.TODO() diff --git a/apps/gateway/supergraph.yml b/apps/gateway/supergraph.yml index a3180ab5f..1f403cb6b 100644 --- a/apps/gateway/supergraph.yml +++ b/apps/gateway/supergraph.yml @@ -17,7 +17,7 @@ subgraphs: schema: file: ./schemas/console-api.schema comms-api: - routing_url: http://comms-api:3000/query + routing_url: http://comms:3000/query schema: file: ./schemas/comms-api.schema infra-api: