From b50bb7388a25ec3ca6b8a1383f9a913bf581f982 Mon Sep 17 00:00:00 2001 From: Penumarthi Navaneeth Date: Thu, 31 Oct 2024 12:59:54 +0530 Subject: [PATCH 01/10] Feature/ Add series to link related articles --- .../create/[[...paramsArr]]/_client.tsx | 29 +++- drizzle/0011_add_series_update_post.sql | 14 ++ schema/post.ts | 1 + schema/series.ts | 6 + server/api/router/index.ts | 3 + server/api/router/post.ts | 28 +++- server/api/router/series.ts | 137 ++++++++++++++++++ server/db/schema.ts | 28 ++++ 8 files changed, 240 insertions(+), 6 deletions(-) create mode 100644 drizzle/0011_add_series_update_post.sql create mode 100644 schema/series.ts create mode 100644 server/api/router/series.ts diff --git a/app/(editor)/create/[[...paramsArr]]/_client.tsx b/app/(editor)/create/[[...paramsArr]]/_client.tsx index 5b1852f9..6a11fe19 100644 --- a/app/(editor)/create/[[...paramsArr]]/_client.tsx +++ b/app/(editor)/create/[[...paramsArr]]/_client.tsx @@ -38,6 +38,8 @@ import { getUploadUrl } from "@/app/actions/getUploadUrl"; import EditorNav from "./navigation"; import { type Session } from "next-auth"; +import { TRPCClientErrorLike } from '@trpc/client'; + const Create = ({ session }: { session: Session | null }) => { const params = useParams(); const router = useRouter(); @@ -161,6 +163,14 @@ const Create = ({ session }: { session: Session | null }) => { Sentry.captureException(error); }, }); + + const { mutate: seriesUpdate, status: seriesStatus } = api.series.update.useMutation({ + onError(error) { + toast.error("Error updating series"); + Sentry.captureException(error); + } + }); + const { mutate: create, data: createData, @@ -217,6 +227,7 @@ const Create = ({ session }: { session: Session | null }) => { tags, canonicalUrl: data.canonicalUrl || undefined, excerpt: data.excerpt || removeMarkdown(data.body, {}).substring(0, 155), + seriesName: data.seriesName || undefined }; return formData; }; @@ -229,6 +240,8 @@ const Create = ({ session }: { session: Session | null }) => { await create({ ...formData }); } else { await save({ ...formData, id: postId }); + await seriesUpdate({ postId, seriesName: formData.seriesName }); + setSavedTime( new Date().toLocaleString(undefined, { dateStyle: "medium", @@ -564,10 +577,24 @@ const Create = ({ session }: { session: Session | null }) => { {copied ? "Copied" : "Copy Link"} -

+

Share this link with others to preview your draft. Anyone with the link can view your draft.

+ + + +

+ This text is case-sensitive so make sure you type it exactly as you did in previous articles to ensure they are connected +

)} diff --git a/drizzle/0011_add_series_update_post.sql b/drizzle/0011_add_series_update_post.sql new file mode 100644 index 00000000..213b2c07 --- /dev/null +++ b/drizzle/0011_add_series_update_post.sql @@ -0,0 +1,14 @@ +-- Create Series table +CREATE TABLE IF NOT EXISTS "Series" ( + "id" SERIAL PRIMARY KEY, + "name" TEXT NOT NULL, + "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL +); +-- Update Post table to add seriesId column +ALTER TABLE "Post" +ADD COLUMN "seriesId" INTEGER +ADD CONSTRAINT fk_post_series + FOREIGN KEY ("seriesId") + REFERENCES "Series" ("id") + ON DELETE SET NULL; \ No newline at end of file diff --git a/schema/post.ts b/schema/post.ts index 224e8940..99ec60ba 100644 --- a/schema/post.ts +++ b/schema/post.ts @@ -25,6 +25,7 @@ export const SavePostSchema = z.object({ canonicalUrl: z.optional(z.string().trim().url()), tags: z.string().array().max(5).optional(), published: z.string().datetime().optional(), + seriesName: z.string().trim().optional() }); export const PublishPostSchema = z.object({ diff --git a/schema/series.ts b/schema/series.ts new file mode 100644 index 00000000..e457b4cb --- /dev/null +++ b/schema/series.ts @@ -0,0 +1,6 @@ +import z from "zod"; + +export const UpdateSeriesSchema = z.object({ + postId: z.string(), + seriesName: z.string().trim().optional() +}); \ No newline at end of file diff --git a/server/api/router/index.ts b/server/api/router/index.ts index d7274a63..26709ba3 100644 --- a/server/api/router/index.ts +++ b/server/api/router/index.ts @@ -8,6 +8,8 @@ import { adminRouter } from "./admin"; import { reportRouter } from "./report"; import { tagRouter } from "./tag"; +import { seriesRouter } from "./series"; + export const appRouter = createTRPCRouter({ post: postRouter, profile: profileRouter, @@ -16,6 +18,7 @@ export const appRouter = createTRPCRouter({ admin: adminRouter, report: reportRouter, tag: tagRouter, + series: seriesRouter }); // export type definition of API diff --git a/server/api/router/post.ts b/server/api/router/post.ts index 8a41482b..5aba3a2b 100644 --- a/server/api/router/post.ts +++ b/server/api/router/post.ts @@ -14,7 +14,7 @@ import { GetLimitSidePosts, } from "../../../schema/post"; import { removeMarkdown } from "../../../utils/removeMarkdown"; -import { bookmark, like, post, post_tag, tag, user } from "@/server/db/schema"; +import { bookmark, like, post, post_tag, tag, user, series } from "@/server/db/schema"; import { and, eq, @@ -187,10 +187,27 @@ export const postRouter = createTRPCRouter({ }); } - const [deletedPost] = await ctx.db - .delete(post) - .where(eq(post.id, id)) - .returning(); + const deletedPost = await ctx.db.transaction(async (tx) => { + const [deletedPost] = await tx + .delete(post) + .where(eq(post.id, id)) + .returning(); + + if(deletedPost.seriesId){ + // check is there is any other post with the current seriesId + const anotherPostInThisSeries = await tx.query.post.findFirst({ + where: (post, { eq }) => + eq(post.seriesId, deletedPost.seriesId!) + }) + // if another post with the same seriesId is present, then do nothing + // else remove the series from the series table + if(!anotherPostInThisSeries){ + await tx.delete(series).where(eq(series.id, deletedPost.seriesId)); + } + } + + return deletedPost; + }); return deletedPost; }), @@ -428,6 +445,7 @@ export const postRouter = createTRPCRouter({ where: (posts, { eq }) => eq(posts.id, id), with: { tags: { with: { tag: true } }, + series: true }, }); diff --git a/server/api/router/series.ts b/server/api/router/series.ts new file mode 100644 index 00000000..17893eff --- /dev/null +++ b/server/api/router/series.ts @@ -0,0 +1,137 @@ +import { TRPCError } from "@trpc/server"; +import { createTRPCRouter, protectedProcedure } from "../trpc"; +import { series, post } from "@/server/db/schema"; +import { UpdateSeriesSchema } from "@/schema/series"; +import {eq, and} from "drizzle-orm"; +export const seriesRouter = createTRPCRouter({ + update: protectedProcedure + .input(UpdateSeriesSchema) + .mutation(async ({input, ctx}) => { + const {postId, seriesName} = input; + + if (seriesName && seriesName.trim() === "") { + throw new TRPCError({ code: 'BAD_REQUEST', message: 'Series name cannot be empty' }); + } + + const currentPost = await ctx.db.query.post.findFirst({ + columns: { + id: true, + seriesId: true, + userId: true + }, + with: { + series: { + columns: { + id: true, + name: true + }, + }, + }, + where: (post, { eq }) => eq(post.id, postId), + }); + + if (!currentPost) { + throw new TRPCError({ code: 'NOT_FOUND' }); + } + if (currentPost?.userId !== ctx.session.user.id) { + throw new TRPCError({ + code: "FORBIDDEN", + }); + } + const createNewSeries = async (seriesTitle: string) => { + // check if a series with that name already exists + // or else create a new one + return await ctx.db.transaction(async (tx) => { + let seriesId : number; + const currSeries = await tx.query.series.findFirst({ + columns: { + id: true + }, + where: (series, { eq, and }) => and( + eq(series.name, seriesTitle), + eq(series.userId, ctx.session.user.id) + ), + }) + + if(!currSeries){ + const [newSeries] = await tx.insert(series).values({ + name: seriesTitle, + userId: ctx.session.user.id, + updatedAt: new Date() + }).returning(); + + seriesId = newSeries.id; + } + else{ + seriesId = currSeries.id; + } + // update that series id in the current post + await tx + .update(post) + .set({ + seriesId: seriesId + }) + .where(eq(post.id, currentPost.id)); + }) + + } + + const unlinkSeries = async (seriesId: number) => { + // Check if the user has added a another post with the same series id previously + return await ctx.db.transaction(async (tx) =>{ + const anotherPostInThisSeries = await tx.query.post.findFirst({ + where: (post, { eq, and, ne }) => + and ( + ne(post.id, currentPost.id), + eq(post.seriesId, seriesId) + ) + }) + // if another post with the same seriesId is present, then do nothing + // else remove the series from the series table + if(!anotherPostInThisSeries){ + await tx.delete(series).where( + and( + eq(series.id, seriesId), + eq(series.userId, ctx.session.user.id) + ) + ); + } + // update that series id in the current post + await tx + .update(post) + .set({ + seriesId: null + }) + .where(eq(post.id, currentPost.id)); + }) + } + + if(seriesName){ + // check if the current post is already linked to a series + if(currentPost?.seriesId){ + // check if the series title is same as the current series name + // then we do nothing + if(currentPost?.series?.name !== seriesName){ + // then the user has updated the series name in this particular edit + // Check if there is another post with the same title, else delete the series + // and create a new post with the new series name + // and update that new series id in the post + await unlinkSeries(currentPost.seriesId); + await createNewSeries(seriesName); + } + } + else{ + // the current post is not yet linked to a seriesId + // so create a new series and put that Id in the post + await createNewSeries(seriesName); + } + } + else{ + // either the user has not added the series Name (We do nothing) + // or while editing the post, the user has removed the series name + if(currentPost.seriesId !== null){ + await unlinkSeries(currentPost.seriesId); + } + } + }) +}) \ No newline at end of file diff --git a/server/db/schema.ts b/server/db/schema.ts index ce7a53e6..bf8a7573 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -35,6 +35,27 @@ export const sessionRelations = relations(session, ({ one }) => ({ }), })); +export const series = pgTable("Series", { + id: serial("id").primaryKey(), + name: text("title").notNull(), + description: text("description"), + userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" }), + createdAt: timestamp("createdAt", { + precision: 3, + mode: "string", + withTimezone: true, + }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updatedAt", { + precision: 3, + withTimezone: true + }).notNull() + .$onUpdate(() => new Date()) + .default(sql`CURRENT_TIMESTAMP`), +}) + + export const account = pgTable( "account", { @@ -149,6 +170,7 @@ export const post = pgTable( .references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" }), showComments: boolean("showComments").default(true).notNull(), likes: integer("likes").default(0).notNull(), + seriesId: integer("seriesId").references(() => series.id, { onDelete: "set null", onUpdate: "cascade" }), }, (table) => { return { @@ -168,6 +190,7 @@ export const postRelations = relations(post, ({ one, many }) => ({ notifications: many(notification), user: one(user, { fields: [post.userId], references: [user.id] }), tags: many(post_tag), + series: one(series,{ fields: [post.seriesId], references: [series.id] }), })); export const user = pgTable( @@ -273,6 +296,11 @@ export const bookmark = pgTable( }, ); + +export const seriesRelations = relations(series, ({ one, many }) => ({ + posts: many(post), +})); + export const bookmarkRelations = relations(bookmark, ({ one, many }) => ({ post: one(post, { fields: [bookmark.postId], references: [post.id] }), user: one(user, { fields: [bookmark.userId], references: [user.id] }), From 490269d57f733d6605628b2c8e25be0ddb4bde54 Mon Sep 17 00:00:00 2001 From: Penumarthi Navaneeth Date: Thu, 31 Oct 2024 13:00:46 +0530 Subject: [PATCH 02/10] Feature/ Add series to link related articles --- app/(editor)/create/[[...paramsArr]]/_client.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/(editor)/create/[[...paramsArr]]/_client.tsx b/app/(editor)/create/[[...paramsArr]]/_client.tsx index 6a11fe19..b9f6434b 100644 --- a/app/(editor)/create/[[...paramsArr]]/_client.tsx +++ b/app/(editor)/create/[[...paramsArr]]/_client.tsx @@ -244,7 +244,7 @@ const Create = ({ session }: { session: Session | null }) => { setSavedTime( new Date().toLocaleString(undefined, { - dateStyle: "medium", + dateStyle: "medium",git timeStyle: "short", }), ); From c766749c0c3775c6c7f35907580f126122a9c0ab Mon Sep 17 00:00:00 2001 From: Penumarthi Navaneeth Date: Thu, 31 Oct 2024 13:24:11 +0530 Subject: [PATCH 03/10] Feature/ Add series to link related articles --- app/(editor)/create/[[...paramsArr]]/_client.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/(editor)/create/[[...paramsArr]]/_client.tsx b/app/(editor)/create/[[...paramsArr]]/_client.tsx index b9f6434b..cec6528a 100644 --- a/app/(editor)/create/[[...paramsArr]]/_client.tsx +++ b/app/(editor)/create/[[...paramsArr]]/_client.tsx @@ -38,8 +38,6 @@ import { getUploadUrl } from "@/app/actions/getUploadUrl"; import EditorNav from "./navigation"; import { type Session } from "next-auth"; -import { TRPCClientErrorLike } from '@trpc/client'; - const Create = ({ session }: { session: Session | null }) => { const params = useParams(); const router = useRouter(); @@ -244,7 +242,7 @@ const Create = ({ session }: { session: Session | null }) => { setSavedTime( new Date().toLocaleString(undefined, { - dateStyle: "medium",git + dateStyle: "medium", timeStyle: "short", }), ); From 6a05e9eac4a9df752cc388054d1be73a9f4785f2 Mon Sep 17 00:00:00 2001 From: Penumarthi Navaneeth Date: Thu, 31 Oct 2024 13:58:37 +0530 Subject: [PATCH 04/10] Feature/ Add series to link related articles --- drizzle/0011_add_series_update_post.sql | 6 +- package-lock.json | 459 +++++++++++++++++++++++- package.json | 1 + schema/post.ts | 4 +- server/api/router/series.ts | 2 +- server/db/schema.ts | 4 +- 6 files changed, 469 insertions(+), 7 deletions(-) diff --git a/drizzle/0011_add_series_update_post.sql b/drizzle/0011_add_series_update_post.sql index 213b2c07..3bdcd6fe 100644 --- a/drizzle/0011_add_series_update_post.sql +++ b/drizzle/0011_add_series_update_post.sql @@ -2,6 +2,7 @@ CREATE TABLE IF NOT EXISTS "Series" ( "id" SERIAL PRIMARY KEY, "name" TEXT NOT NULL, + "userId" text NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL ); @@ -11,4 +12,7 @@ ADD COLUMN "seriesId" INTEGER ADD CONSTRAINT fk_post_series FOREIGN KEY ("seriesId") REFERENCES "Series" ("id") - ON DELETE SET NULL; \ No newline at end of file + ON DELETE SET NULL; +CREATE INDEX idx_post_series ON "Post"("seriesId"); +CREATE INDEX idx_series_name ON "Series"("name"); +ALTER TABLE series ADD CONSTRAINT unique_series_name_per_user UNIQUE (name, userId); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 37e073df..af2587ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,6 +91,7 @@ "superjson": "^2.2.1", "tailwind-merge": "^2.5.2", "tiptap-markdown": "^0.8.10", + "tsx": "^4.19.2", "zod": "^3.23.8" }, "devDependencies": { @@ -4084,6 +4085,22 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { "version": "0.19.12", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", @@ -12637,7 +12654,6 @@ "version": "4.8.1", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", - "dev": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -17351,7 +17367,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } @@ -18679,6 +18694,446 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, + "node_modules/tsx": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", + "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==", + "license": "MIT", + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, + "node_modules/tsx/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 96a67dd0..ab53b10c 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "superjson": "^2.2.1", "tailwind-merge": "^2.5.2", "tiptap-markdown": "^0.8.10", + "tsx": "^4.19.2", "zod": "^3.23.8" }, "devDependencies": { diff --git a/schema/post.ts b/schema/post.ts index 99ec60ba..e2415550 100644 --- a/schema/post.ts +++ b/schema/post.ts @@ -25,7 +25,9 @@ export const SavePostSchema = z.object({ canonicalUrl: z.optional(z.string().trim().url()), tags: z.string().array().max(5).optional(), published: z.string().datetime().optional(), - seriesName: z.string().trim().optional() + seriesName: z.string() + .trim() + .optional() }); export const PublishPostSchema = z.object({ diff --git a/server/api/router/series.ts b/server/api/router/series.ts index 17893eff..28254c77 100644 --- a/server/api/router/series.ts +++ b/server/api/router/series.ts @@ -9,7 +9,7 @@ export const seriesRouter = createTRPCRouter({ .mutation(async ({input, ctx}) => { const {postId, seriesName} = input; - if (seriesName && seriesName.trim() === "") { + if (seriesName?.trim() === "") { throw new TRPCError({ code: 'BAD_REQUEST', message: 'Series name cannot be empty' }); } diff --git a/server/db/schema.ts b/server/db/schema.ts index bf8a7573..8d7b02b1 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -35,9 +35,9 @@ export const sessionRelations = relations(session, ({ one }) => ({ }), })); -export const series = pgTable("Series", { +export const series = pgTable("series", { id: serial("id").primaryKey(), - name: text("title").notNull(), + name: text("name").notNull(), description: text("description"), userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" }), createdAt: timestamp("createdAt", { From e9b3c24e50547f120032f11b6fab8318562a9482 Mon Sep 17 00:00:00 2001 From: Penumarthi Navaneeth Date: Mon, 4 Nov 2024 20:38:26 +0530 Subject: [PATCH 05/10] Feature/ Add series to link related articles --- .../create/[[...paramsArr]]/_client.tsx | 8 - server/api/router/index.ts | 5 +- server/api/router/post.ts | 211 +++++++++++++----- server/api/router/series.ts | 137 ------------ server/db/schema.ts | 2 +- 5 files changed, 153 insertions(+), 210 deletions(-) delete mode 100644 server/api/router/series.ts diff --git a/app/(editor)/create/[[...paramsArr]]/_client.tsx b/app/(editor)/create/[[...paramsArr]]/_client.tsx index 776cb31a..62c483a6 100644 --- a/app/(editor)/create/[[...paramsArr]]/_client.tsx +++ b/app/(editor)/create/[[...paramsArr]]/_client.tsx @@ -162,13 +162,6 @@ const Create = ({ session }: { session: Session | null }) => { }, }); - const { mutate: seriesUpdate, status: seriesStatus } = api.series.update.useMutation({ - onError(error) { - toast.error("Error updating series"); - Sentry.captureException(error); - } - }); - const { mutate: create, data: createData, @@ -238,7 +231,6 @@ const Create = ({ session }: { session: Session | null }) => { await create({ ...formData }); } else { await save({ ...formData, id: postId }); - await seriesUpdate({ postId, seriesName: formData.seriesName }); setSavedTime( new Date().toLocaleString(undefined, { diff --git a/server/api/router/index.ts b/server/api/router/index.ts index 26709ba3..49b12df9 100644 --- a/server/api/router/index.ts +++ b/server/api/router/index.ts @@ -8,8 +8,6 @@ import { adminRouter } from "./admin"; import { reportRouter } from "./report"; import { tagRouter } from "./tag"; -import { seriesRouter } from "./series"; - export const appRouter = createTRPCRouter({ post: postRouter, profile: profileRouter, @@ -17,8 +15,7 @@ export const appRouter = createTRPCRouter({ notification: notificationRouter, admin: adminRouter, report: reportRouter, - tag: tagRouter, - series: seriesRouter + tag: tagRouter }); // export type definition of API diff --git a/server/api/router/post.ts b/server/api/router/post.ts index 5aba3a2b..0f38fb2d 100644 --- a/server/api/router/post.ts +++ b/server/api/router/post.ts @@ -52,10 +52,13 @@ export const postRouter = createTRPCRouter({ update: protectedProcedure .input(SavePostSchema) .mutation(async ({ input, ctx }) => { - const { id, body, title, excerpt, canonicalUrl, tags = [] } = input; + const { id, body, title, excerpt, canonicalUrl, tags = [], seriesName } = input; const currentPost = await ctx.db.query.post.findFirst({ where: (posts, { eq }) => eq(posts.id, id), + with: { + series: true + }, }); if (currentPost?.userId !== ctx.session.user.id) { @@ -64,6 +67,94 @@ export const postRouter = createTRPCRouter({ }); } + // series + const postId = currentPost.id; + + if (seriesName?.trim() === "") { + throw new TRPCError({ code: 'BAD_REQUEST', message: 'Series name cannot be empty' }); + } + + const createNewSeries = async (seriesTitle: string) => { + return await ctx.db.transaction(async (tx) => { + let seriesId: number; + const currSeries = await tx.query.series.findFirst({ + columns: { + id: true + }, + where: (series, { eq, and }) => and( + eq(series.name, seriesTitle), + eq(series.userId, ctx.session.user.id) + ), + }) + + if (!currSeries) { + const [newSeries] = await tx.insert(series).values({ + name: seriesTitle, + userId: ctx.session.user.id, + updatedAt: new Date() + }).returning(); + + seriesId = newSeries.id; + } + else { + seriesId = currSeries.id; + } + await tx + .update(post) + .set({ + seriesId: seriesId + }) + .where(eq(post.id, currentPost.id)); + }) + + } + + const unlinkSeries = async (seriesId: number) => { + return await ctx.db.transaction(async (tx) => { + const anotherPostInThisSeries = await tx.query.post.findFirst({ + where: (post, { eq, and, ne }) => + and( + ne(post.id, currentPost.id), + eq(post.seriesId, seriesId) + ) + }) + if (!anotherPostInThisSeries) { + await tx.delete(series).where( + and( + eq(series.id, seriesId), + eq(series.userId, ctx.session.user.id) + ) + ); + } + // update that series id in the current post + await tx + .update(post) + .set({ + seriesId: null + }) + .where(eq(post.id, currentPost.id)); + }) + } + + if (seriesName) { + if (currentPost?.seriesId) { + if (currentPost?.series?.name !== seriesName) { + await unlinkSeries(currentPost.seriesId); + await createNewSeries(seriesName); + } + } + else { + await createNewSeries(seriesName); + } + } + else { + if (currentPost.seriesId !== null) { + await unlinkSeries(currentPost.seriesId); + } + } + + + // if user doesnt link any tags to the article no point in doing the tag operations // This also makes autosave during writing faster if (tags.length > 0) { @@ -105,7 +196,7 @@ export const postRouter = createTRPCRouter({ return excerpt && excerpt.length > 0 ? excerpt : // @Todo why is body string | null ? - removeMarkdown(currentPost.body as string, {}).substring(0, 156); + removeMarkdown(currentPost.body as string, {}).substring(0, 156); } return excerpt; }; @@ -193,18 +284,18 @@ export const postRouter = createTRPCRouter({ .where(eq(post.id, id)) .returning(); - if(deletedPost.seriesId){ - // check is there is any other post with the current seriesId - const anotherPostInThisSeries = await tx.query.post.findFirst({ - where: (post, { eq }) => - eq(post.seriesId, deletedPost.seriesId!) - }) - // if another post with the same seriesId is present, then do nothing - // else remove the series from the series table - if(!anotherPostInThisSeries){ - await tx.delete(series).where(eq(series.id, deletedPost.seriesId)); - } + if (deletedPost.seriesId) { + // check is there is any other post with the current seriesId + const anotherPostInThisSeries = await tx.query.post.findFirst({ + where: (post, { eq }) => + eq(post.seriesId, deletedPost.seriesId!) + }) + // if another post with the same seriesId is present, then do nothing + // else remove the series from the series table + if (!anotherPostInThisSeries) { + await tx.delete(series).where(eq(series.id, deletedPost.seriesId)); } + } return deletedPost; }); @@ -220,33 +311,33 @@ export const postRouter = createTRPCRouter({ setLiked ? await ctx.db.transaction(async (tx) => { - res = await tx.insert(like).values({ postId, userId }).returning(); + res = await tx.insert(like).values({ postId, userId }).returning(); + await tx + .update(post) + .set({ + likes: increment(post.likes), + }) + .where(eq(post.id, postId)); + }) + : await ctx.db.transaction(async (tx) => { + res = await tx + .delete(like) + .where( + and( + eq(like.postId, postId), + eq(like.userId, ctx.session?.user?.id), + ), + ) + .returning(); + if (res.length !== 0) { await tx .update(post) .set({ - likes: increment(post.likes), + likes: decrement(post.likes), }) .where(eq(post.id, postId)); - }) - : await ctx.db.transaction(async (tx) => { - res = await tx - .delete(like) - .where( - and( - eq(like.postId, postId), - eq(like.userId, ctx.session?.user?.id), - ), - ) - .returning(); - if (res.length !== 0) { - await tx - .update(post) - .set({ - likes: decrement(post.likes), - }) - .where(eq(post.id, postId)); - } - }); + } + }); return res; }), @@ -258,16 +349,16 @@ export const postRouter = createTRPCRouter({ setBookmarked ? await ctx.db - .insert(bookmark) - .values({ postId, userId: ctx.session?.user?.id }) + .insert(bookmark) + .values({ postId, userId: ctx.session?.user?.id }) : await ctx.db - .delete(bookmark) - .where( - and( - eq(bookmark.postId, postId), - eq(bookmark.userId, ctx.session?.user?.id), - ), - ); + .delete(bookmark) + .where( + and( + eq(bookmark.postId, postId), + eq(bookmark.userId, ctx.session?.user?.id), + ), + ); return res; }), sidebarData: publicProcedure @@ -284,26 +375,26 @@ export const postRouter = createTRPCRouter({ // if user not logged in and they wont have any liked posts so default to a count of 0 ctx.session?.user?.id ? ctx.db - .selectDistinct() - .from(like) - .where( - and( - eq(like.postId, id), - eq(like.userId, ctx.session.user.id), - ), - ) + .selectDistinct() + .from(like) + .where( + and( + eq(like.postId, id), + eq(like.userId, ctx.session.user.id), + ), + ) : [false], // if user not logged in and they wont have any bookmarked posts so default to a count of 0 ctx.session?.user?.id ? ctx.db - .selectDistinct() - .from(bookmark) - .where( - and( - eq(bookmark.postId, id), - eq(bookmark.userId, ctx.session.user.id), - ), - ) + .selectDistinct() + .from(bookmark) + .where( + and( + eq(bookmark.postId, id), + eq(bookmark.userId, ctx.session.user.id), + ), + ) : [false], ]); return { diff --git a/server/api/router/series.ts b/server/api/router/series.ts deleted file mode 100644 index 28254c77..00000000 --- a/server/api/router/series.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { TRPCError } from "@trpc/server"; -import { createTRPCRouter, protectedProcedure } from "../trpc"; -import { series, post } from "@/server/db/schema"; -import { UpdateSeriesSchema } from "@/schema/series"; -import {eq, and} from "drizzle-orm"; -export const seriesRouter = createTRPCRouter({ - update: protectedProcedure - .input(UpdateSeriesSchema) - .mutation(async ({input, ctx}) => { - const {postId, seriesName} = input; - - if (seriesName?.trim() === "") { - throw new TRPCError({ code: 'BAD_REQUEST', message: 'Series name cannot be empty' }); - } - - const currentPost = await ctx.db.query.post.findFirst({ - columns: { - id: true, - seriesId: true, - userId: true - }, - with: { - series: { - columns: { - id: true, - name: true - }, - }, - }, - where: (post, { eq }) => eq(post.id, postId), - }); - - if (!currentPost) { - throw new TRPCError({ code: 'NOT_FOUND' }); - } - if (currentPost?.userId !== ctx.session.user.id) { - throw new TRPCError({ - code: "FORBIDDEN", - }); - } - const createNewSeries = async (seriesTitle: string) => { - // check if a series with that name already exists - // or else create a new one - return await ctx.db.transaction(async (tx) => { - let seriesId : number; - const currSeries = await tx.query.series.findFirst({ - columns: { - id: true - }, - where: (series, { eq, and }) => and( - eq(series.name, seriesTitle), - eq(series.userId, ctx.session.user.id) - ), - }) - - if(!currSeries){ - const [newSeries] = await tx.insert(series).values({ - name: seriesTitle, - userId: ctx.session.user.id, - updatedAt: new Date() - }).returning(); - - seriesId = newSeries.id; - } - else{ - seriesId = currSeries.id; - } - // update that series id in the current post - await tx - .update(post) - .set({ - seriesId: seriesId - }) - .where(eq(post.id, currentPost.id)); - }) - - } - - const unlinkSeries = async (seriesId: number) => { - // Check if the user has added a another post with the same series id previously - return await ctx.db.transaction(async (tx) =>{ - const anotherPostInThisSeries = await tx.query.post.findFirst({ - where: (post, { eq, and, ne }) => - and ( - ne(post.id, currentPost.id), - eq(post.seriesId, seriesId) - ) - }) - // if another post with the same seriesId is present, then do nothing - // else remove the series from the series table - if(!anotherPostInThisSeries){ - await tx.delete(series).where( - and( - eq(series.id, seriesId), - eq(series.userId, ctx.session.user.id) - ) - ); - } - // update that series id in the current post - await tx - .update(post) - .set({ - seriesId: null - }) - .where(eq(post.id, currentPost.id)); - }) - } - - if(seriesName){ - // check if the current post is already linked to a series - if(currentPost?.seriesId){ - // check if the series title is same as the current series name - // then we do nothing - if(currentPost?.series?.name !== seriesName){ - // then the user has updated the series name in this particular edit - // Check if there is another post with the same title, else delete the series - // and create a new post with the new series name - // and update that new series id in the post - await unlinkSeries(currentPost.seriesId); - await createNewSeries(seriesName); - } - } - else{ - // the current post is not yet linked to a seriesId - // so create a new series and put that Id in the post - await createNewSeries(seriesName); - } - } - else{ - // either the user has not added the series Name (We do nothing) - // or while editing the post, the user has removed the series name - if(currentPost.seriesId !== null){ - await unlinkSeries(currentPost.seriesId); - } - } - }) -}) \ No newline at end of file diff --git a/server/db/schema.ts b/server/db/schema.ts index 8d7b02b1..d245b4f9 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -35,7 +35,7 @@ export const sessionRelations = relations(session, ({ one }) => ({ }), })); -export const series = pgTable("series", { +export const series = pgTable("Series", { id: serial("id").primaryKey(), name: text("name").notNull(), description: text("description"), From 93a83fe7d32a5712fe66eed581c836a490993273 Mon Sep 17 00:00:00 2001 From: Penumarthi Navaneeth Date: Mon, 4 Nov 2024 20:44:03 +0530 Subject: [PATCH 06/10] Feature/ Add series to link related articles --- package-lock.json | 1 - package.json | 1 - 2 files changed, 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index af2587ce..9272b5f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,7 +91,6 @@ "superjson": "^2.2.1", "tailwind-merge": "^2.5.2", "tiptap-markdown": "^0.8.10", - "tsx": "^4.19.2", "zod": "^3.23.8" }, "devDependencies": { diff --git a/package.json b/package.json index ab53b10c..96a67dd0 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,6 @@ "superjson": "^2.2.1", "tailwind-merge": "^2.5.2", "tiptap-markdown": "^0.8.10", - "tsx": "^4.19.2", "zod": "^3.23.8" }, "devDependencies": { From b8603c18f2d17997dfc3335fad90714d7df04c56 Mon Sep 17 00:00:00 2001 From: Penumarthi Navaneeth Date: Mon, 4 Nov 2024 20:48:14 +0530 Subject: [PATCH 07/10] Feature/ Add series to link related articles --- package-lock.json | 460 +--------------------------------------------- 1 file changed, 3 insertions(+), 457 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9272b5f9..0e1ee4b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4084,22 +4084,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", - "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/openbsd-x64": { "version": "0.19.12", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", @@ -12653,6 +12637,7 @@ "version": "4.8.1", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "dev": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -17366,6 +17351,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } @@ -18693,446 +18679,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, - "node_modules/tsx": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", - "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==", - "license": "MIT", - "dependencies": { - "esbuild": "~0.23.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", - "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", - "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", - "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", - "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", - "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", - "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", - "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", - "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", - "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", - "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ia32": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", - "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-loong64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", - "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", - "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", - "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", - "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-s390x": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", - "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", - "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", - "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", - "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/sunos-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", - "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", - "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-ia32": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", - "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", - "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/esbuild": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", - "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.23.1", - "@esbuild/android-arm": "0.23.1", - "@esbuild/android-arm64": "0.23.1", - "@esbuild/android-x64": "0.23.1", - "@esbuild/darwin-arm64": "0.23.1", - "@esbuild/darwin-x64": "0.23.1", - "@esbuild/freebsd-arm64": "0.23.1", - "@esbuild/freebsd-x64": "0.23.1", - "@esbuild/linux-arm": "0.23.1", - "@esbuild/linux-arm64": "0.23.1", - "@esbuild/linux-ia32": "0.23.1", - "@esbuild/linux-loong64": "0.23.1", - "@esbuild/linux-mips64el": "0.23.1", - "@esbuild/linux-ppc64": "0.23.1", - "@esbuild/linux-riscv64": "0.23.1", - "@esbuild/linux-s390x": "0.23.1", - "@esbuild/linux-x64": "0.23.1", - "@esbuild/netbsd-x64": "0.23.1", - "@esbuild/openbsd-arm64": "0.23.1", - "@esbuild/openbsd-x64": "0.23.1", - "@esbuild/sunos-x64": "0.23.1", - "@esbuild/win32-arm64": "0.23.1", - "@esbuild/win32-ia32": "0.23.1", - "@esbuild/win32-x64": "0.23.1" - } - }, - "node_modules/tsx/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -20105,4 +19651,4 @@ } } } -} +} \ No newline at end of file From 1a2320d940eb69399ea6d9dd776a75772cd4d2c2 Mon Sep 17 00:00:00 2001 From: Penumarthi Navaneeth Date: Mon, 4 Nov 2024 20:54:04 +0530 Subject: [PATCH 08/10] Feature/ Add series to link related articles --- schema/post.ts | 7 +++++-- schema/series.ts | 6 ------ 2 files changed, 5 insertions(+), 8 deletions(-) delete mode 100644 schema/series.ts diff --git a/schema/post.ts b/schema/post.ts index e2415550..30aa730c 100644 --- a/schema/post.ts +++ b/schema/post.ts @@ -26,8 +26,11 @@ export const SavePostSchema = z.object({ tags: z.string().array().max(5).optional(), published: z.string().datetime().optional(), seriesName: z.string() - .trim() - .optional() + .trim() + .min(1, "Series name cannot be empty") + .max(50, "Series name is too long") + .regex(/^[\w\s-]+$/, "Series name can only contain letters, numbers, spaces, and hyphens") + .optional() }); export const PublishPostSchema = z.object({ diff --git a/schema/series.ts b/schema/series.ts deleted file mode 100644 index e457b4cb..00000000 --- a/schema/series.ts +++ /dev/null @@ -1,6 +0,0 @@ -import z from "zod"; - -export const UpdateSeriesSchema = z.object({ - postId: z.string(), - seriesName: z.string().trim().optional() -}); \ No newline at end of file From 2b75c06afc326db9e7d3cbbdb2c3f8650d44d1ed Mon Sep 17 00:00:00 2001 From: Penumarthi Navaneeth Date: Mon, 4 Nov 2024 21:25:41 +0530 Subject: [PATCH 09/10] Feature/ Add series to link related articles --- drizzle/0000_initial_schema.sql | 18 +++++++++++++++++- drizzle/0011_add_series_update_post.sql | 18 ------------------ server/db/schema.ts | 1 - 3 files changed, 17 insertions(+), 20 deletions(-) delete mode 100644 drizzle/0011_add_series_update_post.sql diff --git a/drizzle/0000_initial_schema.sql b/drizzle/0000_initial_schema.sql index b0338104..566f15d1 100644 --- a/drizzle/0000_initial_schema.sql +++ b/drizzle/0000_initial_schema.sql @@ -21,7 +21,8 @@ CREATE TABLE IF NOT EXISTS "Post" ( "updatedAt" timestamp(3) with time zone NOT NULL, "slug" text NOT NULL, "userId" text NOT NULL, - "showComments" boolean DEFAULT true NOT NULL + "showComments" boolean DEFAULT true NOT NULL, + "seriesId" INTEGER ); --> statement-breakpoint CREATE TABLE IF NOT EXISTS "PostTag" ( @@ -183,6 +184,15 @@ CREATE TABLE IF NOT EXISTS "Membership" ( "createdAt" timestamp(3) with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL ); --> statement-breakpoint +CREATE TABLE IF NOT EXISTS "Series" ( + "id" SERIAL PRIMARY KEY, + "name" TEXT NOT NULL, + "userId" text NOT NULL, + "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL +); +--> statement-breakpoint + CREATE UNIQUE INDEX IF NOT EXISTS "Post_id_key" ON "Post" ("id");--> statement-breakpoint CREATE UNIQUE INDEX IF NOT EXISTS "Post_slug_key" ON "Post" ("slug");--> statement-breakpoint CREATE UNIQUE INDEX IF NOT EXISTS "PostTag_tagId_postId_key" ON "PostTag" ("tagId","postId");--> statement-breakpoint @@ -208,6 +218,12 @@ EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "Post" ADD CONSTRAINT "Post_seriesId_fkey" FOREIGN KEY ("seriesId") REFERENCES "public"."Series" ("id") ON DELETE SET NULL; +EXCEPTION + WHEN duplicate_object THEN null; +END $$ +--> statement-breakpoint DO $$ BEGIN ALTER TABLE "Post" ADD CONSTRAINT "Post_userId_User_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade; EXCEPTION diff --git a/drizzle/0011_add_series_update_post.sql b/drizzle/0011_add_series_update_post.sql deleted file mode 100644 index 3bdcd6fe..00000000 --- a/drizzle/0011_add_series_update_post.sql +++ /dev/null @@ -1,18 +0,0 @@ --- Create Series table -CREATE TABLE IF NOT EXISTS "Series" ( - "id" SERIAL PRIMARY KEY, - "name" TEXT NOT NULL, - "userId" text NOT NULL, - "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, - "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL -); --- Update Post table to add seriesId column -ALTER TABLE "Post" -ADD COLUMN "seriesId" INTEGER -ADD CONSTRAINT fk_post_series - FOREIGN KEY ("seriesId") - REFERENCES "Series" ("id") - ON DELETE SET NULL; -CREATE INDEX idx_post_series ON "Post"("seriesId"); -CREATE INDEX idx_series_name ON "Series"("name"); -ALTER TABLE series ADD CONSTRAINT unique_series_name_per_user UNIQUE (name, userId); \ No newline at end of file diff --git a/server/db/schema.ts b/server/db/schema.ts index d245b4f9..ee87eabd 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -38,7 +38,6 @@ export const sessionRelations = relations(session, ({ one }) => ({ export const series = pgTable("Series", { id: serial("id").primaryKey(), name: text("name").notNull(), - description: text("description"), userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" }), createdAt: timestamp("createdAt", { precision: 3, From 454067fa6790768f5cd96093f9dcebf1316ab60c Mon Sep 17 00:00:00 2001 From: Penumarthi Navaneeth Date: Mon, 18 Nov 2024 09:13:14 +0530 Subject: [PATCH 10/10] Feature/ Add series to link related articles --- .../create/[[...paramsArr]]/_client.tsx | 8 +++++++- drizzle/0000_initial_schema.sql | 17 +---------------- drizzle/0011_add_series.sql | 17 +++++++++++++++++ schema/post.ts | 4 ++++ 4 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 drizzle/0011_add_series.sql diff --git a/app/(editor)/create/[[...paramsArr]]/_client.tsx b/app/(editor)/create/[[...paramsArr]]/_client.tsx index 62c483a6..a2793a57 100644 --- a/app/(editor)/create/[[...paramsArr]]/_client.tsx +++ b/app/(editor)/create/[[...paramsArr]]/_client.tsx @@ -37,6 +37,7 @@ import { uploadFile } from "@/utils/s3helpers"; import { getUploadUrl } from "@/app/actions/getUploadUrl"; import EditorNav from "./navigation"; import { type Session } from "next-auth"; +import { FormDataSchema } from "@/schema/post"; const Create = ({ session }: { session: Session | null }) => { const params = useParams(); @@ -213,12 +214,17 @@ const Create = ({ session }: { session: Session | null }) => { const getFormData = () => { const data = getValues(); + + const sanitizedSeriesName = FormDataSchema.parse({ + seriesName: data.seriesName, + }); + const formData = { ...data, tags, canonicalUrl: data.canonicalUrl || undefined, excerpt: data.excerpt || removeMarkdown(data.body, {}).substring(0, 155), - seriesName: data.seriesName || undefined + ...sanitizedSeriesName }; return formData; }; diff --git a/drizzle/0000_initial_schema.sql b/drizzle/0000_initial_schema.sql index 566f15d1..430f7b0b 100644 --- a/drizzle/0000_initial_schema.sql +++ b/drizzle/0000_initial_schema.sql @@ -21,8 +21,7 @@ CREATE TABLE IF NOT EXISTS "Post" ( "updatedAt" timestamp(3) with time zone NOT NULL, "slug" text NOT NULL, "userId" text NOT NULL, - "showComments" boolean DEFAULT true NOT NULL, - "seriesId" INTEGER + "showComments" boolean DEFAULT true NOT NULL ); --> statement-breakpoint CREATE TABLE IF NOT EXISTS "PostTag" ( @@ -184,14 +183,6 @@ CREATE TABLE IF NOT EXISTS "Membership" ( "createdAt" timestamp(3) with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL ); --> statement-breakpoint -CREATE TABLE IF NOT EXISTS "Series" ( - "id" SERIAL PRIMARY KEY, - "name" TEXT NOT NULL, - "userId" text NOT NULL, - "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, - "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL -); ---> statement-breakpoint CREATE UNIQUE INDEX IF NOT EXISTS "Post_id_key" ON "Post" ("id");--> statement-breakpoint CREATE UNIQUE INDEX IF NOT EXISTS "Post_slug_key" ON "Post" ("slug");--> statement-breakpoint @@ -218,12 +209,6 @@ EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "Post" ADD CONSTRAINT "Post_seriesId_fkey" FOREIGN KEY ("seriesId") REFERENCES "public"."Series" ("id") ON DELETE SET NULL; -EXCEPTION - WHEN duplicate_object THEN null; -END $$ ---> statement-breakpoint DO $$ BEGIN ALTER TABLE "Post" ADD CONSTRAINT "Post_userId_User_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade; EXCEPTION diff --git a/drizzle/0011_add_series.sql b/drizzle/0011_add_series.sql new file mode 100644 index 00000000..bc22b577 --- /dev/null +++ b/drizzle/0011_add_series.sql @@ -0,0 +1,17 @@ +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "Series" ( + "id" SERIAL PRIMARY KEY, + "name" TEXT NOT NULL, + "userId" text NOT NULL, + "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL +); +-->statement-breakpoint + +ALTER TABLE "Post" ADD COLUMN "seriesId" INTEGER; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "Post" ADD CONSTRAINT "Post_seriesId_fkey" FOREIGN KEY ("seriesId") REFERENCES "public"."Series" ("id") ON DELETE SET NULL; +EXCEPTION + WHEN duplicate_object THEN null; +END $$ \ No newline at end of file diff --git a/schema/post.ts b/schema/post.ts index 30aa730c..9f253fff 100644 --- a/schema/post.ts +++ b/schema/post.ts @@ -72,6 +72,10 @@ export const GetPostsSchema = z.object({ tag: z.string().nullish(), }); +export const FormDataSchema = z.object({ + seriesName: z.string().trim().optional() +}) + export type SavePostInput = z.TypeOf; export type ConfirmPostInput = z.TypeOf;