feat: add new and total users data route
This commit is contained in:
25
apps/openpage-api/app/growth/new-users/route.ts
Normal file
25
apps/openpage-api/app/growth/new-users/route.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import cors from '@/lib/cors';
|
||||||
|
import { getUserMonthlyGrowth } from '@/lib/growth/get-user-monthly-growth';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const monthlyUsers = await getUserMonthlyGrowth();
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(monthlyUsers), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
37
apps/openpage-api/app/growth/route.ts
Normal file
37
apps/openpage-api/app/growth/route.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
import cors from '@/lib/cors';
|
||||||
|
|
||||||
|
const paths = [
|
||||||
|
{ path: '/total-customers', description: 'Total Customers' },
|
||||||
|
{ path: '/total-users', description: 'Total Users' },
|
||||||
|
{ path: '/new-users', description: 'New Users' },
|
||||||
|
{ path: '/completed-documents', description: 'Completed Documents per Month' },
|
||||||
|
{ path: '/total-completed-documents', description: 'Total Completed Documents' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function GET(request: NextRequest) {
|
||||||
|
const url = request.nextUrl.toString();
|
||||||
|
const apis = paths.map(({ path, description }) => {
|
||||||
|
return { path: url + path, description };
|
||||||
|
});
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(apis), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
25
apps/openpage-api/app/growth/total-users/route.ts
Normal file
25
apps/openpage-api/app/growth/total-users/route.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import cors from '@/lib/cors';
|
||||||
|
import { getUserMonthlyGrowth } from '@/lib/growth/get-user-monthly-growth';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const totalUsers = await getUserMonthlyGrowth();
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(totalUsers), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import cors from '@/lib/cors';
|
|||||||
const paths = [
|
const paths = [
|
||||||
{ path: 'github', description: 'GitHub Data' },
|
{ path: 'github', description: 'GitHub Data' },
|
||||||
{ path: 'community', description: 'Community Data' },
|
{ path: 'community', description: 'Community Data' },
|
||||||
|
{ path: 'growth', description: 'Growth Data' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function GET(request: NextRequest) {
|
export function GET(request: NextRequest) {
|
||||||
|
|||||||
38
apps/openpage-api/lib/growth/get-user-monthly-growth.ts
Normal file
38
apps/openpage-api/lib/growth/get-user-monthly-growth.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
|
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||||
|
|
||||||
|
export const getUserMonthlyGrowth = async (type: 'count' | 'cumulative' = 'count') => {
|
||||||
|
const qb = kyselyPrisma.$kysely
|
||||||
|
.selectFrom('User')
|
||||||
|
.select(({ fn }) => [
|
||||||
|
fn<Date>('DATE_TRUNC', [sql.lit('MONTH'), 'User.createdAt']).as('month'),
|
||||||
|
fn.count('id').as('count'),
|
||||||
|
fn
|
||||||
|
.sum(fn.count('id'))
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
||||||
|
.over((ob) => ob.orderBy(fn('DATE_TRUNC', [sql.lit('MONTH'), 'User.createdAt']) as any))
|
||||||
|
.as('cume_count'),
|
||||||
|
])
|
||||||
|
.groupBy('month')
|
||||||
|
.orderBy('month', 'desc')
|
||||||
|
.limit(12);
|
||||||
|
|
||||||
|
const result = await qb.execute();
|
||||||
|
|
||||||
|
const transformedData = {
|
||||||
|
labels: result.map((row) => DateTime.fromJSDate(row.month).toFormat('MMM yyyy')).reverse(),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: type === 'count' ? 'New Users' : 'Total Users',
|
||||||
|
data: result
|
||||||
|
.map((row) => (type === 'count' ? Number(row.count) : Number(row.cume_count)))
|
||||||
|
.reverse(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return transformedData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetUserMonthlyGrowthResult = Awaited<ReturnType<typeof getUserMonthlyGrowth>>;
|
||||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -13,8 +13,10 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@documenso/pdf-sign": "^0.1.0",
|
"@documenso/pdf-sign": "^0.1.0",
|
||||||
|
"@documenso/prisma": "^0.0.0",
|
||||||
"@lingui/core": "^4.11.3",
|
"@lingui/core": "^4.11.3",
|
||||||
"inngest-cli": "^0.29.1",
|
"inngest-cli": "^0.29.1",
|
||||||
|
"luxon": "^3.5.0",
|
||||||
"next-runtime-env": "^3.2.0",
|
"next-runtime-env": "^3.2.0",
|
||||||
"react": "^18"
|
"react": "^18"
|
||||||
},
|
},
|
||||||
@@ -22279,9 +22281,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/luxon": {
|
"node_modules/luxon": {
|
||||||
"version": "3.4.4",
|
"version": "3.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz",
|
||||||
"integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==",
|
"integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,8 +64,10 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@documenso/pdf-sign": "^0.1.0",
|
"@documenso/pdf-sign": "^0.1.0",
|
||||||
|
"@documenso/prisma": "^0.0.0",
|
||||||
"@lingui/core": "^4.11.3",
|
"@lingui/core": "^4.11.3",
|
||||||
"inngest-cli": "^0.29.1",
|
"inngest-cli": "^0.29.1",
|
||||||
|
"luxon": "^3.5.0",
|
||||||
"next-runtime-env": "^3.2.0",
|
"next-runtime-env": "^3.2.0",
|
||||||
"react": "^18"
|
"react": "^18"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user