Skip to content

Commit

Permalink
Add linter and configure it
Browse files Browse the repository at this point in the history
  • Loading branch information
cham11ng committed Mar 17, 2024
1 parent f35d830 commit a7ae487
Show file tree
Hide file tree
Showing 14 changed files with 87 additions and 30 deletions.
17 changes: 9 additions & 8 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["sonarjs"],
"plugins": ["sonarjs", "simple-import-sort", "jsdoc"],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
Expand All @@ -11,16 +11,17 @@
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"plugin:sonarjs/recommended"
"plugin:sonarjs/recommended",
"plugin:jsdoc/recommended-typescript"
],
"rules": {
"no-error-on-unmatched-pattern": "off",
"@typescript-eslint/no-explicit-any": "off", // TODO: Remove this.
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-namespace": "off",
"no-return-await": "off",
"@typescript-eslint/no-explicit-any": "warn", // TODO: Remove this.
"sonarjs/cognitive-complexity": "off",
"@typescript-eslint/no-shadow": ["error"]
"@typescript-eslint/no-shadow": ["error"],
"simple-import-sort/imports": "warn",
"jsdoc/no-types": "off",
"jsdoc/tag-lines": "off",
"jsdoc/require-returns-description": "off"
}
}
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ jobs:
- name: Installing dependencies
run: bun install

- name: Run linter
run: bun run lint

- name: Run tests
run: bun run test
env:
Expand Down
Binary file modified bun.lockb
Binary file not shown.
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"test": "NODE_ENV=test bun test ./test/**.test.ts",
"dev": "bun run --watch src/index.ts",
"pretty": "prettier --write \"**/*.{ts,js}\"",
"lint": "eslint src --ext .ts"
"lint": "eslint src --ext .ts",
"lint:fix": "eslint src --ext .ts --fix"
},
"dependencies": {
"@elysiajs/cors": "^1.0.0",
Expand All @@ -21,15 +22,18 @@
"bun-types": "^1.0.31",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jsdoc": "^48.2.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-simple-import-sort": "^12.0.0",
"eslint-plugin-sonarjs": "^0.24.0",
"husky": "^9.0.11",
"lint-staged": "^15.2.2",
"prettier": "^3.2.5"
},
"lint-staged": {
"*.{js,ts}": [
"prettier --write"
"prettier --write",
"eslint --fix"
]
},
"module": "src/index.js"
Expand Down
3 changes: 2 additions & 1 deletion src/config/db.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import config from '.';
import * as mongoose from 'mongoose';

import config from '.';

const { dbUsername, dbPassword, dbHost, dbPort, dbName } = config.db;
const connectionString = `mongodb://${dbUsername}:${dbPassword}@${dbHost}:${dbPort}/${dbName}`;

Expand Down
1 change: 1 addition & 0 deletions src/controllers/user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Context } from 'elysia';

import { User } from '../models/User';
import * as userService from '../services/user';

Expand Down
11 changes: 11 additions & 0 deletions src/domain/exceptions/MongoServerError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class MongoServerError extends Error {
public code: number;

constructor(message: string, code: number) {
super(message);
this.name = 'MongoServerError';
this.code = code;
}
}

export default MongoServerError;
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Elysia } from 'elysia';
import swagger from '@elysiajs/swagger';
import { Elysia } from 'elysia';

import config from './config';
import * as db from './config/db';
import userRoutes from './routes/user';
import errorHandler from './middlewares/errorHandler';
import loggerHandler from './middlewares/loggerHandler';
import securityHandler from './middlewares/securityHandler';
import userRoutes from './routes/user';

const app = new Elysia();

Expand Down
6 changes: 5 additions & 1 deletion src/middlewares/errorHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import ConflictError from '../domain/exceptions/ConflictError';

export default (app: Elysia) =>
app.error({ ConflictError }).onError((handler) => {
console.error(handler.error?.stack);

if (handler.error instanceof ConflictError) {
handler.set.status = handler.error.status;

Expand All @@ -21,7 +23,9 @@ export default (app: Elysia) =>
status: handler.set.status
};
}
if (handler.set.status === StatusCodes.BAD_REQUEST) {

if (handler.code === 'VALIDATION') {
handler.set.status = StatusCodes.BAD_REQUEST;
return {
message: 'Bad Request!',
status: handler.set.status
Expand Down
10 changes: 7 additions & 3 deletions src/middlewares/loggerHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ import * as yc from 'yoctocolors';
import { durationString, methodString } from '../utils/logger';

export default new Elysia()
.state({ beforeTime: process.hrtime.bigint(), as: 'global' })
.onRequest((ctx) => {
ctx.store.beforeTime = process.hrtime.bigint();
})
.onBeforeHandle({ as: 'global' }, (ctx) => {
ctx.store = { ...ctx.store, beforeTime: process.hrtime.bigint() };
ctx.store.beforeTime = process.hrtime.bigint();
})
.onAfterHandle({ as: 'global' }, ({ request, store }) => {
const logStr: string[] = [];

logStr.push(methodString(request.method));
logStr.push(new URL(request.url).pathname);

const beforeTime: bigint = (store as any).beforeTime;
const beforeTime: bigint = store.beforeTime;

logStr.push(durationString(beforeTime));

Expand All @@ -33,7 +37,7 @@ export default new Elysia()

logStr.push(error.message);

const beforeTime: bigint = (store as any).beforeTime;
const beforeTime: bigint = store.beforeTime;
logStr.push(durationString(beforeTime));

console.log(logStr.join(' '));
Expand Down
2 changes: 1 addition & 1 deletion src/middlewares/securityHandler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Elysia } from 'elysia';
import cors from '@elysiajs/cors';
import { Elysia } from 'elysia';

export default (app: Elysia) => app.use(cors());
7 changes: 4 additions & 3 deletions src/routes/user.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Elysia, t } from 'elysia';

import * as userController from '../controllers/user';

export default new Elysia()
.post('/users', userController.create, {
body: t.Object({
name: t.String(),
email: t.String(),
password: t.String()
name: t.String({ minLength: 1, maxLength: 256 }),
email: t.String({ format: 'email', maxLength: 256 }),
password: t.String({ minLength: 8, maxLength: 256 })
})
})
.get('/users', userController.fetchAll);
34 changes: 25 additions & 9 deletions src/services/user.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
import { User } from '../models/User';
import ConflictError from '../domain/exceptions/ConflictError';
import MongoServerError from '../domain/exceptions/MongoServerError';
import { User } from '../models/User';

export const create = (payload: User) => {
/**
* Creates a new user.
*
* @param payload - The user data to be created.
* @returns {Promise<User>} A promise that resolves to the created user.
* @throws {ConflictError} If a user with the same data already exists.
* @throws {Error} If an error occurs while creating the user.
*/
export async function create(payload: User) {
try {
const user = new User(payload);

return user.save();
} catch (e: any) {
if (e.name === 'MongoServerError' && e.code === 11000) {
return await user.save();
} catch (e) {
const error = e as MongoServerError;

if (error.name === 'MongoServerError' && error.code === 11000) {
throw new ConflictError('User exists.');
}

throw e;
throw error;
}
};
}

export const fetchAll = () => {
/**
* Fetches all users from the database.
*
* @returns {Promise<User[]>} A promise that resolves to an array of User objects.
*/
export function fetchAll() {
return User.find();
};
}
11 changes: 11 additions & 0 deletions src/utils/logger.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import * as yc from 'yoctocolors';

/**
* Returns the duration message.
*
* @param {bigint} beforeTime The time before the request.
* @returns {string}
*/
export function durationString(beforeTime: bigint): string {
const now = process.hrtime.bigint();
const timeDifference = now - beforeTime;
Expand All @@ -25,6 +31,11 @@ export function durationString(beforeTime: bigint): string {
return timeMessage;
}

/**
* Returns the duration message.
* @param {string} method The method.
* @returns {string}
*/
export function methodString(method: string): string {
switch (method) {
case 'GET':
Expand Down

0 comments on commit a7ae487

Please sign in to comment.