mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-10 15:58:38 +01:00
new selfhosted version
This commit is contained in:
21
dashboard/server/api/admin/aggregate_all.ts
Normal file
21
dashboard/server/api/admin/aggregate_all.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { eachDayOfInterval } from "date-fns";
|
||||
import { executeAggregation } from "~/server/services/AggregationService";
|
||||
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
const ctx = await getRequestContext(event, 'admin');
|
||||
|
||||
const dates = eachDayOfInterval({
|
||||
start: new Date('2025-09-01T00:00:00.000Z'),
|
||||
end: new Date('2025-09-25T00:00:00.000Z')
|
||||
});
|
||||
|
||||
for (const date of dates) {
|
||||
console.log(new Date().toLocaleTimeString('it-IT'), 'AGGREGATION', date);
|
||||
await executeAggregation(date);
|
||||
}
|
||||
|
||||
console.log('COMPLETED')
|
||||
|
||||
});
|
||||
7
dashboard/server/api/admin/aichats.ts
Normal file
7
dashboard/server/api/admin/aichats.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { AiNewChatModel } from "~/shared/schema/ai/AiNewChatSchema";
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
const ctx = await getRequestContext(event, 'admin');
|
||||
const result = await AiNewChatModel.find({});
|
||||
return result;
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
const userData = getRequestUser(event);
|
||||
if (!userData?.logged) return;
|
||||
if (!userData.user.roles.includes('ADMIN')) return;
|
||||
|
||||
|
||||
const queueRes = await fetch("http://94.130.182.52:3031/metrics/queue");
|
||||
const queue = await queueRes.json();
|
||||
const durationsRes = await fetch("http://94.130.182.52:3031/metrics/durations");
|
||||
const durations = await durationsRes.json();
|
||||
|
||||
return { queue, durations: durations }
|
||||
|
||||
|
||||
});
|
||||
@@ -1,24 +1,112 @@
|
||||
import { ProjectModel } from "@schema/project/ProjectSchema";
|
||||
import { UserModel } from "@schema/UserSchema";
|
||||
import { EventModel } from "~/shared/schema/metrics/EventSchema";
|
||||
import { VisitModel } from "~/shared/schema/metrics/VisitSchema";
|
||||
import { PremiumModel } from "~/shared/schema/PremiumSchema";
|
||||
import { ProjectModel } from "~/shared/schema/project/ProjectSchema";
|
||||
|
||||
export type TAdminCounts = {
|
||||
projects: number;
|
||||
paid: number;
|
||||
appsumo: number;
|
||||
active: number;
|
||||
dead: number;
|
||||
visits: number;
|
||||
events: number;
|
||||
users: number;
|
||||
free_trial: number;
|
||||
free_trial_ended: number;
|
||||
}
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
const userData = getRequestUser(event);
|
||||
if (!userData?.logged) return;
|
||||
if (!userData.user.roles.includes('ADMIN')) return;
|
||||
const ctx = await getRequestContext(event, 'admin');
|
||||
|
||||
const { from } = getQuery(event);
|
||||
const projects = await ProjectModel.countDocuments({});
|
||||
|
||||
const date = new Date(parseInt(from as any));
|
||||
const users = await PremiumModel.countDocuments();
|
||||
|
||||
const projectsCount = await ProjectModel.countDocuments({
|
||||
created_at: { $gte: date }
|
||||
});
|
||||
const usersCount = await UserModel.countDocuments({
|
||||
created_at: { $gte: date }
|
||||
const premium = await PremiumModel.countDocuments({
|
||||
premium_type: {
|
||||
$in: [
|
||||
8001, 8002, 8003, 8004, 8005, 8006, 8007, 8008, 8009, 8010
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
return { users: usersCount, projects: projectsCount }
|
||||
const free_trial = await PremiumModel.countDocuments({ premium_type: 7006 });
|
||||
const free_trial_ended = await PremiumModel.countDocuments({ premium_type: 7999 });
|
||||
|
||||
const appsumo = await PremiumModel.countDocuments({ premium_type: { $in: [6001, 6002, 6003, 6004] } });
|
||||
|
||||
const result = await ProjectModel.aggregate([
|
||||
{
|
||||
$lookup: {
|
||||
from: "visits",
|
||||
let: {
|
||||
projectId: "$_id"
|
||||
},
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: {
|
||||
$and: [
|
||||
{
|
||||
$eq: [
|
||||
"$project_id",
|
||||
"$$projectId"
|
||||
]
|
||||
},
|
||||
{
|
||||
$gte: [
|
||||
"$created_at",
|
||||
{
|
||||
$dateSubtract: {
|
||||
startDate: "$$NOW",
|
||||
unit: "day",
|
||||
amount: 3
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$limit: 1
|
||||
}
|
||||
],
|
||||
as: "recent_visit"
|
||||
}
|
||||
},
|
||||
{
|
||||
$match: {
|
||||
"recent_visit.0": {
|
||||
$exists: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$count: "active"
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
const visits = await VisitModel.estimatedDocumentCount();
|
||||
const events = await EventModel.estimatedDocumentCount();
|
||||
|
||||
const active = result.length == 0 ? 0 : result[0].active;
|
||||
|
||||
return {
|
||||
projects,
|
||||
users,
|
||||
paid: premium,
|
||||
appsumo,
|
||||
active,
|
||||
dead: projects - active,
|
||||
visits,
|
||||
events,
|
||||
free_trial,
|
||||
free_trial_ended
|
||||
} as TAdminCounts;
|
||||
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
import { ProjectModel } from "@schema/project/ProjectSchema";
|
||||
import { ProjectCountModel } from "@schema/project/ProjectsCounts";
|
||||
import { ProjectLimitModel } from "@schema/project/ProjectsLimits";
|
||||
import { UserModel } from "@schema/UserSchema";
|
||||
import StripeService from '~/server/services/StripeService';
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
const userData = getRequestUser(event);
|
||||
if (!userData?.logged) return;
|
||||
if (!userData.user.roles.includes('ADMIN')) return;
|
||||
|
||||
const { project_id } = getQuery(event);
|
||||
if (!project_id) return setResponseStatus(event, 400, 'ProjectId is required');
|
||||
|
||||
const project = await ProjectModel.findById(project_id);
|
||||
const limits = await ProjectLimitModel.findOne({ project_id });
|
||||
const counts = await ProjectCountModel.findOne({ project_id });
|
||||
const user = await UserModel.findOne({ project_id });
|
||||
|
||||
const subscription =
|
||||
project?.subscription_id ?
|
||||
await StripeService.getSubscription(project.subscription_id) : 'NONE';
|
||||
|
||||
const customer =
|
||||
project?.customer_id ?
|
||||
await StripeService.getCustomer(project.customer_id) : 'NONE';
|
||||
|
||||
return { project, limits, counts, user, subscription, customer }
|
||||
|
||||
});
|
||||
16
dashboard/server/api/admin/domains.ts
Normal file
16
dashboard/server/api/admin/domains.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Types } from "mongoose";
|
||||
import { VisitModel } from "~/shared/schema/metrics/VisitSchema";
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
const ctx = await getRequestContext(event, 'admin');
|
||||
|
||||
const { pid } = getQuery(event);
|
||||
|
||||
const start = performance.now();
|
||||
const domains = await VisitModel.distinct('website', { project_id: new Types.ObjectId(pid as string) });
|
||||
const end = performance.now();
|
||||
|
||||
return domains;
|
||||
|
||||
});
|
||||
@@ -1,31 +1,21 @@
|
||||
import { FeedbackModel, TFeedback } from "~/shared/schema/FeedbackSchema";
|
||||
|
||||
import { FeedbackModel } from '@schema/FeedbackSchema';
|
||||
export type PopulatedFeedback = Omit<TFeedback, 'user_id'> & {
|
||||
user_id?: { email?: string };
|
||||
}
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
const ctx = await getRequestContext(event, 'admin');
|
||||
|
||||
const userData = getRequestUser(event);
|
||||
if (!userData?.logged) return;
|
||||
if (!userData.user.roles.includes('ADMIN')) return;
|
||||
|
||||
const feedbacks = await FeedbackModel.aggregate([
|
||||
{
|
||||
$lookup: {
|
||||
from: 'users',
|
||||
localField: 'user_id',
|
||||
foreignField: '_id',
|
||||
as: 'user'
|
||||
}
|
||||
const feedbacks = await FeedbackModel.find({}, {}, {
|
||||
populate: {
|
||||
path: 'user_id',
|
||||
model: 'users',
|
||||
select: 'email'
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: 'projects',
|
||||
localField: 'project_id',
|
||||
foreignField: '_id',
|
||||
as: 'project'
|
||||
}
|
||||
},
|
||||
])
|
||||
lean: true
|
||||
});
|
||||
|
||||
return feedbacks;
|
||||
return feedbacks as any as PopulatedFeedback[];
|
||||
|
||||
});
|
||||
8
dashboard/server/api/admin/feedbacks_delete.ts
Normal file
8
dashboard/server/api/admin/feedbacks_delete.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { FeedbackModel, TFeedback } from "~/shared/schema/FeedbackSchema";
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
const ctx = await getRequestContext(event, 'admin');
|
||||
const { id } = getQuery(event);
|
||||
const deletation = await FeedbackModel.deleteOne({ _id: id });
|
||||
return deletation;
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
import { ProjectModel } from "@schema/project/ProjectSchema";
|
||||
import { UserModel } from "@schema/UserSchema";
|
||||
import { EventModel } from "@schema/metrics/EventSchema";
|
||||
import { VisitModel } from "@schema/metrics/VisitSchema";
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
const userData = getRequestUser(event);
|
||||
if (!userData?.logged) return;
|
||||
if (!userData.user.roles.includes('ADMIN')) return;
|
||||
|
||||
const { filterFrom, filterTo } = getQuery(event);
|
||||
|
||||
|
||||
const matchQuery = {
|
||||
created_at: {
|
||||
$gte: new Date(filterFrom as string),
|
||||
$lte: new Date(filterTo as string)
|
||||
}
|
||||
}
|
||||
|
||||
const totalProjects = await ProjectModel.countDocuments({ ...matchQuery });
|
||||
const premiumProjects = await ProjectModel.countDocuments({ ...matchQuery, premium: true });
|
||||
|
||||
const deadProjects = await ProjectModel.countDocuments({ ...matchQuery });
|
||||
|
||||
const totalUsers = await UserModel.countDocuments({ ...matchQuery });
|
||||
|
||||
const totalVisits = 0;
|
||||
|
||||
const totalEvents = await EventModel.countDocuments({ ...matchQuery });
|
||||
|
||||
|
||||
return { totalProjects, premiumProjects, deadProjects, totalUsers, totalVisits, totalEvents }
|
||||
|
||||
|
||||
});
|
||||
@@ -1,11 +1,8 @@
|
||||
|
||||
import { OnboardingModel } from '~/shared/schema/OnboardingSchema';
|
||||
import { OnboardingModel } from "~/shared/schema/OnboardingSchema";
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
const userData = getRequestUser(event);
|
||||
if (!userData?.logged) return;
|
||||
if (!userData.user.roles.includes('ADMIN')) return;
|
||||
const ctx = await getRequestContext(event, 'admin');
|
||||
|
||||
const analytics = await OnboardingModel.aggregate([
|
||||
{
|
||||
@@ -1,89 +0,0 @@
|
||||
import { ProjectModel, TProject } from "@schema/project/ProjectSchema";
|
||||
import { TProjectLimit } from "~/shared/schema/project/ProjectsLimits";
|
||||
import { TAdminProject } from "./projects";
|
||||
import { Types } from "mongoose";
|
||||
import { VisitModel } from "~/shared/schema/metrics/VisitSchema";
|
||||
|
||||
function addFieldsFromArray(data: { fieldName: string, projectedName: string, arrayName: string }[]) {
|
||||
const content: Record<string, any> = {};
|
||||
data.forEach(e => {
|
||||
content[e.projectedName] = {
|
||||
"$ifNull": [{ "$getField": { "field": e.fieldName, "input": { "$arrayElemAt": [`$${e.arrayName}`, 0] } } }, 0]
|
||||
}
|
||||
});
|
||||
return content;
|
||||
}
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
const userData = getRequestUser(event);
|
||||
if (!userData?.logged) return;
|
||||
if (!userData.user.roles.includes('ADMIN')) return;
|
||||
|
||||
const { pid } = getQuery(event);
|
||||
|
||||
const projects = await ProjectModel.aggregate([
|
||||
{
|
||||
$match: { _id: new Types.ObjectId(pid as string) }
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "project_limits",
|
||||
localField: "_id",
|
||||
foreignField: "project_id",
|
||||
as: "limits"
|
||||
}
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "project_counts",
|
||||
localField: "_id",
|
||||
foreignField: "project_id",
|
||||
as: "counts"
|
||||
}
|
||||
},
|
||||
{
|
||||
$addFields: addFieldsFromArray([
|
||||
{ arrayName: 'counts', fieldName: 'visits', projectedName: 'visits' },
|
||||
{ arrayName: 'counts', fieldName: 'events', projectedName: 'events' },
|
||||
{ arrayName: 'counts', fieldName: 'session', projectedName: 'session' },
|
||||
{ arrayName: 'counts', fieldName: 'updated_at', projectedName: 'last_log_at' },
|
||||
]),
|
||||
},
|
||||
{
|
||||
$addFields: addFieldsFromArray([
|
||||
{ arrayName: 'limits', fieldName: 'visits', projectedName: 'limit_visits' },
|
||||
{ arrayName: 'limits', fieldName: 'events', projectedName: 'limit_events' },
|
||||
{ arrayName: 'limits', fieldName: 'limit', projectedName: 'limit_max' },
|
||||
{ arrayName: 'limits', fieldName: 'ai_messages', projectedName: 'limit_ai_messages' },
|
||||
{ arrayName: 'limits', fieldName: 'ai_limit', projectedName: 'limit_ai_max' },
|
||||
]),
|
||||
},
|
||||
{
|
||||
$addFields: {
|
||||
limit_total: {
|
||||
$add: [
|
||||
{ $ifNull: ["$limit_visits", 0] },
|
||||
{ $ifNull: ["$limit_events", 0] }
|
||||
]
|
||||
},
|
||||
}
|
||||
},
|
||||
{ $unset: 'counts' },
|
||||
{ $unset: 'limits' },
|
||||
]);
|
||||
|
||||
const domains = await VisitModel.aggregate([
|
||||
{
|
||||
$match: { project_id: new Types.ObjectId(pid as string) }
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: '$website',
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
return { domains, project: (projects[0] as TAdminProject) };
|
||||
|
||||
});
|
||||
@@ -1,115 +0,0 @@
|
||||
import { ProjectModel, TProject } from "@schema/project/ProjectSchema";
|
||||
import { TProjectLimit } from "~/shared/schema/project/ProjectsLimits";
|
||||
|
||||
type ExtendedProject = {
|
||||
limits: TProjectLimit[],
|
||||
counts: [{
|
||||
events: number,
|
||||
visits: number,
|
||||
sessions: number
|
||||
}],
|
||||
visits: number,
|
||||
events: number,
|
||||
sessions: number,
|
||||
limit_visits: number,
|
||||
limit_events: number,
|
||||
limit_max: number,
|
||||
limit_ai_messages: number,
|
||||
limit_ai_max: number,
|
||||
limit_total: number,
|
||||
last_log_at: string
|
||||
}
|
||||
|
||||
export type TAdminProject = TProject & ExtendedProject;
|
||||
|
||||
function addFieldsFromArray(data: { fieldName: string, projectedName: string, arrayName: string }[]) {
|
||||
const content: Record<string, any> = {};
|
||||
data.forEach(e => {
|
||||
content[e.projectedName] = {
|
||||
"$ifNull": [{ "$getField": { "field": e.fieldName, "input": { "$arrayElemAt": [`$${e.arrayName}`, 0] } } }, 0]
|
||||
}
|
||||
});
|
||||
return content;
|
||||
}
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
const userData = getRequestUser(event);
|
||||
if (!userData?.logged) return;
|
||||
if (!userData.user.roles.includes('ADMIN')) return;
|
||||
|
||||
const { page, limit, sortQuery, filterQuery, filterFrom, filterTo } = getQuery(event);
|
||||
|
||||
const pageNumber = parseInt(page as string);
|
||||
const limitNumber = parseInt(limit as string);
|
||||
|
||||
const matchQuery = {
|
||||
...JSON.parse(filterQuery as string),
|
||||
created_at: {
|
||||
$gte: new Date(filterFrom as string),
|
||||
$lte: new Date(filterTo as string)
|
||||
}
|
||||
}
|
||||
|
||||
const count = await ProjectModel.countDocuments(matchQuery);
|
||||
|
||||
const projects = await ProjectModel.aggregate([
|
||||
{
|
||||
$match: matchQuery
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "project_limits",
|
||||
localField: "_id",
|
||||
foreignField: "project_id",
|
||||
as: "limits"
|
||||
}
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "project_counts",
|
||||
localField: "_id",
|
||||
foreignField: "project_id",
|
||||
as: "counts"
|
||||
}
|
||||
},
|
||||
{
|
||||
$addFields: addFieldsFromArray([
|
||||
{ arrayName: 'counts', fieldName: 'visits', projectedName: 'visits' },
|
||||
{ arrayName: 'counts', fieldName: 'events', projectedName: 'events' },
|
||||
{ arrayName: 'counts', fieldName: 'session', projectedName: 'session' },
|
||||
{ arrayName: 'counts', fieldName: 'updated_at', projectedName: 'last_log_at' },
|
||||
]),
|
||||
},
|
||||
{
|
||||
$addFields: addFieldsFromArray([
|
||||
{ arrayName: 'limits', fieldName: 'visits', projectedName: 'limit_visits' },
|
||||
{ arrayName: 'limits', fieldName: 'events', projectedName: 'limit_events' },
|
||||
{ arrayName: 'limits', fieldName: 'limit', projectedName: 'limit_max' },
|
||||
{ arrayName: 'limits', fieldName: 'ai_messages', projectedName: 'limit_ai_messages' },
|
||||
{ arrayName: 'limits', fieldName: 'ai_limit', projectedName: 'limit_ai_max' },
|
||||
]),
|
||||
},
|
||||
{
|
||||
$addFields: {
|
||||
limit_total: {
|
||||
$add: [
|
||||
{ $ifNull: ["$limit_visits", 0] },
|
||||
{ $ifNull: ["$limit_events", 0] }
|
||||
]
|
||||
},
|
||||
}
|
||||
},
|
||||
{ $unset: 'counts' },
|
||||
{ $unset: 'limits' },
|
||||
{ $sort: JSON.parse(sortQuery as string) },
|
||||
{ $skip: pageNumber * limitNumber },
|
||||
{ $limit: limitNumber }
|
||||
]);
|
||||
|
||||
return {
|
||||
count,
|
||||
projects: projects as TAdminProject[]
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
import { ProjectCountModel } from "@schema/project/ProjectsCounts";
|
||||
import { EventModel } from "@schema/metrics/EventSchema";
|
||||
import { SessionModel } from "@schema/metrics/SessionSchema";
|
||||
import { VisitModel } from "@schema/metrics/VisitSchema";
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
const userData = getRequestUser(event);
|
||||
if (!userData?.logged) return;
|
||||
if (!userData.user.roles.includes('ADMIN')) return;
|
||||
|
||||
const { project_id } = getQuery(event);
|
||||
if (!project_id) return setResponseStatus(event, 400, 'ProjectId is required');
|
||||
|
||||
const events = await EventModel.countDocuments({ project_id });
|
||||
const visits = await VisitModel.countDocuments({ project_id });
|
||||
const sessions = await SessionModel.countDocuments({ project_id });
|
||||
|
||||
await ProjectCountModel.updateOne({ project_id, events, visits, sessions }, {}, { upsert: true });
|
||||
|
||||
return { ok: true };
|
||||
});
|
||||
89
dashboard/server/api/admin/shard/info.ts
Normal file
89
dashboard/server/api/admin/shard/info.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
|
||||
|
||||
import mongoose from "mongoose";
|
||||
|
||||
|
||||
async function executeAggregation(uuid: string) {
|
||||
const aggregation = [
|
||||
{ $match: { uuid: new mongoose.Types.UUID(uuid) } },
|
||||
{ $group: { _id: "$shard", chunkCount: { $sum: 1 } } },
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
total: { $sum: "$chunkCount" },
|
||||
shards: { $push: { shard: "$_id", count: "$chunkCount" } }
|
||||
}
|
||||
},
|
||||
{ $unwind: "$shards" },
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
shard: "$shards.shard",
|
||||
chunkCount: "$shards.count",
|
||||
percent: { $round: [{ $multiply: [{ $divide: ["$shards.count", "$total"] }, 100] }, 2] }
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const result = await mongoose.connection.useDb('config').collection('chunks').aggregate(aggregation).toArray();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
async function getAllCollections() {
|
||||
const result = await mongoose.connection.useDb('config').collection('collections').find({}).toArray();
|
||||
return result.filter((e: any) => e._id.startsWith('SimpleMetrics'));
|
||||
}
|
||||
|
||||
async function getOperations() {
|
||||
try {
|
||||
|
||||
const db = mongoose.connection.db?.admin();
|
||||
if (!db) return [];
|
||||
const result = await db.command({
|
||||
aggregate: 1,
|
||||
pipeline: [
|
||||
{ $currentOp: { allUsers: true, localOps: false } },
|
||||
{ $match: { type: 'op', "originatingCommand.reshardCollection": { $regex: "^SimpleMetrics.*" } } }
|
||||
],
|
||||
cursor: {}
|
||||
});
|
||||
|
||||
return result.cursor.firstBatch
|
||||
} catch (ex) {
|
||||
console.error('Error fetching current ops:', ex);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function getAdvancedInfo(collection: string) {
|
||||
try {
|
||||
const db = mongoose.connection.useDb('SimpleMetrics');
|
||||
const nativeDb = db.db;
|
||||
const stats = await nativeDb?.command({ collStats: collection });
|
||||
return stats;
|
||||
} catch (ex) {
|
||||
console.error("Error getting index info:", ex);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
const ctx = await getRequestContext(event, 'admin');
|
||||
|
||||
const collections = await getAllCollections();
|
||||
|
||||
const aggregations = await Promise.all(collections.map(async e => {
|
||||
const collName = e._id.toString().split('.')[1];
|
||||
const chunks = await executeAggregation(e.uuid.toString());
|
||||
const advanced = await getAdvancedInfo(collName);
|
||||
return { info: e, advanced, chunks }
|
||||
}))
|
||||
|
||||
const operations = await getOperations();
|
||||
|
||||
return { aggregations, operations };
|
||||
|
||||
|
||||
});
|
||||
@@ -1,48 +1,226 @@
|
||||
import { TProject } from "@schema/project/ProjectSchema";
|
||||
import { TUser, UserModel } from "@schema/UserSchema";
|
||||
import { ProjectModel } from "~/shared/schema/project/ProjectSchema";
|
||||
import { UserModel } from "~/shared/schema/UserSchema";
|
||||
import { parseNumberInt } from "~/utils/parseNumber";
|
||||
|
||||
export type TAdminUser = TUser & { _id: string, projects: TProject[] };
|
||||
export type TAdminUser = {
|
||||
email: string;
|
||||
premium_type: string;
|
||||
created_at: string;
|
||||
limit: number;
|
||||
visits: number;
|
||||
events: number;
|
||||
projects: { name: string, counts: [any], _id: string }[]
|
||||
}
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
const userData = getRequestUser(event);
|
||||
if (!userData?.logged) return;
|
||||
if (!userData.user.roles.includes('ADMIN')) return;
|
||||
|
||||
const { page, limit, sortQuery, filterQuery, filterFrom, filterTo } = getQuery(event);
|
||||
const ctx = await getRequestContext(event, 'admin');
|
||||
|
||||
const pageNumber = parseInt(page as string);
|
||||
const limitNumber = parseInt(limit as string);
|
||||
const { page, limit, from, to, sort, search } = getQuery(event);
|
||||
|
||||
const matchQuery = {
|
||||
...JSON.parse(filterQuery as string),
|
||||
created_at: {
|
||||
$gte: new Date(filterFrom as string),
|
||||
$lte: new Date(filterTo as string)
|
||||
}
|
||||
const pageValue = parseNumberInt(page, 1);
|
||||
const limitValue = parseNumberInt(limit, 10);
|
||||
const skipValue = (pageValue - 1) * limitValue;
|
||||
|
||||
|
||||
const getSortQuery: () => any = () => {
|
||||
if (sort === 'usage-more') return { visits: -1 }
|
||||
if (sort === 'usage-less') return { visits: 1 }
|
||||
if (sort === 'newer') return { created_at: -1 }
|
||||
if (sort === 'older') return { created_at: 1 }
|
||||
return { created_at: -1 }
|
||||
}
|
||||
|
||||
|
||||
const count = await UserModel.countDocuments(matchQuery);
|
||||
|
||||
let users: any[] = [];
|
||||
|
||||
|
||||
const users = await UserModel.aggregate([
|
||||
{
|
||||
$match: matchQuery
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "projects",
|
||||
localField: "_id",
|
||||
foreignField: "owner",
|
||||
as: "projects"
|
||||
if (!search || search === '') {
|
||||
users = await UserModel.aggregate([
|
||||
{
|
||||
$match: {
|
||||
created_at: {
|
||||
$gte: new Date(from as string),
|
||||
$lte: new Date(to as string),
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "premiums",
|
||||
localField: "_id",
|
||||
foreignField: "user_id",
|
||||
as: "premiums"
|
||||
}
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "user_limits",
|
||||
localField: "_id",
|
||||
foreignField: "user_id",
|
||||
as: "limits"
|
||||
}
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "projects",
|
||||
let: {
|
||||
userId: "$_id"
|
||||
},
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: {
|
||||
$eq: ["$$userId", "$owner"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "project_counts",
|
||||
localField: "_id",
|
||||
foreignField: "project_id",
|
||||
as: "counts"
|
||||
}
|
||||
}
|
||||
],
|
||||
as: "projects"
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
email: "$email",
|
||||
created_at: "$created_at",
|
||||
premium_type: {
|
||||
$arrayElemAt: [
|
||||
"$premiums.premium_type",
|
||||
0
|
||||
]
|
||||
},
|
||||
limit: {
|
||||
$arrayElemAt: ["$limits.limit", 0]
|
||||
},
|
||||
visits: {
|
||||
$arrayElemAt: ["$limits.visits", 0]
|
||||
},
|
||||
events: {
|
||||
$arrayElemAt: ["$limits.events", 0]
|
||||
},
|
||||
projects: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
$sort: getSortQuery()
|
||||
},
|
||||
{
|
||||
$skip: skipValue
|
||||
},
|
||||
{
|
||||
$limit: limitValue
|
||||
}
|
||||
},
|
||||
{ $sort: JSON.parse(sortQuery as string) },
|
||||
{ $skip: pageNumber * limitNumber },
|
||||
{ $limit: limitNumber }
|
||||
]);
|
||||
])
|
||||
} else {
|
||||
|
||||
return { count, users: users as TAdminUser[] }
|
||||
const matchingProjects = await ProjectModel.find({
|
||||
name: { $regex: new RegExp(search as string) }
|
||||
}, { owner: 1 });
|
||||
|
||||
|
||||
|
||||
users = await UserModel.aggregate([
|
||||
{
|
||||
$match: {
|
||||
$or: [
|
||||
{ _id: { $in: matchingProjects.map(e => e.owner) } },
|
||||
{ name: { $regex: new RegExp(search as string) } },
|
||||
{ email: { $regex: new RegExp(search as string) } },
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "premiums",
|
||||
localField: "_id",
|
||||
foreignField: "user_id",
|
||||
as: "premiums"
|
||||
}
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "user_limits",
|
||||
localField: "_id",
|
||||
foreignField: "user_id",
|
||||
as: "limits"
|
||||
}
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "projects",
|
||||
let: {
|
||||
userId: "$_id"
|
||||
},
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: {
|
||||
$eq: ["$$userId", "$owner"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "project_counts",
|
||||
localField: "_id",
|
||||
foreignField: "project_id",
|
||||
as: "counts"
|
||||
}
|
||||
}
|
||||
],
|
||||
as: "projects"
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
email: "$email",
|
||||
created_at: "$created_at",
|
||||
premium_type: {
|
||||
$arrayElemAt: [
|
||||
"$premiums.premium_type",
|
||||
0
|
||||
]
|
||||
},
|
||||
limit: {
|
||||
$arrayElemAt: ["$limits.limit", 0]
|
||||
},
|
||||
visits: {
|
||||
$arrayElemAt: ["$limits.visits", 0]
|
||||
},
|
||||
events: {
|
||||
$arrayElemAt: ["$limits.events", 0]
|
||||
},
|
||||
projects: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
$sort: getSortQuery()
|
||||
},
|
||||
{
|
||||
$skip: skipValue
|
||||
},
|
||||
{
|
||||
$limit: limitValue
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
|
||||
const count = await UserModel.countDocuments({
|
||||
created_at: {
|
||||
$gte: new Date(from as string),
|
||||
$lte: new Date(to as string),
|
||||
}
|
||||
});
|
||||
|
||||
return { users, count } as { users: TAdminUser[], count: number };
|
||||
|
||||
});
|
||||
@@ -1,97 +0,0 @@
|
||||
import { ProjectModel, TProject } from "@schema/project/ProjectSchema";
|
||||
import { TUser, UserModel } from "@schema/UserSchema";
|
||||
import { TProjectLimit } from "~/shared/schema/project/ProjectsLimits";
|
||||
|
||||
export type TAdminUserProjectInfo = TUser & {
|
||||
projects: (TProject & {
|
||||
limits: TProjectLimit[],
|
||||
visits: number,
|
||||
events: number,
|
||||
sessions: number
|
||||
})[],
|
||||
}
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
const userData = getRequestUser(event);
|
||||
if (!userData?.logged) return;
|
||||
if (!userData.user.roles.includes('ADMIN')) return;
|
||||
|
||||
const { page, limit, sortQuery } = getQuery(event);
|
||||
|
||||
const pageNumber = parseInt(page as string);
|
||||
const limitNumber = parseInt(limit as string);
|
||||
|
||||
const users = await UserModel.aggregate([
|
||||
{
|
||||
$lookup: {
|
||||
from: "projects",
|
||||
localField: "_id",
|
||||
foreignField: "owner",
|
||||
pipeline: [
|
||||
{
|
||||
$lookup: {
|
||||
from: "project_limits",
|
||||
localField: "_id",
|
||||
foreignField: "project_id",
|
||||
as: "limits"
|
||||
}
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "visits",
|
||||
localField: "_id",
|
||||
foreignField: "project_id",
|
||||
pipeline: [
|
||||
{
|
||||
$count: "total_visits"
|
||||
}
|
||||
],
|
||||
as: "visit_data"
|
||||
}
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "events",
|
||||
localField: "_id",
|
||||
foreignField: "project_id",
|
||||
pipeline: [
|
||||
{
|
||||
$count: "total_events"
|
||||
}
|
||||
],
|
||||
as: "event_data"
|
||||
}
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "sessions",
|
||||
localField: "_id",
|
||||
foreignField: "project_id",
|
||||
pipeline: [
|
||||
{
|
||||
$count: "total_sessions"
|
||||
}
|
||||
],
|
||||
as: "session_data"
|
||||
}
|
||||
},
|
||||
{ $addFields: { visits: { $ifNull: [{ $arrayElemAt: ["$visit_data.total_visits", 0] }, 0] } } },
|
||||
{ $addFields: { events: { $ifNull: [{ $arrayElemAt: ["$event_data.total_events", 0] }, 0] } } },
|
||||
{ $addFields: { sessions: { $ifNull: [{ $arrayElemAt: ["$session_data.total_sessions", 0] }, 0] } }, },
|
||||
{ $unset: "visit_data" },
|
||||
{ $unset: "event_data" },
|
||||
{ $unset: "session_data" }
|
||||
|
||||
],
|
||||
as: "projects"
|
||||
},
|
||||
},
|
||||
{ $sort: JSON.parse(sortQuery as string) },
|
||||
{ $skip: pageNumber * limitNumber },
|
||||
{ $limit: limitNumber }
|
||||
]);
|
||||
|
||||
return users as TAdminUserProjectInfo[];
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user