Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♻️ Keep guest ids in separate column #1468

Merged
merged 6 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
6 changes: 5 additions & 1 deletion apps/web/src/app/[locale]/(admin)/polls/user-polls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,11 @@ function PollsListView({
status: PollStatus;
title: string;
createdAt: Date;
userId: string;
user: {
id: string;
name: string;
} | null;
guestId?: string | null;
participants: {
id: string;
name: string;
Expand Down
9 changes: 6 additions & 3 deletions apps/web/src/auth/merge-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,36 @@ export const mergeGuestsIntoUser = async (
) => {
await prisma.poll.updateMany({
where: {
userId: {
guestId: {
in: guestIds,
},
},
data: {
guestId: null,
userId: userId,
},
});

await prisma.participant.updateMany({
where: {
userId: {
guestId: {
in: guestIds,
},
},
data: {
guestId: null,
userId: userId,
},
});

await prisma.comment.updateMany({
where: {
userId: {
guestId: {
in: guestIds,
},
},
data: {
guestId: null,
lukevella marked this conversation as resolved.
Show resolved Hide resolved
userId: userId,
},
});
Expand Down
10 changes: 2 additions & 8 deletions apps/web/src/components/discussion/discussion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import { OptimizedAvatarImage } from "@/components/optimized-avatar-image";
import { Participant, ParticipantName } from "@/components/participant";
import { useParticipants } from "@/components/participants-provider";
import { Trans } from "@/components/trans";
import { usePermissions } from "@/contexts/permissions";
import { usePoll } from "@/contexts/poll";
import { useRole } from "@/contexts/role";
import { trpc } from "@/trpc/client";
Expand Down Expand Up @@ -73,7 +72,6 @@ function NewCommentForm({

const posthog = usePostHog();


const session = useUser();

const { register, reset, control, handleSubmit, formState } =
Expand Down Expand Up @@ -162,9 +160,7 @@ function DiscussionInner() {

const pollId = poll.id;

const { data: comments } = trpc.polls.comments.list.useQuery(
{ pollId },
);
const { data: comments } = trpc.polls.comments.list.useQuery({ pollId });
const posthog = usePostHog();

const queryClient = trpc.useUtils();
Expand All @@ -187,7 +183,6 @@ function DiscussionInner() {

const [isWriting, setIsWriting] = React.useState(false);
const role = useRole();
const { isUser } = usePermissions();

if (!comments) {
return null;
Expand All @@ -205,8 +200,7 @@ function DiscussionInner() {
<CardContent className="border-b">
<div className="space-y-4">
{comments.map((comment) => {
const canDelete =
role === "admin" || (comment.userId && isUser(comment.userId));
const canDelete = role === "admin" || session.ownsObject(comment);

return (
<div className="" key={comment.id}>
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/poll/desktop-poll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ const DesktopPoll: React.FunctionComponent = () => {
id: participant.id,
name: participant.name,
userId: participant.userId ?? undefined,
guestId: participant.guestId ?? undefined,
email: participant.email ?? undefined,
votes: participant.votes,
}}
Expand Down
5 changes: 3 additions & 2 deletions apps/web/src/components/poll/desktop-poll/participant-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface ParticipantRowProps {
id: string;
name: string;
userId?: string;
guestId?: string;
email?: string;
votes: Vote[];
};
Expand Down Expand Up @@ -105,10 +106,10 @@ const ParticipantRow: React.FunctionComponent<ParticipantRowProps> = ({
className,
onChangeEditMode,
}) => {
const { user, ownsObject } = useUser();
const { ownsObject } = useUser();
const { getVote, optionIds } = usePoll();

const isYou = user && ownsObject(participant) ? true : false;
const isYou = ownsObject(participant) ? true : false;

const { canEditParticipant } = usePermissions();
const canEdit = canEditParticipant(participant.id);
Expand Down
10 changes: 7 additions & 3 deletions apps/web/src/components/user-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useSubscription } from "@/contexts/plan";
import { PreferencesProvider } from "@/contexts/preferences";
import { useTranslation } from "@/i18n/client";
import { trpc } from "@/trpc/client";
import { isOwner } from "@/utils/permissions";

import { useRequiredContext } from "./use-required-context";

Expand All @@ -28,7 +29,10 @@ type UserData = {
export const UserContext = React.createContext<{
user: UserData;
refresh: (data?: Record<string, unknown>) => Promise<Session | null>;
ownsObject: (obj: { userId?: string | null }) => boolean;
ownsObject: (obj: {
userId?: string | null;
guestId?: string | null;
}) => boolean;
} | null>(null);

export const useUser = () => {
Expand Down Expand Up @@ -101,8 +105,8 @@ export const UserProvider = (props: { children?: React.ReactNode }) => {
locale: user.locale ?? i18n.language,
},
refresh: session.update,
ownsObject: ({ userId }) => {
return userId ? [user.id].includes(userId) : false;
ownsObject: (resource) => {
return isOwner(resource, { id: user.id, isGuest });
},
}}
>
Expand Down
13 changes: 9 additions & 4 deletions apps/web/src/contexts/permissions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const PermissionProvider = ({
export const usePermissions = () => {
const poll = usePoll();
const context = React.useContext(PermissionsContext);
const { user } = useUser();
const { user, ownsObject } = useUser();
const role = useRole();
const { participants } = useParticipants();
const isClosed = poll.closed === true || poll.event !== null;
Expand All @@ -45,10 +45,15 @@ export const usePermissions = () => {
(participant) => participant.id === participantId,
);

if (!participant) {
return false;
}

if (
participant &&
(participant.userId === user.id ||
(context.userId && participant.userId === context.userId))
ownsObject(participant) ||
(context.userId &&
(participant.userId === context.userId ||
participant.guestId === context.userId))
) {
return true;
}
Expand Down
16 changes: 13 additions & 3 deletions apps/web/src/trpc/routers/polls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ export const polls = router({
const { cursor, limit, status } = input;
const polls = await prisma.poll.findMany({
where: {
userId: ctx.user.id,
...(ctx.user.isGuest
? { guestId: ctx.user.id }
: { userId: ctx.user.id }),
deletedAt: null,
status: status === "all" ? undefined : status,
},
Expand All @@ -94,7 +96,13 @@ export const polls = router({
timeZone: true,
createdAt: true,
status: true,
userId: true,
guestId: true,
user: {
select: {
id: true,
name: true,
},
},
participants: {
where: {
deletedAt: null,
Expand Down Expand Up @@ -164,7 +172,9 @@ export const polls = router({
description: input.description,
adminUrlId: adminToken,
participantUrlId,
userId: ctx.user.id,
...(ctx.user.isGuest
? { guestId: ctx.user.id }
: { userId: ctx.user.id }),
watchers: !ctx.user.isGuest
? {
create: {
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/trpc/routers/polls/comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ export const comments = router({
content,
pollId,
authorName,
userId: ctx.user.id,
...(ctx.user.isGuest
? { guestId: ctx.user.id }
: { userId: ctx.user.id }),
},
select: {
id: true,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/trpc/routers/polls/participants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const participants = router({
pollId: pollId,
name: name,
email,
userId: user.id,
...(user.isGuest ? { guestId: user.id } : { userId: user.id }),
locale: user.locale ?? undefined,
},
include: {
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/utils/permissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function isOwner(
resource: { userId?: string | null; guestId?: string | null },
user: { id: string; isGuest: boolean },
) {
if (user.isGuest) {
return resource.guestId === user.id;
}

return resource.userId === user.id;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
-- AlterTable
ALTER TABLE "comments" ADD COLUMN "guest_id" TEXT;

-- AlterTable
ALTER TABLE "participants" ADD COLUMN "guest_id" TEXT;

-- AlterTable
ALTER TABLE "polls" ADD COLUMN "guest_id" TEXT,
ALTER COLUMN "user_id" DROP NOT NULL;

-- CreateIndex
CREATE INDEX "comments_guest_id_idx" ON "comments" USING HASH ("guest_id");

-- CreateIndex
CREATE INDEX "participants_guest_id_idx" ON "participants" USING HASH ("guest_id");

-- CreateIndex
CREATE INDEX "polls_guest_id_idx" ON "polls" USING HASH ("guest_id");

-- Migrate polls
UPDATE "polls" p
SET
"guest_id" = CASE
WHEN NOT EXISTS (SELECT 1 FROM "users" u WHERE u.id = p.user_id) THEN p.user_id
ELSE NULL
END,
"user_id" = CASE
WHEN NOT EXISTS (SELECT 1 FROM "users" u WHERE u.id = p.user_id) THEN NULL
ELSE p.user_id
END
WHERE p.user_id IS NOT NULL;

-- Migrate participants
UPDATE "participants" p
SET
"guest_id" = CASE
WHEN NOT EXISTS (SELECT 1 FROM "users" u WHERE u.id = p.user_id) THEN p.user_id
ELSE NULL
END,
"user_id" = CASE
WHEN NOT EXISTS (SELECT 1 FROM "users" u WHERE u.id = p.user_id) THEN NULL
ELSE p.user_id
END
WHERE p.user_id IS NOT NULL;

-- Migrate comments
UPDATE "comments" c
SET
"guest_id" = CASE
WHEN NOT EXISTS (SELECT 1 FROM "users" u WHERE u.id = c.user_id) THEN c.user_id
ELSE NULL
END,
"user_id" = CASE
WHEN NOT EXISTS (SELECT 1 FROM "users" u WHERE u.id = c.user_id) THEN NULL
ELSE c.user_id
END
WHERE c.user_id IS NOT NULL;
8 changes: 7 additions & 1 deletion packages/database/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ model Poll {
title String
description String?
location String?
userId String @map("user_id")
userId String? @map("user_id")
guestId String? @map("guest_id")
timeZone String? @map("time_zone")
closed Boolean @default(false) // @deprecated
status PollStatus @default(live)
Expand All @@ -147,6 +148,7 @@ model Poll {
comments Comment[]

@@index([userId], type: Hash)
@@index([guestId], type: Hash)
@@map("polls")
}

Expand Down Expand Up @@ -184,6 +186,7 @@ model Participant {
name String
email String?
userId String? @map("user_id")
guestId String? @map("guest_id")
poll Poll @relation(fields: [pollId], references: [id])
pollId String @map("poll_id")
votes Vote[]
Expand All @@ -194,6 +197,7 @@ model Participant {
deletedAt DateTime? @map("deleted_at")

@@index([pollId], type: Hash)
@@index([guestId], type: Hash)
@@map("participants")
}

Expand Down Expand Up @@ -241,10 +245,12 @@ model Comment {
authorName String @map("author_name")
user User? @relation(fields: [userId], references: [id])
userId String? @map("user_id")
guestId String? @map("guest_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime? @updatedAt @map("updated_at")

@@index([userId], type: Hash)
@@index([guestId], type: Hash)
@@index([pollId], type: Hash)
@@map("comments")
}
Expand Down
Loading