mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 03:57:32 +00:00
feat: text query (#2735)
* feat: change mail template to new query side * feat: adminapi message text * feat: adminapi message text * feat: adminapi message text * feat: message texts * feat: admin texts * feat: tests * feat: tests * feat: custom login text on adminapi * feat: custom login text * feat: custom login text * feat: message text prepare test * feat: login text texts * feat: custom login text * merge main * fix go.sum Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
1091
internal/query/custom_text.go
Normal file
1091
internal/query/custom_text.go
Normal file
File diff suppressed because it is too large
Load Diff
221
internal/query/custom_text_test.go
Normal file
221
internal/query/custom_text_test.go
Normal file
@@ -0,0 +1,221 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
errs "github.com/caos/zitadel/internal/errors"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
func Test_CustomTextPrepares(t *testing.T) {
|
||||
type want struct {
|
||||
sqlExpectations sqlExpectation
|
||||
err checkErr
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare interface{}
|
||||
want want
|
||||
object interface{}
|
||||
}{
|
||||
{
|
||||
name: "prepareCustomTextQuery no result",
|
||||
prepare: prepareCustomTextsQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
regexp.QuoteMeta(`SELECT zitadel.projections.custom_texts.aggregate_id,`+
|
||||
` zitadel.projections.custom_texts.sequence,`+
|
||||
` zitadel.projections.custom_texts.creation_date,`+
|
||||
` zitadel.projections.custom_texts.change_date,`+
|
||||
` zitadel.projections.custom_texts.language,`+
|
||||
` zitadel.projections.custom_texts.template,`+
|
||||
` zitadel.projections.custom_texts.key,`+
|
||||
` zitadel.projections.custom_texts.text,`+
|
||||
` COUNT(*) OVER ()`+
|
||||
` FROM zitadel.projections.custom_texts`),
|
||||
nil,
|
||||
nil,
|
||||
),
|
||||
err: func(err error) (error, bool) {
|
||||
if !errs.IsNotFound(err) {
|
||||
return fmt.Errorf("err should be zitadel.NotFoundError got: %w", err), false
|
||||
}
|
||||
return nil, true
|
||||
},
|
||||
},
|
||||
object: &CustomTexts{CustomTexts: []*CustomText{}},
|
||||
},
|
||||
{
|
||||
name: "prepareCustomTextQuery one result",
|
||||
prepare: prepareCustomTextsQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
regexp.QuoteMeta(`SELECT zitadel.projections.custom_texts.aggregate_id,`+
|
||||
` zitadel.projections.custom_texts.sequence,`+
|
||||
` zitadel.projections.custom_texts.creation_date,`+
|
||||
` zitadel.projections.custom_texts.change_date,`+
|
||||
` zitadel.projections.custom_texts.language,`+
|
||||
` zitadel.projections.custom_texts.template,`+
|
||||
` zitadel.projections.custom_texts.key,`+
|
||||
` zitadel.projections.custom_texts.text,`+
|
||||
` COUNT(*) OVER ()`+
|
||||
` FROM zitadel.projections.custom_texts`),
|
||||
[]string{
|
||||
"aggregate_id",
|
||||
"sequence",
|
||||
"creation_date",
|
||||
"change_date",
|
||||
"language",
|
||||
"template",
|
||||
"key",
|
||||
"text",
|
||||
"count",
|
||||
},
|
||||
[][]driver.Value{
|
||||
{
|
||||
"agg-id",
|
||||
uint64(20211109),
|
||||
testNow,
|
||||
testNow,
|
||||
"en",
|
||||
"template",
|
||||
"key",
|
||||
"text",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &CustomTexts{
|
||||
SearchResponse: SearchResponse{
|
||||
Count: 1,
|
||||
},
|
||||
CustomTexts: []*CustomText{
|
||||
{
|
||||
AggregateID: "agg-id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211109,
|
||||
Language: language.English,
|
||||
Template: "template",
|
||||
Key: "key",
|
||||
Text: "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareCustomTextQuery multiple result",
|
||||
prepare: prepareCustomTextsQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
regexp.QuoteMeta(`SELECT zitadel.projections.custom_texts.aggregate_id,`+
|
||||
` zitadel.projections.custom_texts.sequence,`+
|
||||
` zitadel.projections.custom_texts.creation_date,`+
|
||||
` zitadel.projections.custom_texts.change_date,`+
|
||||
` zitadel.projections.custom_texts.language,`+
|
||||
` zitadel.projections.custom_texts.template,`+
|
||||
` zitadel.projections.custom_texts.key,`+
|
||||
` zitadel.projections.custom_texts.text,`+
|
||||
` COUNT(*) OVER ()`+
|
||||
` FROM zitadel.projections.custom_texts`),
|
||||
[]string{
|
||||
"aggregate_id",
|
||||
"sequence",
|
||||
"creation_date",
|
||||
"change_date",
|
||||
"language",
|
||||
"template",
|
||||
"key",
|
||||
"text",
|
||||
"count",
|
||||
},
|
||||
[][]driver.Value{
|
||||
{
|
||||
"agg-id",
|
||||
uint64(20211109),
|
||||
testNow,
|
||||
testNow,
|
||||
"en",
|
||||
"template",
|
||||
"key",
|
||||
"text",
|
||||
},
|
||||
{
|
||||
"agg-id",
|
||||
uint64(20211109),
|
||||
testNow,
|
||||
testNow,
|
||||
"en",
|
||||
"template",
|
||||
"key2",
|
||||
"text",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &CustomTexts{
|
||||
SearchResponse: SearchResponse{
|
||||
Count: 2,
|
||||
},
|
||||
CustomTexts: []*CustomText{
|
||||
{
|
||||
AggregateID: "agg-id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211109,
|
||||
Language: language.English,
|
||||
Template: "template",
|
||||
Key: "key",
|
||||
Text: "text",
|
||||
},
|
||||
{
|
||||
AggregateID: "agg-id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211109,
|
||||
Language: language.English,
|
||||
Template: "template",
|
||||
Key: "key2",
|
||||
Text: "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareCustomTextQuery sql err",
|
||||
prepare: prepareCustomTextsQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueryErr(
|
||||
regexp.QuoteMeta(`SELECT zitadel.projections.custom_texts.aggregate_id,`+
|
||||
` zitadel.projections.custom_texts.sequence,`+
|
||||
` zitadel.projections.custom_texts.creation_date,`+
|
||||
` zitadel.projections.custom_texts.change_date,`+
|
||||
` zitadel.projections.custom_texts.language,`+
|
||||
` zitadel.projections.custom_texts.template,`+
|
||||
` zitadel.projections.custom_texts.key,`+
|
||||
` zitadel.projections.custom_texts.text,`+
|
||||
` COUNT(*) OVER ()`+
|
||||
` FROM zitadel.projections.custom_texts`),
|
||||
sql.ErrConnDone,
|
||||
),
|
||||
err: func(err error) (error, bool) {
|
||||
if !errors.Is(err, sql.ErrConnDone) {
|
||||
return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false
|
||||
}
|
||||
return nil, true
|
||||
},
|
||||
},
|
||||
object: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err)
|
||||
})
|
||||
}
|
||||
}
|
126
internal/query/mail_template.go
Normal file
126
internal/query/mail_template.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
errs "errors"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/query/projection"
|
||||
)
|
||||
|
||||
type MailTemplate struct {
|
||||
AggregateID string
|
||||
Sequence uint64
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
State domain.PolicyState
|
||||
|
||||
Template []byte
|
||||
IsDefault bool
|
||||
}
|
||||
|
||||
var (
|
||||
mailTemplateTable = table{
|
||||
name: projection.MailTemplateTable,
|
||||
}
|
||||
MailTemplateColAggregateID = Column{
|
||||
name: projection.MailTemplateAggregateIDCol,
|
||||
table: mailTemplateTable,
|
||||
}
|
||||
MailTemplateColSequence = Column{
|
||||
name: projection.MailTemplateSequenceCol,
|
||||
table: mailTemplateTable,
|
||||
}
|
||||
MailTemplateColCreationDate = Column{
|
||||
name: projection.MailTemplateCreationDateCol,
|
||||
table: mailTemplateTable,
|
||||
}
|
||||
MailTemplateColChangeDate = Column{
|
||||
name: projection.MailTemplateChangeDateCol,
|
||||
table: mailTemplateTable,
|
||||
}
|
||||
MailTemplateColTemplate = Column{
|
||||
name: projection.MailTemplateTemplateCol,
|
||||
table: mailTemplateTable,
|
||||
}
|
||||
MailTemplateColIsDefault = Column{
|
||||
name: projection.MailTemplateIsDefaultCol,
|
||||
table: mailTemplateTable,
|
||||
}
|
||||
MailTemplateColState = Column{
|
||||
name: projection.MailTemplateStateCol,
|
||||
table: mailTemplateTable,
|
||||
}
|
||||
)
|
||||
|
||||
func (q *Queries) MailTemplateByOrg(ctx context.Context, orgID string) (*MailTemplate, error) {
|
||||
stmt, scan := prepareMailTemplateQuery()
|
||||
query, args, err := stmt.Where(
|
||||
sq.Or{
|
||||
sq.Eq{
|
||||
MailTemplateColAggregateID.identifier(): orgID,
|
||||
},
|
||||
sq.Eq{
|
||||
MailTemplateColAggregateID.identifier(): q.iamID,
|
||||
},
|
||||
}).
|
||||
OrderBy(MailTemplateColIsDefault.identifier()).
|
||||
Limit(1).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-m0sJg", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
row := q.client.QueryRowContext(ctx, query, args...)
|
||||
return scan(row)
|
||||
}
|
||||
|
||||
func (q *Queries) DefaultMailTemplate(ctx context.Context) (*MailTemplate, error) {
|
||||
stmt, scan := prepareMailTemplateQuery()
|
||||
query, args, err := stmt.Where(sq.Eq{
|
||||
MailTemplateColAggregateID.identifier(): q.iamID,
|
||||
}).
|
||||
OrderBy(MailTemplateColIsDefault.identifier()).
|
||||
Limit(1).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-2m0fH", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
row := q.client.QueryRowContext(ctx, query, args...)
|
||||
return scan(row)
|
||||
}
|
||||
|
||||
func prepareMailTemplateQuery() (sq.SelectBuilder, func(*sql.Row) (*MailTemplate, error)) {
|
||||
return sq.Select(
|
||||
MailTemplateColAggregateID.identifier(),
|
||||
MailTemplateColSequence.identifier(),
|
||||
MailTemplateColCreationDate.identifier(),
|
||||
MailTemplateColChangeDate.identifier(),
|
||||
MailTemplateColTemplate.identifier(),
|
||||
MailTemplateColIsDefault.identifier(),
|
||||
MailTemplateColState.identifier(),
|
||||
).
|
||||
From(mailTemplateTable.identifier()).PlaceholderFormat(sq.Dollar),
|
||||
func(row *sql.Row) (*MailTemplate, error) {
|
||||
policy := new(MailTemplate)
|
||||
err := row.Scan(
|
||||
&policy.AggregateID,
|
||||
&policy.Sequence,
|
||||
&policy.CreationDate,
|
||||
&policy.ChangeDate,
|
||||
&policy.Template,
|
||||
&policy.IsDefault,
|
||||
&policy.State,
|
||||
)
|
||||
if err != nil {
|
||||
if errs.Is(err, sql.ErrNoRows) {
|
||||
return nil, errors.ThrowNotFound(err, "QUERY-2NO0g", "Errors.MailTemplate.NotFound")
|
||||
}
|
||||
return nil, errors.ThrowInternal(err, "QUERY-4Nisf", "Errors.Internal")
|
||||
}
|
||||
return policy, nil
|
||||
}
|
||||
}
|
324
internal/query/message_text.go
Normal file
324
internal/query/message_text.go
Normal file
@@ -0,0 +1,324 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
errs "errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"golang.org/x/text/language"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/query/projection"
|
||||
)
|
||||
|
||||
type MessageTexts struct {
|
||||
InitCode MessageText
|
||||
PasswordReset MessageText
|
||||
VerifyEmail MessageText
|
||||
VerifyPhone MessageText
|
||||
DomainClaimed MessageText
|
||||
PasswordlessRegistration MessageText
|
||||
}
|
||||
|
||||
type MessageText struct {
|
||||
AggregateID string
|
||||
Sequence uint64
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
State domain.PolicyState
|
||||
|
||||
IsDefault bool
|
||||
|
||||
Type string
|
||||
Language language.Tag
|
||||
Title string
|
||||
PreHeader string
|
||||
Subject string
|
||||
Greeting string
|
||||
Text string
|
||||
ButtonText string
|
||||
Footer string
|
||||
}
|
||||
|
||||
var (
|
||||
messageTextTable = table{
|
||||
name: projection.MessageTextTable,
|
||||
}
|
||||
MessageTextColAggregateID = Column{
|
||||
name: projection.MessageTextAggregateIDCol,
|
||||
table: messageTextTable,
|
||||
}
|
||||
MessageTextColSequence = Column{
|
||||
name: projection.MessageTextSequenceCol,
|
||||
table: messageTextTable,
|
||||
}
|
||||
MessageTextColCreationDate = Column{
|
||||
name: projection.MessageTextCreationDateCol,
|
||||
table: messageTextTable,
|
||||
}
|
||||
MessageTextColChangeDate = Column{
|
||||
name: projection.MessageTextChangeDateCol,
|
||||
table: messageTextTable,
|
||||
}
|
||||
MessageTextColState = Column{
|
||||
name: projection.MessageTextStateCol,
|
||||
table: messageTextTable,
|
||||
}
|
||||
MessageTextColType = Column{
|
||||
name: projection.MessageTextTypeCol,
|
||||
table: messageTextTable,
|
||||
}
|
||||
MessageTextColLanguage = Column{
|
||||
name: projection.MessageTextLanguageCol,
|
||||
table: messageTextTable,
|
||||
}
|
||||
MessageTextColTitle = Column{
|
||||
name: projection.MessageTextTitleCol,
|
||||
table: messageTextTable,
|
||||
}
|
||||
MessageTextColPreHeader = Column{
|
||||
name: projection.MessageTextPreHeaderCol,
|
||||
table: messageTextTable,
|
||||
}
|
||||
MessageTextColSubject = Column{
|
||||
name: projection.MessageTextSubjectCol,
|
||||
table: messageTextTable,
|
||||
}
|
||||
MessageTextColGreeting = Column{
|
||||
name: projection.MessageTextGreetingCol,
|
||||
table: messageTextTable,
|
||||
}
|
||||
MessageTextColText = Column{
|
||||
name: projection.MessageTextTextCol,
|
||||
table: messageTextTable,
|
||||
}
|
||||
MessageTextColButtonText = Column{
|
||||
name: projection.MessageTextButtonTextCol,
|
||||
table: messageTextTable,
|
||||
}
|
||||
MessageTextColFooter = Column{
|
||||
name: projection.MessageTextFooterCol,
|
||||
table: messageTextTable,
|
||||
}
|
||||
)
|
||||
|
||||
func (q *Queries) MessageTextByOrg(ctx context.Context, orgID string) (*MessageText, error) {
|
||||
stmt, scan := prepareMessageTextQuery()
|
||||
query, args, err := stmt.Where(
|
||||
sq.Or{
|
||||
sq.Eq{
|
||||
MessageTextColAggregateID.identifier(): orgID,
|
||||
},
|
||||
sq.Eq{
|
||||
MessageTextColAggregateID.identifier(): q.iamID,
|
||||
},
|
||||
}).
|
||||
OrderBy(MessageTextColAggregateID.identifier()).
|
||||
Limit(1).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-90n3N", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
row := q.client.QueryRowContext(ctx, query, args...)
|
||||
return scan(row)
|
||||
}
|
||||
|
||||
func (q *Queries) DefaultMessageText(ctx context.Context) (*MessageText, error) {
|
||||
stmt, scan := prepareMessageTextQuery()
|
||||
query, args, err := stmt.Where(sq.Eq{
|
||||
MessageTextColAggregateID.identifier(): q.iamID,
|
||||
}).
|
||||
Limit(1).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-1b9mf", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
row := q.client.QueryRowContext(ctx, query, args...)
|
||||
return scan(row)
|
||||
}
|
||||
|
||||
func (q *Queries) DefaultMessageTextByTypeAndLanguageFromFileSystem(messageType, language string) (*MessageText, error) {
|
||||
contents, err := q.readNotificationTextMessages(language)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
messageTexts := new(MessageTexts)
|
||||
if err := yaml.Unmarshal(contents, messageTexts); err != nil {
|
||||
return nil, errors.ThrowInternal(err, "TEXT-3N9fs", "Errors.TranslationFile.ReadError")
|
||||
}
|
||||
return messageTexts.GetMessageTextByType(messageType), nil
|
||||
}
|
||||
|
||||
func (q *Queries) CustomMessageTextByTypeAndLanguage(ctx context.Context, aggregateID, messageType, language string) (*MessageText, error) {
|
||||
stmt, scan := prepareMessageTextQuery()
|
||||
query, args, err := stmt.Where(
|
||||
sq.Eq{
|
||||
MessageTextColAggregateID.identifier(): aggregateID,
|
||||
MessageTextColType.identifier(): messageType,
|
||||
MessageTextColLanguage.identifier(): language,
|
||||
},
|
||||
).
|
||||
Limit(1).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-1b9mf", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
row := q.client.QueryRowContext(ctx, query, args...)
|
||||
return scan(row)
|
||||
}
|
||||
|
||||
func (q *Queries) IAMMessageTextByTypeAndLanguage(ctx context.Context, messageType, language string) (*MessageText, error) {
|
||||
contents, err := q.readNotificationTextMessages(language)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
notificationTextMap := make(map[string]interface{})
|
||||
if err := yaml.Unmarshal(contents, ¬ificationTextMap); err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-ekjFF", "Errors.TranslationFile.ReadError")
|
||||
}
|
||||
texts, err := q.CustomTextList(ctx, domain.IAMID, messageType, language)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, text := range texts.CustomTexts {
|
||||
messageTextMap, ok := notificationTextMap[messageType].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
messageTextMap[text.Key] = text.Text
|
||||
}
|
||||
jsonbody, err := json.Marshal(notificationTextMap)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-3m8fJ", "Errors.TranslationFile.MergeError")
|
||||
}
|
||||
notificationText := new(MessageTexts)
|
||||
if err := json.Unmarshal(jsonbody, ¬ificationText); err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-9MkfD", "Errors.TranslationFile.MergeError")
|
||||
}
|
||||
result := notificationText.GetMessageTextByType(messageType)
|
||||
result.IsDefault = true
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (q *Queries) readNotificationTextMessages(language string) ([]byte, error) {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
var err error
|
||||
contents, ok := q.NotificationTranslationFileContents[language]
|
||||
if !ok {
|
||||
contents, err = q.readTranslationFile(q.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", language))
|
||||
if errors.IsNotFound(err) {
|
||||
contents, err = q.readTranslationFile(q.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", q.DefaultLanguage.String()))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.NotificationTranslationFileContents[language] = contents
|
||||
}
|
||||
return contents, nil
|
||||
}
|
||||
|
||||
func prepareMessageTextQuery() (sq.SelectBuilder, func(*sql.Row) (*MessageText, error)) {
|
||||
return sq.Select(
|
||||
MessageTextColAggregateID.identifier(),
|
||||
MessageTextColSequence.identifier(),
|
||||
MessageTextColCreationDate.identifier(),
|
||||
MessageTextColChangeDate.identifier(),
|
||||
MessageTextColState.identifier(),
|
||||
MessageTextColType.identifier(),
|
||||
MessageTextColLanguage.identifier(),
|
||||
MessageTextColTitle.identifier(),
|
||||
MessageTextColPreHeader.identifier(),
|
||||
MessageTextColSubject.identifier(),
|
||||
MessageTextColGreeting.identifier(),
|
||||
MessageTextColText.identifier(),
|
||||
MessageTextColButtonText.identifier(),
|
||||
MessageTextColFooter.identifier(),
|
||||
).
|
||||
From(messageTextTable.identifier()).PlaceholderFormat(sq.Dollar),
|
||||
func(row *sql.Row) (*MessageText, error) {
|
||||
msg := new(MessageText)
|
||||
lang := ""
|
||||
title := sql.NullString{}
|
||||
preHeader := sql.NullString{}
|
||||
subject := sql.NullString{}
|
||||
greeting := sql.NullString{}
|
||||
text := sql.NullString{}
|
||||
buttonText := sql.NullString{}
|
||||
footer := sql.NullString{}
|
||||
err := row.Scan(
|
||||
&msg.AggregateID,
|
||||
&msg.Sequence,
|
||||
&msg.CreationDate,
|
||||
&msg.ChangeDate,
|
||||
&msg.State,
|
||||
&msg.Type,
|
||||
&lang,
|
||||
&title,
|
||||
&preHeader,
|
||||
&subject,
|
||||
&greeting,
|
||||
&text,
|
||||
&buttonText,
|
||||
&footer,
|
||||
)
|
||||
if err != nil {
|
||||
if errs.Is(err, sql.ErrNoRows) {
|
||||
return nil, errors.ThrowNotFound(err, "QUERY-3nlrS", "Errors.MessageText.NotFound")
|
||||
}
|
||||
return nil, errors.ThrowInternal(err, "QUERY-499gJ", "Errors.Internal")
|
||||
}
|
||||
msg.Language = language.Make(lang)
|
||||
msg.Title = title.String
|
||||
msg.PreHeader = preHeader.String
|
||||
msg.Subject = subject.String
|
||||
msg.Greeting = greeting.String
|
||||
msg.Text = text.String
|
||||
msg.ButtonText = buttonText.String
|
||||
msg.Footer = footer.String
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Queries) readTranslationFile(dir http.FileSystem, filename string) ([]byte, error) {
|
||||
r, err := dir.Open(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, errors.ThrowNotFound(err, "QUERY-sN9wg", "Errors.TranslationFile.NotFound")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-93njw", "Errors.TranslationFile.ReadError")
|
||||
}
|
||||
contents, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-l0fse", "Errors.TranslationFile.ReadError")
|
||||
}
|
||||
return contents, nil
|
||||
}
|
||||
|
||||
func (m *MessageTexts) GetMessageTextByType(msgType string) *MessageText {
|
||||
switch msgType {
|
||||
case domain.InitCodeMessageType:
|
||||
return &m.InitCode
|
||||
case domain.PasswordResetMessageType:
|
||||
return &m.PasswordReset
|
||||
case domain.VerifyEmailMessageType:
|
||||
return &m.VerifyEmail
|
||||
case domain.VerifyPhoneMessageType:
|
||||
return &m.VerifyPhone
|
||||
case domain.DomainClaimedMessageType:
|
||||
return &m.DomainClaimed
|
||||
case domain.PasswordlessRegistrationMessageType:
|
||||
return &m.PasswordlessRegistration
|
||||
}
|
||||
return nil
|
||||
}
|
167
internal/query/message_text_test.go
Normal file
167
internal/query/message_text_test.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
errs "github.com/caos/zitadel/internal/errors"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
func Test_MessageTextPrepares(t *testing.T) {
|
||||
type want struct {
|
||||
sqlExpectations sqlExpectation
|
||||
err checkErr
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare interface{}
|
||||
want want
|
||||
object interface{}
|
||||
}{
|
||||
{
|
||||
name: "prepareMessageTextQuery no result",
|
||||
prepare: prepareMessageTextQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
regexp.QuoteMeta(`SELECT zitadel.projections.message_texts.aggregate_id,`+
|
||||
` zitadel.projections.message_texts.sequence,`+
|
||||
` zitadel.projections.message_texts.creation_date,`+
|
||||
` zitadel.projections.message_texts.change_date,`+
|
||||
` zitadel.projections.message_texts.state,`+
|
||||
` zitadel.projections.message_texts.type,`+
|
||||
` zitadel.projections.message_texts.language,`+
|
||||
` zitadel.projections.message_texts.title,`+
|
||||
` zitadel.projections.message_texts.pre_header,`+
|
||||
` zitadel.projections.message_texts.subject,`+
|
||||
` zitadel.projections.message_texts.greeting,`+
|
||||
` zitadel.projections.message_texts.text,`+
|
||||
` zitadel.projections.message_texts.button_text,`+
|
||||
` zitadel.projections.message_texts.footer_text`+
|
||||
` FROM zitadel.projections.message_texts`),
|
||||
nil,
|
||||
nil,
|
||||
),
|
||||
err: func(err error) (error, bool) {
|
||||
if !errs.IsNotFound(err) {
|
||||
return fmt.Errorf("err should be zitadel.NotFoundError got: %w", err), false
|
||||
}
|
||||
return nil, true
|
||||
},
|
||||
},
|
||||
object: (*MessageText)(nil),
|
||||
},
|
||||
{
|
||||
name: "prepareMesssageTextQuery found",
|
||||
prepare: prepareMessageTextQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQuery(
|
||||
regexp.QuoteMeta(`SELECT zitadel.projections.message_texts.aggregate_id,`+
|
||||
` zitadel.projections.message_texts.sequence,`+
|
||||
` zitadel.projections.message_texts.creation_date,`+
|
||||
` zitadel.projections.message_texts.change_date,`+
|
||||
` zitadel.projections.message_texts.state,`+
|
||||
` zitadel.projections.message_texts.type,`+
|
||||
` zitadel.projections.message_texts.language,`+
|
||||
` zitadel.projections.message_texts.title,`+
|
||||
` zitadel.projections.message_texts.pre_header,`+
|
||||
` zitadel.projections.message_texts.subject,`+
|
||||
` zitadel.projections.message_texts.greeting,`+
|
||||
` zitadel.projections.message_texts.text,`+
|
||||
` zitadel.projections.message_texts.button_text,`+
|
||||
` zitadel.projections.message_texts.footer_text`+
|
||||
` FROM zitadel.projections.message_texts`),
|
||||
[]string{
|
||||
"aggregate_id",
|
||||
"sequence",
|
||||
"creation_date",
|
||||
"change_date",
|
||||
"state",
|
||||
"type",
|
||||
"language",
|
||||
"title",
|
||||
"pre_header",
|
||||
"subject",
|
||||
"greeting",
|
||||
"text",
|
||||
"button_text",
|
||||
"footer_text",
|
||||
},
|
||||
[]driver.Value{
|
||||
"agg-id",
|
||||
uint64(20211109),
|
||||
testNow,
|
||||
testNow,
|
||||
domain.PolicyStateActive,
|
||||
"type",
|
||||
"en",
|
||||
"title",
|
||||
"pre_header",
|
||||
"subject",
|
||||
"greeting",
|
||||
"text",
|
||||
"button_text",
|
||||
"footer_text",
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &MessageText{
|
||||
AggregateID: "agg-id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211109,
|
||||
State: domain.PolicyStateActive,
|
||||
Type: "type",
|
||||
Language: language.English,
|
||||
Title: "title",
|
||||
PreHeader: "pre_header",
|
||||
Subject: "subject",
|
||||
Greeting: "greeting",
|
||||
Text: "text",
|
||||
ButtonText: "button_text",
|
||||
Footer: "footer_text",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareMessageTextQuery sql err",
|
||||
prepare: prepareMessageTextQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueryErr(
|
||||
regexp.QuoteMeta(`SELECT zitadel.projections.message_texts.aggregate_id,`+
|
||||
` zitadel.projections.message_texts.sequence,`+
|
||||
` zitadel.projections.message_texts.creation_date,`+
|
||||
` zitadel.projections.message_texts.change_date,`+
|
||||
` zitadel.projections.message_texts.state,`+
|
||||
` zitadel.projections.message_texts.type,`+
|
||||
` zitadel.projections.message_texts.language,`+
|
||||
` zitadel.projections.message_texts.title,`+
|
||||
` zitadel.projections.message_texts.pre_header,`+
|
||||
` zitadel.projections.message_texts.subject,`+
|
||||
` zitadel.projections.message_texts.greeting,`+
|
||||
` zitadel.projections.message_texts.text,`+
|
||||
` zitadel.projections.message_texts.button_text,`+
|
||||
` zitadel.projections.message_texts.footer_text`+
|
||||
` FROM zitadel.projections.message_texts`),
|
||||
sql.ErrConnDone,
|
||||
),
|
||||
err: func(err error) (error, bool) {
|
||||
if !errors.Is(err, sql.ErrConnDone) {
|
||||
return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false
|
||||
}
|
||||
return nil, true
|
||||
},
|
||||
},
|
||||
object: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err)
|
||||
})
|
||||
}
|
||||
}
|
@@ -92,7 +92,7 @@ func (p *CustomTextProjection) reduceSet(event eventstore.EventReader) (*handler
|
||||
logging.LogWithFields("PROJE-g0Jfs", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.CustomTextSetEventType, iam.CustomTextSetEventType}).Error("wrong event type")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "PROJE-KKfw4", "reduce.wrong.event.type")
|
||||
}
|
||||
return crdb.NewCreateStatement(
|
||||
return crdb.NewUpsertStatement(
|
||||
&customTextEvent,
|
||||
[]handler.Column{
|
||||
handler.NewCol(CustomTextAggregateIDCol, customTextEvent.Aggregate().ID),
|
||||
@@ -124,7 +124,7 @@ func (p *CustomTextProjection) reduceRemoved(event eventstore.EventReader) (*han
|
||||
handler.NewCond(CustomTextAggregateIDCol, customTextEvent.Aggregate().ID),
|
||||
handler.NewCond(CustomTextTemplateCol, customTextEvent.Template),
|
||||
handler.NewCond(CustomTextKeyCol, customTextEvent.Key),
|
||||
handler.NewCond(CustomTextLanguageCol, customTextEvent.Language),
|
||||
handler.NewCond(CustomTextLanguageCol, customTextEvent.Language.String()),
|
||||
}), nil
|
||||
}
|
||||
|
||||
|
@@ -45,7 +45,7 @@ func TestCustomTextProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO zitadel.projections.custom_texts (aggregate_id, creation_date, change_date, sequence, is_default, template, language, key, text) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedStmt: "UPSERT INTO zitadel.projections.custom_texts (aggregate_id, creation_date, change_date, sequence, is_default, template, language, key, text) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
anyArg{},
|
||||
@@ -89,7 +89,7 @@ func TestCustomTextProjection_reduces(t *testing.T) {
|
||||
"agg-id",
|
||||
"InitCode",
|
||||
"Text",
|
||||
language.English,
|
||||
"en",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -152,7 +152,7 @@ func TestCustomTextProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO zitadel.projections.custom_texts (aggregate_id, creation_date, change_date, sequence, is_default, template, language, key, text) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedStmt: "UPSERT INTO zitadel.projections.custom_texts (aggregate_id, creation_date, change_date, sequence, is_default, template, language, key, text) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
anyArg{},
|
||||
@@ -196,7 +196,7 @@ func TestCustomTextProjection_reduces(t *testing.T) {
|
||||
"agg-id",
|
||||
"InitCode",
|
||||
"Text",
|
||||
language.English,
|
||||
"en",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@@ -96,7 +96,7 @@ func (p *MessageTextProjection) reduceAdded(event eventstore.EventReader) (*hand
|
||||
return nil, errors.ThrowInvalidArgument(nil, "PROJE-2n90r", "reduce.wrong.event.type")
|
||||
}
|
||||
if !isMessageTemplate(templateEvent.Template) {
|
||||
return nil, nil
|
||||
return crdb.NewNoOpStatement(event), nil
|
||||
}
|
||||
|
||||
cols := []handler.Column{
|
||||
|
@@ -3,7 +3,10 @@ package query
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/caos/logging"
|
||||
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"github.com/caos/zitadel/internal/config/types"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
@@ -16,12 +19,21 @@ import (
|
||||
"github.com/caos/zitadel/internal/repository/project"
|
||||
usr_repo "github.com/caos/zitadel/internal/repository/user"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
"github.com/rakyll/statik/fs"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type Queries struct {
|
||||
iamID string
|
||||
eventstore *eventstore.Eventstore
|
||||
client *sql.DB
|
||||
|
||||
DefaultLanguage language.Tag
|
||||
LoginDir http.FileSystem
|
||||
NotificationDir http.FileSystem
|
||||
mutex sync.Mutex
|
||||
LoginTranslationFileContents map[string][]byte
|
||||
NotificationTranslationFileContents map[string][]byte
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -34,10 +46,21 @@ func StartQueries(ctx context.Context, es *eventstore.Eventstore, projections pr
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
repo = &Queries{
|
||||
iamID: defaults.IamID,
|
||||
eventstore: es,
|
||||
client: sqlClient,
|
||||
iamID: defaults.IamID,
|
||||
eventstore: es,
|
||||
client: sqlClient,
|
||||
DefaultLanguage: defaults.DefaultLanguage,
|
||||
LoginDir: statikLoginFS,
|
||||
NotificationDir: statikNotificationFS,
|
||||
LoginTranslationFileContents: make(map[string][]byte),
|
||||
NotificationTranslationFileContents: make(map[string][]byte),
|
||||
}
|
||||
iam_repo.RegisterEventMappers(repo.eventstore)
|
||||
usr_repo.RegisterEventMappers(repo.eventstore)
|
||||
|
Reference in New Issue
Block a user