♻️ Update trpcs routes (#531)

This commit is contained in:
Luke Vella 2023-02-27 15:08:59 +00:00 committed by GitHub
parent d9f6a0d097
commit 18eca7cd8c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 300 additions and 317 deletions

View file

@ -27,10 +27,10 @@
"@svgr/webpack": "^6.5.1", "@svgr/webpack": "^6.5.1",
"@tailwindcss/typography": "^0.5.9", "@tailwindcss/typography": "^0.5.9",
"@tanstack/react-query": "^4.22.0", "@tanstack/react-query": "^4.22.0",
"@trpc/client": "^10.0.0-rc.8", "@trpc/client": "^10.13.0",
"@trpc/next": "^10.0.0-rc.8", "@trpc/next": "^10.13.0",
"@trpc/react-query": "^10.0.0-rc.8", "@trpc/react-query": "^10.13.0",
"@trpc/server": "^10.0.0-rc.8", "@trpc/server": "^10.13.0",
"@vercel/analytics": "^0.1.8", "@vercel/analytics": "^0.1.8",
"accept-language-parser": "^1.5.0", "accept-language-parser": "^1.5.0",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",

View file

@ -23,10 +23,7 @@ export const softDeleteMiddleware = (
params.args["data"] = { deleted: true, deletedAt: new Date() }; params.args["data"] = { deleted: true, deletedAt: new Date() };
} }
} }
if (params.action === "findUnique" || params.action === "findFirst") { if (params.action === "findFirst") {
// Change to findFirst - you cannot filter
// by anything except ID / unique with findUnique
params.action = "findFirst";
// Add 'deleted' filter // Add 'deleted' filter
// ID filter maintained // ID filter maintained
params.args.where["deleted"] = params.args.where["deleted"] || false; params.args.where["deleted"] = params.args.where["deleted"] || false;

View file

@ -9,7 +9,7 @@ import { useMount } from "react-use";
import { Button } from "@/components/button"; import { Button } from "@/components/button";
import Share from "@/components/icons/share.svg"; import Share from "@/components/icons/share.svg";
import { trpc, trpcNext } from "../utils/trpc"; import { trpc } from "../utils/trpc";
import { useParticipants } from "./participants-provider"; import { useParticipants } from "./participants-provider";
import ManagePoll from "./poll/manage-poll"; import ManagePoll from "./poll/manage-poll";
import { useUpdatePollMutation } from "./poll/mutations"; import { useUpdatePollMutation } from "./poll/mutations";
@ -25,7 +25,7 @@ export const AdminControls = (props: { children?: React.ReactNode }) => {
const router = useRouter(); const router = useRouter();
const queryClient = trpcNext.useContext(); const queryClient = trpc.useContext();
const session = useUser(); const session = useUser();
@ -48,10 +48,10 @@ export const AdminControls = (props: { children?: React.ReactNode }) => {
} }
}, [urlId, router, updatePollMutation, t]); }, [urlId, router, updatePollMutation, t]);
const verifyEmail = trpc.useMutation(["polls.verification.verify"], { const verifyEmail = trpc.polls.verification.verify.useMutation({
onSuccess: () => { onSuccess: () => {
toast.success(t("pollHasBeenVerified")); toast.success(t("pollHasBeenVerified"));
queryClient.poll.invalidate(); queryClient.polls.invalidate();
session.refresh(); session.refresh();
posthog.capture("verified email"); posthog.capture("verified email");
}, },

View file

@ -5,7 +5,7 @@ import React from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { requiredString, validEmail } from "../../utils/form-validation"; import { requiredString, validEmail } from "../../utils/form-validation";
import { trpcNext } from "../../utils/trpc"; import { trpc } from "../../utils/trpc";
import { Button } from "../button"; import { Button } from "../button";
import { TextInput } from "../text-input"; import { TextInput } from "../text-input";
@ -133,9 +133,9 @@ export const RegisterForm: React.VoidFunctionComponent<{
useForm<RegisterFormData>({ useForm<RegisterFormData>({
defaultValues, defaultValues,
}); });
const requestRegistration = trpcNext.auth.requestRegistration.useMutation(); const requestRegistration = trpc.auth.requestRegistration.useMutation();
const authenticateRegistration = const authenticateRegistration =
trpcNext.auth.authenticateRegistration.useMutation(); trpc.auth.authenticateRegistration.useMutation();
const [token, setToken] = React.useState<string>(); const [token, setToken] = React.useState<string>();
if (token) { if (token) {
@ -273,8 +273,8 @@ export const LoginForm: React.VoidFunctionComponent<{
const { t } = useTranslation("app"); const { t } = useTranslation("app");
const { register, handleSubmit, getValues, formState, setError } = const { register, handleSubmit, getValues, formState, setError } =
useForm<{ email: string }>(); useForm<{ email: string }>();
const requestLogin = trpcNext.auth.requestLogin.useMutation(); const requestLogin = trpc.auth.requestLogin.useMutation();
const authenticateLogin = trpcNext.auth.authenticateLogin.useMutation(); const authenticateLogin = trpc.auth.authenticateLogin.useMutation();
const [token, setToken] = React.useState<string>(); const [token, setToken] = React.useState<string>();

View file

@ -90,7 +90,7 @@ const Page: NextPage<CreatePollPageProps> = ({
const [isRedirecting, setIsRedirecting] = React.useState(false); const [isRedirecting, setIsRedirecting] = React.useState(false);
const createPoll = trpc.useMutation(["polls.create"], { const createPoll = trpc.polls.create.useMutation({
onSuccess: (res) => { onSuccess: (res) => {
setIsRedirecting(true); setIsRedirecting(true);
posthog.capture("created poll", { posthog.capture("created poll", {

View file

@ -31,19 +31,20 @@ const Discussion: React.VoidFunctionComponent = () => {
const pollId = poll.id; const pollId = poll.id;
const { data: comments } = trpc.useQuery( const { data: comments } = trpc.polls.comments.list.useQuery(
["polls.comments.list", { pollId }], { pollId },
{ {
refetchInterval: 10000, // refetch every 10 seconds refetchInterval: 10000, // refetch every 10 seconds
trpc: {},
}, },
); );
const addComment = trpc.useMutation("polls.comments.add", { const addComment = trpc.polls.comments.add.useMutation({
onSuccess: (newComment) => { onSuccess: (newComment) => {
posthog.capture("created comment"); posthog.capture("created comment");
queryClient.setQueryData( queryClient.polls.comments.list.setData(
["polls.comments.list", { pollId }], { pollId },
(existingComments = []) => { (existingComments = []) => {
return [...existingComments, newComment]; return [...existingComments, newComment];
}, },
@ -51,10 +52,10 @@ const Discussion: React.VoidFunctionComponent = () => {
}, },
}); });
const deleteComment = trpc.useMutation("polls.comments.delete", { const deleteComment = trpc.polls.comments.delete.useMutation({
onMutate: ({ commentId }) => { onMutate: ({ commentId }) => {
queryClient.setQueryData( queryClient.polls.comments.list.setData(
["polls.comments.list", { pollId }], { pollId },
(existingComments = []) => { (existingComments = []) => {
return [...existingComments].filter(({ id }) => id !== commentId); return [...existingComments].filter(({ id }) => id !== commentId);
}, },

View file

@ -22,10 +22,7 @@ export const ParticipantsProvider: React.VoidFunctionComponent<{
}> = ({ children, pollId }) => { }> = ({ children, pollId }) => {
const { t } = useTranslation("app"); const { t } = useTranslation("app");
const { data: participants } = trpc.useQuery([ const { data: participants } = trpc.polls.participants.list.useQuery({ pollId });
"polls.participants.list",
{ pollId },
]);
const getParticipants = ( const getParticipants = (
optionId: string, optionId: string,

View file

@ -28,8 +28,6 @@ type PollContextValue = {
setTargetTimeZone: (timeZone: string) => void; setTargetTimeZone: (timeZone: string) => void;
pollType: "date" | "timeSlot"; pollType: "date" | "timeSlot";
highScore: number; highScore: number;
isDeleted: boolean;
setDeleted: React.Dispatch<React.SetStateAction<boolean>>;
optionIds: string[]; optionIds: string[];
// TODO (Luke Vella) [2022-05-18]: Move this stuff to participants provider // TODO (Luke Vella) [2022-05-18]: Move this stuff to participants provider
getParticipantsWhoVotedForOption: (optionId: string) => Participant[]; // maybe just attach votes to parsed options getParticipantsWhoVotedForOption: (optionId: string) => Participant[]; // maybe just attach votes to parsed options
@ -60,7 +58,6 @@ export const PollContextProvider: React.VoidFunctionComponent<{
}> = ({ poll, urlId, admin, children }) => { }> = ({ poll, urlId, admin, children }) => {
const { t } = useTranslation("app"); const { t } = useTranslation("app");
const { participants } = useParticipants(); const { participants } = useParticipants();
const [isDeleted, setDeleted] = React.useState(false);
const { user } = useUser(); const { user } = useUser();
const [targetTimeZone, setTargetTimeZone] = const [targetTimeZone, setTargetTimeZone] =
React.useState(getBrowserTimeZone); React.useState(getBrowserTimeZone);
@ -159,13 +156,10 @@ export const PollContextProvider: React.VoidFunctionComponent<{
...parsedOptions, ...parsedOptions,
targetTimeZone, targetTimeZone,
setTargetTimeZone, setTargetTimeZone,
isDeleted,
setDeleted,
}; };
}, [ }, [
admin, admin,
getScore, getScore,
isDeleted,
participants, participants,
poll, poll,
targetTimeZone, targetTimeZone,
@ -174,7 +168,7 @@ export const PollContextProvider: React.VoidFunctionComponent<{
user, user,
]); ]);
if (isDeleted) { if (poll.deleted) {
return ( return (
<ErrorPage <ErrorPage
icon={Trash} icon={Trash}

View file

@ -27,8 +27,7 @@ const ManagePoll: React.VoidFunctionComponent<{
placement?: Placement; placement?: Placement;
}> = ({ placement }) => { }> = ({ placement }) => {
const { t } = useTranslation("app"); const { t } = useTranslation("app");
const { poll, getParticipantsWhoVotedForOption, setDeleted, urlId } = const { poll, getParticipantsWhoVotedForOption, urlId } = usePoll();
usePoll();
const { exportToCsv } = useCsvExporter(); const { exportToCsv } = useCsvExporter();
@ -218,7 +217,6 @@ const ManagePoll: React.VoidFunctionComponent<{
<DeletePollForm <DeletePollForm
onConfirm={async () => { onConfirm={async () => {
close(); close();
setDeleted(true);
}} }}
onCancel={close} onCancel={close}
urlId={urlId} urlId={urlId}

View file

@ -21,7 +21,7 @@ export const DeletePollForm: React.VoidFunctionComponent<{
const confirmationText = watch("confirmation"); const confirmationText = watch("confirmation");
const canDelete = confirmationText === confirmText; const canDelete = confirmationText === confirmText;
const deletePoll = trpc.useMutation("polls.delete", { const deletePoll = trpc.polls.delete.useMutation({
onSuccess: () => { onSuccess: () => {
posthog.capture("deleted poll"); posthog.capture("deleted poll");
}, },

View file

@ -1,6 +1,6 @@
import posthog from "posthog-js"; import posthog from "posthog-js";
import { trpc, trpcNext } from "../../utils/trpc"; import { trpc } from "../../utils/trpc";
import { ParticipantForm } from "./types"; import { ParticipantForm } from "./types";
export const normalizeVotes = ( export const normalizeVotes = (
@ -14,32 +14,24 @@ export const normalizeVotes = (
}; };
export const useAddParticipantMutation = () => { export const useAddParticipantMutation = () => {
const queryClient = trpc.useContext(); return trpc.polls.participants.add.useMutation({
return trpc.useMutation(["polls.participants.add"], {
onSuccess: (participant) => { onSuccess: (participant) => {
posthog.capture("add participant", { posthog.capture("add participant", {
name: participant.name, name: participant.name,
}); });
queryClient.setQueryData(
["polls.participants.list", { pollId: participant.pollId }],
(existingParticipants = []) => {
return [participant, ...existingParticipants];
},
);
}, },
}); });
}; };
export const useUpdateParticipantMutation = () => { export const useUpdateParticipantMutation = () => {
const queryClient = trpc.useContext(); const queryClient = trpc.useContext();
return trpc.useMutation("polls.participants.update", { return trpc.polls.participants.update.useMutation({
onSuccess: (participant) => { onSuccess: (participant) => {
posthog.capture("update participant", { posthog.capture("update participant", {
name: participant.name, name: participant.name,
}); });
queryClient.setQueryData( queryClient.polls.participants.list.setData(
["polls.participants.list", { pollId: participant.pollId }], { pollId: participant.pollId },
(existingParticipants = []) => { (existingParticipants = []) => {
const newParticipants = [...existingParticipants]; const newParticipants = [...existingParticipants];
@ -60,10 +52,10 @@ export const useUpdateParticipantMutation = () => {
export const useDeleteParticipantMutation = () => { export const useDeleteParticipantMutation = () => {
const queryClient = trpc.useContext(); const queryClient = trpc.useContext();
return trpc.useMutation("polls.participants.delete", { return trpc.polls.participants.delete.useMutation({
onMutate: ({ participantId, pollId }) => { onMutate: ({ participantId, pollId }) => {
queryClient.setQueryData( queryClient.polls.participants.list.setData(
["polls.participants.list", { pollId: pollId }], { pollId: pollId },
(existingParticipants = []) => { (existingParticipants = []) => {
return existingParticipants.filter(({ id }) => id !== participantId); return existingParticipants.filter(({ id }) => id !== participantId);
}, },
@ -78,10 +70,10 @@ export const useDeleteParticipantMutation = () => {
}; };
export const useUpdatePollMutation = () => { export const useUpdatePollMutation = () => {
const queryClient = trpcNext.useContext(); const queryClient = trpc.useContext();
return trpc.useMutation(["polls.update"], { return trpc.polls.update.useMutation({
onSuccess: (data) => { onSuccess: (data) => {
queryClient.poll.invalidate(); queryClient.polls.invalidate();
posthog.capture("updated poll", { posthog.capture("updated poll", {
id: data.id, id: data.id,
}); });

View file

@ -7,9 +7,8 @@ import { usePoll } from "../poll-context";
export const UnverifiedPollNotice = () => { export const UnverifiedPollNotice = () => {
const { t } = useTranslation("app"); const { t } = useTranslation("app");
const { poll } = usePoll(); const { poll } = usePoll();
const requestVerificationEmail = trpc.useMutation( const requestVerificationEmail = trpc.polls.verification.request.useMutation(
"polls.verification.request", );
);
return ( return (
<div className="space-y-3 rounded-md border border-amber-200 bg-amber-100 p-3 text-gray-700 shadow-sm"> <div className="space-y-3 rounded-md border border-amber-200 bg-amber-100 p-3 text-gray-700 shadow-sm">

View file

@ -7,7 +7,7 @@ import { trpc } from "../../utils/trpc";
* find polls that haven't been accessed for some time so that they can be deleted by house keeping. * find polls that haven't been accessed for some time so that they can be deleted by house keeping.
*/ */
export const useTouchBeacon = (pollId: string) => { export const useTouchBeacon = (pollId: string) => {
const touchMutation = trpc.useMutation(["polls.touch"]); const touchMutation = trpc.polls.touch.useMutation();
useMount(() => { useMount(() => {
touchMutation.mutate({ pollId }); touchMutation.mutate({ pollId });

View file

@ -19,7 +19,7 @@ export const Profile: React.VoidFunctionComponent = () => {
const { dayjs } = useDayjs(); const { dayjs } = useDayjs();
const { t } = useTranslation("app"); const { t } = useTranslation("app");
const { data: userPolls } = trpc.useQuery(["user.getPolls"]); const { data: userPolls } = trpc.user.getPolls.useQuery();
const createdPolls = userPolls?.polls; const createdPolls = userPolls?.polls;
const router = useRouter(); const router = useRouter();

View file

@ -33,7 +33,7 @@ export const UserDetails: React.VoidFunctionComponent<UserDetailsProps> = ({
const { refresh } = useUser(); const { refresh } = useUser();
const changeName = trpc.useMutation("user.changeName", { const changeName = trpc.user.changeName.useMutation({
onSuccess: (_, { name }) => { onSuccess: (_, { name }) => {
posthog.people.set({ name }); posthog.people.set({ name });
refresh(); refresh();

View file

@ -5,7 +5,7 @@ import { useMount } from "react-use";
import { UserSession } from "@/utils/auth"; import { UserSession } from "@/utils/auth";
import { trpcNext } from "../utils/trpc"; import { trpc } from "../utils/trpc";
import { useRequiredContext } from "./use-required-context"; import { useRequiredContext } from "./use-required-context";
export const UserContext = export const UserContext =
@ -51,12 +51,12 @@ export const IfGuest = (props: { children?: React.ReactNode }) => {
export const UserProvider = (props: { children?: React.ReactNode }) => { export const UserProvider = (props: { children?: React.ReactNode }) => {
const { t } = useTranslation("app"); const { t } = useTranslation("app");
const queryClient = trpcNext.useContext(); const queryClient = trpc.useContext();
const { data: user } = trpcNext.whoami.get.useQuery(); const { data: user } = trpc.whoami.get.useQuery();
const [isUpdating, setIsUpdating] = React.useState(false); const [isUpdating, setIsUpdating] = React.useState(false);
const logout = trpcNext.whoami.destroy.useMutation({ const logout = trpc.whoami.destroy.useMutation({
onSuccess: async () => { onSuccess: async () => {
setIsUpdating(true); setIsUpdating(true);
await queryClient.whoami.invalidate(); await queryClient.whoami.invalidate();

View file

@ -18,7 +18,7 @@ import { useCrispChat } from "../components/crisp-chat";
import { NextPageWithLayout } from "../types"; import { NextPageWithLayout } from "../types";
import { absoluteUrl } from "../utils/absolute-url"; import { absoluteUrl } from "../utils/absolute-url";
import { UserSession } from "../utils/auth"; import { UserSession } from "../utils/auth";
import { trpcNext } from "../utils/trpc"; import { trpc } from "../utils/trpc";
const inter = Inter({ const inter = Inter({
subsets: ["latin"], subsets: ["latin"],
@ -93,4 +93,4 @@ const MyApp: NextPage<AppPropsWithLayout> = ({ Component, pageProps }) => {
); );
}; };
export default trpcNext.withTRPC(appWithTranslation(MyApp)); export default trpc.withTRPC(appWithTranslation(MyApp));

View file

@ -8,7 +8,7 @@ import { ParticipantsProvider } from "@/components/participants-provider";
import { Poll } from "@/components/poll"; import { Poll } from "@/components/poll";
import { PollContextProvider } from "@/components/poll-context"; import { PollContextProvider } from "@/components/poll-context";
import { withSessionSsr } from "@/utils/auth"; import { withSessionSsr } from "@/utils/auth";
import { trpcNext } from "@/utils/trpc"; import { trpc } from "@/utils/trpc";
import { withPageTranslations } from "@/utils/with-page-translations"; import { withPageTranslations } from "@/utils/with-page-translations";
import { AdminControls } from "../../components/admin-control"; import { AdminControls } from "../../components/admin-control";
@ -18,7 +18,7 @@ import { NextPageWithLayout } from "../../types";
const Page: NextPageWithLayout<{ urlId: string }> = ({ urlId }) => { const Page: NextPageWithLayout<{ urlId: string }> = ({ urlId }) => {
const { t } = useTranslation("app"); const { t } = useTranslation("app");
const pollQuery = trpcNext.poll.getByAdminUrlId.useQuery({ urlId }); const pollQuery = trpc.polls.getByAdminUrlId.useQuery({ urlId });
const poll = pollQuery.data; const poll = pollQuery.data;
@ -62,7 +62,7 @@ export const getServerSideProps: GetServerSideProps = withSessionSsr(
], ],
{ {
onPrefetch: async (ssg, ctx) => { onPrefetch: async (ssg, ctx) => {
await ssg.poll.getByAdminUrlId.fetch({ await ssg.polls.getByAdminUrlId.fetch({
urlId: ctx.params?.urlId as string, urlId: ctx.params?.urlId as string,
}); });
}, },

View file

@ -15,7 +15,7 @@ const Demo: NextPage = () => {
const { t } = useTranslation("app"); const { t } = useTranslation("app");
const router = useRouter(); const router = useRouter();
const createDemo = trpc.useMutation(["polls.demo.create"]); const createDemo = trpc.polls.demo.create.useMutation();
useMount(async () => { useMount(async () => {
const urlId = await createDemo.mutateAsync(); const urlId = await createDemo.mutateAsync();

View file

@ -8,7 +8,7 @@ import { Poll } from "@/components/poll";
import { PollContextProvider } from "@/components/poll-context"; import { PollContextProvider } from "@/components/poll-context";
import { useUser } from "@/components/user-provider"; import { useUser } from "@/components/user-provider";
import { withSessionSsr } from "@/utils/auth"; import { withSessionSsr } from "@/utils/auth";
import { trpcNext } from "@/utils/trpc"; import { trpc } from "@/utils/trpc";
import { withPageTranslations } from "@/utils/with-page-translations"; import { withPageTranslations } from "@/utils/with-page-translations";
import StandardLayout from "../../components/layouts/standard-layout"; import StandardLayout from "../../components/layouts/standard-layout";
@ -16,7 +16,7 @@ import ModalProvider from "../../components/modal/modal-provider";
import { NextPageWithLayout } from "../../types"; import { NextPageWithLayout } from "../../types";
const Page: NextPageWithLayout<{ urlId: string }> = ({ urlId }) => { const Page: NextPageWithLayout<{ urlId: string }> = ({ urlId }) => {
const pollQuery = trpcNext.poll.getByParticipantUrlId.useQuery({ urlId }); const pollQuery = trpc.polls.getByParticipantUrlId.useQuery({ urlId });
const { user } = useUser(); const { user } = useUser();
const poll = pollQuery.data; const poll = pollQuery.data;
@ -70,7 +70,7 @@ export const getServerSideProps: GetServerSideProps = withSessionSsr(
], ],
{ {
onPrefetch: async (ssg, ctx) => { onPrefetch: async (ssg, ctx) => {
await ssg.poll.getByParticipantUrlId.fetch({ await ssg.polls.getByParticipantUrlId.fetch({
urlId: ctx.params?.urlId as string, urlId: ctx.params?.urlId as string,
}); });
}, },

View file

@ -1,8 +0,0 @@
import * as trpc from "@trpc/server";
import { Context } from "./context";
// Helper function to create a router with your app's context
export function createRouter() {
return trpc.router<Context>();
}

View file

@ -1,22 +1,17 @@
import { createRouter } from "../createRouter";
import { mergeRouters, router } from "../trpc"; import { mergeRouters, router } from "../trpc";
import { auth } from "./auth"; import { auth } from "./auth";
import { login } from "./login"; import { login } from "./login";
import { legacyPolls, poll } from "./polls"; import { polls } from "./polls";
import { user } from "./user"; import { user } from "./user";
import { whoami } from "./whoami"; import { whoami } from "./whoami";
const legacyRouter = createRouter()
.merge("user.", user)
.merge(login)
.merge("polls.", legacyPolls);
export const appRouter = mergeRouters( export const appRouter = mergeRouters(
legacyRouter.interop(),
router({ router({
whoami, whoami,
auth, auth,
poll, polls,
user,
login,
}), }),
); );

View file

@ -5,35 +5,38 @@ import loginTemplate from "~/templates/login";
import { absoluteUrl } from "../../utils/absolute-url"; import { absoluteUrl } from "../../utils/absolute-url";
import { sendEmailTemplate } from "../../utils/api-utils"; import { sendEmailTemplate } from "../../utils/api-utils";
import { createToken } from "../../utils/auth"; import { createToken } from "../../utils/auth";
import { createRouter } from "../createRouter"; import { publicProcedure, router } from "../trpc";
export const login = createRouter().mutation("login", { export const login = router({
input: z.object({ login: publicProcedure
email: z.string(), .input(
path: z.string(), z.object({
}), email: z.string(),
resolve: async ({ ctx, input }) => { path: z.string(),
const { email, path } = input; }),
const homePageUrl = absoluteUrl(); )
const user = ctx.session.user; .mutation(async ({ ctx, input }) => {
const { email, path } = input;
const homePageUrl = absoluteUrl();
const user = ctx.session.user;
const token = await createToken({ const token = await createToken({
email, email,
guestId: user.id, guestId: user.id,
path, path,
}); });
const loginUrl = `${homePageUrl}/login?code=${token}`; const loginUrl = `${homePageUrl}/login?code=${token}`;
await sendEmailTemplate({ await sendEmailTemplate({
templateString: loginTemplate, templateString: loginTemplate,
to: email, to: email,
subject: "Rallly - Login", subject: "Rallly - Login",
templateVars: { templateVars: {
loginUrl, loginUrl,
homePageUrl, homePageUrl,
supportEmail: process.env.SUPPORT_EMAIL, supportEmail: process.env.SUPPORT_EMAIL,
}, },
}); });
}, }),
}); });

View file

@ -10,7 +10,6 @@ import { sendEmailTemplate } from "../../utils/api-utils";
import { createToken } from "../../utils/auth"; import { createToken } from "../../utils/auth";
import { nanoid } from "../../utils/nanoid"; import { nanoid } from "../../utils/nanoid";
import { GetPollApiResponse } from "../../utils/trpc/types"; import { GetPollApiResponse } from "../../utils/trpc/types";
import { createRouter } from "../createRouter";
import { publicProcedure, router } from "../trpc"; import { publicProcedure, router } from "../trpc";
import { comments } from "./polls/comments"; import { comments } from "./polls/comments";
import { demo } from "./polls/demo"; import { demo } from "./polls/demo";
@ -38,6 +37,7 @@ const defaultSelectFields: {
}; };
}; };
user: true; user: true;
deleted: true;
} = { } = {
id: true, id: true,
timeZone: true, timeZone: true,
@ -59,6 +59,7 @@ const defaultSelectFields: {
}, },
}, },
user: true, user: true,
deleted: true,
}; };
const getPollIdFromAdminUrlId = async (urlId: string) => { const getPollIdFromAdminUrlId = async (urlId: string) => {
@ -77,26 +78,25 @@ const getPollIdFromAdminUrlId = async (urlId: string) => {
return res.id; return res.id;
}; };
export const legacyPolls = createRouter() export const polls = router({
.merge("demo.", demo) // START LEGACY ROUTES
.merge("participants.", participants) create: publicProcedure
.merge("comments.", comments) .input(
.merge("verification.", verification) z.object({
.mutation("create", { title: z.string(),
input: z.object({ type: z.literal("date"),
title: z.string(), timeZone: z.string().optional(),
type: z.literal("date"), location: z.string().optional(),
timeZone: z.string().optional(), description: z.string().optional(),
location: z.string().optional(), user: z.object({
description: z.string().optional(), name: z.string(),
user: z.object({ email: z.string(),
name: z.string(), }),
email: z.string(), options: z.string().array(),
demo: z.boolean().optional(),
}), }),
options: z.string().array(), )
demo: z.boolean().optional(), .mutation(async ({ ctx, input }): Promise<{ urlId: string }> => {
}),
resolve: async ({ ctx, input }): Promise<{ urlId: string }> => {
const adminUrlId = await nanoid(); const adminUrlId = await nanoid();
let verified = false; let verified = false;
@ -188,21 +188,22 @@ export const legacyPolls = createRouter()
} }
return { urlId: adminUrlId }; return { urlId: adminUrlId };
},
})
.mutation("update", {
input: z.object({
urlId: z.string(),
title: z.string().optional(),
timeZone: z.string().optional(),
location: z.string().optional(),
description: z.string().optional(),
optionsToDelete: z.string().array().optional(),
optionsToAdd: z.string().array().optional(),
notifications: z.boolean().optional(),
closed: z.boolean().optional(),
}), }),
resolve: async ({ input }): Promise<GetPollApiResponse> => { update: publicProcedure
.input(
z.object({
urlId: z.string(),
title: z.string().optional(),
timeZone: z.string().optional(),
location: z.string().optional(),
description: z.string().optional(),
optionsToDelete: z.string().array().optional(),
optionsToAdd: z.string().array().optional(),
notifications: z.boolean().optional(),
closed: z.boolean().optional(),
}),
)
.mutation(async ({ input }): Promise<GetPollApiResponse> => {
const pollId = await getPollIdFromAdminUrlId(input.urlId); const pollId = await getPollIdFromAdminUrlId(input.urlId);
if (input.optionsToDelete && input.optionsToDelete.length > 0) { if (input.optionsToDelete && input.optionsToDelete.length > 0) {
@ -241,22 +242,24 @@ export const legacyPolls = createRouter()
}); });
return { ...poll }; return { ...poll };
},
})
.mutation("delete", {
input: z.object({
urlId: z.string(),
}), }),
resolve: async ({ input: { urlId } }) => { delete: publicProcedure
.input(
z.object({
urlId: z.string(),
}),
)
.mutation(async ({ input: { urlId } }) => {
const pollId = await getPollIdFromAdminUrlId(urlId); const pollId = await getPollIdFromAdminUrlId(urlId);
await prisma.poll.delete({ where: { id: pollId } }); await prisma.poll.delete({ where: { id: pollId } });
},
})
.mutation("touch", {
input: z.object({
pollId: z.string(),
}), }),
resolve: async ({ input: { pollId } }) => { touch: publicProcedure
.input(
z.object({
pollId: z.string(),
}),
)
.mutation(async ({ input: { pollId } }) => {
await prisma.poll.update({ await prisma.poll.update({
where: { where: {
id: pollId, id: pollId,
@ -265,10 +268,12 @@ export const legacyPolls = createRouter()
touchedAt: new Date(), touchedAt: new Date(),
}, },
}); });
}, }),
}); demo,
participants,
export const poll = router({ comments,
verification,
// END LEGACY ROUTES
getByAdminUrlId: publicProcedure getByAdminUrlId: publicProcedure
.input( .input(
z.object({ z.object({

View file

@ -3,14 +3,16 @@ import { z } from "zod";
import { prisma } from "~/prisma/db"; import { prisma } from "~/prisma/db";
import { sendNotification } from "../../../utils/api-utils"; import { sendNotification } from "../../../utils/api-utils";
import { createRouter } from "../../createRouter"; import { publicProcedure, router } from "../../trpc";
export const comments = createRouter() export const comments = router({
.query("list", { list: publicProcedure
input: z.object({ .input(
pollId: z.string(), z.object({
}), pollId: z.string(),
resolve: async ({ input: { pollId } }) => { }),
)
.query(async ({ input: { pollId } }) => {
return await prisma.comment.findMany({ return await prisma.comment.findMany({
where: { pollId }, where: { pollId },
orderBy: [ orderBy: [
@ -19,15 +21,16 @@ export const comments = createRouter()
}, },
], ],
}); });
},
})
.mutation("add", {
input: z.object({
pollId: z.string(),
authorName: z.string(),
content: z.string(),
}), }),
resolve: async ({ ctx, input: { pollId, authorName, content } }) => { add: publicProcedure
.input(
z.object({
pollId: z.string(),
authorName: z.string(),
content: z.string(),
}),
)
.mutation(async ({ ctx, input: { pollId, authorName, content } }) => {
const user = ctx.session.user; const user = ctx.session.user;
const newComment = await prisma.comment.create({ const newComment = await prisma.comment.create({
@ -45,14 +48,15 @@ export const comments = createRouter()
}); });
return newComment; return newComment;
},
})
.mutation("delete", {
input: z.object({
pollId: z.string(),
commentId: z.string(),
}), }),
resolve: async ({ input: { pollId, commentId } }) => { delete: publicProcedure
.input(
z.object({
pollId: z.string(),
commentId: z.string(),
}),
)
.mutation(async ({ input: { pollId, commentId } }) => {
await prisma.comment.delete({ await prisma.comment.delete({
where: { where: {
id_pollId: { id_pollId: {
@ -61,5 +65,5 @@ export const comments = createRouter()
}, },
}, },
}); });
}, }),
}); });

View file

@ -4,7 +4,7 @@ import dayjs from "dayjs";
import { prisma } from "~/prisma/db"; import { prisma } from "~/prisma/db";
import { nanoid } from "../../../utils/nanoid"; import { nanoid } from "../../../utils/nanoid";
import { createRouter } from "../../createRouter"; import { publicProcedure, router } from "../../trpc";
const participantData: Array<{ name: string; votes: VoteType[] }> = [ const participantData: Array<{ name: string; votes: VoteType[] }> = [
{ {
@ -27,8 +27,8 @@ const participantData: Array<{ name: string; votes: VoteType[] }> = [
const optionValues = ["2022-12-14", "2022-12-15", "2022-12-16", "2022-12-17"]; const optionValues = ["2022-12-14", "2022-12-15", "2022-12-16", "2022-12-17"];
export const demo = createRouter().mutation("create", { export const demo = router({
resolve: async () => { create: publicProcedure.mutation(async () => {
const adminUrlId = await nanoid(); const adminUrlId = await nanoid();
const demoUser = { name: "John Example", email: "noreply@rallly.co" }; const demoUser = { name: "John Example", email: "noreply@rallly.co" };
@ -111,5 +111,5 @@ export const demo = createRouter().mutation("create", {
}); });
return adminUrlId; return adminUrlId;
}, }),
}); });

View file

@ -3,14 +3,16 @@ import { z } from "zod";
import { prisma } from "~/prisma/db"; import { prisma } from "~/prisma/db";
import { sendNotification } from "../../../utils/api-utils"; import { sendNotification } from "../../../utils/api-utils";
import { createRouter } from "../../createRouter"; import { publicProcedure, router } from "../../trpc";
export const participants = createRouter() export const participants = router({
.query("list", { list: publicProcedure
input: z.object({ .input(
pollId: z.string(), z.object({
}), pollId: z.string(),
resolve: async ({ input: { pollId } }) => { }),
)
.query(async ({ input: { pollId } }) => {
const participants = await prisma.participant.findMany({ const participants = await prisma.participant.findMany({
where: { where: {
pollId, pollId,
@ -26,33 +28,35 @@ export const participants = createRouter()
], ],
}); });
return participants; return participants;
},
})
.mutation("delete", {
input: z.object({
pollId: z.string(),
participantId: z.string(),
}), }),
resolve: async ({ input: { participantId } }) => { delete: publicProcedure
.input(
z.object({
pollId: z.string(),
participantId: z.string(),
}),
)
.mutation(async ({ input: { participantId } }) => {
await prisma.participant.delete({ await prisma.participant.delete({
where: { where: {
id: participantId, id: participantId,
}, },
}); });
},
})
.mutation("add", {
input: z.object({
pollId: z.string(),
name: z.string().nonempty("Participant name is required"),
votes: z
.object({
optionId: z.string(),
type: z.enum(["yes", "no", "ifNeedBe"]),
})
.array(),
}), }),
resolve: async ({ ctx, input: { pollId, votes, name } }) => { add: publicProcedure
.input(
z.object({
pollId: z.string(),
name: z.string().nonempty("Participant name is required"),
votes: z
.object({
optionId: z.string(),
type: z.enum(["yes", "no", "ifNeedBe"]),
})
.array(),
}),
)
.mutation(async ({ ctx, input: { pollId, votes, name } }) => {
const user = ctx.session.user; const user = ctx.session.user;
const participant = await prisma.participant.create({ const participant = await prisma.participant.create({
data: { data: {
@ -80,20 +84,21 @@ export const participants = createRouter()
}); });
return participant; return participant;
},
})
.mutation("update", {
input: z.object({
pollId: z.string(),
participantId: z.string(),
votes: z
.object({
optionId: z.string(),
type: z.enum(["yes", "no", "ifNeedBe"]),
})
.array(),
}), }),
resolve: async ({ input: { pollId, participantId, votes } }) => { update: publicProcedure
.input(
z.object({
pollId: z.string(),
participantId: z.string(),
votes: z
.object({
optionId: z.string(),
type: z.enum(["yes", "no", "ifNeedBe"]),
})
.array(),
}),
)
.mutation(async ({ input: { pollId, participantId, votes } }) => {
const participant = await prisma.participant.update({ const participant = await prisma.participant.update({
where: { where: {
id: participantId, id: participantId,
@ -118,5 +123,5 @@ export const participants = createRouter()
}); });
return participant; return participant;
}, }),
}); });

View file

@ -11,15 +11,17 @@ import {
decryptToken, decryptToken,
mergeGuestsIntoUser, mergeGuestsIntoUser,
} from "../../../utils/auth"; } from "../../../utils/auth";
import { createRouter } from "../../createRouter"; import { publicProcedure, router } from "../../trpc";
export const verification = createRouter() export const verification = router({
.mutation("verify", { verify: publicProcedure
input: z.object({ .input(
pollId: z.string(), z.object({
code: z.string(), pollId: z.string(),
}), code: z.string(),
resolve: async ({ ctx, input }) => { }),
)
.mutation(async ({ ctx, input }) => {
const { pollId } = await decryptToken<{ const { pollId } = await decryptToken<{
pollId: string; pollId: string;
}>(input.code); }>(input.code);
@ -52,14 +54,15 @@ export const verification = createRouter()
isGuest: false, isGuest: false,
}; };
await ctx.session.save(); await ctx.session.save();
},
})
.mutation("request", {
input: z.object({
pollId: z.string(),
adminUrlId: z.string(),
}), }),
resolve: async ({ input: { pollId, adminUrlId } }) => { request: publicProcedure
.input(
z.object({
pollId: z.string(),
adminUrlId: z.string(),
}),
)
.mutation(async ({ input: { pollId, adminUrlId } }) => {
const poll = await prisma.poll.findUnique({ const poll = await prisma.poll.findUnique({
where: { where: {
id: pollId, id: pollId,
@ -96,5 +99,5 @@ export const verification = createRouter()
supportEmail: process.env.SUPPORT_EMAIL, supportEmail: process.env.SUPPORT_EMAIL,
}, },
}); });
}, }),
}); });

View file

@ -4,7 +4,7 @@ import { z } from "zod";
import { prisma } from "~/prisma/db"; import { prisma } from "~/prisma/db";
import { createRouter } from "../createRouter"; import { publicProcedure, router } from "../trpc";
const requireUser = (user: IronSessionData["user"]) => { const requireUser = (user: IronSessionData["user"]) => {
if (!user) { if (!user) {
@ -16,42 +16,42 @@ const requireUser = (user: IronSessionData["user"]) => {
return user; return user;
}; };
export const user = createRouter() export const user = router({
.query("getPolls", { getPolls: publicProcedure.query(async ({ ctx }) => {
resolve: async ({ ctx }) => { const user = requireUser(ctx.session.user);
const user = requireUser(ctx.session.user); const userPolls = await prisma.user.findUnique({
const userPolls = await prisma.user.findUnique({ where: {
where: { id: user.id,
id: user.id, },
}, select: {
select: { polls: {
polls: { where: {
where: { deleted: false,
deleted: false, },
}, select: {
select: { title: true,
title: true, closed: true,
closed: true, verified: true,
verified: true, createdAt: true,
createdAt: true, adminUrlId: true,
adminUrlId: true, },
}, take: 10,
take: 10, orderBy: {
orderBy: { createdAt: "desc",
createdAt: "desc",
},
}, },
}, },
}); },
return userPolls; });
}, return userPolls;
}) }),
.mutation("changeName", { changeName: publicProcedure
input: z.object({ .input(
userId: z.string(), z.object({
name: z.string().min(1).max(100), userId: z.string(),
}), name: z.string().min(1).max(100),
resolve: async ({ input }) => { }),
)
.mutation(async ({ input }) => {
await prisma.user.update({ await prisma.user.update({
where: { where: {
id: input.userId, id: input.userId,
@ -60,5 +60,5 @@ export const user = createRouter()
name: input.name, name: input.name,
}, },
}); });
}, }),
}); });

View file

@ -1,15 +1,12 @@
import { MutationCache } from "@tanstack/react-query"; import { MutationCache } from "@tanstack/react-query";
import { httpBatchLink } from "@trpc/client"; import { httpBatchLink } from "@trpc/client";
import { createTRPCNext } from "@trpc/next"; import { createTRPCNext } from "@trpc/next";
import { createReactQueryHooks } from "@trpc/react-query";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import superjson from "superjson"; import superjson from "superjson";
import { AppRouter } from "../server/routers/_app"; import { AppRouter } from "../server/routers/_app";
export const trpc = createReactQueryHooks<AppRouter>(); export const trpc = createTRPCNext<AppRouter>({
export const trpcNext = createTRPCNext<AppRouter>({
unstable_overrides: { unstable_overrides: {
useMutation: { useMutation: {
async onSuccess(opts) { async onSuccess(opts) {

View file

@ -17,4 +17,5 @@ export type GetPollApiResponse = {
demo: boolean; demo: boolean;
notifications: boolean; notifications: boolean;
createdAt: Date; createdAt: Date;
deleted: boolean;
}; };

View file

@ -2028,27 +2028,27 @@
"@tanstack/query-core" "4.24.4" "@tanstack/query-core" "4.24.4"
use-sync-external-store "^1.2.0" use-sync-external-store "^1.2.0"
"@trpc/client@^10.0.0-rc.8": "@trpc/client@^10.13.0":
version "10.0.0-rc.8" version "10.13.0"
resolved "https://registry.yarnpkg.com/@trpc/client/-/client-10.0.0-rc.8.tgz#2b6b0db8ec41e4403b72d25d37a2cd9d6c6c1257" resolved "https://registry.yarnpkg.com/@trpc/client/-/client-10.13.0.tgz#8999a7ba068a684629071b77a07c00a141585eca"
integrity sha512-NuQl7g1tkfFYn6P6dZJJkVsxnxutYpaxmpG/w5ZKW3QVu6cY1joUOrWakunKrghh3HS+svgLaPz0Xl0PlmdSlw== integrity sha512-r4KuN0os2J194lxg5jn4+o3uNlqunLFYptwTHcVW4Q0XGO0ZoTKLHuxT7c9IeDivkAs6G5oVEPiKhptkag36dQ==
"@trpc/next@^10.0.0-rc.8": "@trpc/next@^10.13.0":
version "10.0.0-rc.8" version "10.13.0"
resolved "https://registry.yarnpkg.com/@trpc/next/-/next-10.0.0-rc.8.tgz#78a4acc4404c3a8295f0b0a8da4e2c89b21e1366" resolved "https://registry.yarnpkg.com/@trpc/next/-/next-10.13.0.tgz#64393f0d8dbfae7d87e54260dea532702bd1ecdf"
integrity sha512-l2TZF22virmC3RROD7qu0dnc3sdYfVvkOhAu1qP5+8fRgs4cgHKBFdalQ+SKBnU0RNuVLYVw0CkyvU+gHo5u2w== integrity sha512-Q4rnuuiSUXDYv34f8FNUKhEMQFgLJTTJean78YjhG3Aaci+r4sew4hPmRvDRut8fBpa+EtExq+dv1EUbzlXgJg==
dependencies: dependencies:
react-ssr-prepass "^1.5.0" react-ssr-prepass "^1.5.0"
"@trpc/react-query@^10.0.0-rc.8": "@trpc/react-query@^10.13.0":
version "10.0.0-rc.8" version "10.13.0"
resolved "https://registry.yarnpkg.com/@trpc/react-query/-/react-query-10.0.0-rc.8.tgz#68957743cd99e9c97fb167fb955b00bd1f9010e2" resolved "https://registry.yarnpkg.com/@trpc/react-query/-/react-query-10.13.0.tgz#ca5a1da0ea126976d7435775f2ba1846cb7fad59"
integrity sha512-LKL29llvGtwYmfKGuCYSgNNChOL8/6PInY90i+ZgI8TLO6ZCJaVrPBSvqtxKj3MSezbK3kOxWIviKVFwV35L0A== integrity sha512-y4jbojrDFdEl1KBejBoMWIofcUXDHQA8wf01eKMEDV7Jwc7lhq6R1dxYtKzeF+s5wqfnPWFOGZDmB3flzv07Dw==
"@trpc/server@^10.0.0-rc.8": "@trpc/server@^10.13.0":
version "10.0.0-rc.8" version "10.13.0"
resolved "https://registry.yarnpkg.com/@trpc/server/-/server-10.0.0-rc.8.tgz#941cb4b06a2d59419bdbcc61241f1e8946b67a1e" resolved "https://registry.yarnpkg.com/@trpc/server/-/server-10.13.0.tgz#0945b5210a18934c2284c8679cb6692ecb5b054c"
integrity sha512-+NsQqaJGHTiiBcOesH1bIictnnSO+SXaCOy5pM/324QBnJRt9VOO8oYDLcHSwAbJpAbOA+vl6YHs8OoykW6D2g== integrity sha512-d/bu6utCC4ALxhTJkolEPAHMOSuCAu3mG79TZswa6wD2ob0/Z3AIvBF/meeSTqDxe4tvXY78lQqOkQI81dgi/g==
"@trysound/sax@0.2.0": "@trysound/sax@0.2.0":
version "0.2.0" version "0.2.0"