372 lines
12 KiB
JavaScript
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);
|
|
}
|
|
};
|