Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions prisma/migrations/22_add_team_invite/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
BEGIN;

-- CreateTable
CREATE TABLE "team_invite" (
"team_invite_id" UUID NOT NULL,
"team_id" UUID NOT NULL,
"issuer_id" UUID NOT NULL,
"token_digest" VARCHAR(128) NOT NULL,
"role" VARCHAR(50) NOT NULL,
"expires_at" TIMESTAMPTZ(6) NOT NULL,
"revoked_at" TIMESTAMPTZ(6),
"used_at" TIMESTAMPTZ(6),
"used_by" UUID,
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "team_invite_pkey" PRIMARY KEY ("team_invite_id"),
CONSTRAINT "team_invite_role_check" CHECK (
"role" IN ('team-member', 'team-view-only')
),
CONSTRAINT "team_invite_single_terminal_state" CHECK (
NOT ("revoked_at" IS NOT NULL AND "used_at" IS NOT NULL)
),
CONSTRAINT "team_invite_redemption_attribution" CHECK (
("used_at" IS NULL) = ("used_by" IS NULL)
)
);

-- DeduplicateTeamUsers
LOCK TABLE "team_user" IN SHARE ROW EXCLUSIVE MODE;

WITH "ranked_team_user" AS (
SELECT
"team_user_id",
ROW_NUMBER() OVER (
PARTITION BY "team_id", "user_id"
-- Preserve the most recently changed membership so an older duplicate
-- cannot restore a stale role when the unique constraint is added.
ORDER BY "updated_at" DESC NULLS LAST, "created_at" DESC NULLS LAST, "team_user_id" DESC
) AS "row_number"
FROM "team_user"
)
Comment thread
shmlkv marked this conversation as resolved.
DELETE FROM "team_user"
USING "ranked_team_user"
WHERE "team_user"."team_user_id" = "ranked_team_user"."team_user_id"
AND "ranked_team_user"."row_number" > 1;

-- CreateIndex
CREATE UNIQUE INDEX "team_user_team_id_user_id_key" ON "team_user"("team_id", "user_id");

-- CreateIndex
CREATE UNIQUE INDEX "team_invite_token_digest_key" ON "team_invite"("token_digest");
CREATE INDEX "team_invite_team_id_idx" ON "team_invite"("team_id");
CREATE INDEX "team_invite_issuer_id_idx" ON "team_invite"("issuer_id");
CREATE INDEX "team_invite_used_by_idx" ON "team_invite"("used_by");
CREATE INDEX "team_invite_expires_at_idx" ON "team_invite"("expires_at");

COMMIT;
27 changes: 27 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ model User {
teams TeamUser[]
reports Report[]
boards Board[] @relation("user")
issuedTeamInvites TeamInvite[] @relation("teamInviteIssuer")
usedTeamInvites TeamInvite[] @relation("teamInviteUser")

@@map("user")
}
Expand Down Expand Up @@ -220,6 +222,7 @@ model Team {
links Link[]
pixels Pixel[]
boards Board[]
invites TeamInvite[]

@@index([accessCode])
@@map("team")
Expand All @@ -236,11 +239,35 @@ model TeamUser {
team Team @relation(fields: [teamId], references: [id])
user User @relation(fields: [userId], references: [id])

@@unique([teamId, userId])
@@index([teamId])
@@index([userId])
@@map("team_user")
}

model TeamInvite {
id String @id() @map("team_invite_id") @db.Uuid
teamId String @map("team_id") @db.Uuid
issuerId String @map("issuer_id") @db.Uuid
tokenDigest String @unique @map("token_digest") @db.VarChar(128)
role String @db.VarChar(50)
expiresAt DateTime @map("expires_at") @db.Timestamptz(6)
revokedAt DateTime? @map("revoked_at") @db.Timestamptz(6)
usedAt DateTime? @map("used_at") @db.Timestamptz(6)
usedBy String? @map("used_by") @db.Uuid
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)

team Team @relation(fields: [teamId], references: [id])
issuer User @relation("teamInviteIssuer", fields: [issuerId], references: [id])
usedByUser User? @relation("teamInviteUser", fields: [usedBy], references: [id])

@@index([teamId])
@@index([issuerId])
@@index([usedBy])
@@index([expiresAt])
@@map("team_invite")
}

model Report {
id String @id() @map("report_id") @db.Uuid
userId String @map("user_id") @db.Uuid
Expand Down
35 changes: 33 additions & 2 deletions public/intl/messages/ar-SA.json
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,29 @@
"website-id": "معرّف الموقع",
"websites": "المواقع",
"window": "النافذة",
"yesterday": "الأمس"
"yesterday": "الأمس",
"active": "Active",
"copy-invitation-link": "Copy invitation link",
"create-account": "Create account",
"create-account-and-join-team": "Create account and join team",
"create-invitation": "Create invitation",
"done": "Done",
"expired": "Expired",
"expires": "Expires",
"expires-in": "Expires in",
"invitation-link": "Invitation link",
"invitations": "Invitations",
"invitation-unavailable": "Invitation unavailable",
"one-day": "1 day",
"revoke": "Revoke",
"revoked": "Revoked",
"revoke-invitation": "Revoke invitation",
"seven-days": "7 days",
"sign-in": "Sign in",
"sign-in-and-join-team": "Sign in and join team",
"status": "Status",
"thirty-days": "30 days",
"used": "Used"
},
"message": {
"action-confirmation": "اكتب {confirmation} في المربع أدناه للتأكيد.",
Expand Down Expand Up @@ -423,6 +445,15 @@
"user-deleted": "تم حذف المستخدم.",
"replay-min-duration-description": "إظهار التسجيلات ذات المدة الأطول أو المساوية لهذه المدة فقط.",
"viewed-page": "شوهدت الصفحة",
"visitor-log": "زائر من <b>{country}</b> يستخدم <b>{browser}</b> على <b>{os}</b> <b>{device}</b>"
"visitor-log": "زائر من <b>{country}</b> يستخدم <b>{browser}</b> على <b>{os}</b> <b>{device}</b>",
"confirm-revoke-invitation": "Are you sure you want to revoke this invitation?",
"invitation-link-one-time": "Copy this invitation link now. It will not be shown again.",
"invitation-unavailable": "This invitation can no longer be used.",
"invitation-authentication-required": "Sign in through your configured authentication provider, then reopen this invitation link.",
"invited-to-join": "You've been invited to join",
"no-invitations": "There are no invitations.",
"ALREADY_TEAM_MEMBER": "You are already a member of the team.",
"INVALID_TEAM_INVITE": "This invitation can no longer be used.",
"USERNAME_UNAVAILABLE": "Username is unavailable."
}
}
35 changes: 33 additions & 2 deletions public/intl/messages/be-BY.json
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,29 @@
"website-id": "Ідэнтыфікатар сайта",
"websites": "Сайты",
"window": "Вакно",
"yesterday": "Учора"
"yesterday": "Учора",
"active": "Active",
"copy-invitation-link": "Copy invitation link",
"create-account": "Create account",
"create-account-and-join-team": "Create account and join team",
"create-invitation": "Create invitation",
"done": "Done",
"expired": "Expired",
"expires": "Expires",
"expires-in": "Expires in",
"invitation-link": "Invitation link",
"invitations": "Invitations",
"invitation-unavailable": "Invitation unavailable",
"one-day": "1 day",
"revoke": "Revoke",
"revoked": "Revoked",
"revoke-invitation": "Revoke invitation",
"seven-days": "7 days",
"sign-in": "Sign in",
"sign-in-and-join-team": "Sign in and join team",
"status": "Status",
"thirty-days": "30 days",
"used": "Used"
},
"message": {
"action-confirmation": "Увядзіце {confirmation} у поле ніжэй, каб пацвердзіць.",
Expand Down Expand Up @@ -423,6 +445,15 @@
"user-deleted": "Карыстальнік выдалены.",
"replay-min-duration-description": "Паказваць толькі паўторы з такой або большай працягласцю.",
"viewed-page": "Праглядзеў старонку",
"visitor-log": "Наведвальнік з <b>{country}</b> праз <b>{browser}</b> на <b>{os}</b> <b>{device}</b>"
"visitor-log": "Наведвальнік з <b>{country}</b> праз <b>{browser}</b> на <b>{os}</b> <b>{device}</b>",
"confirm-revoke-invitation": "Are you sure you want to revoke this invitation?",
"invitation-link-one-time": "Copy this invitation link now. It will not be shown again.",
"invitation-unavailable": "This invitation can no longer be used.",
"invitation-authentication-required": "Sign in through your configured authentication provider, then reopen this invitation link.",
"invited-to-join": "You've been invited to join",
"no-invitations": "There are no invitations.",
"ALREADY_TEAM_MEMBER": "You are already a member of the team.",
"INVALID_TEAM_INVITE": "This invitation can no longer be used.",
"USERNAME_UNAVAILABLE": "Username is unavailable."
}
}
35 changes: 33 additions & 2 deletions public/intl/messages/bg-BG.json
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,29 @@
"website-id": "Идентификатор на уебсайт",
"websites": "Уебсайтове",
"window": "Прозорец",
"yesterday": "Вчера"
"yesterday": "Вчера",
"active": "Active",
"copy-invitation-link": "Copy invitation link",
"create-account": "Create account",
"create-account-and-join-team": "Create account and join team",
"create-invitation": "Create invitation",
"done": "Done",
"expired": "Expired",
"expires": "Expires",
"expires-in": "Expires in",
"invitation-link": "Invitation link",
"invitations": "Invitations",
"invitation-unavailable": "Invitation unavailable",
"one-day": "1 day",
"revoke": "Revoke",
"revoked": "Revoked",
"revoke-invitation": "Revoke invitation",
"seven-days": "7 days",
"sign-in": "Sign in",
"sign-in-and-join-team": "Sign in and join team",
"status": "Status",
"thirty-days": "30 days",
"used": "Used"
},
"message": {
"action-confirmation": "Въведете {confirmation} в полето по-долу, за да потвърдите.",
Expand Down Expand Up @@ -423,6 +445,15 @@
"user-deleted": "Потребителят е изтрит.",
"replay-min-duration-description": "Показвай само записи с тази или по-голяма продължителност.",
"viewed-page": "Страницата е видяна",
"visitor-log": "Посетител от <b>{country}</b>, използващ <b>{browser}</b> на <b>{os}</b> <b>{device}</b>"
"visitor-log": "Посетител от <b>{country}</b>, използващ <b>{browser}</b> на <b>{os}</b> <b>{device}</b>",
"confirm-revoke-invitation": "Are you sure you want to revoke this invitation?",
"invitation-link-one-time": "Copy this invitation link now. It will not be shown again.",
"invitation-unavailable": "This invitation can no longer be used.",
"invitation-authentication-required": "Sign in through your configured authentication provider, then reopen this invitation link.",
"invited-to-join": "You've been invited to join",
"no-invitations": "There are no invitations.",
"ALREADY_TEAM_MEMBER": "You are already a member of the team.",
"INVALID_TEAM_INVITE": "This invitation can no longer be used.",
"USERNAME_UNAVAILABLE": "Username is unavailable."
}
}
35 changes: 33 additions & 2 deletions public/intl/messages/bn-BD.json
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,29 @@
"website-id": "ওয়েবসাইট ID",
"websites": "সবগুলো ওয়েবসাইট",
"window": "উইন্ডো",
"yesterday": "গতকাল"
"yesterday": "গতকাল",
"active": "Active",
"copy-invitation-link": "Copy invitation link",
"create-account": "Create account",
"create-account-and-join-team": "Create account and join team",
"create-invitation": "Create invitation",
"done": "Done",
"expired": "Expired",
"expires": "Expires",
"expires-in": "Expires in",
"invitation-link": "Invitation link",
"invitations": "Invitations",
"invitation-unavailable": "Invitation unavailable",
"one-day": "1 day",
"revoke": "Revoke",
"revoked": "Revoked",
"revoke-invitation": "Revoke invitation",
"seven-days": "7 days",
"sign-in": "Sign in",
"sign-in-and-join-team": "Sign in and join team",
"status": "Status",
"thirty-days": "30 days",
"used": "Used"
},
"message": {
"action-confirmation": "নিশ্চিত করতে নিচের বক্সে {confirmation} টাইপ করুন।",
Expand Down Expand Up @@ -423,6 +445,15 @@
"user-deleted": "ব্যবহারকারী মুছে ফেলা হয়েছে।",
"replay-min-duration-description": "শুধুমাত্র এই সময়কাল বা তার উপরে রিপ্লেগুলি দেখান।",
"viewed-page": "দেখা পৃষ্ঠা",
"visitor-log": "<b>{country}</b> থেকে একজন ভিসিটর {ব্রাউজার}, ব্যবহার করছেন <b>{os}</b> <b>{device}</b> এর মধ্যে।"
"visitor-log": "<b>{country}</b> থেকে একজন ভিসিটর {ব্রাউজার}, ব্যবহার করছেন <b>{os}</b> <b>{device}</b> এর মধ্যে।",
"confirm-revoke-invitation": "Are you sure you want to revoke this invitation?",
"invitation-link-one-time": "Copy this invitation link now. It will not be shown again.",
"invitation-unavailable": "This invitation can no longer be used.",
"invitation-authentication-required": "Sign in through your configured authentication provider, then reopen this invitation link.",
"invited-to-join": "You've been invited to join",
"no-invitations": "There are no invitations.",
"ALREADY_TEAM_MEMBER": "You are already a member of the team.",
"INVALID_TEAM_INVITE": "This invitation can no longer be used.",
"USERNAME_UNAVAILABLE": "Username is unavailable."
}
}
35 changes: 33 additions & 2 deletions public/intl/messages/bs-BA.json
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,29 @@
"website-id": "ID web stranice",
"websites": "Web stranice",
"window": "Prozor",
"yesterday": "Jučer"
"yesterday": "Jučer",
"active": "Active",
"copy-invitation-link": "Copy invitation link",
"create-account": "Create account",
"create-account-and-join-team": "Create account and join team",
"create-invitation": "Create invitation",
"done": "Done",
"expired": "Expired",
"expires": "Expires",
"expires-in": "Expires in",
"invitation-link": "Invitation link",
"invitations": "Invitations",
"invitation-unavailable": "Invitation unavailable",
"one-day": "1 day",
"revoke": "Revoke",
"revoked": "Revoked",
"revoke-invitation": "Revoke invitation",
"seven-days": "7 days",
"sign-in": "Sign in",
"sign-in-and-join-team": "Sign in and join team",
"status": "Status",
"thirty-days": "30 days",
"used": "Used"
},
"message": {
"action-confirmation": "Unesite {confirmation} ispod da potvrdite.",
Expand Down Expand Up @@ -435,6 +457,15 @@
"replay-min-duration-description": "Prikaži samo snimke s ovim ili dužim trajanjem.",
"viewed-page": "Pogledana stranica",
"upgrade-required": "Ova funkcija zahtijeva pretplatu na plan {plan}.",
"visitor-log": "Posjetitelj iz <b>{country}</b> koristi <b>{browser}</b> na <b>{os}</b> <b>{device}</b>"
"visitor-log": "Posjetitelj iz <b>{country}</b> koristi <b>{browser}</b> na <b>{os}</b> <b>{device}</b>",
"confirm-revoke-invitation": "Are you sure you want to revoke this invitation?",
"invitation-link-one-time": "Copy this invitation link now. It will not be shown again.",
"invitation-unavailable": "This invitation can no longer be used.",
"invitation-authentication-required": "Sign in through your configured authentication provider, then reopen this invitation link.",
"invited-to-join": "You've been invited to join",
"no-invitations": "There are no invitations.",
"ALREADY_TEAM_MEMBER": "You are already a member of the team.",
"INVALID_TEAM_INVITE": "This invitation can no longer be used.",
"USERNAME_UNAVAILABLE": "Username is unavailable."
}
}
35 changes: 33 additions & 2 deletions public/intl/messages/ca-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,29 @@
"website-id": "ID del lloc web",
"websites": "Llocs web",
"window": "Finestra",
"yesterday": "Ahir"
"yesterday": "Ahir",
"active": "Active",
"copy-invitation-link": "Copy invitation link",
"create-account": "Create account",
"create-account-and-join-team": "Create account and join team",
"create-invitation": "Create invitation",
"done": "Done",
"expired": "Expired",
"expires": "Expires",
"expires-in": "Expires in",
"invitation-link": "Invitation link",
"invitations": "Invitations",
"invitation-unavailable": "Invitation unavailable",
"one-day": "1 day",
"revoke": "Revoke",
"revoked": "Revoked",
"revoke-invitation": "Revoke invitation",
"seven-days": "7 days",
"sign-in": "Sign in",
"sign-in-and-join-team": "Sign in and join team",
"status": "Status",
"thirty-days": "30 days",
"used": "Used"
},
"message": {
"action-confirmation": "Escrigui {confirmation} al cuadre inferior per confirmar.",
Expand Down Expand Up @@ -435,6 +457,15 @@
"replay-min-duration-description": "Mostra només les reproduccions amb aquesta durada o superior.",
"viewed-page": "Pàgina vista",
"upgrade-required": "Aquesta funció requereix una subscripció al pla {plan}.",
"visitor-log": "Visitant de <b>{country}</b> usant <b>{browser}</b> a <b>{os}</b> <b>{device}</b>"
"visitor-log": "Visitant de <b>{country}</b> usant <b>{browser}</b> a <b>{os}</b> <b>{device}</b>",
"confirm-revoke-invitation": "Are you sure you want to revoke this invitation?",
"invitation-link-one-time": "Copy this invitation link now. It will not be shown again.",
"invitation-unavailable": "This invitation can no longer be used.",
"invitation-authentication-required": "Sign in through your configured authentication provider, then reopen this invitation link.",
"invited-to-join": "You've been invited to join",
"no-invitations": "There are no invitations.",
"ALREADY_TEAM_MEMBER": "You are already a member of the team.",
"INVALID_TEAM_INVITE": "This invitation can no longer be used.",
"USERNAME_UNAVAILABLE": "Username is unavailable."
}
}
Loading