Skip to content

Commit

Permalink
Merge pull request #19 from MRoyhanF/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
MRoyhanF authored Nov 19, 2024
2 parents 39e930f + 5bd43f7 commit 0b5114f
Show file tree
Hide file tree
Showing 22 changed files with 2,422 additions and 157 deletions.
1,579 changes: 1,533 additions & 46 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
"dependencies": {
"@faker-js/faker": "^9.0.3",
"@prisma/client": "^5.21.0",
"@sentry/node": "^8.38.0",
"@sentry/profiling-node": "^8.38.0",
"@sentry/tracing": "^7.114.0",
"bcrypt": "^5.1.1",
"dotenv": "^16.4.5",
"ejs": "^3.1.10",
Expand All @@ -35,7 +38,9 @@
"jsonwebtoken": "^9.0.2",
"morgan": "^1.10.0",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.16",
"pg": "^8.13.0",
"socket.io": "^4.8.1",
"swagger-ui-express": "^5.0.1"
},
"devDependencies": {
Expand Down
30 changes: 25 additions & 5 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
import "dotenv/config";
// import dotenv from "dotenv";
import http from "http";
import app from "./server/app.js";
import { Server as SocketIOServer } from "socket.io";
import listEndpoints from "express-list-endpoints";

// Menentukan environment dan file .env sesuai environment
// const env = process.env.NODE_ENV || "development";
// dotenv.config({ path: `.env.${env}` });

const PORT = process.env.PORT || 3000;

const server = http.createServer(app);
export const io = new SocketIOServer(server);

io.on("connection", (socket) => {
const total = io.engine.clientsCount;
console.log(`${total} user connected`);
// register
socket.on("register", (msg) => {
const responseMessage = `Welcome ${msg} with email...`;
io.emit("register", responseMessage);
// io.emit("register", msg);
// console.log(`pesan: ${msg}`);
});
// change password
socket.on("changePassword", (msg) => {
const responseMessage = `Password ${msg} Changges...`;
io.emit("changePassword", responseMessage);
});
// Handle disconnection
socket.on("disconnect", () => {
console.log(`${total} user disconnected`);
});
});

// Start the server
const start = async () => {
try {
console.log("=====================================================");
Expand Down
27 changes: 21 additions & 6 deletions src/server/app.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import "./config/instrument.js"
import * as Sentry from "@sentry/node";
import path from "path";
import { fileURLToPath } from 'url';
import express from "express";
Expand All @@ -9,10 +11,8 @@ import authRoutes from "./routes/authRoutes.js";
import userRoutes from "./routes/userRoutes.js";
import accountRoutes from "./routes/accountRoutes.js";
import transactionRoutes from "./routes/transactionRoutes.js";

import * as indexController from "./controllers/indexController.js";

import { handleError } from "./middlewares/errorHandler.js";
import indexRoutes from "./routes/indexRoutes.js";
import ErrorHandler from "./middlewares/errorHandler.js";

const app = express();
const __filename = fileURLToPath(import.meta.url);
Expand All @@ -25,15 +25,30 @@ app.use(express.urlencoded({ extended: true }));
app.set('view engine', 'ejs');
app.set('views', viewsFolder);

app.use((req, res, next) => {
console.log(`[REQUEST] ${req.method} ${req.url}`);
res.on("finish", () => {
console.log(`[RESPONSE] ${res.statusCode} - ${res.statusMessage}`);
});
next();
});

app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));

// API Routes
app.get("/", indexController.homePage);
app.use("/", indexRoutes);
app.use("/api/v1/auth", authRoutes);
app.use("/api/v1/users", userRoutes);
app.use("/api/v1/accounts", accountRoutes);
app.use("/api/v1/transactions", transactionRoutes);
app.get("/api/v1/error", () => {
throw new Error("This is an error route");
});

// sentry setup
Sentry.setupExpressErrorHandler(app);

app.use(handleError);
// app.use(handleError);
app.use(ErrorHandler.handleError);

export default app;
8 changes: 8 additions & 0 deletions src/server/config/instrument.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { init } from "@sentry/node";
import { nodeProfilingIntegration } from "@sentry/profiling-node";

init({
dsn: process.env.SENTRY_DSN,
integrations: [nodeProfilingIntegration()],
tracesSampleRate: 1.0,
});
11 changes: 11 additions & 0 deletions src/server/config/mailer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import nodemailer from "nodemailer";

export const transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
service: "gmail",
port: 587,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASSWORD,
},
});
68 changes: 48 additions & 20 deletions src/server/controllers/accountController.js
Original file line number Diff line number Diff line change
@@ -1,89 +1,117 @@
import { AccountService } from "../services/accountService.js";
import { UserService } from "../services/userService.js";
import { AccountValidation } from "../validations/accountValidation.js";
import { ErrorHandler } from "../middlewares/errorHandler.js";

import ResponseHandler from "../utils/response.js";
import { Error400, Error404 } from "../utils/custom_error.js";

class AccountController {
constructor() {
this.accountService = new AccountService();
this.userService = new UserService();
this.response = new ResponseHandler();
}

async getAllAccount(req, res, next) {
async getAllAccount(req, res) {
try {
const account = await this.accountService.getAllAccount();
res.status(200).json({ Status: "Success", Data: account });
// res.status(200).json({ Status: "Success", Data: account });
return this.response.res200("Success", account, res);
} catch (error) {
next(new ErrorHandler(error.statusCode || 500, error.message));
if (error instanceof Error400) {
return this.response.res400(error.message, res);
} else {
return this.response.res500(res);
}
}
}

async getAccountById(req, res, next) {
async getAccountById(req, res) {
try {
const account = await this.accountService.getAccountById(req.params.id);
if (!account) throw new ErrorHandler(404, "User Not Found");
res.status(200).json({ Status: "Success", Data: account });
if (!account) throw new Error404("Account not found");
return this.response.res200("Success", account, res);
} catch (error) {
next(new ErrorHandler(error.statusCode || 500, error.message));
if (error instanceof Error404) {
return this.response.res404(error.message, res);
} else {
return this.response.res500(res);
}
}
}

async createAccount(req, res, next) {
async createAccount(req, res) {
try {
AccountValidation.validate(AccountValidation.createAccountSchema, req.body);

const validAccount = await this.userService.getUserById(req.body.user_id);
if (!validAccount) throw new ErrorHandler(404, "User Not Found");
if (!validAccount) throw new Error404("User not found");

const existingAccount = await this.accountService.getAccountByUser(req.body.user_id);
if (existingAccount) {
throw new ErrorHandler(400, "User Already Has an Account");
throw new Error400("User already has an account");
}

const account = await this.accountService.createAccount(req.body);
res.status(201).json({ Status: "Success", Data: account });
} catch (error) {
next(new ErrorHandler(error.statusCode || 500, error.message));
if (error instanceof Error400) {
return this.response.res400(error.message, res);
} else if (error instanceof Error404) {
return this.response.res404(error.message, res);
} else {
return this.response.res500(res);
}
}
}

async updateAccount(req, res, next) {
async updateAccount(req, res) {
try {
AccountValidation.validate(AccountValidation.updateAccountSchema, req.body);

const account = await this.accountService.getAccountById(req.params.id);

if (!account) {
throw new ErrorHandler(404, "Account not found");
throw new Error404("Account not found");
}

if (req.body.user_id) {
const existingAccount = await this.accountService.getAccountByUser(req.body.user_id);
if (existingAccount && existingAccount.id !== account.id) {
throw new ErrorHandler(400, "User already has an account");
throw new Error400("User already has an account");
}
if (!existingAccount) {
throw new ErrorHandler(404, "User not found");
throw new Error404("User not found");
}
}

const updatedAccount = await this.accountService.updateAccount(req.params.id, req.body);

res.status(200).json({ status: "Success", message: "Account updated successfully", data: updatedAccount });
} catch (error) {
next(new ErrorHandler(error.statusCode || 500, error.message));
if (error instanceof Error400) {
return this.response.res400(error.message, res);
} else if (error instanceof Error404) {
return this.response.res404(error.message, res);
} else {
return this.response.res500(res);
}
}
}

async deleteAccount(req, res, next) {
async deleteAccount(req, res) {
try {
const account = await this.accountService.getAccountById(req.params.id);
if (!account) throw new ErrorHandler(404, "Account Not Found");
if (!account) throw new Error404("Account not found");

await this.accountService.deleteAccount(req.params.id);
res.status(200).json({ Status: "Success", Message: "Account Deleted Successfully" });
} catch (error) {
next(new ErrorHandler(error.statusCode || 500, error.message));
if (error instanceof Error404) {
return this.response.res404(error.message, res);
} else {
return this.response.res500(res);
}
}
}
}
Expand Down
60 changes: 42 additions & 18 deletions src/server/controllers/authController.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { UserService } from "../services/userService.js";
import { ErrorHandler } from "../middlewares/errorHandler.js";
import jwt from "jsonwebtoken";
import { UserValidation } from "../validations/userValidation.js";
import { storeToken } from "../utils/tokenStore.js";
import { io } from "../../main.js";
import bcrypt from "bcrypt";

import ResponseHandler from "../utils/response.js";
import { Error400, Error404 } from "../utils/custom_error.js";

class AuthController {
constructor() {
this.userService = new UserService();
this.response = new ResponseHandler();
}

async register(req, res, next) {
async register(req, res) {
try {
const { name, email, password, profile } = req.body;

Expand All @@ -24,13 +28,17 @@ class AuthController {
let imageUrl = null;
if (req.file) {
if (req.file.size > 1024 * 1024 * 2) {
throw new ErrorHandler(400, "File size is too large");
io.emit("register", `Failed Registration File size is too large`);
throw new Error400(400, "File size is too large");
}
imageUrl = await this.userService.uploadImageToImageKit(req.file);
}

const validEmail = await this.userService.getUserByEmail(email);
if (validEmail) throw new ErrorHandler(400, "Email already exists");
if (validEmail) {
io.emit("register", `Failed Registration ${email} Already Exist`);
throw new Error400("Email already exists");
}

const hashedPassword = await bcrypt.hash(password, 10);

Expand All @@ -44,44 +52,60 @@ class AuthController {
imageUrl
);

res.status(201).json({ status: "Success", data: newUser });
io.emit("register", `Welcome ${name} with email ${email}`);
return this.response.res201("User created successfully", newUser, res);
} catch (error) {
next(new ErrorHandler(error.statusCode || 500, error.message));
if (error instanceof Error400) {
return this.response.res400(error.message, res);
} else {
console.log(error.message);
return this.response.res500(res);
}
}
}

async login(req, res, next) {
async login(req, res) {
try {
const { email, password, confmPassword } = req.body;

const user = await this.userService.getUserByEmail(email);
if (!user) throw new ErrorHandler(404, "User not found");
if (!user) throw new Error404("User not found");

const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) throw new ErrorHandler(400, "Invalid password");
if (!isMatch) throw new Error400("Invalid password");

if (password !== confmPassword) throw new ErrorHandler(400, "Password does not match");
if (password !== confmPassword) throw new Error400("Password does not match");

// Generate JWT token
const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET, { expiresIn: "1h" });
const secretKey = process.env.JWT_SECRET || "secret";
const token = jwt.sign({ id: user.id, name: user.name }, secretKey, { expiresIn: "1h" });

res.status(200).json({ status: "Success", token });
return this.response.res200("Login successful", { token }, res);
} catch (error) {
next(new ErrorHandler(error.statusCode || 500, error.message));
if (error instanceof Error400) {
return this.response.res400(error.message, res);
} else if (error instanceof Error404) {
return this.response.res404(error.message, res);
} else {
return this.response.res500(res);
}
}
}

async logout(req, res, next) {
async logout(req, res) {
try {
const token = req.headers.authorization?.split(" ")[1];

if (!token) throw new ErrorHandler(400, "No token provided");
if (!token) throw new Error400("No token provided");

storeToken(token);

res.status(200).json({ status: "Success", message: "User logged out successfully" });
return this.response.res200("User logged out successfully", null, res);
} catch (error) {
next(new ErrorHandler(error.statusCode || 500, error.message));
if (error instanceof Error400) {
return this.response.res400(error.message, res);
} else {
return this.response.res500(res);
}
}
}
}
Expand Down
Loading

0 comments on commit 0b5114f

Please sign in to comment.