diff --git a/declarations/iron-session.d.ts b/declarations/iron-session.d.ts index c6fa8cb81..bddcf3354 100644 --- a/declarations/iron-session.d.ts +++ b/declarations/iron-session.d.ts @@ -2,7 +2,7 @@ import "iron-session"; declare module "iron-session" { export interface IronSessionData { - user?: + user: | { id: string; name: string; diff --git a/package.json b/package.json index e8acb41b1..0419a999a 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@floating-ui/react-dom-interactions": "^0.4.0", "@headlessui/react": "^1.5.0", "@next/bundle-analyzer": "^12.1.0", - "@prisma/client": "^3.14.0", + "@prisma/client": "^3.15.2", "@sentry/nextjs": "^7.0.0", "@svgr/webpack": "^6.2.1", "@tailwindcss/forms": "^0.4.0", @@ -40,7 +40,7 @@ "next-i18next": "^10.5.0", "next-plausible": "^3.1.9", "nodemailer": "^6.7.2", - "prisma": "^3.14.0", + "prisma": "^3.15.2", "react": "17.0.2", "react-big-calendar": "^0.38.9", "react-dom": "17.0.2", diff --git a/prisma/migrations/20220627191901_remove_user_relation/migration.sql b/prisma/migrations/20220627191901_remove_user_relation/migration.sql new file mode 100644 index 000000000..d0bdcb1f0 --- /dev/null +++ b/prisma/migrations/20220627191901_remove_user_relation/migration.sql @@ -0,0 +1,22 @@ +/* + Warnings: + + - You are about to drop the column `guest_id` on the `comments` table. All the data in the column will be lost. + - You are about to drop the column `guest_id` on the `participants` table. All the data in the column will be lost. + +*/ +-- Set user_id to guest_id +UPDATE "comments" +SET "user_id" = "guest_id" +WHERE "user_id" IS NULL; + +-- AlterTable +ALTER TABLE "comments" DROP COLUMN "guest_id"; + +-- Set user_id to guest_id +UPDATE "participants" +SET "user_id" = "guest_id" +WHERE "user_id" IS NULL; + +-- AlterTable +ALTER TABLE "participants" DROP COLUMN "guest_id"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f876bffb7..7508be803 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -16,8 +16,6 @@ model User { createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime? @updatedAt @map("updated_at") polls Poll[] - participants Participant[] - comments Comment[] @@map("users") } @@ -62,9 +60,7 @@ model Poll { model Participant { id String @id @default(cuid()) name String - user User? @relation(fields: [userId], references: [id]) userId String? @map("user_id") - guestId String? @map("guest_id") poll Poll @relation(fields: [pollId], references: [id]) pollId String @map("poll_id") votes Vote[] @@ -116,9 +112,7 @@ model Comment { poll Poll @relation(fields: [pollId], references: [id]) pollId String @map("poll_id") 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") diff --git a/src/components/poll-context.tsx b/src/components/poll-context.tsx index 6e44f8e6a..fb8dc0356 100644 --- a/src/components/poll-context.tsx +++ b/src/components/poll-context.tsx @@ -110,11 +110,7 @@ export const PollContextProvider: React.VoidFunctionComponent<{ const userAlreadyVoted = user && participants - ? participants.some((participant) => - user.isGuest - ? participant.guestId === user.id - : participant.userId === user.id, - ) + ? participants.some((participant) => participant.userId === user.id) : false; const optionIds = parsedOptions.options.map(({ optionId }) => optionId); diff --git a/src/components/poll/desktop-poll/participant-row.tsx b/src/components/poll/desktop-poll/participant-row.tsx index eedd42a8d..04df07b0e 100644 --- a/src/components/poll/desktop-poll/participant-row.tsx +++ b/src/components/poll/desktop-poll/participant-row.tsx @@ -113,9 +113,9 @@ const ParticipantRow: React.VoidFunctionComponent = ({ const isYou = session.user && session.ownsObject(participant) ? true : false; - const isAnonymous = !participant.userId && !participant.guestId; + const isUnclaimed = !participant.userId; - const canEdit = !poll.closed && (poll.admin || isYou || isAnonymous); + const canEdit = !poll.closed && (poll.admin || isYou || isUnclaimed); if (editMode) { return ( diff --git a/src/components/poll/mobile-poll.tsx b/src/components/poll/mobile-poll.tsx index 9ff88db6f..45a2fe3a0 100644 --- a/src/components/poll/mobile-poll.tsx +++ b/src/components/poll/mobile-poll.tsx @@ -69,10 +69,8 @@ const MobilePoll: React.VoidFunctionComponent = () => { } const { user } = session; if (user) { - const userParticipant = participants.find((participant) => - user.isGuest - ? participant.guestId === user.id - : participant.userId === user.id, + const userParticipant = participants.find( + (participant) => participant.userId === user.id, ); return userParticipant?.id; } diff --git a/src/components/session.tsx b/src/components/session.tsx index 00e266e59..cf7e1a7b2 100644 --- a/src/components/session.tsx +++ b/src/components/session.tsx @@ -9,12 +9,11 @@ import { useRequiredContext } from "./use-required-context"; export type UserSessionData = NonNullable; export type SessionProps = { - user: UserSessionData | null; + user: UserSessionData; }; type ParticipantOrComment = { userId: string | null; - guestId: string | null; }; export type UserSessionDataExtended = UserSessionData & { @@ -36,35 +35,33 @@ SessionContext.displayName = "SessionContext"; export const SessionProvider: React.VoidFunctionComponent<{ children?: React.ReactNode; - session: UserSessionData | null; -}> = ({ children, session }) => { + defaultUser: UserSessionData; +}> = ({ children, defaultUser }) => { const queryClient = trpc.useContext(); const { - data: user = session, + data: user = defaultUser, refetch, isLoading, } = trpc.useQuery(["session.get"]); const logout = trpc.useMutation(["session.destroy"], { onSuccess: () => { - queryClient.setQueryData(["session.get"], null); + queryClient.invalidateQueries(["session.get"]); }, }); const sessionData: SessionContextValue = { - user: user - ? { - ...user, - shortName: - // try to get the first name in the event - // that the user entered a full name - user.isGuest - ? user.id.substring(0, 12) - : user.name.length > 12 && user.name.indexOf(" ") !== -1 - ? user.name.substring(0, user.name.indexOf(" ")) - : user.name, - } - : null, + user: { + ...user, + shortName: + // try to get the first name in the event + // that the user entered a full name + user.isGuest + ? user.id.substring(0, 10) + : user.name.length > 12 && user.name.indexOf(" ") !== -1 + ? user.name.substring(0, user.name.indexOf(" ")) + : user.name, + }, refresh: () => { refetch(); }, @@ -77,14 +74,6 @@ export const SessionProvider: React.VoidFunctionComponent<{ }); }, ownsObject: (obj) => { - if (!user) { - return false; - } - - if (user.isGuest) { - return obj.guestId === user.id; - } - return obj.userId === user.id; }, }; @@ -106,7 +95,7 @@ export const withSession =

( const ComposedComponent: React.VoidFunctionComponent

= (props: P) => { const Component = component; return ( - + ); @@ -115,5 +104,4 @@ export const withSession =

( return ComposedComponent; }; -export const isUnclaimed = (obj: ParticipantOrComment) => - !obj.guestId && !obj.userId; +export const isUnclaimed = (obj: ParticipantOrComment) => !obj.userId; diff --git a/src/pages/new.tsx b/src/pages/new.tsx index 1cd5c87ed..30dfa92c0 100644 --- a/src/pages/new.tsx +++ b/src/pages/new.tsx @@ -2,26 +2,20 @@ import { GetServerSideProps } from "next"; import dynamic from "next/dynamic"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; -import { CreatePollPageProps } from "@/components/create-poll"; -import { withSessionSsr } from "@/utils/auth"; +import { withSessionSsr } from "../utils/auth"; -const getProps: GetServerSideProps = async ({ - locale = "en", - query, - req, -}) => { - return { - props: { - ...(await serverSideTranslations(locale, ["app"])), - ...query, - user: req.session.user ?? null, - }, - }; -}; +export const getServerSideProps: GetServerSideProps = withSessionSsr( + async ({ locale = "en", query, req }) => { + return { + props: { + ...(await serverSideTranslations(locale, ["app"])), + ...query, + user: req.session.user ?? null, + }, + }; + }, +); -export const getServerSideProps = withSessionSsr(getProps); - -// We disable SSR because the data on this page relies on sessionStore export default dynamic(() => import("@/components/create-poll"), { ssr: false, }); diff --git a/src/pages/profile.tsx b/src/pages/profile.tsx index 689e28541..c56a087cd 100644 --- a/src/pages/profile.tsx +++ b/src/pages/profile.tsx @@ -11,7 +11,7 @@ import StandardLayout from "../components/standard-layout"; const Page: NextPage<{ user: UserSessionData }> = ({ user }) => { const name = user.isGuest ? user.id : user.name; return ( - + Profile - {name} diff --git a/src/server/routers/login.ts b/src/server/routers/login.ts index c4412c0f2..350f8022b 100644 --- a/src/server/routers/login.ts +++ b/src/server/routers/login.ts @@ -13,9 +13,11 @@ export const login = createRouter().mutation("login", { resolve: async ({ ctx, input }) => { const { email, path } = input; const homePageUrl = absoluteUrl(); + const user = ctx.session.user; + const token = await createToken({ email, - guestId: ctx.session.user?.isGuest ? ctx.session.user.id : undefined, + guestId: user.id, path, }); diff --git a/src/server/routers/polls/comments.ts b/src/server/routers/polls/comments.ts index f0db7ffa4..e8827b2e7 100644 --- a/src/server/routers/polls/comments.ts +++ b/src/server/routers/polls/comments.ts @@ -3,7 +3,6 @@ import { z } from "zod"; import { prisma } from "~/prisma/db"; import { sendNotification } from "../../../utils/api-utils"; -import { createGuestUser } from "../../../utils/auth"; import { createRouter } from "../../createRouter"; export const comments = createRouter() @@ -29,17 +28,14 @@ export const comments = createRouter() content: z.string(), }), resolve: async ({ ctx, input: { pollId, authorName, content } }) => { - if (!ctx.session.user) { - await createGuestUser(ctx.session); - } + const user = ctx.session.user; const newComment = await prisma.comment.create({ data: { content, pollId, authorName, - userId: ctx.session.user?.isGuest ? undefined : ctx.session.user?.id, - guestId: ctx.session.user?.isGuest ? ctx.session.user.id : undefined, + userId: user.id, }, }); diff --git a/src/server/routers/polls/demo.ts b/src/server/routers/polls/demo.ts index 8eebccddf..4993777c7 100644 --- a/src/server/routers/polls/demo.ts +++ b/src/server/routers/polls/demo.ts @@ -42,7 +42,7 @@ export const demo = createRouter().mutation("create", { const participants: Array<{ name: string; id: string; - guestId: string; + userId: string; createdAt: Date; }> = []; @@ -58,7 +58,7 @@ export const demo = createRouter().mutation("create", { participants.push({ id: participantId, name, - guestId: "user-demo", + userId: "user-demo", createdAt: addMinutes(today, i * -1), }); diff --git a/src/server/routers/polls/participants.ts b/src/server/routers/polls/participants.ts index 7a1754f1a..2ab3dad04 100644 --- a/src/server/routers/polls/participants.ts +++ b/src/server/routers/polls/participants.ts @@ -3,7 +3,6 @@ import { z } from "zod"; import { prisma } from "~/prisma/db"; import { sendNotification } from "../../../utils/api-utils"; -import { createGuestUser } from "../../../utils/auth"; import { createRouter } from "../../createRouter"; export const participants = createRouter() @@ -57,22 +56,12 @@ export const participants = createRouter() .array(), }), resolve: async ({ ctx, input: { pollId, votes, name } }) => { - if (!ctx.session.user) { - await createGuestUser(ctx.session); - } - + const user = ctx.session.user; const participant = await prisma.participant.create({ data: { pollId: pollId, name: name, - userId: - ctx.session.user?.isGuest === false - ? ctx.session.user.id - : undefined, - guestId: - ctx.session.user?.isGuest === true - ? ctx.session.user.id - : undefined, + userId: user.id, votes: { createMany: { data: votes.map(({ optionId, type }) => ({ diff --git a/src/server/routers/session.ts b/src/server/routers/session.ts index 9de43fc03..9077ea925 100644 --- a/src/server/routers/session.ts +++ b/src/server/routers/session.ts @@ -1,30 +1,8 @@ -import { prisma } from "~/prisma/db"; - import { createRouter } from "../createRouter"; export const session = createRouter() .query("get", { async resolve({ ctx }) { - if (ctx.session.user?.isGuest === false) { - const user = await prisma.user.findUnique({ - where: { id: ctx.session.user.id }, - }); - - if (!user) { - ctx.session.destroy(); - return null; - } - - ctx.session.user = { - id: user.id, - name: user.name, - email: user.email, - isGuest: false, - }; - - await ctx.session.save(); - } - return ctx.session.user; }, }) diff --git a/src/utils/auth.ts b/src/utils/auth.ts index b3782a477..15dc794a7 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -1,9 +1,4 @@ -import { - IronSession, - IronSessionOptions, - sealData, - unsealData, -} from "iron-session"; +import { IronSessionOptions, sealData, unsealData } from "iron-session"; import { withIronSessionApiRoute, withIronSessionSsr } from "iron-session/next"; import { GetServerSideProps, NextApiHandler } from "next"; @@ -21,11 +16,24 @@ const sessionOptions: IronSessionOptions = { }; export function withSessionRoute(handler: NextApiHandler) { - return withIronSessionApiRoute(handler, sessionOptions); + return withIronSessionApiRoute(async (req, res) => { + if (!req.session.user) { + req.session.user = await createGuestUser(); + await req.session.save(); + } + return await handler(req, res); + }, sessionOptions); } export function withSessionSsr(handler: GetServerSideProps) { - return withIronSessionSsr(handler, sessionOptions); + return withIronSessionSsr(async (context) => { + const { req } = context; + if (!req.session.user) { + req.session.user = await createGuestUser(); + await req.session.save(); + } + return await handler(context); + }, sessionOptions); } export const decryptToken = async

>( @@ -43,12 +51,14 @@ export const createToken = async >( }); }; -export const createGuestUser = async (session: IronSession) => { - session.user = { +const createGuestUser = async (): Promise<{ + isGuest: true; + id: string; +}> => { + return { id: `user-${await randomid()}`, isGuest: true, }; - await session.save(); }; // assigns participants and comments created by guests to a user @@ -60,24 +70,22 @@ export const mergeGuestsIntoUser = async ( ) => { await prisma.participant.updateMany({ where: { - guestId: { + userId: { in: guestIds, }, }, data: { - guestId: null, userId: userId, }, }); await prisma.comment.updateMany({ where: { - guestId: { + userId: { in: guestIds, }, }, data: { - guestId: null, userId: userId, }, }); diff --git a/yarn.lock b/yarn.lock index b5994ce9d..b655c9ffe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1299,22 +1299,22 @@ resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz" integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q== -"@prisma/client@^3.14.0": - version "3.14.0" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.14.0.tgz#bb90405c012fcca11f4647d91153ed4c58f3bd48" - integrity sha512-atb41UpgTR1MCst0VIbiHTMw8lmXnwUvE1KyUCAkq08+wJyjRE78Due+nSf+7uwqQn+fBFYVmoojtinhlLOSaA== +"@prisma/client@^3.15.2": + version "3.15.2" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.15.2.tgz#2181398147afc79bfe0d83c03a88dc45b49bd365" + integrity sha512-ErqtwhX12ubPhU4d++30uFY/rPcyvjk+mdifaZO5SeM21zS3t4jQrscy8+6IyB0GIYshl5ldTq6JSBo1d63i8w== dependencies: - "@prisma/engines-version" "3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a" + "@prisma/engines-version" "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e" -"@prisma/engines-version@3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a": - version "3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a" - resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a.tgz#4edae57cf6527f35e22cebe75e49214fc0e99ac9" - integrity sha512-D+yHzq4a2r2Rrd0ZOW/mTZbgDIkUkD8ofKgusEI1xPiZz60Daks+UM7Me2ty5FzH3p/TgyhBpRrfIHx+ha20RQ== +"@prisma/engines-version@3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e": + version "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e.tgz#bf5e2373ca68ce7556b967cb4965a7095e93fe53" + integrity sha512-e3k2Vd606efd1ZYy2NQKkT4C/pn31nehyLhVug6To/q8JT8FpiMrDy7zmm3KLF0L98NOQQcutaVtAPhzKhzn9w== -"@prisma/engines@3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a": - version "3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a" - resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a.tgz#7fa11bc26a51d450185c816cc0ab8cac673fb4bf" - integrity sha512-LwZvI3FY6f43xFjQNRuE10JM5R8vJzFTSmbV9X0Wuhv9kscLkjRlZt0BEoiHmO+2HA3B3xxbMfB5du7ZoSFXGg== +"@prisma/engines@3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e": + version "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e.tgz#f691893df506b93e3cb1ccc15ec6e5ac64e8e570" + integrity sha512-NHlojO1DFTsSi3FtEleL9QWXeSF/UjhCW0fgpi7bumnNZ4wj/eQ+BJJ5n2pgoOliTOGv9nX2qXvmHap7rJMNmg== "@restart/hooks@^0.3.25": version "0.3.26" @@ -4503,12 +4503,12 @@ prettier@^2.3.0: resolved "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz" integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w== -prisma@^3.14.0: - version "3.14.0" - resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.14.0.tgz#dd67ece37d7b5373e9fd9588971de0024b49be81" - integrity sha512-l9MOgNCn/paDE+i1K2fp9NZ+Du4trzPTJsGkaQHVBufTGqzoYHuNk8JfzXuIn0Gte6/ZjyKj652Jq/Lc1tp2yw== +prisma@^3.15.2: + version "3.15.2" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.15.2.tgz#4ebe32fb284da3ac60c49fbc16c75e56ecf32067" + integrity sha512-nMNSMZvtwrvoEQ/mui8L/aiCLZRCj5t6L3yujKpcDhIPk7garp8tL4nMx2+oYsN0FWBacevJhazfXAbV1kfBzA== dependencies: - "@prisma/engines" "3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a" + "@prisma/engines" "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e" process-nextick-args@~2.0.0: version "2.0.1"