server-core-module 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +27 -0
- package/src/controllers/order.controller.ts +108 -0
- package/src/controllers/product.controller.ts +94 -0
- package/src/error.ts +37 -0
- package/src/models/order.model.ts +72 -0
- package/src/models/product.model.ts +42 -0
- package/src/repositories/order.repository.ts +123 -0
- package/src/repositories/product.repository.ts +124 -0
- package/src/utils/email.service.ts +165 -0
- package/src/utils/paginate.util.ts +19 -0
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "server-core-module",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [],
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"type": "commonjs",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"dotenv": "^17.3.1",
|
|
15
|
+
"express": "^5.2.1",
|
|
16
|
+
"joi": "^18.0.2",
|
|
17
|
+
"mongodb": "^7.1.0",
|
|
18
|
+
"nodemailer": "^8.0.1"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/express": "^5.0.6",
|
|
22
|
+
"@types/node": "^25.3.0",
|
|
23
|
+
"@types/nodemailer": "^7.0.11",
|
|
24
|
+
"ts-node": "^10.9.2",
|
|
25
|
+
"typescript": "^5.9.3"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
|
+
import { schemaOrder, schemaOrderUpdate } from "../models/order.model";
|
|
3
|
+
import { useOrderRepository } from "../repositories/order.repository";
|
|
4
|
+
import { BadRequestError } from "../error";
|
|
5
|
+
import { sendOrderConfirmation, sendOrderStatusUpdate, sendAdminNotification } from "../utils/email.service";
|
|
6
|
+
import dotenv from "dotenv";
|
|
7
|
+
dotenv.config();
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export function useOrderController() {
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
getAll: _getAll,
|
|
14
|
+
add: _add,
|
|
15
|
+
getById: _getById,
|
|
16
|
+
deleteById: _deleteById,
|
|
17
|
+
updateById: _updateById
|
|
18
|
+
} = useOrderRepository();
|
|
19
|
+
|
|
20
|
+
async function add(req: Request, res: Response, next: NextFunction) {
|
|
21
|
+
const value = req.body;
|
|
22
|
+
const { error } = schemaOrder.validate(value);
|
|
23
|
+
|
|
24
|
+
if(error) {
|
|
25
|
+
next(new BadRequestError(error.details[0].message));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const message = await _add(value);
|
|
30
|
+
// ← dagdag mo dito
|
|
31
|
+
await sendOrderConfirmation(value.email, value.name, value.items, value.totalAmount, value.deliveryFee);
|
|
32
|
+
await sendAdminNotification(value.name, value.items, value.totalAmount, value.email, value.deliveryFee);
|
|
33
|
+
res.json({message});
|
|
34
|
+
} catch (error) {
|
|
35
|
+
next(error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function getAll(req: Request, res: Response, next: NextFunction) {
|
|
40
|
+
try {
|
|
41
|
+
const status = req.query.status as string | undefined;
|
|
42
|
+
const page = Number(req.query.page) || 1;
|
|
43
|
+
const limit = Number(req.query.limit) || 10;
|
|
44
|
+
const items = await _getAll({status, page, limit});
|
|
45
|
+
res.json(items);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
next(error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function getById(req: Request, res: Response, next: NextFunction) {
|
|
52
|
+
const id = req.params.id as string;
|
|
53
|
+
try {
|
|
54
|
+
const item = await _getById(id);
|
|
55
|
+
res.json(item);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
next(error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function updateById(req: Request, res: Response, next: NextFunction) {
|
|
62
|
+
try {
|
|
63
|
+
const id = req.params.id as string;
|
|
64
|
+
const value = req.body;
|
|
65
|
+
const { error } = schemaOrderUpdate.validate(value);
|
|
66
|
+
|
|
67
|
+
if(error) {
|
|
68
|
+
next(new BadRequestError(error.message));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await _updateById(id, value);
|
|
73
|
+
|
|
74
|
+
// fetch yung order para makuha email at name
|
|
75
|
+
const order = await _getById(id);
|
|
76
|
+
if(!order){
|
|
77
|
+
res.status(404).json({message: "Order not found"});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if(value.status) {
|
|
82
|
+
await sendOrderStatusUpdate(order.email as string, order.name as string, value.status);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
res.json({message: "Order updated successfully", order});
|
|
86
|
+
} catch (error) {
|
|
87
|
+
next(error);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function deleteById(req: Request, res: Response, next: NextFunction) {
|
|
92
|
+
try {
|
|
93
|
+
const id = req.params.id as string;
|
|
94
|
+
await _deleteById(id);
|
|
95
|
+
res.json({message: "Order deleted successfully"});
|
|
96
|
+
} catch (error) {
|
|
97
|
+
next(error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return{
|
|
102
|
+
add,
|
|
103
|
+
getAll,
|
|
104
|
+
getById,
|
|
105
|
+
updateById,
|
|
106
|
+
deleteById,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { schemaProduct, schemaProductUpdate } from '../models/product.model';
|
|
3
|
+
import { useProductRepository } from '../repositories/product.repository';
|
|
4
|
+
import { BadRequestError } from '../error';
|
|
5
|
+
|
|
6
|
+
export function useProductController() {
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
getAll: _getAll,
|
|
10
|
+
add: _add,
|
|
11
|
+
getById: _getById,
|
|
12
|
+
deleteById: _deleteById,
|
|
13
|
+
updateById: _updateById
|
|
14
|
+
} = useProductRepository();
|
|
15
|
+
|
|
16
|
+
async function add(req: Request, res: Response, next: NextFunction) {
|
|
17
|
+
const value = req.body;
|
|
18
|
+
const { error } = schemaProduct.validate(value);
|
|
19
|
+
|
|
20
|
+
if(error) {
|
|
21
|
+
next(new BadRequestError(error.details[0].message));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const message = await _add(value);
|
|
26
|
+
res.json({message});
|
|
27
|
+
} catch (error) {
|
|
28
|
+
next(error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function getAll(req: Request, res: Response, next: NextFunction) {
|
|
33
|
+
try {
|
|
34
|
+
const category = req.query.category as string | undefined;
|
|
35
|
+
const page = Number(req.query.page) || 1;
|
|
36
|
+
const limit = Number(req.query.limit) || 10;
|
|
37
|
+
const items = await _getAll({category, page, limit});
|
|
38
|
+
res.json(items);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
next(error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function getById(req: Request, res: Response, next: NextFunction) {
|
|
45
|
+
const id = req.params.id as string;
|
|
46
|
+
try {
|
|
47
|
+
const item = await _getById(id);
|
|
48
|
+
res.json(item);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
next(error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function updateById(req: Request, res: Response, next: NextFunction) {
|
|
55
|
+
try {
|
|
56
|
+
const id = req.params.id as string;
|
|
57
|
+
const value = req.body;
|
|
58
|
+
const { error } = schemaProductUpdate.validate(value);
|
|
59
|
+
|
|
60
|
+
if(error) {
|
|
61
|
+
next(new BadRequestError(error.message));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const updated = await _updateById(id, value);
|
|
66
|
+
if(!updated){
|
|
67
|
+
res.status(404).json({message: "Product not found"});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
res.json({message: "Product updated successfully", updated});
|
|
72
|
+
} catch (error) {
|
|
73
|
+
next(error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function deleteById(req: Request, res: Response, next: NextFunction) {
|
|
78
|
+
try {
|
|
79
|
+
const id = req.params.id as string;
|
|
80
|
+
await _deleteById(id);
|
|
81
|
+
res.json({message: "Product deleted successfully"});
|
|
82
|
+
} catch (error) {
|
|
83
|
+
next(error);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return{
|
|
88
|
+
add,
|
|
89
|
+
getAll,
|
|
90
|
+
getById,
|
|
91
|
+
updateById,
|
|
92
|
+
deleteById,
|
|
93
|
+
}
|
|
94
|
+
}
|
package/src/error.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
|
|
2
|
+
export class AppError extends Error {
|
|
3
|
+
public readonly statusCode: number;
|
|
4
|
+
public readonly isOperational: boolean;
|
|
5
|
+
|
|
6
|
+
constructor(message: string, statusCode: number, isOperational: boolean = true){
|
|
7
|
+
super(message);
|
|
8
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
9
|
+
this.statusCode = statusCode;
|
|
10
|
+
this.isOperational = isOperational;
|
|
11
|
+
Error.captureStackTrace(this);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class BadRequestError extends AppError {
|
|
16
|
+
constructor(message: string = 'Bad Request'){
|
|
17
|
+
super(message, 400);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class Unauthorized extends AppError {
|
|
22
|
+
constructor(message: string = 'Unauthorized'){
|
|
23
|
+
super(message, 401);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class NotFoundError extends AppError {
|
|
28
|
+
constructor(message: string = 'Not Found'){
|
|
29
|
+
super(message, 404);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class InternalServerError extends AppError {
|
|
34
|
+
constructor(message: string = 'Internal Server Error'){
|
|
35
|
+
super(message, 500);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import Joi from 'joi';
|
|
2
|
+
import { ObjectId } from 'mongodb';
|
|
3
|
+
|
|
4
|
+
export type TOrder = {
|
|
5
|
+
_id?: ObjectId;
|
|
6
|
+
name: string;
|
|
7
|
+
email: string;
|
|
8
|
+
phone: string;
|
|
9
|
+
address: string;
|
|
10
|
+
items: {
|
|
11
|
+
productId: string;
|
|
12
|
+
name: string;
|
|
13
|
+
price: number;
|
|
14
|
+
quantity: number;
|
|
15
|
+
}[];
|
|
16
|
+
totalAmount: number;
|
|
17
|
+
note?: string;
|
|
18
|
+
deliveryDate?: Date;
|
|
19
|
+
status?: string;
|
|
20
|
+
createdAt?: Date;
|
|
21
|
+
updatedAt?: Date;
|
|
22
|
+
location?: string;
|
|
23
|
+
deliveryFee?: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const schemaOrder = Joi.object({
|
|
27
|
+
name: Joi.string().required(),
|
|
28
|
+
email: Joi.string().email().required(),
|
|
29
|
+
phone: Joi.string().required(),
|
|
30
|
+
address: Joi.string().required(),
|
|
31
|
+
items: Joi.array().items(Joi.object({
|
|
32
|
+
productId: Joi.string().required(),
|
|
33
|
+
name: Joi.string().required(),
|
|
34
|
+
price: Joi.number().min(0).required(),
|
|
35
|
+
quantity: Joi.number().min(1).required(),
|
|
36
|
+
})).required(),
|
|
37
|
+
totalAmount: Joi.number().min(0).required(),
|
|
38
|
+
note: Joi.string().optional(),
|
|
39
|
+
deliveryDate: Joi.date().optional(),
|
|
40
|
+
status: Joi.string().default("pending"),
|
|
41
|
+
location: Joi.string().optional(),
|
|
42
|
+
deliveryFee: Joi.number().min(0).optional(),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
export const schemaOrderUpdate = Joi.object({
|
|
47
|
+
name: Joi.string(),
|
|
48
|
+
email: Joi.string().email(),
|
|
49
|
+
phone: Joi.string(),
|
|
50
|
+
address: Joi.string(),
|
|
51
|
+
items: Joi.array().items(Joi.object({
|
|
52
|
+
productId: Joi.string().required(),
|
|
53
|
+
name: Joi.string().required(),
|
|
54
|
+
price: Joi.number().min(0).required(),
|
|
55
|
+
quantity: Joi.number().min(1).required(),
|
|
56
|
+
})),
|
|
57
|
+
totalAmount: Joi.number().min(0),
|
|
58
|
+
note: Joi.string(),
|
|
59
|
+
deliveryDate: Joi.date(),
|
|
60
|
+
status: Joi.string(),
|
|
61
|
+
location: Joi.string(),
|
|
62
|
+
deliveryFee: Joi.number().min(0),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export function modelOrder(value: TOrder): TOrder {
|
|
66
|
+
return {
|
|
67
|
+
...value,
|
|
68
|
+
status: value.status || "pending",
|
|
69
|
+
createdAt: value.createdAt || new Date(),
|
|
70
|
+
updatedAt: new Date(),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import Joi from 'joi';
|
|
2
|
+
import { ObjectId } from 'mongodb';
|
|
3
|
+
|
|
4
|
+
export type TProduct = {
|
|
5
|
+
_id?: ObjectId;
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
price: number;
|
|
9
|
+
imageUrl?: string;
|
|
10
|
+
status?:string;
|
|
11
|
+
createdAt?: Date;
|
|
12
|
+
updatedAt?: Date;
|
|
13
|
+
category?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const schemaProduct = Joi.object({
|
|
17
|
+
|
|
18
|
+
name: Joi.string().required(),
|
|
19
|
+
description: Joi.string().required(),
|
|
20
|
+
price: Joi.number().min(0).required(),
|
|
21
|
+
imageUrl: Joi.string().optional(),
|
|
22
|
+
status: Joi.string().default("available"),
|
|
23
|
+
category: Joi.string().optional(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const schemaProductUpdate = Joi.object({
|
|
27
|
+
|
|
28
|
+
name: Joi.string(),
|
|
29
|
+
description: Joi.string(),
|
|
30
|
+
price: Joi.number().min(0),
|
|
31
|
+
imageUrl: Joi.string(),
|
|
32
|
+
status: Joi.string(),
|
|
33
|
+
category: Joi.string(),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export function modelProduct(value: TProduct): TProduct {
|
|
37
|
+
return {
|
|
38
|
+
...value,
|
|
39
|
+
createdAt: value.createdAt || new Date(),
|
|
40
|
+
updatedAt: new Date(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Collection, ObjectId, Db } from "mongodb";
|
|
2
|
+
import { TOrder, modelOrder } from "../models/order.model";
|
|
3
|
+
import { paginate } from "../utils/paginate.util";
|
|
4
|
+
|
|
5
|
+
let db: Db;
|
|
6
|
+
|
|
7
|
+
export function setDb(database: Db) {
|
|
8
|
+
db = database;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useOrderRepository(){
|
|
12
|
+
const collection = db.collection('orders');
|
|
13
|
+
|
|
14
|
+
//CREATE INDEXES
|
|
15
|
+
|
|
16
|
+
async function createIndexes() {
|
|
17
|
+
try {
|
|
18
|
+
await collection.createIndexes([
|
|
19
|
+
{ key: { name:1}},
|
|
20
|
+
{ key: { createdAt:1}},
|
|
21
|
+
]);
|
|
22
|
+
return "Indexes created successfully";
|
|
23
|
+
} catch (error: any) {
|
|
24
|
+
throw new Error(`Error creating indexes: ${error.message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//ADD ORDER
|
|
29
|
+
|
|
30
|
+
async function add(value: TOrder){
|
|
31
|
+
try {
|
|
32
|
+
value = modelOrder(value);
|
|
33
|
+
const result = await collection.insertOne(value);
|
|
34
|
+
return result.insertedId;
|
|
35
|
+
} catch (error: any) {
|
|
36
|
+
throw new Error(`Error adding order: ${error.message}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//GET ALL ORDERS
|
|
41
|
+
|
|
42
|
+
async function getAll({
|
|
43
|
+
page = 1,
|
|
44
|
+
limit = 10,
|
|
45
|
+
status = "",
|
|
46
|
+
} = {}) {
|
|
47
|
+
page = page > 0 ? page - 1 : 0;
|
|
48
|
+
const query: any = {};
|
|
49
|
+
if (status) query.status = status;
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const items = await collection.aggregate([
|
|
53
|
+
{ $match: query },
|
|
54
|
+
{ $skip: page * limit },
|
|
55
|
+
{ $limit: limit },
|
|
56
|
+
]).toArray();
|
|
57
|
+
|
|
58
|
+
const length = await collection.countDocuments(query);
|
|
59
|
+
return paginate(items, page, limit, length);
|
|
60
|
+
} catch (error: any) {
|
|
61
|
+
throw new Error("Failed to get orders: " + error.message);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//GET ORDER BY ID
|
|
66
|
+
|
|
67
|
+
async function getById(id: string) {
|
|
68
|
+
try {
|
|
69
|
+
const item =await collection.findOne({_id: new ObjectId(id)});
|
|
70
|
+
return item;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
throw new Error("Failed to get order: " + error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//UPDATE BY ID
|
|
77
|
+
|
|
78
|
+
async function updateById(id:string, value: Partial<TOrder>){
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
new ObjectId(id);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
throw new Error("Invalid ID format");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
value.updatedAt = new Date();
|
|
88
|
+
const result = await collection.updateOne(
|
|
89
|
+
{_id: new ObjectId(id)},
|
|
90
|
+
{ $set: value }
|
|
91
|
+
);
|
|
92
|
+
return result.modifiedCount;
|
|
93
|
+
} catch (error:any) {
|
|
94
|
+
throw new Error("Failed to update order:" + error.message)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
//DELETE BY ID
|
|
99
|
+
|
|
100
|
+
async function deleteById(id: string){
|
|
101
|
+
try {
|
|
102
|
+
id= new ObjectId(id) as any;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
throw new Error("Failed to delete order: " + error);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
await collection.deleteOne({_id: id as any});
|
|
109
|
+
return "Order deleted successfully";
|
|
110
|
+
} catch (error: any) {
|
|
111
|
+
throw new Error("Failed to delete order: " + error.message);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
add,
|
|
117
|
+
getAll,
|
|
118
|
+
getById,
|
|
119
|
+
updateById,
|
|
120
|
+
deleteById,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Collection, ObjectId, Db } from "mongodb";
|
|
2
|
+
import { TProduct, modelProduct } from "../models/product.model";
|
|
3
|
+
import { paginate } from "../utils/paginate.util";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
let db: Db;
|
|
7
|
+
|
|
8
|
+
export function setDb(database: Db) {
|
|
9
|
+
db = database;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useProductRepository(){
|
|
13
|
+
const collection = db.collection('products');
|
|
14
|
+
|
|
15
|
+
//CREATE INDEXES
|
|
16
|
+
|
|
17
|
+
async function createIndexes() {
|
|
18
|
+
try {
|
|
19
|
+
await collection.createIndexes([
|
|
20
|
+
{ key: { name:1}},
|
|
21
|
+
{ key: { createdAt:1}},
|
|
22
|
+
]);
|
|
23
|
+
return "Indexes created successfully";
|
|
24
|
+
} catch (error: any) {
|
|
25
|
+
throw new Error(`Error creating indexes: ${error.message}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//ADD PRODUCT
|
|
30
|
+
|
|
31
|
+
async function add(value: TProduct){
|
|
32
|
+
try {
|
|
33
|
+
value = modelProduct(value);
|
|
34
|
+
const result = await collection.insertOne(value);
|
|
35
|
+
return result.insertedId;
|
|
36
|
+
} catch (error: any) {
|
|
37
|
+
throw new Error(`Error adding product: ${error.message}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//GET ALL PRODUCTS
|
|
42
|
+
|
|
43
|
+
async function getAll({
|
|
44
|
+
page = 1,
|
|
45
|
+
limit = 10,
|
|
46
|
+
category = "",
|
|
47
|
+
} = {}) {
|
|
48
|
+
page = page > 0 ? page - 1 : 0;
|
|
49
|
+
const query: any = {};
|
|
50
|
+
if (category) query.category = category;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const items = await collection.aggregate([
|
|
54
|
+
{ $match: query },
|
|
55
|
+
{ $skip: page * limit },
|
|
56
|
+
{ $limit: limit },
|
|
57
|
+
]).toArray();
|
|
58
|
+
|
|
59
|
+
const length = await collection.countDocuments(query);
|
|
60
|
+
return paginate(items, page, limit, length);
|
|
61
|
+
} catch (error: any) {
|
|
62
|
+
throw new Error("Failed to get products: " + error.message);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//GET PRODUCT BY ID
|
|
67
|
+
|
|
68
|
+
async function getById(id: string) {
|
|
69
|
+
try {
|
|
70
|
+
const item =await collection.findOne({_id: new ObjectId(id)});
|
|
71
|
+
return item;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
throw new Error("Failed to get product: " + error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
//UPDATE BY ID
|
|
78
|
+
|
|
79
|
+
async function updateById(id:string, value: Partial<TProduct>){
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
new ObjectId(id);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
throw new Error("Invalid ID format");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
value.updatedAt = new Date();
|
|
89
|
+
const result = await collection.updateOne(
|
|
90
|
+
{_id: new ObjectId(id)},
|
|
91
|
+
{ $set: value }
|
|
92
|
+
);
|
|
93
|
+
return result.modifiedCount;
|
|
94
|
+
} catch (error:any) {
|
|
95
|
+
throw new Error("Failed to update item:" + error.message)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
//DELETE BY ID
|
|
100
|
+
|
|
101
|
+
async function deleteById(id: string){
|
|
102
|
+
try {
|
|
103
|
+
id= new ObjectId(id) as any;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
throw new Error("Failed to delete product: " + error);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
await collection.deleteOne({_id: id as any});
|
|
110
|
+
return "Product deleted successfully";
|
|
111
|
+
} catch (error: any) {
|
|
112
|
+
throw new Error("Failed to delete product: " + error.message);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
add,
|
|
118
|
+
getAll,
|
|
119
|
+
getById,
|
|
120
|
+
updateById,
|
|
121
|
+
deleteById,
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import nodemailer from "nodemailer";
|
|
2
|
+
|
|
3
|
+
function createTransporter() {
|
|
4
|
+
return nodemailer.createTransport({
|
|
5
|
+
service: "gmail",
|
|
6
|
+
auth: {
|
|
7
|
+
user: process.env.EMAIL_USER,
|
|
8
|
+
pass: process.env.EMAIL_PASS,
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function sendOrderConfirmation(email: string, name: string, items: any[], totalAmount: number, deliveryFee?: number) {
|
|
14
|
+
console.log("Sending email to:", email);
|
|
15
|
+
try {
|
|
16
|
+
const transporter = createTransporter();
|
|
17
|
+
|
|
18
|
+
const itemsList = items.map(item =>
|
|
19
|
+
`<tr>
|
|
20
|
+
<td>${item.name}</td>
|
|
21
|
+
<td>${item.quantity}</td>
|
|
22
|
+
<td>₱${item.price}</td>
|
|
23
|
+
<td>₱${item.price * item.quantity}</td>
|
|
24
|
+
</tr>`
|
|
25
|
+
).join("");
|
|
26
|
+
|
|
27
|
+
await transporter.sendMail({
|
|
28
|
+
from: `"Cesar's Flower Shop 🌸" <${process.env.EMAIL_USER}>`,
|
|
29
|
+
to: email,
|
|
30
|
+
subject: "Order Received! 🌸",
|
|
31
|
+
html: `
|
|
32
|
+
<h2>Hi ${name}!</h2>
|
|
33
|
+
<p>Your order has been received! 🌸</p>
|
|
34
|
+
<table border="1" cellpadding="8" cellspacing="0" style="border-collapse: collapse;">
|
|
35
|
+
<tr>
|
|
36
|
+
<th>Item</th>
|
|
37
|
+
<th>Qty</th>
|
|
38
|
+
<th>Price</th>
|
|
39
|
+
<th>Subtotal</th>
|
|
40
|
+
</tr>
|
|
41
|
+
${itemsList}
|
|
42
|
+
</table>
|
|
43
|
+
<h3>Items Subtotal: ₱${totalAmount - (deliveryFee || 0)}</h3>
|
|
44
|
+
<h3>Delivery Fee: ₱${deliveryFee || 0}</h3>
|
|
45
|
+
<h3>Total: ₱${totalAmount}</h3>
|
|
46
|
+
<p>We will confirm your order soon.</p>
|
|
47
|
+
<p>Thank you for ordering from Cesar's Flower Shop!</p>
|
|
48
|
+
`,
|
|
49
|
+
});
|
|
50
|
+
console.log("Email sent successfully!");
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error("Email error:", error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function sendOrderStatusUpdate(email: string, name: string, status: string) {
|
|
57
|
+
try {
|
|
58
|
+
const transporter = createTransporter();
|
|
59
|
+
await transporter.sendMail({
|
|
60
|
+
from: `"Cesar's Flower Shop 🌸" <${process.env.EMAIL_USER}>`,
|
|
61
|
+
to: email,
|
|
62
|
+
subject: `Order ${status}! 🌸`,
|
|
63
|
+
html: `
|
|
64
|
+
<h2>Hi ${name}!</h2>
|
|
65
|
+
<p>Your order is now <strong>${status}</strong>! 🌸</p>
|
|
66
|
+
<br/>
|
|
67
|
+
<p>Thank you for ordering from Cesar's Flower Shop!</p>
|
|
68
|
+
`,
|
|
69
|
+
});
|
|
70
|
+
console.log("Status email sent!");
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error("Status email error:", error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function sendAdminNotification(name: string, items: any[], totalAmount: number, customerEmail: string, deliveryFee?:number) {
|
|
77
|
+
try {
|
|
78
|
+
const transporter = createTransporter();
|
|
79
|
+
|
|
80
|
+
const itemsList = items.map(item =>
|
|
81
|
+
`<tr>
|
|
82
|
+
<td>${item.name}</td>
|
|
83
|
+
<td>${item.quantity}</td>
|
|
84
|
+
<td>₱${item.price}</td>
|
|
85
|
+
<td>₱${item.price * item.quantity}</td>
|
|
86
|
+
</tr>`
|
|
87
|
+
).join("");
|
|
88
|
+
|
|
89
|
+
await transporter.sendMail({
|
|
90
|
+
from: `"Cesar's Flower Shop 🌸" <${process.env.EMAIL_USER}>`,
|
|
91
|
+
to: process.env.EMAIL_USER,
|
|
92
|
+
subject: `New Order from ${name}! 🌸`,
|
|
93
|
+
html: `
|
|
94
|
+
<h2>New Order Received! 🌸</h2>
|
|
95
|
+
<p><strong>Customer:</strong> ${name}</p>
|
|
96
|
+
<p><strong>Email:</strong> ${customerEmail}</p>
|
|
97
|
+
<table border="1" cellpadding="8" cellspacing="0">
|
|
98
|
+
<tr>
|
|
99
|
+
<th>Item</th>
|
|
100
|
+
<th>Qty</th>
|
|
101
|
+
<th>Price</th>
|
|
102
|
+
<th>Subtotal</th>
|
|
103
|
+
</tr>
|
|
104
|
+
${itemsList}
|
|
105
|
+
<tr>
|
|
106
|
+
<td colspan="3"><strong>Items Subtotal</strong></td>
|
|
107
|
+
<td>₱${totalAmount - (deliveryFee || 0)}</td>
|
|
108
|
+
</tr>
|
|
109
|
+
<tr>
|
|
110
|
+
<td colspan="3"><strong>Delivery Fee</strong></td>
|
|
111
|
+
<td>₱${deliveryFee || 0}</td>
|
|
112
|
+
</tr>
|
|
113
|
+
<tr>
|
|
114
|
+
<td colspan="3"><strong>Total</strong></td>
|
|
115
|
+
<td><strong>₱${totalAmount}</strong></td>
|
|
116
|
+
</tr>
|
|
117
|
+
</table>
|
|
118
|
+
<p>Login to admin panel to confirm the order!</p>
|
|
119
|
+
`,
|
|
120
|
+
});
|
|
121
|
+
console.log("Admin notified!");
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error("Admin email error:", error);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function sendContactEmail(name: string, email: string, phone: string, eventType: string, message: string) {
|
|
128
|
+
try {
|
|
129
|
+
const transporter = createTransporter();
|
|
130
|
+
|
|
131
|
+
await transporter.sendMail({
|
|
132
|
+
from: `"Cesar's Flower Shop 🌸" <${process.env.EMAIL_USER}>`,
|
|
133
|
+
to: process.env.EMAIL_USER,
|
|
134
|
+
subject: `New Inquiry from ${name} - ${eventType} 🌸`,
|
|
135
|
+
html: `
|
|
136
|
+
<h2>New Customer Inquiry! 🌸</h2>
|
|
137
|
+
<p><strong>Name:</strong> ${name}</p>
|
|
138
|
+
<p><strong>Email:</strong> ${email}</p>
|
|
139
|
+
<p><strong>Phone:</strong> ${phone}</p>
|
|
140
|
+
<p><strong>Event Type:</strong> ${eventType}</p>
|
|
141
|
+
<p><strong>Message:</strong></p>
|
|
142
|
+
<p>${message}</p>
|
|
143
|
+
`,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
await transporter.sendMail({
|
|
147
|
+
from: `"Cesar's Flower Shop 🌸" <${process.env.EMAIL_USER}>`,
|
|
148
|
+
to: email,
|
|
149
|
+
subject: `We received your message! 🌸`,
|
|
150
|
+
html: `
|
|
151
|
+
<h2>Hi ${name}! 🌸</h2>
|
|
152
|
+
<p>Thank you for reaching out to Cesar's Flower Shop!</p>
|
|
153
|
+
<p>We have received your inquiry about <strong>${eventType}</strong>.</p>
|
|
154
|
+
<p>We will get back to you as soon as possible.</p>
|
|
155
|
+
<br/>
|
|
156
|
+
<p>Thank you for choosing Cesar's Flower Shop! 🌺</p>
|
|
157
|
+
`,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
console.log("Contact email sent!");
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error("Contact email error:", error);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function paginate(
|
|
2
|
+
items: any[],
|
|
3
|
+
page: number,
|
|
4
|
+
limit: number,
|
|
5
|
+
length: number
|
|
6
|
+
) {
|
|
7
|
+
if (length === 0) {
|
|
8
|
+
return { items: [], totalPages: 0, currentPage: 0 };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const startIndex = page * limit + 1;
|
|
12
|
+
const endIndex = Math.min(startIndex + limit - 1, length);
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
items,
|
|
16
|
+
pages: Math.ceil(length / limit),
|
|
17
|
+
pageRange: `${startIndex}-${endIndex} of ${length}`,
|
|
18
|
+
};
|
|
19
|
+
}
|