From b260daffed806b89548309f858fe20d80b3670c1 Mon Sep 17 00:00:00 2001 From: fabio Date: Sun, 26 Apr 2026 14:31:25 +0200 Subject: [PATCH] Refactor TypeScript RPC codebase and API structure - Simplified error handling in `getFieldInfo` and `getFieldTsInfo` functions. - Removed unused `getSourceInfo` function and its references. - Updated `getStruct` method in `TSStruct` to eliminate source info retrieval. - Cleaned up `TSSouces` population logic by commenting out unnecessary debug statements. - Adjusted TypeScript source generation to use type imports instead of default imports. - Consolidated API endpoints into dedicated files for better organization (admin, systemUtils, users). - Introduced new types for API responses and requests to enhance type safety. - Removed redundant code and comments from generated API files. - Updated frontend components to reflect new API structure and types. - Ensured consistent naming conventions and type usage across the codebase. --- backend/GeneratedCode/admin.ts | 20 +- backend/GeneratedCode/api.ts | 2 +- backend/GeneratedCode/apiTypes.ts | 4 +- backend/GeneratedCode/systemUtils.ts | 35 +- backend/GeneratedCode/users.ts | 221 ++++++----- backend/go.mod | 12 +- backend/go.sum | 14 + backend/internal/admin/controller.go | 3 +- backend/internal/seed/seed.go | 4 +- backend/internal/user/controller.go | 27 +- backend/internal/user/model.go | 66 ++-- backend/internal/user/routes.go | 4 +- backend/pkg/ts-rpc/TSFiles.go | 4 - backend/pkg/ts-rpc/tsEndpoint.go | 9 +- backend/pkg/ts-rpc/tsInfo.go | 360 +++++++++--------- backend/pkg/ts-rpc/tsStruct.go | 27 +- backend/pkg/ts-rpc/tsTsInfo.go | 31 +- backend/pkg/ts-rpc/tsrpc.go | 10 +- frontend/src/api/admin.ts | 34 ++ frontend/src/api/api.ts | 341 +---------------- frontend/src/api/apiTypes.ts | 4 + frontend/src/api/responses.ts | 3 + frontend/src/api/systemUtils.ts | 43 +++ frontend/src/api/tokens.ts | 4 + frontend/src/api/users.ts | 239 ++++++++++++ frontend/src/layouts/MainLayout.vue | 4 +- frontend/src/pages/admin/UsersPage.vue | 41 +- frontend/src/pages/dev/ApiEndpointsPage.vue | 18 +- frontend/src/pages/dev/LoginPage.vue | 2 +- frontend/src/pages/dev/MailDebugPage.vue | 2 +- .../src/pages/dev/RecoverPasswordPage.vue | 2 +- frontend/src/pages/dev/ResetPasswordPage.vue | 2 +- frontend/src/pages/dev/SignupPage.vue | 8 +- 33 files changed, 777 insertions(+), 823 deletions(-) create mode 100644 frontend/src/api/admin.ts create mode 100644 frontend/src/api/apiTypes.ts create mode 100644 frontend/src/api/responses.ts create mode 100644 frontend/src/api/systemUtils.ts create mode 100644 frontend/src/api/tokens.ts create mode 100644 frontend/src/api/users.ts diff --git a/backend/GeneratedCode/admin.ts b/backend/GeneratedCode/admin.ts index df6922d..8653af2 100644 --- a/backend/GeneratedCode/admin.ts +++ b/backend/GeneratedCode/admin.ts @@ -1,9 +1,10 @@ -import { api } from "./api.ts"; -import { Nullable } from "./apiTypes.ts"; -import * as users from "./users.ts"; +import { api } from "./api"; +import type { Nullable } from "./apiTypes.ts"; +import type * as users from "./users.ts"; // Typescript: TSEndpoint= path=/admin/users; name=listUsers; method=POST; request=admin.ListUsersRequest; response=users.[]User -// internal/admin/routes.go Line: 12 + +// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/admin/routes.go Line: 13 export const listUsers = async ( data: ListUsersRequest, ): Promise<{ data: users.User[]; error: Nullable }> => { @@ -14,7 +15,8 @@ export const listUsers = async ( }; // Typescript: TSEndpoint= path=/admin/users/:uuid/block; name=blockUser; method=PUT; request=admin.BlockUserRequest; response=users.User -// internal/admin/routes.go Line: 16 + +// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/admin/routes.go Line: 17 export const blockUser = async ( data: BlockUserRequest, ): Promise<{ data: users.User; error: Nullable }> => { @@ -24,11 +26,11 @@ export const blockUser = async ( }; }; -export interface BlockUserRequest { - action: string; -} - export interface ListUsersRequest { page: number; pageSize: number; } + +export interface BlockUserRequest { + action: string; +} diff --git a/backend/GeneratedCode/api.ts b/backend/GeneratedCode/api.ts index 2c36a4a..7eb30ae 100644 --- a/backend/GeneratedCode/api.ts +++ b/backend/GeneratedCode/api.ts @@ -4,7 +4,7 @@ // // This file was generated by github.com/millevolte/ts-rpc // -// Apr 14, 2026 21:39:07 UTC +// Apr 26, 2026 14:23:26 UTC // export interface ApiRestResponse { diff --git a/backend/GeneratedCode/apiTypes.ts b/backend/GeneratedCode/apiTypes.ts index e6d9a4b..4a30202 100644 --- a/backend/GeneratedCode/apiTypes.ts +++ b/backend/GeneratedCode/apiTypes.ts @@ -1,4 +1,4 @@ // API Declarations -export type Nullable = T | null; - export type Record = { [P in K]: T }; + +export type Nullable = T | null; diff --git a/backend/GeneratedCode/systemUtils.ts b/backend/GeneratedCode/systemUtils.ts index b398dd6..1be01e6 100644 --- a/backend/GeneratedCode/systemUtils.ts +++ b/backend/GeneratedCode/systemUtils.ts @@ -1,8 +1,22 @@ -import { api } from "./api.ts"; -import { Nullable } from "./apiTypes.ts"; +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: 39 +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 -// internal/systemUtils/routes.go Line: 41 + +// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/systemUtils/routes.go Line: 42 export const metrics = async (): Promise<{ data: string; error: Nullable; @@ -14,7 +28,8 @@ export const metrics = async (): Promise<{ }; // Typescript: TSEndpoint= path=/maildebug; name=mailDebug; method=GET; response=[]MailDebugItem -// internal/systemUtils/routes.go Line: 53 + +// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/systemUtils/routes.go Line: 54 export const mailDebug = async (): Promise<{ data: MailDebugItem[]; error: Nullable; @@ -25,18 +40,6 @@ export const mailDebug = async (): Promise<{ }; }; -// Typescript: TSEndpoint= path=/health; name=health; method=GET; response=string -// internal/systemUtils/routes.go Line: 38 -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/backend/GeneratedCode/users.ts b/backend/GeneratedCode/users.ts index 9faa0cb..af1e7d8 100644 --- a/backend/GeneratedCode/users.ts +++ b/backend/GeneratedCode/users.ts @@ -1,10 +1,11 @@ -import { api } from "./api.ts"; -import { Nullable } from "./apiTypes.ts"; -import * as responses from "./responses.ts"; -import * as tokens from "./tokens.ts"; +import { api } from "./api"; +import type { Nullable } from "./apiTypes.ts"; +import type * as responses from "./responses.ts"; +import type * as tokens from "./tokens.ts"; // Typescript: TSEndpoint= path=/auth/refresh; name=refresh; method=POST; request=users.RefreshRequest; response=tokens.TokenPair -// internal/user/routes.go Line: 46 + +// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 47 export const refresh = async ( data: RefreshRequest, ): Promise<{ data: tokens.TokenPair; error: Nullable }> => { @@ -14,61 +15,9 @@ export const refresh = async ( }; }; -// Typescript: TSEndpoint= path=/auth/password/reset; name=resetPassword; method=POST; request=users.ResetPasswordRequest; response=responses.SimpleResponse -// internal/user/routes.go Line: 55 -export const resetPassword = async ( - data: ResetPasswordRequest, -): Promise<{ data: responses.SimpleResponse; error: Nullable }> => { - return (await api.POST("/auth/password/reset", data)) as { - data: responses.SimpleResponse; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/users/:uuid; name=getUser; method=GET; response=users.User -// internal/user/routes.go Line: 27 -export const getUser = async ( - uuid: string, -): Promise<{ data: User; error: Nullable }> => { - return (await api.GET(`/users/${uuid}`)) as { - data: User; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/auth/me; name=me; method=GET; response=users.User -// internal/user/routes.go Line: 39 -export const me = async (): Promise<{ - data: User; - error: Nullable; -}> => { - return (await api.GET("/auth/me")) as { data: User; error: Nullable }; -}; - -// Typescript: TSEndpoint= path=/auth/register; name=register; method=POST; request=users.UserCreateInput; response=users.User -// internal/user/routes.go Line: 49 -export const register = async ( - data: UserCreateInput, -): Promise<{ data: User; error: Nullable }> => { - return (await api.POST("/auth/register", data)) as { - data: User; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/auth/password/forgot; name=forgotPassword; method=POST; request=users.ForgotPasswordRequest; response=responses.SimpleResponse -// internal/user/routes.go Line: 52 -export const forgotPassword = async ( - data: ForgotPasswordRequest, -): Promise<{ data: responses.SimpleResponse; error: Nullable }> => { - return (await api.POST("/auth/password/forgot", data)) as { - data: responses.SimpleResponse; - error: Nullable; - }; -}; - // Typescript: TSEndpoint= path=/users/:uuid; name=updateUser; method=PUT; request=users.UpdateUserRequest; response=users.User -// internal/user/routes.go Line: 33 + +// /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 }> => { @@ -78,30 +27,43 @@ export const updateUser = async ( }; }; -// Typescript: TSEndpoint= path=/users/:uuid; name=deleteUser; method=DELETE; response=responses.SimpleResponse -// internal/user/routes.go Line: 36 -export const deleteUser = async ( - uuid: string, +// Typescript: TSEndpoint= path=/auth/me; name=me; method=GET; 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("/auth/me")) as { data: User; error: Nullable }; +}; + +// Typescript: TSEndpoint= path=/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: 53 +export const forgotPassword = async ( + data: ForgotPasswordRequest, ): Promise<{ data: responses.SimpleResponse; error: Nullable }> => { - return (await api.DELETE(`/users/${uuid}`)) as { + return (await api.POST("/auth/password/forgot", data)) as { data: responses.SimpleResponse; error: Nullable; }; }; -// Typescript: TSEndpoint= path=/auth/login; name=login; method=POST; request=users.LoginRequest; response=tokens.TokenPair -// internal/user/routes.go Line: 43 -export const login = async ( - data: LoginRequest, -): Promise<{ data: tokens.TokenPair; error: Nullable }> => { - return (await api.POST("/auth/login", data)) as { - data: tokens.TokenPair; +// Typescript: TSEndpoint= path=/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: 56 +export const resetPassword = async ( + data: ResetPasswordRequest, +): Promise<{ data: responses.SimpleResponse; error: Nullable }> => { + return (await api.POST("/auth/password/reset", data)) as { + data: responses.SimpleResponse; error: Nullable; }; }; // Typescript: TSEndpoint= path=/auth/password/valid; name=validToken; method=POST; request=string; response=responses.SimpleResponse -// internal/user/routes.go Line: 58 + +// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 59 export const validToken = async ( data: string, ): Promise<{ data: responses.SimpleResponse; error: Nullable }> => { @@ -111,10 +73,47 @@ export const validToken = async ( }; }; -// Typescript: TSEndpoint= path=/users; name=createUser; method=POST; request=users.UserCreateInput; response=users.User -// internal/user/routes.go Line: 30 +// Typescript: TSEndpoint= path=/users/:uuid; name=deleteUser; method=DELETE; response=responses.SimpleResponse + +// /Users/fabio/CODE/omnimed/go-quasar-partial-ssr/backend/internal/user/routes.go Line: 37 +export const deleteUser = async ( + uuid: string, +): Promise<{ data: responses.SimpleResponse; error: Nullable }> => { + return (await api.DELETE(`/users/${uuid}`)) as { + data: responses.SimpleResponse; + error: Nullable; + }; +}; + +// Typescript: TSEndpoint= path=/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(`/users/${uuid}`)) as { + data: User; + error: Nullable; + }; +}; + +// Typescript: TSEndpoint= path=/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: 50 +export const register = async ( + data: UserCreateRequest, +): Promise<{ data: User; error: Nullable }> => { + return (await api.POST("/auth/register", data)) as { + data: User; + error: Nullable; + }; +}; + +// Typescript: TSEndpoint= path=/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: UserCreateInput, + data: UserCreateRequest, ): Promise<{ data: User; error: Nullable }> => { return (await api.POST("/users", data)) as { data: User; @@ -122,8 +121,28 @@ export const createUser = async ( }; }; -export interface RefreshRequest { - refresh_token: string; +// Typescript: TSEndpoint= path=/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: 44 +export const login = async ( + data: LoginRequest, +): Promise<{ data: tokens.TokenPair; error: Nullable }> => { + return (await api.POST("/auth/login", data)) as { + data: tokens.TokenPair; + error: Nullable; + }; +}; + +export interface UpdateUserRequest { + name: string; + email: string; + password: string; + roles: UserRoles; + status: UserStatus; + types: UserTypes; + avatar: Nullable; + details: Nullable; + preferences: Nullable; } export interface User { @@ -142,16 +161,13 @@ export interface User { updatedAt: Date; } -export interface UserCreateInput { - name: string; +export interface ForgotPasswordRequest { email: string; +} + +export interface ResetPasswordRequest { + token: string; password: string; - roles: UserRoles; - status: UserStatus; - types: UserTypes; - avatar: Nullable; - details: Nullable; - preferences: Nullable; } export interface UserPreferences { @@ -169,11 +185,7 @@ export interface UserPreferences { updatedAt: Date; } -export interface ForgotPasswordRequest { - email: string; -} - -export interface UpdateUserRequest { +export interface UserCreateRequest { name: string; email: string; password: string; @@ -201,37 +213,20 @@ export interface UserDetails { zipCode: string; country: string; phone: string; - createdAt: Date; - updatedAt: Date; + createdAt: Nullable; + updatedAt: Nullable; } -export interface UserProfile { - id: number; - email: string; - name: string; - roles: UserRoles; - types: UserTypes; - status: UserStatus; - activatedAt: Date; - uuid: string; - details: Nullable; - preferences: Nullable; - avatar: Nullable; - createdAt: Date; - updatedAt: Date; -} - -export interface ResetPasswordRequest { - token: string; - password: string; +export interface RefreshRequest { + refresh_token: string; } export type UserRoles = string[]; -export type UserTypes = string[]; - export type UserStatus = (typeof EnumUserStatus)[keyof typeof EnumUserStatus]; +export type UserTypes = string[]; + export const EnumUserStatus = { UserStatusPending: "pending", UserStatusActive: "active", diff --git a/backend/go.mod b/backend/go.mod index e5e960f..434b135 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -9,7 +9,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/uuid v1.6.0 github.com/prometheus/client_golang v1.23.2 - golang.org/x/crypto v0.48.0 + golang.org/x/crypto v0.50.0 gorm.io/driver/postgres v1.6.0 gorm.io/driver/sqlite v1.6.0 gorm.io/gorm v1.31.1 @@ -45,9 +45,11 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.69.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/net v0.50.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/text v0.34.0 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.53.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.0 // indirect + golang.org/x/tools v0.44.0 // indirect google.golang.org/protobuf v1.36.8 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index 9e0d0a0..a8091f4 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -101,15 +101,29 @@ go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/backend/internal/admin/controller.go b/backend/internal/admin/controller.go index 9608e3b..e4e4aa8 100644 --- a/backend/internal/admin/controller.go +++ b/backend/internal/admin/controller.go @@ -106,7 +106,8 @@ func (ac *AdminController) BlockUser(c fiber.Ctx) error { default: return fiber.NewError(fiber.StatusBadRequest, "invalid action") } - u.UpdatedAt = time.Now().UTC() + now := time.Now().UTC() + u.UpdatedAt = &now if err := db.Save(&u).Error; err != nil { return fiber.NewError(fiber.StatusInternalServerError, "failed to update user status") diff --git a/backend/internal/seed/seed.go b/backend/internal/seed/seed.go index 56d1c69..3ce8d52 100644 --- a/backend/internal/seed/seed.go +++ b/backend/internal/seed/seed.go @@ -73,8 +73,8 @@ func SeedUsers(db *gorm.DB, n int) ([]users.User, []Credential, error) { Language: gofakeit.Language(), }, Avatar: nil, - CreatedAt: now, - UpdatedAt: now, + CreatedAt: &now, + UpdatedAt: &now, ActivatedAt: func() *time.Time { ts := now return &ts diff --git a/backend/internal/user/controller.go b/backend/internal/user/controller.go index 2481fc2..ca77c97 100644 --- a/backend/internal/user/controller.go +++ b/backend/internal/user/controller.go @@ -31,19 +31,6 @@ func NewUserController(tockenService *tokens.TockenService) *UserController { } } -// Typescript: interface -type UpdateUserRequest struct { - Name string `json:"name" validate:"required,min=1,max=255"` - Email string `json:"email" validate:"required,email"` - Password string `json:"password" validate:"omitempty,min=8,max=128"` - Roles UserRoles `json:"roles"` - Status UserStatus `json:"status"` - Types UserTypes `json:"types"` - Avatar *string `json:"avatar"` - Details *UserDetails `json:"details"` - Preferences *UserPreferences `json:"preferences"` -} - // GetUser returns a single user by UUID. func (uc *UserController) GetUser(c fiber.Ctx) error { user, err := loadUserByUUID(c) @@ -55,7 +42,7 @@ func (uc *UserController) GetUser(c fiber.Ctx) error { // CreateUser creates a user together with optional details and preferences. func (uc *UserController) CreateUser(c fiber.Ctx) error { - var req UserCreateInput + var req UserCreateRequest if err := c.Bind().Body(&req); err != nil { return fiber.NewError(fiber.StatusBadRequest, "invalid payload") } @@ -107,8 +94,8 @@ func (uc *UserController) CreateUser(c fiber.Ctx) error { UUID: uuid.NewString(), Details: req.Details, Preferences: req.Preferences, - CreatedAt: now, - UpdatedAt: now, + CreatedAt: &now, + UpdatedAt: &now, } if err := db.Create(&user).Error; err != nil { @@ -155,7 +142,7 @@ func (uc *UserController) UpdateUser(c fiber.Ctx) error { user.Name = req.Name user.Email = req.Email user.Avatar = req.Avatar - user.UpdatedAt = now + user.UpdatedAt = &now if req.Status != "" { user.Status = req.Status } @@ -277,7 +264,7 @@ func (uc *UserController) Login(c fiber.Ctx) error { // Register creates a new user with optional roles/types/preferences. func (uc *UserController) Register(c fiber.Ctx) error { - var req UserCreateInput + var req UserCreateRequest if err := c.Bind().Body(&req); err != nil { return fiber.NewError(fiber.StatusBadRequest, "invalid payload") } @@ -342,8 +329,8 @@ func (uc *UserController) Register(c fiber.Ctx) error { Language: req.Preferences.Language, } }(), - CreatedAt: now, - UpdatedAt: now, + CreatedAt: &now, + UpdatedAt: &now, } if err := db.Create(&user).Error; err != nil { diff --git a/backend/internal/user/model.go b/backend/internal/user/model.go index 0ef9743..8bb196a 100644 --- a/backend/internal/user/model.go +++ b/backend/internal/user/model.go @@ -7,11 +7,8 @@ import ( ) // UserRoles is stored as JSON array of strings. - -// Typescript: type type UserRoles []string -// Typescript: interface type User struct { ID int `json:"id" gorm:"primaryKey"` Email string `json:"email" gorm:"uniqueIndex;size:255"` @@ -25,32 +22,15 @@ type User struct { Details *UserDetails `json:"details" gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"` Preferences *UserPreferences `json:"preferences" gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"` Avatar *string `json:"avatar" gorm:"size:512"` - CreatedAt time.Time `json:"createdAt" ts:"type=Date"` - UpdatedAt time.Time `json:"updatedAt" ts:"type=Date"` - DeletedAt gorm.DeletedAt `json:"-" gorm:"index" ts:"type=Date"` -} - -// UserCreateInput captures the minimal payload to create a user. - -// Typescript: interface -type UserCreateInput struct { - Name string `json:"name" validate:"required,min=1,max=255"` - Email string `json:"email" validate:"required,email"` - Password string `json:"password" validate:"required,min=8,max=128"` - Roles UserRoles `json:"roles"` - Status UserStatus `json:"status"` - Types UserTypes `json:"types"` - Avatar *string `json:"avatar"` - Details *UserDetails `json:"details" ` - Preferences *UserPreferences `json:"preferences" ` + CreatedAt *time.Time `json:"createdAt" ts:"type=Date"` + UpdatedAt *time.Time `json:"updatedAt" ts:"type=Date"` + DeletedAt *gorm.DeletedAt `json:"-" gorm:"index" ts:"type=Date"` } // UserTypes is stored as JSON array (e.g. ["internal","external"]). type UserTypes []string // UserProfile is the safe full representation of a user returned by CRUD endpoints. - -// Typescript: interface type UserProfile struct { ID int `json:"id"` Email string `json:"email"` @@ -63,8 +43,8 @@ type UserProfile struct { Details *UserDetails `json:"details"` Preferences *UserPreferences `json:"preferences"` Avatar *string `json:"avatar"` - CreatedAt time.Time `json:"createdAt" ts:"type=Date"` - UpdatedAt time.Time `json:"updatedAt" ts:"type=Date"` + CreatedAt *time.Time `json:"createdAt" ts:"type=Date"` + UpdatedAt *time.Time `json:"updatedAt" ts:"type=Date"` } // ToUserProfile maps a User to a full response without exposing the password hash. @@ -91,7 +71,6 @@ func ToUserProfile(u *User) UserProfile { // UserPreferences holds per-user settings stored as JSON. -// Typescript: interface type UserPreferences struct { ID int `json:"id" gorm:"primaryKey"` UserID int `json:"userId" gorm:"index"` @@ -109,7 +88,6 @@ type UserPreferences struct { // UserDetails holds optional profile data. -// Typescript: interface type UserDetails struct { ID int `json:"id" gorm:"primaryKey"` UserID int `json:"userId" gorm:"index"` @@ -121,8 +99,8 @@ type UserDetails struct { ZipCode string `json:"zipCode"` Country string `json:"country"` Phone string `json:"phone"` - CreatedAt time.Time `json:"createdAt" ts:"type=Date"` - UpdatedAt time.Time `json:"updatedAt" ts:"type=Date"` + CreatedAt time.Time `json:"createdAt" ts:"type=Nullable"` + UpdatedAt time.Time `json:"updatedAt" ts:"type=Nullable"` } // UserDetails holds optional profile data. @@ -164,24 +142,46 @@ const ( UserStatusDisabled UserStatus = "disabled" ) -// Typescript: interface type LoginRequest struct { Username string `json:"username" validate:"required,email"` Password string `json:"password" validate:"required,min=8,max=128"` } -// Typescript: interface type RefreshRequest struct { RefreshToken string `json:"refresh_token"` } -// Typescript: interface type ForgotPasswordRequest struct { Email string `json:"email" validate:"required,email"` } -// Typescript: interface type ResetPasswordRequest struct { Token string `json:"token" validate:"required,min=20,max=255"` Password string `json:"password" validate:"required,min=8,max=128"` } + +type UpdateUserRequest struct { + Name string `json:"name" validate:"required,min=1,max=255"` + Email string `json:"email" validate:"required,email"` + Password string `json:"password" validate:"omitempty,min=8,max=128"` + Roles UserRoles `json:"roles"` + Status UserStatus `json:"status"` + Types UserTypes `json:"types"` + Avatar *string `json:"avatar"` + Details *UserDetails `json:"details"` + Preferences *UserPreferences `json:"preferences"` +} + +// UserCreateRequest captures the minimal payload to create a user. + +type UserCreateRequest struct { + Name string `json:"name" validate:"required,min=1,max=255"` + Email string `json:"email" validate:"required,email"` + Password string `json:"password" validate:"required,min=8,max=128"` + Roles UserRoles `json:"roles"` + Status UserStatus `json:"status"` + Types UserTypes `json:"types"` + Avatar *string `json:"avatar"` + Details *UserDetails `json:"details" ` + Preferences *UserPreferences `json:"preferences" ` +} diff --git a/backend/internal/user/routes.go b/backend/internal/user/routes.go index b45f344..638599c 100644 --- a/backend/internal/user/routes.go +++ b/backend/internal/user/routes.go @@ -27,7 +27,7 @@ func RegisterUserRoutes(app *fiber.App) { // Typescript: TSEndpoint= path=/users/:uuid; name=getUser; method=GET; response=users.User app.Get("/users/:uuid", tockenService.Middleware(), userController.GetUser) - // Typescript: TSEndpoint= path=/users; name=createUser; method=POST; request=users.UserCreateInput; response=users.User + // Typescript: TSEndpoint= path=/users; name=createUser; method=POST; request=users.UserCreateRequest; response=users.User app.Post("/users", tockenService.Middleware(), userController.CreateUser) // Typescript: TSEndpoint= path=/users/:uuid; name=updateUser; method=PUT; request=users.UpdateUserRequest; response=users.User @@ -46,7 +46,7 @@ func RegisterUserRoutes(app *fiber.App) { // Typescript: TSEndpoint= path=/auth/refresh; name=refresh; method=POST; request=users.RefreshRequest; response=tokens.TokenPair app.Post("/auth/refresh", authRateLimiter, userController.Refresh) - // Typescript: TSEndpoint= path=/auth/register; name=register; method=POST; request=users.UserCreateInput; response=users.User + // Typescript: TSEndpoint= path=/auth/register; name=register; method=POST; request=users.UserCreateRequest; response=users.User app.Post("/auth/register", authRateLimiter, userController.Register) // Typescript: TSEndpoint= path=/auth/password/forgot; name=forgotPassword; method=POST; request=users.ForgotPasswordRequest; response=responses.SimpleResponse diff --git a/backend/pkg/ts-rpc/TSFiles.go b/backend/pkg/ts-rpc/TSFiles.go index 329de55..6ad7eab 100644 --- a/backend/pkg/ts-rpc/TSFiles.go +++ b/backend/pkg/ts-rpc/TSFiles.go @@ -33,10 +33,6 @@ func (t *TSFiles) Save() error { return fmt.Errorf("TS Files not initialized") } - for k, v := range *t { - fmt.Printf("file: %s\nsource:\n%s\n\n", k, v) - } - for name, source := range *t { formatted, err := formatJS(source) if err != nil { diff --git a/backend/pkg/ts-rpc/tsEndpoint.go b/backend/pkg/ts-rpc/tsEndpoint.go index 82c6f87..1a18ee4 100644 --- a/backend/pkg/ts-rpc/tsEndpoint.go +++ b/backend/pkg/ts-rpc/tsEndpoint.go @@ -55,7 +55,7 @@ func ParseEndpoint(source string, file string, line int) TSEndpoint { endpoint.Request = strings.Trim(t[1], " ") case "response": n++ - endpoint.Response = strings.Trim(t[1], " ") + endpoint.Response = strings.Trim(t[1], " \n") } } else { exitOnError(fmt.Errorf("wrong endpoint props: %s", s)) @@ -85,11 +85,6 @@ type tplData struct { func (e *TSEndpoint) VerifyTypes(info TSInfo, p string) { kind := "" - - if e.Name == "listUsers" { - fmt.Println("endpoint request", e.Request) - } - a := strings.Split(e.Request, ".") if e.Request != "" { if len(a) == 2 { @@ -159,7 +154,7 @@ func (e *TSEndpoint) VerifyTypes(info TSInfo, p string) { allreadyImported := slices.Contains(e.Imports[a[0]], strings.Trim(a[1], "[]*")) if !allreadyImported { e.Imports[a[0]] = append(e.Imports[a[0]], strings.Trim(a[1], "[]*")) - fmt.Printf("endpoint %s response import: %s.%s\n", e.Name, a[0], a[1]) + //fmt.Printf("endpoint %s response import: %s.%s\n", e.Name, a[0], a[1]) } } info.setTypescript(a[0], a[1], true) diff --git a/backend/pkg/ts-rpc/tsInfo.go b/backend/pkg/ts-rpc/tsInfo.go index 0a88ff3..a327ccc 100644 --- a/backend/pkg/ts-rpc/tsInfo.go +++ b/backend/pkg/ts-rpc/tsInfo.go @@ -4,15 +4,16 @@ package tsrpc import ( + "bufio" "fmt" "go/ast" "go/doc" - "go/parser" - "go/token" + "io" "log" "os" - "path/filepath" "strings" + + "golang.org/x/tools/go/packages" ) type TSInfoPakage struct { @@ -27,9 +28,8 @@ type TSInfoPakage struct { } type TSDec struct { - Name string - Value string - SourceInfo string + Name string + Value string } type TSType struct { @@ -38,7 +38,6 @@ type TSType struct { TsType string Typescript bool dependOn bool - SourceInfo string } type TSInfo struct { @@ -128,7 +127,7 @@ 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, src []TSSourceFile) { +func (i *TSInfo) getConst(p string, c *doc.Value) { var isTypescript = strings.HasPrefix(c.Doc, "Typescript:") if isTypescript { command := strings.TrimPrefix(c.Doc, "Typescript:") @@ -150,7 +149,7 @@ func (i *TSInfo) getConst(p string, c *doc.Value, src []TSSourceFile) { 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 AT: %s", x.Value, be.Op.String(), be.Y, getSourceInfo(int(x.ValuePos), src))) + 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 { @@ -177,11 +176,10 @@ func (i *TSInfo) getConst(p string, c *doc.Value, src []TSSourceFile) { i.Packages[p].enums[enumName] = enum t1 := TSType{ Name: enumName, - Typescript: true, + Typescript: false, Type: "", TsType: fmt.Sprintf("typeof Enum%s[keyof typeof Enum%s] ", enumName, enumName), //getFieldTsInfo(expr.Type), dependOn: false, - SourceInfo: "", } i.Packages[p].types[enumName] = t1 } @@ -204,32 +202,10 @@ func (i *TSInfo) getConst(p string, c *doc.Value, src []TSSourceFile) { } } -func (i *TSInfo) getType(p string, t *doc.Type, src []TSSourceFile) { - var isType = t.Decl.Tok == token.TYPE && t.Doc == "" - var isTypescript = strings.HasPrefix(t.Doc, "Typescript:") - command := "" - param := "" - if isTypescript { - //fmt.Println(t.Doc) - command = strings.TrimPrefix(t.Doc, "Typescript:") - command = strings.TrimSpace(command) - command = strings.Trim(command, "\n") - - if strings.Contains(command, "=") { - a := strings.Split(command, "=") - if len(a) == 2 { - param = a[1] - } - command = strings.Trim(a[0], " ") - } - } - if isType { - //fmt.Println(t.Doc) - command = "interface" - } +func (i *TSInfo) getType(p string, t *doc.Type) { for _, spec := range t.Decl.Specs { if len(t.Consts) > 0 { - i.getConst(p, t.Consts[0], src) + i.getConst(p, t.Consts[0]) continue } switch spec.(type) { @@ -237,32 +213,28 @@ func (i *TSInfo) getType(p string, t *doc.Type, src []TSSourceFile) { typeSpec := spec.(*ast.TypeSpec) switch typeSpec.Type.(type) { case *ast.StructType: - if (isTypescript || isType) && command != "interface" { - exitOnError(fmt.Errorf("mismatch delaration for interface %s AT: %s", t.Doc, getSourceInfo(int(typeSpec.Name.NamePos), src))) - } + // 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: isTypescript, + Typescript: false, Fields: []TSSField{}, - SourceInfo: getSourceInfo(int(typeSpec.Name.NamePos), src), + SourceInfo: "", } - v.getStruct(typeSpec, src) + v.getStruct(typeSpec) i.Packages[p].structs[typeSpec.Name.Name] = v default: - if isTypescript && command != "type" { - exitOnError(fmt.Errorf("mismatch delaration for type %s AT: %s", t.Doc, getSourceInfo(int(typeSpec.Name.NamePos), src))) - } + // 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) - if command == "type" && param != "" { - tsInfo = param - } t := TSType{ Name: typeSpec.Name.Name, - Typescript: isTypescript, + Typescript: false, Type: getFieldInfo(typeSpec.Type), TsType: tsInfo, dependOn: toBeImported(typeSpec.Type), - SourceInfo: getSourceInfo(int(typeSpec.Name.NamePos), src), } //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 @@ -271,160 +243,186 @@ func (i *TSInfo) getType(p string, t *doc.Type, src []TSSourceFile) { } } -func (i *TSInfo) Populate(path string) { - i.Packages = make(map[string]TSInfoPakage) - err := filepath.Walk(path, - func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - fset := token.NewFileSet() - packages, err := parser.ParseDir(fset, path, nil, parser.ParseComments) - - if err != nil { - exitOnError(err) - } - - for pkg, f := range packages { - 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)} - } - if pkg == "typescript" || pkg == "tsrpc" { - continue - } - - var src = []TSSourceFile{} - for n := range f.Files { - - dat, err := os.ReadFile(n) - if err == nil { - lines := []TSSourceLine{} - pos := 0 - line := 1 - for p, k := range dat { - if string(k) == "\n" { - l := TSSourceLine{ - Pos: pos, - End: p, - Line: line, - Source: string(dat[pos:p]), - } - lines = append(lines, l) - pos = p + 1 - line++ - if strings.Contains(l.Source, "// Typescript:") { - - if strings.Contains(l.Source, "TStype=") { - p := strings.Index(l.Source, "TStype=") - s := strings.Trim(l.Source[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, - SourceInfo: getSourceInfo(int(l.Pos), src), - } - i.Packages[pkg].types[strings.Trim(a[0], " ")] = t - } - - } - if strings.Contains(l.Source, "TSDeclaration=") { - p := strings.Index(l.Source, "TSDeclaration=") - s := strings.Trim(l.Source[p+len("TSDeclaration="):], " ") - a := strings.Split(s, "=") - if len(a) == 2 { - t := TSDec{ - Name: strings.Trim(a[0], " "), - Value: strings.Trim(a[1], " "), - SourceInfo: getSourceInfo(int(l.Pos), src), - } - i.Packages[pkg].decs[strings.Trim(a[0], " ")] = t - } - } - - if strings.Contains(l.Source, "TSEndpoint= ") { - e := ParseEndpoint(l.Source, n, l.Line) - if _, ok := i.Packages[pkg].endpoints[e.Name]; ok { - exitOnError(fmt.Errorf("enpoint name %s allready in use: %s", e.Name, l.Source)) - } - pkg_info := i.Packages[pkg] - pkg_info.endpoints[e.Name] = e - pkg_info.imports = e.Imports - pkg_info.isUsed = true - i.Packages[pkg] = pkg_info - } - - } - - } - } - s := TSSourceFile{ - Name: n, - Source: string(dat), - Len: len(dat), - Lines: lines, - } - src = append(src, s) - } - } - - p := doc.New(f, "./", 0) - - for _, t := range p.Types { - i.getType(pkg, t, src) - } - - for _, c := range p.Consts { - i.getConst(pkg, c, src) - } - } - } - return nil - }) +func parseTypescriptDeclarations(n string, pkg TSInfoPakage) { + f, err := os.OpenFile(n, os.O_RDONLY, os.ModePerm) if err != nil { - log.Fatal(err) + 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 (ts *TSInfo) findAvailableStruct(n string) (TSStruct, bool) { +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) { + return deep, true + } + + deep++ + if deep > 10 { + exitOnError(fmt.Errorf("too much deep for struct %s", n)) + } + + if len(a) == 1 { + n = strings.TrimPrefix(n, "[]") + n = strings.TrimPrefix(n, "*") + if _, ok := ts.Packages[pkg].structs[n]; ok { + s := ts.Packages[pkg].structs[n] + s.Typescript = true + ts.Packages[pkg].structs[n] = s // write back + for _, v := range s.Fields { + deep, _ = ts.findAvailableStruct(pkg, v.Type, deep) + } + deep-- + return deep, true + } + + } + if len(a) == 2 { a[1] = strings.TrimPrefix(a[1], "[]") a[1] = strings.TrimPrefix(a[1], "*") if _, ok := ts.Packages[a[0]]; ok { if _, ok := ts.Packages[a[0]].structs[a[1]]; ok { - return ts.Packages[a[0]].structs[a[1]], true + s := ts.Packages[a[0]].structs[a[1]] + if s.Typescript { + deep-- + return deep, true + } + s.Typescript = true + ts.Packages[a[0]].structs[a[1]] = s // write back + for _, v := range s.Fields { + ts.findAvailableStruct(pkg, v.Type, deep) + } + deep-- + return deep, true } } } - if len(a) == 1 { - n = strings.TrimPrefix(n, "[]") - n = strings.TrimPrefix(n, "*") - for p := range ts.Packages { - if _, ok := ts.Packages[p].structs[n]; ok { - return ts.Packages[p].structs[n], true - } - } - } - if n == "" || IsNativeType(n) { - return TSStruct{}, true - } - return TSStruct{}, false + return deep, false } func (i *TSInfo) TestEndpoints() { for p := range i.Packages { for _, v1 := range i.Packages[p].endpoints { - _, f := i.findAvailableStruct(v1.Request) + _, f := i.findAvailableStruct(p, v1.Request, 0) if !f { fmt.Printf("??Endpoint: request %s not found\n", v1.Request) } - _, f = i.findAvailableStruct(v1.Response) + _, f = i.findAvailableStruct(p, v1.Response, 0) if !f { fmt.Printf("??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 36995e5..7e17047 100644 --- a/backend/pkg/ts-rpc/tsStruct.go +++ b/backend/pkg/ts-rpc/tsStruct.go @@ -79,9 +79,7 @@ func getFieldInfo(t ast.Expr) string { case *ast.InterfaceType: result = "interface{}" default: - f := fmt.Sprintf("this go type: %T is not evaluated", ft) - fmt.Println(f) - //exitOnError(fmt.Errorf("this go type: %T is not evaluated", ft)) + exitOnError(fmt.Errorf("this go type: %T is not evaluated", ft)) } return result } @@ -104,25 +102,12 @@ func getFieldTsInfo(t ast.Expr) string { case *ast.FuncType: fmt.Println("*ast.FuncType found, skipping...") default: - //f := fmt.Sprintf("getFieldTsInfo can't evaluate type: %T %T", t, ft) - //fmt.Println(f) exitOnError(fmt.Errorf("getFieldTsInfo can't evaluate type: %T %T", t, ft)) } return result } -func getSourceInfo(pos int, src []TSSourceFile) string { - for _, v := range src { - for _, l := range v.Lines { - if pos >= l.Pos && pos <= l.End { - return fmt.Sprintf("%s Line: %d", v.Name, l.Line) - } - } - } - return "" -} - -func (s *TSStruct) getStruct(ts *ast.TypeSpec, src []TSSourceFile) { +func (s *TSStruct) getStruct(ts *ast.TypeSpec) { if st, ok := ts.Type.(*ast.StructType); ok { for _, field := range st.Fields.List { @@ -148,7 +133,7 @@ func (s *TSStruct) getStruct(ts *ast.TypeSpec, src []TSSourceFile) { Type: getFieldInfo(field.Type), TsType: tsType, DependOn: toBeImported(field.Type), - SourceInfo: getSourceInfo(int(field.Type.Pos()), src), + SourceInfo: "", } s.Fields = append(s.Fields, f) } else { @@ -160,7 +145,7 @@ func (s *TSStruct) getStruct(ts *ast.TypeSpec, src []TSSourceFile) { Type: getFieldInfo(field.Type), TsType: tsType, DependOn: toBeImported(field.Type), - SourceInfo: getSourceInfo(int(field.Type.Pos()), src), + SourceInfo: "", } s.Fields = append(s.Fields, f) } else { @@ -172,11 +157,9 @@ func (s *TSStruct) getStruct(ts *ast.TypeSpec, src []TSSourceFile) { Type: se.Name, TsType: tsType, DependOn: false, - SourceInfo: getSourceInfo(int(field.Type.Pos()), src), + SourceInfo: "", } s.Fields = append(s.Fields, f) - - fmt.Println(f) } else { exitOnError(fmt.Errorf("this typescript type: %T is not evaluated", field.Type)) } diff --git a/backend/pkg/ts-rpc/tsTsInfo.go b/backend/pkg/ts-rpc/tsTsInfo.go index 7ffa51b..0de77db 100644 --- a/backend/pkg/ts-rpc/tsTsInfo.go +++ b/backend/pkg/ts-rpc/tsTsInfo.go @@ -17,7 +17,6 @@ type TSModule struct { GTypes map[string]string Endpoints map[string]string Imports map[string][]string - Source string } type TSOutputSources []string @@ -222,7 +221,7 @@ func (ts *TSSouces) Populate(info TSInfo) { for p := range info.Packages { ts.ensurePackage(p) - ts.Errors = append(ts.Errors, fmt.Sprintf("Process pakage %s\n", p)) + // ts.Errors = append(ts.Errors, fmt.Sprintf("Process pakage %s\n", p)) for _, st := range info.Packages[p].structs { if st.Typescript { @@ -259,30 +258,6 @@ func (ts *TSSouces) Populate(info TSInfo) { } for _, e := range info.Packages[p].endpoints { - - /* if e.Request != "" { - a := strings.Split(e.Request, ".") - if len(a) == 2 { - s, d, err := structToTs(info, a[0], a[1]) - if err != nil { - ts.Errors = append(ts.Errors, err.Error()) - } - fmt.Println(s, d) - } - - } - - if e.Response != "" { - a := strings.Split(e.Response, ".") - if len(a) == 2 { - s, d, err := structToTs(info, a[0], a[1]) - if err != nil { - ts.Errors = append(ts.Errors, err.Error()) - } - fmt.Println(s, d) - } - - } */ responseAndRequest := "" if e.Request != "" { @@ -303,10 +278,6 @@ func (ts *TSSouces) Populate(info TSInfo) { } pkg.Endpoints[e.Name] = e.ToTs() ts.Pakages[p] = pkg - if p == "users" { - fmt.Printf("endpoint %s imports: %v\n", e.Name, pkg.Imports) - } - } } } diff --git a/backend/pkg/ts-rpc/tsrpc.go b/backend/pkg/ts-rpc/tsrpc.go index b747229..f8d6044 100644 --- a/backend/pkg/ts-rpc/tsrpc.go +++ b/backend/pkg/ts-rpc/tsrpc.go @@ -61,9 +61,6 @@ func GetTSSource() error { tsFiles.Add("apiTypes.ts", tsIndexSource) for p := range tsSourcesData.Pakages { - if p == "users" { - fmt.Println("users package") - } source := "" for _, v1 := range tsSourcesData.Pakages[p].Endpoints { source += fmt.Sprintln(v1) @@ -90,7 +87,7 @@ func GetTSSource() error { } } if found { - tmp += "import { api } from './api.ts'\n" + tmp += "import { api } from './api'\n" } if len(tsApiDeclarations) > 0 { @@ -108,20 +105,19 @@ func GetTSSource() error { } } if len(decs) > 0 { - TSApiDeclarations := "import { " + TSApiDeclarations := "import type { " for _, d := range decs { TSApiDeclarations += d + ", " } TSApiDeclarations = TSApiDeclarations[:len(TSApiDeclarations)-2] TSApiDeclarations += " } from './apiTypes.ts'\n" - fmt.Println("tsApiDeclarations", TSApiDeclarations) tmp += TSApiDeclarations } } imports := "" for f := range tsSourcesData.Pakages[p].Imports { - imports += "import * as " + f + " from './" + f + ".ts'\n" + imports += "import type * as " + f + " from './" + f + ".ts'\n" } tmp += imports tmp += source diff --git a/frontend/src/api/admin.ts b/frontend/src/api/admin.ts new file mode 100644 index 0000000..d496361 --- /dev/null +++ b/frontend/src/api/admin.ts @@ -0,0 +1,34 @@ +import { api } from "./api"; +import type { Nullable } from "./apiTypes.ts"; +import type * as users from "./users.ts"; + +// Typescript: TSEndpoint= path=/admin/users/:uuid/block; name=blockUser; method=PUT; request=admin.BlockUserRequest; response=users.User +// internal/admin/routes.go Line: 16 +export const blockUser = async ( + data: BlockUserRequest, +): Promise<{ data: users.User; error: Nullable }> => { + return (await api.PUT("/admin/users/:uuid/block", data)) as { + data: users.User; + error: Nullable; + }; +}; + +// Typescript: TSEndpoint= path=/admin/users; name=listUsers; method=POST; request=admin.ListUsersRequest; response=users.[]User +// internal/admin/routes.go Line: 12 +export const listUsers = async ( + data: ListUsersRequest, +): Promise<{ data: users.User[]; error: Nullable }> => { + return (await api.POST("/admin/users", data)) as { + data: users.User[]; + error: Nullable; + }; +}; + +export interface BlockUserRequest { + action: string; +} + +export interface ListUsersRequest { + page: number; + pageSize: number; +} diff --git a/frontend/src/api/api.ts b/frontend/src/api/api.ts index 4d24791..c965b1d 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 05, 2026 17:08:11 UTC +// Apr 15, 2026 18:49:19 UTC // export interface ApiRestResponse { @@ -273,341 +273,4 @@ export default class Api { } } -const api = new Api("http://localhost:3000"); - -// Global Declarations -export type Nullable = T | null; - -export type Record = { [P in K]: T }; - -// -// package model -// - -export interface RefreshRequest { - refresh_token: string; -} - -export interface TokenPair { - access_token: string; - refresh_token: string; -} - -export interface ForgotPasswordRequest { - email: string; -} - -export interface LoginRequest { - username: string; - password: string; -} - -export interface ResetPasswordRequest { - token: string; - password: string; -} - -// -// package controllers -// - -export interface BlockUserRequest { - action: string; -} - -export interface ListUsersRequest { - page: number; - pageSize: number; -} - -export interface SimpleResponse { - message: string; -} - -export interface UpdateUserRequest { - name: string; - email: string; - password: string; - roles: models.UserRoles; - status: models.UserStatus; - types: models.UserTypes; - avatar: Nullable; - details: Nullable; - preferences: Nullable; -} - -// -// package routes -// - -// Typescript: TSEndpoint= path=/metrics; name=metrics; method=GET; response=string -// internal/http/routes/system_routes.go Line: 37 -export const metrics = async (): Promise<{ - data: string; - error: Nullable; -}> => { - return (await api.GET("/metrics")) as { - data: string; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/maildebug; name=mailDebug; method=GET; response=routes.[]MailDebugItem -// internal/http/routes/system_routes.go Line: 48 -export const mailDebug = async (): Promise<{ - data: MailDebugItem[]; - error: Nullable; -}> => { - return (await api.GET("/maildebug")) as { - data: MailDebugItem[]; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/users/:uuid; name=getUser; method=GET; response=models.UserProfile -// internal/http/routes/user_routes.go Line: 13 -export const getUser = async ( - uuid: string, -): Promise<{ data: UserProfile; error: Nullable }> => { - return (await api.GET(`/users/${uuid}`)) as { - data: UserProfile; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/users/:uuid; name=updateUser; method=PUT; request=controllers.UpdateUserRequest; response=models.UserProfile -// internal/http/routes/user_routes.go Line: 19 - -export const updateUser = async ( - data: UpdateUserRequest, -): Promise<{ data: UserProfile; error: Nullable }> => { - return (await api.PUT("/users/:uuid", data)) as { - data: UserProfile; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/admin/users; name=listUsers; method=POST; request=controllers.ListUsersRequest; response=models.[]UserShort -// internal/http/routes/admin_routes.go Line: 12 - -export const listUsers = async ( - data: ListUsersRequest, -): Promise<{ data: UserShort[]; error: Nullable }> => { - return (await api.POST("/admin/users", data)) as { - data: UserShort[]; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/admin/users/:uuid/block; name=blockUser; method=PUT; request=controllers.BlockUserRequest; response=models.UserShort -// internal/http/routes/admin_routes.go Line: 15 - -export const blockUser = async ( - data: BlockUserRequest, -): Promise<{ data: UserShort; error: Nullable }> => { - return (await api.PUT("/admin/users/:uuid/block", data)) as { - data: UserShort; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/users; name=createUser; method=POST; request=models.UserCreateInput; response=models.UserProfile -// internal/http/routes/user_routes.go Line: 16 - -export const createUser = async ( - data: UserCreateInput, -): Promise<{ data: UserProfile; error: Nullable }> => { - return (await api.POST("/users", data)) as { - data: UserProfile; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/users/:uuid; name=deleteUser; method=DELETE; response=controllers.SimpleResponse -// internal/http/routes/user_routes.go Line: 22 - -export const deleteUser = async ( - uuid: string, -): Promise<{ data: SimpleResponse; error: Nullable }> => { - return (await api.DELETE(`/users/${uuid}`)) as { - data: SimpleResponse; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/health; name=health; method=GET; response=string -// internal/http/routes/system_routes.go Line: 34 -export const health = async (): Promise<{ - data: string; - error: Nullable; -}> => { - return (await api.GET("/health")) as { - data: string; - error: Nullable; - }; -}; - -export interface FormRequest { - req: string; - count: number; -} - -export interface FormResponse { - test: string; -} - -export interface MailDebugItem { - name: string; - content: string; -} - -// -// package endpoint -// - -// Typescript: TSEndpoint= path=/auth/password/forgot; name=forgotPassword; method=POST; request=model.ForgotPasswordRequest; response=controllers.SimpleResponse -// internal/auth/endpoint/routes.go Line: 34 - -export const forgotPassword = async ( - data: ForgotPasswordRequest, -): Promise<{ data: SimpleResponse; error: Nullable }> => { - return (await api.POST("/auth/password/forgot", data)) as { - data: SimpleResponse; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/auth/password/reset; name=resetPassword; method=POST; request=model.ResetPasswordRequest; response=controllers.SimpleResponse -// internal/auth/endpoint/routes.go Line: 37 - -export const resetPassword = async ( - data: ResetPasswordRequest, -): Promise<{ data: SimpleResponse; error: Nullable }> => { - return (await api.POST("/auth/password/reset", data)) as { - data: SimpleResponse; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/auth/password/valid; name=validToken; method=POST; request=string; response=controllers.SimpleResponse -// internal/auth/endpoint/routes.go Line: 40 - -export const validToken = async ( - data: string, -): Promise<{ data: SimpleResponse; error: Nullable }> => { - return (await api.POST("/auth/password/valid", data)) as { - data: SimpleResponse; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/auth/login; name=login; method=POST; request=model.LoginRequest; response=model.TokenPair -// internal/auth/endpoint/routes.go Line: 22 - -export const login = async ( - data: LoginRequest, -): Promise<{ data: TokenPair; error: Nullable }> => { - return (await api.POST("/auth/login", data)) as { - data: TokenPair; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/auth/refresh; name=refresh; method=POST; request=model.RefreshRequest; response=model.TokenPair -// internal/auth/endpoint/routes.go Line: 25 - -export const refresh = async ( - data: RefreshRequest, -): Promise<{ data: TokenPair; error: Nullable }> => { - return (await api.POST("/auth/refresh", data)) as { - data: TokenPair; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/auth/me; name=me; method=GET; response=models.UserShort -// internal/auth/endpoint/routes.go Line: 28 -export const me = async (): Promise<{ - data: UserShort; - error: Nullable; -}> => { - return (await api.GET("/auth/me")) as { - data: UserShort; - error: Nullable; - }; -}; - -// Typescript: TSEndpoint= path=/auth/register; name=register; method=POST; request=models.UserCreateInput; response=models.UserShort -// internal/auth/endpoint/routes.go Line: 31 - -export const register = async ( - data: UserCreateInput, -): Promise<{ data: UserShort; error: Nullable }> => { - return (await api.POST("/auth/register", data)) as { - data: UserShort; - error: Nullable; - }; -}; - -// -// package models -// - -export interface UserCreateInput { - name: string; - email: string; - password: string; - roles: UserRoles; - status: UserStatus; - types: UserTypes; - avatar: Nullable; - details: Nullable; - preferences: Nullable; -} - -export interface UserDetailsShort { - title: string; - firstName: string; - lastName: string; - address: string; - city: string; - zipCode: string; - country: string; - phone: string; -} - -export interface UserPreferencesShort { - useIdle: boolean; - idleTimeout: number; - useIdlePassword: boolean; - idlePin: string; - useDirectLogin: boolean; - useQuadcodeLogin: boolean; - sendNoticesMail: boolean; - language: string; -} - -export interface UserShort { - email: string; - name: string; - roles: UserRoles; - status: UserStatus; - uuid: string; - details: Nullable; - preferences: Nullable; - avatar: Nullable; -} - -export type UserRoles = string[]; - -export type UserStatus = (typeof EnumUserStatus)[keyof typeof EnumUserStatus]; - -export type UserTypes = string[]; - -export type UsersShort = UserShort[]; - -export const EnumUserStatus = { - UserStatusPending: "pending", - UserStatusActive: "active", - UserStatusDisabled: "disabled", -} as const; +export const api = new Api("http://localhost:3000"); diff --git a/frontend/src/api/apiTypes.ts b/frontend/src/api/apiTypes.ts new file mode 100644 index 0000000..e6d9a4b --- /dev/null +++ b/frontend/src/api/apiTypes.ts @@ -0,0 +1,4 @@ +// API Declarations +export type Nullable = T | null; + +export type Record = { [P in K]: T }; diff --git a/frontend/src/api/responses.ts b/frontend/src/api/responses.ts new file mode 100644 index 0000000..939b5e5 --- /dev/null +++ b/frontend/src/api/responses.ts @@ -0,0 +1,3 @@ +export interface SimpleResponse { + message: string; +} diff --git a/frontend/src/api/systemUtils.ts b/frontend/src/api/systemUtils.ts new file mode 100644 index 0000000..206291f --- /dev/null +++ b/frontend/src/api/systemUtils.ts @@ -0,0 +1,43 @@ +import { api } from "./api"; +import type { Nullable } from "./apiTypes.ts"; + +// Typescript: TSEndpoint= path=/health; name=health; method=GET; response=string +// internal/systemUtils/routes.go Line: 38 +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 +// internal/systemUtils/routes.go Line: 41 +export const metrics = async (): Promise<{ + data: string; + error: Nullable; +}> => { + return (await api.GET("/metrics")) as { + data: string; + error: Nullable; + }; +}; + +// Typescript: TSEndpoint= path=/maildebug; name=mailDebug; method=GET; response=[]MailDebugItem +// internal/systemUtils/routes.go Line: 53 +export const mailDebug = async (): Promise<{ + data: MailDebugItem[]; + error: Nullable; +}> => { + return (await api.GET("/maildebug")) as { + data: MailDebugItem[]; + error: Nullable; + }; +}; + +export interface MailDebugItem { + name: string; + content: string; +} diff --git a/frontend/src/api/tokens.ts b/frontend/src/api/tokens.ts new file mode 100644 index 0000000..769e217 --- /dev/null +++ b/frontend/src/api/tokens.ts @@ -0,0 +1,4 @@ +export interface TokenPair { + access_token: string; + refresh_token: string; +} diff --git a/frontend/src/api/users.ts b/frontend/src/api/users.ts new file mode 100644 index 0000000..ef9ee62 --- /dev/null +++ b/frontend/src/api/users.ts @@ -0,0 +1,239 @@ +import { api } from "./api"; +import type { Nullable } from "./apiTypes.ts"; +import type * as tokens from "./tokens.ts"; +import type * as responses from "./responses.ts"; + +// Typescript: TSEndpoint= path=/auth/password/valid; name=validToken; method=POST; request=string; response=responses.SimpleResponse +// internal/user/routes.go Line: 58 +export const validToken = async ( + data: string, +): Promise<{ data: responses.SimpleResponse; error: Nullable }> => { + return (await api.POST("/auth/password/valid", data)) as { + data: responses.SimpleResponse; + error: Nullable; + }; +}; + +// Typescript: TSEndpoint= path=/users/:uuid; name=updateUser; method=PUT; request=users.UpdateUserRequest; response=users.User +// internal/user/routes.go Line: 33 +export const updateUser = async ( + data: UpdateUserRequest, +): Promise<{ data: User; error: Nullable }> => { + return (await api.PUT("/users/:uuid", data)) as { + data: User; + error: Nullable; + }; +}; + +// Typescript: TSEndpoint= path=/users/:uuid; name=deleteUser; method=DELETE; response=responses.SimpleResponse +// internal/user/routes.go Line: 36 +export const deleteUser = async ( + uuid: string, +): Promise<{ data: responses.SimpleResponse; error: Nullable }> => { + return (await api.DELETE(`/users/${uuid}`)) as { + data: responses.SimpleResponse; + error: Nullable; + }; +}; + +// Typescript: TSEndpoint= path=/auth/register; name=register; method=POST; request=users.UserCreateRequest; response=users.User +// internal/user/routes.go Line: 49 +export const register = async ( + data: UserCreateRequest, +): Promise<{ data: User; error: Nullable }> => { + return (await api.POST("/auth/register", data)) as { + data: User; + error: Nullable; + }; +}; + +// Typescript: TSEndpoint= path=/auth/password/forgot; name=forgotPassword; method=POST; request=users.ForgotPasswordRequest; response=responses.SimpleResponse +// internal/user/routes.go Line: 52 +export const forgotPassword = async ( + data: ForgotPasswordRequest, +): Promise<{ data: responses.SimpleResponse; error: Nullable }> => { + return (await api.POST("/auth/password/forgot", data)) as { + data: responses.SimpleResponse; + error: Nullable; + }; +}; + +// Typescript: TSEndpoint= path=/users; name=createUser; method=POST; request=users.UserCreateRequest; response=users.User +// internal/user/routes.go Line: 30 +export const createUser = async ( + data: UserCreateRequest, +): Promise<{ data: User; error: Nullable }> => { + return (await api.POST("/users", data)) as { + data: User; + error: Nullable; + }; +}; + +// Typescript: TSEndpoint= path=/auth/refresh; name=refresh; method=POST; request=users.RefreshRequest; response=tokens.TokenPair +// internal/user/routes.go Line: 46 +export const refresh = async ( + data: RefreshRequest, +): Promise<{ data: tokens.TokenPair; error: Nullable }> => { + return (await api.POST("/auth/refresh", data)) as { + data: tokens.TokenPair; + error: Nullable; + }; +}; + +// Typescript: TSEndpoint= path=/users/:uuid; name=getUser; method=GET; response=users.User +// internal/user/routes.go Line: 27 +export const getUser = async ( + uuid: string, +): Promise<{ data: User; error: Nullable }> => { + return (await api.GET(`/users/${uuid}`)) as { + data: User; + error: Nullable; + }; +}; + +// Typescript: TSEndpoint= path=/auth/me; name=me; method=GET; response=users.User +// internal/user/routes.go Line: 39 +export const me = async (): Promise<{ + data: User; + error: Nullable; +}> => { + return (await api.GET("/auth/me")) as { data: User; error: Nullable }; +}; + +// Typescript: TSEndpoint= path=/auth/login; name=login; method=POST; request=users.LoginRequest; response=tokens.TokenPair +// internal/user/routes.go Line: 43 +export const login = async ( + data: LoginRequest, +): Promise<{ data: tokens.TokenPair; error: Nullable }> => { + return (await api.POST("/auth/login", data)) as { + data: tokens.TokenPair; + error: Nullable; + }; +}; + +// Typescript: TSEndpoint= path=/auth/password/reset; name=resetPassword; method=POST; request=users.ResetPasswordRequest; response=responses.SimpleResponse +// internal/user/routes.go Line: 55 +export const resetPassword = async ( + data: ResetPasswordRequest, +): Promise<{ data: responses.SimpleResponse; error: Nullable }> => { + return (await api.POST("/auth/password/reset", data)) as { + data: responses.SimpleResponse; + error: Nullable; + }; +}; + +export interface LoginRequest { + username: string; + password: string; +} + +export interface RefreshRequest { + refresh_token: string; +} + +export interface User { + id: number; + email: string; + name: string; + roles: UserRoles; + types: UserTypes; + status: UserStatus; + activatedAt: Date; + uuid: string; + details: Nullable; + preferences: Nullable; + avatar: Nullable; + createdAt: Date; + updatedAt: Date; +} + +export interface ForgotPasswordRequest { + email: string; +} + +export interface ResetPasswordRequest { + token: string; + password: string; +} + +export interface UpdateUserRequest { + name: string; + email: string; + password: string; + roles: UserRoles; + status: UserStatus; + types: UserTypes; + avatar: Nullable; + details: Nullable; + preferences: Nullable; +} + +export interface UserCreateRequest { + name: string; + email: string; + password: string; + roles: UserRoles; + status: UserStatus; + types: UserTypes; + avatar: Nullable; + details: Nullable; + 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: Date; + updatedAt: Date; +} + +export interface UserPreferences { + id: number; + userId: number; + useIdle: boolean; + idleTimeout: number; + useIdlePassword: boolean; + idlePin: string; + useDirectLogin: boolean; + useQuadcodeLogin: boolean; + sendNoticesMail: boolean; + language: string; + createdAt: Date; + updatedAt: Date; +} + +export interface UserProfile { + id: number; + email: string; + name: string; + roles: UserRoles; + types: UserTypes; + status: UserStatus; + activatedAt: Date; + uuid: string; + details: Nullable; + preferences: Nullable; + avatar: Nullable; + createdAt: Date; + updatedAt: Date; +} + +export type UserTypes = string[]; + +export type UserRoles = string[]; + +export type UserStatus = (typeof EnumUserStatus)[keyof typeof EnumUserStatus]; + +export const EnumUserStatus = { + UserStatusPending: "pending", + UserStatusActive: "active", + UserStatusDisabled: "disabled", +} as const; diff --git a/frontend/src/layouts/MainLayout.vue b/frontend/src/layouts/MainLayout.vue index da5700a..9510bf0 100644 --- a/frontend/src/layouts/MainLayout.vue +++ b/frontend/src/layouts/MainLayout.vue @@ -108,14 +108,14 @@ import { computed, onMounted, ref, watch } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRouter, useRoute } from 'vue-router'; import LogoUrl from 'src/assets/home/logo.png'; -import { me, type UserShort } from 'src/api/api'; +import { me, type User } from 'src/api/users'; import { usePreferencesStore, type LanguageCode } from 'src/stores/preferences-store'; const { t } = useI18n(); const router = useRouter(); const route = useRoute(); const preferencesStore = usePreferencesStore(); -const currentUser = ref(null); +const currentUser = ref(null); const model = computed({ get: () => preferencesStore.language, set: (language: LanguageCode) => { diff --git a/frontend/src/pages/admin/UsersPage.vue b/frontend/src/pages/admin/UsersPage.vue index 13fe8b6..21e40ec 100644 --- a/frontend/src/pages/admin/UsersPage.vue +++ b/frontend/src/pages/admin/UsersPage.vue @@ -509,21 +509,24 @@ import VuePictureCropper from 'vue-picture-cropper'; import { blockUser, - createUser, - getUser, listUsers, - updateUser, - EnumUserStatus, type BlockUserRequest, type ListUsersRequest, +}from 'src/api/admin'; + +import { + createUser, + getUser, + updateUser, + EnumUserStatus, type UpdateUserRequest, - type UserCreateInput, - type UserDetailsShort, - type UserPreferencesShort, + type UserCreateRequest, type UserProfile, - type UserShort, + type User, type UserStatus, -} from 'src/api/api'; + type UserDetails, + type UserPreferences, +} from 'src/api/users'; type DialogMode = 'create' | 'edit' | 'view'; @@ -536,8 +539,8 @@ interface UserFormState { roles: string[]; types: string[]; avatar: string; - details: UserDetailsShort; - preferences: UserPreferencesShort; + details: UserDetails; + preferences: UserPreferences; } const $q = useQuasar(); @@ -550,7 +553,7 @@ const avatarDialogOpen = ref(false); const dialogMode = ref('create'); const editorTab = ref<'account' | 'details' | 'preferences'>('account'); const filter = ref(''); -const rows = ref([]); +const rows = ref([]); const detailsEnabled = ref(true); const preferencesEnabled = ref(true); const passwordDialogUserUuid = ref(''); @@ -593,7 +596,7 @@ const avatarPresetMode = { height: 320, } as const; -const columns: QTableColumn[] = [ +const columns: QTableColumn[] = [ { name: 'name', label: 'Utente', field: 'name', align: 'left', sortable: true }, { name: 'status', label: 'Status', field: 'status', align: 'left', sortable: true }, { name: 'roles', label: 'Roles', field: (row) => row.roles.join(', '), align: 'left' }, @@ -608,7 +611,7 @@ const passwordForm = reactive({ confirmPassword: '', }); -const payload = computed(() => ({ +const payload = computed(() => ({ name: form.name.trim(), email: form.email.trim(), password: dialogMode.value === 'create' ? form.password : '', @@ -643,6 +646,10 @@ function emptyForm(): UserFormState { zipCode: '', country: '', phone: '', + id: 0, + userId: 0, + createdAt: undefined, + updatedAt: undefined }, preferences: { useIdle: false, @@ -653,6 +660,10 @@ function emptyForm(): UserFormState { useQuadcodeLogin: false, sendNoticesMail: false, language: 'it', + id: 0, + userId: 0, + createdAt: undefined, + updatedAt: undefined }, }; } @@ -955,7 +966,7 @@ async function saveUser(): Promise { saving.value = true; try { if (dialogMode.value === 'create') { - const response = await createUser(payload.value as UserCreateInput); + const response = await createUser(payload.value as UserCreateRequest); if (response.error) { throw new Error(response.error); } diff --git a/frontend/src/pages/dev/ApiEndpointsPage.vue b/frontend/src/pages/dev/ApiEndpointsPage.vue index 8398852..381517e 100644 --- a/frontend/src/pages/dev/ApiEndpointsPage.vue +++ b/frontend/src/pages/dev/ApiEndpointsPage.vue @@ -124,16 +124,22 @@ import { reactive, ref } from 'vue'; import { forgotPassword, - health, - listUsers, login, me, - metrics, refresh, register, resetPassword, -} from 'src/api/api'; -import type { UserCreateInput } from 'src/api/api'; + type UserCreateRequest, +} from 'src/api/users'; + +import { + health, + metrics, +} from 'src/api/systemUtils'; + +import { + listUsers, +} from 'src/api/admin'; type Method = 'GET' | 'POST'; type FieldType = 'text' | 'password' | 'number' | 'textarea'; @@ -385,7 +391,7 @@ const endpoints: EndpointConfig[] = [ details: '', preferences: '', }, - call: (payload) => register(payload as UserCreateInput), + call: (payload) => register(payload as UserCreateRequest), buildPayload: (form) => ({ name: String(form.name ?? ''), email: String(form.email ?? ''), diff --git a/frontend/src/pages/dev/LoginPage.vue b/frontend/src/pages/dev/LoginPage.vue index 2efe21b..1b24579 100644 --- a/frontend/src/pages/dev/LoginPage.vue +++ b/frontend/src/pages/dev/LoginPage.vue @@ -53,7 +53,7 @@ import { reactive, ref } from 'vue'; import { useRouter } from 'vue-router'; import { useQuasar } from 'quasar'; -import { login } from 'src/api/api'; +import { login } from 'src/api/users'; const router = useRouter(); const $q = useQuasar(); diff --git a/frontend/src/pages/dev/MailDebugPage.vue b/frontend/src/pages/dev/MailDebugPage.vue index e54f843..c5f62bb 100644 --- a/frontend/src/pages/dev/MailDebugPage.vue +++ b/frontend/src/pages/dev/MailDebugPage.vue @@ -81,7 +81,7 @@