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:
Émile Ré
2026-04-23 10:49:19 +04:00
parent ef60f080f1
commit 04001c0d90
4 changed files with 154 additions and 1 deletions

View File

@@ -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} }