refactor: refactor website pageview endpoint to trpc
This commit is contained in:
parent
d3df3f2692
commit
dd0ad8c5de
@ -1,8 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { DateUnit } from '../../utils/date';
|
||||
import { queryClient } from '../cache';
|
||||
import { request } from '../request';
|
||||
import { getUserTimezone } from './user';
|
||||
import { AppRouterOutput } from '../trpc';
|
||||
|
||||
export type WebsiteInfo = NonNullable<AppRouterOutput['website']['info']>;
|
||||
@ -19,47 +16,3 @@ export async function deleteWorkspaceWebsite(
|
||||
export function refreshWorkspaceWebsites(workspaceId: string) {
|
||||
queryClient.refetchQueries(['websites', workspaceId]);
|
||||
}
|
||||
|
||||
export async function getWorkspaceWebsitePageview(
|
||||
workspaceId: string,
|
||||
websiteId: string,
|
||||
filter: Record<string, any>
|
||||
) {
|
||||
const { data } = await request.get(
|
||||
`/api/workspace/${workspaceId}/website/${websiteId}/pageviews`,
|
||||
{
|
||||
params: {
|
||||
...filter,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export function useWorkspaceWebsitePageview(
|
||||
workspaceId: string,
|
||||
websiteId: string,
|
||||
startAt: number,
|
||||
endAt: number,
|
||||
unit: DateUnit
|
||||
) {
|
||||
const { data, isLoading, refetch } = useQuery(
|
||||
['websitePageview', { workspaceId, websiteId, startAt, endAt }],
|
||||
() => {
|
||||
return getWorkspaceWebsitePageview(workspaceId, websiteId, {
|
||||
startAt,
|
||||
endAt,
|
||||
unit,
|
||||
timezone: getUserTimezone(),
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
pageviews: data?.pageviews ?? [],
|
||||
sessions: data?.sessions ?? [],
|
||||
isLoading,
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
|
@ -40,17 +40,18 @@ export const WebsiteOverview: React.FC<{
|
||||
);
|
||||
|
||||
const {
|
||||
pageviews,
|
||||
sessions,
|
||||
data,
|
||||
isLoading: isLoadingPageview,
|
||||
refetch: refetchPageview,
|
||||
} = useWorkspaceWebsitePageview(
|
||||
website.workspaceId,
|
||||
website.id,
|
||||
startDate.unix() * 1000,
|
||||
endDate.unix() * 1000,
|
||||
unit
|
||||
);
|
||||
} = trpc.website.pageviews.useQuery({
|
||||
workspaceId: website.workspaceId,
|
||||
websiteId: website.id,
|
||||
startAt: startDate.valueOf(),
|
||||
endAt: endDate.valueOf(),
|
||||
unit,
|
||||
});
|
||||
const pageviews = data?.pageviews ?? [];
|
||||
const sessions = data?.sessions ?? [];
|
||||
|
||||
const {
|
||||
data: stats,
|
||||
|
@ -12,7 +12,12 @@ import {
|
||||
} from '../utils/const';
|
||||
import type { DynamicData } from '../utils/types';
|
||||
import dayjs from 'dayjs';
|
||||
import { QueryFilters, parseFilters } from '../utils/prisma';
|
||||
import {
|
||||
QueryFilters,
|
||||
getDateQuery,
|
||||
getTimestampIntervalQuery,
|
||||
parseFilters,
|
||||
} from '../utils/prisma';
|
||||
|
||||
export interface WebsiteEventPayload {
|
||||
data?: object;
|
||||
@ -344,3 +349,88 @@ export async function getPageviewMetrics(
|
||||
limit 100
|
||||
`;
|
||||
}
|
||||
|
||||
export async function getWorkspaceWebsitePageview(
|
||||
websiteId: string,
|
||||
filters: QueryFilters
|
||||
) {
|
||||
const { timezone = 'utc', unit = 'day' } = filters;
|
||||
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
|
||||
...filters,
|
||||
});
|
||||
|
||||
return prisma.$queryRaw`
|
||||
select
|
||||
${getDateQuery('"WebsiteEvent"."createdAt"', unit, timezone)} x,
|
||||
count(1) y
|
||||
from "WebsiteEvent"
|
||||
${joinSession}
|
||||
where "WebsiteEvent"."websiteId" = ${params.websiteId}
|
||||
and "WebsiteEvent"."createdAt" between ${
|
||||
params.startDate
|
||||
}::timestamptz and ${params.endDate}::timestamptz
|
||||
and "WebsiteEvent"."eventType" = ${EVENT_TYPE.pageView}
|
||||
${filterQuery}
|
||||
group by 1
|
||||
`;
|
||||
}
|
||||
|
||||
export async function getWorkspaceWebsiteSession(
|
||||
websiteId: string,
|
||||
filters: QueryFilters
|
||||
) {
|
||||
const { timezone = 'utc', unit = 'day' } = filters;
|
||||
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
|
||||
...filters,
|
||||
});
|
||||
|
||||
return prisma.$queryRaw`
|
||||
select
|
||||
${getDateQuery('"WebsiteEvent"."createdAt"', unit, timezone)} x,
|
||||
count(distinct "WebsiteEvent"."sessionId") y
|
||||
from "WebsiteEvent"
|
||||
${joinSession}
|
||||
where "WebsiteEvent"."websiteId" = ${params.websiteId}
|
||||
and "WebsiteEvent"."createdAt" between ${
|
||||
params.startDate
|
||||
}::timestamptz and ${params.endDate}::timestamptz
|
||||
and "WebsiteEvent"."eventType" = ${EVENT_TYPE.pageView}
|
||||
${filterQuery}
|
||||
group by 1
|
||||
`;
|
||||
}
|
||||
|
||||
export async function getWorkspaceWebsiteStats(
|
||||
websiteId: string,
|
||||
filters: QueryFilters
|
||||
): Promise<any> {
|
||||
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
|
||||
...filters,
|
||||
});
|
||||
|
||||
return prisma.$queryRaw`
|
||||
select
|
||||
sum(t.c) as "pageviews",
|
||||
count(distinct t."sessionId") as "uniques",
|
||||
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
|
||||
sum(t.time) as "totaltime"
|
||||
from (
|
||||
select
|
||||
"WebsiteEvent"."sessionId",
|
||||
${getDateQuery('"WebsiteEvent"."createdAt"', 'hour')},
|
||||
count(*) as c,
|
||||
${getTimestampIntervalQuery('"WebsiteEvent"."createdAt"')} as "time"
|
||||
from "WebsiteEvent"
|
||||
join "Website"
|
||||
on "WebsiteEvent"."websiteId" = "Website"."id"
|
||||
${joinSession}
|
||||
where "Website"."id" = ${params.websiteId}
|
||||
and "WebsiteEvent"."createdAt" between ${
|
||||
params.startDate
|
||||
}::timestamptz and ${params.endDate}::timestamptz
|
||||
and "eventType" = ${EVENT_TYPE.pageView}
|
||||
${filterQuery}
|
||||
group by 1, 2
|
||||
) as t
|
||||
`;
|
||||
}
|
||||
|
@ -52,22 +52,6 @@ export async function getWorkspaceWebsites(workspaceId: string) {
|
||||
return workspace?.websites ?? [];
|
||||
}
|
||||
|
||||
export async function addWorkspaceWebsite(
|
||||
workspaceId: string,
|
||||
name: string,
|
||||
domain: string
|
||||
) {
|
||||
const website = await prisma.website.create({
|
||||
data: {
|
||||
name,
|
||||
domain,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
return website;
|
||||
}
|
||||
|
||||
export async function deleteWorkspaceWebsite(
|
||||
workspaceId: string,
|
||||
websiteId: string
|
||||
@ -107,88 +91,3 @@ export async function getWorkspaceWebsiteDateRange(websiteId: string) {
|
||||
min: res._min.createdAt,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getWorkspaceWebsitePageview(
|
||||
websiteId: string,
|
||||
filters: QueryFilters
|
||||
) {
|
||||
const { timezone = 'utc', unit = 'day' } = filters;
|
||||
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
|
||||
...filters,
|
||||
});
|
||||
|
||||
return prisma.$queryRaw`
|
||||
select
|
||||
${getDateQuery('"WebsiteEvent"."createdAt"', unit, timezone)} x,
|
||||
count(1) y
|
||||
from "WebsiteEvent"
|
||||
${joinSession}
|
||||
where "WebsiteEvent"."websiteId" = ${params.websiteId}
|
||||
and "WebsiteEvent"."createdAt" between ${
|
||||
params.startDate
|
||||
}::timestamptz and ${params.endDate}::timestamptz
|
||||
and "WebsiteEvent"."eventType" = ${EVENT_TYPE.pageView}
|
||||
${filterQuery}
|
||||
group by 1
|
||||
`;
|
||||
}
|
||||
|
||||
export async function getWorkspaceWebsiteSession(
|
||||
websiteId: string,
|
||||
filters: QueryFilters
|
||||
) {
|
||||
const { timezone = 'utc', unit = 'day' } = filters;
|
||||
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
|
||||
...filters,
|
||||
});
|
||||
|
||||
return prisma.$queryRaw`
|
||||
select
|
||||
${getDateQuery('"WebsiteEvent"."createdAt"', unit, timezone)} x,
|
||||
count(distinct "WebsiteEvent"."sessionId") y
|
||||
from "WebsiteEvent"
|
||||
${joinSession}
|
||||
where "WebsiteEvent"."websiteId" = ${params.websiteId}
|
||||
and "WebsiteEvent"."createdAt" between ${
|
||||
params.startDate
|
||||
}::timestamptz and ${params.endDate}::timestamptz
|
||||
and "WebsiteEvent"."eventType" = ${EVENT_TYPE.pageView}
|
||||
${filterQuery}
|
||||
group by 1
|
||||
`;
|
||||
}
|
||||
|
||||
export async function getWorkspaceWebsiteStats(
|
||||
websiteId: string,
|
||||
filters: QueryFilters
|
||||
): Promise<any> {
|
||||
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
|
||||
...filters,
|
||||
});
|
||||
|
||||
return prisma.$queryRaw`
|
||||
select
|
||||
sum(t.c) as "pageviews",
|
||||
count(distinct t."sessionId") as "uniques",
|
||||
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
|
||||
sum(t.time) as "totaltime"
|
||||
from (
|
||||
select
|
||||
"WebsiteEvent"."sessionId",
|
||||
${getDateQuery('"WebsiteEvent"."createdAt"', 'hour')},
|
||||
count(*) as c,
|
||||
${getTimestampIntervalQuery('"WebsiteEvent"."createdAt"')} as "time"
|
||||
from "WebsiteEvent"
|
||||
join "Website"
|
||||
on "WebsiteEvent"."websiteId" = "Website"."id"
|
||||
${joinSession}
|
||||
where "Website"."id" = ${params.websiteId}
|
||||
and "WebsiteEvent"."createdAt" between ${
|
||||
params.startDate
|
||||
}::timestamptz and ${params.endDate}::timestamptz
|
||||
and "eventType" = ${EVENT_TYPE.pageView}
|
||||
${filterQuery}
|
||||
group by 1, 2
|
||||
) as t
|
||||
`;
|
||||
}
|
||||
|
@ -1,18 +1,8 @@
|
||||
import dayjs from 'dayjs';
|
||||
import { Router } from 'express';
|
||||
import { auth } from '../middleware/auth';
|
||||
import { body, param, query, validate } from '../middleware/validate';
|
||||
import { param, validate } from '../middleware/validate';
|
||||
import { workspacePermission } from '../middleware/workspace';
|
||||
import {
|
||||
addWorkspaceWebsite,
|
||||
deleteWorkspaceWebsite,
|
||||
getWorkspaceWebsitePageview,
|
||||
getWorkspaceWebsites,
|
||||
getWorkspaceWebsiteSession,
|
||||
getWorkspaceWebsiteStats,
|
||||
} from '../model/workspace';
|
||||
import { parseDateRange } from '../utils/common';
|
||||
import { QueryFilters } from '../utils/prisma';
|
||||
import { deleteWorkspaceWebsite } from '../model/workspace';
|
||||
import { ROLES } from '@tianji/shared';
|
||||
|
||||
export const workspaceRouter = Router();
|
||||
@ -31,63 +21,3 @@ workspaceRouter.delete(
|
||||
res.json({ website });
|
||||
}
|
||||
);
|
||||
|
||||
workspaceRouter.get(
|
||||
'/:workspaceId/website/:websiteId/pageviews',
|
||||
validate(
|
||||
param('workspaceId').isString(),
|
||||
param('websiteId').isString(),
|
||||
query('startAt').isNumeric().withMessage('startAt should be number'),
|
||||
query('endAt').isNumeric().withMessage('startAt should be number')
|
||||
),
|
||||
auth(),
|
||||
workspacePermission(),
|
||||
async (req, res) => {
|
||||
const workspaceId = req.params.workspaceId;
|
||||
const websiteId = req.params.websiteId;
|
||||
const {
|
||||
timezone,
|
||||
url,
|
||||
referrer,
|
||||
title,
|
||||
os,
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
region,
|
||||
city,
|
||||
startAt,
|
||||
endAt,
|
||||
} = req.query;
|
||||
|
||||
const { startDate, endDate, unit } = await parseDateRange({
|
||||
websiteId,
|
||||
startAt: Number(startAt),
|
||||
endAt: Number(endAt),
|
||||
unit: String(req.query.unit),
|
||||
});
|
||||
|
||||
const filters = {
|
||||
startDate,
|
||||
endDate,
|
||||
timezone,
|
||||
unit,
|
||||
url,
|
||||
referrer,
|
||||
title,
|
||||
os,
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
region,
|
||||
city,
|
||||
};
|
||||
|
||||
const [pageviews, sessions] = await Promise.all([
|
||||
getWorkspaceWebsitePageview(websiteId, filters as QueryFilters),
|
||||
getWorkspaceWebsiteSession(websiteId, filters as QueryFilters),
|
||||
]);
|
||||
|
||||
res.json({ pageviews, sessions });
|
||||
}
|
||||
);
|
||||
|
@ -5,7 +5,11 @@ import {
|
||||
workspaceProcedure,
|
||||
} from '../trpc';
|
||||
import { z } from 'zod';
|
||||
import { getWebsiteOnlineUserCount } from '../../model/website';
|
||||
import {
|
||||
getWebsiteOnlineUserCount,
|
||||
getWorkspaceWebsitePageview,
|
||||
getWorkspaceWebsiteStats,
|
||||
} from '../../model/website';
|
||||
import { prisma } from '../../model/_client';
|
||||
import {
|
||||
EVENT_COLUMNS,
|
||||
@ -18,10 +22,6 @@ import { getSessionMetrics, getPageviewMetrics } from '../../model/website';
|
||||
import { websiteInfoSchema } from '../../model/_schema';
|
||||
import { OpenApiMeta } from 'trpc-openapi';
|
||||
import { hostnameRegex } from '@tianji/shared';
|
||||
import {
|
||||
addWorkspaceWebsite,
|
||||
getWorkspaceWebsiteStats,
|
||||
} from '../../model/workspace';
|
||||
import {
|
||||
websiteFilterSchema,
|
||||
websiteStatsSchema,
|
||||
@ -241,6 +241,74 @@ export const websiteRouter = router({
|
||||
};
|
||||
});
|
||||
}),
|
||||
pageviews: workspaceProcedure
|
||||
.meta(
|
||||
buildWebsiteOpenapi({
|
||||
method: 'GET',
|
||||
path: '/pageviews',
|
||||
})
|
||||
)
|
||||
.input(
|
||||
z
|
||||
.object({
|
||||
websiteId: z.string(),
|
||||
startAt: z.number(),
|
||||
endAt: z.number(),
|
||||
unit: z.string().optional(),
|
||||
})
|
||||
.merge(websiteFilterSchema.partial())
|
||||
)
|
||||
.output(z.object({ pageviews: z.any(), sessions: z.any() }))
|
||||
.query(async ({ input }) => {
|
||||
const {
|
||||
websiteId,
|
||||
startAt,
|
||||
endAt,
|
||||
timezone,
|
||||
url,
|
||||
referrer,
|
||||
title,
|
||||
os,
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
region,
|
||||
city,
|
||||
} = input;
|
||||
|
||||
const { startDate, endDate, unit } = await parseDateRange({
|
||||
websiteId,
|
||||
startAt: Number(startAt),
|
||||
endAt: Number(endAt),
|
||||
unit: String(input.unit),
|
||||
});
|
||||
|
||||
const filters = {
|
||||
startDate,
|
||||
endDate,
|
||||
timezone,
|
||||
unit,
|
||||
url,
|
||||
referrer,
|
||||
title,
|
||||
os,
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
region,
|
||||
city,
|
||||
};
|
||||
|
||||
const [pageviews, sessions] = await Promise.all([
|
||||
getWorkspaceWebsitePageview(websiteId, filters as QueryFilters),
|
||||
getWorkspaceWebsiteSession(websiteId, filters as QueryFilters),
|
||||
]);
|
||||
|
||||
return {
|
||||
pageviews,
|
||||
sessions,
|
||||
};
|
||||
}),
|
||||
metrics: workspaceProcedure
|
||||
.meta(
|
||||
buildWebsiteOpenapi({
|
||||
@ -379,7 +447,13 @@ export const websiteRouter = router({
|
||||
.mutation(async ({ input }) => {
|
||||
const { workspaceId, name, domain } = input;
|
||||
|
||||
const website = await addWorkspaceWebsite(workspaceId, name, domain);
|
||||
const website = await prisma.website.create({
|
||||
data: {
|
||||
name,
|
||||
domain,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
return website;
|
||||
}),
|
||||
|
Loading…
Reference in New Issue
Block a user