Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export default function CustomerInsights() {
);
}

console.log(data);

if (!data) return null;

const stats = [
Expand Down
74 changes: 71 additions & 3 deletions services/checkout-service/src/analytics/analytics.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { AnalyticsService } from './analytics.service.js';
export class AnalyticsController {
constructor(private analyticsService: AnalyticsService) {}

// SALES OVERVIEW

getOverview = async (req: Request, res: Response) => {
try {
const data = await this.analyticsService.getOverview();
Expand All @@ -18,13 +20,26 @@ export class AnalyticsController {
try {
const days = Number(req.query.range) || 30;
const data = await this.analyticsService.getRevenueChart(days);

res.json(data);
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Failed to fetch revenue chart' });
}
};

getGrowthStats = async (req: Request, res: Response) => {
try {
const data = await this.analyticsService.getGrowthStats();
res.json(data);
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Failed to fetch growth stats' });
}
};

// CART ANALYTICS

getCartStats = async (req: Request, res: Response) => {
try {
const data = await this.analyticsService.getCartStats();
Expand All @@ -35,6 +50,8 @@ export class AnalyticsController {
}
};

// PRODUCT ANALYTICS

// TOP SELLING PRODUCTS
getTopProducts = async (req: Request, res: Response) => {
try {
Expand Down Expand Up @@ -77,6 +94,7 @@ export class AnalyticsController {
trackProductView = async (req: Request, res: Response) => {
try {
const { productId } = req.body;

await this.analyticsService.trackProductView(productId);

res.json({ success: true });
Expand All @@ -86,6 +104,8 @@ export class AnalyticsController {
}
};

// FUNNEL ANALYTICS

getFunnelStats = async (req: Request, res: Response) => {
try {
const data = await this.analyticsService.getFunnelStats();
Expand All @@ -96,6 +116,8 @@ export class AnalyticsController {
}
};

// DASHBOARD SUMMARY

getDashboardSummary = async (req: Request, res: Response) => {
try {
const data = await this.analyticsService.getDashboardSummary();
Expand All @@ -106,9 +128,55 @@ export class AnalyticsController {
}
};

getGrowthStats = async (req: Request, res: Response) => {
const data = await this.analyticsService.getGrowthStats();
// USER ANALYTICS

getUserAnalytics = async (req: Request, res: Response) => {
try {
const { userId } = req.params;

const data = await this.analyticsService.getUserAnalytics(userId);

res.json(data);
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Failed to fetch user analytics' });
}
};

res.json(data);
getTopCustomers = async (req: Request, res: Response) => {
try {
const limit = Number(req.query.limit) || 10;

const data = await this.analyticsService.getTopCustomers(limit);

res.json(data);
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Failed to fetch top customers' });
}
};

getMostActiveCustomers = async (req: Request, res: Response) => {
try {
const limit = Number(req.query.limit) || 10;

const data = await this.analyticsService.getMostActiveCustomers(limit);

res.json(data);
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Failed to fetch active customers' });
}
};

getUserOverview = async (req: Request, res: Response) => {
try {
const data = await this.analyticsService.getUserOverview();

res.json(data);
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Failed to fetch user overview' });
}
};
}
28 changes: 26 additions & 2 deletions services/checkout-service/src/analytics/analytics.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,39 @@ const router = Router();
const analyticsService = new AnalyticsService();
const controller = new AnalyticsController(analyticsService);

/* =============================
SALES ANALYTICS
============================= */

router.get('/overview', controller.getOverview);
router.get('/revenue', controller.getRevenueChart);
router.get('/growth', controller.getGrowthStats);

//DASHBOARD SUMMARY

router.get('/dashboard', controller.getDashboardSummary);

//CART ANALYTICS

router.get('/cart', controller.getCartStats);

//PRODUCT ANALYTICS

router.get('/products/top', controller.getTopProducts);
router.get('/products/views', controller.getMostViewedProducts);
router.get('/products/revenue', controller.getTopRevenueProducts);

router.post('/product-view', controller.trackProductView);

//FUNNEL ANALYTICS

router.get('/funnel', controller.getFunnelStats);
router.get('/dashboard', controller.getDashboardSummary);
router.get('/growth', controller.getGrowthStats);

//USER ANALYTICS

router.get('/users/top', controller.getTopCustomers);
router.get('/users/active', controller.getMostActiveCustomers);
router.get('/users/overview', controller.getUserOverview);
router.get('/users/:userId', controller.getUserAnalytics);

export default router;
115 changes: 88 additions & 27 deletions services/checkout-service/src/analytics/analytics.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,16 @@ import { CartStats } from './models/cartStats.model.js';
import { FunnelStats } from './models/funnelStats.model.js';
import { ProductStats } from './models/productStats.model.js';
import { SalesDaily } from './models/salesDaily.model.js';
import { UserStats } from './models/userAnalytics.model.js';

export class AnalyticsService {
//ORDER CREATED

async trackOrder(order: Order): Promise<void> {
const date = new Date(order.createdAt).toISOString().split('T')[0];

await SalesDaily.updateOne(
{ date },
{
$inc: {
revenue: order.totalAmount,
orders: 1,
},
},
{ upsert: true },
);

for (const item of order.items) {
await ProductStats.updateOne(
const productUpdates = order.items.map((item) =>
ProductStats.updateOne(
{ productId: item.productId },
{
$inc: {
Expand All @@ -31,22 +21,49 @@ export class AnalyticsService {
},
},
{ upsert: true },
);
}

await CartStats.updateOne(
{ date },
{
$inc: { cartsConverted: 1 },
},
{ upsert: true },
),
);

await FunnelStats.updateOne(
{ date },
{ $inc: { ordersCompleted: 1 } },
{ upsert: true },
);
await Promise.all([
SalesDaily.updateOne(
{ date },
{
$inc: {
revenue: order.totalAmount,
orders: 1,
},
},
{ upsert: true },
),

CartStats.updateOne(
{ date },
{ $inc: { cartsConverted: 1 } },
{ upsert: true },
),

FunnelStats.updateOne(
{ date },
{ $inc: { ordersCompleted: 1 } },
{ upsert: true },
),

UserStats.updateOne(
{ userId: order.userId },
{
$inc: {
totalOrders: 1,
totalSpent: order.totalAmount,
},
$set: {
lastOrderAt: new Date(order.createdAt),
},
},
{ upsert: true },
),

...productUpdates,
]);
}

//REFUND CREATED
Expand Down Expand Up @@ -349,4 +366,48 @@ export class AnalyticsService {
orderGrowth,
};
}

async getUserAnalytics(userId: string) {
return UserStats.findOne({ userId });
}

async getTopCustomers(limit = 10) {
return UserStats.find().sort({ totalSpent: -1 }).limit(limit);
}

async getMostActiveCustomers(limit = 10) {
return UserStats.find().sort({ totalOrders: -1 }).limit(limit);
}

async getUserOverview() {
const stats = await UserStats.aggregate([
{
$group: {
_id: null,
totalCustomers: { $sum: 1 },
totalOrders: { $sum: '$totalOrders' },
totalRevenue: { $sum: '$totalSpent' },
},
},
]);

return (
stats[0] ?? {
totalCustomers: 0,
totalOrders: 0,
totalRevenue: 0,
}
);
}

async getCustomerGrowth() {
return UserStats.aggregate([
{
$group: {
_id: '$createdAt',
users: { $sum: 1 },
},
},
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import mongoose, { Schema, Document } from 'mongoose';

export interface MonthlyStat {
month: string;
orders: number;
spent: number;
}

export interface IUserStats extends Document {
userId: mongoose.Types.ObjectId;
totalOrders: number;
totalSpent: number;
lastOrderAt?: Date;
monthlyStats: MonthlyStat[];
}

const MonthlyStatSchema = new Schema<MonthlyStat>(
{
month: {
type: String,
required: true,
},
orders: {
type: Number,
default: 0,
},
spent: {
type: Number,
default: 0,
},
},
{ _id: false },
);

const UserStatsSchema = new Schema<IUserStats>(
{
userId: {
type: Schema.Types.ObjectId,
required: true,
unique: true,
index: true,
},

totalOrders: {
type: Number,
default: 0,
},

totalSpent: {
type: Number,
default: 0,
},

lastOrderAt: {
type: Date,
},

monthlyStats: [MonthlyStatSchema],
},
{
timestamps: true,
},
);

export const UserStats = mongoose.model<IUserStats>(
'UserStats',
UserStatsSchema,
);
Loading