+
-
-
- Close
-
-
- {{ JSON.stringify(details, null, 3) }}
-
-
-
-
-
-
-
-
- Last day
- Last week
- Last month
- All
-
-
-
-
-
-
-
-
-
-
-
- Users: {{ counts?.users }}
-
-
- Projects: {{ counts?.projects }} ( {{ premiumCount }} premium )
-
-
- Total visits: {{ formatNumberK(totalVisits) }}
-
-
- Active: {{ activeProjects }} |
- Dead: {{ (counts?.projects || 0) - activeProjects }}
-
-
- Total events: {{ formatNumberK(totalEvents) }}
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/dashboard/pages/admin/old.vue b/dashboard/pages/admin/old.vue
new file mode 100644
index 0000000..cf2513a
--- /dev/null
+++ b/dashboard/pages/admin/old.vue
@@ -0,0 +1,287 @@
+
+
+
+
+
+
+
+
+ Close
+
+
+ {{ JSON.stringify(details, null, 3) }}
+
+
+
+
+
+
+
+
+ Last day
+ Last week
+ Last month
+ All
+
+
+
+
+
+
+
+
+
+
+
+ Users: {{ counts?.users }}
+
+
+ Projects: {{ counts?.projects }} ( {{ premiumCount }} premium )
+
+
+ Total visits: {{ formatNumberK(totalVisits) }}
+
+
+ Active: {{ activeProjects }} |
+ Dead: {{ (counts?.projects || 0) - activeProjects }}
+
+
+ Total events: {{ formatNumberK(totalEvents) }}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/server/api/admin/projects.ts b/dashboard/server/api/admin/projects.ts
index e4aa918..c86f6f7 100644
--- a/dashboard/server/api/admin/projects.ts
+++ b/dashboard/server/api/admin/projects.ts
@@ -1,24 +1,34 @@
-import { UserModel } from "@schema/UserSchema";
+import { ProjectModel, TProject } from "@schema/project/ProjectSchema";
+import { TProjectLimit } from "~/shared/schema/project/ProjectsLimits";
-export type AdminProjectsList = {
- _id: string,
- name: string,
- given_name: string,
- created_at: string,
- email: string,
- projects: {
- _id: string,
- owner: string,
- name: string,
- premium: boolean,
- premium_type: number,
- customer_id: string,
- subscription_id: string,
- premium_expire_at: string,
- created_at: string,
- __v: number,
- counts: { _id: string, project_id: string, events: number, visits: number, sessions: number, updated_at?: string }
- }[],
+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
+}
+
+export type TAdminProject = TProject & ExtendedProject;
+
+function addFieldsFromArray(data: { fieldName: string, projectedName: string, arrayName: string }[]) {
+ const content: Record
= {};
+ data.forEach(e => {
+ content[e.projectedName] = {
+ "$ifNull": [{ "$getField": { "field": e.fieldName, "input": { "$arrayElemAt": [`$${e.arrayName}`, 0] } } }, 0]
+ }
+ });
+ return content;
}
export default defineEventHandler(async event => {
@@ -27,58 +37,61 @@ export default defineEventHandler(async event => {
if (!userData?.logged) return;
if (!userData.user.roles.includes('ADMIN')) return;
- const data: AdminProjectsList[] = await UserModel.aggregate([
+ const { page, limit, sortQuery } = getQuery(event);
+
+ const pageNumber = parseInt(page as string);
+ const limitNumber = parseInt(limit as string);
+
+ const projects = await ProjectModel.aggregate([
{
$lookup: {
- from: "projects",
+ from: "project_limits",
localField: "_id",
- foreignField: "owner",
- as: "projects"
- }
- },
- {
- $unwind: {
- path: "$projects",
- preserveNullAndEmptyArrays: true
+ foreignField: "project_id",
+ as: "limits"
}
},
{
$lookup: {
from: "project_counts",
- localField: "projects._id",
+ localField: "_id",
foreignField: "project_id",
- as: "projects.counts"
+ as: "counts"
}
},
+ {
+ $addFields: addFieldsFromArray([
+ { arrayName: 'counts', fieldName: 'visits', projectedName: 'visits' },
+ { arrayName: 'counts', fieldName: 'events', projectedName: 'events' },
+ { arrayName: 'counts', fieldName: 'session', projectedName: 'session' },
+ ]),
+ },
+ {
+ $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: {
- "projects.counts": {
- $arrayElemAt: ["$projects.counts", 0]
- }
+ limit_total: {
+ $add: [
+ { $ifNull: ["$limit_visits", 0] },
+ { $ifNull: ["$limit_events", 0] }
+ ]
+ },
}
},
- {
- $group: {
- _id: "$_id",
- name: {
- $first: "$name"
- },
- given_name: {
- $first: "$given_name"
- },
- created_at: {
- $first: "$created_at"
- },
- email: {
- $first: "$email"
- },
- projects: {
- $push: "$projects"
- }
- }
- }
+ { $unset: 'counts' },
+ { $unset: 'limits' },
+ { $sort: JSON.parse(sortQuery as string) },
+ { $skip: pageNumber * limitNumber },
+ { $limit: limitNumber }
]);
- return data;
+ return projects as TAdminProject[];
});
\ No newline at end of file
diff --git a/dashboard/server/api/admin/users_projects.ts b/dashboard/server/api/admin/users_projects.ts
new file mode 100644
index 0000000..66208d6
--- /dev/null
+++ b/dashboard/server/api/admin/users_projects.ts
@@ -0,0 +1,97 @@
+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[];
+
+});
\ No newline at end of file
diff --git a/dashboard/server/utils/getRequestData.ts b/dashboard/server/utils/getRequestData.ts
index 5a37463..8476d77 100644
--- a/dashboard/server/utils/getRequestData.ts
+++ b/dashboard/server/utils/getRequestData.ts
@@ -5,6 +5,7 @@ import { ProjectModel, TProject } from "@schema/project/ProjectSchema";
import { Model, Types } from "mongoose";
import { TeamMemberModel } from "@schema/TeamMemberSchema";
import { Slice } from "@services/DateService";
+import { ADMIN_EMAILS } from "~/shared/data/ADMINS";
export function getRequestUser(event: H3Event) {
if (!event.context.auth) return;
@@ -40,6 +41,10 @@ async function hasAccessToProject(user_id: string, project: TProject) {
if (owner === user_id) return [true, 'OWNER'];
const isGuest = await TeamMemberModel.exists({ project_id, user_id });
if (isGuest) return [true, 'GUEST'];
+
+ //TODO: Create table with admins
+ if (user_id === '66520c90f381ec1e9284938b') return [true, 'ADMIN'];
+
return [false, 'NONE'];
}
diff --git a/dashboard/tsconfig.json b/dashboard/tsconfig.json
index 28b66c5..0c32851 100644
--- a/dashboard/tsconfig.json
+++ b/dashboard/tsconfig.json
@@ -1,4 +1,10 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
- "extends": "./.nuxt/tsconfig.json"
+ "extends": "./.nuxt/tsconfig.json",
+ "compilerOptions": {
+ "skipLibCheck": true,
+ },
+ "exclude": [
+ "node_modules"
+ ]
}
\ No newline at end of file
diff --git a/shared_global/data/PREMIUM.ts b/shared_global/data/PREMIUM.ts
index 0ff4b1c..eacdbf6 100644
--- a/shared_global/data/PREMIUM.ts
+++ b/shared_global/data/PREMIUM.ts
@@ -26,7 +26,8 @@ export type PREMIUM_DATA = {
PRICE: string,
PRICE_TEST: string,
ID: number,
- COST: number
+ COST: number,
+ TAG: PREMIUM_TAG
}
export const PREMIUM_PLAN: Record = {
@@ -36,7 +37,8 @@ export const PREMIUM_PLAN: Record = {
AI_MESSAGE_LIMIT: 10,
PRICE: 'price_1POKCMB2lPUiVs9VLe3QjIHl',
PRICE_TEST: 'price_1PNbHYB2lPUiVs9VZP32xglF',
- COST: 0
+ COST: 0,
+ TAG: 'FREE'
},
PLAN_1: {
ID: 1,
@@ -44,7 +46,8 @@ export const PREMIUM_PLAN: Record = {
AI_MESSAGE_LIMIT: 100,
PRICE: 'price_1POKCOB2lPUiVs9VC13s2rQw',
PRICE_TEST: 'price_1PNZjVB2lPUiVs9VrsTbJL04',
- COST: 0
+ COST: 0,
+ TAG: 'PLAN_1'
},
PLAN_2: {
ID: 2,
@@ -52,7 +55,8 @@ export const PREMIUM_PLAN: Record = {
AI_MESSAGE_LIMIT: 5_000,
PRICE: 'price_1POKCKB2lPUiVs9Vol8XOmhW',
PRICE_TEST: 'price_1POK34B2lPUiVs9VIROb0IIV',
- COST: 0
+ COST: 0,
+ TAG: 'PLAN_2'
},
CUSTOM_1: {
ID: 1001,
@@ -60,7 +64,8 @@ export const PREMIUM_PLAN: Record = {
AI_MESSAGE_LIMIT: 100_000,
PRICE: 'price_1POKZyB2lPUiVs9VMAY6jXTV',
PRICE_TEST: '',
- COST: 0
+ COST: 0,
+ TAG: 'CUSTOM_1'
},
INCUBATION: {
ID: 101,
@@ -68,7 +73,8 @@ export const PREMIUM_PLAN: Record = {
AI_MESSAGE_LIMIT: 30,
PRICE: 'price_1PdsyzB2lPUiVs9V4J246Jw0',
PRICE_TEST: '',
- COST: 499
+ COST: 499,
+ TAG: 'INCUBATION'
},
ACCELERATION: {
ID: 102,
@@ -76,7 +82,8 @@ export const PREMIUM_PLAN: Record = {
AI_MESSAGE_LIMIT: 100,
PRICE: 'price_1Pdt5bB2lPUiVs9VhkuCouEt',
PRICE_TEST: '',
- COST: 999
+ COST: 999,
+ TAG: 'ACCELERATION'
},
GROWTH: {
ID: 103,
@@ -84,7 +91,8 @@ export const PREMIUM_PLAN: Record = {
AI_MESSAGE_LIMIT: 3_000,
PRICE: 'price_1PdszrB2lPUiVs9VIdkT3thv',
PRICE_TEST: '',
- COST: 2999
+ COST: 2999,
+ TAG: 'GROWTH'
},
EXPANSION: {
ID: 104,
@@ -92,7 +100,8 @@ export const PREMIUM_PLAN: Record = {
AI_MESSAGE_LIMIT: 5_000,
PRICE: 'price_1Pdt0xB2lPUiVs9V0Rdt80Fe',
PRICE_TEST: '',
- COST: 5999
+ COST: 5999,
+ TAG: 'EXPANSION'
},
SCALING: {
ID: 105,
@@ -100,7 +109,8 @@ export const PREMIUM_PLAN: Record = {
AI_MESSAGE_LIMIT: 10_000,
PRICE: 'price_1Pdt1UB2lPUiVs9VUmxntSwZ',
PRICE_TEST: '',
- COST: 9999
+ COST: 9999,
+ TAG: 'SCALING'
},
UNICORN: {
ID: 106,
@@ -108,7 +118,8 @@ export const PREMIUM_PLAN: Record = {
AI_MESSAGE_LIMIT: 20_000,
PRICE: 'price_1Pdt2LB2lPUiVs9VGBFAIG9G',
PRICE_TEST: '',
- COST: 14999
+ COST: 14999,
+ TAG: 'UNICORN'
},
LIFETIME_GROWTH_ONETIME: {
ID: 2001,
@@ -116,7 +127,8 @@ export const PREMIUM_PLAN: Record = {
AI_MESSAGE_LIMIT: 3_000,
PRICE: 'price_1PvewGB2lPUiVs9VLheJC8s1',
PRICE_TEST: 'price_1Pvf7LB2lPUiVs9VMFNyzpim',
- COST: 239900
+ COST: 239900,
+ TAG: 'LIFETIME_GROWTH_ONETIME'
},
GROWTH_DUMMY: {
ID: 5001,
@@ -124,7 +136,8 @@ export const PREMIUM_PLAN: Record = {
AI_MESSAGE_LIMIT: 3_000,
PRICE: 'price_1PvgoRB2lPUiVs9VC51YBT7J',
PRICE_TEST: 'price_1PvgRTB2lPUiVs9V3kFSNC3G',
- COST: 0
+ COST: 0,
+ TAG: 'GROWTH_DUMMY'
},
APPSUMO_INCUBATION: {
ID: 6001,
@@ -132,7 +145,8 @@ export const PREMIUM_PLAN: Record = {
AI_MESSAGE_LIMIT: 30,
PRICE: 'price_1QIXwbB2lPUiVs9VKSsoksaU',
PRICE_TEST: '',
- COST: 0
+ COST: 0,
+ TAG: 'APPSUMO_INCUBATION'
},
APPSUMO_ACCELERATION: {
ID: 6002,
@@ -140,7 +154,8 @@ export const PREMIUM_PLAN: Record = {
AI_MESSAGE_LIMIT: 100,
PRICE: 'price_1QIXxRB2lPUiVs9VrjaVRoOl',
PRICE_TEST: '',
- COST: 0
+ COST: 0,
+ TAG: 'APPSUMO_ACCELERATION'
},
APPSUMO_GROWTH: {
ID: 6003,
@@ -148,7 +163,8 @@ export const PREMIUM_PLAN: Record = {
AI_MESSAGE_LIMIT: 3_000,
PRICE: 'price_1QIXy8B2lPUiVs9VQBOUPAoE',
PRICE_TEST: '',
- COST: 0
+ COST: 0,
+ TAG: 'APPSUMO_GROWTH'
},
APPSUMO_UNICORN: {
ID: 6006,
@@ -156,7 +172,8 @@ export const PREMIUM_PLAN: Record = {
AI_MESSAGE_LIMIT: 20_000,
PRICE: 'price_1Qls1lB2lPUiVs9VI6ej8hwE',
PRICE_TEST: '',
- COST: 0
+ COST: 0,
+ TAG: 'APPSUMO_UNICORN'
}
}