feat: Custom message files (#1992)

* feat: add get custom message text to admin api

* feat: read custom message texts from files

* feat: get languages in apis

* feat: get languages in apis

* feat: get languages in apis

* feat: pr feedback

* feat: docs

* feat: merge main
This commit is contained in:
Fabi
2021-07-13 07:13:39 +02:00
committed by GitHub
parent 7ebf0333c3
commit 03a38fbf1c
40 changed files with 1108 additions and 245 deletions

View File

@@ -14,6 +14,7 @@ import (
"github.com/caos/logging"
"github.com/ghodss/yaml"
"github.com/golang/protobuf/ptypes"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/config/systemdefaults"
@@ -21,6 +22,7 @@ import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/i18n"
iam_model "github.com/caos/zitadel/internal/iam/model"
iam_view "github.com/caos/zitadel/internal/iam/repository/view"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model"
@@ -37,15 +39,30 @@ import (
)
type OrgRepository struct {
SearchLimit uint64
Eventstore v1.Eventstore
View *mgmt_view.View
Roles []string
SystemDefaults systemdefaults.SystemDefaults
PrefixAvatarURL string
LoginDir http.FileSystem
TranslationFileContents map[string][]byte
mutex sync.Mutex
SearchLimit uint64
Eventstore v1.Eventstore
View *mgmt_view.View
Roles []string
SystemDefaults systemdefaults.SystemDefaults
PrefixAvatarURL string
LoginDir http.FileSystem
NotificationDir http.FileSystem
LoginTranslationFileContents map[string][]byte
NotificationTranslationFileContents map[string][]byte
mutex sync.Mutex
supportedLangs []language.Tag
}
func (repo *OrgRepository) Languages(ctx context.Context) ([]language.Tag, error) {
if len(repo.supportedLangs) == 0 {
langs, err := i18n.SupportedLanguages(repo.LoginDir)
if err != nil {
logging.Log("ADMIN-tiMWs").WithError(err).Debug("unable to parse language")
return nil, err
}
repo.supportedLangs = langs
}
return repo.supportedLangs, nil
}
func (repo *OrgRepository) OrgByID(ctx context.Context, id string) (*org_model.OrgView, error) {
@@ -576,68 +593,74 @@ func (repo *OrgRepository) GetMailTemplate(ctx context.Context) (*iam_model.Mail
return iam_es_model.MailTemplateViewToModel(template), err
}
func (repo *OrgRepository) GetDefaultMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error) {
texts, err := repo.View.MessageTextsByAggregateID(repo.SystemDefaults.IamID)
if err != nil {
return nil, err
}
return iam_es_model.MessageTextsViewToModel(texts, true), err
}
func (repo *OrgRepository) GetMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error) {
defaultIn := false
texts, err := repo.View.MessageTextsByAggregateID(authz.GetCtxData(ctx).OrgID)
if errors.IsNotFound(err) || len(texts) == 0 {
texts, err = repo.View.MessageTextsByAggregateID(repo.SystemDefaults.IamID)
func (repo *OrgRepository) GetDefaultMessageText(ctx context.Context, textType, lang string) (*domain.CustomMessageText, error) {
repo.mutex.Lock()
defer repo.mutex.Unlock()
var err error
contents, ok := repo.NotificationTranslationFileContents[lang]
if !ok {
contents, err = repo.readTranslationFile(repo.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", lang))
if errors.IsNotFound(err) {
contents, err = repo.readTranslationFile(repo.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", repo.SystemDefaults.DefaultLanguage.String()))
}
if err != nil {
return nil, err
}
defaultIn = true
repo.NotificationTranslationFileContents[lang] = contents
}
notificationTextMap := make(map[string]interface{})
if err := yaml.Unmarshal(contents, &notificationTextMap); err != nil {
return nil, errors.ThrowInternal(err, "TEXT-093sd", "Errors.TranslationFile.ReadError")
}
texts, err := repo.View.CustomTextsByAggregateIDAndTemplateAndLand(repo.SystemDefaults.IamID, textType, lang)
if err != nil {
return nil, err
}
return iam_es_model.MessageTextsViewToModel(texts, defaultIn), err
}
func (repo *OrgRepository) GetDefaultMessageText(ctx context.Context, textType, lang string) (*iam_model.MessageTextView, error) {
text, err := repo.View.MessageTextByIDs(repo.SystemDefaults.IamID, textType, lang)
if err != nil {
return nil, err
}
text.Default = true
return iam_es_model.MessageTextViewToModel(text), err
}
func (repo *OrgRepository) GetMessageText(ctx context.Context, orgID, textType, lang string) (*iam_model.MessageTextView, error) {
text, err := repo.View.MessageTextByIDs(orgID, textType, lang)
if errors.IsNotFound(err) {
result, err := repo.GetDefaultMessageText(ctx, textType, lang)
if err != nil {
return nil, err
for _, text := range texts {
messageTextMap, ok := notificationTextMap[textType].(map[string]interface{})
if !ok {
continue
}
return result, nil
messageTextMap[text.Key] = text.Text
}
jsonbody, err := json.Marshal(notificationTextMap)
if err != nil {
return nil, errors.ThrowInternal(err, "TEXT-02m8f", "Errors.TranslationFile.MergeError")
}
notificationText := new(domain.MessageTexts)
if err := json.Unmarshal(jsonbody, &notificationText); err != nil {
return nil, errors.ThrowInternal(err, "TEXT-20ops", "Errors.TranslationFile.MergeError")
}
result := notificationText.GetMessageTextByType(textType)
result.Default = true
return result, nil
}
func (repo *OrgRepository) GetMessageText(ctx context.Context, orgID, textType, lang string) (*domain.CustomMessageText, error) {
texts, err := repo.View.CustomTextsByAggregateIDAndTemplateAndLand(orgID, textType, lang)
if err != nil {
return nil, err
}
return iam_es_model.MessageTextViewToModel(text), err
if len(texts) == 0 {
return repo.GetDefaultMessageText(ctx, textType, lang)
}
return iam_es_model.CustomTextViewsToMessageDomain(repo.SystemDefaults.IamID, lang, texts), err
}
func (repo *OrgRepository) GetDefaultLoginTexts(ctx context.Context, lang string) (*domain.CustomLoginText, error) {
repo.mutex.Lock()
defer repo.mutex.Unlock()
contents, ok := repo.TranslationFileContents[lang]
contents, ok := repo.LoginTranslationFileContents[lang]
var err error
if !ok {
contents, err = repo.readTranslationFile(fmt.Sprintf("/i18n/%s.yaml", lang))
contents, err = repo.readTranslationFile(repo.LoginDir, fmt.Sprintf("/i18n/%s.yaml", lang))
if errors.IsNotFound(err) {
contents, err = repo.readTranslationFile(fmt.Sprintf("/i18n/%s.yaml", repo.SystemDefaults.DefaultLanguage.String()))
contents, err = repo.readTranslationFile(repo.LoginDir, fmt.Sprintf("/i18n/%s.yaml", repo.SystemDefaults.DefaultLanguage.String()))
}
if err != nil {
return nil, err
}
repo.TranslationFileContents[lang] = contents
repo.LoginTranslationFileContents[lang] = contents
}
loginTextMap := make(map[string]interface{})
if err := yaml.Unmarshal(contents, &loginTextMap); err != nil {
@@ -770,8 +793,8 @@ func (repo *OrgRepository) getIAMEvents(ctx context.Context, sequence uint64) ([
return repo.Eventstore.FilterEvents(ctx, query)
}
func (repo *OrgRepository) readTranslationFile(filename string) ([]byte, error) {
r, err := repo.LoginDir.Open(filename)
func (repo *OrgRepository) readTranslationFile(dir http.FileSystem, filename string) ([]byte, error) {
r, err := dir.Open(filename)
if os.IsNotExist(err) {
return nil, errors.ThrowNotFound(err, "TEXT-93nfl", "Errors.TranslationFile.NotFound")
}

View File

@@ -58,17 +58,22 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, roles []string, querie
statikLoginFS, err := fs.NewWithNamespace("login")
logging.Log("CONFI-7usEW").OnError(err).Panic("unable to start login statik dir")
statikNotificationFS, err := fs.NewWithNamespace("notification")
logging.Log("CONFI-7usEW").OnError(err).Panic("unable to start notification statik dir")
return &EsRepository{
spooler: spool,
OrgRepository: eventstore.OrgRepository{
SearchLimit: conf.SearchLimit,
Eventstore: es,
View: view,
Roles: roles,
SystemDefaults: systemDefaults,
PrefixAvatarURL: assetsAPI,
LoginDir: statikLoginFS,
TranslationFileContents: make(map[string][]byte),
SearchLimit: conf.SearchLimit,
Eventstore: es,
View: view,
Roles: roles,
SystemDefaults: systemDefaults,
PrefixAvatarURL: assetsAPI,
LoginDir: statikLoginFS,
NotificationDir: statikNotificationFS,
LoginTranslationFileContents: make(map[string][]byte),
NotificationTranslationFileContents: make(map[string][]byte),
},
ProjectRepo: eventstore.ProjectRepo{es, conf.SearchLimit, view, roles, systemDefaults.IamID, assetsAPI},
UserRepo: eventstore.UserRepo{es, conf.SearchLimit, view, systemDefaults, assetsAPI},

View File

@@ -4,6 +4,8 @@ import (
"context"
"time"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
iam_model "github.com/caos/zitadel/internal/iam/model"
@@ -11,6 +13,7 @@ import (
)
type OrgRepository interface {
Languages(ctx context.Context) ([]language.Tag, error)
OrgByID(ctx context.Context, id string) (*org_model.OrgView, error)
OrgByDomainGlobal(ctx context.Context, domain string) (*org_model.OrgView, error)
OrgChanges(ctx context.Context, id string, lastSequence uint64, limit uint64, sortAscending bool, auditLogRetention time.Duration) (*org_model.OrgChanges, error)
@@ -48,10 +51,8 @@ type OrgRepository interface {
GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)
GetMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)
GetDefaultMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error)
GetMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error)
GetDefaultMessageText(ctx context.Context, textType string, language string) (*iam_model.MessageTextView, error)
GetMessageText(ctx context.Context, orgID, textType, language string) (*iam_model.MessageTextView, error)
GetDefaultMessageText(ctx context.Context, textType string, language string) (*domain.CustomMessageText, error)
GetMessageText(ctx context.Context, orgID, textType, lang string) (*domain.CustomMessageText, error)
GetDefaultLoginTexts(ctx context.Context, lang string) (*domain.CustomLoginText, error)
GetLoginTexts(ctx context.Context, orgID, lang string) (*domain.CustomLoginText, error)