Skip to content

Commit b4be911

Browse files
authored
feat: doc status & share status (#14426)
#### PR Dependency Tree * **PR #14426** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Admin dashboard: view workspace analytics (storage, sync activity, top shared links) with charts and configurable windows. * Document analytics tab: see total/unique/guest views and trends over selectable time windows. * Last-accessed members: view who last accessed a document, with pagination. * Shared links analytics: browse and paginate all shared links with view/unique/guest metrics and share URLs. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent b46bf91 commit b4be911

File tree

44 files changed

+5701
-86
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+5701
-86
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
CREATE TABLE IF NOT EXISTS "workspace_admin_stats_daily" (
2+
"workspace_id" VARCHAR NOT NULL,
3+
"date" DATE NOT NULL,
4+
"snapshot_size" BIGINT NOT NULL DEFAULT 0,
5+
"blob_size" BIGINT NOT NULL DEFAULT 0,
6+
"member_count" BIGINT NOT NULL DEFAULT 0,
7+
"updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT NOW(),
8+
CONSTRAINT "workspace_admin_stats_daily_pkey" PRIMARY KEY ("workspace_id", "date"),
9+
CONSTRAINT "workspace_admin_stats_daily_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE
10+
);
11+
12+
CREATE INDEX IF NOT EXISTS "workspace_admin_stats_daily_date_idx" ON "workspace_admin_stats_daily" ("date");
13+
14+
CREATE TABLE IF NOT EXISTS "sync_active_users_minutely" (
15+
"minute_ts" TIMESTAMPTZ(3) NOT NULL,
16+
"active_users" INTEGER NOT NULL DEFAULT 0,
17+
"updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT NOW(),
18+
CONSTRAINT "sync_active_users_minutely_pkey" PRIMARY KEY ("minute_ts")
19+
);
20+
21+
CREATE TABLE IF NOT EXISTS "workspace_doc_view_daily" (
22+
"workspace_id" VARCHAR NOT NULL,
23+
"doc_id" VARCHAR NOT NULL,
24+
"date" DATE NOT NULL,
25+
"total_views" BIGINT NOT NULL DEFAULT 0,
26+
"unique_views" BIGINT NOT NULL DEFAULT 0,
27+
"guest_views" BIGINT NOT NULL DEFAULT 0,
28+
"last_accessed_at" TIMESTAMPTZ(3),
29+
"updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT NOW(),
30+
CONSTRAINT "workspace_doc_view_daily_pkey" PRIMARY KEY ("workspace_id", "doc_id", "date"),
31+
CONSTRAINT "workspace_doc_view_daily_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE
32+
);
33+
34+
CREATE INDEX IF NOT EXISTS "workspace_doc_view_daily_workspace_id_date_idx" ON "workspace_doc_view_daily" ("workspace_id", "date");
35+
36+
CREATE TABLE IF NOT EXISTS "workspace_member_last_access" (
37+
"workspace_id" VARCHAR NOT NULL,
38+
"user_id" VARCHAR NOT NULL,
39+
"last_accessed_at" TIMESTAMPTZ(3) NOT NULL,
40+
"last_doc_id" VARCHAR,
41+
"updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT NOW(),
42+
CONSTRAINT "workspace_member_last_access_pkey" PRIMARY KEY ("workspace_id", "user_id"),
43+
CONSTRAINT "workspace_member_last_access_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE,
44+
CONSTRAINT "workspace_member_last_access_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE
45+
);
46+
47+
CREATE INDEX IF NOT EXISTS "workspace_member_last_access_workspace_id_last_accessed_at_idx" ON "workspace_member_last_access" ("workspace_id", "last_accessed_at" DESC);
48+
49+
CREATE INDEX IF NOT EXISTS "workspace_member_last_access_workspace_id_last_doc_id_idx" ON "workspace_member_last_access" ("workspace_id", "last_doc_id");
50+
51+
CREATE INDEX IF NOT EXISTS "workspace_pages_public_published_at_idx" ON "workspace_pages" ("public", "published_at");
52+
53+
CREATE INDEX IF NOT EXISTS "ai_sessions_messages_created_at_role_idx" ON "ai_sessions_messages" ("created_at", "role");
54+
55+
DROP TRIGGER IF EXISTS user_features_set_feature_id ON "user_features";
56+
57+
DROP TRIGGER IF EXISTS workspace_features_set_feature_id ON "workspace_features";
58+
59+
DROP FUNCTION IF EXISTS set_user_feature_id_from_name();
60+
61+
DROP FUNCTION IF EXISTS set_workspace_feature_id_from_name();
62+
63+
DROP FUNCTION IF EXISTS ensure_feature_exists(TEXT);
64+
65+
ALTER TABLE
66+
"user_features" DROP CONSTRAINT "user_features_feature_id_fkey";
67+
68+
ALTER TABLE
69+
"workspace_features" DROP CONSTRAINT "workspace_features_feature_id_fkey";
70+
71+
DROP INDEX "user_features_feature_id_idx";
72+
73+
DROP INDEX "workspace_features_feature_id_idx";
74+
75+
ALTER TABLE
76+
"user_features" DROP COLUMN "feature_id";
77+
78+
ALTER TABLE
79+
"workspace_features" DROP COLUMN "feature_id";
80+
81+
DROP TABLE "features";

packages/backend/server/schema.prisma

Lines changed: 85 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,31 +25,32 @@ model User {
2525
registered Boolean @default(true)
2626
disabled Boolean @default(false)
2727
28-
features UserFeature[]
29-
userStripeCustomer UserStripeCustomer?
30-
workspaces WorkspaceUserRole[]
28+
features UserFeature[]
29+
userStripeCustomer UserStripeCustomer?
30+
workspaces WorkspaceUserRole[]
3131
// Invite others to join the workspace
32-
WorkspaceInvitations WorkspaceUserRole[] @relation("inviter")
33-
docPermissions WorkspaceDocUserRole[]
34-
connectedAccounts ConnectedAccount[]
35-
calendarAccounts CalendarAccount[]
36-
sessions UserSession[]
37-
aiSessions AiSession[]
38-
appConfigs AppConfig[]
39-
userSnapshots UserSnapshot[]
40-
createdSnapshot Snapshot[] @relation("createdSnapshot")
41-
updatedSnapshot Snapshot[] @relation("updatedSnapshot")
42-
createdUpdate Update[] @relation("createdUpdate")
43-
createdHistory SnapshotHistory[] @relation("createdHistory")
44-
createdAiJobs AiJobs[] @relation("createdAiJobs")
32+
WorkspaceInvitations WorkspaceUserRole[] @relation("inviter")
33+
docPermissions WorkspaceDocUserRole[]
34+
connectedAccounts ConnectedAccount[]
35+
calendarAccounts CalendarAccount[]
36+
sessions UserSession[]
37+
aiSessions AiSession[]
38+
appConfigs AppConfig[]
39+
userSnapshots UserSnapshot[]
40+
createdSnapshot Snapshot[] @relation("createdSnapshot")
41+
updatedSnapshot Snapshot[] @relation("updatedSnapshot")
42+
createdUpdate Update[] @relation("createdUpdate")
43+
createdHistory SnapshotHistory[] @relation("createdHistory")
44+
createdAiJobs AiJobs[] @relation("createdAiJobs")
4545
// receive notifications
46-
notifications Notification[] @relation("user_notifications")
47-
settings UserSettings?
48-
comments Comment[]
49-
replies Reply[]
50-
commentAttachments CommentAttachment[] @relation("createdCommentAttachments")
51-
AccessToken AccessToken[]
52-
workspaceCalendars WorkspaceCalendar[]
46+
notifications Notification[] @relation("user_notifications")
47+
settings UserSettings?
48+
comments Comment[]
49+
replies Reply[]
50+
commentAttachments CommentAttachment[] @relation("createdCommentAttachments")
51+
AccessToken AccessToken[]
52+
workspaceCalendars WorkspaceCalendar[]
53+
workspaceMemberLastAccesses WorkspaceMemberLastAccess[]
5354
5455
@@index([email])
5556
@@map("users")
@@ -151,6 +152,9 @@ model Workspace {
151152
workspaceCalendars WorkspaceCalendar[]
152153
workspaceAdminStats WorkspaceAdminStats[]
153154
workspaceAdminStatsDirties WorkspaceAdminStatsDirty[]
155+
workspaceAdminStatsDaily WorkspaceAdminStatsDaily[]
156+
workspaceDocViewDaily WorkspaceDocViewDaily[]
157+
workspaceMemberLastAccess WorkspaceMemberLastAccess[]
154158
155159
@@index([lastCheckEmbeddings])
156160
@@index([createdAt])
@@ -180,6 +184,7 @@ model WorkspaceDoc {
180184
181185
@@id([workspaceId, docId])
182186
@@index([workspaceId, public])
187+
@@index([public, publishedAt])
183188
@@map("workspace_pages")
184189
}
185190

@@ -320,6 +325,62 @@ model WorkspaceAdminStatsDirty {
320325
@@map("workspace_admin_stats_dirty")
321326
}
322327

328+
model WorkspaceAdminStatsDaily {
329+
workspaceId String @map("workspace_id") @db.VarChar
330+
date DateTime @db.Date
331+
snapshotSize BigInt @default(0) @map("snapshot_size") @db.BigInt
332+
blobSize BigInt @default(0) @map("blob_size") @db.BigInt
333+
memberCount BigInt @default(0) @map("member_count") @db.BigInt
334+
updatedAt DateTime @default(now()) @map("updated_at") @db.Timestamptz(3)
335+
336+
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
337+
338+
@@id([workspaceId, date])
339+
@@index([date])
340+
@@map("workspace_admin_stats_daily")
341+
}
342+
343+
model SyncActiveUsersMinutely {
344+
minuteTs DateTime @id @map("minute_ts") @db.Timestamptz(3)
345+
activeUsers Int @default(0) @map("active_users") @db.Integer
346+
updatedAt DateTime @default(now()) @map("updated_at") @db.Timestamptz(3)
347+
348+
@@map("sync_active_users_minutely")
349+
}
350+
351+
model WorkspaceDocViewDaily {
352+
workspaceId String @map("workspace_id") @db.VarChar
353+
docId String @map("doc_id") @db.VarChar
354+
date DateTime @db.Date
355+
totalViews BigInt @default(0) @map("total_views") @db.BigInt
356+
uniqueViews BigInt @default(0) @map("unique_views") @db.BigInt
357+
guestViews BigInt @default(0) @map("guest_views") @db.BigInt
358+
lastAccessedAt DateTime? @map("last_accessed_at") @db.Timestamptz(3)
359+
updatedAt DateTime @default(now()) @map("updated_at") @db.Timestamptz(3)
360+
361+
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
362+
363+
@@id([workspaceId, docId, date])
364+
@@index([workspaceId, date])
365+
@@map("workspace_doc_view_daily")
366+
}
367+
368+
model WorkspaceMemberLastAccess {
369+
workspaceId String @map("workspace_id") @db.VarChar
370+
userId String @map("user_id") @db.VarChar
371+
lastAccessedAt DateTime @map("last_accessed_at") @db.Timestamptz(3)
372+
lastDocId String? @map("last_doc_id") @db.VarChar
373+
updatedAt DateTime @default(now()) @map("updated_at") @db.Timestamptz(3)
374+
375+
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
376+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
377+
378+
@@id([workspaceId, userId])
379+
@@index([workspaceId, lastAccessedAt(sort: Desc)])
380+
@@index([workspaceId, lastDocId])
381+
@@map("workspace_member_last_access")
382+
}
383+
323384
// the latest snapshot of each doc that we've seen
324385
// Snapshot + Updates are the latest state of the doc
325386
model Snapshot {
@@ -456,6 +517,7 @@ model AiSessionMessage {
456517
session AiSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
457518
458519
@@index([sessionId])
520+
@@index([createdAt, role])
459521
@@map("ai_sessions_messages")
460522
}
461523

0 commit comments

Comments
 (0)