From f457ee6c127fd7afe92e3bad61c99b86395a721b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Jerman?= Date: Tue, 14 Sep 2021 13:05:02 +0200 Subject: [PATCH] Define base codegen for resource translations --- def/compose.module-field.yaml | 9 ++ def/compose.module.yaml | 8 + def/compose.namespace.yaml | 6 + def/compose.page.yaml | 12 ++ .../templates/gocode/locale/service.go.tpl | 147 ++++++++++++++++++ .../templates/gocode/locale/types.go.tpl | 132 ++++++++++++++++ pkg/codegen-v3/internal/def/doc.go | 5 + pkg/codegen-v3/internal/def/locale.go | 127 +++++++++++++++ pkg/codegen-v3/internal/gen/locale.go | 69 ++++++++ pkg/codegen-v3/loader.go | 5 +- 10 files changed, 518 insertions(+), 2 deletions(-) create mode 100644 pkg/codegen-v3/assets/templates/gocode/locale/service.go.tpl create mode 100644 pkg/codegen-v3/assets/templates/gocode/locale/types.go.tpl create mode 100644 pkg/codegen-v3/internal/def/locale.go create mode 100644 pkg/codegen-v3/internal/gen/locale.go diff --git a/def/compose.module-field.yaml b/def/compose.module-field.yaml index a0eba5213..72003bb8c 100644 --- a/def/compose.module-field.yaml +++ b/def/compose.module-field.yaml @@ -9,3 +9,12 @@ rbac: record.value.update: canFnName: CanUpdateRecordValue description: Update field value on records + +locale: + resource: + references: [ namespace, module, ID ] + + skipSvc: true + keys: + - label + - { name: validatorError, path: "expression.validator.{{validatorID}}.error", custom: true, customHandler: validatorError } diff --git a/def/compose.module.yaml b/def/compose.module.yaml index e9a281a73..b3a8a7ffd 100644 --- a/def/compose.module.yaml +++ b/def/compose.module.yaml @@ -14,3 +14,11 @@ rbac: description: Create record records.search: description: List, search or filter records + +locale: + resource: + references: [ namespace, ID ] + + extended: true + keys: + - name diff --git a/def/compose.namespace.yaml b/def/compose.namespace.yaml index 26bab3c5e..fa0f355e8 100644 --- a/def/compose.namespace.yaml +++ b/def/compose.namespace.yaml @@ -23,3 +23,9 @@ rbac: description: Create page on namespace pages.search: description: List, search or filter pages on namespace + +locale: + keys: + - name + - { path: subtitle, field: "Meta.Subtitle" } + - { path: description, field: "Meta.Description" } diff --git a/def/compose.page.yaml b/def/compose.page.yaml index 627189337..34e3dd79c 100644 --- a/def/compose.page.yaml +++ b/def/compose.page.yaml @@ -9,3 +9,15 @@ rbac: description: Update page delete: description: Delete page + +locale: + resource: + references: [ namespace, ID ] + + extended: true + keys: + - title + - description + - { name: blockTitle, path: "pageBlock.{{blockID}}.title", custom: true } + - { name: blockDescription, path: "pageBlock.{{blockID}}.description", custom: true } + - { name: blockAutomationButtonlabel, path: "pageBlock.{{blockID}}.automation.{{buttonID}}.label", custom: true } diff --git a/pkg/codegen-v3/assets/templates/gocode/locale/service.go.tpl b/pkg/codegen-v3/assets/templates/gocode/locale/service.go.tpl new file mode 100644 index 000000000..5f3c3a6d5 --- /dev/null +++ b/pkg/codegen-v3/assets/templates/gocode/locale/service.go.tpl @@ -0,0 +1,147 @@ +package {{ .Package }} + +{{ template "header-gentext.tpl" }} +{{ template "header-definitions.tpl" . }} + +import ( + "context" + + "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/actionlog" + "github.com/cortezaproject/corteza-server/pkg/auth" + "github.com/cortezaproject/corteza-server/pkg/locale" + "github.com/cortezaproject/corteza-server/store" + systemTypes "github.com/cortezaproject/corteza-server/system/types" +) + +type ( + resourceTranslation struct { + actionlog actionlog.Recorder + locale locale.Resource + store store.Storer + ac localeAccessController + } + + localeAccessController interface { + // CanManageResourceTranslation(context.Context) bool + } + + ResourceTranslationService interface { +{{- range .Def }} + {{ .Resource }}(ctx context.Context, {{ if .Locale.Resource }}{{ range .Locale.Resource.References }}{{ .Field }} uint64, {{ end }}{{ end }}) (locale.ResourceTranslationSet, error) +{{- end }} + + Upsert(context.Context, locale.ResourceTranslationSet) error + Locale() locale.Resource + } +) + +func ResourceTranslation(ls locale.Resource) *resourceTranslation { + return &resourceTranslation{ + actionlog: DefaultActionlog, + store: DefaultStore, + locale: ls, + } +} + +func (svc resourceTranslation) Upsert(ctx context.Context, rr locale.ResourceTranslationSet) (err error) { + // @todo AC + // @todo validation + + defer locale.Global().ReloadResourceTranslations(ctx) + me := auth.GetIdentityFromContext(ctx) + + // - group by resource + localeByRes := make(map[string]locale.ResourceTranslationSet) + for _, r := range rr { + localeByRes[r.Resource] = append(localeByRes[r.Resource], r) + } + + // - for each resource, fetch the current state + sysLocale := make(systemTypes.ResourceTranslationSet, 0, len(rr)) + for res, rr := range localeByRes { + current, _, err := store.SearchResourceTranslations(ctx, svc.store, systemTypes.ResourceTranslationFilter{ + Resource: res, + }) + if err != nil { + return err + } + + // get deltas and prepare upsert accordingly + aux := current.New(rr) + aux.Walk(func(cc *systemTypes.ResourceTranslation) error { + cc.ID = nextID() + cc.CreatedAt = *now() + cc.CreatedBy = me.Identity() + + return nil + }) + sysLocale = append(sysLocale, aux...) + + aux = current.Old(rr) + aux.Walk(func(cc *systemTypes.ResourceTranslation) error { + cc.UpdatedAt = now() + cc.UpdatedBy = me.Identity() + return nil + }) + sysLocale = append(sysLocale, aux...) + } + + err = store.UpsertResourceTranslation(ctx, svc.store, sysLocale...) + if err != nil { + return err + } + return nil +} + +func (svc resourceTranslation) Locale() locale.Resource { + return svc.locale +} + +{{- range .Def }} +{{ $Resource := .Resource }} +{{ $GoType := printf "types.%s" .Resource }} + +func (svc resourceTranslation) {{ .Resource }}(ctx context.Context, {{ if .Locale.Resource }}{{ range .Locale.Resource.References }}{{ .Field }} uint64, {{ end }}{{ end }}) (locale.ResourceTranslationSet, error) { + var ( + err error + out locale.ResourceTranslationSet + res *{{ $GoType }} + k types.LocaleKey + ) + + res, err = svc.load{{$Resource}}(ctx, svc.store, {{ if .Locale.Resource }}{{ range .Locale.Resource.References }}{{ .Field }}, {{ end }}{{ end }}) + if err != nil { + return nil, err + } + + for _, tag := range svc.locale.Tags() { +{{- range .Locale.Keys}} + {{- if not .Custom }} + k = types.LocaleKey{{ $Resource }}{{coalesce (export .Name) (export .Path) }} + out = append(out, &locale.ResourceTranslation{ + Resource: res.ResourceTranslation(), + Lang: tag.String(), + Key: k.Path, + Msg: svc.locale.TRFor(tag, res.ResourceTranslation(), k.Path), + }) + {{ end }} +{{- end}} + +{{- range .Locale.Keys}} + {{- if and .Custom .CustomHandler }} + auxResSet, err = svc.{{unexport $Resource}}{{export .CustomHandler}}Handler(ctx, tag, res, k.Path) + {{- end}} +{{- end}} + } + +{{- if .Locale.Extended }} + + tmp, err := svc.{{unexport $Resource}}Extended(ctx, res) + return append(out, tmp...), err + {{- else }} + return out, nil +{{- end }} +} + +{{- end }} diff --git a/pkg/codegen-v3/assets/templates/gocode/locale/types.go.tpl b/pkg/codegen-v3/assets/templates/gocode/locale/types.go.tpl new file mode 100644 index 000000000..48563f920 --- /dev/null +++ b/pkg/codegen-v3/assets/templates/gocode/locale/types.go.tpl @@ -0,0 +1,132 @@ +package {{ .Package }} + +{{ template "header-gentext.tpl" }} +{{ template "header-definitions.tpl" . }} + +import ( + "fmt" + "strconv" + "github.com/cortezaproject/corteza-server/pkg/locale" +) + +type ( + LocaleKey struct { + Name string + Resource string + Path string + CustomHandler string + } +) + +// Types and stuff +const ( +{{- range .Def }} + {{ .Resource }}ResourceTranslationType = "{{ .Locale.ResourceType }}" +{{- end }} +) + +var ( +{{- range .Def }} +{{- $Resource := .Resource }} + {{- range .Locale.Keys}} + LocaleKey{{ $Resource }}{{coalesce (export .Name) (export .Path) }} = LocaleKey{ + Name: "{{.Name}}", + Resource: {{ $Resource }}ResourceTranslationType, + Path: "{{.Path }}",{{ if .CustomHandler }} + CustomHandler: "{{ .CustomHandler }}", + {{- end }} + } + {{- end}} +{{- end }} +) + +{{- range .Def }} +{{ $Resource := .Resource }} +{{ $GoType := printf "types.%s" .Resource }} + + +// ResourceTranslation returns string representation of Locale resource for {{ .Resource }} by calling {{ .Resource }}ResourceTranslation fn +// +// Locale resource is in the {{ .Locale.ResourceType }}/... format +// +// This function is auto-generated +func (r {{ .Resource }}) ResourceTranslation() string { + return {{ .Resource }}ResourceTranslation({{ if .Locale.Resource }}{{ range .Locale.Resource.References }}r.{{ export .Field }},{{ end }}{{ end }}) +} + +// {{ .Resource }}ResourceTranslation returns string representation of Locale resource for {{ .Resource }} +// +// Locale resource is in the {{ .Locale.ResourceType }}/{{- if .Locale.Resource.References }}...{{ end }} format +// +// This function is auto-generated +func {{ .Resource }}ResourceTranslation({{ if .Locale.Resource }}{{ range .Locale.Resource.References }}{{ unexport .Field }} uint64,{{ end }}{{ end }}) string { + {{- if .Locale.Resource.References }} + cpts := []interface{{"{}"}}{{"{"}}{{ .Resource }}ResourceTranslationType{{"}"}} + cpts = append(cpts, {{range .Locale.Resource.References -}} + strconv.FormatUint({{ unexport .Field }}, 10), + {{- end }}) + + return fmt.Sprintf({{ .Resource }}ResourceTranslationTpl(), cpts...) + {{- end }} +} + +// @todo template +func {{ .Resource }}ResourceTranslationTpl() string { + {{- if .Locale.Resource.References }} + return "%s + {{- range .Locale.Resource.References }}/%s{{- end }}" + + {{- else }} + return "%s" + {{- end }} +} + +func (r *{{ .Resource }}) DecodeTranslations(tt locale.ResourceTranslationIndex) { + var aux *locale.ResourceTranslation + +{{- range .Locale.Keys}} +{{- if not .Custom }} + if aux = tt.FindByKey(LocaleKey{{ $Resource }}{{coalesce (export .Name) (export .Path) }}.Path); aux != nil { + r.{{ .Field }} = aux.Msg + } +{{- end}} +{{- end}} + +{{- range .Locale.Keys}} +{{- if and .Custom .CustomHandler }} + r.decodeTranslations{{export .CustomHandler }}(tt) +{{- end}} +{{- end}} + +{{- if .Locale.Extended }} + + r.decodeTranslations(tt) +{{- end }} +} + +func (r *{{ .Resource }}) EncodeTranslations() (out locale.ResourceTranslationSet) { + out = locale.ResourceTranslationSet{ +{{- range .Locale.Keys}} +{{- if not .Custom }} + { + Resource: r.ResourceTranslation(), + Key: LocaleKey{{ $Resource }}{{coalesce (export .Name) (export .Path) }}.Path, + Msg: r.{{ .Field }}, + }, +{{- end}} +{{- end}} + } +{{range .Locale.Keys}} +{{- if and .Custom .CustomHandler }} + out = append(out, r.encodeTranslations{{export .CustomHandler}}()...) +{{- end}} +{{- end}} + +{{- if .Locale.Extended }} + out = append(out, r.encodeTranslations()...) +{{- end }} + + return out +} + +{{- end }} diff --git a/pkg/codegen-v3/internal/def/doc.go b/pkg/codegen-v3/internal/def/doc.go index ab11b98fe..b47b1058e 100644 --- a/pkg/codegen-v3/internal/def/doc.go +++ b/pkg/codegen-v3/internal/def/doc.go @@ -18,6 +18,7 @@ type ( Resource string Source string RBAC *rbac + Locale *locale Envoy bool `yaml:"envoy"` } ) @@ -67,6 +68,10 @@ func (doc *Document) Proc(filename string) error { return err } + if err := doc.Locale.proc(doc.Component, doc.Resource); err != nil { + return err + } + doc.Resource = tpl.Export(doc.Resource) return nil diff --git a/pkg/codegen-v3/internal/def/locale.go b/pkg/codegen-v3/internal/def/locale.go new file mode 100644 index 000000000..f173158c2 --- /dev/null +++ b/pkg/codegen-v3/internal/def/locale.go @@ -0,0 +1,127 @@ +package def + +import ( + "fmt" + "strings" + + "github.com/cortezaproject/corteza-server/pkg/codegen-v3/internal/tpl" + "github.com/cortezaproject/corteza-server/pkg/y7s" + "gopkg.in/yaml.v3" +) + +type ( + locale struct { + ResourceType string `yaml:"resourceType"` + Resource *resourceTranslation + Extended bool + SkipSvc bool `yaml:"skipSvc"` + Keys localeKeys + } + + resourceTranslation struct { + References []*resourceTranslationRef + } + + resourceTranslationRef struct { + Field string + ResourceType string + Resource string + Component string + + custom bool + } + + localeKeys []*localeKey + + localeKey struct { + Name string + Path string + Custom bool + CustomHandler string `yaml:"customHandler"` + Description string + Field string + } +) + +func (r *locale) proc(component, resource string) error { + if r == nil { + return nil + } + + if r.ResourceType == "" { + if strings.ToLower(resource) == "component" { + r.ResourceType = component + } else { + r.ResourceType = fmt.Sprintf("%s:%s", component, resource) + } + } + + if r.Resource == nil { + r.Resource = &resourceTranslation{References: []*resourceTranslationRef{{Field: "ID"}}} + } + + // check types of each referenced component + // and self-references (field==ID) with own resource type + for _, rc := range r.Resource.References { + if !rc.custom { + if rc.Field == "ID" { + rc.ResourceType = r.ResourceType + rc.Component = component + rc.Resource = resource + } else { + rc.ResourceType = fmt.Sprintf("%s:%s", component, rc.Field) + rc.Component = component + rc.Resource = rc.Field + rc.Field = rc.Field + "ID" + } + } + } + + // assure missing key field paths + for _, k := range r.Keys { + k.Custom = k.Custom || k.CustomHandler != "" + + // Guess the key path + if k.Field == "" { + k.Field = tpl.Export(k.Path) + } + + // Guess the name + if k.Name == "" { + k.Name = k.Path + } + } + + return nil +} + +func (op *localeKey) UnmarshalYAML(n *yaml.Node) error { + type auxType localeKey + + switch { + case y7s.IsKind(n, yaml.ScalarNode): + return y7s.DecodeScalar(n, "locale key", &op.Path) + + case y7s.IsKind(n, yaml.MappingNode): + var aux = (*auxType)(op) + return n.Decode(aux) + } + + return y7s.NodeErr(n, "unsupported formatting") +} + +func (op *resourceTranslationRef) UnmarshalYAML(n *yaml.Node) error { + if y7s.IsKind(n, yaml.ScalarNode) { + op.Field = n.Value + if n.Value != "ID" { + op.ResourceType = n.Value + } + + return nil + } + + type auxType resourceTranslationRef + var aux = (*auxType)(op) + aux.custom = true + return n.Decode(aux) +} diff --git a/pkg/codegen-v3/internal/gen/locale.go b/pkg/codegen-v3/internal/gen/locale.go new file mode 100644 index 000000000..01147e4cd --- /dev/null +++ b/pkg/codegen-v3/internal/gen/locale.go @@ -0,0 +1,69 @@ +package gen + +import ( + "fmt" + "text/template" + + "github.com/cortezaproject/corteza-server/pkg/codegen-v3/internal/def" + "github.com/cortezaproject/corteza-server/pkg/codegen-v3/internal/tpl" +) + +func Locale(t *template.Template, dd []*def.Document) error { + return List{ + "types": localeTypes, + "services": localeServices, + }.Generate(t, dd) +} + +func localeTypes(t *template.Template, dd []*def.Document) (err error) { + const ( + templateName = "locale/types.go.tpl" + outputPathTpl = "%s/types/locale.gen.go" + ) + + dd = filter(dd, func(d *def.Document) bool { return d.Locale != nil }) + + for component, perComponent := range partByComponent(dd) { + w := tpl.Wrap{ + Package: "types", + Component: component, + Def: perComponent, + } + + err = tpl.GoTemplate(fmt.Sprintf(outputPathTpl, component), t.Lookup(templateName), w) + if err != nil { + return + } + } + + return +} + +func localeServices(t *template.Template, dd []*def.Document) (err error) { + const ( + templateName = "locale/service.go.tpl" + outputPathTpl = "%s/service/locale.gen.go" + ) + + dd = filter(dd, func(d *def.Document) bool { return d.Locale != nil && !d.Locale.SkipSvc }) + + for component, perComponent := range partByComponent(dd) { + w := tpl.Wrap{ + Package: "service", + Component: component, + Def: perComponent, + } + + err = tpl.GoTemplate(fmt.Sprintf(outputPathTpl, component), t.Lookup(templateName), w) + if err != nil { + return + } + } + + for _, d := range dd { + if d.Locale != nil { + } + } + + return +} diff --git a/pkg/codegen-v3/loader.go b/pkg/codegen-v3/loader.go index a0f0ac865..1dc8f46ed 100644 --- a/pkg/codegen-v3/loader.go +++ b/pkg/codegen-v3/loader.go @@ -26,8 +26,9 @@ func main() { } cli.HandleError(gen.List{ - "RBAC": gen.RBAC, - "Envoy": gen.Envoy, + "RBAC": gen.RBAC, + "Envoy": gen.Envoy, + "Locale": gen.Locale, }.Generate(tpls, dd)) }