Add console GraphQL schema and resolvers for i18n
Add CookieBannerTranslation type, defaultLanguage field on CookieBanner, and upsert/delete mutations for managing per-language translations from the console. Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -64,6 +64,7 @@ type (
|
|||||||
PrivacyPolicyURL *string
|
PrivacyPolicyURL *string
|
||||||
ConsentExpiryDays *int
|
ConsentExpiryDays *int
|
||||||
ConsentMode *coredata.CookieConsentMode
|
ConsentMode *coredata.CookieConsentMode
|
||||||
|
DefaultLanguage *string
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateCookieCategoryRequest struct {
|
UpdateCookieCategoryRequest struct {
|
||||||
@@ -706,7 +707,7 @@ func (s *Service) UpdateCookieBanner(
|
|||||||
return fmt.Errorf("cannot load cookie banner: %w", err)
|
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 {
|
if req.Name != nil {
|
||||||
banner.Name = *req.Name
|
banner.Name = *req.Name
|
||||||
@@ -723,6 +724,9 @@ func (s *Service) UpdateCookieBanner(
|
|||||||
if req.ConsentMode != nil {
|
if req.ConsentMode != nil {
|
||||||
banner.ConsentMode = *req.ConsentMode
|
banner.ConsentMode = *req.ConsentMode
|
||||||
}
|
}
|
||||||
|
if req.DefaultLanguage != nil {
|
||||||
|
banner.DefaultLanguage = *req.DefaultLanguage
|
||||||
|
}
|
||||||
|
|
||||||
banner.UpdatedAt = time.Now()
|
banner.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ package console_v1
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"go.gearno.de/kit/log"
|
"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
|
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.
|
// LatestVersion is the resolver for the latestVersion field.
|
||||||
func (r *cookieBannerResolver) LatestVersion(ctx context.Context, obj *types.CookieBanner) (*types.CookieBannerVersion, error) {
|
func (r *cookieBannerResolver) LatestVersion(ctx context.Context, obj *types.CookieBanner) (*types.CookieBannerVersion, error) {
|
||||||
if err := r.authorize(ctx, obj.ID, probo.ActionCookieBannerVersionList); err != nil {
|
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,
|
PrivacyPolicyURL: input.PrivacyPolicyURL,
|
||||||
ConsentExpiryDays: input.ConsentExpiryDays,
|
ConsentExpiryDays: input.ConsentExpiryDays,
|
||||||
ConsentMode: input.ConsentMode,
|
ConsentMode: input.ConsentMode,
|
||||||
|
DefaultLanguage: input.DefaultLanguage,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -717,6 +737,85 @@ func (r *mutationResolver) DeleteCookie(ctx context.Context, input types.DeleteC
|
|||||||
}, nil
|
}, 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.
|
// Cookie returns schema.CookieResolver implementation.
|
||||||
func (r *Resolver) Cookie() schema.CookieResolver { return &cookieResolver{r} }
|
func (r *Resolver) Cookie() schema.CookieResolver { return &cookieResolver{r} }
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ type CookieBanner implements Node {
|
|||||||
consentExpiryDays: Int!
|
consentExpiryDays: Int!
|
||||||
consentMode: CookieConsentMode!
|
consentMode: CookieConsentMode!
|
||||||
showBranding: Boolean!
|
showBranding: Boolean!
|
||||||
|
defaultLanguage: String!
|
||||||
|
|
||||||
organization: Organization @goField(forceResolver: true)
|
organization: Organization @goField(forceResolver: true)
|
||||||
|
|
||||||
@@ -96,6 +97,8 @@ type CookieBanner implements Node {
|
|||||||
orderBy: CookieCategoryOrder
|
orderBy: CookieCategoryOrder
|
||||||
): CookieCategoryConnection @goField(forceResolver: true)
|
): CookieCategoryConnection @goField(forceResolver: true)
|
||||||
|
|
||||||
|
translations: [CookieBannerTranslation!]! @goField(forceResolver: true)
|
||||||
|
|
||||||
latestVersion: CookieBannerVersion @goField(forceResolver: true)
|
latestVersion: CookieBannerVersion @goField(forceResolver: true)
|
||||||
|
|
||||||
createdAt: Datetime!
|
createdAt: Datetime!
|
||||||
@@ -104,6 +107,14 @@ type CookieBanner implements Node {
|
|||||||
permission(action: String!): Boolean! @goField(forceResolver: true)
|
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 {
|
type CookieCategory implements Node {
|
||||||
id: ID!
|
id: ID!
|
||||||
cookieBanner: CookieBanner @goField(forceResolver: true)
|
cookieBanner: CookieBanner @goField(forceResolver: true)
|
||||||
@@ -243,6 +254,12 @@ extend type Mutation {
|
|||||||
createCookie(input: CreateCookieInput!): CreateCookiePayload!
|
createCookie(input: CreateCookieInput!): CreateCookiePayload!
|
||||||
updateCookie(input: UpdateCookieInput!): UpdateCookiePayload!
|
updateCookie(input: UpdateCookieInput!): UpdateCookiePayload!
|
||||||
deleteCookie(input: DeleteCookieInput!): DeleteCookiePayload!
|
deleteCookie(input: DeleteCookieInput!): DeleteCookiePayload!
|
||||||
|
upsertCookieBannerTranslation(
|
||||||
|
input: UpsertCookieBannerTranslationInput!
|
||||||
|
): UpsertCookieBannerTranslationPayload!
|
||||||
|
deleteCookieBannerTranslation(
|
||||||
|
input: DeleteCookieBannerTranslationInput!
|
||||||
|
): DeleteCookieBannerTranslationPayload!
|
||||||
}
|
}
|
||||||
|
|
||||||
input CreateCookieBannerInput {
|
input CreateCookieBannerInput {
|
||||||
@@ -261,6 +278,7 @@ input UpdateCookieBannerInput {
|
|||||||
privacyPolicyUrl: String
|
privacyPolicyUrl: String
|
||||||
consentExpiryDays: Int
|
consentExpiryDays: Int
|
||||||
consentMode: CookieConsentMode
|
consentMode: CookieConsentMode
|
||||||
|
defaultLanguage: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input DeleteCookieBannerInput {
|
input DeleteCookieBannerInput {
|
||||||
@@ -387,3 +405,24 @@ type DeleteCookiePayload {
|
|||||||
deletedCookieId: ID!
|
deletedCookieId: ID!
|
||||||
cookieBanner: CookieBanner!
|
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!
|
||||||
|
}
|
||||||
|
|||||||
@@ -73,7 +73,18 @@ func NewCookieBanner(b *coredata.CookieBanner) *CookieBanner {
|
|||||||
ConsentExpiryDays: b.ConsentExpiryDays,
|
ConsentExpiryDays: b.ConsentExpiryDays,
|
||||||
ConsentMode: b.ConsentMode,
|
ConsentMode: b.ConsentMode,
|
||||||
ShowBranding: b.ShowBranding,
|
ShowBranding: b.ShowBranding,
|
||||||
|
DefaultLanguage: b.DefaultLanguage,
|
||||||
CreatedAt: b.CreatedAt,
|
CreatedAt: b.CreatedAt,
|
||||||
UpdatedAt: b.UpdatedAt,
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user