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 = [
|
||||
{ path: 'github', description: 'GitHub Data' },
|
||||
{ path: 'community', description: 'Community Data' },
|
||||
{ path: 'growth', description: 'Growth Data' },
|
||||
];
|
||||
|
||||
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": {
|
||||
"@documenso/pdf-sign": "^0.1.0",
|
||||
"@documenso/prisma": "^0.0.0",
|
||||
"@lingui/core": "^4.11.3",
|
||||
"inngest-cli": "^0.29.1",
|
||||
"luxon": "^3.5.0",
|
||||
"next-runtime-env": "^3.2.0",
|
||||
"react": "^18"
|
||||
},
|
||||
@@ -22279,9 +22281,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/luxon": {
|
||||
"version": "3.4.4",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
|
||||
"integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==",
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz",
|
||||
"integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
|
||||
@@ -64,8 +64,10 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@documenso/pdf-sign": "^0.1.0",
|
||||
"@documenso/prisma": "^0.0.0",
|
||||
"@lingui/core": "^4.11.3",
|
||||
"inngest-cli": "^0.29.1",
|
||||
"luxon": "^3.5.0",
|
||||
"next-runtime-env": "^3.2.0",
|
||||
"react": "^18"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user