Compare commits

...

5 commits

Author SHA1 Message Date
Francis Cao
86d2672c47 fix shareId error
Some checks failed
Create docker images (cloud) / Build, push, and deploy (push) Has been cancelled
Node.js CI / build (postgresql, 18.18, 10) (push) Has been cancelled
2025-12-18 11:33:29 -08:00
Francis Cao
741c6039e6 refactor 6 month retention. use auth instead of cache:website 2025-12-18 10:35:52 -08:00
Francis Cao
37b6194c5f set 6 month retention for hobby users
Some checks are pending
Node.js CI / build (postgresql, 18.18, 10) (push) Waiting to run
2025-12-17 13:28:34 -08:00
Francis Cao
b75c15dc43 Merge branch 'analytics' of https://github.com/umami-software/umami into dev
Some checks are pending
Node.js CI / build (postgresql, 18.18, 10) (push) Waiting to run
2025-12-17 09:35:51 -08:00
Francis Cao
59a16e719b update bug template
Some checks failed
Node.js CI / build (postgresql, 18.18, 10) (push) Has been cancelled
2025-12-09 10:35:10 -08:00
30 changed files with 61 additions and 44 deletions

View file

@ -24,13 +24,13 @@ body:
render: shell render: shell
- type: input - type: input
attributes: attributes:
label: Which Umami version are you using? (if relevant) label: Which Umami version are you using?
description: 'For example: 2.18.0, 2.15.1, 1.39.0, etc' description: 'For example: 2.18.0, 2.15.1, 1.39.0, etc'
- type: input - type: input
attributes: attributes:
label: Which browser are you using? (if relevant) label: How are you deploying your application?
description: 'For example: Chrome, Edge, Firefox, etc' description: 'For example: Vercel, Railway, Docker, etc'
- type: input - type: input
attributes: attributes:
label: How are you deploying your application? (if relevant) label: Which browser are you using?
description: 'For example: Vercel, Railway, Docker, etc' description: 'For example: Chrome, Edge, Firefox, etc'

View file

@ -17,8 +17,8 @@ export async function POST(request: Request) {
return unauthorized(); return unauthorized();
} }
const parameters = await setWebsiteDate(websiteId, body.parameters); const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters);
const filters = await getQueryFilters(body.filters, websiteId); const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id);
const data = await getAttribution(websiteId, parameters as AttributionParameters, filters); const data = await getAttribution(websiteId, parameters as AttributionParameters, filters);

View file

@ -17,8 +17,8 @@ export async function POST(request: Request) {
return unauthorized(); return unauthorized();
} }
const parameters = await setWebsiteDate(websiteId, body.parameters); const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters);
const filters = await getQueryFilters(body.filters, websiteId); const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id);
const data = await getBreakdown(websiteId, parameters as BreakdownParameters, filters); const data = await getBreakdown(websiteId, parameters as BreakdownParameters, filters);

View file

@ -17,8 +17,8 @@ export async function POST(request: Request) {
return unauthorized(); return unauthorized();
} }
const parameters = await setWebsiteDate(websiteId, body.parameters); const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters);
const filters = await getQueryFilters(body.filters, websiteId); const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id);
const data = await getFunnel(websiteId, parameters as FunnelParameters, filters); const data = await getFunnel(websiteId, parameters as FunnelParameters, filters);

View file

@ -17,8 +17,8 @@ export async function POST(request: Request) {
return unauthorized(); return unauthorized();
} }
const parameters = await setWebsiteDate(websiteId, body.parameters); const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters);
const filters = await getQueryFilters(body.filters, websiteId); const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id);
const data = await getGoal(websiteId, parameters as GoalParameters, filters); const data = await getGoal(websiteId, parameters as GoalParameters, filters);

View file

@ -17,8 +17,8 @@ export async function POST(request: Request) {
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(body.filters, websiteId); const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id);
const parameters = await setWebsiteDate(websiteId, body.parameters); const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters);
const data = await getRetention(websiteId, parameters as RetentionParameters, filters); const data = await getRetention(websiteId, parameters as RetentionParameters, filters);

View file

@ -17,8 +17,8 @@ export async function POST(request: Request) {
return unauthorized(); return unauthorized();
} }
const parameters = await setWebsiteDate(websiteId, body.parameters); const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters);
const filters = await getQueryFilters(body.filters, websiteId); const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id);
const data = await getRevenue(websiteId, parameters as RevenuParameters, filters); const data = await getRevenue(websiteId, parameters as RevenuParameters, filters);

View file

@ -18,8 +18,8 @@ export async function POST(request: Request) {
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(body.filters, websiteId); const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id);
const parameters = await setWebsiteDate(websiteId, body.parameters); const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters);
const data = { const data = {
utm_source: [], utm_source: [],

View file

@ -27,7 +27,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
const data = await getEventDataEvents(websiteId, { const data = await getEventDataEvents(websiteId, {
...filters, ...filters,

View file

@ -27,7 +27,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
const data = await getEventDataFields(websiteId, filters); const data = await getEventDataFields(websiteId, filters);

View file

@ -27,7 +27,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
const data = await getEventDataProperties(websiteId, filters); const data = await getEventDataProperties(websiteId, filters);

View file

@ -27,7 +27,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
const data = await getEventDataStats(websiteId, filters); const data = await getEventDataStats(websiteId, filters);

View file

@ -30,7 +30,7 @@ export async function GET(
} }
const { propertyName } = query; const { propertyName } = query;
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
const data = await getEventDataValues(websiteId, { const data = await getEventDataValues(websiteId, {
...filters, ...filters,

View file

@ -29,7 +29,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
const data = await getWebsiteEvents(websiteId, filters); const data = await getWebsiteEvents(websiteId, filters);

View file

@ -29,7 +29,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
const data = await getEventStats(websiteId, filters); const data = await getEventStats(websiteId, filters);

View file

@ -28,7 +28,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
const [events, pages, referrers, browsers, os, devices, countries] = await Promise.all([ const [events, pages, referrers, browsers, os, devices, countries] = await Promise.all([
getEventMetrics(websiteId, { type: 'event' }, filters), getEventMetrics(websiteId, { type: 'event' }, filters),

View file

@ -37,7 +37,7 @@ export async function GET(
} }
const { type, limit, offset, search } = query; const { type, limit, offset, search } = query;
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
if (search) { if (search) {
filters[type] = `c.${search}`; filters[type] = `c.${search}`;

View file

@ -37,7 +37,7 @@ export async function GET(
} }
const { type, limit, offset, search } = query; const { type, limit, offset, search } = query;
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
if (search) { if (search) {
filters[type] = `c.${search}`; filters[type] = `c.${search}`;

View file

@ -27,7 +27,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
const [pageviews, sessions] = await Promise.all([ const [pageviews, sessions] = await Promise.all([
getPageviewStats(websiteId, filters), getPageviewStats(websiteId, filters),

View file

@ -27,7 +27,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
const data = await getSessionDataProperties(websiteId, filters); const data = await getSessionDataProperties(websiteId, filters);

View file

@ -29,7 +29,7 @@ export async function GET(
} }
const { propertyName } = query; const { propertyName } = query;
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
const data = await getSessionDataValues(websiteId, { const data = await getSessionDataValues(websiteId, {
...filters, ...filters,

View file

@ -25,7 +25,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
const data = await getSessionActivity(websiteId, sessionId, filters); const data = await getSessionActivity(websiteId, sessionId, filters);

View file

@ -28,7 +28,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
const data = await getWebsiteSessions(websiteId, filters); const data = await getWebsiteSessions(websiteId, filters);

View file

@ -27,7 +27,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
const metrics = await getWebsiteSessionStats(websiteId, filters); const metrics = await getWebsiteSessionStats(websiteId, filters);

View file

@ -28,7 +28,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
const data = await getWeeklyTraffic(websiteId, filters); const data = await getWeeklyTraffic(websiteId, filters);

View file

@ -27,7 +27,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
const data = await getWebsiteStats(websiteId, filters); const data = await getWebsiteStats(websiteId, filters);

View file

@ -42,7 +42,7 @@ export async function GET(
value: segment.name, value: segment.name,
})); }));
} else { } else {
const filters = await getQueryFilters(query, websiteId); const filters = await getQueryFilters(query, websiteId, auth.user?.id);
values = await getValues(websiteId, FILTER_COLUMNS[type], filters); values = await getValues(websiteId, FILTER_COLUMNS[type], filters);
} }

View file

@ -1,6 +1,6 @@
import { z } from 'zod'; import { z } from 'zod';
import { uuid } from '@/lib/crypto'; import { uuid } from '@/lib/crypto';
import redis from '@/lib/redis'; import { fetchAccount } from '@/lib/load';
import { getQueryFilters, parseRequest } from '@/lib/request'; import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response'; import { json, unauthorized } from '@/lib/response';
import { pagingParams, searchParams } from '@/lib/schema'; import { pagingParams, searchParams } from '@/lib/schema';
@ -52,7 +52,7 @@ export async function POST(request: Request) {
const { id, name, domain, shareId, teamId } = body; const { id, name, domain, shareId, teamId } = body;
if (process.env.CLOUD_MODE && !teamId) { if (process.env.CLOUD_MODE && !teamId) {
const account = await redis.client.get(`account:${auth.user.id}`); const account = await fetchAccount(auth.user.id);
if (!account?.hasSubscription) { if (!account?.hasSubscription) {
const count = await getWebsiteCount(auth.user.id); const count = await getWebsiteCount(auth.user.id);

View file

@ -38,3 +38,9 @@ export async function fetchSession(websiteId: string, sessionId: string): Promis
return session; return session;
} }
export async function fetchAccount(userId: string) {
const account = await redis.client.get(`account:${userId}`);
return account;
}

View file

@ -1,8 +1,9 @@
import { startOfMonth, subMonths } from 'date-fns';
import { z } from 'zod'; import { z } from 'zod';
import { checkAuth } from '@/lib/auth'; import { checkAuth } from '@/lib/auth';
import { DEFAULT_PAGE_SIZE, FILTER_COLUMNS } from '@/lib/constants'; import { DEFAULT_PAGE_SIZE, FILTER_COLUMNS } from '@/lib/constants';
import { getAllowedUnits, getMinimumUnit, maxDate, parseDateRange } from '@/lib/date'; import { getAllowedUnits, getMinimumUnit, maxDate, parseDateRange } from '@/lib/date';
import { fetchWebsite } from '@/lib/load'; import { fetchAccount, fetchWebsite } from '@/lib/load';
import { filtersArrayToObject } from '@/lib/params'; import { filtersArrayToObject } from '@/lib/params';
import { badRequest, unauthorized } from '@/lib/response'; import { badRequest, unauthorized } from '@/lib/response';
import type { QueryFilters } from '@/lib/types'; import type { QueryFilters } from '@/lib/types';
@ -16,7 +17,7 @@ export async function parseRequest(
const url = new URL(request.url); const url = new URL(request.url);
let query = Object.fromEntries(url.searchParams); let query = Object.fromEntries(url.searchParams);
let body = await getJsonBody(request); let body = await getJsonBody(request);
let error: () => undefined | undefined; let error: () => undefined | undefined | Response;
let auth = null; let auth = null;
if (schema) { if (schema) {
@ -80,8 +81,17 @@ export function getRequestFilters(query: Record<string, any>) {
return result; return result;
} }
export async function setWebsiteDate(websiteId: string, data: Record<string, any>) { export async function setWebsiteDate(websiteId: string, userId: string, data: Record<string, any>) {
const website = await fetchWebsite(websiteId); const website = await fetchWebsite(websiteId);
const cloudMode = !!process.env.CLOUD_MODE;
if (cloudMode && !website.teamId) {
const account = await fetchAccount(userId);
if (!account?.hasSubscription) {
data.startDate = maxDate(data.startDate, startOfMonth(subMonths(new Date(), 6)));
}
}
if (website?.resetAt) { if (website?.resetAt) {
data.startDate = maxDate(data.startDate, new Date(website?.resetAt)); data.startDate = maxDate(data.startDate, new Date(website?.resetAt));
@ -93,12 +103,13 @@ export async function setWebsiteDate(websiteId: string, data: Record<string, any
export async function getQueryFilters( export async function getQueryFilters(
params: Record<string, any>, params: Record<string, any>,
websiteId?: string, websiteId?: string,
userId?: string,
): Promise<QueryFilters> { ): Promise<QueryFilters> {
const dateRange = getRequestDateRange(params); const dateRange = getRequestDateRange(params);
const filters = getRequestFilters(params); const filters = getRequestFilters(params);
if (websiteId) { if (websiteId) {
await setWebsiteDate(websiteId, dateRange); await setWebsiteDate(websiteId, userId, dateRange);
if (params.segment) { if (params.segment) {
const segmentParams = (await getWebsiteSegment(websiteId, params.segment)) const segmentParams = (await getWebsiteSegment(websiteId, params.segment))