From 3b5c39ffc0788a6e7a7728f3694c044a5cfc257a Mon Sep 17 00:00:00 2001 From: fabio Date: Mon, 4 May 2026 16:10:49 +0200 Subject: [PATCH] sistemato ts generator --- backend/data/data.db | Bin 81920 -> 81920 bytes backend/internal/auth/roles.go | 21 +- backend/pkg/ts-rpc/tsInfo.go | 547 ++++++++++++++++---------------- backend/pkg/ts-rpc/tsStruct.go | 65 ++-- backend/pkg/ts-rpc/tsTsInfo.go | 31 +- backend/pkg/ts-rpc/tsrpc.go | 12 +- frontend/src/api/admin.ts | 34 +- frontend/src/api/api.ts | 2 +- frontend/src/api/apiTypes.ts | 4 +- frontend/src/api/auth.ts | 21 +- frontend/src/api/systemUtils.ts | 26 +- frontend/src/api/users.ts | 224 ++++++------- 12 files changed, 522 insertions(+), 465 deletions(-) diff --git a/backend/data/data.db b/backend/data/data.db index ddfd2180285738dad2aacf9004564793ef56d1da..b5b654425065d13c6a593fc15c449736cbc40bdf 100644 GIT binary patch delta 610 zcmbu6KT8}z6u@U@??eyRhed2+@CpM4Ih}tyGkaJBf`Y|CK7zA1JLH-u9*2#PTde~L z*K4XY7816wu((ek`2s=;E3vS;SeU{Q3mZ*}yz>3|y~pqM`_+EG`sF2_`8xX!&-~c< zK35we7{LXc!LXdJAH&pSC3=OsdAHly{MaSs*|OdkCow@&MNC4+@5xVly95w^Q^ZBw znk#l%EmZS1>ukQ=+P2OvwLdtjUD8fc!Dwcj<5pOm(mcXPK8?IO%h( zQ)zsnJe5k>)Nv-6&bW3wO>}Op5mqoQ0+w37B6-3CEkxTX?UXRwGiQ`#JYhz-+;YlQ zgC-5i>WnodYYMSs6z7HwRt`UFQ)l8aYcyM=yh-W9lx*;4>Sb}TkH-nzAov3}<@C2J zcu|QECQ*bYY6u|$CkW2rBxpK<*Yi;eKYz#~rC}u*oDUZQvWK7tr(v!GYpaza#(!7v mZv$j-`g8qB@PGa$fPdYCM+k0V6eL{2ST(}9M#|gk>iauVQKGp3 delta 162 zcmZo@U~On%ogmF9I#I@%QFLR%GI_284E$gDAM>B(Kd@PF!3KU_ZWd-)&f?VK;>`TK z;>~CE%@f!d`5GDc8aE3nl=E$FoY&0A#>oGNf&b5D!4L2G1sNC^n3)+kxqvhy{~ZSY wJ3wPD@lU?9UjwLYHv|7}Ab%tO 0 { + log.Fatal("package loading failed") + } + + //to get line info for endpoints and typescript declarations + for _, loadedPkg := range pkgs { + pkg := loadedPkg.Name + // skip packages + skip := false + for _, excluded := range excludedPackages { + if pkg == excluded { + skip = true + break + } + } + if skip { + continue + } + // initialize package info if not exists + if _, ok := i.Packages[pkg]; !ok { + ip := TSInfoPakage{} + ip.MakeMaps() + i.Packages[pkg] = ip + } + + // start parsing files + for _, n := range loadedPkg.CompiledGoFiles { + // search for declarative // Typescript: declarations and endpoints + i.parseTypescriptDeclarations(pkg, n) + } + // parse doc to get all types and consts + docPkg, err := doc.NewFromFiles(loadedPkg.Fset, loadedPkg.Syntax, loadedPkg.PkgPath) + if err != nil { + exitOnError(err) + } + + for _, t := range docPkg.Types { + i.getType(pkg, t) + } + + for _, c := range docPkg.Consts { + i.getConst(pkg, c) + } + } +} + +// estrae le definizioni typescript // Typescript: ... +func (ts *TSInfo) parseTypescriptDeclarations(p string, n string) { + pkg := ts.Packages[p] + f, err := os.OpenFile(n, os.O_RDONLY, os.ModePerm) + if err != nil { + log.Fatalf("open file error: %v", err) + return + } + defer f.Close() + l := 1 + dat := "" + lines := []TSSourceLine{} + rd := bufio.NewReader(f) + // scansione del file per trovare le dichiarazioni typescript + for { + line, err := rd.ReadString('\n') + if err != nil { + if err == io.EOF { + break + } + log.Fatalf("read file line error: %v", err) + return + } + lines = append(lines, TSSourceLine{Pos: l, End: l + len(line), Line: l, Source: line}) + l++ + dat += line + // รจ una definizione typescript? + if strings.Contains(line, "// Typescript:") { + // typescript type, ad esempio: // Typescript: TStype=MyType=string | number + if strings.Contains(line, "TStype=") { + p := strings.Index(line, "TStype=") + s := strings.Trim(line[p+len("TStype="):], " ") + a := strings.Split(s, "=") + if len(a) == 2 { + t := TSType{ + Name: strings.Trim(a[0], " "), + Typescript: true, + Type: "UserDefined", + TsType: strings.Trim(a[1], " "), + dependOn: false, + } + pkg.types[strings.Trim(a[0], " ")] = t + } + } + // dicchiarazione typescript, ad esempio: // Typescript: TSDeclaration=MyType=string | number + if strings.Contains(line, "TSDeclaration=") { + p := strings.Index(line, "TSDeclaration=") + s := strings.Trim(line[p+len("TSDeclaration="):], " ") + a := strings.Split(s, "=") + if len(a) == 2 { + t := TSDec{ + Name: strings.Trim(a[0], " "), + Value: strings.Trim(a[1], " "), + } + pkg.decs[strings.Trim(a[0], " ")] = t + } + } + // dichiarazione endpoint, ad esempio: // Typescript: TSEndpoint= path=/api/auth/login; name=login; method=POST; request=users.LoginRequest; response=tokens.TokenPair + if strings.Contains(line, "TSEndpoint= ") { + e := ParseEndpoint(line, n, l) + if _, ok := pkg.endpoints[e.Name]; ok { + exitOnError(fmt.Errorf("enpoint name %s allready in use: %s", e.Name, line)) + } + pkg_info := pkg + pkg_info.endpoints[e.Name] = e + pkg_info.imports = e.Imports + pkg_info.isUsed = true + pkg = pkg_info + } + } + } +} + +func (i *TSInfo) getType(p string, t *doc.Type) { + var isTypescript = strings.HasPrefix(t.Doc, "Typescript:") + if isTypescript { + command := strings.TrimPrefix(t.Doc, "Typescript:") + command = strings.TrimSpace(command) + command = strings.Trim(command, "\n") + } + for _, spec := range t.Decl.Specs { + if len(t.Consts) > 0 { + i.getConst(p, t.Consts[0]) + } + switch spec.(type) { + case *ast.TypeSpec: + typeSpec := spec.(*ast.TypeSpec) + switch typeSpec.Type.(type) { + case *ast.StructType: + v := TSStruct{ + Name: typeSpec.Name.Name, + Typescript: false, + Fields: []TSSField{}, + } + v.getStruct(typeSpec) + if len(v.Fields) == 0 { + continue + } + i.Packages[p].structs[typeSpec.Name.Name] = v + for _, imp := range v.Imports { + a := strings.Split(imp, ".") + if len(a) == 2 { + if _, ok := i.Packages[p].imports[a[0]]; !ok { + i.Packages[p].imports[a[0]] = []string{} + } + if !slices.Contains(i.Packages[p].imports[a[0]], a[1]) { + i.Packages[p].imports[a[0]] = append(i.Packages[p].imports[a[0]], a[1]) + } + } + } + case *ast.Ident: + tsInfo := getFieldTsInfo(typeSpec.Type) + t := TSType{ + Name: typeSpec.Name.Name, + Typescript: true, + Type: getFieldInfo(typeSpec.Type), + TsType: tsInfo, + dependOn: toBeImported(typeSpec.Type), + } + //fmt.Printf("Type found: %s Type: %s TsType: %s AT: %s\n", t.Name, t.Type, t.TsType, t.SourceInfo) + i.Packages[p].types[typeSpec.Name.Name] = t + default: + // if isType && command != "interface" { + // exitOnError(fmt.Errorf("mismatch delaration for interface %s AT: %s", t.Doc, getSourceInfo(int(typeSpec.Name.NamePos), src))) + // } + tsInfo := getFieldTsInfo(typeSpec.Type) + t := TSType{ + Name: typeSpec.Name.Name, + Typescript: true, + Type: getFieldInfo(typeSpec.Type), + TsType: tsInfo, + dependOn: toBeImported(typeSpec.Type), + } + //fmt.Printf("Type found: %s Type: %s TsType: %s AT: %s\n", t.Name, t.Type, t.TsType, t.SourceInfo) + i.Packages[p].types[typeSpec.Name.Name] = t + } + } + } +} + +func (i *TSInfo) getConst(p string, c *doc.Value) { + var isTypescript = strings.HasPrefix(c.Doc, "Typescript:") + if isTypescript { + command := strings.TrimPrefix(c.Doc, "Typescript:") + command = strings.TrimSpace(command) + command = strings.Trim(command, "\n") + + if strings.Contains(command, "enum=") { + enumName := strings.TrimPrefix(command, "enum=") + enum := TSEnum{ + Name: enumName, + Info: []TSEnumInfo{}, + } + d := c.Decl + iota := false + iotaValue := 0 + for _, s := range d.Specs { + v := s.(*ast.ValueSpec) // safe because decl.Tok == token.CONST + if len(v.Values) > 0 { + be, ok := v.Values[0].(*ast.BinaryExpr) + if ok { + x := be.X.(*ast.BasicLit) + exitOnError(fmt.Errorf("enum binary expression not implemented %s %s %s", x.Value, be.Op.String(), be.Y)) + } + ident, ok := v.Values[0].(*ast.Ident) + if ok { + if ident.Name == "iota" { + iota = true + iotaValue = v.Names[0].Obj.Data.(int) + enum.Info = append(enum.Info, TSEnumInfo{Key: v.Names[0].Name, Value: fmt.Sprintf("%d", iotaValue)}) + } + } + list, ok := v.Values[0].(*ast.BasicLit) + if ok { + enum.Info = append(enum.Info, TSEnumInfo{Key: v.Names[0].Name, Value: list.Value}) + } + } else { + for _, name := range v.Names { + if iota { + iotaValue++ + enum.Info = append(enum.Info, TSEnumInfo{Key: name.Name, Value: fmt.Sprintf("%d", iotaValue)}) + } + + } + } + } + i.Packages[p].enums[enumName] = enum + t1 := TSType{ + Name: enumName, + Typescript: true, + Type: "", + TsType: fmt.Sprintf("typeof Enum%s[keyof typeof Enum%s] ", enumName, enumName), //getFieldTsInfo(expr.Type), + dependOn: false, + } + i.Packages[p].types[enumName] = t1 + } + + if strings.Contains(command, "const") { + d := c.Decl + for _, s := range d.Specs { + v := s.(*ast.ValueSpec) + if len(v.Names) == 0 || len(v.Values) == 0 { + continue + } + c := TSConst{ + Name: v.Names[0].Name, + Value: v.Values[0].(*ast.BasicLit).Value, + } + i.Packages[p].consts[c.Name] = c + } + } + + if strings.Contains(command, "type") { + fmt.Printf("TypeScript type declaration found in const s, but type parsing is not implemented yet\n") + } + + } +} + func (ts *TSInfo) findStruct(p string, n string) bool { if _, ok := ts.Packages[p]; ok { @@ -127,269 +407,6 @@ func (ts TSInfo) find(p string, n string) bool { // popola TsInfo con tutte le definizioni dei tipi -func (i *TSInfo) getConst(p string, c *doc.Value) { - var isTypescript = strings.HasPrefix(c.Doc, "Typescript:") - if isTypescript { - command := strings.TrimPrefix(c.Doc, "Typescript:") - command = strings.TrimSpace(command) - command = strings.Trim(command, "\n") - - if strings.Contains(command, "type") { - fmt.Printf("TypeScript type declaration found in const s, but type parsing is not implemented yet\n") - } - - if strings.Contains(command, "enum=") { - enumName := strings.TrimPrefix(command, "enum=") - enum := TSEnum{ - Name: enumName, - Info: []TSEnumInfo{}, - } - d := c.Decl - iota := false - iotaValue := 0 - for _, s := range d.Specs { - v := s.(*ast.ValueSpec) // safe because decl.Tok == token.CONST - if len(v.Values) > 0 { - be, ok := v.Values[0].(*ast.BinaryExpr) - if ok { - x := be.X.(*ast.BasicLit) - exitOnError(fmt.Errorf("enum binary expression not implemented %s %s %s", x.Value, be.Op.String(), be.Y)) - } - ident, ok := v.Values[0].(*ast.Ident) - if ok { - if ident.Name == "iota" { - iota = true - iotaValue = v.Names[0].Obj.Data.(int) - enum.Info = append(enum.Info, TSEnumInfo{Key: v.Names[0].Name, Value: fmt.Sprintf("%d", iotaValue)}) - } - } - list, ok := v.Values[0].(*ast.BasicLit) - if ok { - enum.Info = append(enum.Info, TSEnumInfo{Key: v.Names[0].Name, Value: list.Value}) - } - } else { - for _, name := range v.Names { - if iota { - iotaValue++ - enum.Info = append(enum.Info, TSEnumInfo{Key: name.Name, Value: fmt.Sprintf("%d", iotaValue)}) - } - - } - } - } - i.Packages[p].enums[enumName] = enum - t1 := TSType{ - Name: enumName, - Typescript: false, - Type: "", - TsType: fmt.Sprintf("typeof Enum%s[keyof typeof Enum%s] ", enumName, enumName), //getFieldTsInfo(expr.Type), - dependOn: false, - } - i.Packages[p].types[enumName] = t1 - } - - if strings.Contains(command, "const") { - d := c.Decl - for _, s := range d.Specs { - v := s.(*ast.ValueSpec) - if len(v.Names) == 0 || len(v.Values) == 0 { - continue - } - c := TSConst{ - Name: v.Names[0].Name, - Value: v.Values[0].(*ast.BasicLit).Value, - } - i.Packages[p].consts[c.Name] = c - } - } - - if strings.Contains(command, "type") { - fmt.Printf("TypeScript type declaration found in const s, but type parsing is not implemented yet\n") - } - - } -} - -func (i *TSInfo) getType(p string, t *doc.Type) { - var isTypescript = strings.HasPrefix(t.Doc, "Typescript:") - if isTypescript { - command := strings.TrimPrefix(t.Doc, "Typescript:") - command = strings.TrimSpace(command) - command = strings.Trim(command, "\n") - - if strings.Contains(command, "interface") { - fmt.Printf("TypeScript interface declaration found in type s, but interface parsing is not implemented yet\n") - } - - if strings.Contains(command, "type") { - fmt.Printf("TypeScript type declaration found in type s, but type parsing is not implemented yet\n") - } - - if strings.Contains(command, "enum=") { - fmt.Printf("TypeScript enum declaration found in type s, but enum parsing is not implemented yet\n") - } - } - for _, spec := range t.Decl.Specs { - /* if len(t.Consts) > 0 { - i.getConst(p, t.Consts[0]) - continue - } */ - switch spec.(type) { - case *ast.TypeSpec: - typeSpec := spec.(*ast.TypeSpec) - switch typeSpec.Type.(type) { - case *ast.StructType: - // if isType && command != "interface" { - // exitOnError(fmt.Errorf("mismatch delaration for interface %s AT: %s", t.Doc, getSourceInfo(int(typeSpec.Name.NamePos), src))) - // } - v := TSStruct{ - Name: typeSpec.Name.Name, - Typescript: false, - Fields: []TSSField{}, - SourceInfo: "", - } - v.getStruct(typeSpec) - i.Packages[p].structs[typeSpec.Name.Name] = v - default: - // if isType && command != "interface" { - // exitOnError(fmt.Errorf("mismatch delaration for interface %s AT: %s", t.Doc, getSourceInfo(int(typeSpec.Name.NamePos), src))) - // } - tsInfo := getFieldTsInfo(typeSpec.Type) - t := TSType{ - Name: typeSpec.Name.Name, - Typescript: false, - Type: getFieldInfo(typeSpec.Type), - TsType: tsInfo, - dependOn: toBeImported(typeSpec.Type), - } - //fmt.Printf("Type found: %s Type: %s TsType: %s AT: %s\n", t.Name, t.Type, t.TsType, t.SourceInfo) - i.Packages[p].types[typeSpec.Name.Name] = t - } - } - } -} - -func parseTypescriptDeclarations(n string, pkg TSInfoPakage) { - f, err := os.OpenFile(n, os.O_RDONLY, os.ModePerm) - if err != nil { - log.Fatalf("open file error: %v", err) - return - } - defer f.Close() - l := 1 - dat := "" - lines := []TSSourceLine{} - rd := bufio.NewReader(f) - for { - line, err := rd.ReadString('\n') - if err != nil { - if err == io.EOF { - break - } - - log.Fatalf("read file line error: %v", err) - return - } - lines = append(lines, TSSourceLine{Pos: l, End: l + len(line), Line: l, Source: line}) - l++ - dat += line - - if strings.Contains(line, "// Typescript:") { - if strings.Contains(line, "TStype=") { - p := strings.Index(line, "TStype=") - s := strings.Trim(line[p+len("TStype="):], " ") - a := strings.Split(s, "=") - if len(a) == 2 { - t := TSType{ - Name: strings.Trim(a[0], " "), - Typescript: true, - Type: "UserDefined", - TsType: strings.Trim(a[1], " "), - dependOn: false, - } - pkg.types[strings.Trim(a[0], " ")] = t - } - - } - if strings.Contains(line, "TSDeclaration=") { - p := strings.Index(line, "TSDeclaration=") - s := strings.Trim(line[p+len("TSDeclaration="):], " ") - a := strings.Split(s, "=") - if len(a) == 2 { - t := TSDec{ - Name: strings.Trim(a[0], " "), - Value: strings.Trim(a[1], " "), - } - pkg.decs[strings.Trim(a[0], " ")] = t - } - } - - if strings.Contains(line, "TSEndpoint= ") { - e := ParseEndpoint(line, n, l) - if _, ok := pkg.endpoints[e.Name]; ok { - exitOnError(fmt.Errorf("enpoint name %s allready in use: %s", e.Name, line)) - } - pkg_info := pkg - pkg_info.endpoints[e.Name] = e - pkg_info.imports = e.Imports - pkg_info.isUsed = true - pkg = pkg_info - } - } - - } -} - -func (i *TSInfo) Populate(path string) { - i.Packages = make(map[string]TSInfoPakage) - cfg := &packages.Config{ - Mode: packages.NeedName | - packages.NeedFiles | - packages.NeedCompiledGoFiles | - packages.NeedSyntax, - Dir: path, - } - - pkgs, err := packages.Load(cfg, "./...") - if err != nil { - log.Fatal(err) - } - if packages.PrintErrors(pkgs) > 0 { - log.Fatal("package loading failed") - } - - //to get line info for endpoints and typescript declarations - for _, loadedPkg := range pkgs { - pkg := loadedPkg.Name - // skip packages - if pkg == "typescript" || pkg == "tsrpc" { - continue - } - // initialize package info if not exists - if _, ok := i.Packages[pkg]; !ok { - i.Packages[pkg] = TSInfoPakage{structs: make(map[string]TSStruct), types: make(map[string]TSType), enums: make(map[string]TSEnum), consts: make(map[string]TSConst), decs: make(map[string]TSDec), endpoints: make(map[string]TSEndpoint)} - } - // start parsing files - for _, n := range loadedPkg.CompiledGoFiles { - // search for declarative // Typescript: declarations and endpoints - parseTypescriptDeclarations(n, i.Packages[pkg]) - } - // parse doc to get all types and consts - docPkg, err := doc.NewFromFiles(loadedPkg.Fset, loadedPkg.Syntax, loadedPkg.PkgPath) - if err != nil { - exitOnError(err) - } - - for _, t := range docPkg.Types { - i.getType(pkg, t) - } - - for _, c := range docPkg.Consts { - i.getConst(pkg, c) - } - } -} - func (ts *TSInfo) findAvailableStruct(pkg string, n string, deep int) (int, bool) { a := strings.Split(n, ".") if n == "" || IsNativeType(n) { @@ -446,11 +463,11 @@ func (i *TSInfo) TestEndpoints() { for _, v1 := range i.Packages[p].endpoints { _, f := i.findAvailableStruct(p, v1.Request, 0) if !f { - fmt.Printf("??Endpoint: request %s not found\n", v1.Request) + fmt.Printf("\n??Endpoint: request %s not found\n", v1.Request) } _, f = i.findAvailableStruct(p, v1.Response, 0) if !f { - fmt.Printf("??Endpoint: response %s not found\n", v1.Response) + fmt.Printf("\n??Endpoint: response %s not found\n", v1.Response) } } diff --git a/backend/pkg/ts-rpc/tsStruct.go b/backend/pkg/ts-rpc/tsStruct.go index 53a9125..d033f7d 100644 --- a/backend/pkg/ts-rpc/tsStruct.go +++ b/backend/pkg/ts-rpc/tsStruct.go @@ -6,23 +6,24 @@ package tsrpc import ( "fmt" "go/ast" + "slices" + "strings" ) type TSSField struct { - Name string - Type string - TsType string - Json TSTagJson - Ts TSTagTs - DependOn bool - SourceInfo string + Name string + Type string + TsType string + Json TSTagJson + Ts TSTagTs + DependOn bool } type TSStruct struct { Name string Typescript bool Fields []TSSField - SourceInfo string + Imports []string } func IsNativeType(t string) bool { @@ -131,37 +132,43 @@ func (s *TSStruct) getStruct(ts *ast.TypeSpec) { if len(field.Names) > 0 { tsType = getFieldTsInfo(field.Type) var f = TSSField{ - Name: field.Names[0].String(), - Json: tagJson, - Ts: tagTs, - Type: getFieldInfo(field.Type), - TsType: tsType, - DependOn: toBeImported(field.Type), - SourceInfo: "", + Name: field.Names[0].String(), + Json: tagJson, + Ts: tagTs, + Type: getFieldInfo(field.Type), + TsType: tsType, + DependOn: toBeImported(field.Type), + } + if len(strings.Split(f.Type, ".")) == 2 && tagTs.Type == "" { + a := strings.Split(f.Type, ".") + a[1] = strings.Trim(a[1], "[]*") + a[0] = strings.Trim(a[0], "[]*") + imp := fmt.Sprintf("%s.%s", a[0], a[1]) + if !slices.Contains(s.Imports, imp) { + s.Imports = append(s.Imports, imp) + } } s.Fields = append(s.Fields, f) } else { if se, ok := field.Type.(*ast.SelectorExpr); ok { var f = TSSField{ - Name: fmt.Sprintf("%s.%s", se.X, se.Sel), - Json: tagJson, - Ts: tagTs, - Type: getFieldInfo(field.Type), - TsType: tsType, - DependOn: toBeImported(field.Type), - SourceInfo: "", + Name: fmt.Sprintf("%s.%s", se.X, se.Sel), + Json: tagJson, + Ts: tagTs, + Type: getFieldInfo(field.Type), + TsType: tsType, + DependOn: toBeImported(field.Type), } s.Fields = append(s.Fields, f) } else { if se, ok := field.Type.(*ast.Ident); ok { var f = TSSField{ - Name: se.Name, - Json: tagJson, - Ts: tagTs, - Type: se.Name, - TsType: tsType, - DependOn: false, - SourceInfo: "", + Name: se.Name, + Json: tagJson, + Ts: tagTs, + Type: se.Name, + TsType: tsType, + DependOn: false, } s.Fields = append(s.Fields, f) } else { diff --git a/backend/pkg/ts-rpc/tsTsInfo.go b/backend/pkg/ts-rpc/tsTsInfo.go index e737468..fc20017 100644 --- a/backend/pkg/ts-rpc/tsTsInfo.go +++ b/backend/pkg/ts-rpc/tsTsInfo.go @@ -6,6 +6,7 @@ package tsrpc import ( "errors" "fmt" + "slices" "strings" ) @@ -59,6 +60,9 @@ func (ts *TSSouces) ensurePackage(p string) { if pkg.Endpoints == nil { pkg.Endpoints = make(map[string]string) } + if pkg.Imports == nil { + pkg.Imports = make(map[string][]string) + } ts.Pakages[p] = pkg } @@ -147,7 +151,7 @@ func structToTs(info TSInfo, p string, k string) (string, []string, error) { result += "}\n" } if fields == 0 { - return result, dependencies, fmt.Errorf("struct %s not export fields AT: %s", k, info.Packages[p].structs[k].SourceInfo) + return result, dependencies, fmt.Errorf("struct %s not export fields", k) } return result, dependencies, nil } @@ -193,7 +197,7 @@ func (ts *TSSouces) AddDependencies(info TSInfo, p string, s string, dependencie if info.findStruct(pk, st) { if emptySrtuct(info, pk, st) { - ts.Errors = append(ts.Errors, fmt.Sprintf("Empty struct %s.%s AT: %s", pk, st, info.Packages[p].structs[s].SourceInfo)) + ts.Errors = append(ts.Errors, fmt.Sprintf("Empty struct %s.%s ", pk, st)) } s, d, err := structToTs(info, pk, st) if err != nil { @@ -209,14 +213,14 @@ func (ts *TSSouces) AddDependencies(info TSInfo, p string, s string, dependencie ts.ensurePackage(pk) ts.Pakages[pk].Types[st] = s } else { - ts.Errors = append(ts.Errors, fmt.Sprintf("Dipendence not found %s.%s AT: %s", pk, st, info.Packages[p].structs[s].SourceInfo)) + ts.Errors = append(ts.Errors, fmt.Sprintf("Dipendence not found %s.%s", pk, st)) } } } } -func (ts *TSSouces) Populate(info TSInfo) { +func (ts *TSSouces) BuildTSSources(info TSInfo) { ts.Pakages = make(map[string]TSModule) ts.Errors = []string{} for p := range info.Packages { @@ -227,7 +231,7 @@ func (ts *TSSouces) Populate(info TSInfo) { for _, st := range info.Packages[p].structs { if st.Typescript { if len(st.Fields) == 0 { - ts.Errors = append(ts.Errors, fmt.Sprintf("Empty struct %s.%s AT: %s", p, st.Name, info.Packages[p].structs[st.Name].SourceInfo)) + ts.Errors = append(ts.Errors, fmt.Sprintf("Empty struct %s.%s ", p, st.Name)) } s, dependencies, err := structToTs(info, p, st.Name) if err != nil { @@ -253,9 +257,9 @@ func (ts *TSSouces) Populate(info TSInfo) { } for _, t := range info.Packages[p].types { - if t.Typescript { - ts.Pakages[p].Types[t.Name] = fmt.Sprintf("export type %s = %s\n", t.Name, t.TsType) - } + + ts.Pakages[p].Types[t.Name] = fmt.Sprintf("export type %s = %s\n", t.Name, t.TsType) + } for _, e := range info.Packages[p].endpoints { @@ -280,5 +284,16 @@ func (ts *TSSouces) Populate(info TSInfo) { pkg.Endpoints[e.Name] = e.ToTs() ts.Pakages[p] = pkg } + + for pk, t := range info.Packages[p].imports { + pkg := ts.Pakages[p] + if _, ok := pkg.Imports[pk]; !ok { + for _, v := range t { + if !slices.Contains(pkg.Imports[pk], v) { + pkg.Imports[pk] = append(pkg.Imports[pk], v) + } + } + } + } } } diff --git a/backend/pkg/ts-rpc/tsrpc.go b/backend/pkg/ts-rpc/tsrpc.go index b1823b7..41fe87c 100644 --- a/backend/pkg/ts-rpc/tsrpc.go +++ b/backend/pkg/ts-rpc/tsrpc.go @@ -16,6 +16,12 @@ var TSReport = "" var tsFiles = TSFiles{} +type ExcudedPackages []string + +func (ip *TSInfoPakage) MakeMaps() { + *ip = TSInfoPakage{structs: make(map[string]TSStruct), types: make(map[string]TSType), enums: make(map[string]TSEnum), consts: make(map[string]TSConst), decs: make(map[string]TSDec), endpoints: make(map[string]TSEndpoint), imports: make(map[string][]string), isUsed: false} +} + func GetTSSource() error { path := "" if value, exists := os.LookupEnv("TS_GENERATOR_PATH"); exists { @@ -26,9 +32,9 @@ func GetTSSource() error { var tsInfoData = TSInfo{} var tsSourcesData = TSSouces{} - tsInfoData.Populate(path) + tsInfoData.Populate(path, ExcudedPackages{"Typescript", "tsrpc"}) tsInfoData.TestEndpoints() - tsSourcesData.Populate(tsInfoData) + tsSourcesData.BuildTSSources(tsInfoData) if len(tsSourcesData.Errors) != 0 { err := "" @@ -119,6 +125,7 @@ func GetTSSource() error { for f := range tsSourcesData.Pakages[p].Imports { imports += "import type * as " + f + " from './" + f + ".ts'\n" } + tmp += imports tmp += source tsFiles.Add(p+".ts", tmp) @@ -128,7 +135,6 @@ func GetTSSource() error { err = tsFiles.Save() if err != nil { fmt.Printf("save ts files: %s\n", err) - } return err } diff --git a/frontend/src/api/admin.ts b/frontend/src/api/admin.ts index c54d444..09f9cc5 100644 --- a/frontend/src/api/admin.ts +++ b/frontend/src/api/admin.ts @@ -2,18 +2,6 @@ import { api } from "./api"; import type { Nullable } from "./apiTypes.ts"; import type * as users from "./users.ts"; -// Typescript: TSEndpoint= path=/api/admin/users; name=listUsers; method=POST; request=admin.ListUsersRequest; response=admin.ListUsersResponse - -// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/admin/routes.go Line: 11 -export const listUsers = async ( - data: ListUsersRequest, -): Promise<{ data: ListUsersResponse; error: Nullable }> => { - return (await api.POST("/api/admin/users", data)) as { - data: ListUsersResponse; - error: Nullable; - }; -}; - // Typescript: TSEndpoint= path=/api/admin/users/block; name=blockUser; method=PUT; request=admin.BlockUserRequest; response=users.User // /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/admin/routes.go Line: 14 @@ -26,6 +14,23 @@ export const blockUser = async ( }; }; +// Typescript: TSEndpoint= path=/api/admin/users; name=listUsers; method=POST; request=admin.ListUsersRequest; response=admin.ListUsersResponse + +// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/admin/routes.go Line: 11 +export const listUsers = async ( + data: ListUsersRequest, +): Promise<{ data: ListUsersResponse; error: Nullable }> => { + return (await api.POST("/api/admin/users", data)) as { + data: ListUsersResponse; + error: Nullable; + }; +}; + +export interface ListUsersRequest { + page: number; + pageSize: number; +} + export interface ListUsersResponse { items: users.User[]; page: number; @@ -36,8 +41,3 @@ export interface BlockUserRequest { uuid: string; action: string; } - -export interface ListUsersRequest { - page: number; - pageSize: number; -} diff --git a/frontend/src/api/api.ts b/frontend/src/api/api.ts index 88adfc8..ba8907a 100644 --- a/frontend/src/api/api.ts +++ b/frontend/src/api/api.ts @@ -4,7 +4,7 @@ // // This file was generated by github.com/millevolte/ts-rpc // -// Apr 27, 2026 22:35:43 UTC +// May 04, 2026 16:09:10 UTC // export interface ApiRestResponse { diff --git a/frontend/src/api/apiTypes.ts b/frontend/src/api/apiTypes.ts index 4a30202..e6d9a4b 100644 --- a/frontend/src/api/apiTypes.ts +++ b/frontend/src/api/apiTypes.ts @@ -1,4 +1,4 @@ // API Declarations -export type Record = { [P in K]: T }; - export type Nullable = T | null; + +export type Record = { [P in K]: T }; diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts index 03c7cc1..4b8e49e 100644 --- a/frontend/src/api/auth.ts +++ b/frontend/src/api/auth.ts @@ -1,5 +1,5 @@ import { api } from "./api"; -import type { Nullable, Record } from "./apiTypes.ts"; +import type { Record, Nullable } from "./apiTypes.ts"; // Typescript: TSEndpoint= path=/api/roles; name=getRoles; method=GET; response=auth.AllRoles @@ -13,9 +13,18 @@ export const getRoles = async (): Promise<{ error: Nullable; }; }; - -export interface AllRoles { - roles: Record; -} - export type Permission = string; + +export type AllRoles = Record; + +export type EnumPermission = + (typeof EnumEnumPermission)[keyof typeof EnumEnumPermission]; + +export const EnumEnumPermission = { + RoleSuperAdmin: "superadmin", + RoleAdmin: "admin", + RoleManager: "manager", + RoleContentCreator: "content_creator", + RoleUser: "user", + RoleGuest: "guest", +} as const; diff --git a/frontend/src/api/systemUtils.ts b/frontend/src/api/systemUtils.ts index 3a86047..6c3bda3 100644 --- a/frontend/src/api/systemUtils.ts +++ b/frontend/src/api/systemUtils.ts @@ -1,19 +1,6 @@ import { api } from "./api"; import type { Nullable } from "./apiTypes.ts"; -// Typescript: TSEndpoint= path=/health; name=health; method=GET; response=string - -// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/systemUtils/routes.go Line: 36 -export const health = async (): Promise<{ - data: string; - error: Nullable; -}> => { - return (await api.GET("/health")) as { - data: string; - error: Nullable; - }; -}; - // Typescript: TSEndpoint= path=/metrics; name=metrics; method=GET; response=string // /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/systemUtils/routes.go Line: 39 @@ -40,6 +27,19 @@ export const mailDebug = async (): Promise<{ }; }; +// Typescript: TSEndpoint= path=/health; name=health; method=GET; response=string + +// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/systemUtils/routes.go Line: 36 +export const health = async (): Promise<{ + data: string; + error: Nullable; +}> => { + return (await api.GET("/health")) as { + data: string; + error: Nullable; + }; +}; + export interface MailDebugItem { name: string; content: string; diff --git a/frontend/src/api/users.ts b/frontend/src/api/users.ts index 22583f5..5307ff0 100644 --- a/frontend/src/api/users.ts +++ b/frontend/src/api/users.ts @@ -4,62 +4,13 @@ import type * as responses from "./responses.ts"; import type * as tokens from "./tokens.ts"; import type * as auth from "./auth.ts"; -// Typescript: TSEndpoint= path=/api/auth/me; name=me; method=GET; response=users.User +// Typescript: TSEndpoint= path=/api/users; name=createUser; method=POST; request=users.UserCreateRequest; response=users.User -// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 40 -export const me = async (): Promise<{ - data: User; - error: Nullable; -}> => { - return (await api.GET("/api/auth/me")) as { - data: User; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/api/auth/refresh; name=refresh; method=POST; request=users.RefreshRequest; response=tokens.TokenPair - -// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 46 -export const refresh = async ( - data: RefreshRequest, -): Promise<{ data: tokens.TokenPair; error: Nullable }> => { - return (await api.POST("/api/auth/refresh", data)) as { - data: tokens.TokenPair; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/api/auth/password/reset; name=resetPassword; method=POST; request=users.ResetPasswordRequest; response=responses.SimpleResponse - -// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 55 -export const resetPassword = async ( - data: ResetPasswordRequest, -): Promise<{ data: responses.SimpleResponse; error: Nullable }> => { - return (await api.POST("/api/auth/password/reset", data)) as { - data: responses.SimpleResponse; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/api/users/:uuid; name=getUser; method=GET; response=users.User - -// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 28 -export const getUser = async ( - uuid: string, +// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 31 +export const createUser = async ( + data: UserCreateRequest, ): Promise<{ data: User; error: Nullable }> => { - return (await api.GET(`/api/users/${uuid}`)) as { - data: User; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/api/users/update; name=updateUser; method=PUT; request=users.UpdateUserRequest; response=users.User - -// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 34 -export const updateUser = async ( - data: UpdateUserRequest, -): Promise<{ data: User; error: Nullable }> => { - return (await api.PUT("/api/users/update", data)) as { + return (await api.POST("/api/users", data)) as { data: User; error: Nullable; }; @@ -77,6 +28,18 @@ export const updatePassword = async ( }; }; +// Typescript: TSEndpoint= path=/api/users/:uuid; name=getUser; method=GET; response=users.User + +// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 28 +export const getUser = async ( + uuid: string, +): Promise<{ data: User; error: Nullable }> => { + return (await api.GET(`/api/users/${uuid}`)) as { + data: User; + error: Nullable; + }; +}; + // Typescript: TSEndpoint= path=/api/users/:uuid; name=deleteUser; method=DELETE; response=responses.SimpleResponse // /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 37 @@ -89,30 +52,6 @@ export const deleteUser = async ( }; }; -// Typescript: TSEndpoint= path=/api/auth/password/forgot; name=forgotPassword; method=POST; request=users.ForgotPasswordRequest; response=responses.SimpleResponse - -// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 52 -export const forgotPassword = async ( - data: ForgotPasswordRequest, -): Promise<{ data: responses.SimpleResponse; error: Nullable }> => { - return (await api.POST("/api/auth/password/forgot", data)) as { - data: responses.SimpleResponse; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/api/users; name=createUser; method=POST; request=users.UserCreateRequest; response=users.User - -// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 31 -export const createUser = async ( - data: UserCreateRequest, -): Promise<{ data: User; error: Nullable }> => { - return (await api.POST("/api/users", data)) as { - data: User; - error: Nullable; - }; -}; - // Typescript: TSEndpoint= path=/api/auth/login; name=login; method=POST; request=users.LoginRequest; response=tokens.TokenPair // /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 43 @@ -125,6 +64,30 @@ export const login = async ( }; }; +// Typescript: TSEndpoint= path=/api/auth/password/reset; name=resetPassword; method=POST; request=users.ResetPasswordRequest; response=responses.SimpleResponse + +// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 55 +export const resetPassword = async ( + data: ResetPasswordRequest, +): Promise<{ data: responses.SimpleResponse; error: Nullable }> => { + return (await api.POST("/api/auth/password/reset", data)) as { + data: responses.SimpleResponse; + error: Nullable; + }; +}; + +// Typescript: TSEndpoint= path=/api/users/update; name=updateUser; method=PUT; request=users.UpdateUserRequest; response=users.User + +// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 34 +export const updateUser = async ( + data: UpdateUserRequest, +): Promise<{ data: User; error: Nullable }> => { + return (await api.PUT("/api/users/update", data)) as { + data: User; + error: Nullable; + }; +}; + // Typescript: TSEndpoint= path=/api/auth/register; name=register; method=POST; request=users.UserCreateRequest; response=users.User // /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 49 @@ -149,12 +112,45 @@ export const validToken = async ( }; }; -export interface ForgotPasswordRequest { - email: string; -} +// Typescript: TSEndpoint= path=/api/auth/me; name=me; method=GET; response=users.User -export interface LoginRequest { - username: string; +// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 40 +export const me = async (): Promise<{ + data: User; + error: Nullable; +}> => { + return (await api.GET("/api/auth/me")) as { + data: User; + error: Nullable; + }; +}; + +// Typescript: TSEndpoint= path=/api/auth/refresh; name=refresh; method=POST; request=users.RefreshRequest; response=tokens.TokenPair + +// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 46 +export const refresh = async ( + data: RefreshRequest, +): Promise<{ data: tokens.TokenPair; error: Nullable }> => { + return (await api.POST("/api/auth/refresh", data)) as { + data: tokens.TokenPair; + error: Nullable; + }; +}; + +// Typescript: TSEndpoint= path=/api/auth/password/forgot; name=forgotPassword; method=POST; request=users.ForgotPasswordRequest; response=responses.SimpleResponse + +// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 52 +export const forgotPassword = async ( + data: ForgotPasswordRequest, +): Promise<{ data: responses.SimpleResponse; error: Nullable }> => { + return (await api.POST("/api/auth/password/forgot", data)) as { + data: responses.SimpleResponse; + error: Nullable; + }; +}; + +export interface ResetPasswordRequest { + token: string; password: string; } @@ -171,6 +167,10 @@ export interface UpdateUserRequest { preferences: Nullable; } +export interface UpdatePasswordRequest { + password: string; +} + export interface UserCreateRequest { name: string; email: string; @@ -183,30 +183,6 @@ export interface UserCreateRequest { preferences: Nullable; } -export interface UserDetails { - id: number; - userId: number; - title: string; - firstName: string; - lastName: string; - address: string; - city: string; - zipCode: string; - country: string; - phone: string; - createdAt: Nullable; - updatedAt: Nullable; -} - -export interface RefreshRequest { - refresh_token: string; -} - -export interface ResetPasswordRequest { - token: string; - password: string; -} - export interface User { id: number; email: string; @@ -223,8 +199,19 @@ export interface User { updatedAt: Nullable; } -export interface UpdatePasswordRequest { - password: string; +export interface UserDetails { + id: number; + userId: number; + title: string; + firstName: string; + lastName: string; + address: string; + city: string; + zipCode: string; + country: string; + phone: string; + createdAt: Nullable; + updatedAt: Nullable; } export interface UserPreferences { @@ -242,6 +229,25 @@ export interface UserPreferences { updatedAt: Nullable; } +export interface ForgotPasswordRequest { + email: string; +} + +export interface LoginRequest { + username: string; + password: string; +} + +export interface RefreshRequest { + refresh_token: string; +} + export type UserStatus = string; export type UserTypes = string[]; + +export const EnumUserStatus = { + UserStatusPending: "pending", + UserStatusActive: "active", + UserStatusDisabled: "disabled", +} as const;