Skip to content

Commit

Permalink
Merge pull request #21 from brunotot/file-structure-refactor-frontend
Browse files Browse the repository at this point in the history
chore: file structure refactor for app-vite-react
  • Loading branch information
brunotot authored Sep 15, 2024
2 parents 1c1d61f + a024cdd commit 43f9d87
Show file tree
Hide file tree
Showing 151 changed files with 2,057 additions and 1,755 deletions.
17 changes: 10 additions & 7 deletions packages/mern-sample-app/app-node-express/src/ExpressApp.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import express from "express";

import * as utils from "@org/lib-commons";
import * as apiClientUtils from "@org/lib-api-client";
import * as tsRest from "@org/app-node-express/lib/@ts-rest";
import * as bottleJs from "@org/app-node-express/lib/bottlejs";
import * as mongodb from "@org/app-node-express/lib/mongodb";

import { log } from "@org/app-node-express/logger";
import { env } from "@org/app-node-express/env";

type Class = new () => utils.TODO;

export type ExpressAppConfig = Partial<{
middleware: tsRest.RouteMiddlewareFactory[];
modules: Record<string, new () => utils.TODO>;
modules: Record<string, Class>;
}>;

export class ExpressApp {
Expand All @@ -19,10 +22,10 @@ export class ExpressApp {
public readonly url: string;
public readonly keycloakUrl?: string;
public readonly middleware: tsRest.RouteMiddlewareFactory[];
public readonly modules: Record<string, new () => utils.TODO>;
public readonly modules: Record<string, Class>;

#mongoClient: mongodb.MongoClient;
#mockModules: Record<string, new () => utils.TODO>;
#mockModules: Record<string, Class>;

constructor(config: ExpressAppConfig = {}) {
this.middleware = config.middleware ?? [];
Expand All @@ -45,7 +48,7 @@ export class ExpressApp {
return `${Math.round((process.memoryUsage().heapUsed / 1024 / 1024) * 100) / 100} MB`;
}

public async init(mocks: Record<string, new () => utils.TODO> = {}): Promise<void> {
public async init(mocks: Record<string, Class> = {}): Promise<void> {
log.info("Initializing Swagger");
this.#initializeSwagger();
log.info("Initializing IoC container");
Expand Down Expand Up @@ -88,10 +91,10 @@ export class ExpressApp {
await this.#mongoClient.connect();
}

#initializeIoc(mocks: Record<string, new () => utils.TODO>) {
#initializeIoc(mocks: Record<string, Class>) {
this.#mockModules = mocks;
const modules = this.modules;
const localModules: Record<string, new () => utils.TODO> = {};
const localModules: Record<string, Class> = {};
Object.entries(modules).forEach(([key, value]) => (localModules[key.toLowerCase()] = value));
Object.entries(mocks).forEach(([key, value]) => (localModules[key.toLowerCase()] = value));
bottleJs.iocRegistry.iocStartup(localModules);
Expand Down Expand Up @@ -121,7 +124,7 @@ export class ExpressApp {
#initializeErrorHandlerMiddleware() {
const errorHandler: express.ErrorRequestHandler = (error: unknown, req, res, next) => {
if (res.headersSent) return next(error);
const err = utils.getTypedError(error);
const err = apiClientUtils.getTypedError(error);
log.warn(`Headers sent before reaching main error handler`, err);
res.status(err.content.status).json(err.content);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { type TODO, type Class } from "@org/lib-commons";
import { type TODO } from "@org/lib-commons";
import type { DecoratorMetadataEntry } from "./DecoratorMetadataEntry";

type Class<T = TODO> = new (...args: TODO[]) => T;

export type DecoratorMetadataEntryInstance<T extends DecoratorMetadataEntryConstructor> =
T extends DecoratorMetadataEntryConstructor<infer U> ? U : never;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { type TODO } from "@org/lib-commons";
import { ErrorLogRepository } from "@org/app-node-express/infrastructure/repository/impl/ErrorLogRepository";
import type { AppRoute } from "@ts-rest/core";
import { iocRegistry } from "@org/app-node-express/lib/bottlejs";
import { MongoDatabaseService } from "@org/app-node-express/lib/mongodb";
import { withSecured } from "@org/app-node-express/infrastructure/middleware/withSecured";
import { getTypedError } from "@org/lib-commons";
import { getTypedError } from "@org/lib-api-client";
import type { RequestHandler } from "express";
import { type RouteHandler, TsRestRouterService } from "@org/app-node-express/lib/@ts-rest";
import {
type RouteHandler,
RouteInput,
TsRestRouterService,
} from "@org/app-node-express/lib/@ts-rest";
import { type KeycloakRole } from "@org/app-node-express/lib/keycloak-connect";

export function contract<const Route extends AppRoute, This, Fn extends RouteHandler<Route>>(
Expand All @@ -20,20 +22,17 @@ export function contract<const Route extends AppRoute, This, Fn extends RouteHan
).flat();

return function (target: Fn, context: ClassMethodDecoratorContext<This, Fn>) {
async function handler(data: TODO): Promise<TODO> {
async function handler(data: unknown): Promise<unknown> {
const session = MongoDatabaseService.getInstance().client.startSession();
try {
MongoDatabaseService.getInstance().startTransaction(session);
const container = iocRegistry.inject(context);
const result = await target.call(container, data);
const result = await target.call(container, data as RouteInput<Route>);
await MongoDatabaseService.getInstance().commitTransaction(session);
return result;
} catch (error: unknown) {
await MongoDatabaseService.getInstance().rollbackTransaction(session);
const typedError = getTypedError(error);
await iocRegistry
.inject<ErrorLogRepository>(ErrorLogRepository.name)
.insertOne(typedError.content);
return { status: typedError.content.status, body: typedError.content };
} finally {
session.endSession();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { env } from "@org/app-node-express/env";
import { type Authorization } from "@org/app-node-express/interface/Authorization";
import { type AuthorizationRepository } from "@org/app-node-express/interface/AuthorizationRepository";

import * as utils from "@org/lib-commons";
import { RestError, getTypedError } from "@org/lib-api-client";
import * as bottlejs from "@org/app-node-express/lib/bottlejs";
import type * as KC from "@org/app-node-express/lib/keycloak-connect";

Expand All @@ -28,13 +28,13 @@ export function withSecured(...roles: KC.KeycloakRole[]): RequestHandler[] {
const hasRole = flattenedRoles.some(role => roles.includes(role));

if (!hasRole) {
throw new utils.ErrorResponse(403, "User does not have the required role");
throw new RestError(403, "User does not have the required role");
}

next();
} catch (error: unknown) {
console.log("error in withSecured", error);
next(utils.getTypedError(error));
next(getTypedError(error));
}
};

Expand All @@ -45,7 +45,7 @@ export function withSecured(...roles: KC.KeycloakRole[]): RequestHandler[] {
handler(req, res, next);
} catch (error: unknown) {
console.log("Error in keycloak.protect()", error);
next(utils.getTypedError(error));
next(getTypedError(error));
}
};

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ErrorResponse, PaginationOptions, type TODO } from "@org/lib-commons";
import { PaginationOptions, type TODO } from "@org/lib-commons";
import { type PaginationResult } from "@org/lib-commons";
import { type UserRepository } from "@org/app-node-express/infrastructure/repository/impl/UserRepository";
import { type User } from "@org/lib-commons";
import { autowired } from "@org/app-node-express/decorators/autowired";
import { type AuthorizationRepository } from "@org/app-node-express/interface/AuthorizationRepository";
import type * as KC from "@org/app-node-express/lib/keycloak-connect";
import { RestError } from "@org/lib-api-client";

export class UserService {
@autowired private userRepository: UserRepository;
Expand All @@ -20,7 +21,7 @@ export class UserService {

async findOneByUsername(username: string): Promise<KC.KeycloakUser> {
const user = await this.authorizationRepository.findUserByUsername(username);
if (user === null) throw new ErrorResponse(404, "User not found");
if (user === null) throw new RestError(404, "User not found");
return user;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { TsRestRouterService } from "./TsRestRouterService";
import { initServer, createExpressEndpoints } from "@ts-rest/express";
import type express from "express";
import swaggerUi from "swagger-ui-express";
import { type TODO } from "@org/lib-commons";

export function initializeExpressRoutes(app: express.Application): void {
const s = initServer();
const router = s.router(contracts, TsRestRouterService.getInstance().getRouters());
const unsafeRouters: TODO = TsRestRouterService.getInstance().getRouters();
const router = s.router(contracts, unsafeRouters);
function suppressConsole<T>(handler: () => T): T {
const originalConsole = {
log: console.log,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import type { RequestHandler } from "express";
import type { AppRoute } from "@ts-rest/core";
import { type TODO } from "@org/lib-commons";
import { contracts } from "@org/lib-api-client";

export class TsRestRouterService {
private static instance: TsRestRouterService;

#routers: TODO;
#routers: Record<
string,
Record<
string,
{
handler: (data: unknown) => Promise<unknown>;
middleware: RequestHandler[];
}
>
>;

public static getInstance(): TsRestRouterService {
TsRestRouterService.instance ??= new TsRestRouterService();
Expand All @@ -17,7 +25,11 @@ export class TsRestRouterService {
this.#routers = {};
}

addRouter(routeContract: AppRoute, handler: TODO, middleware: RequestHandler[]) {
addRouter(
routeContract: AppRoute,
handler: (data: unknown) => Promise<unknown>,
middleware: RequestHandler[],
) {
for (const [controllerName, controllerRoutes] of Object.entries(contracts)) {
for (const [functionName, route] of Object.entries(controllerRoutes)) {
if (route !== routeContract) continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { env } from "@org/app-node-express/env";
import Keycloak, { type Keycloak as KeycloakType, type KeycloakConfig } from "keycloak-connect";
import { z } from "zod";
import { keycloakMemoryStore } from "@org/app-node-express/lib/keycloak-connect/KeycloakMemoryStore";
import { ErrorResponse } from "@org/lib-commons";
import { RestError } from "@org/lib-api-client";
import { type Authorization } from "@org/app-node-express/interface/Authorization";

export class KeycloakAuthorization implements Authorization {
Expand Down Expand Up @@ -61,7 +61,7 @@ export class KeycloakAuthorization implements Authorization {

// Overriding keycloak access denied to return 401 status code and custom message.
this.keycloak.accessDenied = () => {
throw new ErrorResponse(401, "Unauthorized");
throw new RestError(401, "Unauthorized");
};
}

Expand Down
1 change: 0 additions & 1 deletion packages/mern-sample-app/app-node-express/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { server } from "@org/app-node-express/server";
try {
await server.init();
await server.startListening();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: unknown) {
console.log(error);
if (typeof error === "object" && error !== null && "message" in error) {
Expand Down
2 changes: 0 additions & 2 deletions packages/mern-sample-app/app-node-express/src/modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ import { KeycloakAuthorization } from "@org/app-node-express/lib/keycloak-connec
import { UserController } from "@org/app-node-express/infrastructure/controllers/UserController";
import { UserRepository } from "@org/app-node-express/infrastructure/repository/impl/UserRepository";
import { KeycloakRepository } from "@org/app-node-express/lib/keycloak-connect";
import { ErrorLogRepository } from "@org/app-node-express/infrastructure/repository/impl/ErrorLogRepository";
import { UserService } from "@org/app-node-express/infrastructure/service/UserService";

export function scanIocModules() {
return {
Authorization: KeycloakAuthorization,
UserController,
UserRepository,
ErrorLogRepository,
UserService,
AuthorizationRepository: KeycloakRepository,
} as const satisfies Record<string, new () => TODO>;
Expand Down
2 changes: 1 addition & 1 deletion packages/mern-sample-app/app-vite-react/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"root": true,
"env": { "browser": true, "es2020": true },
"extends": [
"../../.eslintrc",
"../../../.eslintrc",
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended"
Expand Down
19 changes: 14 additions & 5 deletions packages/mern-sample-app/app-vite-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,17 @@
"version": "0.0.1",
"type": "module",
"scripts": {
"test": "echo 'No tests found'",
"test": "vitest --run",
"test:coverage": "vitest --run --coverage",
"clean": "rm -rf ./dist",
"debug": "vite --mode debug",
"dev": "vite",
"build": "tsc && vite build --emptyOutDir",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint": "npx eslint . --ext ts,tsx --report-unused-disable-directives",
"start": "serve -s dist",
"loadTranslationTypes": "i18next-resources-for-ts interface -i ./public/locales/en -o ./src/lib/i18next/@types/resources.d.ts"
},
"dependencies": {
"@org/lib-commons": "workspace:*",
"@org/lib-api-client": "workspace:*",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@mui/icons-material": "^6.0.2",
Expand All @@ -24,6 +23,8 @@
"@mui/styled-engine-sc": "^6.0.2",
"@mui/styles": "^6.0.2",
"@mui/x-tree-view": "^7.15.0",
"@org/lib-api-client": "workspace:*",
"@org/lib-commons": "workspace:*",
"@preact/signals-react": "^2.0.1",
"@react-keycloak/web": "^3.4.0",
"@tanstack/react-query": "^5.32.1",
Expand All @@ -46,13 +47,21 @@
},
"devDependencies": {
"@preact/signals-react-transform": "^0.3.1",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/react": "^18.2.56",
"@types/react-dom": "^18.2.19",
"@typescript-eslint/eslint-plugin": "^8.5.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.11",
"i18next-resources-for-ts": "^1.5.0",
"jsdom": "^25.0.0",
"locize-cli": "^8.0.0",
"serve": "^14.2.1",
"typescript": "^5.2.2",
"vite": "^5.1.4"
"vite": "^5.1.4",
"vitest": "^2.0.5"
}
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
{
"test": "Test EN",
"setDarkMode": "Dark mode",
"setLightMode": "Light mode",
"systemLanguage": "Language",
"accountSettings": "Account Settings",
"appTheme": "Theme",
"apps": "Apps",
"borderRadius": "Border radius",
"doSearch": "Search",
"calendar": "Calendar",
"cards": "Cards",
"dashboard": "Dashboard",
"accountSettings": "Account Settings",
"pages": "Pages",
"login": "Login",
"register": "Register",
"doSearch": "Search",
"error": "Error",
"userInterface": "User Interface",
"typography": "Typography",
"icons": "Icons",
"cards": "Cards",
"tables": "Tables",
"forms": "Forms",
"apps": "Apps",
"calendar": "Calendar",
"icons": "Icons",
"invoice": "Invoice",
"user": "User",
"rolesAndPermissions": "Roles & Permissions",
"list": "List",
"view": "View",
"login": "Login",
"pages": "Pages",
"register": "Register",
"rolesAndPermissions": "Roles & Permissions",
"setDarkMode": "Dark mode",
"setLightMode": "Light mode",
"systemLanguage": "Language",
"systemLayout": "Layout",
"systemLayoutHorizontal": "Horizontal view",
"systemLayoutSidebar": "Sidebar view",
"systemLayoutHorizontal": "Horizontal view"
"tables": "Tables",
"test": "Test EN",
"typography": "Typography",
"user": "User",
"userInterface": "User Interface",
"view": "View"
}
Loading

0 comments on commit 43f9d87

Please sign in to comment.