Define base codegen for resource translations

This commit is contained in:
Tomaž Jerman
2021-09-22 11:26:14 +02:00
parent 4da147ec1a
commit f457ee6c12
10 changed files with 518 additions and 2 deletions
+9
View File
@@ -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 }
+8
View File
@@ -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
+6
View File
@@ -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" }
+12
View File
@@ -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 }
@@ -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 }}
@@ -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 }}
+5
View File
@@ -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
+127
View File
@@ -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)
}
+69
View File
@@ -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
}
+3 -2
View File
@@ -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))
}