Skip to content

Commit

Permalink
Merge pull request #6 from brunotot/refactor-with-keycloak-implementa…
Browse files Browse the repository at this point in the history
…tion

chore: Fixes, refactoring and implement Keycloak locally
  • Loading branch information
brunotot authored Aug 16, 2024
2 parents 550c1a5 + 9bb794f commit 49f6cfa
Show file tree
Hide file tree
Showing 147 changed files with 2,565 additions and 1,385 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"ignorePatterns": ["node_modules/", "dist/", "logger.js", "docs/", "updateDepDocs.js", "npx", "scripts"],
"ignorePatterns": ["node_modules/", "dist/", "logger.js", "docs/", "updateDepDocs.js", "npx", "scripts", "keycloak.js"],
"rules": {
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/ban-ts-comment": "off"
Expand Down
10 changes: 10 additions & 0 deletions md/7-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@
<td align="right">^7.2.0</td>
<td>Provides rate limiting to protect against brute force attacks</td>
</tr>
<tr>
<td>flatted</td>
<td align="right">^3.3.1</td>
<td>-</td>
</tr>
<tr>
<td>helmet</td>
<td align="right">^7.1.0</td>
Expand Down Expand Up @@ -256,6 +261,11 @@
<td align="right">^6.22.3</td>
<td>Provides routing functionality for the React frontend application</td>
</tr>
<tr>
<td>react-use</td>
<td align="right">^17.5.0</td>
<td>-</td>
</tr>
</tbody>
</table>

Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"devDependencies": {
"@commitlint/cli": "^19.2.2",
"@commitlint/config-conventional": "^19.2.2",
"@types/express-session": "^1.18.0",
"cross-dirname": "^0.1.0",
"husky": "^9.0.11",
"lint-staged": "^15.2.2",
Expand All @@ -56,6 +57,10 @@
"node"
],
"dependencies": {
"axios": "^1.6.8",
"express-session": "^1.18.0",
"glob": "^11.0.0",
"keycloak-connect": "^25.0.2",
"react-use": "^17.5.0"
}
}
4 changes: 3 additions & 1 deletion packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"express": "^4.18.2",
"express-rate-limit": "^7.2.0",
"flatted": "^3.3.1",
"glob": "^11.0.0",
"helmet": "^7.1.0",
"hpp": "^0.2.3",
"jsonwebtoken": "^9.0.2",
Expand All @@ -39,7 +40,8 @@
"swagger-ui-express": "^5.0.0",
"winston": "^3.11.0",
"winston-daily-rotate-file": "^5.0.0",
"zod": "^3.22.5"
"zod": "^3.22.5",
"express-session": "^1.18.0"
},
"devDependencies": {
"@babel/preset-env": "^7.24.5",
Expand Down
52 changes: 25 additions & 27 deletions packages/backend/src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,49 @@ import { MongoClient, type MongoClientOptions } from "mongodb";
import { generateOpenApi } from "@ts-rest/open-api";
import { initServer, createExpressEndpoints } from "@ts-rest/express";

import { RouterCollection, Environment, Logger } from "@org/backend/config";
import { GLOBAL_MIDDLEWARES } from "@org/backend/infrastructure";
import { GLOBAL_MIDDLEWARES } from "@org/backend/infrastructure/middleware/global";
import { CONTRACTS, operationMapper, suppressConsole } from "@org/shared";

export type MongoConnectParams = {
uri: string;
options?: MongoClientOptions;
};

export type AppOptions = {
mongoConnection: MongoConnectParamsFactory;
};

export type MongoConnectParamsFactory = () => Promise<MongoConnectParams>;
import { Environment } from "@org/backend/config/singletons/Environment";
import { Logger } from "@org/backend/config/singletons/Logger";
import { RouterCollection } from "./config/singletons/RouterCollection";
import { ServiceRegistry } from "./config/singletons/ServiceRegistry";
import MODULES from "./modules";
import keycloak from "./keycloak";

export class App {
public readonly app: express.Application;
public readonly expressApp: express.Application;
public readonly env: string;
public readonly port: string;
public readonly swaggerPath: string;
public readonly url: string;

#mongoConnection: MongoConnectParamsFactory;

private environment = Environment.getInstance();
private logger = Logger.getInstance();
mongoClient: MongoClient;

constructor(options: AppOptions) {
this.app = express();
constructor() {
this.expressApp = express();
this.env = this.environment.vars.NODE_ENV;
this.port = this.environment.vars.PORT;
this.swaggerPath = "api-docs";
this.#mongoConnection = options.mongoConnection;
const domain =
this.env === "production"
? `https://${process.env.RAILWAY_PUBLIC_DOMAIN}`
: "http://localhost";
this.url = `${domain}:${this.port}`;

this.#initializeGlobalMiddlewares();
this.#initializeRoutes();
this.#initializeSwagger();
}

public async listen(): Promise<void> {
public async prepare(): Promise<void> {
ServiceRegistry.getInstance().iocStartup(MODULES);
await this.#initializeDatabase();
}

public async start(): Promise<void> {
return new Promise(resolve => {
this.app.listen(this.port, () => {
this.expressApp.listen(this.port, () => {
this.logger.table({
title: `[Express] MERN Sample App v${this.environment.vars.PACKAGE_JSON_VERSION}`,
data: {
Expand All @@ -74,22 +68,26 @@ export class App {

async #initializeDatabase() {
try {
const { uri, options } = await this.#mongoConnection();
this.mongoClient = new MongoClient(uri, options);
const { MONGO_URL } = Environment.getInstance().vars;
const MONGO_OPTIONS: MongoClientOptions = {};
this.mongoClient = new MongoClient(MONGO_URL, MONGO_OPTIONS);
await this.mongoClient.connect();
} catch (error) {
console.log(error);
}
}

#initializeGlobalMiddlewares() {
GLOBAL_MIDDLEWARES.forEach(middleware => this.app.use(middleware));
this.expressApp.use(keycloak.middleware());
GLOBAL_MIDDLEWARES.forEach(middleware => {
this.expressApp.use(middleware);
});
}

#initializeRoutes() {
const s = initServer();
const router = s.router(CONTRACTS, RouterCollection.getInstance().getRouters());
suppressConsole(() => createExpressEndpoints(CONTRACTS, router, this.app));
suppressConsole(() => createExpressEndpoints(CONTRACTS, router, this.expressApp));
}

#initializeSwagger() {
Expand All @@ -113,7 +111,7 @@ export class App {

const openApiDocument = generateOpenApi(CONTRACTS, apiDoc, { operationMapper });

this.app.use(
this.expressApp.use(
"/api-docs",
swaggerUi.serve,
swaggerUi.setup(openApiDocument, {
Expand Down
14 changes: 0 additions & 14 deletions packages/backend/src/config/index.ts

This file was deleted.

44 changes: 37 additions & 7 deletions packages/backend/src/config/managers/DatabaseManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Environment } from "@org/backend/config/singletons/Environment";
import { type Db } from "mongodb";
import type { MongoClient, Db, ClientSession } from "mongodb";
import { type ZodSchema, type z } from "zod";
import server from "@org/backend/server";

Expand All @@ -11,25 +11,55 @@ export class DatabaseManager {
return this.instance;
}

#client: Db;
#client: MongoClient;
#db: Db;

session: ClientSession | undefined;

private constructor() {
// NOOP
this.#client = server.mongoClient;
}

get client() {
if (this.#client) return this.#client;
this.#client = server.mongoClient.db(Environment.getInstance().vars.MONGO_DATABASE);
private get client() {
return this.#client;
}

async rollbackTransaction() {
if (this.session) {
await this.session.abortTransaction();
this.session.endSession();
}
this.session = undefined;
}

async commitTransaction() {
if (this.session) {
await this.session.commitTransaction();
this.session.endSession();
}
this.session = undefined;
}

startTransaction() {
this.session = this.client.startSession();
this.session.startTransaction();
}

get db() {
if (this.#db) return this.#db;
this.#db = this.#client.db(Environment.getInstance().vars.MONGO_DATABASE);
return this.#db;
}

// Show deleted in table checkbox

collection<const T extends ZodSchema>(zodSchema: T) {
const documentName = zodSchema.description;
if (!documentName) throw new Error("No document name provided.");
const lowerCaseName = documentName.toLowerCase();
const suffix = "s";
const computedSuffix = lowerCaseName.endsWith(suffix) ? "" : suffix;
const name = lowerCaseName + computedSuffix;
return this.client.collection<z.infer<T>>(name);
return this.db.collection<z.infer<T>>(name);
}
}
15 changes: 7 additions & 8 deletions packages/backend/src/config/managers/InjectorMetadataManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type { TODO } from "@org/shared";
import { type MetaClassInjectionData } from "@org/backend/types";
import { ClassMetadataEntry, type ClassMetadataInjectType } from "@org/backend/decorators";
import { ClassMetadataEntry } from "@org/backend/config/utils/ClassMetadataEntry";
import { type ClassMetadataInjectType } from "@org/backend/config/utils/ClassMetadata";

export type MetaClassInjectionData = {
name: string;
dependencies: string[];
};

export class InjectorMetadataManager extends ClassMetadataEntry<MetaClassInjectionData> {
static getBy(injection: ClassMetadataInjectType) {
Expand All @@ -11,14 +15,9 @@ export class InjectorMetadataManager extends ClassMetadataEntry<MetaClassInjecti
super(injection, () => ({
name: "",
dependencies: [],
constructorParams: [],
}));
}

setConstructorParams(params: TODO[]) {
this.value.constructorParams = params;
}

setName(name: string) {
this.value.name = name;
}
Expand Down
13 changes: 0 additions & 13 deletions packages/backend/src/config/setup/index.ts

This file was deleted.

8 changes: 7 additions & 1 deletion packages/backend/src/config/singletons/Environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class Environment {
readonly schema = z.object({
PACKAGE_JSON_VERSION: z.string().default("?.?.?"),
NODE_ENV: z.string().default("development"),
PORT: z.string().default("8080"),
PORT: z.string().default("8081"),
LOG_FORMAT: z.string().default("dev"),
LOG_DIR: z.string().default("../../logs"),
ORIGIN: z.string().default("*"),
Expand All @@ -26,6 +26,12 @@ export class Environment {
MONGO_DATABASE: z.string(),
ACCESS_TOKEN_SECRET: z.string(),
REFRESH_TOKEN_SECRET: z.string(),
KEYCLOAK_URL: z.string(),
KEYCLOAK_SSL_REQUIRED: z.string(),
KEYCLOAK_REALM: z.string(),
KEYCLOAK_ADMIN_CLI_ID: z.string(),
KEYCLOAK_ADMIN_CLI_SECRET: z.string(),
KEYCLOAK_CONFIDENTIAL_PORT: z.string(),
});

private constructor() {
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/config/singletons/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export class Logger {

logger.add(
new winston.transports.Console({
level: "debug", // Set the level to 'debug' to capture all logs, including errors
format: winston.format.combine(winston.format.splat(), winston.format.colorize()),
}),
);
Expand Down
4 changes: 3 additions & 1 deletion packages/backend/src/config/singletons/RouterCollection.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { type RouteMiddleware } from "@org/backend/types";
import { type ContractName, type TODO } from "@org/shared";
import type { Request, Response, NextFunction } from "express";

export type RouteMiddleware = (req: Request, res: Response, next: NextFunction) => void;

export class RouterCollection {
private static instance: RouterCollection;
Expand Down
Loading

0 comments on commit 49f6cfa

Please sign in to comment.