Skip to content

Commit

Permalink
feat: add authentication system (#4)
Browse files Browse the repository at this point in the history
* fix: this context in controller instance undefined

* feat: add cookie management

* feat: add sidebar

* feat:add searchbar

* feat: fix searchbar

* feat:finish searchbar ♥

* feat:add layout home page , TwobarChart, AreaChart

* refactor: cookie implementation and document

* feat:add headless ui and add responesive

* feat:fix MachineLog error

* feat:fix case bug

* feat:delete MachineLog and and Progressing circle

* feat: remove gridline form areachart and twobar

* feat:add Pagination Errorlog but dont no type of pagination

* feat:fix misspell

* refactor: eliminate type 'any

* feat:fix prevent loading component

* feat: reuse components

* feat:add Loginpage

* feat: add database connector and prepared auth service (#3)

* build: add devDependencies 'dotenv'

* build: preload environment variables

* build: add dependencies 'mysql2'

* feat: add mysql database connection

* chore: add example environment variable

* refactor: eliminate type 'any'

* refactor: add missing modifier

* feat: adds Address, Branch model

* feat: adds Address,Branch,Machine,Staff,Zone model

* feat:adds MainrenanceLog, MaintenanceParts

* feat: fix Branch

* feat:add MachinePart model

* feat: adds Order, Bill model

* feat: removes useless constructor, adds pk getter

* feat: add pk getter, assign readonly only for pk

* refactor: remove unit testing (base code)

* refactor: use MYSQL_PASSWORD instead

* build: add redis dependency

* refactor: database connection with mysql and redis

* refactor: add generateRedisUri method

* refactor: make some redis environment optional

* build: remove unused environment variable

* build: add redis's environment variable

* feat: add DatabaseEntity Interface

* feat: add set pk for Address, Bill, Branch

* feat: add set pk for Machine, MachinePart, MLog

* feat: add set pk for MPart, Order, Staff, Zone

* style: add vscode setting for coding style

* refactor: class naming and use path mapping

* feat: add regex util

* feat: add base code of auth service

* feat: add auth service unit test

Co-authored-by: XiaoXuxxxx <aonrok555@gmail.com>
Co-authored-by: eltfshr <puntf2@gmail.com>
Co-authored-by: Porping <porsteam@hotmail.com>

* feat: add not found 404 fallback middleware

* feat: add request decorator for basic validation

* refactor: remove next function parameter

* fix: missing get connection methods

* refactor: login method return cookie with sid

* refactor: invoke auth service instead of mock

* refactor: call express url encoding & json parser

* fix: wrong expect (forget to fix after changed)

* feat: add auth controller unit test

* feat: add DateUtil class

* feat: add repository interface

* feat: add staff repository, add password in staff

* build: add bcrypt depedency

* refactor: dependecy inversion for testability

* refactor: complete auth service for logging in

* refactor: auth service tester with new interface

* refactor: entity interface

* refactor: database connection for repository

* fix: tsconfig annoying warning

* feat: add parameter 'readOptions' to read method

* refactor: convert to marker interface

* feat: add test script on top-level module

* feat: add address repository

* feat: add zone repository

* refactor: clean yarn.lock with yarn install

* feat: add machine repository

* fix: linting issue

* fix: add dateutil

* feat:add order repository

* refactor: convert to marker interface

* feat: add maintenancepart repository

* feat: add maintenancelog repository

* fix: variable same in database

* fix: method typo

* refactor: register all repositories

* feat: add machinepart repository

* feat: add branch repository

* feat: add bill repository

* refactor: change return type of update and delete

* refactor: change return type of concrete classes

* refactor: change return type related to interface

* typo: fix misspelling words

* feat: register alll repositories

* refactor: pool typing as mysql like redis

* fix: annoying new line

* feat: add session entity/repository

* fix: fix typos

* fix: wrong casing on import

* refactor: add sql builder for utility

* refactor: use new sql builder for example

* refactor: use new sql builder for example

* refactor: remove unnecessary json parse

* feat: add axios and login state

* fix: easy fix when pass undefined into the method

* refactor: implement session and logout

* refactor: util raw cookie and signed cookie

* refactor: controller registration for middleware

* build: clean yarn.lock for ci

* refactor: auth service unit test

* feat: add get staff info route

* feat: add validation for readoptions

* refactor: remove unnecessary logging

* fix: limit and offset must be integer

* refactor: clean sourcecode

* feat: add number utility class

* feat: add optional request body

* feat: implement authorization with decorator

* refactor: throw meaningful exceception instead

Co-authored-by: Porping <porsteam@hotmail.com>
Co-authored-by: XiaoXuxxxx <aonrok555@gmail.com>
Co-authored-by: eltfshr <puntf2@gmail.com>
  • Loading branch information
4 people committed Apr 24, 2022
1 parent b39fa59 commit 1e63726
Show file tree
Hide file tree
Showing 97 changed files with 4,735 additions and 344 deletions.
4 changes: 4 additions & 0 deletions .vscode/settings.json
@@ -0,0 +1,4 @@
{
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.quoteStyle": "single",
}
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -3,7 +3,8 @@
"private": true,
"workspaces": ["packages/*"],
"scripts": {
"dev": "lerna run dev --stream"
"dev": "lerna run dev --stream",
"test": "lerna run test --stream"
},
"devDependencies": {
"lerna": "3.22.1"
Expand Down
10 changes: 10 additions & 0 deletions packages/backend/.env.example
@@ -0,0 +1,10 @@
MYSQL_HOST=
MYSQL_PORT=
MYSQL_USER=
MYSQL_PASSWORD=
MYSQL_DATABASE=

REDIS_HOST=
REDIS_PORT=
REDIS_USER=
REDIS_PASSWORD=
8 changes: 8 additions & 0 deletions packages/backend/declarations/express.d.ts
@@ -0,0 +1,8 @@
declare namespace Express {
export interface Request {
session?: {
sessionId: string,
staffId: number,
}
}
}
9 changes: 7 additions & 2 deletions packages/backend/package.json
Expand Up @@ -9,21 +9,26 @@
"build": "yarn run build:prod",
"dev": "npm-run-all --silent --parallel build:dev start:dev",
"lint": "eslint . --ext .ts",
"start:dev": "nodemon dist/app.js -q -w dist -e js",
"start:prod": "node dist/app.js",
"start:dev": "nodemon -r dotenv/config dist/app.js -q -w dist -e js",
"start:prod": "node -r dotenv/config dist/app.js",
"start": "yarn run start:prod",
"test": "jest --silent"
},
"dependencies": {
"bcrypt": "^5.0.1",
"express": "^4.17.3",
"mysql2": "^2.3.3",
"redis": "^4.0.6",
"reflect-metadata": "^0.1.13"
},
"devDependencies": {
"@types/bcrypt": "^5.0.0",
"@types/express": "^4.17.13",
"@types/jest": "^27.4.1",
"@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^5.12.1",
"@typescript-eslint/parser": "^5.12.1",
"dotenv": "^16.0.0",
"eslint": "^8.9.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^16.1.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/App.ts
Expand Up @@ -4,7 +4,7 @@ try {
console.log('Starting up the application...');
const server = new Server(4000);
server.run();
} catch (error: Error | unknown) {
} catch (error: unknown) {
if (error instanceof Error) {
console.log(`${error.name}: ${error.message}`);
console.log('Closing the application...');
Expand Down
115 changes: 106 additions & 9 deletions packages/backend/src/Server.ts
Expand Up @@ -3,28 +3,73 @@ import http from 'http';
import express, { Application } from 'express';
import { IndexController } from '@/controllers/IndexController';
import { ControllerRegistry } from '@/controllers/ControllerRegistry';
import { DatabaseConnector } from '@/utils/database/DatabaseConnector';
import { DatabaseException } from '@/exceptions/DatabaseException';
import { CookieProvider } from '@/utils/cookie/CookieProvider';
import { AuthController } from '@/controllers/AuthController';
import { AuthService } from '@/services/AuthService';
import { AddressRepository } from '@/repositories/address/AddressRepository';
import { BillRepository } from '@/repositories/bill/BillRepository';
import { BranchRepository } from '@/repositories/branch/BranchRepository';
import { MachineRepository } from '@/repositories/machine/MachineRepository';
import { MachinePartRepository } from '@/repositories/machinePart/MachinePartRepository';
import { MaintenanceLogRepository } from '@/repositories/maintenancelog/MaintenanceLogRepository';
import { MaintenancePartRepository } from '@/repositories/maintenancepart/MaintenancePartRepository';
import { OrderRepository } from '@/repositories/order/OrderRepository';
import { StaffRepository } from '@/repositories/staff/StaffRepository';
import { ZoneRepository } from '@/repositories/zone/ZoneRepository';
import { DefaultAddressRepository } from '@/repositories/address/DefaultAddressRepository';
import { DefaultBillRepository } from '@/repositories/bill/DefaultBillRepository';
import { DefaultBranchRepository } from '@/repositories/branch/DefaultBranchRepository';
import { DefaultMachineRepository } from '@/repositories/machine/DefaultMachineRepository';
import { DefaultMachinePartRepository } from '@/repositories/machinePart/DefaultMachinePartRepository';
import { DefaultMaintenanceLogRepository } from '@/repositories/maintenancelog/DefaultMaintenanceLogRepository';
import { DefaultMaintenancePartRepository } from '@/repositories/maintenancepart/DefaultMaintenancePartRepository';
import { DefaultOrderRepository } from '@/repositories/order/DefaultOrderRepository';
import { DefaultStaffRepository } from '@/repositories/staff/DefaultStaffRepository';
import { DefaultZoneRepository } from '@/repositories/zone/DefaultZoneRepository';
import { SessionRepository } from '@/repositories/session/SessionRepository';
import { DefaultSessionRepository } from '@/repositories/session/DefaultSessionRepository';
import { StaffController } from '@/controllers/StaffController';

export class Server {

private readonly app: Application;
private readonly port: number;
private readonly controllerRegistry: ControllerRegistry;

private databaseConnector: DatabaseConnector;
private controllerRegistry: ControllerRegistry;
private cookieProvider: CookieProvider;

private addressRepository: AddressRepository;
private billRepository: BillRepository;
private branchRepository: BranchRepository;
private machineRepository: MachineRepository;
private machinePartRepository: MachinePartRepository;
private maintenanceLogRepository: MaintenanceLogRepository;
private maintenancePartRepository: MaintenancePartRepository;
private orderRepository: OrderRepository;
private sessionRepository: SessionRepository;
private staffRepository: StaffRepository;
private zoneRepository: ZoneRepository;

private authService: AuthService;

public constructor(port: number) {
this.app = express();
this.port = port;
this.controllerRegistry = new ControllerRegistry(this.app);
this.databaseConnector = new DatabaseConnector();
}

public run(): http.Server {
this.controllerRegistry.loadControllers([
new IndexController(),
]);

public async run(): Promise<http.Server> {
this.app.use(express.urlencoded({ extended: true }));
this.app.use(express.json());
this.app.disable('x-powered-by');

const controllerCount = this.controllerRegistry.size();
console.log(`Registered ${controllerCount} controller${controllerCount > 1 ? 's' : ''}`);
await this.connectDatabase();
await this.registerRepository();
await this.registerServices();
await this.loadControllers();

return this.app.listen(this.port, this.onStartup.bind(this));
}
Expand All @@ -33,4 +78,56 @@ export class Server {
console.log(`Listening on http://localhost:${this.port}/`);
}

private async registerRepository(): Promise<void> {
const defaultDb = await this.databaseConnector.getDefaultDatabase();
const cachingDb = await this.databaseConnector.getCachingDatabase();

this.addressRepository = new DefaultAddressRepository(defaultDb, cachingDb);
this.billRepository = new DefaultBillRepository(defaultDb, cachingDb);
this.branchRepository = new DefaultBranchRepository(defaultDb, cachingDb);
this.machineRepository = new DefaultMachineRepository(defaultDb, cachingDb);
this.machinePartRepository = new DefaultMachinePartRepository(defaultDb, cachingDb);
this.maintenanceLogRepository = new DefaultMaintenanceLogRepository(defaultDb, cachingDb);
this.maintenancePartRepository = new DefaultMaintenancePartRepository(defaultDb, cachingDb);
this.orderRepository = new DefaultOrderRepository(defaultDb, cachingDb);
this.sessionRepository = new DefaultSessionRepository(defaultDb, cachingDb);
this.staffRepository = new DefaultStaffRepository(defaultDb, cachingDb);
this.zoneRepository = new DefaultZoneRepository(defaultDb, cachingDb);
}

private async registerServices(): Promise<void> {
this.authService = new AuthService(this.staffRepository, this.sessionRepository);
}

private async loadControllers(): Promise<void> {
this.cookieProvider = new CookieProvider('this-is-the-secret-just-keep-it');

this.controllerRegistry = new ControllerRegistry(
this.app,
this.cookieProvider,
this.sessionRepository,
this.staffRepository,
);

this.controllerRegistry.loadControllers([
new IndexController(),
new AuthController(this.cookieProvider, this.authService),
new StaffController(this.staffRepository),
]);

const controllerCount = this.controllerRegistry.size();
console.log(`Registered ${controllerCount} controller${controllerCount > 1 ? 's' : ''}`);
}

private async connectDatabase(): Promise<void> {
try {
await this.databaseConnector.connect();
} catch (error: unknown) {
if (error instanceof DatabaseException) {
console.log(error.message);
}
process.exit(1);
}
}

}
49 changes: 49 additions & 0 deletions packages/backend/src/controllers/AuthController.ts
@@ -0,0 +1,49 @@
import { Controller } from '@/controllers/Controller';
import { Methods } from '@/controllers/Route';
import { ControllerMapping } from '@/decorators/ControllerDecorator';
import { RouteMapping } from '@/decorators/RouteDecorator';
import { CookieProvider } from '@/utils/cookie/CookieProvider';
import { Request, Response } from 'express';
import { RequestBody } from '@/decorators/RequestDecorator';
import { AuthService } from '@/services/AuthService';

@ControllerMapping('/auth')
export class AuthController extends Controller {

private readonly cookieProvider: CookieProvider;
private readonly authService: AuthService;

public constructor(cookieProvider: CookieProvider, authService: AuthService) {
super();
this.cookieProvider = cookieProvider;
this.authService = authService;
}

@RouteMapping('/login', Methods.POST)
@RequestBody('username', 'password')
private async loginRoute(req: Request, res: Response): Promise<void> {
const { username, password } = req.body;

const cookie = await this.authService.login(username, password);

if (cookie) {
this.cookieProvider.setSignedCookie(res, cookie);

res.status(200).json({
message: 'Logged in successfully',
});
}
}

@RouteMapping('/logout', Methods.GET)
private async logoutRoute(req: Request, res: Response): Promise<void> {
const sessionId = this.cookieProvider.getSignedCookie(req, 'sid');

await this.authService.logout(sessionId);

res.status(200).json({
message: 'Logged out successfully',
});
}

}
24 changes: 11 additions & 13 deletions packages/backend/src/controllers/Controller.ts
@@ -1,15 +1,11 @@
import { RouteHandler } from '@/controllers/Route';
import { RouteMetadata } from '@/decorators/RouteDecorator';
import { ErrorHandler } from '@/exceptions/ErrorHandler';
import { Router } from 'express';
import { Route } from '@/controllers/Route';

export class Controller {

/**
* Injected with {@link ControllerDecorator} on class declaration
*/
private readonly path: string;
private readonly router: Router = Router();
private readonly hasAvailable: boolean;

/**
Expand All @@ -28,19 +24,21 @@ export class Controller {
}

/**
* Returns the modular express router of the controller instance
* @returns The express {@link Router} instance
* Returns the router structure with handler and metadata of the controller
* for converting to the modular express router
* @returns The router structure (array of {@link Route})
*/
public getRouter(): Router {
public getRouter(): Route[] {
const routes = Reflect.getMetadataKeys(this);

routes.forEach((route) => {
const routeHandler: RouteHandler = ErrorHandler.wrap(this[route]);
const routeProperty: RouteMetadata = Reflect.getMetadata(route, this);
this.router[routeProperty.method.toLowerCase()](routeProperty.path, routeHandler);
const router: Route[] = routes.map((route) => {
return {
handler: this[route].bind(this),
metadata: Reflect.getMetadata(route, this),
};
});

return this.router;
return router;
}

}

0 comments on commit 1e63726

Please sign in to comment.