Skip to content

Commit aea3415

Browse files
more 25 api added Done
1 parent 4d39422 commit aea3415

33 files changed

Lines changed: 2128 additions & 2 deletions

docker-compose.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ services:
2727
ANALYSIS_QUEUE: ${ANALYSIS_QUEUE}
2828
ALERT_QUEUE: ${ALERT_QUEUE}
2929
DLX_EXCHANGE: ${DLX_EXCHANGE}
30+
NOTIFICATION_QUEUE: ${NOTIFICATION_QUEUE:-lexai.notification.queue}
31+
ANALYTICS_EVENTS_QUEUE: ${ANALYTICS_EVENTS_QUEUE:-lexai.analytics.queue}
32+
ORDER_QUEUE: ${ORDER_QUEUE:-lexai.order.queue}
33+
REVIEW_QUEUE: ${REVIEW_QUEUE:-lexai.review.queue}
3034

3135
# ── PASETO ──
3236
PASETO_LOCAL_SECRET: ${PASETO_LOCAL_SECRET}
@@ -113,6 +117,10 @@ services:
113117
ANALYSIS_QUEUE: ${ANALYSIS_QUEUE}
114118
ALERT_QUEUE: ${ALERT_QUEUE}
115119
DLX_EXCHANGE: ${DLX_EXCHANGE}
120+
NOTIFICATION_QUEUE: ${NOTIFICATION_QUEUE:-lexai.notification.queue}
121+
ANALYTICS_EVENTS_QUEUE: ${ANALYTICS_EVENTS_QUEUE:-lexai.analytics.queue}
122+
ORDER_QUEUE: ${ORDER_QUEUE:-lexai.order.queue}
123+
REVIEW_QUEUE: ${REVIEW_QUEUE:-lexai.review.queue}
116124

117125
# ── OpenRouter AI ──
118126
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}

src/config/env.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ const envSchema = z.object({
4545
ANALYSIS_QUEUE: z.string().default('lexai.analysis.queue'),
4646
ALERT_QUEUE: z.string().default('lexai.alert.queue'),
4747
DLX_EXCHANGE: z.string().default('lexai.dlx'),
48+
NOTIFICATION_QUEUE: z.string().default('lexai.notification.queue'),
49+
ANALYTICS_EVENTS_QUEUE: z.string().default('lexai.analytics.queue'),
50+
ORDER_QUEUE: z.string().default('lexai.order.queue'),
51+
REVIEW_QUEUE: z.string().default('lexai.review.queue'),
4852

4953
// ─── PASETO ───────────────────────────────────────────────
5054
PASETO_LOCAL_SECRET: z.string().min(32, 'PASETO_LOCAL_SECRET must be at least 32 characters'),

src/config/rabbitmq.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,23 @@ export async function connectRabbitMQ(url) {
8080
const alertQueue = process.env.ALERT_QUEUE || 'lexai.alert.queue';
8181
await channel.assertQueue(alertQueue, { durable: true });
8282

83+
// ─── New Module Queues ──────────────────────────────────────
84+
// Notification consumer queue — processes order/review events
85+
const notificationQueue = process.env.NOTIFICATION_QUEUE || 'lexai.notification.queue';
86+
await channel.assertQueue(notificationQueue, { durable: true });
87+
88+
// Analytics event consumer queue — async aggregation
89+
const analyticsEventsQueue = process.env.ANALYTICS_EVENTS_QUEUE || 'lexai.analytics.queue';
90+
await channel.assertQueue(analyticsEventsQueue, { durable: true });
91+
92+
// Order event queue
93+
const orderQueue = process.env.ORDER_QUEUE || 'lexai.order.queue';
94+
await channel.assertQueue(orderQueue, { durable: true });
95+
96+
// Review event queue
97+
const reviewQueue = process.env.REVIEW_QUEUE || 'lexai.review.queue';
98+
await channel.assertQueue(reviewQueue, { durable: true });
99+
83100
logger.info('RabbitMQ queues and exchanges asserted');
84101
} catch (err) {
85102
logger.error('RabbitMQ connection failed:', err.message);

src/constants/queues.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ export const QUEUES = Object.freeze({
1212
ALERT: 'lexai.alert.queue', // Contract expiry alert job queue
1313
DLX_EXCHANGE: 'lexai.dlx', // Dead Letter Exchange for failed jobs
1414
DLQ_ANALYSIS: 'lexai.analysis.dlq', // Dead Letter Queue — holds permanently failed analysis jobs
15+
16+
// ─── New Module Queues ──────────────────────────────────
17+
NOTIFICATION: 'lexai.notification.queue', // Notification consumer queue
18+
ANALYTICS_EVENTS: 'lexai.analytics.queue', // Analytics aggregation consumer queue
19+
ORDER: 'lexai.order.queue', // Order event queue
20+
REVIEW: 'lexai.review.queue', // Review event queue
1521
});
1622

1723
// Redis Pub/Sub channel used by workers to push real-time events
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Analytics Controller
3+
*
4+
* Thin HTTP layer for analytics endpoints.
5+
* All aggregation logic lives in analytics.service.js.
6+
* Admin-only — requires authorize('admin') middleware.
7+
*/
8+
9+
import * as analyticsService from '../services/analytics.service.js';
10+
import { sendSuccess } from '../utils/apiResponse.js';
11+
12+
/** GET /analytics/sales — Sales analytics */
13+
export async function getSalesAnalytics(req, res) {
14+
const data = await analyticsService.getSalesAnalytics(req.query);
15+
sendSuccess(res, { data });
16+
}
17+
18+
/** GET /analytics/products — Product performance analytics */
19+
export async function getProductAnalytics(req, res) {
20+
const data = await analyticsService.getProductAnalytics(req.query);
21+
sendSuccess(res, { data });
22+
}
23+
24+
/** GET /analytics/users — User activity analytics */
25+
export async function getUserActivityAnalytics(req, res) {
26+
const data = await analyticsService.getUserActivityAnalytics(req.query);
27+
sendSuccess(res, { data });
28+
}
29+
30+
/** GET /analytics/revenue — Revenue trend analytics */
31+
export async function getRevenueAnalytics(req, res) {
32+
const data = await analyticsService.getRevenueAnalytics(req.query);
33+
sendSuccess(res, { data });
34+
}
35+
36+
/** GET /analytics/top-products — Top products by sales */
37+
export async function getTopProducts(req, res) {
38+
const data = await analyticsService.getTopProducts(req.query);
39+
sendSuccess(res, { data });
40+
}

src/controllers/notification.controller.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
*
44
* Handles CRUD operations for in-app notifications:
55
* - List org notifications with pagination
6+
* - List user-specific notifications with pagination
67
* - Mark individual notification as read
78
* - Mark all notifications as read (bulk)
89
* - Get unread count for badge display
10+
* - Delete a notification
911
*/
1012

1113
import Notification from '../models/Notification.model.js';
@@ -95,3 +97,56 @@ export async function markAllAsRead(req, res) {
9597

9698
sendSuccess(res, { message: 'All notifications marked as read.', data: { modifiedCount: result.modifiedCount } });
9799
}
100+
101+
/**
102+
* GET /notifications/user
103+
* List notifications for the authenticated user (user-scoped, not org-scoped).
104+
*/
105+
export async function getUserNotifications(req, res) {
106+
const { userId } = req.user;
107+
const { page = 1, limit = 20, read, type } = req.query;
108+
109+
const filter = { userId };
110+
111+
if (read !== undefined) {
112+
filter.read = read === 'true';
113+
}
114+
if (type) {
115+
filter.type = type;
116+
}
117+
118+
const skip = (page - 1) * limit;
119+
120+
const [notifications, total] = await Promise.all([
121+
Notification.find(filter)
122+
.sort({ createdAt: -1 })
123+
.skip(skip)
124+
.limit(parseInt(limit))
125+
.lean(),
126+
Notification.countDocuments(filter),
127+
]);
128+
129+
const meta = buildPaginationMeta(total, parseInt(page), parseInt(limit));
130+
131+
sendSuccess(res, { data: { notifications, meta } });
132+
}
133+
134+
/**
135+
* DELETE /notifications/:id
136+
* Delete a notification.
137+
*/
138+
export async function deleteNotification(req, res) {
139+
const { userId } = req.user;
140+
const { id } = req.params;
141+
142+
const notification = await Notification.findOneAndDelete({ _id: id, userId });
143+
144+
if (!notification) {
145+
return res.status(HTTP.NOT_FOUND).json({
146+
success: false,
147+
error: { code: 'NOT_FOUND', message: 'Notification not found.' },
148+
});
149+
}
150+
151+
sendSuccess(res, { message: 'Notification deleted.' });
152+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Order Controller
3+
*
4+
* Thin HTTP layer for order operations.
5+
* All business logic (stock validation, status transitions) lives in order.service.js.
6+
*/
7+
8+
import * as orderService from '../services/order.service.js';
9+
import { sendSuccess } from '../utils/apiResponse.js';
10+
import HTTP from '../constants/httpStatus.js';
11+
12+
/** POST /orders — Create a new order */
13+
export async function createOrder(req, res) {
14+
const order = await orderService.createOrder(req.user.userId, req.body);
15+
sendSuccess(res, {
16+
statusCode: HTTP.CREATED,
17+
message: 'Order placed successfully.',
18+
data: { order },
19+
});
20+
}
21+
22+
/** GET /orders — List user orders with pagination */
23+
export async function listOrders(req, res) {
24+
const { orders, meta } = await orderService.listUserOrders(req.user.userId, req.query);
25+
sendSuccess(res, { data: { orders, meta } });
26+
}
27+
28+
/** GET /orders/stats — Get order stats for user */
29+
export async function getOrderStats(req, res) {
30+
const stats = await orderService.getOrderStats(req.user.userId);
31+
sendSuccess(res, { data: { stats } });
32+
}
33+
34+
/** GET /orders/:id — Get order by ID */
35+
export async function getOrder(req, res) {
36+
const order = await orderService.getOrderById(req.params.id, req.user.userId);
37+
sendSuccess(res, { data: { order } });
38+
}
39+
40+
/** PATCH /orders/:id/status — Update order status */
41+
export async function updateOrderStatus(req, res) {
42+
const order = await orderService.updateOrderStatus(
43+
req.params.id,
44+
req.user.userId,
45+
req.body.status
46+
);
47+
sendSuccess(res, { message: 'Order status updated.', data: { order } });
48+
}
49+
50+
/** PATCH /orders/:id/cancel — Cancel an order */
51+
export async function cancelOrder(req, res) {
52+
const order = await orderService.cancelOrder(
53+
req.params.id,
54+
req.user.userId,
55+
req.body.reason
56+
);
57+
sendSuccess(res, { message: 'Order cancelled successfully.', data: { order } });
58+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Product Controller
3+
*
4+
* Thin HTTP layer for product CRUD, search, and listing.
5+
* All business logic lives in product.service.js.
6+
*/
7+
8+
import * as productService from '../services/product.service.js';
9+
import { sendSuccess } from '../utils/apiResponse.js';
10+
import HTTP from '../constants/httpStatus.js';
11+
12+
/** POST /products — Create a new product */
13+
export async function createProduct(req, res) {
14+
const product = await productService.createProduct(req.user.userId, req.body);
15+
sendSuccess(res, {
16+
statusCode: HTTP.CREATED,
17+
message: 'Product created successfully.',
18+
data: { product },
19+
});
20+
}
21+
22+
/** GET /products — List products with pagination, filtering, sorting */
23+
export async function listProducts(req, res) {
24+
const { products, meta } = await productService.listProducts(req.query);
25+
sendSuccess(res, { data: { products, meta } });
26+
}
27+
28+
/** GET /products/search — Full-text search products */
29+
export async function searchProducts(req, res) {
30+
const { products, meta } = await productService.searchProducts(req.query);
31+
sendSuccess(res, { data: { products, meta } });
32+
}
33+
34+
/** GET /products/:id — Get product by ID */
35+
export async function getProduct(req, res) {
36+
const product = await productService.getProductById(req.params.id);
37+
sendSuccess(res, { data: { product } });
38+
}
39+
40+
/** PATCH /products/:id — Update product (owner only) */
41+
export async function updateProduct(req, res) {
42+
const product = await productService.updateProduct(req.params.id, req.user.userId, req.body);
43+
sendSuccess(res, { message: 'Product updated successfully.', data: { product } });
44+
}
45+
46+
/** DELETE /products/:id — Delete product (owner only) */
47+
export async function deleteProduct(req, res) {
48+
await productService.deleteProduct(req.params.id, req.user.userId);
49+
sendSuccess(res, { message: 'Product deleted successfully.' });
50+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Review Controller
3+
*
4+
* Thin HTTP layer for review CRUD.
5+
* All business logic (rating recalculation, duplicate detection) lives in review.service.js.
6+
*/
7+
8+
import * as reviewService from '../services/review.service.js';
9+
import { sendSuccess } from '../utils/apiResponse.js';
10+
import HTTP from '../constants/httpStatus.js';
11+
12+
/** POST /reviews — Add a review */
13+
export async function addReview(req, res) {
14+
const review = await reviewService.addReview(req.user.userId, req.body);
15+
sendSuccess(res, {
16+
statusCode: HTTP.CREATED,
17+
message: 'Review added successfully.',
18+
data: { review },
19+
});
20+
}
21+
22+
/** GET /reviews/product/:productId — Get product reviews */
23+
export async function getProductReviews(req, res) {
24+
const { reviews, meta } = await reviewService.getProductReviews(
25+
req.params.productId,
26+
req.query
27+
);
28+
sendSuccess(res, { data: { reviews, meta } });
29+
}
30+
31+
/** GET /reviews/my — Get authenticated user's reviews */
32+
export async function getMyReviews(req, res) {
33+
const { reviews, meta } = await reviewService.getUserReviews(req.user.userId, req.query);
34+
sendSuccess(res, { data: { reviews, meta } });
35+
}
36+
37+
/** DELETE /reviews/:id — Delete a review (author only) */
38+
export async function deleteReview(req, res) {
39+
await reviewService.deleteReview(req.params.id, req.user.userId);
40+
sendSuccess(res, { message: 'Review deleted successfully.' });
41+
}

src/models/Notification.model.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const notificationSchema = new mongoose.Schema(
2525
},
2626
type: {
2727
type: String,
28-
enum: ['analysis_complete', 'analysis_failed', 'contract_expiring', 'quota_warning', 'invitation'],
28+
enum: ['analysis_complete', 'analysis_failed', 'contract_expiring', 'quota_warning', 'invitation', 'order_created', 'order_updated', 'review_added'],
2929
required: true,
3030
},
3131
channel: {

0 commit comments

Comments
 (0)