Skip to content

Commit 98c9acb

Browse files
authored
rename session recording to session replay (#1207)
<!-- Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md -->
1 parent 68ab2d3 commit 98c9acb

20 files changed

Lines changed: 230 additions & 208 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,22 @@
11
ALTER TABLE "SessionRecordingChunk" RENAME COLUMN "tabId" TO "sessionReplaySegmentId";
2+
3+
ALTER TABLE "SessionRecording" RENAME TO "SessionReplay";
4+
ALTER TABLE "SessionRecordingChunk" RENAME TO "SessionReplayChunk";
5+
ALTER TABLE "SessionReplayChunk" RENAME COLUMN "sessionRecordingId" TO "sessionReplayId";
6+
7+
-- Rename primary key constraints
8+
ALTER TABLE "SessionReplay" RENAME CONSTRAINT "SessionRecording_pkey" TO "SessionReplay_pkey";
9+
ALTER TABLE "SessionReplayChunk" RENAME CONSTRAINT "SessionRecordingChunk_pkey" TO "SessionReplayChunk_pkey";
10+
11+
-- Rename foreign key constraints
12+
ALTER TABLE "SessionReplay" RENAME CONSTRAINT "SessionRecording_tenancyId_fkey" TO "SessionReplay_tenancyId_fkey";
13+
ALTER TABLE "SessionReplay" RENAME CONSTRAINT "SessionRecording_tenancyId_projectUserId_fkey" TO "SessionReplay_tenancyId_projectUserId_fkey";
14+
ALTER TABLE "SessionReplayChunk" RENAME CONSTRAINT "SessionRecordingChunk_tenancyId_fkey" TO "SessionReplayChunk_tenancyId_fkey";
15+
ALTER TABLE "SessionReplayChunk" RENAME CONSTRAINT "SessionRecordingChunk_tenancyId_sessionRecordingId_fkey" TO "SessionReplayChunk_tenancyId_sessionReplayId_fkey";
16+
17+
-- Rename indexes
18+
ALTER INDEX "SessionRecording_tenancyId_lastEventAt_idx" RENAME TO "SessionReplay_tenancyId_lastEventAt_idx";
19+
ALTER INDEX "SessionRecording_tenancyId_projectUserId_startedAt_idx" RENAME TO "SessionReplay_tenancyId_projectUserId_startedAt_idx";
20+
ALTER INDEX "SessionRecording_tenancyId_refreshTokenId_updatedAt_idx" RENAME TO "SessionReplay_tenancyId_refreshTokenId_updatedAt_idx";
21+
ALTER INDEX "SessionRecordingChunk_tenancyId_sessionRecordingId_batchId_key" RENAME TO "SessionReplayChunk_tenancyId_sessionReplayId_batchId_key";
22+
ALTER INDEX "SessionRecordingChunk_tenancyId_sessionRecordingId_createdA_idx" RENAME TO "SessionReplayChunk_tenancyId_sessionReplayId_createdAt_idx";

apps/backend/prisma/schema.prisma

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ model Tenancy {
6060
organizationId String? @db.Uuid
6161
hasNoOrganization BooleanTrue?
6262
emailOutboxes EmailOutbox[]
63-
sessionRecordings SessionRecording[]
64-
sessionRecordingChunks SessionRecordingChunk[]
63+
sessionReplays SessionReplay[]
64+
sessionReplayChunks SessionReplayChunk[]
6565
6666
@@unique([projectId, branchId, organizationId])
6767
@@unique([projectId, branchId, hasNoOrganization])
@@ -236,7 +236,7 @@ model ProjectUser {
236236
Project Project? @relation(fields: [projectId], references: [id])
237237
projectId String?
238238
userNotificationPreference UserNotificationPreference[]
239-
sessionRecordings SessionRecording[]
239+
sessionReplays SessionReplay[]
240240
241241
@@id([tenancyId, projectUserId])
242242
@@unique([mirroredProjectId, mirroredBranchId, projectUserId])
@@ -280,7 +280,7 @@ model ProjectUserOAuthAccount {
280280
@@index([tenancyId, projectUserId])
281281
}
282282

283-
model SessionRecording {
283+
model SessionReplay {
284284
id String @db.Uuid
285285
286286
tenancyId String @db.Uuid
@@ -296,20 +296,21 @@ model SessionRecording {
296296
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
297297
tenancy Tenancy @relation(fields: [tenancyId], references: [id], onDelete: Cascade)
298298
299-
chunks SessionRecordingChunk[]
299+
chunks SessionReplayChunk[]
300300
301301
@@id([tenancyId, id])
302+
@@map("SessionReplay")
302303
@@index([tenancyId, projectUserId, startedAt])
303304
@@index([tenancyId, lastEventAt])
304305
// index by updatedAt instead of lastEventAt because event timing can be spoofed
305306
@@index([tenancyId, refreshTokenId, updatedAt])
306307
}
307308

308-
model SessionRecordingChunk {
309+
model SessionReplayChunk {
309310
id String @id @default(uuid()) @db.Uuid
310311
311-
tenancyId String @db.Uuid
312-
sessionRecordingId String @db.Uuid
312+
tenancyId String @db.Uuid
313+
sessionReplayId String @db.Uuid @map("sessionReplayId")
313314
314315
// Unique per uploaded batch for a given session id.
315316
batchId String @db.Uuid
@@ -329,11 +330,12 @@ model SessionRecordingChunk {
329330
330331
createdAt DateTime @default(now())
331332
332-
sessionRecording SessionRecording @relation(fields: [tenancyId, sessionRecordingId], references: [tenancyId, id], onDelete: Cascade)
333-
tenancy Tenancy @relation(fields: [tenancyId], references: [id], onDelete: Cascade)
333+
sessionReplay SessionReplay @relation(fields: [tenancyId, sessionReplayId], references: [tenancyId, id], onDelete: Cascade)
334+
tenancy Tenancy @relation(fields: [tenancyId], references: [id], onDelete: Cascade)
334335
335-
@@unique([tenancyId, sessionRecordingId, batchId])
336-
@@index([tenancyId, sessionRecordingId, createdAt])
336+
@@unique([tenancyId, sessionReplayId, batchId])
337+
@@map("SessionReplayChunk")
338+
@@index([tenancyId, sessionReplayId, createdAt])
337339
}
338340

339341
enum ContactChannelType {

apps/backend/prisma/seed.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,11 +1118,11 @@ async function seedDummyProject(options: DummyProjectSeedOptions) {
11181118
userEmailToId,
11191119
});
11201120

1121-
await seedDummySessionRecordings({
1121+
await seedDummySessionReplays({
11221122
prisma: dummyPrisma,
11231123
tenancyId: dummyTenancy.id,
11241124
userEmailToId,
1125-
targetSessionRecordingCount: 75
1125+
targetSessionReplayCount: 75
11261126
});
11271127

11281128
console.log('Seeded dummy project data');
@@ -1773,43 +1773,43 @@ async function seedDummySessionActivityEvents(options: SessionActivityEventSeedO
17731773
console.log('Finished seeding session activity events');
17741774
}
17751775

1776-
type SessionRecordingSeedOptions = {
1776+
type SessionReplaySeedOptions = {
17771777
prisma: PrismaClientTransaction,
17781778
tenancyId: string,
17791779
userEmailToId: Map<string, string>,
1780-
targetSessionRecordingCount?: number,
1780+
targetSessionReplayCount?: number,
17811781
};
17821782

1783-
async function seedDummySessionRecordings(options: SessionRecordingSeedOptions) {
1783+
async function seedDummySessionReplays(options: SessionReplaySeedOptions) {
17841784
const {
17851785
prisma,
17861786
tenancyId,
17871787
userEmailToId,
1788-
targetSessionRecordingCount = 250,
1788+
targetSessionReplayCount = 250,
17891789
} = options;
17901790

1791-
const existingCount = await prisma.sessionRecording.count({
1791+
const existingCount = await prisma.sessionReplay.count({
17921792
where: {
17931793
tenancyId,
17941794
},
17951795
});
17961796

1797-
if (existingCount >= targetSessionRecordingCount) {
1798-
console.log(`Dummy project already has ${existingCount} session recordings, skipping seeding`);
1797+
if (existingCount >= targetSessionReplayCount) {
1798+
console.log(`Dummy project already has ${existingCount} session replays, skipping seeding`);
17991799
return;
18001800
}
18011801

1802-
const toCreate = targetSessionRecordingCount - existingCount;
1802+
const toCreate = targetSessionReplayCount - existingCount;
18031803
const userIds = Array.from(userEmailToId.values());
18041804
if (userIds.length === 0) {
1805-
throw new Error('Cannot seed session recordings: no dummy project users exist');
1805+
throw new Error('Cannot seed session replays: no dummy project users exist');
18061806
}
18071807

18081808
const now = new Date();
18091809
const twoWeeksAgo = new Date(now);
18101810
twoWeeksAgo.setDate(twoWeeksAgo.getDate() - 14);
18111811

1812-
const seeds: Prisma.SessionRecordingCreateManyInput[] = [];
1812+
const seeds: Prisma.SessionReplayCreateManyInput[] = [];
18131813
for (let i = 0; i < toCreate; i++) {
18141814
const startedAt = new Date(
18151815
twoWeeksAgo.getTime() + Math.random() * (now.getTime() - twoWeeksAgo.getTime()),
@@ -1828,9 +1828,9 @@ async function seedDummySessionRecordings(options: SessionRecordingSeedOptions)
18281828
});
18291829
}
18301830

1831-
await prisma.sessionRecording.createMany({
1831+
await prisma.sessionReplay.createMany({
18321832
data: seeds,
18331833
});
18341834

1835-
console.log(`Seeded ${toCreate} session recordings`);
1835+
console.log(`Seeded ${toCreate} session replays`);
18361836
}

apps/backend/src/app/api/latest/internal/session-recordings/[session_recording_id]/chunks/[chunk_id]/events/route.tsx renamed to apps/backend/src/app/api/latest/internal/session-replays/[session_replay_id]/chunks/[chunk_id]/events/route.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const GET = createSmartRouteHandler({
1717
tenancy: adaptSchema.defined(),
1818
}).defined(),
1919
params: yupObject({
20-
session_recording_id: yupString().defined(),
20+
session_replay_id: yupString().defined(),
2121
chunk_id: yupString().defined(),
2222
}).defined(),
2323
}),
@@ -31,13 +31,13 @@ export const GET = createSmartRouteHandler({
3131
async handler({ auth, params }) {
3232
const prisma = await getPrismaClientForTenancy(auth.tenancy);
3333

34-
const sessionRecordingId = params.session_recording_id;
34+
const sessionReplayId = params.session_replay_id;
3535
const chunkId = params.chunk_id;
3636

37-
const chunk = await prisma.sessionRecordingChunk.findFirst({
37+
const chunk = await prisma.sessionReplayChunk.findFirst({
3838
where: {
3939
tenancyId: auth.tenancy.id,
40-
sessionRecordingId,
40+
sessionReplayId,
4141
id: chunkId,
4242
},
4343
select: {
@@ -64,20 +64,20 @@ export const GET = createSmartRouteHandler({
6464
try {
6565
parsed = JSON.parse(new TextDecoder().decode(unzipped));
6666
} catch (e) {
67-
throw new StackAssertionError("Failed to decode session recording chunk JSON", { cause: e });
67+
throw new StackAssertionError("Failed to decode session replay chunk JSON", { cause: e });
6868
}
6969

7070
if (typeof parsed !== "object" || parsed === null) {
71-
throw new StackAssertionError("Decoded session recording chunk is not an object");
71+
throw new StackAssertionError("Decoded session replay chunk is not an object");
7272
}
73-
if (parsed.session_recording_id !== sessionRecordingId) {
74-
throw new StackAssertionError("Decoded session recording chunk session_recording_id mismatch", {
75-
expected: sessionRecordingId,
76-
actual: parsed.session_recording_id,
73+
if (parsed.session_replay_id !== sessionReplayId) {
74+
throw new StackAssertionError("Decoded session replay chunk session_replay_id mismatch", {
75+
expected: sessionReplayId,
76+
actual: parsed.session_replay_id,
7777
});
7878
}
7979
if (!Array.isArray(parsed.events)) {
80-
throw new StackAssertionError("Decoded session recording chunk events is not an array");
80+
throw new StackAssertionError("Decoded session replay chunk events is not an array");
8181
}
8282

8383
return {

apps/backend/src/app/api/latest/internal/session-recordings/[session_recording_id]/chunks/route.tsx renamed to apps/backend/src/app/api/latest/internal/session-replays/[session_replay_id]/chunks/route.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export const GET = createSmartRouteHandler({
1515
tenancy: adaptSchema.defined(),
1616
}).defined(),
1717
params: yupObject({
18-
session_recording_id: yupString().defined(),
18+
session_replay_id: yupString().defined(),
1919
}).defined(),
2020
query: yupObject({
2121
cursor: yupString().optional(),
@@ -45,13 +45,13 @@ export const GET = createSmartRouteHandler({
4545
async handler({ auth, params, query }) {
4646
const prisma = await getPrismaClientForTenancy(auth.tenancy);
4747

48-
const sessionRecordingId = params.session_recording_id;
49-
const exists = await prisma.sessionRecording.findUnique({
50-
where: { tenancyId_id: { tenancyId: auth.tenancy.id, id: sessionRecordingId } },
48+
const sessionReplayId = params.session_replay_id;
49+
const exists = await prisma.sessionReplay.findUnique({
50+
where: { tenancyId_id: { tenancyId: auth.tenancy.id, id: sessionReplayId } },
5151
select: { id: true },
5252
});
5353
if (!exists) {
54-
throw new KnownErrors.ItemNotFound(sessionRecordingId);
54+
throw new KnownErrors.ItemNotFound(sessionReplayId);
5555
}
5656

5757
const rawLimit = query.limit ?? String(DEFAULT_LIMIT);
@@ -61,10 +61,10 @@ export const GET = createSmartRouteHandler({
6161
const cursorId = query.cursor;
6262
let cursorPivot: { firstEventAt: Date } | null = null;
6363
if (cursorId) {
64-
cursorPivot = await prisma.sessionRecordingChunk.findFirst({
64+
cursorPivot = await prisma.sessionReplayChunk.findFirst({
6565
where: {
6666
tenancyId: auth.tenancy.id,
67-
sessionRecordingId,
67+
sessionReplayId,
6868
id: cursorId,
6969
},
7070
select: { firstEventAt: true },
@@ -74,17 +74,17 @@ export const GET = createSmartRouteHandler({
7474
}
7575
}
7676

77-
const cursorWhere: Prisma.SessionRecordingChunkWhereInput = cursorId && cursorPivot ? {
77+
const cursorWhere: Prisma.SessionReplayChunkWhereInput = cursorId && cursorPivot ? {
7878
OR: [
7979
{ firstEventAt: { gt: cursorPivot.firstEventAt } },
8080
{ AND: [{ firstEventAt: { equals: cursorPivot.firstEventAt } }, { id: { gt: cursorId } }] },
8181
],
8282
} : {};
8383

84-
const chunks = await prisma.sessionRecordingChunk.findMany({
84+
const chunks = await prisma.sessionReplayChunk.findMany({
8585
where: {
8686
tenancyId: auth.tenancy.id,
87-
sessionRecordingId,
87+
sessionReplayId,
8888
...cursorWhere,
8989
},
9090
orderBy: [{ firstEventAt: "asc" }, { id: "asc" }],

apps/backend/src/app/api/latest/internal/session-recordings/[session_recording_id]/events/route.tsx renamed to apps/backend/src/app/api/latest/internal/session-replays/[session_replay_id]/events/route.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const GET = createSmartRouteHandler({
1919
tenancy: adaptSchema.defined(),
2020
}).defined(),
2121
params: yupObject({
22-
session_recording_id: yupString().defined(),
22+
session_replay_id: yupString().defined(),
2323
}).defined(),
2424
query: yupObject({
2525
offset: yupString().optional(),
@@ -49,19 +49,19 @@ export const GET = createSmartRouteHandler({
4949
async handler({ auth, params, query }) {
5050
const prisma = await getPrismaClientForTenancy(auth.tenancy);
5151

52-
const sessionRecordingId = params.session_recording_id;
53-
const exists = await prisma.sessionRecording.findUnique({
54-
where: { tenancyId_id: { tenancyId: auth.tenancy.id, id: sessionRecordingId } },
52+
const sessionReplayId = params.session_replay_id;
53+
const exists = await prisma.sessionReplay.findUnique({
54+
where: { tenancyId_id: { tenancyId: auth.tenancy.id, id: sessionReplayId } },
5555
select: { id: true },
5656
});
5757
if (!exists) {
58-
throw new KnownErrors.ItemNotFound(sessionRecordingId);
58+
throw new KnownErrors.ItemNotFound(sessionReplayId);
5959
}
6060

61-
const chunks = await prisma.sessionRecordingChunk.findMany({
61+
const chunks = await prisma.sessionReplayChunk.findMany({
6262
where: {
6363
tenancyId: auth.tenancy.id,
64-
sessionRecordingId,
64+
sessionReplayId,
6565
},
6666
orderBy: [{ firstEventAt: "asc" }, { id: "asc" }],
6767
select: {
@@ -110,20 +110,20 @@ export const GET = createSmartRouteHandler({
110110
try {
111111
parsed = JSON.parse(new TextDecoder().decode(unzipped));
112112
} catch (e) {
113-
throw new StackAssertionError("Failed to decode session recording chunk JSON", { cause: e });
113+
throw new StackAssertionError("Failed to decode session replay chunk JSON", { cause: e });
114114
}
115115

116116
if (typeof parsed !== "object" || parsed === null) {
117-
throw new StackAssertionError("Decoded session recording chunk is not an object");
117+
throw new StackAssertionError("Decoded session replay chunk is not an object");
118118
}
119-
if (parsed.session_recording_id !== sessionRecordingId) {
120-
throw new StackAssertionError("Decoded session recording chunk session_recording_id mismatch", {
121-
expected: sessionRecordingId,
122-
actual: parsed.session_recording_id,
119+
if (parsed.session_replay_id !== sessionReplayId) {
120+
throw new StackAssertionError("Decoded session replay chunk session_replay_id mismatch", {
121+
expected: sessionReplayId,
122+
actual: parsed.session_replay_id,
123123
});
124124
}
125125
if (!Array.isArray(parsed.events)) {
126-
throw new StackAssertionError("Decoded session recording chunk events is not an array");
126+
throw new StackAssertionError("Decoded session replay chunk events is not an array");
127127
}
128128

129129
chunkEvents[idx] = { chunk_id: chunk.id, events: parsed.events as any[] };

0 commit comments

Comments
 (0)