add dashboard

This commit is contained in:
Litlyx
2024-06-01 15:27:40 +02:00
parent 75f0787c3b
commit df4faf366f
201 changed files with 91267 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
import { ProjectModel, TProject } from "@schema/ProjectSchema";
export default defineEventHandler(async event => {
const body = await readBody(event);
const newProjectName = body.name;
if (!newProjectName) return setResponseStatus(event, 400, 'ProjectName too short');
if (newProjectName.length < 2) return setResponseStatus(event, 400, 'ProjectName too short');
const userData = getRequestUser(event);
if (!userData?.logged) return setResponseStatus(event, 400, 'NotLogged');
const existingUserProjects = await ProjectModel.countDocuments({ owner: userData.id });
if (existingUserProjects == 3) return setResponseStatus(event, 400, 'Already have 3 projects');
const newProject = new ProjectModel({ owner: userData.id, name: newProjectName });
const saved = await newProject.save();
return saved.toJSON() as TProject;
});

View File

@@ -0,0 +1,18 @@
import { ProjectModel } from "@schema/ProjectSchema";
export default defineEventHandler(async event => {
const body = await readBody(event);
const projectId = body.project_id;
const userData = getRequestUser(event);
if (!userData?.logged) return setResponseStatus(event, 400, 'NotLogged');
const projects = await ProjectModel.countDocuments({ owner: userData.id });
if (projects == 1) return setResponseStatus(event, 400, 'Cannot delete last project');
const deletation = await ProjectModel.deleteOne({ owner: userData.id, _id: projectId });
return { ok: deletation.acknowledged };
});

View File

@@ -0,0 +1,36 @@
import { ProjectModel } from "@schema/ProjectSchema";
import { UserSettingsModel } from "@schema/UserSettings";
import { EventModel } from '@schema/metrics/EventSchema';
export default defineEventHandler(async event => {
const userData = getRequestUser(event);
if (!userData?.logged) return setResponseStatus(event, 400, 'NotLogged');
const currentActiveProject = await UserSettingsModel.findOne({ user_id: userData.id });
if (!currentActiveProject) return setResponseStatus(event, 400, 'You need to select a project');
const project_id = currentActiveProject.active_project_id;
const project = await ProjectModel.findById(project_id);
if (!project) return setResponseStatus(event, 400, 'Project not found');
const eventsReportData = await EventModel.find({
project_id: project._id,
created_at: { $gt: Date.now() - 1000 * 60 * 60 * 24 * 7 + 30 }
});
const csvLines: string[][] = [['name']];
eventsReportData.forEach(e => {
csvLines.push([e.name])
});
return csvLines.map(row => {
return row.join(',');
}).join('\n');
});

View File

@@ -0,0 +1,164 @@
import pdfkit from 'pdfkit';
import { PassThrough } from 'node:stream';
import fs from 'fs';
import { ProjectModel, TProject } from "@schema/ProjectSchema";
import { UserSettingsModel } from "@schema/UserSettings";
import { VisitModel } from '@schema/metrics/VisitSchema';
import { EventModel } from '@schema/metrics/EventSchema';
type PDF_Data = {
pageVisits: number, customEvents: number,
visitsDay: number, eventsDay: number, visitsSessions: number,
visitsSessionsDay: number
}
function formatNumberK(value: string | number, decimals: number = 1) {
const num = parseInt(value.toString());
if (num > 1_000_000) return (num / 1_000_000).toFixed(decimals) + ' M';
if (num > 1_000) return (num / 1_000).toFixed(decimals) + ' K';
return num.toFixed();
}
function createPdf(projectName: string, data: PDF_Data) {
const pdf = new pdfkit({
size: 'A4',
margins: { top: 50, bottom: 50, left: 50, right: 50 },
});
pdf.pipe(fs.createWriteStream('out.pdf'));
// Set up fonts and colors
pdf
.fillColor('#ffffff')
.rect(0, 0, pdf.page.width, pdf.page.height)
.fill('#000000');
// Title
pdf
.font('pdf_fonts/Poppins-Bold.ttf')
.fontSize(26)
.fillColor('#ffffff')
.text(`Report of: ${projectName}`, 50, 50);
// Section 1
pdf
.font('pdf_fonts/Poppins-SemiBold.ttf')
.fontSize(20)
.fillColor('#ffffff')
.text('-> This month has seen a lot of visits!', 50, 120);
pdf
.image('pdf_images/d.png', 50, 160, { width: 300 })
.font('pdf_fonts/Poppins-Bold.ttf')
.fontSize(28)
.fillColor('#ffffff')
.text(`${formatNumberK(data.pageVisits, 2)}`, 400, 180)
.text('WOW!', 400, 210);
// Section 2
pdf
.font('pdf_fonts/Poppins-SemiBold.ttf')
.fontSize(20)
.fillColor('#ffffff')
.text('-> There are also many recorded events!', 50, 350);
pdf
.image('pdf_images/c.png', 50, 390, { width: 300 })
.font('pdf_fonts/Poppins-Bold.ttf')
.fontSize(28)
.fillColor('#ffffff')
.text(`${formatNumberK(data.customEvents, 2)}`, 400, 420)
.text('Let\'s go!', 400, 450);
// Final section
pdf
.font('pdf_fonts/Poppins-SemiBold.ttf')
.fontSize(20)
.fillColor('#ffffff')
.text('This report is not final, it only serves to demonstrate the potential of this tool. LitLyx will improve soon! Stay tuned!', 50, 600);
pdf
.font('pdf_fonts/Poppins-Regular.ttf')
.fontSize(14)
.fillColor('#ffffff')
.text('Generated on litlyx.com', 50, 760);
pdf
.image('pdf_images/logo.png', 460, 700, { width: 100 }) // replace with the correct path to your Unsplash image
// End PDF creation and save to file
pdf.end();
return pdf;
}
export default defineEventHandler(async event => {
const userData = getRequestUser(event);
if (!userData?.logged) return setResponseStatus(event, 400, 'NotLogged');
const currentActiveProject = await UserSettingsModel.findOne({ user_id: userData.id });
if (!currentActiveProject) return setResponseStatus(event, 400, 'You need to select a project');
const project_id = currentActiveProject.active_project_id;
const project = await ProjectModel.findById(project_id);
if (!project) return setResponseStatus(event, 400, 'Project not found');
const eventsCount = await EventModel.countDocuments({ project_id: project._id });
const visitsCount = await VisitModel.countDocuments({ project_id: project._id });
const sessionsVisitsCount: any[] = await VisitModel.aggregate([
{ $match: { project_id: project._id } },
{ $group: { _id: "$session" } },
{ $count: "count" }
]);
const firstEventDate = await EventModel.findOne({ project_id: project._id }, { created_at: 1 }, { sort: { created_at: 1 } });
const firstViewDate = await VisitModel.findOne({ project_id: project._id }, { created_at: 1 }, { sort: { created_at: 1 } });
const avgEventsDay = () => {
const days = (Date.now() - (firstEventDate?.created_at.getTime() || 0)) / 1000 / 60 / 60 / 24;
const avg = eventsCount / Math.max(days, 1);
return avg;
};
const avgVisitDay = () => {
const days = (Date.now() - (firstViewDate?.created_at.getTime() || 0)) / 1000 / 60 / 60 / 24;
const avg = visitsCount / Math.max(days, 1);
return avg;
};
const avgVisitsSessionsDay = () => {
const days = (Date.now() - (firstViewDate?.created_at.getTime() || 0)) / 1000 / 60 / 60 / 24;
const avg = sessionsVisitsCount[0].count / Math.max(days, 1);
return avg;
};
const pdf = createPdf(
project.name, {
customEvents: eventsCount,
eventsDay: avgEventsDay(),
pageVisits: visitsCount,
visitsDay: avgVisitDay(),
visitsSessions: sessionsVisitsCount[0].count,
visitsSessionsDay: avgVisitsSessionsDay()
});
const passThrough = new PassThrough();
pdf.pipe(passThrough);
await sendStream(event, passThrough);
});

View File

@@ -0,0 +1,12 @@
import { ProjectModel, TProject } from "@schema/ProjectSchema";
export default defineEventHandler(async event => {
const userData = getRequestUser(event);
if (!userData?.logged) return [];
const userProjects = await ProjectModel.find({ owner: userData.id });
return userProjects.map(e => e.toJSON()) as TProject[];
});

View File

@@ -0,0 +1,43 @@
import { ProjectModel, TProject } from "@schema/ProjectSchema";
import { ProjectCountModel } from "@schema/ProjectsCounts";
import { UserSettingsModel } from "@schema/UserSettings";
const { BROKER_UPDATE_EXPIRE_TIME_PATH } = useRuntimeConfig();
export default defineEventHandler(async event => {
const userData = getRequestUser(event);
if (!userData?.logged) return setResponseStatus(event, 400, 'NotLogged');
const currentActiveProject = await UserSettingsModel.findOne({ user_id: userData.id });
if (!currentActiveProject) return setResponseStatus(event, 400, 'You need to select a project');
const project_id = currentActiveProject.active_project_id;
const project = await ProjectModel.findById(project_id);
if (!project) return setResponseStatus(event, 400, 'Project not found');
let projectCounts = await ProjectCountModel.findOne({ project_id }, {}, {
sort: { billing_expire_at: -1 }
});
if (!projectCounts || Date.now() > new Date(projectCounts.billing_expire_at).getTime()) {
await fetch(BROKER_UPDATE_EXPIRE_TIME_PATH + project._id.toString());
projectCounts = await ProjectCountModel.findOne({ project_id }, {}, { sort: { billing_expire_at: -1 } });
}
if (!projectCounts) return setResponseStatus(event, 400, 'Project counts not found');
const result = {
premium: project.premium,
premium_type: project.premium_type,
billing_start_at: projectCounts.billing_start_at,
billing_expire_at: projectCounts.billing_expire_at,
limit: projectCounts.limit,
count: projectCounts.events + projectCounts.visits,
}
return result;
});