From 2929b229c4aec3f6bd9405af5e4162b5e002acfa Mon Sep 17 00:00:00 2001 From: Emily Date: Mon, 11 Nov 2024 16:54:02 +0100 Subject: [PATCH] add domain wipe --- dashboard/app.vue | 3 + dashboard/components/CVerticalNavigation.vue | 4 +- .../components/dialog/DeleteDomainData.vue | 73 +++++++++ dashboard/components/settings/Data.vue | 154 ++++++++++++++++++ dashboard/composables/useCustomDialog.ts | 2 +- dashboard/pages/settings.vue | 4 + .../api/settings/delete_domain.delete.ts | 104 ++++++++++++ .../server/api/settings/domain_counts.ts | 79 +++++++++ dashboard/server/api/settings/domains.ts | 18 ++ shared/schema/metrics/EventSchema.ts | 2 +- shared/schema/metrics/SessionSchema.ts | 2 +- 11 files changed, 440 insertions(+), 5 deletions(-) create mode 100644 dashboard/components/dialog/DeleteDomainData.vue create mode 100644 dashboard/components/settings/Data.vue create mode 100644 dashboard/server/api/settings/delete_domain.delete.ts create mode 100644 dashboard/server/api/settings/domain_counts.ts create mode 100644 dashboard/server/api/settings/domains.ts diff --git a/dashboard/app.vue b/dashboard/app.vue index 9a7010f..21ef47b 100644 --- a/dashboard/app.vue +++ b/dashboard/app.vue @@ -67,6 +67,9 @@ const { visible } = usePricingDrawer(); + + + diff --git a/dashboard/components/CVerticalNavigation.vue b/dashboard/components/CVerticalNavigation.vue index 1125813..107a744 100644 --- a/dashboard/components/CVerticalNavigation.vue +++ b/dashboard/components/CVerticalNavigation.vue @@ -140,8 +140,8 @@ const pricingDrawer = usePricingDrawer();
-
-
Create new project
+
+
New Project
diff --git a/dashboard/components/dialog/DeleteDomainData.vue b/dashboard/components/dialog/DeleteDomainData.vue new file mode 100644 index 0000000..82de7ed --- /dev/null +++ b/dashboard/components/dialog/DeleteDomainData.vue @@ -0,0 +1,73 @@ + + + \ No newline at end of file diff --git a/dashboard/components/settings/Data.vue b/dashboard/components/settings/Data.vue new file mode 100644 index 0000000..46d8250 --- /dev/null +++ b/dashboard/components/settings/Data.vue @@ -0,0 +1,154 @@ + + + + diff --git a/dashboard/composables/useCustomDialog.ts b/dashboard/composables/useCustomDialog.ts index 0a2ea20..669165e 100644 --- a/dashboard/composables/useCustomDialog.ts +++ b/dashboard/composables/useCustomDialog.ts @@ -16,7 +16,7 @@ export type CustomDialogOptions = { params?: any, width?: string, height?: string, - closable?: boolean + closable?: boolean, } function openDialogEx(component: Component, options?: CustomDialogOptions) { diff --git a/dashboard/pages/settings.vue b/dashboard/pages/settings.vue index a1d40b9..6fb85cf 100644 --- a/dashboard/pages/settings.vue +++ b/dashboard/pages/settings.vue @@ -5,6 +5,7 @@ definePageMeta({ layout: 'dashboard' }); const items = [ { label: 'General', slot: 'general' }, + { label: 'Data', slot: 'data' }, { label: 'Members', slot: 'members' }, { label: 'Billing', slot: 'billing' }, { label: 'Codes', slot: 'codes' }, @@ -22,6 +23,9 @@ const items = [ + diff --git a/dashboard/server/api/settings/delete_domain.delete.ts b/dashboard/server/api/settings/delete_domain.delete.ts new file mode 100644 index 0000000..7b903e3 --- /dev/null +++ b/dashboard/server/api/settings/delete_domain.delete.ts @@ -0,0 +1,104 @@ + +import { EventModel } from "@schema/metrics/EventSchema"; +import { SessionModel } from "@schema/metrics/SessionSchema"; +import { VisitModel } from "@schema/metrics/VisitSchema"; +import { Types } from "mongoose"; +import { getRequestData } from "~/server/utils/getRequestData"; + +export default defineEventHandler(async event => { + + const data = await getRequestData(event, { requireSchema: false }); + if (!data) return; + + const { project_id } = data; + + const { domain, visits, events, sessions } = await readBody(event); + + taskDeleteDomain(project_id, domain, visits, events, sessions); + + return { ok: true } + +}); + + +async function taskDeleteDomain(project_id: Types.ObjectId, domain: string, deleteVisits: boolean, deleteEvents: boolean, deleteSessions: boolean) { + + console.log('Deletation started'); + + const start = Date.now(); + + const data = await VisitModel.aggregate([ + { + $match: { + project_id, + website: domain + } + }, + { + $group: { + _id: "$session", + count: { $sum: 1 } + } + }, + { + $lookup: { + from: "events", + let: { sessionId: "$_id" }, + pipeline: [ + { $match: { $expr: { $eq: ["$session", "$$sessionId"] } } }, + { $match: { project_id } }, + { $project: { _id: 1 } } + ], + as: "events" + } + }, + { + $lookup: { + from: "sessions", + let: { sessionId: "$_id" }, + pipeline: [ + { $match: { $expr: { $eq: ["$session", "$$sessionId"] } } }, + { $match: { project_id } }, + { $project: { _id: 1 } } + ], + as: "sessions" + } + }, + { + $project: { + _id: 1, + count: 1, + "events._id": 1, + "sessions._id": 1 + } + } + ]) as { _id: string, events: { _id: string }[], sessions: { _id: string }[] }[] + + + if (deleteSessions === true) { + const sessions = data.flatMap(e => e.sessions).map(e => e._id.toString()); + const batchSize = 1000; + for (let i = 0; i < sessions.length; i += batchSize) { + const batch = sessions.slice(i, i + batchSize); + await SessionModel.deleteMany({ _id: { $in: batch } }); + } + } + + if (deleteEvents === true) { + const sessions = data.flatMap(e => e.sessions).map(e => e._id.toString()); + const batchSize = 1000; + for (let i = 0; i < sessions.length; i += batchSize) { + const batch = sessions.slice(i, i + batchSize); + await EventModel.deleteMany({ _id: { $in: batch } }); + } + } + + if (deleteVisits === true) { + await VisitModel.deleteMany({ project_id, website: domain }) + } + + const s = (Date.now() - start) / 1000; + + console.log(`Deletation done in ${s.toFixed(2)} seconds`); + +} \ No newline at end of file diff --git a/dashboard/server/api/settings/domain_counts.ts b/dashboard/server/api/settings/domain_counts.ts new file mode 100644 index 0000000..57b35f2 --- /dev/null +++ b/dashboard/server/api/settings/domain_counts.ts @@ -0,0 +1,79 @@ + +import { VisitModel } from "@schema/metrics/VisitSchema"; +import { getRequestData } from "~/server/utils/getRequestData"; + +export default defineEventHandler(async event => { + + const data = await getRequestData(event, { requireSchema: false }); + if (!data) return; + + const { project_id } = data; + + const { domain } = getQuery(event); + + try { + const resultData = await VisitModel.aggregate([ + { + $match: { + project_id, + website: domain + } + }, + { + $group: { + _id: "$session", + count: { $sum: 1 } + } + }, + { + $lookup: { + from: "events", + let: { sessionId: "$_id" }, + pipeline: [ + { $match: { $expr: { $eq: ["$session", "$$sessionId"] } } }, + { $match: { project_id } }, + { $project: { _id: 1 } } + ], + as: "events" + } + }, + { + $lookup: { + from: "sessions", + let: { sessionId: "$_id" }, + pipeline: [ + { $match: { $expr: { $eq: ["$session", "$$sessionId"] } } }, + { $match: { project_id } }, + { $project: { _id: 1 } } + ], + as: "sessions" + } + }, + { + $project: { + _id: 1, + count: 1, + "events._id": 1, + "sessions._id": 1 + } + } + ], { maxTimeMS: 5000 }) as { _id: string, count: number, events: { _id: string }[], sessions: { _id: string }[] }[] + + + const visits = resultData.reduce((a, e) => a + e.count, 0); + + const sessions = resultData.reduce((a, e) => { + const count = e.sessions.length; + return a + count; + }, 0); + + const events = resultData.reduce((a, e) => { + const count = e.events.length; + return a + count; + }, 0); + + return { visits, sessions, events, error: false, message: '' }; + } catch (ex: any) { + return { error: true, message: ex.message.toString(), visits: -1, sessions: -1, events: -1 } + } +}); \ No newline at end of file diff --git a/dashboard/server/api/settings/domains.ts b/dashboard/server/api/settings/domains.ts new file mode 100644 index 0000000..5c469b6 --- /dev/null +++ b/dashboard/server/api/settings/domains.ts @@ -0,0 +1,18 @@ + +import { VisitModel } from "@schema/metrics/VisitSchema"; +import { getRequestData } from "~/server/utils/getRequestData"; + +export default defineEventHandler(async event => { + + const data = await getRequestData(event, { requireSchema: false }); + if (!data) return; + + const { project_id } = data; + + const result = await VisitModel.aggregate([ + { $match: { project_id } }, + { $group: { _id: "$website", count: { $sum: 1 } } }, + ]); + + return result as { _id: string, count: number }[]; +}); \ No newline at end of file diff --git a/shared/schema/metrics/EventSchema.ts b/shared/schema/metrics/EventSchema.ts index 2f98a59..17ffc2b 100644 --- a/shared/schema/metrics/EventSchema.ts +++ b/shared/schema/metrics/EventSchema.ts @@ -13,7 +13,7 @@ const EventSchema = new Schema({ project_id: { type: Types.ObjectId, index: 1 }, name: { type: String, required: true, index: 1 }, metadata: Schema.Types.Mixed, - session: { type: String }, + session: { type: String, index: 1 }, flowHash: { type: String }, created_at: { type: Date, default: () => Date.now(), index: true }, }) diff --git a/shared/schema/metrics/SessionSchema.ts b/shared/schema/metrics/SessionSchema.ts index 8535e11..152e534 100644 --- a/shared/schema/metrics/SessionSchema.ts +++ b/shared/schema/metrics/SessionSchema.ts @@ -12,7 +12,7 @@ export type TSession = { const SessionSchema = new Schema({ project_id: { type: Types.ObjectId, index: 1 }, - session: { type: String, required: true }, + session: { type: String, required: true, index: 1 }, flowHash: { type: String }, duration: { type: Number, required: true, default: 0 }, updated_at: { type: Date, default: () => Date.now() },