feat: revenue tracking

* wip

* wip

* wip

* wip

* show revenue better on overview

* align realtime and overview counters

* update revenue docs

* always return device id

* add project settings, improve projects charts,

* fix: comments

* fixes

* fix migration

* ignore sql files

* fix comments
This commit is contained in:
Carl-Gerhard Lindesvärd
2025-11-19 14:27:34 +01:00
committed by GitHub
parent d61cbf6f2c
commit 790801b728
58 changed files with 2191 additions and 23691 deletions

View File

@@ -60,13 +60,18 @@ export const chartRouter = createTRPCRouter({
}),
)
.query(async ({ input: { projectId } }) => {
const chartPromise = chQuery<{ value: number; date: Date }>(
const { timezone } = await getSettingsForProject(projectId);
const chartPromise = chQuery<{
value: number;
date: Date;
revenue: number;
}>(
`SELECT
uniqHLL12(profile_id) as value,
toStartOfDay(created_at) as date
toStartOfDay(created_at) as date,
sum(revenue * sign) as revenue
FROM ${TABLE_NAMES.sessions}
WHERE
sign = 1 AND
project_id = ${sqlstring.escape(projectId)} AND
created_at >= now() - interval '3 month'
GROUP BY date
@@ -74,22 +79,25 @@ export const chartRouter = createTRPCRouter({
WITH FILL FROM toStartOfDay(now() - interval '1 month')
TO toStartOfDay(now())
STEP INTERVAL 1 day
SETTINGS session_timezone = '${timezone}'
`,
);
const metricsPromise = clix(ch)
const metricsPromise = clix(ch, timezone)
.select<{
months_3: number;
months_3_prev: number;
month: number;
day: number;
day_prev: number;
revenue: number;
}>([
'uniqHLL12(if(created_at >= (now() - toIntervalMonth(3)), profile_id, null)) AS months_3',
'uniqHLL12(if(created_at >= (now() - toIntervalMonth(6)) AND created_at < (now() - toIntervalMonth(3)), profile_id, null)) AS months_3_prev',
'uniqHLL12(if(created_at >= (now() - toIntervalMonth(1)), profile_id, null)) AS month',
'uniqHLL12(if(created_at >= (now() - toIntervalDay(1)), profile_id, null)) AS day',
'uniqHLL12(if(created_at >= (now() - toIntervalDay(2)) AND created_at < (now() - toIntervalDay(1)), profile_id, null)) AS day_prev',
'sum(revenue * sign) as revenue',
])
.from(TABLE_NAMES.sessions)
.where('project_id', '=', projectId)
@@ -207,6 +215,7 @@ export const chartRouter = createTRPCRouter({
}
properties.push(
'revenue',
'has_profile',
'path',
'origin',

View File

@@ -103,7 +103,6 @@ export const overviewRouter = createTRPCRouter({
])
.from(TABLE_NAMES.events)
.where('project_id', '=', input.projectId)
.where('name', '=', 'session_start')
.where('created_at', '>=', clix.exp('now() - INTERVAL 30 MINUTE'));
// Get counts per minute for the last 30 minutes
@@ -119,7 +118,6 @@ export const overviewRouter = createTRPCRouter({
])
.from(TABLE_NAMES.events)
.where('project_id', '=', input.projectId)
.where('name', 'IN', ['session_start', 'screen_view'])
.where('created_at', '>=', clix.exp('now() - INTERVAL 30 MINUTE'))
.groupBy(['minute'])
.orderBy('minute', 'ASC')
@@ -138,11 +136,10 @@ export const overviewRouter = createTRPCRouter({
}>([
`${clix.toStartOf('created_at', 'minute')} as minute`,
'referrer_name',
'count(*) as count',
'uniq(session_id) as count',
])
.from(TABLE_NAMES.events)
.where('project_id', '=', input.projectId)
.where('name', '=', 'session_start')
.where('created_at', '>=', clix.exp('now() - INTERVAL 30 MINUTE'))
.where('referrer_name', '!=', '')
.where('referrer_name', 'IS NOT NULL')
@@ -154,11 +151,10 @@ export const overviewRouter = createTRPCRouter({
const referrersQuery = clix(ch, timezone)
.select<{ referrer: string; count: number }>([
'referrer_name as referrer',
'count(*) as count',
'uniq(session_id) as count',
])
.from(TABLE_NAMES.events)
.where('project_id', '=', input.projectId)
.where('name', '=', 'session_start')
.where('created_at', '>=', clix.exp('now() - INTERVAL 30 MINUTE'))
.where('referrer_name', '!=', '')
.where('referrer_name', 'IS NOT NULL')
@@ -234,6 +230,7 @@ export const overviewRouter = createTRPCRouter({
previous?.metrics.avg_session_duration || null,
prev_views_per_session: previous?.metrics.views_per_session || null,
prev_total_sessions: previous?.metrics.total_sessions || null,
prev_total_revenue: previous?.metrics.total_revenue || null,
},
series: current.series.map((item, index) => {
const prev = previous?.series[index];
@@ -246,6 +243,7 @@ export const overviewRouter = createTRPCRouter({
prev_avg_session_duration: prev?.avg_session_duration,
prev_views_per_session: prev?.views_per_session,
prev_total_sessions: prev?.total_sessions,
prev_total_revenue: prev?.total_revenue,
};
}),
};

View File

@@ -84,6 +84,7 @@ export const projectRouter = createTRPCRouter({
input.cors === undefined
? undefined
: input.cors.map((c) => stripTrailingSlash(c)) || [],
allowUnsafeRevenueTracking: input.allowUnsafeRevenueTracking,
},
include: {
clients: {
@@ -123,6 +124,7 @@ export const projectRouter = createTRPCRouter({
domain: input.domain,
cors: input.cors,
crossDomain: false,
allowUnsafeRevenueTracking: false,
filters: [],
clients: {
create: data,

View File

@@ -191,7 +191,7 @@ export const cacheMiddleware = (
key += JSON.stringify(rawInput).replace(/\"/g, "'");
}
const cache = await getRedisCache().getJson(key);
if (cache) {
if (cache && process.env.NODE_ENV === 'production') {
return {
ok: true,
data: cache,