Skip to content

Commit

Permalink
Implement Course and Exam Payment CRUD logic (#15)
Browse files Browse the repository at this point in the history
* feat: Add `PaymentEntity`

* refactor: Rename payment entity and update module export

* feat: add create payment route

* Update the 'amount' field decimal precision

* feat: add `getAllPayments` and `getPaymentById` endpoints

* feat: Add update and delete payments endpoints

* feat: Add payments router to global router

* feat: Add filter to `PaymentService.getAllPayments` method

* feat: Add endpoint to retrieve sender and receiver payment data

* fix: Importation error

* feat: Add payment entity to `AppDataSource`

* feat: Add ID parameter validation to payments route

* fix: Preserve 'this' context in callback function
  • Loading branch information
cjScrypt authored Feb 26, 2025
1 parent 1ec587f commit b6c06a5
Show file tree
Hide file tree
Showing 10 changed files with 456 additions and 6 deletions.
5 changes: 3 additions & 2 deletions config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@ import { UserCourse } from "../entities/userCourse.entity.js"
import UserEntity from "../entities/user.entity.js";
import Job from "../entities/job.entity.js";
import Notification from "../entities/notification.entity.js";
import { Payment } from "../entities/payment.entity.js";

dotenv.config()

const AppDataSource = new DataSource({
type: "postgres",
host: process.env.DB_HOST,
port: Number.parseInt(process.env.DB_PORT),
port: Number.parseInt(process.env.DB_PORT || '5432'),
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
synchronize: true,
logging: true,
entities: [UserEntity, Course, UserCourse, Job, Notification],
entities: [UserEntity, Course, UserCourse, Job, Notification, Payment],
subscribers: [],
migrations: [],
})
Expand Down
113 changes: 113 additions & 0 deletions controllers/payment.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { PaymentService } from "../services/paymentServices.js";
import { AppError } from "../utils/errors.js";
import { idParamValidation } from "../validation/common.validation.js";
import {
createPaymentValidation, updatePaymentValidation
} from "../validation/payment.validation.js";


class PaymentController {
constructor() {
this.paymentService = new PaymentService();
}

async createPayment(req, res) {
try {
const { error, value } = createPaymentValidation(req.body);
if (error) {
return res.status(400).json({ message: error.details[0].message });
}
const payment = await this.paymentService.createPayment(value);

res.status(201).json({ data: payment });
} catch (error) {
res.status(400).json({ message: error.message });
}
}

async getSenderPayments(req, res) {
try {
const senderId = idParamValidation(req.params);
const payments = await this.paymentService.getSenderPayments(senderId);

res.status(200).json({ data: payments });
} catch (error) {
if (error instanceof AppError) {
res.status(error.statusCode).json({ message: error.message });
} else {
res.status(400).json({ message: error.message });
}
}
}

async getReceiverPayments(req, res) {
try {
const receiverId = req.params.id;
const payments = await this.paymentService.getReceiverPayments(receiverId);

res.status(200).json({ data: payments });
} catch (error) {
if (error instanceof AppError) {
res.status(error.statusCode).json({ message: error.message });
} else {
res.status(400).json({ message: error.message });
}
}
}

async getAllPayments(req, res) {
try {
const payments = await this.paymentService.getAllPayments();

res.status(200).json({ data: payments });
} catch (error) {
res.status(400).json({ message: error.message });
}
}

async getPaymentById(req, res) {
try {
const id = req.params.id;
const payment = await this.paymentService.getPaymentByid(id);

res.status(200).json({ data: payment });
} catch (error) {
if (error instanceof AppError) {
res.status(error.statusCode).json({ message: error.message });
} else {
res.status(400).json({ message: error.message });
}
}
}

async updatePayment(req, res) {
try {
const id = idParamValidation(req.params);
const { error, value } = updatePaymentValidation(req.body);
if (error) {
return res.status(400).json({ message: error.details[0].message });
}
const payment = await this.paymentService.updatePayment(id, value);

res.status(200).json({ data: payment });
} catch (error) {
if (error instanceof AppError) {
res.status(error.statusCode).json({ message: error.message });
} else {
res.status(400).json({ message: error.message });
}
}
}

async deletePayment(req, res) {
try {
const id = req.params.id;
await this.paymentService.deletePayment(id);
res.status(200).json({ message: "Delete operation was successful"});
} catch (error) {
res.status(400).json({ error: error.message });
}
}
}

export default new PaymentController();
71 changes: 71 additions & 0 deletions entities/payment.entity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { EntitySchema } from 'typeorm';

export const Payment = new EntitySchema({
name: 'Payment',
tableName: 'payment',
columns: {
id: {
primary: true,
type: 'int',
generated: true,
},
senderId: {
type: 'int',
nullable: true,
},
receiverId: {
type: 'int',
nullable: true,
},
date: {
type: 'timestamptz',
default: () => "CURRENT_TIMESTAMP",
nullable: false,
},
amount: {
type: 'decimal',
precision: 10,
scale: 5,
nullable: false,
},
payment_code: {
type: 'varchar',
length: 50,
nullable: false,
unique: true
},
transaction_type: {
type: "enum",
enum: [
"course_creation",
"course_enrollment",
"certification_exam"
],
nullable: false,
},
created_at: {
type: 'timestamptz',
default: () => 'CURRENT_TIMESTAMP'
},
},
relations: {
sender: {
type: "many-to-one",
target: "User",
joinColumn: {
name: 'senderId',
referencedColumnName: 'id'
},
onDelete: 'SET NULL'
},
receiver: {
type: 'many-to-one',
target: 'User',
joinColumn: {
name: 'receiverId',
referencedColumnName: 'id'
},
onDelete: 'SET NULL'
}
}
});
50 changes: 49 additions & 1 deletion routes/paymentRoutes.js
Original file line number Diff line number Diff line change
@@ -1 +1,49 @@
//start
import express from "express";
import { authenticateToken } from "../middlewares/auth.middleware.js";
import PaymentController from "../controllers/payment.controller.js";

const router = express.Router();

router.post(
"/",
authenticateToken,
(req, res) => PaymentController.createPayment(req, res)
);

router.get(
"/sender/:id",
authenticateToken,
(req, res) => PaymentController.getSenderPayments(req, res)
);

router.get(
"/receiver/:id",
authenticateToken,
(req, res) => PaymentController.getReceiverPayments(req, res)
);

router.get(
"/",
authenticateToken,
(req, res) => PaymentController.getAllPayments(req, res)
);

router.get(
"/:id",
authenticateToken,
(req, res) => PaymentController.getPaymentById(req, res)
);

router.put(
"/:id",
authenticateToken,
(req, res) => PaymentController.updatePayment(req, res)
);

router.delete(
"/:id",
authenticateToken,
(req, res) => PaymentController.deletePayment(req, res)
);

export default router;
8 changes: 5 additions & 3 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import express, { json } from 'express';
import authRouter from './routes/auth.routes.js';
import jobRoutes from './routes/jobRoutes.js';
import courseRoutes from "./routes/courseRoutes.js"
import notificationRoutes from "./routes/notificationRoutes.js"
import notificationRoutes from "./routes/notificationRoutes.js";
import paymentRoutes from "./routes/paymentRoutes.js";

import helmet from 'helmet';
import cors from 'cors';
Expand All @@ -25,8 +26,9 @@ AppDataSource.initialize()
// Routes
app.use('/api/auth', authRouter);
app.use('/api/jobs', jobRoutes);
app.use("/api/courses", courseRoutes)
app.use("/api/notifications", notificationRoutes)
app.use("/api/courses", courseRoutes);
app.use("/api/notifications", notificationRoutes);
app.use("/api/payments", paymentRoutes);

// Error handling
app.use((err, req, res, next) => {
Expand Down
62 changes: 62 additions & 0 deletions services/paymentServices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import AppDataSource from "../config/config.js";
import { Payment } from "../entities/payment.entity.js";
import userService from "../services/userServices.js";
import { AppError } from "../utils/errors.js";

export class PaymentService {
constructor() {
this.repository = AppDataSource.getRepository(Payment);
}

async createPayment(paymentData) {
const existingPayment = await this.repository.findOne({
where: {
payment_code: paymentData.payment_code
}
});
if (existingPayment) {
throw new Error("Duplicate Payment Code");
}
const payment = this.repository.create(paymentData);

return this.repository.save(payment);
}

async getAllPayments(filter) {
return this.repository.find({ where: filter });
}

async getSenderPayments(senderId) {
const sender = await userService.getUserById(senderId);

return this.getAllPayments({ senderId });
}

async getReceiverPayments(receiverId) {
const receiver = await userService.getUserById(receiverId);

return this.getAllPayments({ receiverId });
}

async getPaymentByid(id) {
const payment = await this.repository.findOne({ where: { id } });
if (!payment) {
throw new AppError(`Payment with ID ${id} not found.`, 404);
}

return payment;
}

async updatePayment(id, updateData) {
const payment = await this.getPaymentByid(id);
Object.assign(payment, updateData);

return this.repository.save(payment);
}

async deletePayment(id) {
const payment = await this.getPaymentByid(id);

return this.repository.delete(id);
}
}
22 changes: 22 additions & 0 deletions services/userServices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import AppDataSource from "../config/config.js";
import { AppError } from "../utils/errors.js";
import User from "../entities/user.entity.js";

class UserService {
constructor() {
this.userRepository = AppDataSource.getRepository(User);
}

async getUserById(id) {
const user = await this.userRepository.findOne({
where: { id }
});
if (!user) {
throw new AppError(`User with this ID ${id} not found`, 404);
}

return user;
}
}

export default new UserService();
6 changes: 6 additions & 0 deletions utils/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class AppError extends Error {
constructor(message, statusCode=400) {
super(message);
this.statusCode = statusCode
}
}
Loading

0 comments on commit b6c06a5

Please sign in to comment.