Compare commits

...

2 commits

Author SHA1 Message Date
Francis Cao
889a404650 share table schema + migration
Some checks are pending
Node.js CI / build (push) Waiting to run
2026-01-14 15:33:06 -08:00
Francis Cao
b6013c3ee8 Revert "refactor 6 month retention. use auth instead of cache:website". Fix share page retention bug.
This reverts commit 741c6039e6.
2026-01-14 10:28:48 -08:00
30 changed files with 97 additions and 39 deletions

View file

@ -0,0 +1,41 @@
-- CreateTable
CREATE TABLE "share" (
"share_id" UUID NOT NULL,
"entity_id" UUID NOT NULL,
"share_type" INTEGER NOT NULL,
"share_code" VARCHAR(50),
"parameters" JSONB NOT NULL,
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(6),
CONSTRAINT "share_pkey" PRIMARY KEY ("share_id")
);
-- CreateIndex
CREATE UNIQUE INDEX "share_share_id_key" ON "share"("share_id");
-- CreateIndex
CREATE UNIQUE INDEX "share_share_code_key" ON "share"("share_code");
-- CreateIndex
CREATE INDEX "share_entity_id_idx" ON "share"("entity_id");
-- MigrateData
INSERT INTO "share" (share_id, entity_id, share_type, share_code, parameters, created_at)
SELECT gen_random_uuid(),
website_id,
1,
share_id,
'{}'::jsonb,
now()
FROM "website"
WHERE share_id IS NOT NULL;
-- DropIndex
DROP INDEX "website_share_id_idx";
-- DropIndex
DROP INDEX "website_share_id_key";
-- AlterTable
ALTER TABLE "website" DROP COLUMN "share_id";

View file

@ -67,7 +67,6 @@ model Website {
id String @id @unique @map("website_id") @db.Uuid id String @id @unique @map("website_id") @db.Uuid
name String @db.VarChar(100) name String @db.VarChar(100)
domain String? @db.VarChar(500) domain String? @db.VarChar(500)
shareId String? @unique @map("share_id") @db.VarChar(50)
resetAt DateTime? @map("reset_at") @db.Timestamptz(6) resetAt DateTime? @map("reset_at") @db.Timestamptz(6)
userId String? @map("user_id") @db.Uuid userId String? @map("user_id") @db.Uuid
teamId String? @map("team_id") @db.Uuid teamId String? @map("team_id") @db.Uuid
@ -88,7 +87,6 @@ model Website {
@@index([userId]) @@index([userId])
@@index([teamId]) @@index([teamId])
@@index([createdAt]) @@index([createdAt])
@@index([shareId])
@@index([createdBy]) @@index([createdBy])
@@map("website") @@map("website")
} }
@ -316,3 +314,16 @@ model Pixel {
@@index([createdAt]) @@index([createdAt])
@@map("pixel") @@map("pixel")
} }
model Share {
id String @id() @unique() @map("share_id") @db.Uuid
entityId String @map("entity_id") @db.Uuid
shareType Int @map("share_type") @db.Integer
shareCode String? @unique @map("share_code") @db.VarChar(50)
parameters Json
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6)
@@index([entityId])
@@map("share")
}

View file

@ -17,8 +17,8 @@ export async function POST(request: Request) {
return unauthorized(); return unauthorized();
} }
const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); const parameters = await setWebsiteDate(websiteId, body.parameters);
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); const filters = await getQueryFilters(body.filters, websiteId);
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, auth.user.id, body.parameters); const parameters = await setWebsiteDate(websiteId, body.parameters);
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); const filters = await getQueryFilters(body.filters, websiteId);
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, auth.user.id, body.parameters); const parameters = await setWebsiteDate(websiteId, body.parameters);
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); const filters = await getQueryFilters(body.filters, websiteId);
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, auth.user.id, body.parameters); const parameters = await setWebsiteDate(websiteId, body.parameters);
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); const filters = await getQueryFilters(body.filters, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(body.filters, websiteId);
const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); const parameters = await setWebsiteDate(websiteId, 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, auth.user.id, body.parameters); const parameters = await setWebsiteDate(websiteId, body.parameters);
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); const filters = await getQueryFilters(body.filters, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(body.filters, websiteId);
const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); const parameters = await setWebsiteDate(websiteId, 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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
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, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
values = await getValues(websiteId, FILTER_COLUMNS[type], filters); values = await getValues(websiteId, FILTER_COLUMNS[type], filters);
} }

View file

@ -95,6 +95,13 @@ export const EVENT_TYPE = {
pixelEvent: 4, pixelEvent: 4,
} as const; } as const;
export const ENTITY_TYPE = {
website: 1,
link: 2,
pixel: 3,
board: 4,
} as const;
export const DATA_TYPE = { export const DATA_TYPE = {
string: 1, string: 1,
number: 2, number: 2,

View file

@ -81,12 +81,12 @@ export function getRequestFilters(query: Record<string, any>) {
return result; return result;
} }
export async function setWebsiteDate(websiteId: string, userId: string, data: Record<string, any>) { export async function setWebsiteDate(websiteId: string, data: Record<string, any>) {
const website = await fetchWebsite(websiteId); const website = await fetchWebsite(websiteId);
const cloudMode = !!process.env.CLOUD_MODE; const cloudMode = !!process.env.CLOUD_MODE;
if (cloudMode && website && !website.teamId) { if (cloudMode && website && !website.teamId) {
const account = await fetchAccount(userId); const account = await fetchAccount(website.userId);
if (!account?.hasSubscription) { if (!account?.hasSubscription) {
data.startDate = maxDate(data.startDate, startOfMonth(subMonths(new Date(), 6))); data.startDate = maxDate(data.startDate, startOfMonth(subMonths(new Date(), 6)));
@ -103,13 +103,12 @@ export async function setWebsiteDate(websiteId: string, userId: string, data: Re
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, userId, dateRange); await setWebsiteDate(websiteId, dateRange);
if (params.segment) { if (params.segment) {
const segmentParams = (await getWebsiteSegment(websiteId, params.segment)) const segmentParams = (await getWebsiteSegment(websiteId, params.segment))