diff --git a/pkg/cookiebanner/service.go b/pkg/cookiebanner/service.go index 401c5a089..0761ff64a 100644 --- a/pkg/cookiebanner/service.go +++ b/pkg/cookiebanner/service.go @@ -64,6 +64,7 @@ type ( PrivacyPolicyURL *string ConsentExpiryDays *int ConsentMode *coredata.CookieConsentMode + DefaultLanguage *string } UpdateCookieCategoryRequest struct { @@ -706,7 +707,7 @@ func (s *Service) UpdateCookieBanner( return fmt.Errorf("cannot load cookie banner: %w", err) } - consentChanged := req.PrivacyPolicyURL != nil || req.ConsentExpiryDays != nil || req.ConsentMode != nil + consentChanged := req.PrivacyPolicyURL != nil || req.ConsentExpiryDays != nil || req.ConsentMode != nil || req.DefaultLanguage != nil if req.Name != nil { banner.Name = *req.Name @@ -723,6 +724,9 @@ func (s *Service) UpdateCookieBanner( if req.ConsentMode != nil { banner.ConsentMode = *req.ConsentMode } + if req.DefaultLanguage != nil { + banner.DefaultLanguage = *req.DefaultLanguage + } banner.UpdatedAt = time.Now() diff --git a/pkg/server/api/console/v1/cookie_banner_resolvers.go b/pkg/server/api/console/v1/cookie_banner_resolvers.go index 437c595ce..0b8542a50 100644 --- a/pkg/server/api/console/v1/cookie_banner_resolvers.go +++ b/pkg/server/api/console/v1/cookie_banner_resolvers.go @@ -7,6 +7,7 @@ package console_v1 import ( "context" + "encoding/json" "errors" "go.gearno.de/kit/log" @@ -66,6 +67,24 @@ func (r *cookieBannerResolver) Categories(ctx context.Context, obj *types.Cookie return types.NewCookieCategoryConnection(p, r, obj.ID), nil } +// Translations is the resolver for the translations field. +func (r *cookieBannerResolver) Translations(ctx context.Context, obj *types.CookieBanner) ([]*types.CookieBannerTranslation, error) { + scope := coredata.NewScopeFromObjectID(obj.ID) + + translations, err := r.cookieBanner.ListCookieBannerTranslations(ctx, scope, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list cookie banner translations", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + result := make([]*types.CookieBannerTranslation, len(translations)) + for i, t := range translations { + result[i] = types.NewCookieBannerTranslation(t) + } + + return result, nil +} + // LatestVersion is the resolver for the latestVersion field. func (r *cookieBannerResolver) LatestVersion(ctx context.Context, obj *types.CookieBanner) (*types.CookieBannerVersion, error) { if err := r.authorize(ctx, obj.ID, probo.ActionCookieBannerVersionList); err != nil { @@ -254,6 +273,7 @@ func (r *mutationResolver) UpdateCookieBanner(ctx context.Context, input types.U PrivacyPolicyURL: input.PrivacyPolicyURL, ConsentExpiryDays: input.ConsentExpiryDays, ConsentMode: input.ConsentMode, + DefaultLanguage: input.DefaultLanguage, }, ) if err != nil { @@ -717,6 +737,85 @@ func (r *mutationResolver) DeleteCookie(ctx context.Context, input types.DeleteC }, nil } +// UpsertCookieBannerTranslation is the resolver for the upsertCookieBannerTranslation field. +func (r *mutationResolver) UpsertCookieBannerTranslation(ctx context.Context, input types.UpsertCookieBannerTranslationInput) (*types.UpsertCookieBannerTranslationPayload, error) { + if err := r.authorize(ctx, input.CookieBannerID, probo.ActionCookieBannerUpdate); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(input.CookieBannerID) + + translation, err := r.cookieBanner.UpsertCookieBannerTranslation( + ctx, + scope, + cookiebanner.UpsertCookieBannerTranslationRequest{ + CookieBannerID: input.CookieBannerID, + Language: input.Language, + Translations: json.RawMessage(input.Translations), + }, + ) + if err != nil { + if errors.Is(err, cookiebanner.ErrBannerNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot upsert cookie banner translation", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + banner, err := r.cookieBanner.GetCookieBanner(ctx, scope, input.CookieBannerID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load cookie banner", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpsertCookieBannerTranslationPayload{ + CookieBannerTranslation: types.NewCookieBannerTranslation(translation), + CookieBanner: types.NewCookieBanner(banner), + }, nil +} + +// DeleteCookieBannerTranslation is the resolver for the deleteCookieBannerTranslation field. +func (r *mutationResolver) DeleteCookieBannerTranslation(ctx context.Context, input types.DeleteCookieBannerTranslationInput) (*types.DeleteCookieBannerTranslationPayload, error) { + if err := r.authorize(ctx, input.CookieBannerID, probo.ActionCookieBannerUpdate); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(input.CookieBannerID) + + err := r.cookieBanner.DeleteCookieBannerTranslation( + ctx, + scope, + cookiebanner.DeleteCookieBannerTranslationRequest{ + CookieBannerID: input.CookieBannerID, + Language: input.Language, + }, + ) + if err != nil { + if errors.Is(err, cookiebanner.ErrTranslationNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + if errors.Is(err, cookiebanner.ErrBannerNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + r.logger.ErrorCtx(ctx, "cannot delete cookie banner translation", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + banner, err := r.cookieBanner.GetCookieBanner(ctx, scope, input.CookieBannerID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load cookie banner", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteCookieBannerTranslationPayload{ + DeletedLanguage: input.Language, + CookieBanner: types.NewCookieBanner(banner), + }, nil +} + // Cookie returns schema.CookieResolver implementation. func (r *Resolver) Cookie() schema.CookieResolver { return &cookieResolver{r} } diff --git a/pkg/server/api/console/v1/graphql/cookie_banner.graphql b/pkg/server/api/console/v1/graphql/cookie_banner.graphql index 29a37a238..8128102e2 100644 --- a/pkg/server/api/console/v1/graphql/cookie_banner.graphql +++ b/pkg/server/api/console/v1/graphql/cookie_banner.graphql @@ -85,6 +85,7 @@ type CookieBanner implements Node { consentExpiryDays: Int! consentMode: CookieConsentMode! showBranding: Boolean! + defaultLanguage: String! organization: Organization @goField(forceResolver: true) @@ -96,6 +97,8 @@ type CookieBanner implements Node { orderBy: CookieCategoryOrder ): CookieCategoryConnection @goField(forceResolver: true) + translations: [CookieBannerTranslation!]! @goField(forceResolver: true) + latestVersion: CookieBannerVersion @goField(forceResolver: true) createdAt: Datetime! @@ -104,6 +107,14 @@ type CookieBanner implements Node { permission(action: String!): Boolean! @goField(forceResolver: true) } +type CookieBannerTranslation implements Node { + id: ID! + language: String! + translations: String! + createdAt: Datetime! + updatedAt: Datetime! +} + type CookieCategory implements Node { id: ID! cookieBanner: CookieBanner @goField(forceResolver: true) @@ -243,6 +254,12 @@ extend type Mutation { createCookie(input: CreateCookieInput!): CreateCookiePayload! updateCookie(input: UpdateCookieInput!): UpdateCookiePayload! deleteCookie(input: DeleteCookieInput!): DeleteCookiePayload! + upsertCookieBannerTranslation( + input: UpsertCookieBannerTranslationInput! + ): UpsertCookieBannerTranslationPayload! + deleteCookieBannerTranslation( + input: DeleteCookieBannerTranslationInput! + ): DeleteCookieBannerTranslationPayload! } input CreateCookieBannerInput { @@ -261,6 +278,7 @@ input UpdateCookieBannerInput { privacyPolicyUrl: String consentExpiryDays: Int consentMode: CookieConsentMode + defaultLanguage: String } input DeleteCookieBannerInput { @@ -387,3 +405,24 @@ type DeleteCookiePayload { deletedCookieId: ID! cookieBanner: CookieBanner! } + +input UpsertCookieBannerTranslationInput { + cookieBannerId: ID! + language: String! + translations: String! +} + +input DeleteCookieBannerTranslationInput { + cookieBannerId: ID! + language: String! +} + +type UpsertCookieBannerTranslationPayload { + cookieBannerTranslation: CookieBannerTranslation! + cookieBanner: CookieBanner! +} + +type DeleteCookieBannerTranslationPayload { + deletedLanguage: String! + cookieBanner: CookieBanner! +} diff --git a/pkg/server/api/console/v1/types/cookie_banner.go b/pkg/server/api/console/v1/types/cookie_banner.go index 1fb613004..be48eca1c 100644 --- a/pkg/server/api/console/v1/types/cookie_banner.go +++ b/pkg/server/api/console/v1/types/cookie_banner.go @@ -73,7 +73,18 @@ func NewCookieBanner(b *coredata.CookieBanner) *CookieBanner { ConsentExpiryDays: b.ConsentExpiryDays, ConsentMode: b.ConsentMode, ShowBranding: b.ShowBranding, + DefaultLanguage: b.DefaultLanguage, CreatedAt: b.CreatedAt, UpdatedAt: b.UpdatedAt, } } + +func NewCookieBannerTranslation(t *coredata.CookieBannerTranslation) *CookieBannerTranslation { + return &CookieBannerTranslation{ + ID: t.ID, + Language: t.Language, + Translations: string(t.Translations), + CreatedAt: t.CreatedAt, + UpdatedAt: t.UpdatedAt, + } +}