feat(tsrpc): enhance TypeScript API generation with improved endpoint handling and imports
- Added a new function `GetApiFile` to generate the TypeScript API file dynamically based on environment variables. - Updated `TSEndpoint` struct to include an `Imports` map for tracking TypeScript imports. - Enhanced `VerifyTypes` method to manage request and response types more effectively, including nullable types. - Modified `ToTs` method to generate TypeScript code with improved type handling. - Introduced `TSFiles` struct for managing generated TypeScript files and saving them to the filesystem. - Implemented formatting of generated TypeScript code using Prettier. - Added new TypeScript files for various endpoints, including user management and system utilities. - Updated existing TypeScript files to reflect changes in API structure and response types.
This commit is contained in:
parent
5b9fe6c9b7
commit
3461395eb3
|
|
@ -13,3 +13,8 @@ DB_dsn=file:./data/data.db?_foreign_keys=on
|
|||
|
||||
# Auth
|
||||
AUTH_SECRET=change-me
|
||||
|
||||
# TS Generator
|
||||
TS_GENERATOR_URL=http://localhost:3000
|
||||
TS_GENERATOR_PATH= .
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
import { api } from "./api.ts";
|
||||
import { Nullable } from "./apiTypes.ts";
|
||||
import * 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
|
||||
export const listUsers = async (
|
||||
data: ListUsersRequest,
|
||||
): Promise<{ data: users.User[]; error: Nullable<string> }> => {
|
||||
return (await api.POST("/admin/users", data)) as {
|
||||
data: users.User[];
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// 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<string> }> => {
|
||||
return (await api.PUT("/admin/users/:uuid/block", data)) as {
|
||||
data: users.User;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
export interface BlockUserRequest {
|
||||
action: string;
|
||||
}
|
||||
|
||||
export interface ListUsersRequest {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
|
@ -0,0 +1,276 @@
|
|||
//
|
||||
// Typescript API generated from gofiber backend
|
||||
// Copyright (C) 2022 - 2025 Fabio Prada
|
||||
//
|
||||
// This file was generated by github.com/millevolte/ts-rpc
|
||||
//
|
||||
// Apr 14, 2026 21:39:07 UTC
|
||||
//
|
||||
|
||||
export interface ApiRestResponse {
|
||||
data?: unknown;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
function isApiRestResponse(data: unknown): data is ApiRestResponse {
|
||||
return (
|
||||
typeof data === "object" &&
|
||||
data !== null &&
|
||||
Object.prototype.hasOwnProperty.call(data, "data") &&
|
||||
Object.prototype.hasOwnProperty.call(data, "error")
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeError(error: unknown): Error {
|
||||
if (error instanceof DOMException && error.name === "AbortError") {
|
||||
return new Error("api.error.timeouterror");
|
||||
}
|
||||
|
||||
if (error instanceof TypeError && error.message === "Failed to fetch") {
|
||||
return new Error("api.error.connectionerror");
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
return new Error(String(error));
|
||||
}
|
||||
|
||||
export default class Api {
|
||||
apiUrl: string;
|
||||
localStorage: Storage | null;
|
||||
|
||||
constructor(apiurl: string) {
|
||||
this.apiUrl = apiurl;
|
||||
this.localStorage = window.localStorage;
|
||||
}
|
||||
|
||||
async request(
|
||||
method: string,
|
||||
url: string,
|
||||
data: unknown,
|
||||
timeout = 7000,
|
||||
upload = false,
|
||||
): Promise<ApiRestResponse> {
|
||||
const headers: { [key: string]: string } = {
|
||||
"Cache-Control": "no-cache",
|
||||
};
|
||||
|
||||
if (!upload) {
|
||||
headers["Content-Type"] = "application/json";
|
||||
}
|
||||
|
||||
if (this.localStorage) {
|
||||
const auth = this.localStorage.getItem("Auth-Token");
|
||||
if (auth) {
|
||||
headers["Auth-Token"] = auth;
|
||||
}
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||
const requestOptions: RequestInit = {
|
||||
method,
|
||||
cache: "no-store",
|
||||
mode: "cors",
|
||||
credentials: "include",
|
||||
headers,
|
||||
signal: controller.signal,
|
||||
};
|
||||
|
||||
if (upload) {
|
||||
requestOptions.body = data as FormData;
|
||||
} else if (data !== null && data !== undefined) {
|
||||
requestOptions.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, requestOptions);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("api.error." + response.statusText);
|
||||
}
|
||||
|
||||
if (this.localStorage) {
|
||||
const jwt = response.headers.get("Auth-Token");
|
||||
if (jwt) {
|
||||
this.localStorage.setItem("Auth-Token", jwt);
|
||||
}
|
||||
}
|
||||
|
||||
const responseData = (await response.json()) as unknown;
|
||||
if (!isApiRestResponse(responseData)) {
|
||||
throw new Error("api.error.wrongdatatype");
|
||||
}
|
||||
|
||||
if (responseData.error) {
|
||||
throw new Error(responseData.error);
|
||||
}
|
||||
|
||||
return responseData;
|
||||
} catch (error: unknown) {
|
||||
throw normalizeError(error);
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
processResult(result: ApiRestResponse): {
|
||||
data: unknown;
|
||||
error: string | null;
|
||||
} {
|
||||
if (typeof result.data !== "object") {
|
||||
return { data: result.data, error: null };
|
||||
}
|
||||
|
||||
if (!result.data) {
|
||||
result.data = {};
|
||||
}
|
||||
|
||||
return { data: result.data, error: null };
|
||||
}
|
||||
|
||||
processError(error: unknown): {
|
||||
data: unknown;
|
||||
error: string | null;
|
||||
} {
|
||||
const normalizedError = normalizeError(error);
|
||||
|
||||
if (normalizedError.message === "api.error.timeouterror") {
|
||||
Object.defineProperty(normalizedError, "__api_error__", {
|
||||
value: normalizedError.message,
|
||||
writable: false,
|
||||
});
|
||||
|
||||
return { data: null, error: normalizedError.message };
|
||||
}
|
||||
|
||||
if (normalizedError.message === "api.error.connectionerror") {
|
||||
Object.defineProperty(normalizedError, "__api_error__", {
|
||||
value: normalizedError.message,
|
||||
writable: false,
|
||||
});
|
||||
|
||||
return { data: null, error: normalizedError.message };
|
||||
}
|
||||
|
||||
return {
|
||||
data: null,
|
||||
error: normalizedError.message,
|
||||
};
|
||||
}
|
||||
|
||||
async POST(
|
||||
url: string,
|
||||
data: unknown,
|
||||
timeout?: number,
|
||||
): Promise<{
|
||||
data: unknown;
|
||||
error: string | null;
|
||||
}> {
|
||||
try {
|
||||
const upload = url.includes("/upload/");
|
||||
const result = await this.request(
|
||||
"POST",
|
||||
this.apiUrl + url,
|
||||
data,
|
||||
timeout,
|
||||
upload,
|
||||
);
|
||||
|
||||
return this.processResult(result);
|
||||
} catch (error: unknown) {
|
||||
return this.processError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async PUT(
|
||||
url: string,
|
||||
data: unknown,
|
||||
timeout?: number,
|
||||
): Promise<{
|
||||
data: unknown;
|
||||
error: string | null;
|
||||
}> {
|
||||
try {
|
||||
const upload = url.includes("/upload/");
|
||||
const result = await this.request(
|
||||
"PUT",
|
||||
this.apiUrl + url,
|
||||
data,
|
||||
timeout,
|
||||
upload,
|
||||
);
|
||||
|
||||
return this.processResult(result);
|
||||
} catch (error: unknown) {
|
||||
return this.processError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async GET(
|
||||
url: string,
|
||||
timeout?: number,
|
||||
): Promise<{
|
||||
data: unknown;
|
||||
error: string | null;
|
||||
}> {
|
||||
try {
|
||||
const result = await this.request(
|
||||
"GET",
|
||||
this.apiUrl + url,
|
||||
null,
|
||||
timeout,
|
||||
);
|
||||
return this.processResult(result);
|
||||
} catch (error: unknown) {
|
||||
return this.processError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async DELETE(
|
||||
url: string,
|
||||
timeout?: number,
|
||||
): Promise<{
|
||||
data: unknown;
|
||||
error: string | null;
|
||||
}> {
|
||||
try {
|
||||
const result = await this.request(
|
||||
"DELETE",
|
||||
this.apiUrl + url,
|
||||
null,
|
||||
timeout,
|
||||
);
|
||||
return this.processResult(result);
|
||||
} catch (error: unknown) {
|
||||
return this.processError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async UPLOAD(
|
||||
url: string,
|
||||
data: unknown,
|
||||
timeout?: number,
|
||||
): Promise<{
|
||||
data: unknown;
|
||||
error: string | null;
|
||||
}> {
|
||||
try {
|
||||
const result = await this.request(
|
||||
"POST",
|
||||
this.apiUrl + url,
|
||||
data,
|
||||
timeout,
|
||||
true,
|
||||
);
|
||||
|
||||
return this.processResult(result);
|
||||
} catch (error: unknown) {
|
||||
return this.processError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const api = new Api("http://localhost:3000");
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
// API Declarations
|
||||
export type Nullable<T> = T | null;
|
||||
|
||||
export type Record<K extends string | number | symbol, T> = { [P in K]: T };
|
||||
|
|
@ -1,621 +0,0 @@
|
|||
//
|
||||
// Typescript API generated from gofiber backend
|
||||
// Copyright (C) 2022 - 2025 Fabio Prada
|
||||
//
|
||||
// This file was generated by github.com/millevolte/ts-rpc
|
||||
//
|
||||
// Apr 06, 2026 16:56:35 UTC
|
||||
//
|
||||
|
||||
export interface ApiRestResponse {
|
||||
data?: unknown;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
function isApiRestResponse(data: unknown): data is ApiRestResponse {
|
||||
return (
|
||||
typeof data === "object" &&
|
||||
data !== null &&
|
||||
Object.prototype.hasOwnProperty.call(data, "data") &&
|
||||
Object.prototype.hasOwnProperty.call(data, "error")
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeError(error: unknown): Error {
|
||||
if (error instanceof DOMException && error.name === "AbortError") {
|
||||
return new Error("api.error.timeouterror");
|
||||
}
|
||||
|
||||
if (error instanceof TypeError && error.message === "Failed to fetch") {
|
||||
return new Error("api.error.connectionerror");
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
return new Error(String(error));
|
||||
}
|
||||
|
||||
export default class Api {
|
||||
apiUrl: string;
|
||||
localStorage: Storage | null;
|
||||
|
||||
constructor(apiurl: string) {
|
||||
this.apiUrl = apiurl;
|
||||
this.localStorage = window.localStorage;
|
||||
}
|
||||
|
||||
async request(
|
||||
method: string,
|
||||
url: string,
|
||||
data: unknown,
|
||||
timeout = 7000,
|
||||
upload = false,
|
||||
): Promise<ApiRestResponse> {
|
||||
const headers: { [key: string]: string } = {
|
||||
"Cache-Control": "no-cache",
|
||||
};
|
||||
|
||||
if (!upload) {
|
||||
headers["Content-Type"] = "application/json";
|
||||
}
|
||||
|
||||
if (this.localStorage) {
|
||||
const auth = this.localStorage.getItem("Auth-Token");
|
||||
if (auth) {
|
||||
headers["Auth-Token"] = auth;
|
||||
}
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||
const requestOptions: RequestInit = {
|
||||
method,
|
||||
cache: "no-store",
|
||||
mode: "cors",
|
||||
credentials: "include",
|
||||
headers,
|
||||
signal: controller.signal,
|
||||
};
|
||||
|
||||
if (upload) {
|
||||
requestOptions.body = data as FormData;
|
||||
} else if (data !== null && data !== undefined) {
|
||||
requestOptions.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, requestOptions);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("api.error." + response.statusText);
|
||||
}
|
||||
|
||||
if (this.localStorage) {
|
||||
const jwt = response.headers.get("Auth-Token");
|
||||
if (jwt) {
|
||||
this.localStorage.setItem("Auth-Token", jwt);
|
||||
}
|
||||
}
|
||||
|
||||
const responseData = (await response.json()) as unknown;
|
||||
if (!isApiRestResponse(responseData)) {
|
||||
throw new Error("api.error.wrongdatatype");
|
||||
}
|
||||
|
||||
if (responseData.error) {
|
||||
throw new Error(responseData.error);
|
||||
}
|
||||
|
||||
return responseData;
|
||||
} catch (error: unknown) {
|
||||
throw normalizeError(error);
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
processResult(result: ApiRestResponse): {
|
||||
data: unknown;
|
||||
error: string | null;
|
||||
} {
|
||||
if (typeof result.data !== "object") {
|
||||
return { data: result.data, error: null };
|
||||
}
|
||||
|
||||
if (!result.data) {
|
||||
result.data = {};
|
||||
}
|
||||
|
||||
return { data: result.data, error: null };
|
||||
}
|
||||
|
||||
processError(error: unknown): {
|
||||
data: unknown;
|
||||
error: string | null;
|
||||
} {
|
||||
const normalizedError = normalizeError(error);
|
||||
|
||||
if (normalizedError.message === "api.error.timeouterror") {
|
||||
Object.defineProperty(normalizedError, "__api_error__", {
|
||||
value: normalizedError.message,
|
||||
writable: false,
|
||||
});
|
||||
|
||||
return { data: null, error: normalizedError.message };
|
||||
}
|
||||
|
||||
if (normalizedError.message === "api.error.connectionerror") {
|
||||
Object.defineProperty(normalizedError, "__api_error__", {
|
||||
value: normalizedError.message,
|
||||
writable: false,
|
||||
});
|
||||
|
||||
return { data: null, error: normalizedError.message };
|
||||
}
|
||||
|
||||
return {
|
||||
data: null,
|
||||
error: normalizedError.message,
|
||||
};
|
||||
}
|
||||
|
||||
async POST(
|
||||
url: string,
|
||||
data: unknown,
|
||||
timeout?: number,
|
||||
): Promise<{
|
||||
data: unknown;
|
||||
error: string | null;
|
||||
}> {
|
||||
try {
|
||||
const upload = url.includes("/upload/");
|
||||
const result = await this.request(
|
||||
"POST",
|
||||
this.apiUrl + url,
|
||||
data,
|
||||
timeout,
|
||||
upload,
|
||||
);
|
||||
|
||||
return this.processResult(result);
|
||||
} catch (error: unknown) {
|
||||
return this.processError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async PUT(
|
||||
url: string,
|
||||
data: unknown,
|
||||
timeout?: number,
|
||||
): Promise<{
|
||||
data: unknown;
|
||||
error: string | null;
|
||||
}> {
|
||||
try {
|
||||
const upload = url.includes("/upload/");
|
||||
const result = await this.request(
|
||||
"PUT",
|
||||
this.apiUrl + url,
|
||||
data,
|
||||
timeout,
|
||||
upload,
|
||||
);
|
||||
|
||||
return this.processResult(result);
|
||||
} catch (error: unknown) {
|
||||
return this.processError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async GET(
|
||||
url: string,
|
||||
timeout?: number,
|
||||
): Promise<{
|
||||
data: unknown;
|
||||
error: string | null;
|
||||
}> {
|
||||
try {
|
||||
const result = await this.request(
|
||||
"GET",
|
||||
this.apiUrl + url,
|
||||
null,
|
||||
timeout,
|
||||
);
|
||||
return this.processResult(result);
|
||||
} catch (error: unknown) {
|
||||
return this.processError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async DELETE(
|
||||
url: string,
|
||||
timeout?: number,
|
||||
): Promise<{
|
||||
data: unknown;
|
||||
error: string | null;
|
||||
}> {
|
||||
try {
|
||||
const result = await this.request(
|
||||
"DELETE",
|
||||
this.apiUrl + url,
|
||||
null,
|
||||
timeout,
|
||||
);
|
||||
return this.processResult(result);
|
||||
} catch (error: unknown) {
|
||||
return this.processError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async UPLOAD(
|
||||
url: string,
|
||||
data: unknown,
|
||||
timeout?: number,
|
||||
): Promise<{
|
||||
data: unknown;
|
||||
error: string | null;
|
||||
}> {
|
||||
try {
|
||||
const result = await this.request(
|
||||
"POST",
|
||||
this.apiUrl + url,
|
||||
data,
|
||||
timeout,
|
||||
true,
|
||||
);
|
||||
|
||||
return this.processResult(result);
|
||||
} catch (error: unknown) {
|
||||
return this.processError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const api = new Api("http://localhost:3000");
|
||||
|
||||
// Global Declarations
|
||||
export type Nullable<T> = T | null;
|
||||
|
||||
export type Record<K extends string | number | symbol, T> = { [P in K]: T };
|
||||
|
||||
//
|
||||
// package user
|
||||
//
|
||||
|
||||
// Typescript: TSEndpoint= path=/users/:uuid; name=getUser; method=GET; response=models.UserProfile
|
||||
// internal/user/routes.go Line: 13
|
||||
export const getUser = async (
|
||||
uuid: string,
|
||||
): Promise<{ data: UserProfile; error: Nullable<string> }> => {
|
||||
return (await api.GET(`/users/${uuid}`)) as {
|
||||
data: UserProfile;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// Typescript: TSEndpoint= path=/users; name=createUser; method=POST; request=models.UserCreateInput; response=models.UserProfile
|
||||
// internal/user/routes.go Line: 16
|
||||
|
||||
export const createUser = async (
|
||||
data: UserCreateInput,
|
||||
): Promise<{ data: UserProfile; error: Nullable<string> }> => {
|
||||
return (await api.POST("/users", data)) as {
|
||||
data: UserProfile;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// Typescript: TSEndpoint= path=/users/:uuid; name=updateUser; method=PUT; request=controllers.UpdateUserRequest; response=models.UserProfile
|
||||
// internal/user/routes.go Line: 19
|
||||
|
||||
export const updateUser = async (
|
||||
data: UpdateUserRequest,
|
||||
): Promise<{ data: UserProfile; error: Nullable<string> }> => {
|
||||
return (await api.PUT("/users/:uuid", data)) as {
|
||||
data: UserProfile;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// Typescript: TSEndpoint= path=/users/:uuid; name=deleteUser; method=DELETE; response=controllers.SimpleResponse
|
||||
// internal/user/routes.go Line: 22
|
||||
|
||||
export const deleteUser = async (
|
||||
uuid: string,
|
||||
): Promise<{ data: SimpleResponse; error: Nullable<string> }> => {
|
||||
return (await api.DELETE(`/users/${uuid}`)) as {
|
||||
data: SimpleResponse;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// Typescript: TSEndpoint= path=/auth/me; name=me; method=GET; response=models.UserShort
|
||||
// internal/user/routes.go Line: 25
|
||||
export const me = async (): Promise<{
|
||||
data: UserShort;
|
||||
error: Nullable<string>;
|
||||
}> => {
|
||||
return (await api.GET("/auth/me")) as {
|
||||
data: UserShort;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
export interface UpdateUserRequest {
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
roles: models.UserRoles;
|
||||
status: models.UserStatus;
|
||||
types: models.UserTypes;
|
||||
avatar: Nullable<string>;
|
||||
details: Nullable<models.UserDetailsShort>;
|
||||
preferences: Nullable<models.UserPreferencesShort>;
|
||||
}
|
||||
|
||||
//
|
||||
// package models
|
||||
//
|
||||
|
||||
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<UserDetailsShort>;
|
||||
preferences: Nullable<UserPreferencesShort>;
|
||||
avatar: Nullable<string>;
|
||||
}
|
||||
|
||||
export interface UserCreateInput {
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
roles: UserRoles;
|
||||
status: UserStatus;
|
||||
types: UserTypes;
|
||||
avatar: Nullable<string>;
|
||||
details: Nullable<UserDetailsShort>;
|
||||
preferences: Nullable<UserPreferencesShort>;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
//
|
||||
// package admin
|
||||
//
|
||||
|
||||
// Typescript: TSEndpoint= path=/admin/users/:uuid/block; name=blockUser; method=PUT; request=admin.BlockUserRequest; response=models.UserShort
|
||||
// internal/admin/routes.go Line: 16
|
||||
|
||||
export const blockUser = async (
|
||||
data: BlockUserRequest,
|
||||
): Promise<{ data: UserShort; error: Nullable<string> }> => {
|
||||
return (await api.PUT("/admin/users/:uuid/block", data)) as {
|
||||
data: UserShort;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// Typescript: TSEndpoint= path=/admin/users; name=listUsers; method=POST; request=admin.ListUsersRequest; response=models.[]UserShort
|
||||
// internal/admin/routes.go Line: 12
|
||||
|
||||
export const listUsers = async (
|
||||
data: ListUsersRequest,
|
||||
): Promise<{ data: UserShort[]; error: Nullable<string> }> => {
|
||||
return (await api.POST("/admin/users", data)) as {
|
||||
data: UserShort[];
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
export interface BlockUserRequest {
|
||||
action: string;
|
||||
}
|
||||
|
||||
export interface ListUsersRequest {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
//
|
||||
// package responses
|
||||
//
|
||||
|
||||
export interface SimpleResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
//
|
||||
// package systemUtils
|
||||
//
|
||||
|
||||
// Typescript: TSEndpoint= path=/health; name=health; method=GET; response=string
|
||||
// internal/systemUtils/routes.go Line: 34
|
||||
export const health = async (): Promise<{
|
||||
data: string;
|
||||
error: Nullable<string>;
|
||||
}> => {
|
||||
return (await api.GET("/health")) as {
|
||||
data: string;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// Typescript: TSEndpoint= path=/metrics; name=metrics; method=GET; response=string
|
||||
// internal/systemUtils/routes.go Line: 37
|
||||
export const metrics = async (): Promise<{
|
||||
data: string;
|
||||
error: Nullable<string>;
|
||||
}> => {
|
||||
return (await api.GET("/metrics")) as {
|
||||
data: string;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// Typescript: TSEndpoint= path=/maildebug; name=mailDebug; method=GET; response=routes.[]MailDebugItem
|
||||
// internal/systemUtils/routes.go Line: 48
|
||||
export const mailDebug = async (): Promise<{
|
||||
data: MailDebugItem[];
|
||||
error: Nullable<string>;
|
||||
}> => {
|
||||
return (await api.GET("/maildebug")) as {
|
||||
data: MailDebugItem[];
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
export interface MailDebugItem {
|
||||
name: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
//
|
||||
// package routes
|
||||
//
|
||||
|
||||
export interface FormRequest {
|
||||
req: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface FormResponse {
|
||||
test: string;
|
||||
}
|
||||
|
||||
//
|
||||
// package auth
|
||||
//
|
||||
|
||||
// Typescript: TSEndpoint= path=/auth/password/reset; name=resetPassword; method=POST; request=model.ResetPasswordRequest; response=controllers.SimpleResponse
|
||||
// internal/auth/routes.go Line: 32
|
||||
|
||||
export const resetPassword = async (
|
||||
data: ResetPasswordRequest,
|
||||
): Promise<{ data: SimpleResponse; error: Nullable<string> }> => {
|
||||
return (await api.POST("/auth/password/reset", data)) as {
|
||||
data: SimpleResponse;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// Typescript: TSEndpoint= path=/auth/password/valid; name=validToken; method=POST; request=string; response=controllers.SimpleResponse
|
||||
// internal/auth/routes.go Line: 35
|
||||
|
||||
export const validToken = async (
|
||||
data: string,
|
||||
): Promise<{ data: SimpleResponse; error: Nullable<string> }> => {
|
||||
return (await api.POST("/auth/password/valid", data)) as {
|
||||
data: SimpleResponse;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// Typescript: TSEndpoint= path=/auth/login; name=login; method=POST; request=model.LoginRequest; response=model.TokenPair
|
||||
// internal/auth/routes.go Line: 20
|
||||
|
||||
export const login = async (
|
||||
data: LoginRequest,
|
||||
): Promise<{ data: TokenPair; error: Nullable<string> }> => {
|
||||
return (await api.POST("/auth/login", data)) as {
|
||||
data: TokenPair;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// Typescript: TSEndpoint= path=/auth/refresh; name=refresh; method=POST; request=model.RefreshRequest; response=model.TokenPair
|
||||
// internal/auth/routes.go Line: 23
|
||||
|
||||
export const refresh = async (
|
||||
data: RefreshRequest,
|
||||
): Promise<{ data: TokenPair; error: Nullable<string> }> => {
|
||||
return (await api.POST("/auth/refresh", data)) as {
|
||||
data: TokenPair;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// Typescript: TSEndpoint= path=/auth/register; name=register; method=POST; request=models.UserCreateInput; response=models.UserShort
|
||||
// internal/auth/routes.go Line: 26
|
||||
|
||||
export const register = async (
|
||||
data: UserCreateInput,
|
||||
): Promise<{ data: UserShort; error: Nullable<string> }> => {
|
||||
return (await api.POST("/auth/register", data)) as {
|
||||
data: UserShort;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// Typescript: TSEndpoint= path=/auth/password/forgot; name=forgotPassword; method=POST; request=model.ForgotPasswordRequest; response=controllers.SimpleResponse
|
||||
// internal/auth/routes.go Line: 29
|
||||
|
||||
export const forgotPassword = async (
|
||||
data: ForgotPasswordRequest,
|
||||
): Promise<{ data: SimpleResponse; error: Nullable<string> }> => {
|
||||
return (await api.POST("/auth/password/forgot", data)) as {
|
||||
data: SimpleResponse;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
export interface TokenPair {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
export interface LoginRequest {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface ForgotPasswordRequest {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface RefreshRequest {
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
export interface ResetPasswordRequest {
|
||||
token: string;
|
||||
password: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export interface SimpleResponse {
|
||||
message: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { api } from "./api.ts";
|
||||
import { Nullable } from "./apiTypes.ts";
|
||||
|
||||
// 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<string>;
|
||||
}> => {
|
||||
return (await api.GET("/metrics")) as {
|
||||
data: string;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// 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<string>;
|
||||
}> => {
|
||||
return (await api.GET("/maildebug")) as {
|
||||
data: MailDebugItem[];
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// 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<string>;
|
||||
}> => {
|
||||
return (await api.GET("/health")) as {
|
||||
data: string;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
export interface MailDebugItem {
|
||||
name: string;
|
||||
content: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export interface TokenPair {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
import { api } from "./api.ts";
|
||||
import { Nullable } from "./apiTypes.ts";
|
||||
import * as responses from "./responses.ts";
|
||||
import * 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
|
||||
export const refresh = async (
|
||||
data: RefreshRequest,
|
||||
): Promise<{ data: tokens.TokenPair; error: Nullable<string> }> => {
|
||||
return (await api.POST("/auth/refresh", data)) as {
|
||||
data: tokens.TokenPair;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// 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<string> }> => {
|
||||
return (await api.POST("/auth/password/reset", data)) as {
|
||||
data: responses.SimpleResponse;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// 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<string> }> => {
|
||||
return (await api.GET(`/users/${uuid}`)) as {
|
||||
data: User;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// 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<string>;
|
||||
}> => {
|
||||
return (await api.GET("/auth/me")) as { data: User; error: Nullable<string> };
|
||||
};
|
||||
|
||||
// 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<string> }> => {
|
||||
return (await api.POST("/auth/register", data)) as {
|
||||
data: User;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// 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<string> }> => {
|
||||
return (await api.POST("/auth/password/forgot", data)) as {
|
||||
data: responses.SimpleResponse;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// 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<string> }> => {
|
||||
return (await api.PUT("/users/:uuid", data)) as {
|
||||
data: User;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// 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<string> }> => {
|
||||
return (await api.DELETE(`/users/${uuid}`)) as {
|
||||
data: responses.SimpleResponse;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// 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<string> }> => {
|
||||
return (await api.POST("/auth/login", data)) as {
|
||||
data: tokens.TokenPair;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// 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<string> }> => {
|
||||
return (await api.POST("/auth/password/valid", data)) as {
|
||||
data: responses.SimpleResponse;
|
||||
error: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
||||
// Typescript: TSEndpoint= path=/users; name=createUser; method=POST; request=users.UserCreateInput; response=users.User
|
||||
// internal/user/routes.go Line: 30
|
||||
export const createUser = async (
|
||||
data: UserCreateInput,
|
||||
): Promise<{ data: User; error: Nullable<string> }> => {
|
||||
return (await api.POST("/users", data)) as {
|
||||
data: User;
|
||||
error: Nullable<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<UserDetails>;
|
||||
preferences: Nullable<UserPreferences>;
|
||||
avatar: Nullable<string>;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface UserCreateInput {
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
roles: UserRoles;
|
||||
status: UserStatus;
|
||||
types: UserTypes;
|
||||
avatar: Nullable<string>;
|
||||
details: Nullable<UserDetails>;
|
||||
preferences: Nullable<UserPreferences>;
|
||||
}
|
||||
|
||||
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 ForgotPasswordRequest {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface UpdateUserRequest {
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
roles: UserRoles;
|
||||
status: UserStatus;
|
||||
types: UserTypes;
|
||||
avatar: Nullable<string>;
|
||||
details: Nullable<UserDetails>;
|
||||
preferences: Nullable<UserPreferences>;
|
||||
}
|
||||
|
||||
export interface LoginRequest {
|
||||
username: string;
|
||||
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: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface UserProfile {
|
||||
id: number;
|
||||
email: string;
|
||||
name: string;
|
||||
roles: UserRoles;
|
||||
types: UserTypes;
|
||||
status: UserStatus;
|
||||
activatedAt: Date;
|
||||
uuid: string;
|
||||
details: Nullable<UserDetails>;
|
||||
preferences: Nullable<UserPreferences>;
|
||||
avatar: Nullable<string>;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface ResetPasswordRequest {
|
||||
token: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export type UserRoles = string[];
|
||||
|
||||
export type UserTypes = string[];
|
||||
|
||||
export type UserStatus = (typeof EnumUserStatus)[keyof typeof EnumUserStatus];
|
||||
|
||||
export const EnumUserStatus = {
|
||||
UserStatusPending: "pending",
|
||||
UserStatusActive: "active",
|
||||
UserStatusDisabled: "disabled",
|
||||
} as const;
|
||||
|
|
@ -62,7 +62,7 @@ func main() {
|
|||
IdleTimeout: time.Duration(cfg.IdleTimeoutSeconds) * time.Second,
|
||||
ErrorHandler: func(c fiber.Ctx, err error) error {
|
||||
code := fiber.StatusInternalServerError
|
||||
msg := "internal server error"
|
||||
msg := "internal server error: " + err.Error()
|
||||
if e, ok := err.(*fiber.Error); ok {
|
||||
code = e.Code
|
||||
msg = e.Message
|
||||
|
|
@ -70,7 +70,7 @@ func main() {
|
|||
reqID := requestid.FromContext(c)
|
||||
log.Printf("error request_id=%s status=%d method=%s path=%s ip=%s ua=%q err=%v", reqID, code, c.Method(), c.Path(), c.IP(), c.Get("User-Agent"), err)
|
||||
if code >= 500 {
|
||||
msg = "internal server error"
|
||||
msg = "internal server error: " + err.Error()
|
||||
}
|
||||
return c.Status(code).JSON(fiber.Map{"data": nil, "error": msg})
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ import (
|
|||
func RegisterAdminRoutes(app *fiber.App) {
|
||||
adminController := NewAdminController()
|
||||
|
||||
// Typescript: TSEndpoint= path=/admin/users; name=listUsers; method=POST; request=admin.ListUsersRequest; response=models.[]UserShort
|
||||
// Typescript: TSEndpoint= path=/admin/users; name=listUsers; method=POST; request=admin.ListUsersRequest; response=users.[]User
|
||||
app.Post("/admin/users", adminController.ListUsers)
|
||||
roles.RegisterEndpoint("POST/admin/users", int(roles.AdminPermission))
|
||||
|
||||
// Typescript: TSEndpoint= path=/admin/users/:uuid/block; name=blockUser; method=PUT; request=admin.BlockUserRequest; response=models.UserShort
|
||||
// Typescript: TSEndpoint= path=/admin/users/:uuid/block; name=blockUser; method=PUT; request=admin.BlockUserRequest; response=users.User
|
||||
app.Put("/admin/users/:uuid/block", adminController.BlockUser)
|
||||
roles.RegisterEndpoint("PUT/admin/users/:uuid/block", int(roles.AdminPermission))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
ciao
|
||||
|
|
@ -31,6 +31,10 @@ func healthHandler(c fiber.Ctx) error {
|
|||
}
|
||||
|
||||
func RegisterSystemRoutes(app *fiber.App) {
|
||||
app.Get("/favicon.ico", func(c fiber.Ctx) error {
|
||||
return c.SendString("boo")
|
||||
})
|
||||
|
||||
// Typescript: TSEndpoint= path=/health; name=health; method=GET; response=string
|
||||
app.Get("/health", healthHandler)
|
||||
|
||||
|
|
@ -42,10 +46,11 @@ func RegisterSystemRoutes(app *fiber.App) {
|
|||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
c.Set(fiber.HeaderContentType, "text/plain")
|
||||
return c.SendString(ts)
|
||||
})
|
||||
|
||||
// Typescript: TSEndpoint= path=/maildebug; name=mailDebug; method=GET; response=routes.[]MailDebugItem
|
||||
// Typescript: TSEndpoint= path=/maildebug; name=mailDebug; method=GET; response=[]MailDebugItem
|
||||
app.Get("/maildebug", func(c fiber.Ctx) error {
|
||||
entries, err := os.ReadDir("data/mail-debug")
|
||||
if err != nil {
|
||||
|
|
@ -90,5 +95,6 @@ func RegisterSystemRoutes(app *fiber.App) {
|
|||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendFile(filepath.Join(spaDistPath, "index.html"))
|
||||
})
|
||||
|
||||
app.Use("/", static.New(spaDistPath))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
package tsgenerator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
tsrpc "server/pkg/ts-rpc"
|
||||
)
|
||||
|
||||
func TsGenerate() (string, error) {
|
||||
path := "GeneratedCode"
|
||||
|
||||
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
|
||||
err := os.Mkdir(path, os.ModePerm)
|
||||
if err != nil {
|
||||
|
|
@ -18,53 +18,26 @@ func TsGenerate() (string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
var tsSource = tsrpc.GetTSSource(tsrpc.TSConfig{Url: "http://localhost:3000", TsApi: nil, Path: "."})
|
||||
|
||||
formatted, err1 := formatJS(tsSource)
|
||||
if err1 != nil {
|
||||
return "", err1
|
||||
}
|
||||
|
||||
err := os.WriteFile("GeneratedCode/generatedTypescript.ts", []byte(formatted), 0644)
|
||||
d, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("write local generated typescript: %w", err)
|
||||
return "", fmt.Errorf("open GeneratedCode directory: %w", err)
|
||||
}
|
||||
|
||||
frontendAPIPath := os.Getenv("FRONTEND_API_PATH")
|
||||
if frontendAPIPath == "" {
|
||||
return "", errors.New("FRONTEND_API_PATH must be set")
|
||||
}
|
||||
|
||||
fmt.Printf("Saving generated TypeScript to %s\n", frontendAPIPath)
|
||||
|
||||
err = os.WriteFile(frontendAPIPath+"/api.ts", []byte(formatted), 0644)
|
||||
defer d.Close()
|
||||
names, err := d.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("write frontend api.ts: %w", err)
|
||||
return "", fmt.Errorf("read GeneratedCode directory: %w", err)
|
||||
}
|
||||
|
||||
return formatted, nil
|
||||
}
|
||||
|
||||
func formatJS(src string) (string, error) {
|
||||
// Se hai prettier globale:
|
||||
cmd := exec.Command("prettier", "--parser", "typescript", "--use-tabs", "false", "--tab-width", "2")
|
||||
|
||||
// Se hai prettier solo come devDependency:
|
||||
// cmd := exec.Command("npx", "prettier", "--parser", "typescript")
|
||||
|
||||
cmd.Stdin = bytes.NewBufferString(src)
|
||||
|
||||
var out bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
for _, name := range names {
|
||||
err = os.RemoveAll(filepath.Join(path, name))
|
||||
if err != nil {
|
||||
// Stampa anche lo stderr di Prettier, così vedi l’errore reale
|
||||
return "", fmt.Errorf("prettier error: %v\nstderr: %s", err, stderr.String())
|
||||
return "", fmt.Errorf("remove GeneratedCode directory content: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return out.String(), nil
|
||||
|
||||
err = tsrpc.GetTSSource()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get ts source: %w", err)
|
||||
}
|
||||
|
||||
return "Generation OK \n\n", nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ import (
|
|||
// Typescript: type
|
||||
type UserRoles []string
|
||||
|
||||
// Typescript: interface
|
||||
type User struct {
|
||||
ID int `json:"id" gorm:"primaryKey"`
|
||||
Email string `json:"email" gorm:"uniqueIndex;size:255"`
|
||||
Name string `json:"name" gorm:"size:255"`
|
||||
Password string `json:"password" gorm:"size:255"`
|
||||
Password string `json:"-" gorm:"size:255"`
|
||||
Roles UserRoles `json:"roles" gorm:"type:text;serializer:json"`
|
||||
Types UserTypes `json:"types" gorm:"type:text;serializer:json"`
|
||||
Status UserStatus `json:"status" gorm:"type:text;default:'pending'"`
|
||||
|
|
@ -48,7 +49,7 @@ type UserCreateInput struct {
|
|||
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"`
|
||||
|
|
@ -89,6 +90,8 @@ 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"`
|
||||
|
|
@ -104,9 +107,9 @@ type UserPreferences struct {
|
|||
UpdatedAt time.Time `json:"updatedAt" ts:"type=Date"`
|
||||
}
|
||||
|
||||
// UserPreferences holds per-user settings stored as JSON.
|
||||
|
||||
// UserDetails holds optional profile data.
|
||||
|
||||
// Typescript: interface
|
||||
type UserDetails struct {
|
||||
ID int `json:"id" gorm:"primaryKey"`
|
||||
UserID int `json:"userId" gorm:"index"`
|
||||
|
|
|
|||
|
|
@ -24,13 +24,13 @@ func RegisterUserRoutes(app *fiber.App) {
|
|||
|
||||
userController := NewUserController(tockenService)
|
||||
|
||||
// Typescript: TSEndpoint= path=/users/:uuid; name=getUser; method=GET; response=users.UserProfile
|
||||
// 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.UserProfile
|
||||
// Typescript: TSEndpoint= path=/users; name=createUser; method=POST; request=users.UserCreateInput; response=users.User
|
||||
app.Post("/users", tockenService.Middleware(), userController.CreateUser)
|
||||
|
||||
// Typescript: TSEndpoint= path=/users/:uuid; name=updateUser; method=PUT; request=users.UpdateUserRequest; response=users.UserProfile
|
||||
// Typescript: TSEndpoint= path=/users/:uuid; name=updateUser; method=PUT; request=users.UpdateUserRequest; response=users.User
|
||||
app.Put("/users/:uuid", tockenService.Middleware(), userController.UpdateUser)
|
||||
|
||||
// Typescript: TSEndpoint= path=/users/:uuid; name=deleteUser; method=DELETE; response=responses.SimpleResponse
|
||||
|
|
@ -43,7 +43,7 @@ func RegisterUserRoutes(app *fiber.App) {
|
|||
// Typescript: TSEndpoint= path=/auth/login; name=login; method=POST; request=users.LoginRequest; response=tokens.TokenPair
|
||||
app.Post("/auth/login", authRateLimiter, userController.Login)
|
||||
|
||||
// Typescript: TSEndpoint= path=/auth/refresh; name=refresh; method=POST; request=tokens.RefreshRequest; response=tokens.TokenPair
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
package tsrpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
type TSFiles map[string]string
|
||||
|
||||
func (t *TSFiles) Add(name string, source string) {
|
||||
if t == nil || *t == nil {
|
||||
*t = make(map[string]string)
|
||||
}
|
||||
if (*t)[name] == "" {
|
||||
(*t)[name] = source
|
||||
} else {
|
||||
(*t)[name] += source
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TSFiles) Get(name string) (string, bool) {
|
||||
if t == nil || *t == nil {
|
||||
return "", false
|
||||
}
|
||||
s, ok := (*t)[name]
|
||||
return s, ok
|
||||
}
|
||||
|
||||
func (t *TSFiles) Save() error {
|
||||
if t == nil || *t == nil {
|
||||
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 {
|
||||
return fmt.Errorf("format JS: %w", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(fmt.Sprintf("GeneratedCode/%s", name), []byte(formatted), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write local generated typescript: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatJS(src string) (string, error) {
|
||||
// Se hai prettier globale:
|
||||
cmd := exec.Command("prettier", "--parser", "typescript", "--use-tabs", "false", "--tab-width", "2")
|
||||
|
||||
// Se hai prettier solo come devDependency:
|
||||
// cmd := exec.Command("npx", "prettier", "--parser", "typescript")
|
||||
|
||||
cmd.Stdin = bytes.NewBufferString(src)
|
||||
|
||||
var out bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
// Stampa anche lo stderr di Prettier, così vedi l’errore reale
|
||||
return "", fmt.Errorf("prettier error: %v\nstderr: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
return out.String(), nil
|
||||
}
|
||||
|
|
@ -3,6 +3,14 @@
|
|||
|
||||
package tsrpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
const TsApiTemplate = `
|
||||
//
|
||||
// Typescript API generated from gofiber backend
|
||||
|
|
@ -248,5 +256,39 @@ export default class Api {
|
|||
}
|
||||
}
|
||||
|
||||
const api = new Api('{{ .APIURL }}');
|
||||
export const api = new Api('{{ .APIURL }}');
|
||||
`
|
||||
|
||||
type templateData struct {
|
||||
APIURL string
|
||||
CreatedOn time.Time
|
||||
}
|
||||
|
||||
func GetApiFile() (string, error) {
|
||||
apiurl := ""
|
||||
if value, exists := os.LookupEnv("TS_GENERATOR_URL"); exists {
|
||||
apiurl = value
|
||||
} else {
|
||||
return "", fmt.Errorf("TS Generator URL environment variable not set")
|
||||
}
|
||||
|
||||
// API file
|
||||
tsApiSource := ""
|
||||
data := TsApiTemplate
|
||||
|
||||
tpl, err := template.New("tsRpc").Parse(data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var templateData = templateData{
|
||||
APIURL: apiurl,
|
||||
CreatedOn: time.Now(),
|
||||
}
|
||||
var result bytes.Buffer
|
||||
err = tpl.Execute(&result, templateData)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tsApiSource += result.String()
|
||||
return tsApiSource, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// exportable typescript generated from golang
|
||||
// able typescript generated from golang
|
||||
// Copyright (C) 2022 Fabio Prada
|
||||
|
||||
package tsrpc
|
||||
|
|
@ -6,6 +6,7 @@ package tsrpc
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
|
@ -16,11 +17,10 @@ type TSEndpoint struct {
|
|||
Method string
|
||||
Request string
|
||||
Response string
|
||||
RequestTs string
|
||||
ResponseTs string
|
||||
Source string
|
||||
File string
|
||||
Line int
|
||||
Imports map[string][]string
|
||||
}
|
||||
|
||||
func ParseEndpoint(source string, file string, line int) TSEndpoint {
|
||||
|
|
@ -32,6 +32,7 @@ func ParseEndpoint(source string, file string, line int) TSEndpoint {
|
|||
endpoint.Source = strings.Trim(source, "\t")
|
||||
endpoint.File = file
|
||||
endpoint.Line = line
|
||||
endpoint.Imports = make(map[string][]string)
|
||||
|
||||
for _, v := range a {
|
||||
t := strings.Split(v, "=")
|
||||
|
|
@ -83,84 +84,116 @@ 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 {
|
||||
e.RequestTs = a[1]
|
||||
kind = a[1]
|
||||
if strings.HasPrefix(a[1], "[]") {
|
||||
a[1] = strings.TrimPrefix(a[1], "[]")
|
||||
e.RequestTs = fmt.Sprintf("%s[]", a[1])
|
||||
kind = fmt.Sprintf("%s[]", strings.TrimPrefix(a[1], "[]"))
|
||||
}
|
||||
// nullable pointer
|
||||
if strings.HasPrefix(a[1], "*") {
|
||||
a[1] = strings.TrimPrefix(a[1], "*")
|
||||
e.RequestTs = fmt.Sprintf("Nullable<%s>", a[1])
|
||||
kind = fmt.Sprintf("Nullable<%s>", strings.TrimPrefix(a[1], "*"))
|
||||
}
|
||||
e.Request = fmt.Sprintf("%s.%s", a[0], a[1])
|
||||
|
||||
if !info.find(a[0], a[1]) {
|
||||
exitOnError(fmt.Errorf("endpoint request not found: %s AT %s Line: %d ", e.Request, e.File, e.Line))
|
||||
}
|
||||
if a[0] == p {
|
||||
e.Request = fmt.Sprintf("%s", kind)
|
||||
} else {
|
||||
e.Request = fmt.Sprintf("%s.%s", kind, a[1])
|
||||
|
||||
if _, ok := e.Imports[a[0]]; !ok {
|
||||
e.Imports[a[0]] = []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], "[]*"))
|
||||
}
|
||||
}
|
||||
info.setTypescript(a[0], a[1], true)
|
||||
}
|
||||
if len(a) == 1 {
|
||||
e.RequestTs = a[0]
|
||||
kind = a[0]
|
||||
if strings.HasPrefix(a[0], "[]") {
|
||||
a[0] = strings.TrimPrefix(a[0], "[]")
|
||||
e.RequestTs = fmt.Sprintf("%s[]", a[0])
|
||||
kind = fmt.Sprintf("%s[]", strings.TrimPrefix(a[0], "[]"))
|
||||
}
|
||||
// nullable pointer
|
||||
if strings.HasPrefix(a[0], "*") {
|
||||
a[0] = strings.TrimPrefix(a[0], "*")
|
||||
e.RequestTs = fmt.Sprintf("Nullable<%s>", a[0])
|
||||
kind = fmt.Sprintf("Nullable<%s>", strings.TrimPrefix(a[0], "*"))
|
||||
}
|
||||
e.Request = a[0]
|
||||
e.Request = fmt.Sprintf("%s", kind)
|
||||
}
|
||||
// if request is a struct, set it as typescript to generate the ts struct
|
||||
}
|
||||
kind = ""
|
||||
a = strings.Split(e.Response, ".")
|
||||
if len(a) == 2 {
|
||||
if !info.find(a[0], a[1]) {
|
||||
exitOnError(fmt.Errorf("endpoint response not found: %s AT %s Line: %d ", e.Request, e.File, e.Line))
|
||||
}
|
||||
e.ResponseTs = a[1]
|
||||
kind = a[1]
|
||||
if strings.HasPrefix(a[1], "[]") {
|
||||
a[1] = strings.TrimPrefix(a[1], "[]")
|
||||
e.ResponseTs = fmt.Sprintf("%s[]", a[1])
|
||||
kind = fmt.Sprintf("%s[]", strings.TrimPrefix(a[1], "[]"))
|
||||
}
|
||||
// nullable pointer
|
||||
if strings.HasPrefix(a[1], "*") {
|
||||
a[1] = strings.TrimPrefix(a[1], "*")
|
||||
e.ResponseTs = fmt.Sprintf("Nullable<%s>", a[1])
|
||||
kind = fmt.Sprintf("Nullable<%s>", strings.TrimPrefix(a[1], "*"))
|
||||
}
|
||||
e.Response = fmt.Sprintf("%s.%s", a[0], a[1])
|
||||
|
||||
if !info.find(a[0], a[1]) {
|
||||
exitOnError(fmt.Errorf("endpoint response not found: %s AT %s Line: %d ", e.Response, e.File, e.Line))
|
||||
}
|
||||
|
||||
if a[0] == p {
|
||||
e.Response = fmt.Sprintf("%s", kind)
|
||||
} else {
|
||||
e.Response = fmt.Sprintf("%s.%s", a[0], kind)
|
||||
if _, ok := e.Imports[a[0]]; !ok {
|
||||
e.Imports[a[0]] = []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])
|
||||
}
|
||||
}
|
||||
info.setTypescript(a[0], a[1], true)
|
||||
}
|
||||
if len(a) == 1 {
|
||||
e.ResponseTs = a[0]
|
||||
if strings.HasPrefix(a[0], "[]") {
|
||||
a[0] = strings.TrimPrefix(a[0], "[]")
|
||||
e.ResponseTs = fmt.Sprintf("%s[]", a[0])
|
||||
kind = fmt.Sprintf("%s[]", strings.TrimPrefix(a[0], "[]"))
|
||||
}
|
||||
// nullable pointer
|
||||
if strings.HasPrefix(a[0], "*") {
|
||||
a[0] = strings.TrimPrefix(a[0], "*")
|
||||
e.ResponseTs = fmt.Sprintf("Nullable<%s>", a[0])
|
||||
kind = fmt.Sprintf("Nullable<%s>", strings.TrimPrefix(a[0], "*"))
|
||||
}
|
||||
if IsNativeType(a[0]) {
|
||||
e.Response = fmt.Sprintf("%s%s", a[0], kind)
|
||||
return
|
||||
}
|
||||
e.Response = fmt.Sprintf("%s", kind)
|
||||
// if request is a struct, set it as typescript to generate the ts struct
|
||||
}
|
||||
e.Response = a[0]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (e *TSEndpoint) ToTs(pkg string) string {
|
||||
func (e *TSEndpoint) ToTs() string {
|
||||
data := tplData{E: e, Path: e.Path, Params: []string{}}
|
||||
tpl := `
|
||||
{{ .E.Source }}
|
||||
// {{ .E.File }} Line: {{ .E.Line }}
|
||||
{{if eq .E.Method "GET"}}export const {{ .E.Name}} = async ({{range $v := .Params}}{{$v}}{{end}}):Promise<{ data:{{.E.ResponseTs}}; error: Nullable<string> }> => {
|
||||
return await api.GET({{ .Path}}) as { data: {{ .E.ResponseTs}}; error: Nullable<string> };
|
||||
}{{end}}
|
||||
{{if eq .E.Method "DELETE"}}export const {{ .E.Name}} = async ({{range $v := .Params}}{{$v}}{{end}}):Promise<{ data:{{.E.ResponseTs}}; error: Nullable<string> }> => {
|
||||
return await api.DELETE({{ .Path}}) as { data: {{ .E.ResponseTs}}; error: Nullable<string> };
|
||||
}{{end}}
|
||||
{{if eq .E.Method "PUT"}}export const {{ .E.Name}} = async (data: {{ .E.RequestTs}}):Promise<{ data:{{.E.ResponseTs}}; error: Nullable<string> }> => {
|
||||
return await api.PUT("{{ .Path}}", data) as { data: {{ .E.ResponseTs}}; error: Nullable<string> };
|
||||
}{{end}}
|
||||
{{if eq .E.Method "POST"}}export const {{ .E.Name}} = async (data: {{ .E.RequestTs}}):Promise<{ data:{{.E.ResponseTs}}; error: Nullable<string> }> => {
|
||||
return await api.POST("{{ .Path}}", data) as { data: {{ .E.ResponseTs}}; error: Nullable<string> };
|
||||
{{if eq .E.Method "GET"}}export const {{ .E.Name}} = async ({{range $v := .Params}}{{$v}}{{end}}):Promise<{ data:{{.E.Response}}; error: Nullable<string> }> => {
|
||||
return await api.GET({{ .Path}}) as { data: {{ .E.Response}}; error: Nullable<string> };
|
||||
}{{end}}{{if eq .E.Method "DELETE"}}export const {{ .E.Name}} = async ({{range $v := .Params}}{{$v}}{{end}}):Promise<{ data:{{.E.Response}}; error: Nullable<string> }> => {
|
||||
return await api.DELETE({{ .Path}}) as { data: {{ .E.Response}}; error: Nullable<string> };
|
||||
}{{end}}{{if eq .E.Method "PUT"}}export const {{ .E.Name}} = async (data: {{ .E.Request}}):Promise<{ data:{{.E.Response}}; error: Nullable<string> }> => {
|
||||
return await api.PUT("{{ .Path}}", data) as { data: {{ .E.Response}}; error: Nullable<string> };
|
||||
}{{end}}{{if eq .E.Method "POST"}}export const {{ .E.Name}} = async (data: {{ .E.Request}}):Promise<{ data:{{.E.Response}}; error: Nullable<string> }> => {
|
||||
return await api.POST("{{ .Path}}", data) as { data: {{ .E.Response }}; error: Nullable<string> };
|
||||
}{{end}}`
|
||||
|
||||
if e.Method == "GET" || e.Method == "DELETE" {
|
||||
|
|
@ -177,11 +210,12 @@ func (e *TSEndpoint) ToTs(pkg string) string {
|
|||
c = ", "
|
||||
}
|
||||
|
||||
if prefix == ":" {
|
||||
switch prefix {
|
||||
case ":":
|
||||
f = true
|
||||
data.Params = append(data.Params, fmt.Sprintf("%s%s: string", c, v[1:]))
|
||||
data.Path = strings.Replace(data.Path, v, fmt.Sprintf("${%s}", v[1:]), 1)
|
||||
} else if prefix == "*" {
|
||||
case "*":
|
||||
f = true
|
||||
data.Params = append(data.Params, fmt.Sprintf("%s%s: Nullable<string>", c, v[1:]))
|
||||
data.Path = strings.Replace(data.Path, v, fmt.Sprintf("${%s}", v[1:]), 1)
|
||||
|
|
@ -203,6 +237,5 @@ func (e *TSEndpoint) ToTs(pkg string) string {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ type TSInfoPakage struct {
|
|||
consts map[string]TSConst
|
||||
decs map[string]TSDec
|
||||
endpoints map[string]TSEndpoint
|
||||
imports map[string][]string
|
||||
isUsed bool
|
||||
}
|
||||
|
||||
type TSDec struct {
|
||||
|
|
@ -349,8 +351,11 @@ func (i *TSInfo) Populate(path string) {
|
|||
if _, ok := i.Packages[pkg].endpoints[e.Name]; ok {
|
||||
exitOnError(fmt.Errorf("enpoint name %s allready in use: %s", e.Name, l.Source))
|
||||
}
|
||||
|
||||
i.Packages[pkg].endpoints[e.Name] = e
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -388,9 +393,6 @@ func (i *TSInfo) Populate(path string) {
|
|||
func (ts *TSInfo) findAvailableStruct(n string) (TSStruct, bool) {
|
||||
a := strings.Split(n, ".")
|
||||
if len(a) == 2 {
|
||||
if n == "[]MailDebugItem" {
|
||||
fmt.Printf("MailDebugItem found\n")
|
||||
}
|
||||
a[1] = strings.TrimPrefix(a[1], "[]")
|
||||
a[1] = strings.TrimPrefix(a[1], "*")
|
||||
if _, ok := ts.Packages[a[0]]; ok {
|
||||
|
|
@ -399,6 +401,15 @@ func (ts *TSInfo) findAvailableStruct(n string) (TSStruct, bool) {
|
|||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,12 @@ type TSModule struct {
|
|||
Consts map[string]string
|
||||
GTypes map[string]string
|
||||
Endpoints map[string]string
|
||||
Imports map[string][]string
|
||||
Source string
|
||||
}
|
||||
|
||||
type TSOutputSources []string
|
||||
|
||||
type TSSouces struct {
|
||||
Pakages map[string]TSModule
|
||||
Errors []string
|
||||
|
|
@ -32,6 +36,7 @@ func (ts *TSSouces) ensurePackage(p string) {
|
|||
Consts: make(map[string]string),
|
||||
GTypes: make(map[string]string),
|
||||
Endpoints: make(map[string]string),
|
||||
Imports: make(map[string][]string),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -214,9 +219,11 @@ func (ts *TSSouces) AddDependencies(info TSInfo, p string, s string, dependencie
|
|||
func (ts *TSSouces) Populate(info TSInfo) {
|
||||
ts.Pakages = make(map[string]TSModule)
|
||||
ts.Errors = []string{}
|
||||
for p, _ := range info.Packages {
|
||||
for p := range info.Packages {
|
||||
ts.ensurePackage(p)
|
||||
|
||||
ts.Errors = append(ts.Errors, fmt.Sprintf("Process pakage %s\n", p))
|
||||
|
||||
for _, st := range info.Packages[p].structs {
|
||||
if st.Typescript {
|
||||
if len(st.Fields) == 0 {
|
||||
|
|
@ -228,6 +235,8 @@ func (ts *TSSouces) Populate(info TSInfo) {
|
|||
}
|
||||
ts.Pakages[p].Structs[st.Name] = s
|
||||
ts.AddDependencies(info, p, st.Name, dependencies)
|
||||
|
||||
// check depenndecies
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -248,7 +257,7 @@ func (ts *TSSouces) Populate(info TSInfo) {
|
|||
ts.Pakages[p].Types[t.Name] = fmt.Sprintf("export type %s = %s\n", t.Name, t.TsType)
|
||||
}
|
||||
}
|
||||
if len(info.Packages[p].endpoints) > 0 {
|
||||
|
||||
for _, e := range info.Packages[p].endpoints {
|
||||
|
||||
/* if e.Request != "" {
|
||||
|
|
@ -274,12 +283,30 @@ func (ts *TSSouces) Populate(info TSInfo) {
|
|||
}
|
||||
|
||||
} */
|
||||
responseAndRequest := ""
|
||||
|
||||
if e.Request != "" {
|
||||
responseAndRequest += fmt.Sprintf(" request: %s ", e.Request)
|
||||
}
|
||||
if e.Response != "" {
|
||||
responseAndRequest += fmt.Sprintf(" response: %s ", e.Response)
|
||||
}
|
||||
|
||||
// TSReport += fmt.Sprintf("verify %s %s%s %s\n", e.Name, e.Method, e.Path, responseAndRequest)
|
||||
|
||||
e.VerifyTypes(info, p)
|
||||
pkg := ts.Pakages[p]
|
||||
for k, v := range e.Imports {
|
||||
if _, ok := pkg.Imports[k]; !ok {
|
||||
pkg.Imports[k] = v
|
||||
}
|
||||
}
|
||||
pkg.Endpoints[e.Name] = e.ToTs()
|
||||
ts.Pakages[p] = pkg
|
||||
if p == "users" {
|
||||
fmt.Printf("endpoint %s imports: %v\n", e.Name, pkg.Imports)
|
||||
}
|
||||
|
||||
endpoint := e.ToTs(p)
|
||||
ts.Pakages[p].Endpoints[e.Name] = endpoint
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,34 +4,29 @@
|
|||
package tsrpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"fmt"
|
||||
"os"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
// configuration
|
||||
|
||||
type TSConfig struct {
|
||||
Url string
|
||||
TsApi *string
|
||||
Path string
|
||||
var TSReport = ""
|
||||
|
||||
var tsFiles = TSFiles{}
|
||||
|
||||
func GetTSSource() error {
|
||||
path := ""
|
||||
if value, exists := os.LookupEnv("TS_GENERATOR_PATH"); exists {
|
||||
path = value
|
||||
} else {
|
||||
return fmt.Errorf("TS Generator PATH environment variable not set")
|
||||
}
|
||||
|
||||
type tsTemplateData struct {
|
||||
APIURL string
|
||||
CreatedOn time.Time
|
||||
}
|
||||
|
||||
var tsConfig TSConfig
|
||||
|
||||
func GetTSSource(config TSConfig) string {
|
||||
tsConfig = config
|
||||
var tsInfoData = TSInfo{}
|
||||
var tsSourcesData = TSSouces{}
|
||||
tsInfoData.Populate(tsConfig.Path)
|
||||
tsInfoData.Populate(path)
|
||||
tsInfoData.TestEndpoints()
|
||||
tsSourcesData.Populate(tsInfoData)
|
||||
|
||||
|
|
@ -43,43 +38,33 @@ func GetTSSource(config TSConfig) string {
|
|||
exitOnError(fmt.Errorf("some errors...\n %s", err))
|
||||
}
|
||||
|
||||
tsSource := ""
|
||||
data := ""
|
||||
if tsConfig.TsApi == nil {
|
||||
data = TsApiTemplate
|
||||
} else {
|
||||
d, err := os.ReadFile(*tsConfig.TsApi)
|
||||
// api file
|
||||
tsApi, err := GetApiFile()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
data = string(d)
|
||||
exitOnError(fmt.Errorf("some errors...\n %s", err))
|
||||
}
|
||||
tsFiles.Add("api.ts", tsApi)
|
||||
|
||||
t, err := template.New("tsRpc").Parse(data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var templateData = tsTemplateData{
|
||||
APIURL: config.Url,
|
||||
CreatedOn: time.Now(),
|
||||
}
|
||||
var result bytes.Buffer
|
||||
err = t.Execute(&result, templateData)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tsSource += result.String()
|
||||
|
||||
tsSource += fmt.Sprintln("\n// Global Declarations ")
|
||||
// index file
|
||||
tsApiDeclarations := []string{}
|
||||
tsIndexSource := ""
|
||||
tsIndexSource += fmt.Sprintln("\n// API Declarations ")
|
||||
for p := range tsSourcesData.Pakages {
|
||||
for _, v1 := range tsSourcesData.Pakages[p].GTypes {
|
||||
tsSource += fmt.Sprintln(v1)
|
||||
tsIndexSource += fmt.Sprintln(v1)
|
||||
}
|
||||
for _, v1 := range tsInfoData.Packages[p].decs {
|
||||
tsApiDeclarations = append(tsApiDeclarations, v1.Name[:strings.Index(v1.Name, "<")])
|
||||
}
|
||||
}
|
||||
|
||||
tsFiles.Add("apiTypes.ts", tsIndexSource)
|
||||
|
||||
for p := range tsSourcesData.Pakages {
|
||||
if p == "users" {
|
||||
fmt.Println("users package")
|
||||
}
|
||||
source := ""
|
||||
head := fmt.Sprintf("\n//\n// package %s\n//\n", p)
|
||||
for _, v1 := range tsSourcesData.Pakages[p].Endpoints {
|
||||
source += fmt.Sprintln(v1)
|
||||
}
|
||||
|
|
@ -95,10 +80,59 @@ func GetTSSource(config TSConfig) string {
|
|||
for _, v1 := range tsSourcesData.Pakages[p].Consts {
|
||||
source += fmt.Sprintln(v1)
|
||||
}
|
||||
|
||||
if len(source) > 0 {
|
||||
tsSource += head + source + "\n\n"
|
||||
tmp := ""
|
||||
|
||||
found := false
|
||||
for _, sentence := range strings.Split(source, "\n") {
|
||||
if strings.Contains(sentence, "api.") {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return tsSource
|
||||
if found {
|
||||
tmp += "import { api } from './api.ts'\n"
|
||||
}
|
||||
|
||||
if len(tsApiDeclarations) > 0 {
|
||||
decs := []string{}
|
||||
|
||||
for _, d := range tsApiDeclarations {
|
||||
found := false
|
||||
for _, sentence := range strings.Split(source, "\n") {
|
||||
if strings.Contains(sentence, d) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if found {
|
||||
decs = append(decs, d)
|
||||
}
|
||||
}
|
||||
if len(decs) > 0 {
|
||||
TSApiDeclarations := "import { "
|
||||
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"
|
||||
}
|
||||
tmp += imports
|
||||
tmp += source
|
||||
tsFiles.Add(p+".ts", tmp)
|
||||
}
|
||||
}
|
||||
|
||||
err = tsFiles.Save()
|
||||
if err != nil {
|
||||
fmt.Printf("save ts files: %s\n", err)
|
||||
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue