Drizzle!
This commit is contained in:
parent
92b057993e
commit
047ebe9c8a
12 changed files with 999 additions and 176 deletions
12
drizzle.config.ts
Normal file
12
drizzle.config.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import type { Config } from "drizzle-kit";
|
||||
import "dotenv/config";
|
||||
|
||||
export default {
|
||||
schema: "./src/server/schema.ts",
|
||||
out: "./drizzle/generated",
|
||||
driver: "mysql2",
|
||||
breakpoints: true,
|
||||
dbCredentials: {
|
||||
connectionString: process.env.DATABASE_URL!,
|
||||
},
|
||||
} satisfies Config;
|
|
@ -15,6 +15,8 @@
|
|||
"@ably-labs/react-hooks": "^2.1.1",
|
||||
"@auth/prisma-adapter": "^1.0.1",
|
||||
"@clerk/nextjs": "^4.23.2",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@planetscale/database": "^1.10.0",
|
||||
"@prisma/client": "5.1.1",
|
||||
"@react-email/components": "^0.0.7",
|
||||
"@tanstack/react-query": "^4.32.6",
|
||||
|
@ -27,6 +29,8 @@
|
|||
"@upstash/redis": "^1.22.0",
|
||||
"ably": "^1.2.43",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"dotenv": "^16.3.1",
|
||||
"drizzle-orm": "^0.28.2",
|
||||
"json2csv": "6.0.0-alpha.2",
|
||||
"next": "^13.4.13",
|
||||
"nextjs-cors": "^2.1.2",
|
||||
|
@ -49,6 +53,7 @@
|
|||
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
||||
"@typescript-eslint/parser": "^6.3.0",
|
||||
"daisyui": "^3.5.1",
|
||||
"drizzle-kit": "^0.19.12",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-config-next": "^13.4.13",
|
||||
"prisma": "5.1.1",
|
||||
|
|
796
pnpm-lock.yaml
generated
796
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -11,7 +11,7 @@ export default createNextApiHandler({
|
|||
env.NODE_ENV === "development"
|
||||
? ({ path, error }) => {
|
||||
console.error(
|
||||
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`,
|
||||
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`
|
||||
);
|
||||
}
|
||||
: undefined,
|
||||
|
|
|
@ -165,16 +165,16 @@ const RoomBody = ({}) => {
|
|||
})
|
||||
.concat({
|
||||
id: "LATEST",
|
||||
createdAt: new Date(),
|
||||
created_at: new Date(),
|
||||
userId: roomFromDb.userId,
|
||||
roomId: roomFromDb.id,
|
||||
scale: roomScale,
|
||||
votes: votesFromDb.map((vote) => {
|
||||
return {
|
||||
name: vote.userId,
|
||||
value: vote.value,
|
||||
};
|
||||
}),
|
||||
room: roomFromDb,
|
||||
roomName: roomFromDb.roomName,
|
||||
storyName: storyNameText,
|
||||
});
|
||||
|
|
|
@ -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.vote.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<number>`count(*)` }).from(votes)
|
||||
)[0];
|
||||
|
||||
const votesCount = votesResult ? Number(votesResult.count) : 0;
|
||||
|
||||
await setCache(`kv_votecount`, votesCount);
|
||||
|
||||
|
@ -48,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<number>`count(*)` }).from(rooms)
|
||||
)[0];
|
||||
|
||||
const roomsCount = roomsResult ? Number(roomsResult.count) : 0;
|
||||
|
||||
await setCache(`kv_roomcount`, roomsCount);
|
||||
|
||||
|
|
|
@ -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,18 +17,18 @@ export const roomRouter = createTRPCRouter({
|
|||
})
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const room = await ctx.prisma.room.create({
|
||||
data: {
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
const success = room.rowsAffected > 0;
|
||||
if (room) {
|
||||
await invalidateCache(`kv_roomcount`);
|
||||
console.log("PUBLISHED TO ", `kv_roomlist_${ctx.auth.userId}`);
|
||||
await invalidateCache(`kv_roomlist_${ctx.auth.userId}`);
|
||||
|
||||
await publishToChannel(
|
||||
|
@ -40,26 +43,26 @@ export const roomRouter = createTRPCRouter({
|
|||
JSON.stringify(room)
|
||||
);
|
||||
}
|
||||
// happy path
|
||||
return !!room;
|
||||
return success;
|
||||
}),
|
||||
|
||||
// Get One
|
||||
get: protectedProcedure
|
||||
.input(z.object({ id: z.string() }))
|
||||
.query(({ ctx, input }) => {
|
||||
return ctx.prisma.room.findUnique({
|
||||
where: {
|
||||
id: input.id,
|
||||
return ctx.db.query.rooms.findFirst({
|
||||
where: eq(rooms.id, input.id),
|
||||
with: {
|
||||
logs: {
|
||||
with: {
|
||||
room: true,
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
with: {
|
||||
room: true,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
userId: true,
|
||||
logs: true,
|
||||
roomName: true,
|
||||
storyName: true,
|
||||
visible: true,
|
||||
scale: true,
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
@ -77,15 +80,8 @@ export const roomRouter = createTRPCRouter({
|
|||
if (cachedResult) {
|
||||
return cachedResult;
|
||||
} else {
|
||||
const roomList = await ctx.prisma.room.findMany({
|
||||
where: {
|
||||
userId: ctx.auth.userId,
|
||||
},
|
||||
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.auth.userId}`, roomList);
|
||||
|
@ -109,26 +105,17 @@ 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: {
|
||||
userId: 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: {
|
||||
(await ctx.db.insert(logs).values({
|
||||
id: createId(),
|
||||
userId: ctx.auth.userId,
|
||||
roomId: input.roomId,
|
||||
scale: oldRoom.scale,
|
||||
|
@ -140,67 +127,54 @@ export const roomRouter = createTRPCRouter({
|
|||
}),
|
||||
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.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: {
|
||||
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.auth.userId}`);
|
||||
|
@ -212,7 +186,7 @@ export const roomRouter = createTRPCRouter({
|
|||
);
|
||||
|
||||
await publishToChannel(
|
||||
`${deletedRoom.id}`,
|
||||
`${input.id}`,
|
||||
EventTypes.ROOM_UPDATE,
|
||||
JSON.stringify(deletedRoom)
|
||||
);
|
||||
|
@ -224,6 +198,6 @@ export const roomRouter = createTRPCRouter({
|
|||
);
|
||||
}
|
||||
|
||||
return !!deletedRoom;
|
||||
return success;
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -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,10 +14,9 @@ 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;
|
||||
roomId: string;
|
||||
}[]
|
||||
|
@ -24,18 +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,
|
||||
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);
|
||||
|
@ -46,37 +37,30 @@ 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.auth.userId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
const vote = await ctx.db
|
||||
.insert(votes)
|
||||
.values({
|
||||
id: createId(),
|
||||
value: input.value,
|
||||
userId: ctx.auth.userId,
|
||||
roomId: input.roomId,
|
||||
},
|
||||
update: {
|
||||
})
|
||||
.onDuplicateKeyUpdate({
|
||||
set: {
|
||||
value: input.value,
|
||||
userId: ctx.auth.userId,
|
||||
roomId: input.roomId,
|
||||
},
|
||||
select: {
|
||||
value: true,
|
||||
userId: true,
|
||||
roomId: true,
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (vote) {
|
||||
const 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
|
||||
);
|
||||
|
@ -84,10 +68,10 @@ export const voteRouter = createTRPCRouter({
|
|||
await publishToChannel(
|
||||
`stats`,
|
||||
EventTypes.STATS_UPDATE,
|
||||
JSON.stringify(vote)
|
||||
JSON.stringify(success)
|
||||
);
|
||||
}
|
||||
|
||||
return !!vote;
|
||||
return success;
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@ import type {
|
|||
SignedOutAuthObject,
|
||||
} from "@clerk/nextjs/api";
|
||||
|
||||
import { prisma } from "../db";
|
||||
import { db } from "../db";
|
||||
|
||||
interface AuthContext {
|
||||
auth: SignedInAuthObject | SignedOutAuthObject;
|
||||
|
@ -40,7 +40,7 @@ interface AuthContext {
|
|||
const createInnerTRPCContext = ({ auth }: AuthContext) => {
|
||||
return {
|
||||
auth,
|
||||
prisma,
|
||||
db,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -61,7 +61,7 @@ export const createTRPCContext = (opts: CreateNextContextOptions) => {
|
|||
*/
|
||||
import { initTRPC, TRPCError } from "@trpc/server";
|
||||
import superjson from "superjson";
|
||||
import { OpenApiMeta } from "trpc-openapi";
|
||||
import type { OpenApiMeta } from "trpc-openapi";
|
||||
|
||||
const t = initTRPC
|
||||
.context<typeof createTRPCContext>()
|
||||
|
|
|
@ -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 };
|
||||
|
||||
export const prisma =
|
||||
globalForPrisma.prisma ||
|
||||
new PrismaClient({
|
||||
log:
|
||||
env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
|
||||
// create the connection
|
||||
const connection = connect({
|
||||
url: env.DATABASE_URL,
|
||||
});
|
||||
|
||||
if (env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
|
||||
export const db = drizzle(connection, { schema });
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
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 <T>(key: string, value: T) => {
|
||||
try {
|
||||
console.log("KEY: ", key);
|
||||
await redis.set(`${env.APP_ENV}_${key}`, value, {
|
||||
ex: Number(env.UPSTASH_REDIS_EXPIRY_SECONDS),
|
||||
});
|
||||
|
@ -21,14 +17,8 @@ export const setCache = async <T>(key: string, value: T) => {
|
|||
export const fetchCache = async <T>(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;
|
||||
}
|
||||
};
|
||||
|
|
65
src/server/schema.ts
Normal file
65
src/server/schema.ts
Normal file
|
@ -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],
|
||||
}),
|
||||
}));
|
Loading…
Add table
Reference in a new issue