From 2d87cc4834b01797199e74ff5e8a9eb48afeaa1d Mon Sep 17 00:00:00 2001 From: kaveh Date: Sat, 4 Jan 2025 22:06:43 +0330 Subject: [PATCH] ADD: SignedUrls for documents, mainImage and images ADD: Bucket and Upload System ADD: New errors ADD: Virtuals for categories ADD: Features system ADD: File management system --- components/belonging/belongingModel.js | 18 +- components/belonging/belongingService.js | 9 +- components/category/categoryModel.js | 10 +- components/category/categoryService.js | 59 +- components/feature/featureController.js | 70 ++ components/feature/featureModel.js | 46 ++ components/feature/featureRoutes.js | 25 + components/feature/featureSeeder.js | 837 +++++++++++++++++++++++ components/feature/featureService.js | 53 ++ components/feature/featureValidator.js | 0 components/file/fileController.js | 103 +++ components/file/fileModel.js | 32 + components/file/fileRoutes.js | 50 ++ components/file/fileService.js | 136 ++++ globalErrorHandler/errors.json | 5 + middlewares/multerMiddleware.js | 24 + package-lock.json | 603 +++++++++++++++- package.json | 2 + setup/routerEndpoints.js | 11 +- utils/upload/bucket.js | 54 ++ utils/upload/minio.js | 30 + 21 files changed, 2100 insertions(+), 77 deletions(-) create mode 100644 components/feature/featureController.js create mode 100644 components/feature/featureModel.js create mode 100644 components/feature/featureRoutes.js create mode 100644 components/feature/featureSeeder.js create mode 100644 components/feature/featureService.js create mode 100644 components/feature/featureValidator.js create mode 100644 components/file/fileController.js create mode 100644 components/file/fileModel.js create mode 100644 components/file/fileRoutes.js create mode 100644 components/file/fileService.js create mode 100644 middlewares/multerMiddleware.js create mode 100644 utils/upload/bucket.js create mode 100644 utils/upload/minio.js diff --git a/components/belonging/belongingModel.js b/components/belonging/belongingModel.js index f3faa91..cd8a6a7 100644 --- a/components/belonging/belongingModel.js +++ b/components/belonging/belongingModel.js @@ -1,6 +1,7 @@ const mongoose = require('mongoose') const ObjectID = mongoose.Schema.Types.ObjectId const AutoIncrement = require('mongoose-sequence')(mongoose) +const { getMultipleSignedUrls, getSignedUrl } = require('../file/fileService') const belongingSchema = new mongoose.Schema( { @@ -27,11 +28,8 @@ const belongingSchema = new mongoose.Schema( }, }, documentNames: [String], - documentUrls: [String], imageNames: [String], - imageUrls: [String], - mainImageUrl: String, - mainImageName: String, + mainImageNames: [String], numId: { type: Number, unique: true, @@ -52,6 +50,18 @@ const belongingSchema = new mongoose.Schema( belongingSchema.plugin(AutoIncrement, { inc_field: 'numId' }) +belongingSchema.methods.getDocumentsSignedUrls = async function () { + return getMultipleSignedUrls(this.documentNames, 'belonging-images') +} + +belongingSchema.methods.getImagesSignedUrls = async function () { + return getMultipleSignedUrls(this.imageNames, 'belonging-images') +} + +belongingSchema.methods.getMainImagedSignedUrl = async function () { + return getMultipleSignedUrls(this.mainImageNames, 'belonging-images') +} + belongingSchema.index({ location: '2dsphere' }) // Enable geospatial queries const Belonging = mongoose.model('Belonging', belongingSchema) diff --git a/components/belonging/belongingService.js b/components/belonging/belongingService.js index 6d576f1..1deeb8c 100644 --- a/components/belonging/belongingService.js +++ b/components/belonging/belongingService.js @@ -12,12 +12,19 @@ const createBelonging = async (data) => { } const getBelonging = async (belongingId) => { - const belonging = await Belonging.findById(belongingId, null, null) + const belonging = await Belonging.findById(belongingId, null, null).lean() if (!belonging) { throw new AppError('NoBelongingFound', 404, {}) } + + belonging.signedDocumentUrls = belonging.getDocumentsSignedUrls() + belonging.signedImageUrls = belonging.getImagesSignedUrls() + belonging.signedMainImageUrls = belonging.getMainImagedSignedUrl() + + + return belonging } diff --git a/components/category/categoryModel.js b/components/category/categoryModel.js index d4d0fb5..2fc0d10 100644 --- a/components/category/categoryModel.js +++ b/components/category/categoryModel.js @@ -12,10 +12,6 @@ const categorySchema = new mongoose.Schema( required: true, unique: true, }, - parentId: { - type: ObjectID, - ref: 'Category', - }, }, { toJSON: { virtuals: true }, @@ -24,5 +20,11 @@ const categorySchema = new mongoose.Schema( } ) +categorySchema.virtual('features', { + ref: 'Feature', + localField: '_id', + foreignField: 'category', +}) + const Category = mongoose.model('Category', categorySchema) module.exports = Category diff --git a/components/category/categoryService.js b/components/category/categoryService.js index f22ed30..c09cd57 100644 --- a/components/category/categoryService.js +++ b/components/category/categoryService.js @@ -1,67 +1,12 @@ const Category = require('./categoryModel') const AppError = require('../../globalErrorHandler/AppError') -module.exports.getCategoryTree = async () => { - const nodes = await Category.find({}) - let tree = [] - for (let i = 0; i < nodes.length; i++) { - if (!nodes[i].parentId) { - const label = nodes[i].nativeName - const children = [] - const key = nodes[i]._id.toString() - const href = '/categories/admin/update/' + nodes[i]._id - const a_attr = { href } - const root = { label, children, key, a_attr } - tree.push(root) - } - } - - tree = recursiveFunction(tree, nodes) - return tree -} - -const recursiveFunction = (tree, categories) => { - if (tree.length > 0) { - for (let i = 0; i < tree.length; i++) { - let subTree = [] - for (let j = 0; j < categories.length; j++) { - if (tree[i].key === categories[j].parentId?.toString()) { - const label = categories[j].nativeName.toString() - const children = [] - const key = categories[j]._id.toString() - const href = '/categories/admin/update/' + categories[j]._id - const a_attr = { href } - subTree.push({ label, children, key, a_attr }) - } - let index = findCategoryIndex(tree, tree[i].label) - tree[index].children = subTree - } - } - for (let i = 0; i < tree.length; i++) { - let index = findCategoryIndex(tree, tree[i].label) - recursiveFunction(tree[index].children, categories) - } - } - return tree -} - -function findCategoryIndex(array, label) { - let index - for (let i = 0; i < array.length; i++) { - if (array[i].label === label) { - index = i - return index - } - } -} - module.exports.getAllCategories = async () => { - return Category.find().populate('parentId') + return Category.find().populate('features') } module.exports.createCategory = async (data) => { - const category = Category.create(data, null) - return category + return Category.create(data, null) } module.exports.getCategory = async (categoryId) => { diff --git a/components/feature/featureController.js b/components/feature/featureController.js new file mode 100644 index 0000000..715d8ed --- /dev/null +++ b/components/feature/featureController.js @@ -0,0 +1,70 @@ +const catchAsync = require('../../globalErrorHandler/catchAsync') +const AppError = require('../../globalErrorHandler/AppError') +const featureService = require('./featureService') + +exports.getAllCategoryFeatures = catchAsync(async (req, res, next) => { + const features = await featureService.getAllCategoryFeatures() + res.status(200).json({ + status: 'success', + results: features.length, + data: features, + }) +}) + +exports.getFeatureByCategory = catchAsync(async (req, res, next) => { + const features = await featureService.getFeatureByCategory(req.params.categoryId) + res.status(200).json({ + status: 'success', + results: features.length, + data: features, + }) +}) + +exports.getAllFeatures = catchAsync(async (req, res, next) => { + const features = await featureService.getAllFeatures() + + res.status(200).json({ + status: 'success', + results: features.length, + data: features, + }) +}) + +exports.createFeature = catchAsync(async (req, res, next) => { + const feature = await featureService.createFeatures(req.body) + + res.status(201).json({ + status: 'success', + data: feature, + }) +}) + +exports.getFeature = catchAsync(async (req, res, next) => { + const feature = await featureService.getFeatures(req.params.id) + + if (!feature) { + throw AppError('NoGroupFound', 404, {}) + } + + res.status(200).json({ + status: 'success', + data: feature, + }) +}) + +exports.updateFeature = catchAsync(async (req, res, next) => { + const updatedFeature = await featureService.updateFeatures(req.params.groupId, req.body) + res.status(200).json({ + status: 'success', + data: updatedFeature, + }) +}) + +exports.deleteFeature = catchAsync(async (req, res, next) => { + await featureService.deleteFeatures(req.params.groupId) + + res.status(204).json({ + status: 'success', + data: null, + }) +}) \ No newline at end of file diff --git a/components/feature/featureModel.js b/components/feature/featureModel.js new file mode 100644 index 0000000..333d28b --- /dev/null +++ b/components/feature/featureModel.js @@ -0,0 +1,46 @@ +const mongoose = require('mongoose') +const ObjectID = mongoose.Schema.Types.ObjectId + +const featureSchema = new mongoose.Schema({ + nativeName: { + type: String, + required: true, + }, + englishName: { + type: String, + required: true, + }, + category: { + type: ObjectID, + ref: 'Category', + required: true, + }, + predefinedValues: [String], + unit: String, + isFilterable: { + type: Boolean, + default: true, + }, + featureType: { + type: String, + enum: ['string', 'boolean', 'number', 'date'], + default: 'string', + }, + filterType: { + type: String, + enum: ['exact', 'range', 'multi', 'bool'], + default: 'exact', + }, + tag: String, + priority: Number, +}, { + toJSON: { virtuals: true }, + toObject: { virtuals: true }, + timestamps: true, +}) + +featureSchema.index({ englishName: 1, category: 1 }, { unique: true }) + +const Feature = mongoose.model('Feature', featureSchema) + +module.exports = Feature diff --git a/components/feature/featureRoutes.js b/components/feature/featureRoutes.js new file mode 100644 index 0000000..a5d673d --- /dev/null +++ b/components/feature/featureRoutes.js @@ -0,0 +1,25 @@ +const express = require('express') +const router = new express.Router() +const FeatureDefinition = require('./featureModel') +const featureController = require('./featureController') +const perm = require('../../middlewares/permissionMiddleware') +const loginCheck = require('../../middlewares/loginCheck') +const permission = require('../../middlewares/permissionMiddleware') + +router.use(loginCheck.loginProtect) + +router.get('/admin/get-all-category', perm.requires('getallfeatures'), featureController.getAllCategoryFeatures) + +router.get('/admin/get-by-category/:groupId', perm.requires('getallfeatures'), featureController.getFeatureByCategory) + +router.get('/admin/get-all', perm.requires('getallfeatures'), featureController.getAllFeatures) + +router.post('/admin/create', perm.requires('createfeature'), featureController.createFeature) + +router.get('/admin/get-one/:featureId', perm.requires('getafeature'), featureController.getFeature) + +router.put('/admin/update/:featureId', perm.requires('updateafeature'), featureController.updateFeature) + +router.delete('/admin/delete/:featureId', perm.requires('deleteafeature'), featureController.deleteFeature) + +module.exports = router diff --git a/components/feature/featureSeeder.js b/components/feature/featureSeeder.js new file mode 100644 index 0000000..abc2d85 --- /dev/null +++ b/components/feature/featureSeeder.js @@ -0,0 +1,837 @@ +const Feature = require('./featureModel') +const chalk = require('chalk') + +const runSeeder = async () => { + const features2 = [ + { + _id: '669d533e90cf9da0fdfabdcb', + nativeName: 'مدل پردازنده', + englishName: 'lcpu', + group: '63822b8fa964fe72ec7be6a0', + predefinedValues: [ + '12450H', + '13500H', + '13620H', + '13700H', + '13900H', + '13500HX', + '13500HX', + '13650HX', + '13700HX', + '13900HX', + '13950HX', + '13980HX', + '7735H', + '7940H', + '7840H', + '7745HX', + '7845HX', + '7940HX', + '7945HX', + '125U', + '135U', + '155U', + '165U', + '125H', + '135H', + '155H', + '165H', + '134U', + '164U', + '185H', + '14500HX', + '14700HX', + '14900HX', + '8945H', + '8845H', + '8840H', + 'X1E-84-100', + 'X1E-80-100', + 'X1E-78-100', + 'X1P-64-100', + ], + isFilterable: true, + filterType: 'exact', + tag: 'پردازنده', + priority: 50, + }, + { + _id: '669d533e90cf9da0fdfabdcc', + nativeName: 'برند پردازنده', + englishName: 'lcpubrand', + group: '63822b8fa964fe72ec7be6a0', + predefinedValues: ['Intel', 'AMD'], + isFilterable: true, + filterType: 'exact', + tag: 'پردازنده', + priority: 30, + }, + { + _id: '669d533e90cf9da0fdfabdcd', + nativeName: 'برند کارت گرافیک', + englishName: 'lgpubrand', + group: '63822b8fa964fe72ec7be6a0', + predefinedValues: ['Nvidia', 'AMD', 'Intel'], + isFilterable: true, + filterType: 'exact', + tag: 'کارت گرافیک', + }, + { + _id: '669d533e90cf9da0fdfabdce', + nativeName: 'مدل کارت گرافیک', + englishName: 'lgpumodel', + group: '63822b8fa964fe72ec7be6a0', + predefinedValues: ['NVIDIA GeForce GTX 4060', 'AMD Radeon RX 7600M XT'], + isFilterable: true, + filterType: 'exact', + tag: 'کارت گرافیک', + }, + { + _id: '669d533e90cf9da0fdfabdcf', + nativeName: 'رزولوشن صفحه نمایش', + englishName: 'ldisres', + group: '63822b8fa964fe72ec7be6a0', + predefinedValues: ['Full HD', '2.5K', '3.2K', '4K'], + isFilterable: true, + filterType: 'exact', + tag: 'کارت گرافیک', + }, + { + _id: '669d533e90cf9da0fdfabdd0', + nativeName: 'رم', + englishName: 'lramcap', + group: '63822b8fa964fe72ec7be6a0', + predefinedValues: [8, 16, 32], + unit: 'GB', + isFilterable: true, + filterType: 'exact', + tag: 'رم', + }, + { + _id: '669d533e90cf9da0fdfabdd1', + nativeName: 'نوع رم', + englishName: 'lramtype', + group: '63822b8fa964fe72ec7be6a0', + predefinedValues: ['DDR4', 'DDR5', 'LPDDR5X'], + isFilterable: true, + filterType: 'exact', + tag: 'رم', + }, + { + _id: '669d533e90cf9da0fdfabdd2', + nativeName: 'فرکانس رم', + englishName: 'lramfreq', + group: '63822b8fa964fe72ec7be6a0', + predefinedValues: [3200, 4800, 5200, 6400], + unit: 'Mhz', + isFilterable: true, + filterType: 'exact', + tag: 'رم', + }, + { + _id: '669d533e90cf9da0fdfabdd3', + nativeName: 'حافظه HDD', + englishName: 'lhdd', + group: '63822b8fa964fe72ec7be6a0', + predefinedValues: ['دارد', 'ندارد'], + isFilterable: false, + filterType: 'bool', + tag: 'حافظه', + }, + { + _id: '669d533e90cf9da0fdfabdd4', + nativeName: 'حافظه SSD', + englishName: 'lssd', + group: '63822b8fa964fe72ec7be6a0', + predefinedValues: ['دارد', 'ندارد'], + isFilterable: false, + filterType: 'bool', + tag: 'حافظه', + }, + { + _id: '669d533e90cf9da0fdfabdd5', + nativeName: 'حجم حافظه SSD', + englishName: 'lssdcap', + group: '63822b8fa964fe72ec7be6a0', + predefinedValues: [128, 256, 512, 1024, 2048], + isFilterable: true, + unit: 'GB', + filterType: 'exact', + tag: 'حافظه', + }, + ] + + const features = [ + { + _id: '63839aed70a51325208b0584', + fullNativeName: 'ابعاد (میلی متر) مشخصات فیزیکی لپتاپ', + nativeName: 'ابعاد (میلی متر)', + englishName: 'ldim', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'مشخصات فیزیکی', + priority: 0, + }, + { + _id: '63839f834c94732a541ce1fd', + fullNativeName: 'وزن (کیلوگرم) مشخصات فیزیکی لپتاپ', + nativeName: 'وزن (کیلوگرم)', + englishName: 'lwe', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'مشخصات فیزیکی', + priority: 0, + }, + { + _id: '63f22c7d358e3427d4428214', + fullNativeName: 'جنس بدنه مشخصات فیزیکی لپتاپ', + nativeName: 'جنس بدنه', + englishName: 'lbmat', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'مشخصات فیزیکی', + priority: 0, + }, + { + _id: '659e2ba341b67ad77d249b17', + fullNativeName: 'سیستم کولینگ و خنک کنندگی مشخصات فیزیکی لپتاپ', + nativeName: 'سیستم کولینگ و خنک کنندگی', + englishName: 'lcooling', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'مشخصات فیزیکی', + priority: 0, + }, + { + _id: '63846e760df5e6a52824d2dc', + fullNativeName: 'ابعاد (اینچ) صفحه نمایش لپتاپ', + nativeName: 'ابعاد (اینچ)', + englishName: 'ldisdim', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'صفحه نمایش', + priority: 0, + }, + { + _id: '63846e7d0df5e6a52824d2e4', + fullNativeName: 'وضوح صفحه نمایش لپتاپ', + nativeName: 'وضوح', + englishName: 'ldisqua', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'صفحه نمایش', + priority: 0, + }, + { + _id: '63846e7f0df5e6a52824d2ec', + fullNativeName: 'رفرش ریت (نرخ نوسازی) صفحه نمایش لپتاپ', + nativeName: 'رفرش ریت (نرخ نوسازی)', + englishName: 'ldisref', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'صفحه نمایش', + priority: 0, + }, + { + _id: '63846e840df5e6a52824d2f4', + fullNativeName: 'میزان روشنایی (نیت) صفحه نمایش لپتاپ', + nativeName: 'میزان روشنایی (نیت)', + englishName: 'ldisbri', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'صفحه نمایش', + priority: 0, + }, + { + _id: '63f22c65358e3427d44281ec', + fullNativeName: 'گستره رنگی صفحه نمایش لپتاپ', + nativeName: 'گستره رنگی', + englishName: 'ldiscol', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'صفحه نمایش', + priority: 0, + }, + { + _id: '63f22c71358e3427d4428200', + fullNativeName: 'نوع پنل صفحه نمایش لپتاپ', + nativeName: 'نوع پنل', + englishName: 'ldispan', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'صفحه نمایش', + priority: 0, + }, + { + _id: '644002e3be9b64da57f3f23b', + fullNativeName: 'رزولوشن صفحه نمایش لپتاپ', + nativeName: 'رزولوشن', + englishName: 'ldisres', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'صفحه نمایش', + priority: 0, + }, + { + _id: '64ce668d74a30bf0da051d90', + fullNativeName: 'نسبت تصویر صفحه نمایش لپتاپ', + nativeName: 'نسبت تصویر', + englishName: 'ldisasp', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'صفحه نمایش', + priority: 0, + }, + { + _id: '65d59ff54497f0662ddcad44', + fullNativeName: 'عمق رنگ(Color depth) صفحه نمایش لپتاپ', + nativeName: 'عمق رنگ(Color depth)', + englishName: 'ldisdep', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'صفحه نمایش', + priority: 0, + }, + { + _id: '63846ec40df5e6a52824d30a', + fullNativeName: 'شرکت سازنده پردازنده لپتاپ', + nativeName: 'شرکت سازنده', + englishName: 'lcpubrand', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'پردازنده', + priority: 0, + }, + + { + _id: '63846ec70df5e6a52824d313', + fullNativeName: 'سری پردازنده پردازنده لپتاپ', + nativeName: 'سری پردازنده', + englishName: 'lcpuser', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'پردازنده', + priority: 0, + }, + { + _id: '63846eca0df5e6a52824d31c', + fullNativeName: 'مدل پردازنده پردازنده لپتاپ', + nativeName: 'مدل پردازنده', + englishName: 'lcpumodel', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'پردازنده', + priority: 0, + }, + { + _id: '63f22c57358e3427d44281d8', + fullNativeName: 'قدرت پردازنده پردازنده لپتاپ', + nativeName: 'قدرت پردازنده ', + englishName: 'lcpupow', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'پردازنده', + priority: 0, + }, + { + _id: '63868a7f042e7c1f4e5a88cc', + fullNativeName: 'شرکت سازنده کارت گرافیک لپتاپ', + nativeName: 'شرکت سازنده', + englishName: 'lgpubrand', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'کارت گرافیک', + priority: 0, + }, + { + _id: '639325b39ba61a7bcaf25e87', + fullNativeName: 'مدل کارت گرافیک کارت گرافیک لپتاپ', + nativeName: 'مدل کارت گرافیک', + englishName: 'lgpumodel', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'کارت گرافیک', + priority: 0, + }, + { + _id: '63f22c8f358e3427d4428228', + fullNativeName: 'توان گرافیک کارت گرافیک لپتاپ', + nativeName: 'توان گرافیک', + englishName: 'lgpuw', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'کارت گرافیک', + priority: 0, + }, + { + _id: '63f22ccc358e3427d4428264', + fullNativeName: 'مقدار حافظه گرافیک کارت گرافیک لپتاپ', + nativeName: 'مقدار حافظه گرافیک', + englishName: 'lgpuvram', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'کارت گرافیک', + priority: 0, + }, + { + _id: '63f22cb4358e3427d442824f', + fullNativeName: 'مقدار حافظه حافظه و رم لپتاپ', + nativeName: 'مقدار حافظه', + englishName: 'lssd', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'حافظه و رم', + priority: 0, + }, + { + _id: '63f22cdb358e3427d4428279', + fullNativeName: 'نوع حافظه حافظه و رم لپتاپ', + nativeName: 'نوع حافظه', + englishName: 'lssdtype', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'حافظه و رم', + priority: 0, + }, + { + _id: '63f22ce4358e3427d442828e', + fullNativeName: 'مقدار رم حافظه و رم لپتاپ', + nativeName: 'مقدار رم', + englishName: 'lramcap', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'حافظه و رم', + priority: 0, + }, + { + _id: '63f22ced358e3427d44282a3', + fullNativeName: 'نوع رم حافظه و رم لپتاپ', + nativeName: 'نوع رم', + englishName: 'lramtype', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'حافظه و رم', + priority: 0, + }, + { + _id: '63f22cf5358e3427d44282b8', + fullNativeName: 'سرعت رم حافظه و رم لپتاپ', + nativeName: 'سرعت رم', + englishName: 'lramspeed', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'حافظه و رم', + priority: 0, + }, + { + _id: '64ce6d0274a30bf0da052796', + fullNativeName: 'ارتقا پذیری رم حافظه و رم لپتاپ', + nativeName: 'ارتقا پذیری رم', + englishName: 'lramupgr', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'حافظه و رم', + priority: 0, + }, + { + _id: '64ce6d1774a30bf0da0527b3', + fullNativeName: 'ارتقا پذیری هارد حافظه و رم لپتاپ', + nativeName: 'ارتقا پذیری هارد', + englishName: 'lssdupgr', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'حافظه و رم', + priority: 0, + }, + { + _id: '63f22d3c358e3427d44282e1', + fullNativeName: 'توان باتری (Wh) باتری و انرژی لپتاپ', + nativeName: 'توان باتری (Wh)', + englishName: 'lbattwh', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'باتری و انرژی', + priority: 0, + }, + { + _id: '63f22d3e358e3427d44282f7', + fullNativeName: 'خروجی آداپتور (W) باتری و انرژی لپتاپ', + nativeName: 'خروجی آداپتور (W)', + englishName: 'ladapw', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'باتری و انرژی', + priority: 0, + }, + { + _id: '63f22d54358e3427d4428323', + fullNativeName: 'نوع باتری باتری و انرژی لپتاپ', + nativeName: 'نوع باتری', + englishName: 'lbattype', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'باتری و انرژی', + priority: 0, + }, + { + _id: '63f4b0e817976077a6f30a12', + fullNativeName: 'نام محصول مشخصات تولید لپتاپ', + nativeName: 'نام محصول', + englishName: 'lname', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'مشخصات تولید', + priority: 0, + }, + { + _id: '63f4b0f817976077a6f30a29', + fullNativeName: 'کد محصول مشخصات تولید لپتاپ', + nativeName: 'کد محصول', + englishName: 'lcode', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'مشخصات تولید', + priority: 0, + }, + + { + _id: '64bbf4ec05f1a17c78c7d77f', + fullNativeName: 'برند مشخصات تولید لپتاپ', + nativeName: 'برند', + englishName: 'lbrand', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'مشخصات تولید', + priority: 0, + }, + { + _id: '64ce70a474a30bf0da0530e6', + fullNativeName: 'گارانتی کمپانی سازنده مشخصات تولید لپتاپ', + nativeName: 'گارانتی کمپانی سازنده', + englishName: 'lgur', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'مشخصات تولید', + priority: 0, + }, + { + _id: '64ce754b74a30bf0da05399c', + fullNativeName: 'لایسنس ویندوز اورجینال مشخصات تولید لپتاپ', + nativeName: 'لایسنس ویندوز اورجینال', + englishName: 'lwlic', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'مشخصات تولید', + priority: 0, + }, + { + _id: '6580df72f565d67d75dacdf2', + fullNativeName: 'سال (نسل) معرفی مدل مشخصات تولید لپتاپ', + nativeName: 'سال (نسل) معرفی مدل', + englishName: 'lgen', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'مشخصات تولید', + priority: 0, + }, + { + _id: '64ce6ba874a30bf0da0526a4', + fullNativeName: 'Wifi ارتباطات و شبکه لپتاپ', + nativeName: 'Wifi', + englishName: 'lwifi', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'ارتباطات و شبکه', + priority: 0, + }, + { + _id: '64ce6bb374a30bf0da0526c1', + fullNativeName: 'Bluetooth ارتباطات و شبکه لپتاپ', + nativeName: 'Bluetooth', + englishName: 'lblu', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'ارتباطات و شبکه', + priority: 0, + }, + { + _id: '64ce6fd374a30bf0da052fb5', + fullNativeName: 'وبکم ارتباطات و شبکه لپتاپ', + nativeName: 'وبکم', + englishName: 'lcam', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'ارتباطات و شبکه', + priority: 0, + }, + { + _id: '64ce6fe374a30bf0da052fe4', + fullNativeName: 'کیفیت وبکم ارتباطات و شبکه لپتاپ', + nativeName: 'کیفیت وبکم', + englishName: 'lcamqu', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'ارتباطات و شبکه', + priority: 0, + }, + { + _id: '64ce67c374a30bf0da051f8e', + fullNativeName: 'تاندربولت پورت ها لپتاپ', + nativeName: 'تاندربولت', + englishName: 'ltb', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'پورت ها', + priority: 0, + }, + { + _id: '64ce69bd74a30bf0da05229b', + fullNativeName: 'Type C پورت ها لپتاپ', + nativeName: 'Type C', + englishName: 'ltypc', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'پورت ها', + priority: 0, + }, + { + _id: '64ce69d674a30bf0da052305', + fullNativeName: 'PD پورت ها لپتاپ', + nativeName: 'PD', + englishName: 'lpd', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'پورت ها', + priority: 0, + }, + { + _id: '64ce6a5174a30bf0da052400', + fullNativeName: 'HDMI 2.0 پورت ها لپتاپ', + nativeName: 'HDMI 2.0', + englishName: 'lhdm2', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'پورت ها', + priority: 0, + }, + { + _id: '64ce6a9774a30bf0da0524a5', + fullNativeName: 'HDMI 2.1 پورت ها لپتاپ', + nativeName: 'HDMI 2.1', + englishName: 'lhdm21', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'پورت ها', + priority: 0, + }, + { + _id: '64ce6b5374a30bf0da05258e', + fullNativeName: 'USB 2.0 پورت ها لپتاپ', + nativeName: 'USB 2.0', + englishName: 'lusb2', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'پورت ها', + priority: 0, + }, + { + _id: '64ce6b5c74a30bf0da0525ab', + fullNativeName: 'USB 3.0 پورت ها لپتاپ', + nativeName: 'USB 3.0', + englishName: 'lusb3', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'پورت ها', + priority: 0, + }, + { + _id: '64ce721674a30bf0da053390', + fullNativeName: 'USB 3.2 پورت ها لپتاپ', + nativeName: 'USB 3.2', + englishName: 'lusb32', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'پورت ها', + priority: 0, + }, + { + _id: '64ce721674a30bf0da053392', + fullNativeName: 'RJ45 پورت ها لپتاپ', + nativeName: 'RJ45', + englishName: 'lrj45', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'پورت ها', + priority: 0, + }, + { + _id: '65a0071b0c1fc5615d7a8237', + fullNativeName: 'Jack 3.5 پورت ها لپتاپ', + nativeName: 'Jack 3.5', + englishName: 'ljack', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'پورت ها', + priority: 0, + }, + { + _id: '65a0076a0c1fc5615d7a82f2', + fullNativeName: 'Sd Card Reader پورت ها لپتاپ', + nativeName: 'Sd Card Reader', + englishName: 'lsdreader', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'پورت ها', + priority: 0, + }, + + { + _id: '65a52fb77b78648044177ec2', + fullNativeName: 'USB-C 4 پورت ها لپتاپ', + nativeName: 'USB-C 4', + englishName: 'lusbc4', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'پورت ها', + priority: 0, + }, + { + _id: '668a564f55b0c17771f0eeb9', + fullNativeName: 'Mini DisplayPort پورت ها لپتاپ', + nativeName: 'Mini DisplayPort', + englishName: 'lminidp', + group: '63822b8fa964fe72ec7be6a0', + isFilterable: true, + tag: 'پورت ها', + priority: 0, + }, + { + _id: '63d578a58678085f9874e016', + fullNativeName: 'وزن مشخصات فیزیکی کوله پشتی', + nativeName: 'وزن', + englishName: 'bpweight', + group: '63d3e9119a1819889d6d00b9', + isFilterable: true, + tag: 'مشخصات فیزیکی', + priority: 0, + }, + { + _id: '63f4c48e17976077a6f3113b', + fullNativeName: 'ابعاد مشخصات فیزیکی کوله پشتی', + nativeName: 'ابعاد', + englishName: 'bpdim', + group: '63d3e9119a1819889d6d00b9', + isFilterable: true, + tag: 'مشخصات فیزیکی', + priority: 0, + }, + { + _id: '63f4c49517976077a6f3114c', + fullNativeName: 'جنس مشخصات فیزیکی کوله پشتی', + nativeName: 'جنس', + englishName: 'bpmat', + group: '63d3e9119a1819889d6d00b9', + isFilterable: true, + tag: 'مشخصات فیزیکی', + priority: 0, + }, + { + _id: '63f4c53b17976077a6f311c7', + fullNativeName: 'مناسب تا سایز مشخصات فیزیکی کوله پشتی', + nativeName: 'مناسب تا سایز', + englishName: 'bpsize', + group: '63d3e9119a1819889d6d00b9', + isFilterable: true, + tag: 'مشخصات فیزیکی', + priority: 0, + }, + { + _id: '63f4c55117976077a6f311d9', + fullNativeName: 'رنگ مشخصات فیزیکی کوله پشتی', + nativeName: 'رنگ', + englishName: 'bpcolor', + group: '63d3e9119a1819889d6d00b9', + isFilterable: true, + tag: 'مشخصات فیزیکی', + priority: 0, + }, + { + _id: '63f4c4ed17976077a6f3116d', + fullNativeName: 'ضد آب قابلیت ها کوله پشتی', + nativeName: 'ضد آب', + englishName: 'bpwatpf', + group: '63d3e9119a1819889d6d00b9', + isFilterable: true, + tag: 'قابلیت ها', + priority: 0, + }, + { + _id: '63f4c4f417976077a6f3117f', + fullNativeName: 'ضد رطوبت قابلیت ها کوله پشتی', + nativeName: 'ضد رطوبت', + englishName: 'bpmoprf', + group: '63d3e9119a1819889d6d00b9', + isFilterable: true, + tag: 'قابلیت ها', + priority: 0, + }, + { + _id: '63f4c4fe17976077a6f31191', + fullNativeName: 'ضد ضربه قابلیت ها کوله پشتی', + nativeName: 'ضد ضربه', + englishName: 'bpdmgprf', + group: '63d3e9119a1819889d6d00b9', + isFilterable: true, + tag: 'قابلیت ها', + priority: 0, + }, + { + _id: '63f4c51217976077a6f311a3', + fullNativeName: 'پورت USB قابلیت ها کوله پشتی', + nativeName: 'پورت USB', + englishName: 'bpusb', + group: '63d3e9119a1819889d6d00b9', + isFilterable: true, + tag: 'قابلیت ها', + priority: 0, + }, + { + _id: '63f4c52c17976077a6f311b5', + fullNativeName: 'محفظه لپتاپ قابلیت ها کوله پشتی', + nativeName: 'محفظه لپتاپ', + englishName: 'bplap', + group: '63d3e9119a1819889d6d00b9', + isFilterable: true, + tag: 'قابلیت ها', + priority: 0, + }, + { + _id: '64bbf55f05f1a17c78c7d8d6', + fullNativeName: 'نام اختصاصی کد محصول کوله پشتی', + nativeName: 'نام اختصاصی', + englishName: 'bpspname', + group: '63d3e9119a1819889d6d00b9', + isFilterable: true, + tag: 'کد محصول', + priority: 0, + }, + { + _id: '64bbf56405f1a17c78c7d8ed', + fullNativeName: 'برند کد محصول کوله پشتی', + nativeName: 'برند', + englishName: 'bpbrand', + group: '63d3e9119a1819889d6d00b9', + isFilterable: true, + tag: 'کد محصول', + priority: 0, + }, + ] + + await Feature.deleteMany({}) + console.log(chalk.red('Deleted all feature records.')) + + await Feature.insertMany(features, null) + console.log(chalk.green.bold(`FEATURES: Created ${features.length} feature(s)`)) +} + +module.exports = { runSeeder } diff --git a/components/feature/featureService.js b/components/feature/featureService.js new file mode 100644 index 0000000..65bfd75 --- /dev/null +++ b/components/feature/featureService.js @@ -0,0 +1,53 @@ +const Feature = require('./featureModel') +const AppError = require('../../globalErrorHandler/AppError') +const categoryService = require('../category/categoryService') + +exports.getAllCategoryFeatures = async () => { + const groups = JSON.parse(JSON.stringify(await categoryService.getAllCategories())) + for (let group of groups) { + group.features = await Feature.find({ category: category._id }) + } + return groups +} + +module.exports.getFeatureByCategory = async (categoryId) => { + return Feature.find({category: categoryId}, null, null) +} + +module.exports.getAllFeatures = async (query) => { + return Feature.find(query, null, null) +} + +module.exports.createFeatures = async (data) => { + return Feature.create(data, null) +} + +module.exports.getFeatures = async (featureId) => { + const group = await Feature.findById(featureId) + + if (!group) { + throw new AppError('NoGroupFound', 404, {}) + } + + return group +} + +module.exports.updateFeatures = async (featureId, data) => { + const group = await Feature.findOneAndUpdate({ _id: featureId }, data) + + if (!group) { + throw new AppError('NoGroupFound', 404, {}) + } + + return group +} + +module.exports.deleteFeatures = async (featureId) => { + const group = await Feature.findOneAndUpdate({ _id: featureId }, { isActive: false }) + + if (!group) { + throw new AppError('NoGroupFound', 404, {}) + } + + return null +} diff --git a/components/feature/featureValidator.js b/components/feature/featureValidator.js new file mode 100644 index 0000000..e69de29 diff --git a/components/file/fileController.js b/components/file/fileController.js new file mode 100644 index 0000000..44d1faa --- /dev/null +++ b/components/file/fileController.js @@ -0,0 +1,103 @@ +const catchAsync = require('../../globalErrorHandler/catchAsync') +const fileService = require('./fileService') + +exports.bucketSingleUpload = catchAsync(async (req, res, next) => { + const fileData = await fileService.bucketSingleUpload(req.file, req.params.bucketName) + + res.status(200).json({ + status: 'success', + data: fileData, + }) +}) + +exports.userUploadOrderDocument = catchAsync(async (req, res, next) => { + const files = await fileService.userUploadOrderDocument(req.files) + + res.status(200).json({ + status: 'success', + results: files.length, + data: files, + }) +}) + +exports.bucketMultiUpload = catchAsync(async (req, res, next) => { + const files = await fileService.bucketMultiUpload(req.files, req.params.bucketName) + + res.status(200).json({ + status: 'success', + results: files.length, + data: files, + }) +}) + +exports.bucketGetFiles = catchAsync(async (req, res, next) => { + const files = await fileService.bucketGetFiles() + + res.status(200).json({ + status: 'success', + results: files.length, + data: files, + }) +}) + +exports.getSignedUrl = catchAsync(async (req, res, next) => { + const url = await fileService.getSignedUrl(req.body.fileName) + + res.status(200).json({ + status: 'success', + data: url, + }) +}) + +exports.bucketDeleteFile = catchAsync(async (req, res, next) => { + await fileService.bucketDeleteFile(req.body, req.params.bucketName) + + res.status(200).json({ + status: 'success', + }) +}) + +exports.getAllFiles = catchAsync(async (req, res, next) => { + const files = await fileService.getAllFiles() + + res.status(200).json({ + status: 'success', + results: files.length, + data: files, + }) +}) + +exports.getFile = catchAsync(async (req, res, next) => { + const file = await fileService.getFile(req.body.id) + + res.status(200).json({ + status: 'success', + data: file, + }) +}) + +exports.createFile = catchAsync(async (req, res, next) => { + const file = await fileService.createFile(req.body) + + res.status(201).json({ + status: 'success', + data: file, + }) +}) + +exports.updateFile = catchAsync(async (req, res, next) => { + const file = await fileService.updateFile(req.body) + res.status(200).json({ + status: 'success', + data: file, + }) +}) + +exports.deleteFile = catchAsync(async (req, res, next) => { + await fileService.deleteFile(req.body.id) + + res.status(204).json({ + status: 'success', + data: null, + }) +}) diff --git a/components/file/fileModel.js b/components/file/fileModel.js new file mode 100644 index 0000000..562659a --- /dev/null +++ b/components/file/fileModel.js @@ -0,0 +1,32 @@ +const mongoose = require('mongoose') +const ObjectID = mongoose.Schema.Types.ObjectId + +const fileSchema = new mongoose.Schema( + { + fileName: { + type: String, + required: true, + }, + owner: { + type: ObjectID, + ref: 'User', + }, + fileEndpoint: String, + bucketName: { + type: String, + required: true, + }, + }, + { + toJSON: { virtuals: true }, + toObject: { virtuals: true }, + }, + { timestamps: true } +) + +fileSchema.virtual('publicUrl').get(function () { + return this.fileEndpoint + '/' + this.bucketName + '/' + this.fileName +}) + +const File = mongoose.model('file', fileSchema) +module.exports = File diff --git a/components/file/fileRoutes.js b/components/file/fileRoutes.js new file mode 100644 index 0000000..cc90e47 --- /dev/null +++ b/components/file/fileRoutes.js @@ -0,0 +1,50 @@ +const express = require('express') +const fileController = require('./fileController') +const perm = require('../../middlewares/permissionMiddleware') +const loginCheck = require('../../middlewares/loginCheck') +const upload = require('../../middlewares/multerMiddleware') + +const router = new express.Router() + +router.use(loginCheck.loginProtect) + +router.post( + '/bucket/upload-single/:bucketName/', + perm.requires('uploadsinglefile'), + upload.single('image'), + fileController.bucketSingleUpload +) + +router.post( + '/bucket/upload-single-filepond/:bucketName', + perm.requires('uploadsinglefile'), + upload.single('filepond-image'), + fileController.bucketSingleUpload +) + +router.post('/bucket/upload-multi', perm.requires('uploadmultifile'), upload.array('image', 100), fileController.bucketMultiUpload) + +router.post( + '/bucket/upload-multi-filepond/:bucketName', + perm.requires('uploadmultifile'), + upload.array('filepond-image', 100), + fileController.bucketMultiUpload +) + +router.get('/bucket/get-files', perm.requires('getbucketfiles'), fileController.bucketGetFiles) + +router.get('/bucket/get-signed', perm.requires('getbucketfiles'), fileController.getSignedUrl) + +router.delete('/bucket/delete-file/:bucketName', perm.requires('deletebucketfiles'), fileController.bucketDeleteFile) + +router.get('/admin/get-all', perm.requires('getallfiles'), fileController.getAllFiles) + +router.post('/admin/create', perm.requires('createfile'), fileController.createFile) + +router.get('/admin/get-one/:fileId', perm.requires('getfile'), fileController.getFile) + +router.put('/admin/update/:fileId', perm.requires('updatefile'), fileController.updateFile) + +router.delete('/admin/delete/:fileId', perm.requires('deletefile'), fileController.deleteFile) + +module.exports = router diff --git a/components/file/fileService.js b/components/file/fileService.js new file mode 100644 index 0000000..9b85d5f --- /dev/null +++ b/components/file/fileService.js @@ -0,0 +1,136 @@ +const File = require('./fileModel') +const AppError = require('../../globalErrorHandler/AppError') +const util = require('util') +const fs = require('fs') +const { + uploadObject, + deleteObject, + listOfObjects, + signedUrl, + copyObject, + removeObject, +} = require('../../utils/upload/bucket') + +const deleteTempFile = util.promisify(fs.unlink) + +const bucketSingleUpload = async (file, bucketName) => { + const fileData = { + originalName: file.originalname, + filename: file.filename, + path: file.path, + } + + const result = await uploadObject(fileData, bucketName) + + const data = { + fileURL: result.fileURL, + fileName: result.fileName, + fileEndpoint: result.fileEndpoint, + bucketName, + } + + // Delete file from uploads folder + await deleteTempFile(file.path) + + const fileRecord = await createFile(data) + + return { + status: 'success', + fileRecord, + } +} + +const bucketMultiUpload = async (files, bucketName) => { + const results = [] + + for (let file of files) { + const result = await bucketSingleUpload(file, bucketName) + results.push(result) + } + + return results +} + +const bucketDeleteFile = async (fileName, bucketName) => { + await deleteObject(fileName, bucketName) + await deleteFile(fileName) + + return null +} + +const getAllFiles = async () => { + return File.find({}, null, null) +} + +const getFile = async (fileId) => { + const file = await File.findById(fileId, null, null) + + if (!file) { + throw new AppError('NoFileFound', 404, {}) + } + + return file +} + +const createFile = async (data) => { + return File.create(data, null) +} + +const updateFile = async (fileId, data) => { + const file = await File.findOneAndUpdate({ _id: fileId }, data, null) + + if (!file) { + throw AppError('NoFileFound', 404, {}) + } + + return file +} + +const deleteFile = async (fileName) => { + await File.findOneAndDelete({ fileName }, null) + + return null +} + +const bucketGetFiles = async (bucketName) => { + return listOfObjects(bucketName) +} + +const getSignedUrl = async (fileName, bucketName) => { + if (!fileName) { + return + } + return signedUrl(bucketName, fileName) +} + +const getMultipleSignedUrls = async (fileNameArray, bucketName) => { + const urls = [] + for (let fileName of fileNameArray) { + urls.push({ url: await getSignedUrl(fileName, bucketName)}) + } + return urls +} + +const moveFromTempToMain = async (files, targetBucket) => { + const tempBucket = 'temp' + + for (const file of files) { + await copyObject(targetBucket, file, `${tempBucket}/${file}`) + + await removeObject(tempBucket, file) + } +} +module.exports = { + moveFromTempToMain, + getMultipleSignedUrls, + getSignedUrl, + createFile, + updateFile, + deleteFile, + getAllFiles, + getFile, + bucketSingleUpload, + bucketMultiUpload, + bucketDeleteFile, + bucketGetFiles, +} diff --git a/globalErrorHandler/errors.json b/globalErrorHandler/errors.json index 856bd83..ab94630 100644 --- a/globalErrorHandler/errors.json +++ b/globalErrorHandler/errors.json @@ -23,5 +23,10 @@ "code": "5", "english": "Access Denied", "persian": "شما دسترسی لازم برای انجام این عمل را ندارید." + }, + "NotLoggedIn": { + "code": "6", + "english": "You need to log in first.", + "persian": "لطفا ابتدا وارد شوید." } } \ No newline at end of file diff --git a/middlewares/multerMiddleware.js b/middlewares/multerMiddleware.js new file mode 100644 index 0000000..6cea728 --- /dev/null +++ b/middlewares/multerMiddleware.js @@ -0,0 +1,24 @@ +const multer = require('multer') +const AppError = require('../globalErrorHandler/AppError') + +const types = ['png', 'jpg', 'jpeg', 'webp'] +const formatsText = types.join(' - ') + +const fileFilter = (req, file, cb) => { + if (types.includes(file.mimetype.split('/')[1])) cb(null, true) + else + cb( + new AppError('UnsupportedFileForUpload', 400, { + formats: formatsText, + }), + false + ) +} + +const upload = multer({ + dest: './uploads/', + fileFilter, + limits: { fileSize: 10000000 }, +}) + +module.exports = upload diff --git a/package-lock.json b/package-lock.json index e1d1a3c..08ae0be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,8 +19,10 @@ "dotenv": "^16.4.7", "express": "^4.21.2", "jsonwebtoken": "^9.0.2", + "minio": "^8.0.3", "mongoose": "^8.9.0", "mongoose-sequence": "^6.0.1", + "multer": "^1.4.5-lts.1", "redis": "^4.7.0", "typesense": "^1.8.2", "validator": "^13.12.0" @@ -115,6 +117,12 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "optional": true + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -162,6 +170,11 @@ "node": ">= 8" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -177,6 +190,20 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/axios": { "version": "1.7.9", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", @@ -210,6 +237,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/block-stream2": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz", + "integrity": "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==", + "dependencies": { + "readable-stream": "^3.4.0" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -255,6 +290,11 @@ "node": ">=8" } }, + "node_modules/browser-or-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-2.1.1.tgz", + "integrity": "sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==" + }, "node_modules/bson": { "version": "6.10.1", "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.1.tgz", @@ -263,11 +303,35 @@ "node": ">=16.20.1" } }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -276,6 +340,23 @@ "node": ">= 0.8" } }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", @@ -430,6 +511,47 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -474,6 +596,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -494,6 +621,30 @@ "ms": "2.0.0" } }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -531,11 +682,11 @@ } }, "node_modules/dunder-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", - "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", + "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" }, @@ -604,6 +755,11 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", @@ -657,6 +813,27 @@ "node": ">= 0.6" } }, + "node_modules/fast-xml-parser": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.1.tgz", + "integrity": "sha512-y655CeyUQ+jj7KBbYMc4FG01V8ZQqjN+gDYGJ50RtfsUB8iG9AmwmwoAgeKLJdmueKKMrH1RJ7yXHTSoczdv5w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -669,6 +846,14 @@ "node": ">=8" } }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -705,6 +890,14 @@ } } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/form-data": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", @@ -787,6 +980,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -819,6 +1024,17 @@ "node": ">=4" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -830,6 +1046,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -886,6 +1116,21 @@ "node": ">= 0.10" } }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -898,6 +1143,17 @@ "node": ">=8" } }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -907,6 +1163,23 @@ "node": ">=0.10.0" } }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -928,6 +1201,42 @@ "node": ">=0.12.0" } }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -1120,6 +1429,57 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minio": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/minio/-/minio-8.0.3.tgz", + "integrity": "sha512-+FIYQ+HZ5GrBjEmIYienRgEikqaTWAflXIV5lJOtUzfYxn3NvjQx7BsJSORXExlqgzWxKTWsqkyk2wiyFjs9/w==", + "dependencies": { + "async": "^3.2.4", + "block-stream2": "^2.1.0", + "browser-or-node": "^2.1.1", + "buffer-crc32": "^1.0.0", + "eventemitter3": "^5.0.1", + "fast-xml-parser": "^4.4.1", + "ipaddr.js": "^2.0.1", + "lodash": "^4.17.21", + "mime-types": "^2.1.35", + "query-string": "^7.1.3", + "stream-json": "^1.8.0", + "through2": "^4.0.2", + "web-encoding": "^1.1.5", + "xml2js": "^0.5.0 || ^0.6.2" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } + }, + "node_modules/minio/node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/mongodb": { "version": "6.12.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz", @@ -1257,6 +1617,23 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", @@ -1388,6 +1765,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1433,6 +1823,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -1455,6 +1862,19 @@ "node": ">= 0.8" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -1508,11 +1928,32 @@ } ] }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -1574,6 +2015,22 @@ "node": ">= 0.8.0" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -1672,6 +2129,14 @@ "memory-pager": "^1.0.2" } }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "engines": { + "node": ">=6" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -1680,6 +2145,48 @@ "node": ">= 0.8" } }, + "node_modules/stream-chain": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==" + }, + "node_modules/stream-json": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", + "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", + "dependencies": { + "stream-chain": "^2.2.5" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -1692,6 +2199,14 @@ "node": ">=4" } }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dependencies": { + "readable-stream": "3" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1744,6 +2259,11 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "node_modules/typesense": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/typesense/-/typesense-1.8.2.tgz", @@ -1773,6 +2293,23 @@ "node": ">= 0.8" } }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1797,6 +2334,17 @@ "node": ">= 0.8" } }, + "node_modules/web-encoding": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", + "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", + "dependencies": { + "util": "^0.12.3" + }, + "optionalDependencies": { + "@zxing/text-encoding": "0.9.0" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -1817,6 +2365,53 @@ "node": ">=16" } }, + "node_modules/which-typed-array": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", + "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 92a8be4..0032a9d 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,10 @@ "dotenv": "^16.4.7", "express": "^4.21.2", "jsonwebtoken": "^9.0.2", + "minio": "^8.0.3", "mongoose": "^8.9.0", "mongoose-sequence": "^6.0.1", + "multer": "^1.4.5-lts.1", "redis": "^4.7.0", "typesense": "^1.8.2", "validator": "^13.12.0" diff --git a/setup/routerEndpoints.js b/setup/routerEndpoints.js index dab970b..5f57d2c 100644 --- a/setup/routerEndpoints.js +++ b/setup/routerEndpoints.js @@ -1,15 +1,11 @@ const express = require('express') -const AppError = require('../globalErrorHandler/AppError') -const catchAsync = require('../globalErrorHandler/catchAsync') const userRoutes = require('../components/user/userRoutes') 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 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() routerEndpoints.use('/users', userRoutes) @@ -17,5 +13,6 @@ routerEndpoints.use('/permissions', permissionRoutes) routerEndpoints.use('/belongings', belongingRoutes) routerEndpoints.use('/roles', roleRoutes) routerEndpoints.use('/categories', categoryRoutes) - +routerEndpoints.use('/files', fileRoutes) +routerEndpoints.use('/features', featureRoutes) module.exports = routerEndpoints diff --git a/utils/upload/bucket.js b/utils/upload/bucket.js new file mode 100644 index 0000000..78d3a9b --- /dev/null +++ b/utils/upload/bucket.js @@ -0,0 +1,54 @@ +const minioClient = require('./minio') +const fs = require('fs') + + +// Upload File to s3 bucket +exports.uploadObject = async (file, bucketName) => { + const fileStream = fs.createReadStream(file.path) + + const uploadedFile = await minioClient.putObject(bucketName, `${file.filename}-${file.originalName}`, fileStream) + + uploadedFile.fileURL = 'https://' + process.env.AWS_ENDPOINT + '/' + bucketName + '/' + file.filename + '-' + file.originalName + + uploadedFile.fileName = file.filename + '-' + file.originalName + + uploadedFile.fileEndpoint = process.env.AWS_ENDPOINT + + return uploadedFile +} + +exports.listOfObjects = (bucketName) => { + return new Promise((resolve, reject) => { + let objects = [] + + const stream = minioClient.listObjectsV2(bucketName, '', true) + + stream.on('data', (obj) => { + objects.push(obj) + }) + + stream.on('error', (err) => { + reject(err) + }) + + stream.on('end', () => { + resolve(objects) + }) + }) +} + +exports.signedUrl = (bucketName, fileName) => { + return minioClient.presignedUrl('GET', bucketName, fileName, 60 * 60) +} + +exports.deleteObject = (fileName, bucketName) => { + return minioClient.removeObject(bucketName, fileName) +} + +exports.copyObject = (targetBucket, fileName, sourceBucketAndFile) => { + return minioClient.copyObject(targetBucket, fileName, sourceBucketAndFile) +} + +exports.removeObject = (bucketName, fileName) => { + return minioClient.removeObject(bucketName, fileName) +} diff --git a/utils/upload/minio.js b/utils/upload/minio.js new file mode 100644 index 0000000..67ac351 --- /dev/null +++ b/utils/upload/minio.js @@ -0,0 +1,30 @@ +const Minio = require('minio') + +const endPoint = process.env.AWS_ENDPOINT +const accessKey = process.env.AWS_ACCESS_KEY +const secretKey = process.env.AWS_SECRET_KEY +const region = process.env.AWS_REGION +const port = parseInt(process.env.AWS_PORT) + +let client + +if (process.env.NODE_ENV === 'test') { + client = new Minio.Client({ + endPoint, + useSSL: true, + accessKey, + port, + secretKey, + region, + }) +} else { + client = new Minio.Client({ + endPoint, + useSSL: true, + accessKey, + secretKey, + region, + }) +} + +module.exports = client