chore: refactor chart access

This commit is contained in:
Carl-Gerhard Lindesvärd
2026-02-05 21:43:59 +00:00
parent 19a48e86c1
commit 21792bfbfa

View File

@@ -60,6 +60,68 @@ function utc(date: string | Date) {
const cacher = cacheMiddleware(60); const cacher = cacheMiddleware(60);
const chartProcedure = publicProcedure.use(
async ({ ctx, next, getRawInput }) => {
const rawInput = (await getRawInput()) as {
projectId: string;
shareId?: string;
id?: string;
};
if (rawInput.shareId) {
// Require reportId when shareId provided
if (!rawInput.id) {
throw new Error('reportId required with shareId');
}
// Validate share access
const shareValidation = await validateShareAccess(
rawInput.shareId,
rawInput.id,
{
cookies: ctx.cookies,
session: ctx.session?.userId
? { userId: ctx.session.userId }
: undefined,
},
);
if (!shareValidation.isValid) {
throw TRPCAccessError('You do not have access to this share');
}
// Fetch report
const report = await getReportById(rawInput.id);
if (!report) {
throw TRPCAccessError('Report not found');
}
return next({
ctx: {
report,
},
});
}
// Regular member access check
if (!ctx.session?.userId) {
throw TRPCAccessError('Authentication required');
}
const access = await getProjectAccess({
projectId: rawInput.projectId,
userId: ctx.session.userId,
});
if (!access) {
throw TRPCAccessError('You do not have access to this project');
}
return next({
ctx: {
report: null,
},
});
},
);
export const chartRouter = createTRPCRouter({ export const chartRouter = createTRPCRouter({
projectCard: protectedProcedure projectCard: protectedProcedure
.use(cacheMiddleware(60 * 5)) .use(cacheMiddleware(60 * 5))
@@ -333,7 +395,8 @@ export const chartRouter = createTRPCRouter({
}; };
}), }),
funnel: publicProcedure funnel: chartProcedure
.use(cacher)
.input( .input(
zReportInput.and( zReportInput.and(
z.object({ z.object({
@@ -343,56 +406,15 @@ export const chartRouter = createTRPCRouter({
), ),
) )
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
let chartInput = input; const chartInput = ctx.report
? {
if (input.shareId) { ...ctx.report,
// Require reportId when shareId provided range: input.range ?? ctx.report.range,
if (!input.id) { startDate: input.startDate ?? ctx.report.startDate,
throw new Error('reportId required with shareId'); endDate: input.endDate ?? ctx.report.endDate,
} interval: input.interval ?? ctx.report.interval,
}
// Validate share access : input;
const shareValidation = await validateShareAccess(
input.shareId,
input.id,
{
cookies: ctx.cookies,
session: ctx.session?.userId
? { userId: ctx.session.userId }
: undefined,
},
);
if (!shareValidation.isValid) {
throw TRPCAccessError('You do not have access to this share');
}
// Fetch report and merge date overrides
const report = await getReportById(input.id);
if (!report) {
throw TRPCAccessError('Report not found');
}
chartInput = {
...report,
// Only allow date overrides
range: input.range ?? report.range,
startDate: input.startDate ?? report.startDate,
endDate: input.endDate ?? report.endDate,
interval: input.interval ?? report.interval,
};
} else {
// Regular member access check
if (!ctx.session?.userId) {
throw TRPCAccessError('Authentication required');
}
const access = await getProjectAccess({
projectId: input.projectId,
userId: ctx.session.userId,
});
if (!access) {
throw TRPCAccessError('You do not have access to this project');
}
}
const { timezone } = await getSettingsForProject(chartInput.projectId); const { timezone } = await getSettingsForProject(chartInput.projectId);
const currentPeriod = getChartStartEndDate(chartInput, timezone); const currentPeriod = getChartStartEndDate(chartInput, timezone);
@@ -415,7 +437,8 @@ export const chartRouter = createTRPCRouter({
}; };
}), }),
conversion: publicProcedure conversion: chartProcedure
.use(cacher)
.input( .input(
zReportInput.and( zReportInput.and(
z.object({ z.object({
@@ -425,56 +448,15 @@ export const chartRouter = createTRPCRouter({
), ),
) )
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
let chartInput = input; const chartInput = ctx.report
? {
if (input.shareId) { ...ctx.report,
// Require reportId when shareId provided range: input.range ?? ctx.report.range,
if (!input.id) { startDate: input.startDate ?? ctx.report.startDate,
throw new Error('reportId required with shareId'); endDate: input.endDate ?? ctx.report.endDate,
} interval: input.interval ?? ctx.report.interval,
}
// Validate share access : input;
const shareValidation = await validateShareAccess(
input.shareId,
input.id,
{
cookies: ctx.cookies,
session: ctx.session?.userId
? { userId: ctx.session.userId }
: undefined,
},
);
if (!shareValidation.isValid) {
throw TRPCAccessError('You do not have access to this share');
}
// Fetch report and merge date overrides
const report = await getReportById(input.id);
if (!report) {
throw TRPCAccessError('Report not found');
}
chartInput = {
...report,
// Only allow date overrides
range: input.range ?? report.range,
startDate: input.startDate ?? report.startDate,
endDate: input.endDate ?? report.endDate,
interval: input.interval ?? report.interval,
};
} else {
// Regular member access check
if (!ctx.session?.userId) {
throw TRPCAccessError('Authentication required');
}
const access = await getProjectAccess({
projectId: input.projectId,
userId: ctx.session.userId,
});
if (!access) {
throw TRPCAccessError('You do not have access to this project');
}
}
const { timezone } = await getSettingsForProject(chartInput.projectId); const { timezone } = await getSettingsForProject(chartInput.projectId);
const currentPeriod = getChartStartEndDate(chartInput, timezone); const currentPeriod = getChartStartEndDate(chartInput, timezone);
@@ -543,8 +525,8 @@ export const chartRouter = createTRPCRouter({
}); });
}), }),
chart: publicProcedure chart: chartProcedure
// .use(cacher) .use(cacher)
.input( .input(
zReportInput.and( zReportInput.and(
z.object({ z.object({
@@ -554,58 +536,23 @@ export const chartRouter = createTRPCRouter({
), ),
) )
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
let chartInput = input;
console.log('input', input); console.log('input', input);
if (input.shareId) { const chartInput = ctx.report
// Require reportId when shareId provided ? {
if (!input.id) { ...ctx.report,
throw new Error('reportId required with shareId'); range: input.range ?? ctx.report.range,
} startDate: input.startDate ?? ctx.report.startDate,
endDate: input.endDate ?? ctx.report.endDate,
// Validate share access interval: input.interval ?? ctx.report.interval,
const shareValidation = await validateShareAccess( }
input.shareId, : input;
input.id,
ctx,
);
if (!shareValidation.isValid) {
throw TRPCAccessError('You do not have access to this share');
}
// Fetch report and merge date overrides
const report = await getReportById(input.id);
if (!report) {
throw TRPCAccessError('Report not found');
}
chartInput = {
...report,
// Only allow date overrides
range: input.range ?? report.range,
startDate: input.startDate ?? report.startDate,
endDate: input.endDate ?? report.endDate,
interval: input.interval ?? report.interval,
};
} else {
// Regular member access check
if (!ctx.session?.userId) {
throw TRPCAccessError('Authentication required');
}
const access = await getProjectAccess({
projectId: input.projectId,
userId: ctx.session.userId,
});
if (!access) {
throw TRPCAccessError('You do not have access to this project');
}
}
return ChartEngine.execute(chartInput); return ChartEngine.execute(chartInput);
}), }),
aggregate: publicProcedure aggregate: chartProcedure
.use(cacher)
.input( .input(
zReportInput.and( zReportInput.and(
z.object({ z.object({
@@ -615,61 +562,21 @@ export const chartRouter = createTRPCRouter({
), ),
) )
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
let chartInput = input; const chartInput = ctx.report
? {
if (input.shareId) { ...ctx.report,
// Require reportId when shareId provided range: input.range ?? ctx.report.range,
if (!input.id) { startDate: input.startDate ?? ctx.report.startDate,
throw new Error('reportId required with shareId'); endDate: input.endDate ?? ctx.report.endDate,
} interval: input.interval ?? ctx.report.interval,
}
// Validate share access : input;
const shareValidation = await validateShareAccess(
input.shareId,
input.id,
{
cookies: ctx.cookies,
session: ctx.session?.userId
? { userId: ctx.session.userId }
: undefined,
},
);
if (!shareValidation.isValid) {
throw TRPCAccessError('You do not have access to this share');
}
// Fetch report and merge date overrides
const report = await getReportById(input.id);
if (!report) {
throw TRPCAccessError('Report not found');
}
chartInput = {
...report,
// Only allow date overrides
range: input.range ?? report.range,
startDate: input.startDate ?? report.startDate,
endDate: input.endDate ?? report.endDate,
interval: input.interval ?? report.interval,
};
} else {
// Regular member access check
if (!ctx.session?.userId) {
throw TRPCAccessError('Authentication required');
}
const access = await getProjectAccess({
projectId: input.projectId,
userId: ctx.session.userId,
});
if (!access) {
throw TRPCAccessError('You do not have access to this project');
}
}
return AggregateChartEngine.execute(chartInput); return AggregateChartEngine.execute(chartInput);
}), }),
cohort: publicProcedure cohort: chartProcedure
.use(cacher)
.input( .input(
z.object({ z.object({
projectId: z.string(), projectId: z.string(),
@@ -685,53 +592,32 @@ export const chartRouter = createTRPCRouter({
}), }),
) )
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
let projectId = input.projectId; const projectId = ctx.report?.projectId ?? input.projectId;
let firstEvent = input.firstEvent; let firstEvent = input.firstEvent;
let secondEvent = input.secondEvent; let secondEvent = input.secondEvent;
let criteria = input.criteria; let criteria = input.criteria;
let dateRange = input.range; const dateRange = ctx.report
let startDate = input.startDate; ? (input.range ?? ctx.report.range)
let endDate = input.endDate; : input.range;
let interval = input.interval; const startDate = ctx.report
? (input.startDate ?? ctx.report.startDate)
: input.startDate;
const endDate = ctx.report
? (input.endDate ?? ctx.report.endDate)
: input.endDate;
const interval = ctx.report
? (input.interval ?? ctx.report.interval)
: input.interval;
if (input.shareId) { // Extract events from report series if shared
// Require reportId when shareId provided if (ctx.report) {
if (!input.id) {
throw new Error('reportId required with shareId');
}
// Validate share access
const shareValidation = await validateShareAccess(
input.shareId,
input.id,
{
cookies: ctx.cookies,
session: ctx.session?.userId
? { userId: ctx.session.userId }
: undefined,
},
);
if (!shareValidation.isValid) {
throw TRPCAccessError('You do not have access to this share');
}
// Fetch report and extract events
const report = await getReportById(input.id);
if (!report) {
throw TRPCAccessError('Report not found');
}
projectId = report.projectId;
const retentionOptions = const retentionOptions =
report.options?.type === 'retention' ? report.options : undefined; ctx.report.options?.type === 'retention'
? ctx.report.options
: undefined;
criteria = retentionOptions?.criteria ?? criteria; criteria = retentionOptions?.criteria ?? criteria;
dateRange = input.range ?? report.range;
startDate = input.startDate ?? report.startDate;
endDate = input.endDate ?? report.endDate;
interval = input.interval ?? report.interval;
// Extract events from report series const eventSeries = onlyReportEvents(ctx.report.series);
const eventSeries = onlyReportEvents(report.series);
const extractedFirstEvent = ( const extractedFirstEvent = (
eventSeries[0]?.filters?.[0]?.value ?? [] eventSeries[0]?.filters?.[0]?.value ?? []
).map(String); ).map(String);
@@ -748,18 +634,6 @@ export const chartRouter = createTRPCRouter({
firstEvent = extractedFirstEvent; firstEvent = extractedFirstEvent;
secondEvent = extractedSecondEvent; secondEvent = extractedSecondEvent;
} else {
// Regular member access check
if (!ctx.session?.userId) {
throw TRPCAccessError('Authentication required');
}
const access = await getProjectAccess({
projectId: input.projectId,
userId: ctx.session.userId,
});
if (!access) {
throw TRPCAccessError('You do not have access to this project');
}
} }
const { timezone } = await getSettingsForProject(projectId); const { timezone } = await getSettingsForProject(projectId);