Merge pull request #5 from kavehhn174/transaction

Transaction
This commit is contained in:
Kaveh HajiNajaf
2025-04-19 23:38:47 +03:30
committed by GitHub
12 changed files with 2166 additions and 1862 deletions
-1
View File
@@ -1,4 +1,3 @@
console.log('Running Something')
require('dotenv').config({ path: './config/.env' })
const express = require('express')
const databaseConnection = require('./setup/databaseConnection.js')
+4
View File
@@ -18,6 +18,10 @@ const belongingSchema = new mongoose.Schema(
type: ObjectID,
ref: 'Category',
},
unit: {
type: String,
required: true,
},
description: String,
relatedProjects: [String],
location: {
@@ -0,0 +1,49 @@
const catchAsync = require('../../globalErrorHandler/catchAsync')
const transactionService = require('./transactionService')
exports.createTransaction = catchAsync(async (req, res, next) => {
const transactions = await transactionService.createTransaction(req.body)
res.status(201).json({
status: 'success',
results: transactions.length,
data: transactions,
})
})
exports.getUserTransaction = catchAsync(async (req, res, next) => {
const transactions = await transactionService.getUserTransaction(req.user._id)
res.status(200).json({
status: 'success',
results: transactions.length,
data: transactions,
})
})
exports.getBelongingInventory = catchAsync(async (req, res, next) => {
const result = await transactionService.getBelongingInventory(req.params.belongingId)
res.status(200).json({
status: 'success',
data: result,
})
})
exports.updateTransaction = catchAsync(async (req, res, next) => {
const transaction = await transactionService.updateTransaction(req.params.transactionId, req.body)
res.status(200).json({
status: 'success',
data: transaction,
})
})
exports.deleteTransaction = catchAsync(async (req, res, next) => {
await transactionService.deleteTransaction(req.params.transactionId)
res.status(204).json({
status: 'success',
data: null,
})
})
@@ -0,0 +1,55 @@
const mongoose = require('mongoose')
const ObjectID = mongoose.Schema.Types.ObjectId
const transactionSchema = new mongoose.Schema(
{
type: {
type: String,
enum: ['withdraw', 'deposit'],
required: true,
},
status: {
type: String,
enum: ['failed', 'pending', 'finished'],
required: true,
},
source: {
type: String,
enum: ['normal'],
},
value: {
type: Number,
required: true,
},
date: {
type: Date,
default: Date.now(),
},
description: String,
userId: {
type: ObjectID,
ref: 'User',
},
belongingId: {
type: ObjectID,
ref: 'Belonging',
},
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
timestamps: true,
}
)
// Add individual indexes for frequently queried fields
transactionSchema.index({ type: 1 })
transactionSchema.index({ status: 1 })
transactionSchema.index({ value: 1 })
// Add compound indexes for common query patterns in aggregation
transactionSchema.index({ belongingId: 1, status: 1 })
transactionSchema.index({ belongingId: 1, type: 1, status: 1 })
const Transaction = mongoose.model('Transaction', transactionSchema)
module.exports = Transaction
@@ -0,0 +1,40 @@
const express = require('express')
const transactionController = require('./transactionController')
// const perm = require('../../middlewares/permissionMiddleware')
const loginCheck = require('../../middlewares/loginCheck')
const router = new express.Router()
router.use(loginCheck.loginProtect)
router.get(
'/admin/self',
// perm.requires('getallbelongings'),
transactionController.getUserTransaction
)
router.post(
'/admin/create',
// perm.requires('getallbelongings'),
transactionController.createTransaction
)
router.get(
'/admin/belonging/:belongingId/inventory',
// perm.requires('getallbelongings'),
transactionController.getBelongingInventory
)
router.patch(
'/admin/update/:transactionId',
// perm.requires('getallbelongings'),
transactionController.updateTransaction
)
router.delete(
'/admin/delete/:transactionId',
// perm.requires('getallbelongings'),
transactionController.deleteTransaction
)
module.exports = router
@@ -0,0 +1,150 @@
const Transaction = require('./transactionModel')
const Belonging = require('../belonging/belongingModel')
const AppError = require('../../globalErrorHandler/AppError')
const mongoose = require('mongoose')
const ObjectID = mongoose.Schema.Types.ObjectId
const { client } = require('../../setup/typesense')
const TsApiFeatures = require('../../utils/TsApiFeatures')
const filterObj = (obj, ...allowedFields) => {
const newObj = {}
Object.keys(obj).forEach((el) => {
if (allowedFields.includes(el)) newObj[el] = obj[el]
})
return newObj
}
const createTransaction = async (data) => {
if (data.type == 'withdraw') {
// Aggregate transactions to calculate inventory
const result = await Transaction.aggregate([
{
$match: {
belongingId: ObjectID,
status: 'finished',
},
},
{
$group: {
_id: '$belongingId',
totalInventory: {
$sum: '$value',
},
},
},
])
if (result[0].totalInventory <= 0) throw new AppError('Invalid Transaction', 404, {})
}
return Transaction.create({
type: data.type,
status: data.status,
source: data.source,
value: data.type === 'withdraw' ? data.value * -1 : data.value,
date: data.Date,
description: data.description,
userId: data.userId,
belongingId: data.belongingId,
})
}
const getUserTransaction = async (userId) => {
return Transaction.find({ userId }, null, null)
}
const getBelongingInventory = async (belongingId) => {
const belonging = await Belonging.findById(belongingId, null, null)
if (!belonging) {
throw new AppError('NoBelongingFound', 404, {})
}
// Simplified aggregation to calculate inventory metrics
const result = await Transaction.aggregate([
{
$match: {
belongingId: ObjectID,
$or: [{ status: 'finished' }, { status: 'pending' }],
},
},
{
$facet: {
// Calculate total inventory from finished transactions
finishedInventory: [
{ $match: { status: 'finished' } },
{
$group: {
_id: null,
total: { $sum: '$value' },
},
},
],
// Calculate pending deposits
pendingDepositsInventory: [
{
$match: {
status: 'pending',
type: 'deposit',
},
},
{
$group: {
_id: null,
total: { $sum: '$value' },
},
},
],
// Calculate pending withdrawals
pendingWithdrawsInventory: [
{
$match: {
status: 'pending',
type: 'withdraw',
},
},
{
$group: {
_id: null,
total: { $sum: '$value' },
},
},
],
},
},
{
// Restructure the output to get only the values
$project: {
finishedInventory: { $ifNull: [{ $arrayElemAt: ['$finishedInventory.total', 0] }, 0] },
pendingDepositsInventory: { $ifNull: [{ $arrayElemAt: ['$pendingDepositsInventory.total', 0] }, 0] },
pendingWithdrawsInventory: { $ifNull: [{ $arrayElemAt: ['$pendingWithdrawsInventory.total', 0] }, 0] },
},
},
])
return { belongingCode: belonging.belongingCode, ...result[0] }
}
const updateTransaction = async (transactionId, data) => {
const filteredBody = filterObj(data, 'status')
const transaction = await Transaction.findOneAndUpdate({ _id: transactionId }, filteredBody, { new: true })
if (!transaction) {
throw new AppError('NoTransactionFound', 404, {})
}
return transaction
}
const deleteTransaction = async (transactionId) => {
const transaction = await Transaction.findOneAndDelete({ _id: transactionId })
if (!transaction) {
throw AppError('NoTransactionFound', 404, {})
}
return null
}
module.exports = { createTransaction, getUserTransaction, getBelongingInventory, updateTransaction, deleteTransaction }
+2 -2
View File
@@ -5,8 +5,8 @@ const loginCheck = require('../../middlewares/loginCheck')
const router = new express.Router()
router.post('/user/signup-email', userController.signup)
router.post('/user/login-email', userController.loginUser)
router.post('/signup-email', userController.signup)
router.post('/login-email', userController.loginUser)
router.post('/logout', userController.logoutUser)
router.use(loginCheck.loginProtect)
+2 -2
View File
@@ -1,8 +1,8 @@
MONGODB_URL=mongodb://root:DBKvf305VCc0csA5@services.irn9.chabokan.net:20690/marbm?authSource=admin
MONGODB_URL=mongodb://root:yBi0FXMb1rWNhFxu@services.irn12.chabokan.net:58178/my-app?authSource=admin
NODE_ENV=development
PORT=3000
JWT_SECRET=Y@RJ$WT!@K0NS@MPL!
REDIS_URL= redis://:JrKdaxaQQGTwpxgm@services.irn9.chabokan.net:6322
REDIS_URL= redis://:GFis6zU4DXW9Q7NQ@services.irn12.chabokan.net:19794/0
LOGIN_ATTEMPT_LIMIT=900
SUPER_ADMIN_ROLE_ID=66a2da5b9b989a8909948848
TYPE_SENSE_URL=services.irn9.chabokan.net
+10 -10
View File
@@ -1,13 +1,13 @@
services:
# api-server:
# build: ./
# ports:
# - 3000:3000
# environment:
# - MONGODB_URL=mongodb://admin:*135792468*@mongo:27017/marbm?authSource=admin
# - NODE_ENV=development
# - PORT=3000
# - REDIS_URL=redis://:135792468@redis:6379/0
# api-server:
# build: ./
# ports:
# - 3000:4000
# environment:
# - MONGODB_URL=mongodb://admin:*135792468*@bm_mongo:27017/marbm?authSource=admin
# - NODE_ENV=development
# - PORT=3000
# - REDIS_URL=redis://:135792468@bm_redis:6379/0
bm_minio:
image: minio/minio
ports:
@@ -42,7 +42,7 @@ services:
image: typesense/typesense:27.1
command: ./typesense-server
ports:
- "8108:8108"
- '8108:8108'
environment:
TYPESENSE_API_KEY: vYQq7pTgl6ACoRy
TYPESENSE_DATA_DIR: /data
+1 -1
View File
@@ -4,7 +4,7 @@
"main": "app.js",
"scripts": {
"seed": "node ./seeder/seed.js",
"start": "node app.js",
"start": "nodemon app.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
+7
View File
@@ -4,6 +4,11 @@ const roleRoutes = require('../components/role/roleRoutes')
const permissionRoutes = require('../components/permission/permissionRoutes')
const belongingRoutes = require('../components/belonging/belongingRoutes')
const categoryRoutes = require('../components/category/categoryRoutes')
const transactionRoutes = require('../components/transaction/transactionRoutes')
// const codeRoutes = require('../components/code/codeRoutes')
// const CRMRoutes = require('../components/pages/crmRoutes')
// const LoginRoute = require('../components/pages/loginRoutes')
const fileRoutes = require('../components/file/fileRoutes')
const featureRoutes = require('../components/feature/featureRoutes')
const routerEndpoints = new express.Router()
@@ -13,6 +18,8 @@ routerEndpoints.use('/permissions', permissionRoutes)
routerEndpoints.use('/belongings', belongingRoutes)
routerEndpoints.use('/roles', roleRoutes)
routerEndpoints.use('/categories', categoryRoutes)
routerEndpoints.use('/transactions', transactionRoutes)
routerEndpoints.use('/files', fileRoutes)
routerEndpoints.use('/features', featureRoutes)
module.exports = routerEndpoints