csa-backend-test/app/controllers/cms.controller.js

372 lines
12 KiB
JavaScript

// ENVIRONMENT
require('dotenv').config();
// DATABASE
const { PrismaClient : CMSClient, ContentType, CorpType } = require("../../prisma/clients/cms");
const prisma = new CMSClient();
// CONSTANTS
const { badRequestResponse, successResponse, notFoundResponse } = require("../res/responses.js");
// SERVICES
const fileServices = require('../services/file.services.js');
const { getAllBuckets } = require('../services/minio.services.js');
const logger = require('../services/logger.services.js');
const { localTime } = require("../services/time.services.js");
const { default: prefixes } = require('../static/prefix.js');
exports.createContent = async (req, res) => {
try {
const { title, description, type, corp } = req.body;
if (!Object.values(CorpType).includes(corp)) {
return badRequestResponse(res, "Invalid corp value", `Allowed values: ${Object.values(CorpType).join(", ")}`);
}
const imageFile = req.files;
if (!imageFile) {
return badRequestResponse(res, "Image file is required", "Missing image file in request");
}
if (!title || !description || !type || !corp) {
return badRequestResponse(res, "Title, description, type, and corp are required", null);
}
if (!Object.values(ContentType).includes(type)) {
return badRequestResponse(res, "Invalid type value", `Allowed values: ${Object.values(ContentType).join(", ")}`);
}
const files = await fileServices.upload(prefixes.bucketName, type, (imageFile[0]).mimetype, imageFile);
await prisma.appContent.create({
data: {
Title_APC: title,
Content_APC: description,
Type_APC: type,
CorpType_APC: corp,
Url_APC: files[0],
TargetUrl_APC: req.body.targetUrl || null,
Filename_APC: files[1],
CreatedAt_APC: localTime(new Date()),
UpdatedAt_APC: localTime(new Date())
}
});
return successResponse(res, `${type} item created successfully`, null);
} catch (err) {
logger.info(err);
return badRequestResponse(res, "Internal Server Error", err);
}
};
exports.getContents = async (req, res) => {
try {
const { type, corp } = req.params;
if (!Object.values(ContentType).includes(type)) {
return badRequestResponse(res, "Invalid type value", `Allowed values: ${Object.values(ContentType).join(", ")}`);
}
if (corp != "all" && !Object.values(CorpType).includes(corp)) {
return badRequestResponse(res, "Invalid corp value", `Allowed values: ${Object.values(CorpType).join(", ")}`);
}
var data = [];
if (corp == "all") {
data = await prisma.appContent.findMany({
where: {
Type_APC: type,
},
orderBy: {
CreatedAt_APC: 'desc'
}
});
} else {
data = await prisma.appContent.findMany({
where: {
Type_APC: type,
CorpType_APC: corp,
},
orderBy: {
CreatedAt_APC: 'desc'
}
});
}
return successResponse(res, `${type} with corp ${corp} items retrieved successfully`, data);
} catch (err) {
logger.info(err);
return badRequestResponse(res, "Internal Server Error", err);
}
};
exports.updateContent = async (req, res) => {
try {
const { id } = req.params;
const { title, description, type, corp } = req.body;
const imageFile = req.files;
if (!imageFile) {
return badRequestResponse(res, "Image file is required", "Missing image file in request");
}
if (!title || !description || !type || !corp) {
return badRequestResponse(res, "Title, description, type, and corp are required", null);
}
if (!Object.values(ContentType).includes(type)) {
return badRequestResponse(res, "Invalid type value", `Allowed values: ${Object.values(ContentType).join(", ")}`);
}
if (corp != "all" && !Object.values(CorpType).includes(corp)) {
return badRequestResponse(res, "Invalid corp value", `Allowed values: ${Object.values(CorpType).join(", ")}`);
}
const existingItem = await prisma.appContent.findUnique({
where: { UUID_APC: id }
});
await fileServices.delete(prefixes.bucketName, existingItem.Type_APC, existingItem.Filename_APC);
const files = await fileServices.upload(prefixes.bucketName, type, (imageFile[0]).mimetype, imageFile);
await prisma.appContent.update({
where: { UUID_APC: id },
data: {
Title_APC: title || existingItem.Title_APC,
Content_APC: description || existingItem.Content_APC,
Type_APC: type || existingItem.Type_APC,
CorpType_APC: corp || existingItem.CorpType_APC,
Url_APC: files[0] || existingItem.Url_APC,
TargetUrl_APC: req.body.targetUrl || existingItem.TargetUrl_APC,
Filename_APC: files[1] || existingItem.Filename_APC,
UpdatedAt_APC: localTime(new Date())
}
});
return successResponse(res, `${type} with corp ${corp} item updated successfully`, null);
} catch (err) {
logger.info(err);
return badRequestResponse(res, "Internal Server Error", err);
}
};
exports.deleteContent = async (req, res) => {
try {
const { id } = req.params;
const existingItem = await prisma.appContent.findFirst({
where: { UUID_APC: id }
});
if (!existingItem) {
return badRequestResponse(res, `${existingItem.Type_APC} item not found`, "Invalid ID");
}
await fileServices.delete(prefixes.bucketName, existingItem.Type_APC, existingItem.Filename_APC);
await prisma.appContent.delete({
where: { UUID_APC: id }
});
return successResponse(res, `${existingItem.Type_APC} item deleted successfully`, null);
} catch (err) {
return badRequestResponse(res, "Internal Server Error", err);
}
};
exports.getStats = async (req, res) => {
try {
const now = new Date();
const last30Days = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
const last7Days = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
const [
splashCount,
promoCount,
articleCount,
bannerCount,
floatingWidgetCount,
apiKeysCount,
usersCount,
buckets,
allContents,
campaignStats,
userActivities,
recentContents,
recentUsers
] = await Promise.all([
prisma.appContent.count({ where: { Type_APC: ContentType.splash } }),
prisma.appContent.count({ where: { Type_APC: ContentType.promo } }),
prisma.appContent.count({ where: { Type_APC: ContentType.article } }),
prisma.appContent.count({ where: { Type_APC: ContentType.banner } }),
prisma.appContent.count({ where: { Type_APC: ContentType.floatingWidget } }),
prisma.appCredential.count(),
prisma.usersToken.count(),
getAllBuckets(),
prisma.appContent.findMany({
select: {
Type_APC: true,
CorpType_APC: true,
CreatedAt_APC: true,
}
}),
prisma.appCampaign.groupBy({
by: ['Status_ACP'],
_count: {
Status_ACP: true
}
}),
prisma.usersActivity.groupBy({
by: ['ActivityType_UA'],
_count: {
ActivityType_UA: true
}
}),
prisma.appContent.findMany({
where: {
CreatedAt_APC: {
gte: last30Days
}
},
select: {
CreatedAt_APC: true,
Type_APC: true,
},
orderBy: {
CreatedAt_APC: 'asc'
}
}),
prisma.usersToken.findMany({
where: {
CreatedAt_UT: {
gte: last7Days
}
},
select: {
CreatedAt_UT: true
},
orderBy: {
CreatedAt_UT: 'asc'
}
})
]);
const bucketCount = Array.isArray(buckets) ? buckets.length : 0;
const contentByCorpType = {};
allContents.forEach(content => {
if (!contentByCorpType[content.CorpType_APC]) {
contentByCorpType[content.CorpType_APC] = 0;
}
contentByCorpType[content.CorpType_APC]++;
});
const contentMatrix = {};
allContents.forEach(content => {
const key = `${content.Type_APC}_${content.CorpType_APC}`;
if (!contentMatrix[key]) {
contentMatrix[key] = {
type: content.Type_APC,
corp: content.CorpType_APC,
count: 0
};
}
contentMatrix[key].count++;
});
const contentTimeline = {};
recentContents.forEach(content => {
const date = new Date(content.CreatedAt_APC).toISOString().split('T')[0];
if (!contentTimeline[date]) {
contentTimeline[date] = {};
}
if (!contentTimeline[date][content.Type_APC]) {
contentTimeline[date][content.Type_APC] = 0;
}
contentTimeline[date][content.Type_APC]++;
});
const userRegistrationTrend = {};
recentUsers.forEach(user => {
const date = new Date(user.CreatedAt_UT).toISOString().split('T')[0];
if (!userRegistrationTrend[date]) {
userRegistrationTrend[date] = 0;
}
userRegistrationTrend[date]++;
});
const campaignStatusDistribution = campaignStats.reduce((acc, item) => {
acc[item.Status_ACP] = item._count.Status_ACP;
return acc;
}, {});
const activityTypeDistribution = userActivities.reduce((acc, item) => {
acc[item.ActivityType_UA] = item._count.ActivityType_UA;
return acc;
}, {});
return successResponse(res, "CMS stats retrieved successfully", {
summary: {
content: {
splash: splashCount,
promo: promoCount,
article: articleCount,
banner: bannerCount,
floatingWidget: floatingWidgetCount,
total: splashCount + promoCount + articleCount + bannerCount + floatingWidgetCount
},
infra: {
buckets: bucketCount,
apiKeys: apiKeysCount,
users: usersCount,
},
campaigns: await prisma.appCampaign.count(),
activities: await prisma.usersActivity.count()
},
charts: {
contentByType: {
labels: ['Splash', 'Promo', 'Article', 'Banner', 'Floating Widget'],
data: [splashCount, promoCount, articleCount, bannerCount, floatingWidgetCount]
},
contentByCorpType: {
labels: Object.keys(contentByCorpType),
data: Object.values(contentByCorpType)
},
contentMatrix: Object.values(contentMatrix),
contentCreationTimeline: contentTimeline,
campaignStatus: {
labels: Object.keys(campaignStatusDistribution),
data: Object.values(campaignStatusDistribution)
},
activityTypes: {
labels: Object.keys(activityTypeDistribution),
data: Object.values(activityTypeDistribution)
},
userRegistrationTrend: {
labels: Object.keys(userRegistrationTrend).sort(),
data: Object.keys(userRegistrationTrend).sort().map(date => userRegistrationTrend[date])
}
}
});
} catch (err) {
return badRequestResponse(res, "Internal Server Error", err);
}
};