From 8c279b5cd7630489b996fb150bd177a42a959eb7 Mon Sep 17 00:00:00 2001 From: sianida26 Date: Wed, 28 Feb 2024 17:50:06 +0700 Subject: [PATCH] Added logger --- .env | 4 +- bun.lockb | Bin 201445 -> 201781 bytes env.ts | 3 +- logs/.gitignore | 1 + package.json | 1 + src/app/api/login/route.ts | 38 +++++++++++++++ src/core/error/BaseError.ts | 28 +++++++++-- src/core/logger/Logger.ts | 44 ++++++++++++++++++ src/core/utils/handleCatchApi.ts | 22 +++++++++ src/modules/auth/error/AuthError.ts | 3 ++ src/modules/auth/formSchemas/signInSchema.ts | 8 ++++ src/modules/auth/utils/getTokenFromHeaders.ts | 10 ++++ 12 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 logs/.gitignore create mode 100644 src/app/api/login/route.ts create mode 100644 src/core/logger/Logger.ts create mode 100644 src/core/utils/handleCatchApi.ts create mode 100644 src/modules/auth/formSchemas/signInSchema.ts create mode 100644 src/modules/auth/utils/getTokenFromHeaders.ts diff --git a/.env b/.env index 32f917e..e88e6fc 100644 --- a/.env +++ b/.env @@ -1,3 +1,5 @@ DATABASE_URL=mysql://root:root@localhost:3306/dashboard_template -JWT_SECRET= \ No newline at end of file +JWT_SECRET= + +ERROR_LOG_PATH=./logs \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 8228dda871141b10f43894a23ad26fa284485192..cffb24bbd296da85f9525c096c93a24282826c72 100755 GIT binary patch delta 32656 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 RqKUj_*W2{ajdo_`{|A5d8c+ZL delta 32059 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 diff --git a/env.ts b/env.ts index 572376e..a7863d8 100644 --- a/env.ts +++ b/env.ts @@ -3,7 +3,8 @@ const envVariables = z.object({ DATABASE_URL: z.string(), JWT_SECRET: z.string(), WS_PORT: z.string().optional(), - WS_HOST: z.string().optional() + WS_HOST: z.string().optional(), + ERROR_LOG_PATH: z.string() }); envVariables.parse(process.env); diff --git a/logs/.gitignore b/logs/.gitignore new file mode 100644 index 0000000..bf0824e --- /dev/null +++ b/logs/.gitignore @@ -0,0 +1 @@ +*.log \ No newline at end of file diff --git a/package.json b/package.json index 7d57c0e..d826b40 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "bcrypt": "^5.1.1", "client-only": "^0.0.1", "clsx": "^2.1.0", + "date-fns": "^3.3.1", "jsonwebtoken": "^9.0.2", "mantine-form-zod-resolver": "^1.1.0", "next": "14.1.0", diff --git a/src/app/api/login/route.ts b/src/app/api/login/route.ts new file mode 100644 index 0000000..95ee487 --- /dev/null +++ b/src/app/api/login/route.ts @@ -0,0 +1,38 @@ +import BaseError from "@/core/error/BaseError"; +import handleCatchApi from "@/core/utils/handleCatchApi"; +import AuthError from "@/modules/auth/error/AuthError"; +import signInSchema from "@/modules/auth/formSchemas/signInSchema"; +import signIn from "@/modules/auth/services/signIn"; +import getTokenFromHeaders from "@/modules/auth/utils/getTokenFromHeaders"; +import { headers } from "next/headers"; +import { NextRequest, NextResponse } from "next/server"; +import { json } from "stream/consumers"; + +export const dynamic = "force-dynamic"; + +export async function POST(request: NextRequest) { + try { + if (request.headers.get("Content-Type") !== "application/json") + throw new BaseError({ + errorCode: "UNSUPPORTED_CONTENT_TYPE", + message: + "This content type is not supported. Please use application/json instead", + statusCode: 400 + }); + const data = signInSchema.safeParse(await request.json()); + + if (!data.success){ + throw new AuthError({ + errorCode: "INVALID_CREDENTIALS", + message: "Email or Password does not match", + statusCode: 401 + }) + } + + const result = await signIn(data.data) + + return NextResponse.json(result); + } catch (e) { + return handleCatchApi(e) + } +} diff --git a/src/core/error/BaseError.ts b/src/core/error/BaseError.ts index 9c82306..6e0fefc 100644 --- a/src/core/error/BaseError.ts +++ b/src/core/error/BaseError.ts @@ -1,20 +1,25 @@ -export const BaseErrorCodes = ["UNKOWN_ERROR"] as const; +import logger from "../logger/Logger"; + +export const BaseErrorCodes = ["UNKNOWN_ERROR", "UNSUPPORTED_CONTENT_TYPE"] as const; interface ErrorOptions { message?: string; errorCode: (typeof BaseErrorCodes)[number] | (string & {}); + statusCode?: number } class BaseError extends Error { public readonly errorCode: (typeof BaseErrorCodes)[number] | (string & {}); + public readonly statusCode: number; constructor(options: ErrorOptions) { super(options.message ?? "Undetermined Error"); - this.errorCode = options.errorCode ?? "UNKOWN_ERROR"; + this.errorCode = options.errorCode ?? "UNKNOWN_ERROR"; + this.statusCode = options.statusCode ?? 500; Object.setPrototypeOf(this, new.target.prototype); - console.error("error:", options) + this.saveToLog(); } getActionResponseObject() { @@ -26,6 +31,23 @@ class BaseError extends Error { }, } as const; } + + getRestApiResponseObject(){ + return { + message: this.message, + errorCode: this.errorCode + } + } + + saveToLog() { + const excludedErrorCodes: string[] = []; + + if (excludedErrorCodes.includes(this.errorCode)) { + return; + } + + logger.error(JSON.stringify({errorCode: this.errorCode, message: this.message, stack: this.stack})) + } } export default BaseError; diff --git a/src/core/logger/Logger.ts b/src/core/logger/Logger.ts new file mode 100644 index 0000000..d682d3a --- /dev/null +++ b/src/core/logger/Logger.ts @@ -0,0 +1,44 @@ +import { appendFileSync } from "node:fs"; +import { format } from 'date-fns'; + +class Logger { + + logDirectory: string; + + readonly severityLevels = { + CRITICAL: 'CRITICAL', + ERROR: 'ERROR', + WARNING: 'WARNING', + }; + + constructor(){ + this.logDirectory = `${process.env.ERROR_LOG_PATH}` + } + + getLogFileName(isError = false) { + // Use a different naming convention for error logs if needed + const suffix = isError ? '-error' : ''; + return `${format(new Date(), 'yyyy-MM-dd')}${suffix}.log`; + } + + log(message: string, level = 'info') { + const timestamp = format(new Date(), 'yyyy-MM-dd HH:mm:ss'); + const logMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`; + const isError = level === this.severityLevels.ERROR || level === this.severityLevels.CRITICAL || level === this.severityLevels.WARNING; + const logFilePath = `${this.logDirectory}/${this.getLogFileName(isError)}`; + + appendFileSync(logFilePath, logMessage); + } + + error(message: string, severity = 'ERROR') { + // Ensure the severity level is valid; default to 'ERROR' if not + if (!Object.values(this.severityLevels).includes(severity)) { + severity = this.severityLevels.ERROR; + } + this.log(message, severity); + } +} + +const logger = new Logger(); + +export default logger; diff --git a/src/core/utils/handleCatchApi.ts b/src/core/utils/handleCatchApi.ts new file mode 100644 index 0000000..76352ab --- /dev/null +++ b/src/core/utils/handleCatchApi.ts @@ -0,0 +1,22 @@ +import { NextResponse } from "next/server"; +import BaseError from "../error/BaseError"; + +export default function handleCatchApi(e: unknown): NextResponse { + if (e instanceof BaseError) { + return NextResponse.json({ + code: e.errorCode, + message: e.message, + }, {status: e.statusCode}); + } + if (e instanceof Error) { + return NextResponse.json({ + code: "GENERAL_ERROR", + message: e.message, + }, {status: 500}); + } + + return NextResponse.json({ + code: "GENERAL_ERROR", + message: "Unexpected", + }, { status: 500 }); +} diff --git a/src/modules/auth/error/AuthError.ts b/src/modules/auth/error/AuthError.ts index beee2b2..1d4b585 100644 --- a/src/modules/auth/error/AuthError.ts +++ b/src/modules/auth/error/AuthError.ts @@ -7,11 +7,13 @@ export const AuthErrorCodes = [ "INVALID_JWT_TOKEN", "JWT_SECRET_EMPTY", "USER_ALREADY_EXISTS", + "ALREADY_LOGGED_IN" ] as const; interface AuthErrorOptions { message?: string; errorCode: (typeof AuthErrorCodes)[number] | (string & {}); + statusCode?: number; } export default class AuthError extends BaseError { @@ -21,6 +23,7 @@ export default class AuthError extends BaseError { super({ errorCode: options.errorCode, message: options.message, + statusCode: options.statusCode, }); this.errorCode = options.errorCode; diff --git a/src/modules/auth/formSchemas/signInSchema.ts b/src/modules/auth/formSchemas/signInSchema.ts new file mode 100644 index 0000000..c129677 --- /dev/null +++ b/src/modules/auth/formSchemas/signInSchema.ts @@ -0,0 +1,8 @@ +import { z } from "zod"; + +const signInSchema = z.object({ + email: z.string().email(), + password: z.string().min(1), +}); + +export default signInSchema; \ No newline at end of file diff --git a/src/modules/auth/utils/getTokenFromHeaders.ts b/src/modules/auth/utils/getTokenFromHeaders.ts new file mode 100644 index 0000000..51cf8bc --- /dev/null +++ b/src/modules/auth/utils/getTokenFromHeaders.ts @@ -0,0 +1,10 @@ +export default function getTokenFromHeaders(headers: Headers) { + const authorizationHeader = headers.get('authorization'); + if (authorizationHeader) { + const parts = authorizationHeader.split(' '); + if (parts.length === 2 && parts[0] === 'Bearer') { + return parts[1]; + } + } + return null; +}