+ {isSignedIn && !!roomFromDb && roomFromDb.userId === user.id && (
+ <>
+
+
+
Room Settings
-
+
-
{
- setRoomScale(event.target.value);
- }}
- />
+
{
+ setRoomScale(event.target.value);
+ }}
+ />
-
+
-
{
- setStoryNameText(event.target.value);
- }}
- />
+
{
+ setStoryNameText(event.target.value);
+ }}
+ />
-
-
-
-
-
-
-
-
-
- {votesFromDb &&
- (roomFromDb.logs.length > 0 ||
- votesFromDb.length > 0) && (
-
-
-
+
+
+
+
+
+
+
+
+ {votesFromDb &&
+ (roomFromDb.logs.length > 0 || votesFromDb.length > 0) && (
+
+
+
+ )}
- >
- )}
+
+ >
+ )}
);
// Room does not exist
diff --git a/src/pages/sign-in/[[...index]].tsx b/src/pages/sign-in/[[...index]].tsx
new file mode 100644
index 0000000..f84cb7f
--- /dev/null
+++ b/src/pages/sign-in/[[...index]].tsx
@@ -0,0 +1,17 @@
+import { SignIn } from "@clerk/nextjs";
+
+const SignInPage = () => (
+
+
+
+);
+
+export default SignInPage;
+
+const styles = {
+ width: "100vw",
+ height: "100vh",
+ display: "flex",
+ justifyContent: "center",
+ alignItems: "center",
+};
diff --git a/src/pages/sign-up/[[...index]].tsx b/src/pages/sign-up/[[...index]].tsx
new file mode 100644
index 0000000..ce6c5ea
--- /dev/null
+++ b/src/pages/sign-up/[[...index]].tsx
@@ -0,0 +1,17 @@
+import { SignUp } from "@clerk/nextjs";
+
+const SignUpPage = () => (
+
+
+
+);
+
+export default SignUpPage;
+
+const styles = {
+ width: "100vw",
+ height: "100vh",
+ display: "flex",
+ justifyContent: "center",
+ alignItems: "center",
+};
diff --git a/src/server/api/root.ts b/src/server/api/root.ts
index 7d5426a..4acf4e6 100644
--- a/src/server/api/root.ts
+++ b/src/server/api/root.ts
@@ -1,7 +1,5 @@
import { roomRouter } from "~/server/api/routers/room";
import { createTRPCRouter } from "~/server/api/trpc";
-import { sessionRouter } from "./routers/session";
-import { userRouter } from "./routers/user";
import { voteRouter } from "./routers/vote";
import { restRouter } from "./routers/rest";
@@ -13,8 +11,6 @@ import { restRouter } from "./routers/rest";
export const appRouter = createTRPCRouter({
room: roomRouter,
vote: voteRouter,
- user: userRouter,
- session: sessionRouter,
rest: restRouter,
});
diff --git a/src/server/api/routers/rest.ts b/src/server/api/routers/rest.ts
index 3d6ff86..ad90207 100644
--- a/src/server/api/routers/rest.ts
+++ b/src/server/api/routers/rest.ts
@@ -4,6 +4,8 @@ import { z } from "zod";
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
import { TRPCError } from "@trpc/server";
import { fetchCache, setCache } from "~/server/redis";
+import { sql } from "drizzle-orm";
+import { rooms, votes } from "~/server/schema";
export const restRouter = createTRPCRouter({
dbWarmer: publicProcedure
@@ -13,7 +15,7 @@ export const restRouter = createTRPCRouter({
.query(async ({ ctx, input }) => {
const isValidKey = await validateApiKey(input.key);
if (isValidKey) {
- await ctx.prisma.verificationToken.findMany();
+ await ctx.db.query.votes.findMany();
return "Toasted the DB";
} else {
throw new TRPCError({ code: "UNAUTHORIZED" });
@@ -30,7 +32,11 @@ export const restRouter = createTRPCRouter({
if (cachedResult) {
return cachedResult;
} else {
- const votesCount = await ctx.prisma.vote.count();
+ const votesResult = (
+ await ctx.db.select({ count: sql
`count(*)` }).from(votes)
+ )[0];
+
+ const votesCount = votesResult ? Number(votesResult.count) : 0;
await setCache(`kv_votecount`, votesCount);
@@ -38,24 +44,6 @@ export const restRouter = createTRPCRouter({
}
}),
- userCount: publicProcedure
- .meta({ openapi: { method: "GET", path: "/rest/users/count" } })
- .input(z.void())
- .output(z.number())
- .query(async ({ ctx }) => {
- const cachedResult = await fetchCache(`kv_usercount`);
-
- if (cachedResult) {
- return cachedResult;
- } else {
- const usersCount = await ctx.prisma.user.count();
-
- await setCache(`kv_usercount`, usersCount);
-
- return usersCount;
- }
- }),
-
roomCount: publicProcedure
.meta({ openapi: { method: "GET", path: "/rest/rooms/count" } })
.input(z.void())
@@ -66,7 +54,11 @@ export const restRouter = createTRPCRouter({
if (cachedResult) {
return cachedResult;
} else {
- const roomsCount = await ctx.prisma.room.count();
+ const roomsResult = (
+ await ctx.db.select({ count: sql`count(*)` }).from(rooms)
+ )[0];
+
+ const roomsCount = roomsResult ? Number(roomsResult.count) : 0;
await setCache(`kv_roomcount`, roomsCount);
diff --git a/src/server/api/routers/room.ts b/src/server/api/routers/room.ts
index 598b818..cf8c096 100644
--- a/src/server/api/routers/room.ts
+++ b/src/server/api/routers/room.ts
@@ -3,7 +3,10 @@ import { publishToChannel } from "~/server/ably";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
import { fetchCache, invalidateCache, setCache } from "~/server/redis";
+import { logs, rooms, votes } from "~/server/schema";
import { EventTypes } from "~/utils/types";
+import { createId } from "@paralleldrive/cuid2";
+import { eq } from "drizzle-orm";
export const roomRouter = createTRPCRouter({
// Create
@@ -14,57 +17,52 @@ export const roomRouter = createTRPCRouter({
})
)
.mutation(async ({ ctx, input }) => {
- if (ctx.session) {
- const room = await ctx.prisma.room.create({
- data: {
- userId: ctx.session.user.id,
- roomName: input.name,
- storyName: "First Story!",
- scale: "0.5,1,2,3,5,8",
- visible: false,
- },
- });
- if (room) {
- await invalidateCache(`kv_roomcount`);
- await invalidateCache(`kv_roomlist_${ctx.session.user.id}`);
+ const room = await ctx.db.insert(rooms).values({
+ id: createId(),
+ userId: ctx.auth.userId,
+ roomName: input.name,
+ storyName: "First Story!",
+ scale: "0.5,1,2,3,5,8",
+ visible: false,
+ });
- await publishToChannel(
- `${ctx.session.user.id}`,
- EventTypes.ROOM_LIST_UPDATE,
- JSON.stringify(room)
- );
+ const success = room.rowsAffected > 0;
+ if (room) {
+ await invalidateCache(`kv_roomcount`);
+ await invalidateCache(`kv_roomlist_${ctx.auth.userId}`);
- await publishToChannel(
- `stats`,
- EventTypes.STATS_UPDATE,
- JSON.stringify(room)
- );
- }
- // happy path
- return !!room;
+ await publishToChannel(
+ `${ctx.auth.userId}`,
+ EventTypes.ROOM_LIST_UPDATE,
+ JSON.stringify(room)
+ );
+
+ await publishToChannel(
+ `stats`,
+ EventTypes.STATS_UPDATE,
+ JSON.stringify(room)
+ );
}
-
- // clinically depressed path
- return false;
+ return success;
}),
// Get One
get: protectedProcedure
.input(z.object({ id: z.string() }))
.query(({ ctx, input }) => {
- return ctx.prisma.room.findUnique({
- where: {
- id: input.id,
- },
- select: {
- id: true,
- userId: true,
- logs: true,
- roomName: true,
- storyName: true,
- visible: true,
- scale: true,
- owner: true,
+ return ctx.db.query.rooms.findFirst({
+ where: eq(rooms.id, input.id),
+ with: {
+ logs: {
+ with: {
+ room: true,
+ },
+ },
+ votes: {
+ with: {
+ room: true,
+ },
+ },
},
});
}),
@@ -77,23 +75,16 @@ export const roomRouter = createTRPCRouter({
createdAt: Date;
roomName: string;
}[]
- >(`kv_roomlist_${ctx.session.user.id}`);
+ >(`kv_roomlist_${ctx.auth.userId}`);
if (cachedResult) {
return cachedResult;
} else {
- const roomList = await ctx.prisma.room.findMany({
- where: {
- userId: ctx.session.user.id,
- },
- select: {
- id: true,
- createdAt: true,
- roomName: true,
- },
+ const roomList = await ctx.db.query.rooms.findMany({
+ where: eq(rooms.userId, ctx.auth.userId),
});
- await setCache(`kv_roomlist_${ctx.session.user.id}`, roomList);
+ await setCache(`kv_roomlist_${ctx.auth.userId}`, roomList);
return roomList;
}
@@ -114,119 +105,88 @@ export const roomRouter = createTRPCRouter({
.mutation(async ({ ctx, input }) => {
if (input.reset) {
if (input.log) {
- const oldRoom = await ctx.prisma.room.findUnique({
- where: {
- id: input.roomId,
- },
- select: {
- roomName: true,
- storyName: true,
- scale: true,
- votes: {
- select: {
- owner: {
- select: {
- name: true,
- },
- },
- value: true,
- },
- },
+ const oldRoom = await ctx.db.query.rooms.findFirst({
+ where: eq(rooms.id, input.roomId),
+ with: {
+ votes: true,
+ logs: true,
},
});
oldRoom &&
- (await ctx.prisma.log.create({
- data: {
- userId: ctx.session.user.id,
- roomId: input.roomId,
- scale: oldRoom.scale,
- votes: oldRoom.votes.map((vote) => {
- return {
- name: vote.owner.name,
- value: vote.value,
- };
- }),
- roomName: oldRoom.roomName,
- storyName: oldRoom.storyName,
- },
+ (await ctx.db.insert(logs).values({
+ id: createId(),
+ userId: ctx.auth.userId,
+ roomId: input.roomId,
+ scale: oldRoom.scale,
+ votes: oldRoom.votes.map((vote) => {
+ return {
+ name: vote.userId,
+ value: vote.value,
+ };
+ }),
+ roomName: oldRoom.roomName,
+ storyName: oldRoom.storyName,
}));
}
- await ctx.prisma.vote.deleteMany({
- where: {
- roomId: input.roomId,
- },
- });
+ await ctx.db.delete(votes).where(eq(votes.roomId, input.roomId));
await invalidateCache(`kv_votes_${input.roomId}`);
}
- const newRoom = await ctx.prisma.room.update({
- where: {
- id: input.roomId,
- },
- data: {
+ const newRoom = await ctx.db
+ .update(rooms)
+ .set({
storyName: input.name,
- userId: ctx.session.user.id,
+ userId: ctx.auth.userId,
visible: input.visible,
scale: [...new Set(input.scale.split(","))]
.filter((item) => item !== "")
.toString(),
- },
- select: {
- id: true,
- roomName: true,
- storyName: true,
- visible: true,
- scale: true,
- votes: {
- select: {
- owner: {
- select: {
- name: true,
- },
- },
- value: true,
- },
- },
- },
- });
+ })
+ .where(eq(rooms.id, input.roomId));
- if (newRoom) {
+ const success = newRoom.rowsAffected > 0;
+
+ if (success) {
await publishToChannel(
- `${newRoom.id}`,
+ `${input.roomId}`,
EventTypes.ROOM_UPDATE,
JSON.stringify(newRoom)
);
}
- return !!newRoom;
+ return success;
}),
// Delete One
delete: protectedProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ ctx, input }) => {
- const deletedRoom = await ctx.prisma.room.delete({
- where: {
- id: input.id,
- },
- });
+ const deletedRoom = await ctx.db
+ .delete(rooms)
+ .where(eq(rooms.id, input.id));
+
+ const success = deletedRoom.rowsAffected > 0;
+
+ if (success) {
+ await ctx.db.delete(votes).where(eq(votes.roomId, input.id));
+
+ await ctx.db.delete(logs).where(eq(logs.roomId, input.id));
- if (deletedRoom) {
await invalidateCache(`kv_roomcount`);
await invalidateCache(`kv_votecount`);
- await invalidateCache(`kv_roomlist_${ctx.session.user.id}`);
+ await invalidateCache(`kv_roomlist_${ctx.auth.userId}`);
await publishToChannel(
- `${ctx.session.user.id}`,
+ `${ctx.auth.userId}`,
EventTypes.ROOM_LIST_UPDATE,
JSON.stringify(deletedRoom)
);
await publishToChannel(
- `${deletedRoom.id}`,
+ `${input.id}`,
EventTypes.ROOM_UPDATE,
JSON.stringify(deletedRoom)
);
@@ -238,6 +198,6 @@ export const roomRouter = createTRPCRouter({
);
}
- return !!deletedRoom;
+ return success;
}),
});
diff --git a/src/server/api/routers/session.ts b/src/server/api/routers/session.ts
deleted file mode 100644
index 7db9918..0000000
--- a/src/server/api/routers/session.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { z } from "zod";
-import { adminProcedure, createTRPCRouter } from "~/server/api/trpc";
-import { invalidateCache } from "~/server/redis";
-
-export const sessionRouter = createTRPCRouter({
- deleteAllByUserId: adminProcedure
- .input(
- z.object({
- userId: z.string(),
- })
- )
- .mutation(async ({ ctx, input }) => {
- const sessions = await ctx.prisma.session.deleteMany({
- where: {
- userId: input.userId,
- },
- });
-
- if (!!sessions) {
- await invalidateCache(`kv_userlist_admin`);
- }
-
- return !!sessions;
- }),
- deleteAll: adminProcedure.mutation(async ({ ctx }) => {
- const sessions = await ctx.prisma.session.deleteMany();
-
- if (!!sessions) {
- await invalidateCache(`kv_userlist_admin`);
- }
-
- return !!sessions;
- }),
-});
diff --git a/src/server/api/routers/user.ts b/src/server/api/routers/user.ts
deleted file mode 100644
index 923d9eb..0000000
--- a/src/server/api/routers/user.ts
+++ /dev/null
@@ -1,194 +0,0 @@
-import type { User } from "@prisma/client";
-import { Resend } from "resend";
-import { z } from "zod";
-import { Goodbye } from "~/components/templates/Goodbye";
-import { env } from "~/env.mjs";
-import { publishToChannel } from "~/server/ably";
-import {
- adminProcedure,
- createTRPCRouter,
- protectedProcedure,
-} from "~/server/api/trpc";
-
-import { fetchCache, invalidateCache, setCache } from "~/server/redis";
-import { EventTypes } from "~/utils/types";
-
-const resend = new Resend(process.env.RESEND_API_KEY);
-
-export const userRouter = createTRPCRouter({
- getProviders: protectedProcedure.query(async ({ ctx }) => {
- const providers = await ctx.prisma.user.findUnique({
- where: {
- id: ctx.session.user.id,
- },
- select: {
- accounts: {
- select: {
- provider: true,
- },
- },
- },
- });
-
- return providers?.accounts.map((account) => {
- return account.provider;
- });
- }),
- getAll: protectedProcedure.query(async ({ ctx }) => {
- const cachedResult = await fetchCache<
- {
- accounts: {
- provider: string;
- }[];
- sessions: {
- id: string;
- }[];
- id: string;
- createdAt: Date;
- isAdmin: boolean;
- isVIP: boolean;
- name: string | null;
- email: string | null;
- }[]
- >(`kv_userlist_admin`);
-
- if (cachedResult) {
- return cachedResult.map((user) => {
- return {
- ...user,
- createdAt: new Date(user.createdAt),
- };
- });
- } else {
- const users = await ctx.prisma.user.findMany({
- select: {
- id: true,
- name: true,
- isAdmin: true,
- isVIP: true,
- createdAt: true,
- email: true,
- sessions: {
- select: {
- id: true,
- },
- },
- accounts: {
- select: {
- provider: true,
- },
- },
- },
- });
-
- await setCache(`${env.APP_ENV}_kv_userlist_admin`, users);
-
- return users;
- }
- }),
- delete: protectedProcedure
- .input(
- z
- .object({
- userId: z.string(),
- })
- .optional()
- )
- .mutation(async ({ ctx, input }) => {
- let user: User;
- if (input?.userId && ctx.session.user.isAdmin) {
- user = await ctx.prisma.user.delete({
- where: {
- id: input.userId,
- },
- });
- } else {
- user = await ctx.prisma.user.delete({
- where: {
- id: ctx.session.user.id,
- },
- });
- }
-
- if (!!user && user.name && user.email) {
- await resend.emails.send({
- from: "Sprint Padawan ",
- to: user.email,
- subject: "Sorry to see you go... 😭",
- react: Goodbye({ name: user.name }),
- });
-
- await invalidateCache(`kv_usercount`);
- await invalidateCache(`kv_userlist_admin`);
-
- await publishToChannel(
- `stats`,
- EventTypes.STATS_UPDATE,
- JSON.stringify(user)
- );
- }
-
- return !!user;
- }),
- save: protectedProcedure
- .input(
- z.object({
- name: z.string(),
- })
- )
- .mutation(async ({ ctx, input }) => {
- const user = await ctx.prisma.user.update({
- where: {
- id: ctx.session.user.id,
- },
- data: {
- name: input.name,
- },
- });
-
- return !!user;
- }),
- setAdmin: adminProcedure
- .input(
- z.object({
- userId: z.string(),
- value: z.boolean(),
- })
- )
- .mutation(async ({ ctx, input }) => {
- const user = await ctx.prisma.user.update({
- where: {
- id: input.userId,
- },
- data: {
- isAdmin: input.value,
- },
- });
-
- await invalidateCache(`kv_userlist_admin`);
-
- return !!user;
- }),
-
- setVIP: adminProcedure
- .input(
- z.object({
- userId: z.string(),
- value: z.boolean(),
- })
- )
- .mutation(async ({ ctx, input }) => {
- const user = await ctx.prisma.user.update({
- where: {
- id: input.userId,
- },
- data: {
- isVIP: input.value,
- },
- });
-
- await invalidateCache(`kv_userlist_admin`);
-
- return !!user;
- }),
-});
diff --git a/src/server/api/routers/vote.ts b/src/server/api/routers/vote.ts
index c3e524b..28cad1d 100644
--- a/src/server/api/routers/vote.ts
+++ b/src/server/api/routers/vote.ts
@@ -1,10 +1,12 @@
import { z } from "zod";
import { publishToChannel } from "~/server/ably";
-import type { Room } from "@prisma/client";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
import { fetchCache, invalidateCache, setCache } from "~/server/redis";
import { EventTypes } from "~/utils/types";
+import { eq } from "drizzle-orm";
+import { votes } from "~/server/schema";
+import { createId } from "@paralleldrive/cuid2";
export const voteRouter = createTRPCRouter({
getAllByRoomId: protectedProcedure
@@ -12,14 +14,10 @@ export const voteRouter = createTRPCRouter({
.query(async ({ ctx, input }) => {
const cachedResult = await fetchCache<
{
- value: string;
- room: Room;
id: string;
- createdAt: Date;
+ value: string;
+ created_at: Date;
userId: string;
- owner: {
- name: string | null;
- };
roomId: string;
}[]
>(`kv_votes_${input.roomId}`);
@@ -27,23 +25,8 @@ export const voteRouter = createTRPCRouter({
if (cachedResult) {
return cachedResult;
} else {
- const votesByRoomId = await ctx.prisma.vote.findMany({
- where: {
- roomId: input.roomId,
- },
- select: {
- id: true,
- createdAt: true,
- owner: {
- select: {
- name: true,
- },
- },
- room: true,
- roomId: true,
- userId: true,
- value: true,
- },
+ const votesByRoomId = await ctx.db.query.votes.findMany({
+ where: eq(votes.roomId, input.roomId),
});
await setCache(`kv_votes_${input.roomId}`, votesByRoomId);
@@ -54,42 +37,34 @@ export const voteRouter = createTRPCRouter({
set: protectedProcedure
.input(z.object({ value: z.string(), roomId: z.string() }))
.mutation(async ({ ctx, input }) => {
- const vote = await ctx.prisma.vote.upsert({
- where: {
- userId_roomId: {
- roomId: input.roomId,
- userId: ctx.session.user.id,
- },
- },
- create: {
+ const updateResult = await ctx.db
+ .update(votes)
+ .set({
value: input.value,
- userId: ctx.session.user.id,
+ userId: ctx.auth.userId,
roomId: input.roomId,
- },
- update: {
- value: input.value,
- userId: ctx.session.user.id,
- roomId: input.roomId,
- },
- select: {
- value: true,
- userId: true,
- roomId: true,
- id: true,
- owner: {
- select: {
- name: true,
- },
- },
- },
- });
+ })
+ .where(eq(votes.userId, ctx.auth.userId));
- if (vote) {
+ let success = updateResult.rowsAffected > 0;
+
+ if (!success) {
+ const vote = await ctx.db.insert(votes).ignore().values({
+ id: createId(),
+ value: input.value,
+ userId: ctx.auth.userId,
+ roomId: input.roomId,
+ });
+
+ success = vote.rowsAffected > 0;
+ }
+
+ if (success) {
await invalidateCache(`kv_votecount`);
await invalidateCache(`kv_votes_${input.roomId}`);
await publishToChannel(
- `${vote.roomId}`,
+ `${input.roomId}`,
EventTypes.VOTE_UPDATE,
input.value
);
@@ -97,10 +72,10 @@ export const voteRouter = createTRPCRouter({
await publishToChannel(
`stats`,
EventTypes.STATS_UPDATE,
- JSON.stringify(vote)
+ JSON.stringify(success)
);
}
- return !!vote;
+ return success;
}),
});
diff --git a/src/server/api/trpc.ts b/src/server/api/trpc.ts
index 2bf95d0..ebce472 100644
--- a/src/server/api/trpc.ts
+++ b/src/server/api/trpc.ts
@@ -1,81 +1,71 @@
/**
* YOU PROBABLY DON'T NEED TO EDIT THIS FILE, UNLESS:
- * 1. You want to modify request context (see Part 1).
- * 2. You want to create a new middleware or type of procedure (see Part 3).
+ * 1. You want to modify request context (see Part 1)
+ * 2. You want to create a new middleware or type of procedure (see Part 3)
*
- * TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will
- * need to use are documented accordingly near the end.
+ * tl;dr - this is where all the tRPC server stuff is created and plugged in.
+ * The pieces you will need to use are documented accordingly near the end
*/
/**
* 1. CONTEXT
*
- * This section defines the "contexts" that are available in the backend API.
+ * This section defines the "contexts" that are available in the backend API
+ *
+ * These allow you to access things like the database, the session, etc, when
+ * processing a request
*
- * These allow you to access things when processing a request, like the database, the session, etc.
*/
import { type CreateNextContextOptions } from "@trpc/server/adapters/next";
-import { type Session } from "next-auth";
+import { getAuth } from "@clerk/nextjs/server";
+import type {
+ SignedInAuthObject,
+ SignedOutAuthObject,
+} from "@clerk/nextjs/api";
-import { getServerAuthSession } from "~/server/auth";
-import { prisma } from "~/server/db";
-
-type CreateContextOptions = {
- session: Session | null;
- ip: string | undefined;
-};
+import { db } from "../db";
+interface AuthContext {
+ auth: SignedInAuthObject | SignedOutAuthObject;
+}
/**
- * This helper generates the "internals" for a tRPC context. If you need to use it, you can export
- * it from here.
+ * This helper generates the "internals" for a tRPC context. If you need to use
+ * it, you can export it from here
*
* Examples of things you may need it for:
- * - testing, so we don't have to mock Next.js' req/res
- * - tRPC's `createSSGHelpers`, where we don't have req/res
- *
+ * - testing, so we dont have to mock Next.js' req/res
+ * - trpc's `createSSGHelpers` where we don't have req/res
* @see https://create.t3.gg/en/usage/trpc#-servertrpccontextts
*/
-const createInnerTRPCContext = (opts: CreateContextOptions) => {
+const createInnerTRPCContext = ({ auth }: AuthContext) => {
return {
- session: opts.session,
- ip: opts.ip,
- prisma,
+ auth,
+ db,
};
};
/**
- * This is the actual context you will use in your router. It will be used to process every request
- * that goes through your tRPC endpoint.
- *
- * @see https://trpc.io/docs/context
+ * This is the actual context you'll use in your router. It will be used to
+ * process every request that goes through your tRPC endpoint
+ * @link https://trpc.io/docs/context
*/
-export const createTRPCContext = async (opts: CreateNextContextOptions) => {
- const { req, res } = opts;
-
- // Get the session from the server using the getServerSession wrapper function
- const session = await getServerAuthSession({ req, res });
-
- return createInnerTRPCContext({
- ip: req.socket.remoteAddress,
- session,
- });
+export const createTRPCContext = (opts: CreateNextContextOptions) => {
+ return createInnerTRPCContext({ auth: getAuth(opts.req) });
};
/**
* 2. INITIALIZATION
*
- * This is where the tRPC API is initialized, connecting the context and transformer.
+ * This is where the trpc api is initialized, connecting the context and
+ * transformer
*/
import { initTRPC, TRPCError } from "@trpc/server";
-import type { OpenApiMeta } from "trpc-openapi";
-import { Ratelimit } from "@upstash/ratelimit";
import superjson from "superjson";
-import { env } from "~/env.mjs";
-import { Redis } from "@upstash/redis";
+import type { OpenApiMeta } from "trpc-openapi";
const t = initTRPC
- .meta()
.context()
+ .meta()
.create({
transformer: superjson,
errorFormatter({ shape }) {
@@ -83,91 +73,37 @@ const t = initTRPC
},
});
+// check if the user is signed in, otherwise through a UNAUTHORIZED CODE
+const isAuthed = t.middleware(({ next, ctx }) => {
+ if (!ctx.auth.userId) {
+ throw new TRPCError({ code: "UNAUTHORIZED" });
+ }
+
+ return next({
+ ctx: {
+ auth: ctx.auth,
+ },
+ });
+});
/**
* 3. ROUTER & PROCEDURE (THE IMPORTANT BIT)
*
- * These are the pieces you use to build your tRPC API. You should import these a lot in the
- * "/src/server/api/routers" directory.
+ * These are the pieces you use to build your tRPC API. You should import these
+ * a lot in the /src/server/api/routers folder
*/
/**
- * This is how you create new routers and sub-routers in your tRPC API.
- *
+ * This is how you create new routers and subrouters in your tRPC API
* @see https://trpc.io/docs/router
*/
export const createTRPCRouter = t.router;
/**
- * Public (unauthenticated) procedure
+ * Public (unauthed) procedure
*
- * This is the base piece you use to build new queries and mutations on your tRPC API. It does not
- * guarantee that a user querying is authorized, but you can still access user session data if they
- * are logged in.
+ * This is the base piece you use to build new queries and mutations on your
+ * tRPC API. It does not guarantee that a user querying is authorized, but you
+ * can still access user session data if they are logged in
*/
export const publicProcedure = t.procedure;
-
-/** Reusable middleware that enforces users are logged in before running the procedure. */
-const enforceAuthSession = t.middleware(async ({ ctx, next }) => {
- // Auth
- if (!ctx.session || !ctx.session.user) {
- throw new TRPCError({ code: "UNAUTHORIZED" });
- }
-
- const rateLimit = new Ratelimit({
- redis: Redis.fromEnv(),
- limiter: Ratelimit.slidingWindow(
- Number(env.UPSTASH_RATELIMIT_REQUESTS),
- `${Number(env.UPSTASH_RATELIMIT_SECONDS)}s`
- ),
- analytics: true,
- });
-
- const { success } = await rateLimit.limit(
- `${env.APP_ENV}_${ctx.session?.user.id}`
- );
-
- if (!success) throw new TRPCError({ code: "TOO_MANY_REQUESTS" });
-
- return next({
- ctx: {
- session: { ...ctx.session, user: ctx.session.user },
- },
- });
-});
-
-const enforceAdminRole = t.middleware(async ({ ctx, next }) => {
- if (!ctx.session || !ctx.session.user || !ctx.session?.user.isAdmin)
- throw new TRPCError({ code: "UNAUTHORIZED" });
-
- return next({
- ctx: {
- session: { ...ctx.session, user: ctx.session.user },
- },
- });
-});
-
-// const enforceApiToken = t.middleware(async ({ ctx, next, path }) => {
-// const res = await unkey.keys.verify({
-// key: ""
-// })
-// if (!ctx.session || !ctx.session.user || !ctx.session?.user.isAdmin)
-// throw new TRPCError({ code: "UNAUTHORIZED" });
-
-// return next({
-// ctx: {
-// session: { ...ctx.session, user: ctx.session.user },
-// },
-// });
-// });
-
-/**
- * Protected (authenticated) procedure
- *
- * If you want a query or mutation to ONLY be accessible to logged in users, use this. It verifies
- * the session is valid and guarantees `ctx.session.user` is not null.
- *
- * @see https://trpc.io/docs/procedures
- */
-export const protectedProcedure = t.procedure.use(enforceAuthSession);
-
-export const adminProcedure = t.procedure.use(enforceAdminRole);
+export const protectedProcedure = t.procedure.use(isAuthed);
diff --git a/src/server/auth.ts b/src/server/auth.ts
deleted file mode 100644
index fc9cf5d..0000000
--- a/src/server/auth.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import { PrismaAdapter } from "@auth/prisma-adapter";
-import { type GetServerSidePropsContext } from "next";
-import {
- getServerSession,
- type DefaultSession,
- type NextAuthOptions,
-} from "next-auth";
-import GithubProvider from "next-auth/providers/github";
-import GoogleProvider from "next-auth/providers/google";
-import { Resend } from "resend";
-import { env } from "~/env.mjs";
-import { prisma } from "~/server/db";
-import { Welcome } from "../components/templates/Welcome";
-import { invalidateCache } from "./redis";
-import { publishToChannel } from "./ably";
-import { EventTypes } from "~/utils/types";
-
-const resend = new Resend(process.env.RESEND_API_KEY);
-
-/**
- * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
- * object and keep type safety.
- *
- * @see https://next-auth.js.org/getting-started/typescript#module-augmentation
- */
-declare module "next-auth" {
- interface Session extends DefaultSession {
- user: {
- id: string;
- isAdmin: boolean;
- isVIP: boolean;
- } & DefaultSession["user"];
- }
-
- interface User {
- isAdmin: boolean;
- isVIP: boolean;
- }
-}
-
-/**
- * Options for NextAuth.js used to configure adapters, providers, callbacks, etc.
- *
- * @see https://next-auth.js.org/configuration/options
- */
-export const authOptions: NextAuthOptions = {
- callbacks: {
- session({ session, user }) {
- if (session.user) {
- session.user.id = user.id;
- session.user.isAdmin = user.isAdmin;
- session.user.isVIP = user.isVIP;
- }
- return session;
- },
- },
- events: {
- async createUser({ user }) {
- if (user && user.name && user.email) {
- await resend.sendEmail({
- from: "no-reply@sprintpadawan.dev",
- to: user.email,
- subject: "🎉 Welcome to Sprint Padawan! 🎉",
- //@ts-ignore: IDK why this doesn't work...
-
- react: Welcome({ name: user.name }),
- });
- await invalidateCache(`kv_userlist_admin`);
- await invalidateCache(`kv_usercount`);
-
- await publishToChannel(
- `stats`,
- EventTypes.STATS_UPDATE,
- JSON.stringify(user)
- );
- }
- },
- async signIn({}) {
- await invalidateCache(`kv_userlist_admin`);
- },
- async signOut() {
- await invalidateCache(`kv_userlist_admin`);
- },
- },
- // @ts-ignore This adapter should work...
- adapter: PrismaAdapter(prisma),
- providers: [
- GithubProvider({
- clientId: env.GITHUB_CLIENT_ID,
- clientSecret: env.GITHUB_CLIENT_SECRET,
- }),
- GoogleProvider({
- clientId: env.GOOGLE_CLIENT_ID,
- clientSecret: env.GOOGLE_CLIENT_SECRET,
- }),
- /**
- * ...add more providers here.
- *
- * Most other providers require a bit more work than the Discord provider. For example, the
- * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account
- * model. Refer to the NextAuth.js docs for the provider you want to use. Example:
- *
- * @see https://next-auth.js.org/providers/github
- */
- ],
-};
-
-/**
- * Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file.
- *
- * @see https://next-auth.js.org/configuration/nextjs
- */
-export const getServerAuthSession = (ctx: {
- req: GetServerSidePropsContext["req"];
- res: GetServerSidePropsContext["res"];
-}) => {
- return getServerSession(ctx.req, ctx.res, authOptions);
-};
diff --git a/src/server/db.ts b/src/server/db.ts
index f3d7be3..8055892 100644
--- a/src/server/db.ts
+++ b/src/server/db.ts
@@ -1,14 +1,11 @@
-import { PrismaClient } from "@prisma/client";
-
+import { drizzle } from "drizzle-orm/planetscale-serverless";
+import { connect } from "@planetscale/database";
import { env } from "~/env.mjs";
+import * as schema from "~/server/schema";
-const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
+// create the connection
+const connection = connect({
+ url: env.DATABASE_URL,
+});
-export const prisma =
- globalForPrisma.prisma ||
- new PrismaClient({
- log:
- env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
- });
-
-if (env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
+export const db = drizzle(connection, { schema });
diff --git a/src/server/redis.ts b/src/server/redis.ts
index f09bb20..aeb0bcc 100644
--- a/src/server/redis.ts
+++ b/src/server/redis.ts
@@ -1,10 +1,7 @@
import { Redis } from "@upstash/redis";
-import https from "https";
import { env } from "~/env.mjs";
-export const redis = Redis.fromEnv({
- agent: new https.Agent({ keepAlive: true }),
-});
+export const redis = Redis.fromEnv();
export const setCache = async (key: string, value: T) => {
try {
@@ -20,14 +17,8 @@ export const setCache = async (key: string, value: T) => {
export const fetchCache = async (key: string) => {
try {
const result = await redis.get(`${env.APP_ENV}_${key}`);
- if (result) {
- console.log("CACHE HIT");
- } else {
- console.log("CACHE MISS");
- }
return result as T;
} catch {
- console.log("CACHE ERROR");
return null;
}
};
diff --git a/src/server/schema.ts b/src/server/schema.ts
new file mode 100644
index 0000000..ea06c05
--- /dev/null
+++ b/src/server/schema.ts
@@ -0,0 +1,65 @@
+import {
+ timestamp,
+ mysqlTable,
+ varchar,
+ boolean,
+ json,
+} from "drizzle-orm/mysql-core";
+import { relations } from "drizzle-orm";
+
+export const rooms = mysqlTable("Room", {
+ id: varchar("id", { length: 255 }).notNull().primaryKey(),
+ created_at: timestamp("created_at", {
+ mode: "date",
+ fsp: 3,
+ }).defaultNow(),
+ userId: varchar("userId", { length: 255 }).notNull(),
+ roomName: varchar("roomName", { length: 255 }),
+ storyName: varchar("storyName", { length: 255 }),
+ visible: boolean("visible").default(false).notNull(),
+ scale: varchar("scale", { length: 255 }).default("0.5,1,2,3,5").notNull(),
+});
+
+export const roomsRelations = relations(rooms, ({ many }) => ({
+ votes: many(votes),
+ logs: many(logs),
+}));
+
+export const votes = mysqlTable("Vote", {
+ id: varchar("id", { length: 255 }).notNull().primaryKey(),
+ created_at: timestamp("created_at", {
+ mode: "date",
+ fsp: 3,
+ }).defaultNow(),
+ userId: varchar("userId", { length: 255 }).notNull(),
+ roomId: varchar("roomId", { length: 255 }).notNull(),
+ value: varchar("value", { length: 255 }).notNull(),
+});
+
+export const votesRelations = relations(votes, ({ one }) => ({
+ room: one(rooms, {
+ fields: [votes.roomId],
+ references: [rooms.id],
+ }),
+}));
+
+export const logs = mysqlTable("Log", {
+ id: varchar("id", { length: 255 }).notNull().primaryKey(),
+ created_at: timestamp("created_at", {
+ mode: "date",
+ fsp: 3,
+ }).defaultNow(),
+ userId: varchar("userId", { length: 255 }).notNull(),
+ roomId: varchar("roomId", { length: 255 }).notNull(),
+ scale: varchar("scale", { length: 255 }),
+ votes: json("votes"),
+ roomName: varchar("roomName", { length: 255 }),
+ storyName: varchar("storyName", { length: 255 }),
+});
+
+export const logsRelations = relations(logs, ({ one }) => ({
+ room: one(rooms, {
+ fields: [logs.roomId],
+ references: [rooms.id],
+ }),
+}));