Added logger
This commit is contained in:
parent
48d079254e
commit
8c279b5cd7
4
.env
4
.env
|
|
@ -1,3 +1,5 @@
|
|||
DATABASE_URL=mysql://root:root@localhost:3306/dashboard_template
|
||||
|
||||
JWT_SECRET=
|
||||
JWT_SECRET=
|
||||
|
||||
ERROR_LOG_PATH=./logs
|
||||
3
env.ts
3
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);
|
||||
|
||||
|
|
|
|||
1
logs/.gitignore
vendored
Normal file
1
logs/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
*.log
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
38
src/app/api/login/route.ts
Normal file
38
src/app/api/login/route.ts
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
44
src/core/logger/Logger.ts
Normal file
44
src/core/logger/Logger.ts
Normal file
|
|
@ -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;
|
||||
22
src/core/utils/handleCatchApi.ts
Normal file
22
src/core/utils/handleCatchApi.ts
Normal file
|
|
@ -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 });
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
8
src/modules/auth/formSchemas/signInSchema.ts
Normal file
8
src/modules/auth/formSchemas/signInSchema.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { z } from "zod";
|
||||
|
||||
const signInSchema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(1),
|
||||
});
|
||||
|
||||
export default signInSchema;
|
||||
10
src/modules/auth/utils/getTokenFromHeaders.ts
Normal file
10
src/modules/auth/utils/getTokenFromHeaders.ts
Normal file
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user