new selfhosted version

This commit is contained in:
antonio
2025-11-28 14:11:51 +01:00
parent afda29997d
commit 951860f67e
1046 changed files with 72586 additions and 574750 deletions

View File

@@ -4,22 +4,22 @@ import { Redis } from "~/server/services/CacheService";
export default defineEventHandler(async event => {
const data = await getRequestData(event, ['RANGE', 'DOMAIN'], ['WEB']);
if (!data) return;
const { pid, from, to, project_id, limit, domain } = data;
const ctx = await getRequestContext(event, 'pid', 'domain', 'range', 'limit', 'permission:webAnalytics','flag:allowShare');
const { pid, project_id, domain, from, to, limit } = ctx;
const cacheKey = `browsers:${pid}:${limit}:${from}:${to}:${domain}`;
const cacheExp = 60;
return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
return await Redis.useCache(cacheKey, cacheExp, async () => {
const websiteMatch = domain ? { website: domain } : {};
const result = await VisitModel.aggregate([
{
$match: {
project_id,
created_at: { $gte: new Date(from), $lte: new Date(to) },
website: domain
...websiteMatch
}
},
{ $group: { _id: "$browser", count: { $sum: 1, } } },

View File

@@ -0,0 +1,33 @@
import { VisitModel } from "@schema/metrics/VisitSchema";
import { Redis } from "~/server/services/CacheService";
export default defineEventHandler(async event => {
const ctx = await getRequestContext(event, 'pid', 'domain', 'range', 'limit', 'permission:webAnalytics','flag:allowShare');
const { pid, project_id, domain, from, to, limit } = ctx;
const cacheKey = `cities:${pid}:${limit}:${from}:${to}:${domain}`;
const cacheExp = 60;
return await Redis.useCache(cacheKey, cacheExp, async () => {
const websiteMatch = domain ? { website: domain } : {};
const result = await VisitModel.aggregate([
{
$match: {
project_id,
created_at: { $gte: new Date(from), $lte: new Date(to) },
...websiteMatch
}
},
{ $group: { _id: { city: "$city", region: "$region", country: "$country" }, count: { $sum: 1, } } },
{ $sort: { count: -1 } },
{ $limit: limit }
]);
return result as { _id: string, count: number }[];
});
});

View File

@@ -0,0 +1,33 @@
import { VisitModel } from "@schema/metrics/VisitSchema";
import { Redis } from "~/server/services/CacheService";
export default defineEventHandler(async event => {
const ctx = await getRequestContext(event, 'pid', 'domain', 'range', 'limit', 'permission:webAnalytics','flag:allowShare');
const { pid, project_id, domain, from, to, limit } = ctx;
const cacheKey = `continents:${pid}:${limit}:${from}:${to}:${domain}`;
const cacheExp = 60;
return await Redis.useCache(cacheKey, cacheExp, async () => {
const websiteMatch = domain ? { website: domain } : {};
const result = await VisitModel.aggregate([
{
$match: {
project_id,
created_at: { $gte: new Date(from), $lte: new Date(to) },
...websiteMatch
}
},
{ $group: { _id: "$continent", count: { $sum: 1, } } },
{ $sort: { count: -1 } },
{ $limit: limit }
]);
return result as { _id: string, count: number }[];
});
});

View File

@@ -1,32 +0,0 @@
import { Redis } from "~/server/services/CacheService";
export default defineEventHandler(async event => {
const data = await getRequestData(event, ['DOMAIN', 'RANGE', 'SCHEMA'], ['WEB']);
if (!data) return;
const { schemaName, pid, from, to, model, project_id, domain } = data;
const cacheKey = `count:${schemaName}:${pid}:${from}:${to}:${domain}`;
const cacheExp = 20;
return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
const result = await model.aggregate([
{
$match: {
project_id,
created_at: { $gte: new Date(from), $lte: new Date(to) },
website: domain
}
},
{ $count: 'count' },
]);
return result;
});
});

View File

@@ -3,23 +3,22 @@ import { VisitModel } from "@schema/metrics/VisitSchema";
import { Redis } from "~/server/services/CacheService";
export default defineEventHandler(async event => {
const data = await getRequestData(event, ['RANGE', 'DOMAIN'], ['WEB']);
if (!data) return;
const { pid, from, to, project_id, limit, domain } = data;
const ctx = await getRequestContext(event, 'pid', 'domain', 'range', 'limit', 'permission:webAnalytics', 'flag:allowShare');
const { pid, project_id, domain, from, to, limit } = ctx;
const cacheKey = `countries:${pid}:${limit}:${from}:${to}:${domain}`;
const cacheExp = 60;
return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
return await Redis.useCache(cacheKey, cacheExp, async () => {
const websiteMatch = domain ? { website: domain } : {};
const result = await VisitModel.aggregate([
{
$match: {
project_id,
created_at: { $gte: new Date(from), $lte: new Date(to) },
website: domain
...websiteMatch
}
},
{ $group: { _id: "$country", count: { $sum: 1, } } },

View File

@@ -4,22 +4,22 @@ import { Redis } from "~/server/services/CacheService";
export default defineEventHandler(async event => {
const data = await getRequestData(event, ['RANGE', 'DOMAIN'], ['WEB']);
if (!data) return;
const { pid, from, to, project_id, limit, domain } = data;
const ctx = await getRequestContext(event, 'pid', 'domain', 'range', 'limit', 'permission:webAnalytics', 'flag:allowShare');
const { pid, project_id, domain, from, to, limit } = ctx;
const cacheKey = `devices:${pid}:${limit}:${from}:${to}:${domain}`;
const cacheExp = 60;
return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
return await Redis.useCache(cacheKey, cacheExp, async () => {
const websiteMatch = domain ? { website: domain } : {};
const result = await VisitModel.aggregate([
{
$match: {
project_id,
created_at: { $gte: new Date(from), $lte: new Date(to) },
website: domain
...websiteMatch
}
},
{ $group: { _id: "$device", count: { $sum: 1, } } },

View File

@@ -0,0 +1,36 @@
import { VisitModel } from "@schema/metrics/VisitSchema";
import { Redis } from "~/server/services/CacheService";
export default defineEventHandler(async event => {
const ctx = await getRequestContext(event, 'pid', 'domain', 'range', 'limit', 'permission:webAnalytics', 'flag:allowShare');
const { pid, project_id, domain, from, to, limit } = ctx;
const cacheKey = `entry_pages:${pid}:${limit}:${from}:${to}:${domain}`;
const cacheExp = 60;
return await Redis.useCache(cacheKey, cacheExp, async () => {
const websiteMatch = domain ? { website: domain } : {};
const result = await VisitModel.aggregate([
{
$match: {
project_id,
created_at: { $gte: new Date(from), $lte: new Date(to) },
...websiteMatch
},
},
{ $sort: { session: 1, created_at: 1 } },
{ $group: { _id: "$session", entryPage: { $first: "$page" } } },
{ $group: { _id: "$entryPage", count: { $sum: 1 } } },
{ $sort: { count: -1 } },
{ $limit: limit }
]);
return result as { _id: string, count: number }[];
});
});

View File

@@ -0,0 +1,37 @@
import { EventModel } from "~/shared/schema/metrics/EventSchema";
export default defineEventHandler(async event => {
const { event_name, field_name } = getQuery(event);
if (!event_name || typeof event_name !== 'string') throw createError({ status: 400, message: 'event_name is required' });
if (!field_name || typeof field_name !== 'string') throw createError({ status: 400, message: 'field_name is required' });
const ctx = await getRequestContext(event, 'pid', 'domain', 'range', 'permission:webAnalytics');
const { project_id, domain, from, to } = ctx;
const websiteMatch = domain ? { website: domain } : {};
const aggregation = [
{
$match: {
project_id,
created_at: {
$gte: new Date(from),
$lte: new Date(to)
},
name: event_name,
...websiteMatch,
$expr: { $eq: [{ $type: "$metadata" }, "object"] }
}
},
{ $group: { _id: `$metadata.${field_name}`, count: { $sum: 1 } } },
{ $sort: { count: -1 } },
]
const result = await EventModel.aggregate(aggregation as any);
return result as { _id: string, count: number }[]
});

View File

@@ -0,0 +1,38 @@
import { EventModel } from "~/shared/schema/metrics/EventSchema";
export default defineEventHandler(async event => {
const { event_name } = getQuery(event);
if (!event_name || typeof event_name !== 'string') throw createError({ status: 400, message: 'event_name is required' });
const ctx = await getRequestContext(event, 'pid', 'domain', 'range', 'permission:webAnalytics');
const { project_id, domain, from, to } = ctx;
const websiteMatch = domain ? { website: domain } : {};
const aggregation = [
{
$match: {
project_id,
created_at: {
$gte: new Date(from),
$lte: new Date(to)
},
name: event_name,
...websiteMatch,
$expr: { $eq: [{ $type: "$metadata" }, "object"] }
}
},
{ $project: { metadataKeys: { $objectToArray: "$metadata" } } },
{ $unwind: "$metadataKeys" },
{ $group: { _id: "result", uniqueFields: { $addToSet: "$metadataKeys.k" } } }
]
const events = await EventModel.aggregate(aggregation);
if (!events[0]) return [];
return events[0].uniqueFields as string[];
});

View File

@@ -0,0 +1,85 @@
import { EventModel } from "~/shared/schema/metrics/EventSchema";
export default defineEventHandler(async event => {
const { event_name } = getQuery(event);
if (!event_name || typeof event_name !== 'string') throw createError({ status: 400, message: 'event_name is required' });
const ctx = await getRequestContext(event, 'pid', 'domain', 'range', 'permission:webAnalytics');
const { project_id, domain, from, to } = ctx;
const websiteMatch = domain ? { website: domain } : {};
const aggregation = [
{
$match: {
project_id,
created_at: {
$gte: new Date(from),
$lte: new Date(to)
},
name: event_name,
...websiteMatch
}
},
{
$project: {
_id: "$name",
flowHash: 1
}
},
{
$lookup: {
from: "visits",
let: {
flowHash: "$flowHash"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$project_id", project_id
]
},
{
$eq: ["$flowHash", "$$flowHash"]
}
]
}
}
},
{
$project: {
_id: 0,
referrer: 1
}
}
],
as: "visitInfo"
}
},
{
$unwind: "$visitInfo"
},
{
$group: {
_id: "$visitInfo.referrer",
count: {
$sum: 1
}
}
}
]
const result = await EventModel.aggregate(aggregation);
return result as { _id: string, count: number }[];
});

View File

@@ -4,22 +4,23 @@ import { Redis } from "~/server/services/CacheService";
export default defineEventHandler(async event => {
const data = await getRequestData(event, ['DOMAIN', 'RANGE'], ['EVENTS']);
if (!data) return;
const ctx = await getRequestContext(event, 'pid', 'domain', 'range', 'limit', 'permission:events');
const { pid, from, to, project_id, limit, domain } = data;
const { pid, project_id, domain, from, to, limit } = ctx;
const cacheKey = `events:${pid}:${limit}:${from}:${to}:${domain}`;
const cacheExp = 20;
return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
return await Redis.useCache(cacheKey, cacheExp, async () => {
const websiteMatch = domain ? { website: domain } : {};
const result = await EventModel.aggregate([
{
$match: {
project_id,
created_at: { $gte: new Date(from), $lte: new Date(to) },
website: domain
...websiteMatch
}
},
{ $group: { _id: "$name", count: { $sum: 1, } } },

View File

@@ -1,92 +0,0 @@
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
import { EventModel } from "@schema/metrics/EventSchema";
import { VisitModel } from "@schema/metrics/VisitSchema";
export default defineEventHandler(async event => {
const data = await getRequestDataOld(event, { requireSchema: false });
if (!data) return;
const { project_id, from, to } = data;
const { name: eventName } = getQuery(event);
if (!eventName) return setResponseStatus(event, 400, 'name is required');
const allEvents = await EventModel.find({
project_id: project_id,
name: eventName,
created_at: {
$gte: new Date(from),
$lte: new Date(to),
}
}, { flowHash: 1 });
const allFlowHashes = new Map<string, number>();
allEvents.forEach(e => {
if (!e.flowHash) return;
if (e.flowHash.length == 0) return;
if (allFlowHashes.has(e.flowHash)) {
const count = allFlowHashes.get(e.flowHash) as number;
allFlowHashes.set(e.flowHash, count + 1);
} else {
allFlowHashes.set(e.flowHash, 1);
}
});
const flowHashIds = Array.from(allFlowHashes.keys());
const allReferrers: { referrer: string, flowHash: string }[] = [];
const promises: any[] = [];
while (flowHashIds.length > 0) {
promises.push(new Promise<void>(async resolve => {
const flowHashIdsChunk = flowHashIds.splice(0, 10);
const visits = await VisitModel.find({ project_id, flowHash: { $in: flowHashIdsChunk } }, { referrer: 1, flowHash: 1 });
allReferrers.push(...visits.map(e => { return { referrer: e.referrer, flowHash: e.flowHash } }));
resolve();
}));
}
await Promise.all(promises);
const groupedFlows: Record<string, { referrers: string[] }> = {};
flowHashIds.forEach(flowHash => {
if (!groupedFlows[flowHash]) groupedFlows[flowHash] = { referrers: [] };
const target = groupedFlows[flowHash];
if (!target) return;
const referrers = allReferrers.filter(e => e.flowHash === flowHash).map(e => e.referrer);
for (const referrer of referrers) {
if (target.referrers.includes(referrer)) continue;
target.referrers.push(referrer);
}
});
const grouped: Record<string, number> = {};
for (const referrerPlusHash of allReferrers) {
const referrer = referrerPlusHash.referrer;
if (!grouped[referrer]) grouped[referrer] = 0
grouped[referrer]++;
}
const eventsCount = allEvents.length;
const allGroupedValue = Object.keys(grouped)
.map(key => grouped[key])
.reduce((a, e) => a + e, 0);
for (const key in grouped) {
grouped[key] = 100 / allGroupedValue * grouped[key];
}
return grouped;
});

View File

@@ -1,43 +0,0 @@
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
import { EventModel } from "@schema/metrics/EventSchema";
import { EVENT_METADATA_FIELDS_EXPIRE_TIME, Redis } from "~/server/services/CacheService";
import { PipelineStage } from "mongoose";
export default defineEventHandler(async event => {
const data = await getRequestDataOld(event, { requireSchema: false });
if (!data) return;
const { project_id } = data;
const { name: eventName, field, from, to } = getQuery(event);
if (!from) return setResponseStatus(event, 400, 'from is required');
if (!to) return setResponseStatus(event, 400, 'to is required');
if (!eventName) return setResponseStatus(event, 400, 'name is required');
if (!field) return setResponseStatus(event, 400, 'field is required');
const aggregation: PipelineStage[] = [
{
$match: {
project_id, name: eventName,
created_at: {
$gte: new Date(from.toString()),
$lte: new Date(to.toString()),
}
}
},
{ $group: { _id: `$metadata.${field}`, count: { $sum: 1 } } },
{ $sort: { count: -1 } }
]
const metadataGrouped = await EventModel.aggregate(aggregation);
return metadataGrouped;
});

View File

@@ -1,32 +0,0 @@
import { EventModel } from "@schema/metrics/EventSchema";
import { Redis } from "~/server/services/CacheService";
export default defineEventHandler(async event => {
const data = await getRequestDataOld(event);
if (!data) return;
const { project_id } = data;
const { name: eventName } = getQuery(event);
if (!eventName) return [];
const fields: string[] = await Redis.useCache({
key: `metadata_fields:${project_id}:${eventName}`,
exp: 60
}, async () => {
const eventsWithName = await EventModel.find({ project_id, name: eventName }, { metadata: 1 }, { limit: 10, sort: { created_at: -1 } });
const allMetadata = eventsWithName.map(e => e.metadata);
const allFields = new Set<string>();
for (const metadata of allMetadata) {
const keys = Object.keys(metadata || {});
keys.forEach(key => allFields.add(key));
}
return Array.from(allFields.values());
});
return fields;
});

View File

@@ -1,26 +0,0 @@
import { EventModel } from "@schema/metrics/EventSchema";
import { Redis } from "~/server/services/CacheService";
export default defineEventHandler(async event => {
const data = await getRequestDataOld(event, { requireSchema: false });
if (!data) return;
const { project_id } = data;
const names: string[] = await Redis.useCache({
key: `event_names:${project_id}`,
exp: 60
}, async () => {
const namesAggregation = await EventModel.aggregate([
{ $match: { project_id } },
{ $group: { _id: "$name" } }
]);
return namesAggregation.map(e => e._id);
});
return names;
});

View File

@@ -0,0 +1,36 @@
import { VisitModel } from "@schema/metrics/VisitSchema";
import { Redis } from "~/server/services/CacheService";
export default defineEventHandler(async event => {
const ctx = await getRequestContext(event, 'pid', 'domain', 'range', 'limit', 'permission:webAnalytics','flag:allowShare');
const { pid, project_id, domain, from, to, limit } = ctx;
const cacheKey = `exit_pages:${pid}:${limit}:${from}:${to}:${domain}`;
const cacheExp = 60;
return await Redis.useCache(cacheKey, cacheExp, async () => {
const websiteMatch = domain ? { website: domain } : {};
const result = await VisitModel.aggregate([
{
$match: {
project_id,
created_at: { $gte: new Date(from), $lte: new Date(to) },
...websiteMatch
},
},
{ $sort: { session: 1, created_at: 1 } },
{ $group: { _id: "$session", exitPage: { $last: "$page" } } },
{ $group: { _id: "$exitPage", count: { $sum: 1 } } },
{ $sort: { count: -1 } },
{ $limit: limit }
]);
return result as { _id: string, count: number }[];
});
});

View File

@@ -1,25 +0,0 @@
import { SessionModel } from "@schema/metrics/SessionSchema";
export default defineEventHandler(async event => {
const data = await getRequestDataOld(event, { requireSchema: false });
if (!data) return;
const { project_id } = data;
const online_users = await SessionModel.aggregate([
{
$match: {
project_id,
updated_at: { $gt: new Date(Date.now() - 1000 * 60 * 5) }
}
},
{ $count: 'count' }
]);
if (!online_users[0]) return 0;
return online_users[0].count;
});

View File

@@ -4,22 +4,22 @@ import { Redis } from "~/server/services/CacheService";
export default defineEventHandler(async event => {
const data = await getRequestData(event, ['RANGE', 'DOMAIN'], ['WEB']);
if (!data) return;
const { pid, from, to, project_id, limit, domain } = data;
const ctx = await getRequestContext(event, 'pid', 'domain', 'range', 'limit', 'permission:webAnalytics','flag:allowShare');
const { pid, project_id, domain, from, to, limit } = ctx;
const cacheKey = `oss:${pid}:${limit}:${from}:${to}:${domain}`;
const cacheExp = 60;
return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
return await Redis.useCache(cacheKey, cacheExp, async () => {
const websiteMatch = domain ? { website: domain } : {};
const result = await VisitModel.aggregate([
{
$match: {
project_id,
created_at: { $gte: new Date(from), $lte: new Date(to) },
website: domain
...websiteMatch
}
},
{ $group: { _id: "$os", count: { $sum: 1, } } },

View File

@@ -4,29 +4,29 @@ import { Redis } from "~/server/services/CacheService";
export default defineEventHandler(async event => {
const data = await getRequestData(event, ['RANGE', 'DOMAIN'], ['WEB']);
if (!data) return;
const { pid, from, to, project_id, limit, domain } = data;
const ctx = await getRequestContext(event, 'pid', 'domain', 'range', 'limit', 'permission:webAnalytics', 'flag:allowShare');
const { pid, project_id, domain, from, to, limit } = ctx;
const cacheKey = `pages:${pid}:${limit}:${from}:${to}:${domain}`;
const cacheExp = 60;
return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
return await Redis.useCache(cacheKey, cacheExp, async () => {
const websiteMatch = domain ? { website: domain } : {};
const result = await VisitModel.aggregate([
{
$match: {
project_id,
created_at: { $gte: new Date(from), $lte: new Date(to) },
website: domain
...websiteMatch
},
},
{ $group: { _id: "$page", count: { $sum: 1, } } },
{ $sort: { count: -1 } },
{ $limit: limit }
]);
return result as { _id: string, count: number }[];
});

View File

@@ -0,0 +1,76 @@
import { VisitModel } from "@schema/metrics/VisitSchema";
import { Redis } from "~/server/services/CacheService";
export default defineEventHandler(async event => {
const ctx = await getRequestContext(event, 'pid', 'domain', 'range', 'limit', 'permission:webAnalytics', 'flag:allowShare');
const { pid, project_id, domain, from, to, limit } = ctx;
const cacheKey = `pages_durations:${pid}:${limit}:${from}:${to}:${domain}`;
const cacheExp = 60;
return await Redis.useCache(cacheKey, cacheExp, async () => {
const websiteMatch = domain ? { website: domain } : {};
const result = await VisitModel.aggregate([
{
$match: {
project_id,
created_at: { $gte: new Date(from), $lte: new Date(to) },
...websiteMatch
}
},
{
$setWindowFields: {
partitionBy: "$session",
sortBy: { created_at: 1 },
output: {
nextCreatedAt: { $shift: { output: "$created_at", by: 1 } },
nextPage: { $shift: { output: "$page", by: 1 } }
}
}
},
{
$project: {
page: 1,
created_at: 1,
nextCreatedAt: 1,
durationMs: {
$cond: [
{ $ne: ["$nextCreatedAt", null] },
{ $subtract: ["$nextCreatedAt", "$created_at"] },
null
]
}
}
},
{
$match: {
durationMs: { $ne: null, $gt: 0, $lte: 1000 * 60 * 60 * 1 }
}
},
{
$group: {
_id: "$page",
count: { $sum: 1 },
avgMs: { $avg: "$durationMs" }
}
},
{
$project: {
_id: 1,
count: 1,
avgSeconds: { $round: [{ $divide: ["$avgMs", 1000] }, 0] },
}
},
{ $sort: { count: -1 } },
{ $limit: limit },
]);
return result as { _id: string, count: number }[];
});
});

View File

@@ -1,25 +1,24 @@
import { VisitModel } from "@schema/metrics/VisitSchema";
import { Redis } from "~/server/services/CacheService";
export default defineEventHandler(async event => {
const data = await getRequestData(event, ['OFFSET', 'RANGE', 'DOMAIN'], ['WEB']);
if (!data) return;
const ctx = await getRequestContext(event, 'pid', 'domain', 'range', 'limit', 'permission:webAnalytics', 'flag:allowShare');
const { pid, project_id, domain, from, to, limit } = ctx;
const { pid, from, to, project_id, limit, domain } = data;
const cacheKey = `referrers:${pid}:${limit}:${from}:${to}:${domain}`;
const cacheKey = `data:referrers:${pid}:${limit}:${from}:${to}:${domain}`;
const cacheExp = 60;
return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
return await Redis.useCache(cacheKey, cacheExp, async () => {
const websiteMatch = domain ? { website: domain } : {};
const result = await VisitModel.aggregate([
{
$match: {
project_id,
created_at: { $gte: new Date(from), $lte: new Date(to) },
website: domain,
...websiteMatch
}
},
{ $group: { _id: "$referrer", count: { $sum: 1, } } },
@@ -32,4 +31,6 @@ export default defineEventHandler(async event => {
});
});

View File

@@ -0,0 +1,33 @@
import { VisitModel } from "@schema/metrics/VisitSchema";
import { Redis } from "~/server/services/CacheService";
export default defineEventHandler(async event => {
const ctx = await getRequestContext(event, 'pid', 'domain', 'range', 'limit', 'permission:webAnalytics','flag:allowShare');
const { pid, project_id, domain, from, to, limit } = ctx;
const cacheKey = `regions:${pid}:${limit}:${from}:${to}:${domain}`;
const cacheExp = 60;
return await Redis.useCache(cacheKey, cacheExp, async () => {
const websiteMatch = domain ? { website: domain } : {};
const result = await VisitModel.aggregate([
{
$match: {
project_id,
created_at: { $gte: new Date(from), $lte: new Date(to) },
...websiteMatch
}
},
{ $group: { _id: { region: "$region", country: "$country" }, count: { $sum: 1, } } },
{ $sort: { count: -1 } },
{ $limit: limit }
]);
return result as { _id: string, count: number }[];
});
});

View File

@@ -0,0 +1,44 @@
import { VisitModel } from "@schema/metrics/VisitSchema";
import { Redis } from "~/server/services/CacheService";
export default defineEventHandler(async event => {
const ctx = await getRequestContext(event, 'pid', 'domain', 'range', 'limit', 'permission:webAnalytics', 'flag:allowShare');
const { pid, project_id, domain, from, to, limit } = ctx;
const { utm_type } = getQuery(event);
const cacheKey = `data:utm:${utm_type}:${pid}:${limit}:${from}:${to}:${domain}`;
const cacheExp = 60;
return await Redis.useCache(cacheKey, cacheExp, async () => {
const websiteMatch = domain ? { website: domain } : {};
const result = await VisitModel.aggregate([
{
$match: {
project_id,
created_at: { $gte: new Date(from), $lte: new Date(to) },
...websiteMatch,
[`utm_${utm_type}`]: { $ne: null }
}
},
{
$group: {
_id: `$utm_${utm_type}`,
count: { $sum: 1 }
}
},
{ $sort: { count: -1 } },
{ $limit: limit }
]);
return result.map(item => ({
_id: item._id ?? 'unknown',
count: item.count
}));
});
});