-
-
Notifications
You must be signed in to change notification settings - Fork 147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/ Add series to link related articles #1141
Conversation
@RangerCreaky is attempting to deploy a commit to the Codú Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughThe changes in this pull request introduce a new "Series" feature that allows users to link related articles together. This includes modifications to the Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CreateComponent
participant Server
participant Database
User->>CreateComponent: Fill out form
CreateComponent->>Server: Save post with seriesName
Server->>Database: Update post and series
Database-->>Server: Confirm update
Server-->>CreateComponent: Return success
CreateComponent-->>User: Show success message
Assessment against linked issues
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 16
🧹 Outside diff range and nitpick comments (6)
schema/series.ts (1)
3-6
: LGTM: UpdateSeriesSchema is well-defined, with minor suggestions for improvement.The
UpdateSeriesSchema
is correctly defined and aligns with the PR objectives. It allows for updating series information with a requiredpostId
and an optionalseriesName
. The use oftrim()
onseriesName
is a good practice.Consider the following suggestions for improvement:
- Add a minimum length validation for
seriesName
to prevent empty strings after trimming.- Include a maximum length validation for
seriesName
to prevent excessively long series names.- If applicable, add a more specific validation for
postId
(e.g., UUID format).Example implementation:
export const UpdateSeriesSchema = z.object({ postId: z.string().uuid(), // If postId is expected to be a UUID seriesName: z.string().trim().min(1).max(100).optional() // Adjust max length as needed });These changes would further enhance the robustness of the schema.
drizzle/0011_add_series_update_post.sql (1)
1-9
: LGTM! Consider adding a trigger for "updatedAt".The "Series" table creation looks good and aligns with the feature requirements. The structure and constraints are well-defined. However, consider adding a trigger to automatically update the "updatedAt" column when a row is modified. This ensures that the "updatedAt" timestamp is always current without relying on application-level logic.
Here's an example of how you could add a trigger:
CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW."updatedAt" = NOW(); RETURN NEW; END; $$ language 'plpgsql'; CREATE TRIGGER update_series_updated_at BEFORE UPDATE ON "Series" FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();This trigger will automatically update the "updatedAt" column whenever a row in the "Series" table is modified.
server/api/router/index.ts (1)
Missing CRUD operations and error handling in
seriesRouter
.The
seriesRouter
currently only implements the create operation. The following essential CRUD operations and error handling are missing:
Read/Get Operation:
- Implement functions to retrieve series data.
Update Operation:
- Implement functions to modify existing series data.
Delete Operation:
- Implement functions to remove series data.
Error Handling:
- Add error handling mechanisms to manage potential exceptions and ensure application stability.
Please address these missing implementations to ensure
seriesRouter
is fully functional and robust.🔗 Analysis chain
Line range hint
11-21
: VerifyseriesRouter
implementation and consider refactoring for better organization.While the changes look good, I recommend the following:
Verify that the
seriesRouter
implementation covers all required functionality as per the PR objectives, including creating, editing, and deleting series.Consider refactoring the router imports and declarations for better organization. You could group related routers together, which might improve readability as the number of routers grows.
Here's a suggested refactor for better organization:
import { createTRPCRouter } from "../trpc"; import { postRouter } from "./post"; +import { seriesRouter } from "./series"; import { profileRouter } from "./profile"; import { commentRouter } from "./comment"; import { notificationRouter } from "./notification"; import { adminRouter } from "./admin"; import { reportRouter } from "./report"; import { tagRouter } from "./tag"; -import { seriesRouter } from "./series"; export const appRouter = createTRPCRouter({ post: postRouter, + series: seriesRouter, profile: profileRouter, comment: commentRouter, notification: notificationRouter, admin: adminRouter, report: reportRouter, tag: tagRouter, - series: seriesRouter });This groups the
seriesRouter
with thepostRouter
, as they are closely related features.To verify the
seriesRouter
implementation:🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify seriesRouter implementation # Test: Check if seriesRouter file exists if [ ! -f "./server/api/router/series.ts" ]; then echo "Error: seriesRouter file not found" exit 1 fi # Test: Check for required CRUD operations in seriesRouter rg --type typescript "create.*series" ./server/api/router/series.ts rg --type typescript "get.*series" ./server/api/router/series.ts rg --type typescript "update.*series" ./server/api/router/series.ts rg --type typescript "delete.*series" ./server/api/router/series.ts # Test: Check for error handling in seriesRouter rg --type typescript "throw.*error" ./server/api/router/series.ts echo "Manual review required to ensure all operations are correctly implemented."Length of output: 725
Script:
#!/bin/bash # Description: Verify seriesRouter implementation # Test: Check if seriesRouter file exists if [ ! -f "./server/api/router/series.ts" ]; then echo "Error: seriesRouter file not found" exit 1 fi # Test: Check for required CRUD operations in seriesRouter rg "create.*series" ./server/api/router/series.ts rg "get.*series" ./server/api/router/series.ts rg "update.*series" ./server/api/router/series.ts rg "delete.*series" ./server/api/router/series.ts # Test: Check for error handling in seriesRouter rg "throw.*error" ./server/api/router/series.ts echo "Manual review required to ensure all operations are correctly implemented."Length of output: 1253
Script:
#!/bin/bash # Description: Verify seriesRouter implementation with flexible patterns # Test: Check if seriesRouter file exists if [ ! -f "./server/api/router/series.ts" ]; then echo "Error: seriesRouter file not found" exit 1 fi # Define flexible patterns for CRUD operations CREATE_PATTERN="create[_a-zA-Z]*series" GET_PATTERN="get[_a-zA-Z]*series|read[_a-zA-Z]*series|fetch[_a-zA-Z]*series" UPDATE_PATTERN="update[_a-zA-Z]*series|modify[_a-zA-Z]*series|edit[_a-zA-Z]*series" DELETE_PATTERN="delete[_a-zA-Z]*series|remove[_a-zA-Z]*series" # Test: Check for Create operation in seriesRouter if ! rg -i "$CREATE_PATTERN" ./server/api/router/series.ts > /dev/null; then echo "Error: Create operation not found in seriesRouter." fi # Test: Check for Read/Get operation in seriesRouter if ! rg -i "$GET_PATTERN" ./server/api/router/series.ts > /dev/null; then echo "Error: Read/Get operation not found in seriesRouter." fi # Test: Check for Update operation in seriesRouter if ! rg -i "$UPDATE_PATTERN" ./server/api/router/series.ts > /dev/null; then echo "Error: Update operation not found in seriesRouter." fi # Test: Check for Delete operation in seriesRouter if ! rg -i "$DELETE_PATTERN" ./server/api/router/series.ts > /dev/null; then echo "Error: Delete operation not found in seriesRouter." fi # Test: Check for error handling in seriesRouter if ! rg -i "throw\s+new\s+Error" ./server/api/router/series.ts > /dev/null; then echo "Error: Error handling not implemented in seriesRouter." fi echo "Manual review required to ensure all CRUD operations and error handling are correctly implemented."Length of output: 1289
schema/post.ts (3)
28-28
: LGTM! Consider adding a maximum length constraint.The addition of the
seriesName
field to theSavePostSchema
is well-implemented. It's correctly defined as an optional trimmed string, which aligns with the PR objectives for the Series feature.Consider adding a maximum length constraint to the
seriesName
field to prevent excessively long series names. For example:seriesName: z.string().trim().max(100, "Max series name length is 100 characters.").optional()This would ensure consistency with other string fields in the schema that have length constraints.
54-54
: LGTM! Consider adding a maximum length constraint for consistency.The addition of the
seriesName
field to theConfirmPostSchema
is correct and consistent with the change made toSavePostSchema
.For consistency with the suggestion made for
SavePostSchema
, consider adding a maximum length constraint to thisseriesName
field as well:seriesName: z.string().trim().max(100, "Max series name length is 100 characters.").optional()This would ensure uniformity across both schemas and prevent excessively long series names.
Action Required: Incomplete handling of
seriesName
in schema usage locationsThe
seriesName
field is not handled in the following files, which may lead to integration issues:
server/api/router/post.ts
hooks/useCreatePage.ts
Please update these files to properly handle the new
seriesName
field in accordance with the schema changes.🔗 Analysis chain
Line range hint
1-54
: Verify schema usage in related components and API routes.The changes to
SavePostSchema
andConfirmPostSchema
are minimal, focused, and consistent, which is excellent for maintainability. They align well with the PR objectives for implementing the Series feature.To ensure smooth integration, please verify that all components and API routes using these schemas have been updated to handle the new
seriesName
field. Run the following script to identify potential usage locations:Review the results to ensure that the new
seriesName
field is properly handled in all relevant locations.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Find potential usage of SavePostSchema and ConfirmPostSchema echo "Searching for SavePostSchema usage:" rg --type typescript -A 5 "SavePostSchema" echo "\nSearching for ConfirmPostSchema usage:" rg --type typescript -A 5 "ConfirmPostSchema"Length of output: 330
Script:
#!/bin/bash # Description: Find potential usage of SavePostSchema and ConfirmPostSchema in TypeScript files echo "Searching for SavePostSchema usage:" rg -g "*.ts" -g "*.tsx" -A 5 "SavePostSchema" echo "\nSearching for ConfirmPostSchema usage:" rg -g "*.ts" -g "*.tsx" -A 5 "ConfirmPostSchema"Length of output: 3841
Script:
#!/bin/bash # Description: Verify usage of 'seriesName' in files utilizing SavePostSchema and ConfirmPostSchema echo "Searching for 'seriesName' usage in files using SavePostSchema and ConfirmPostSchema:" rg -g "*.ts" -g "*.tsx" "seriesName" server/api/router/post.ts hooks/useCreatePage.ts app/(app)/create/[[...paramsArr]]/_client.tsxLength of output: 455
Script:
#!/bin/bash # Description: Verify usage of 'seriesName' in files utilizing SavePostSchema and ConfirmPostSchema echo "Searching for 'seriesName' usage in files using SavePostSchema and ConfirmPostSchema:" rg -g "*.ts" -g "*.tsx" "seriesName" "server/api/router/post.ts" "hooks/useCreatePage.ts" "app/(app)/create/[[...paramsArr]]/_client.tsx"Length of output: 828
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (8)
- app/(app)/create/[[...paramsArr]]/_client.tsx (4 hunks)
- drizzle/0011_add_series_update_post.sql (1 hunks)
- schema/post.ts (2 hunks)
- schema/series.ts (1 hunks)
- server/api/router/index.ts (2 hunks)
- server/api/router/post.ts (3 hunks)
- server/api/router/series.ts (1 hunks)
- server/db/schema.ts (3 hunks)
🧰 Additional context used
🔇 Additional comments (8)
schema/series.ts (2)
1-1
: LGTM: Zod import is correct.The import statement correctly imports the Zod library, which is necessary for schema validation. Using 'z' as an alias is a common and accepted practice when working with Zod.
1-6
: Overall, the schema definition is well-implemented and supports the new Series feature.The
UpdateSeriesSchema
provides a solid foundation for validating series update operations. It correctly handles the requiredpostId
and optionalseriesName
, aligning well with the PR objectives. The use of Zod for schema validation is a good choice, providing type safety and runtime checks.While the current implementation is functional, consider the suggested improvements to make the schema more robust and prevent potential edge cases. These enhancements would further improve data integrity and user experience.
Great job on implementing this new feature!
server/api/router/index.ts (2)
11-11
: LGTM: Import statement forseriesRouter
is correctly added.The import statement for
seriesRouter
is consistent with the existing import style and is appropriately placed at the end of the import block.
21-21
: LGTM:seriesRouter
correctly added toappRouter
.The
seriesRouter
is appropriately added to theappRouter
, following the established pattern for router additions. This change aligns with the PR objectives of implementing the "Series" feature.server/api/router/series.ts (1)
1-5
: Imports are correctly organizedThe necessary modules and functions are properly imported, ensuring that all dependencies are available.
server/api/router/post.ts (2)
17-17
: Importing 'series' from the database schema is appropriateThe addition of
series
to the import statement ensures that the series table can be accessed and manipulated within this file, which is necessary for the new series-related functionality.
444-444
: Including 'series' relationship in theeditDraft
queryAdding
series: true
to thewith
clause ensures that the series information is fetched alongside the post data. This is necessary for editing posts that are part of a series, allowing the front-end to display and manage the series information appropriately.server/db/schema.ts (1)
190-190
: Ensure consistency in relation definitionsThe addition of the
series
relation inpostRelations
looks good. Just ensure that the foreign key constraint forseriesId
in thepost
table is properly defined, as mentioned earlier.
-- Update Post table to add seriesId column | ||
ALTER TABLE "Post" | ||
ADD COLUMN "seriesId" INTEGER |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add a foreign key constraint and consider NOT NULL.
The addition of the "seriesId" column to the "Post" table is good. However, there are two points to consider:
- It's recommended to add a foreign key constraint to ensure referential integrity between the "Post" and "Series" tables.
- Consider whether "seriesId" should be nullable or not, depending on whether all posts must belong to a series.
Here's a suggested modification:
ALTER TABLE "Post"
ADD COLUMN "seriesId" INTEGER,
ADD CONSTRAINT fk_post_series
FOREIGN KEY ("seriesId")
REFERENCES "Series" ("id")
ON DELETE SET NULL;
This adds a foreign key constraint and allows for nullable "seriesId". If you decide that all posts must belong to a series, you can add NOT NULL
to the column definition and adjust the ON DELETE
behavior accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made this change in the second commit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RangerCreaky, glad to hear that you've made this change!
^(^‾ʖ‾^)/
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
server/api/router/series.ts
Outdated
.input(UpdateSeriesSchema) | ||
.mutation(async ({input, ctx}) => { | ||
const {postId, seriesName} = input; | ||
console.group("series Name: ", seriesName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove debug logging statement
The console.group("series Name: ", seriesName);
appears to be a debug logging statement. It's recommended to remove this or replace it with appropriate logging for production environments to avoid unnecessary console output.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done in the second commit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RangerCreaky, great! Thank you for addressing that.
(^_^)/
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
server/api/router/series.ts
Outdated
const createNewSeries = async (seriesTitle: string) => { | ||
// check if a series with that name already exists | ||
// or else create a new one | ||
let seriesId : number; | ||
const currSeries = await ctx.db.query.series.findFirst({ | ||
columns: { | ||
id: true | ||
}, | ||
where: (series, { eq }) => eq(series.title, seriesTitle), | ||
}) | ||
if(!currSeries){ | ||
const [newSeries] = await ctx.db.insert(series).values({ | ||
title: 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 ctx.db | ||
.update(post) | ||
.set({ | ||
seriesId: seriesId | ||
}) | ||
.where(eq(post.id, currentPost.id)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Ensure atomicity of database operations with transactions in createNewSeries
The createNewSeries
function performs multiple database operations: checking for an existing series, possibly creating a new series, and updating the post's seriesId
. To maintain data consistency in case of errors, consider wrapping these operations in a transaction so that all operations either succeed or fail together.
Example using a transaction:
await ctx.db.transaction(async (tx) => {
// Your database operations here, using tx instead of ctx.db
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done in the second commit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RangerCreaky, thanks for making the updates in the second commit!
(_/)
(^_^)
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
server/api/router/series.ts
Outdated
const unlinkSeries = async (seriesId: number) => { | ||
// Check if the user has added a another post with the same series id previously | ||
const anotherPostInThisSeries = await ctx.db.query.post.findFirst({ | ||
where: (post, { eq, and, ne }) => | ||
and ( | ||
ne(post.id, currentPost.id), | ||
eq(post.seriesId, currentPost.seriesId!) | ||
) | ||
}) | ||
// if another post with the same seriesId is present, then do nothing | ||
// else remove the series from the series table | ||
if(!anotherPostInThisSeries){ | ||
await ctx.db.delete(series).where(eq(series.id, seriesId)); | ||
} | ||
// update that series id in the current post | ||
await ctx.db | ||
.update(post) | ||
.set({ | ||
seriesId: null | ||
}) | ||
.where(eq(post.id, currentPost.id)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Ensure atomicity of database operations with transactions in unlinkSeries
Similar to createNewSeries
, the unlinkSeries
function performs multiple database operations that should be atomic. Deleting the series and updating the post's seriesId
should be wrapped in a transaction to prevent partial updates if an error occurs.
Example using a transaction:
await ctx.db.transaction(async (tx) => {
// Your database operations here, using tx instead of ctx.db
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RangerCreaky, great! Thanks for making the changes.
(_/)
( •_•)
/>🎉
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
server/api/router/series.ts
Outdated
seriesId: true, | ||
userId: true | ||
}, | ||
with: { | ||
series: { | ||
columns: { | ||
id: true, | ||
title: true | ||
}, | ||
}, | ||
}, | ||
where: (post, { eq }) => eq(post.id, postId), | ||
}); | ||
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 | ||
let seriesId : number; | ||
const currSeries = await ctx.db.query.series.findFirst({ | ||
columns: { | ||
id: true | ||
}, | ||
where: (series, { eq }) => eq(series.title, seriesTitle), | ||
}) | ||
if(!currSeries){ | ||
const [newSeries] = await ctx.db.insert(series).values({ | ||
title: 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 ctx.db | ||
.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 | ||
const anotherPostInThisSeries = await ctx.db.query.post.findFirst({ | ||
where: (post, { eq, and, ne }) => | ||
and ( | ||
ne(post.id, currentPost.id), | ||
eq(post.seriesId, currentPost.seriesId!) | ||
) | ||
}) | ||
// if another post with the same seriesId is present, then do nothing | ||
// else remove the series from the series table | ||
if(!anotherPostInThisSeries){ | ||
await ctx.db.delete(series).where(eq(series.id, seriesId)); | ||
} | ||
// update that series id in the current post | ||
await ctx.db | ||
.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?.title !== 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); | ||
} | ||
} | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider adding unit tests for the update
mutation
To ensure the reliability and correctness of the update
mutation and its helper functions (createNewSeries
and unlinkSeries
), consider adding unit tests that cover various scenarios:
- Creating a new series when adding
seriesName
to a post. - Updating the series when
seriesName
changes. - Removing the series when
seriesName
is removed from a post. - Ensuring that series are deleted only when no other posts are associated.
Would you like assistance in setting up unit tests for this functionality or creating a GitHub issue to track this task?
updatedAt: timestamp("updatedAt", { | ||
precision: 3, | ||
withTimezone: true | ||
}).notNull() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add default value and auto-update logic for updatedAt
field
The updatedAt
field lacks a default value and auto-update logic, which could lead to null values or inaccurate timestamps. To maintain consistency with other tables and ensure accurate timestamps, consider adding these.
Apply this diff to update the updatedAt
field:
updatedAt: timestamp("updatedAt", {
precision: 3,
withTimezone: true
}).notNull()
+ .$onUpdate(() => new Date().toISOString())
+ .default(sql`CURRENT_TIMESTAMP`),
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
updatedAt: timestamp("updatedAt", { | |
precision: 3, | |
withTimezone: true | |
}).notNull() | |
updatedAt: timestamp("updatedAt", { | |
precision: 3, | |
withTimezone: true | |
}).notNull() | |
.$onUpdate(() => new Date().toISOString()) | |
.default(sql`CURRENT_TIMESTAMP`), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RangerCreaky, great to hear that you've made the change!
(^_^)/
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
server/db/schema.ts
Outdated
export const series = pgTable("Series", { | ||
id: serial("id").primaryKey(), | ||
title: text("title").notNull(), | ||
description: text("description"), | ||
userId: text("userId"), | ||
createdAt: timestamp("createdAt", { | ||
precision: 3, | ||
mode: "string", | ||
withTimezone: true, | ||
}) | ||
.default(sql`CURRENT_TIMESTAMP`) | ||
.notNull(), | ||
updatedAt: timestamp("updatedAt", { | ||
precision: 3, | ||
withTimezone: true | ||
}).notNull() | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add foreign key constraint for userId
in series
table
The userId
field in the series
table should reference the user
table to maintain referential integrity. This ensures that each series is associated with a valid user.
Apply this diff to add the foreign key constraint:
export const series = pgTable("Series", {
id: serial("id").primaryKey(),
title: text("title").notNull(),
description: text("description"),
- userId: text("userId"),
+ 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()
+ .notNull()
+ .$onUpdate(() => new Date().toISOString())
+ .default(sql`CURRENT_TIMESTAMP`),
});
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const series = pgTable("Series", { | |
id: serial("id").primaryKey(), | |
title: text("title").notNull(), | |
description: text("description"), | |
userId: text("userId"), | |
createdAt: timestamp("createdAt", { | |
precision: 3, | |
mode: "string", | |
withTimezone: true, | |
}) | |
.default(sql`CURRENT_TIMESTAMP`) | |
.notNull(), | |
updatedAt: timestamp("updatedAt", { | |
precision: 3, | |
withTimezone: true | |
}).notNull() | |
}) | |
export const series = pgTable("Series", { | |
id: serial("id").primaryKey(), | |
title: 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().toISOString()) | |
.default(sql`CURRENT_TIMESTAMP`), | |
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in the second commit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RangerCreaky, thank you for making the update in the second commit!
🐰🎉
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
@@ -215,6 +224,7 @@ const Create = () => { | |||
tags, | |||
canonicalUrl: data.canonicalUrl || undefined, | |||
excerpt: data.excerpt || removeMarkdown(data.body, {}).substring(0, 155), | |||
seriesName: data.seriesName || undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate and sanitize seriesName
before saving
When adding seriesName
to the formData
, ensure that the value is properly validated and sanitized. This includes trimming whitespace, limiting the length, and preventing invalid characters. This will help maintain data integrity and prevent potential issues with series management.
Consider adding validation rules using zod
or another validation library to enforce these constraints on seriesName
.
await Promise.all([ | ||
save({ ...formData, id: postId }), | ||
seriesUpdate({postId, seriesName: formData.seriesName}) | ||
]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle errors when saving post and updating series concurrently
Using Promise.all
to execute save
and seriesUpdate
mutations concurrently can lead to unhandled errors if one of the promises rejects. It's important to handle each mutation's errors individually to provide clear feedback to the user.
Refactor the code to handle errors for each mutation separately:
-await Promise.all([
- save({ ...formData, id: postId }),
- seriesUpdate({ postId, seriesName: formData.seriesName }),
-]);
+try {
+ await save({ ...formData, id: postId });
+} catch (error) {
+ toast.error("Error saving post.");
+ Sentry.captureException(error);
+}
+
+try {
+ await seriesUpdate({ postId, seriesName: formData.seriesName });
+} catch (error) {
+ toast.error("Error updating series.");
+ Sentry.captureException(error);
+}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
await Promise.all([ | |
save({ ...formData, id: postId }), | |
seriesUpdate({postId, seriesName: formData.seriesName}) | |
]); | |
try { | |
await save({ ...formData, id: postId }); | |
} catch (error) { | |
toast.error("Error saving post."); | |
Sentry.captureException(error); | |
} | |
try { | |
await seriesUpdate({ postId, seriesName: formData.seriesName }); | |
} catch (error) { | |
toast.error("Error updating series."); | |
Sentry.captureException(error); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in the second commit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RangerCreaky, that's great! Thanks for addressing the issue.
(^o^/)🎉
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
const { mutate: seriesUpdate, status: seriesStatus } = api.series.update.useMutation({ | ||
onError(error) { | ||
// TODO: Add error messages from field validations | ||
console.log("Error updating settings: ", error); | ||
toast.error("Error auto-saving"); | ||
Sentry.captureException(error); | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove console.log
and handle field validation errors
In the seriesUpdate
mutation's onError
handler, using console.log
for error logging is not recommended in production code. Instead, rely on toast.error
and Sentry.captureException
for error reporting. Additionally, the TODO comment indicates that error messages from field validations need to be added. Please ensure that meaningful error messages are provided to the user when validation errors occur.
Apply this diff to remove the console.log
statement and handle validation errors:
const { mutate: seriesUpdate, status: seriesStatus } = api.series.update.useMutation({
onError(error) {
// TODO: Add error messages from field validations
- console.log("Error updating settings: ", error);
+ // Provide specific error feedback to the user
+ if (error instanceof ZodError) {
+ toast.error(error.issues[0].message);
+ } else {
+ toast.error("Error updating series.");
+ }
toast.error("Error auto-saving");
Sentry.captureException(error);
}
});
Committable suggestion was skipped due to low confidence.
I am currently going through the coderabbit suggestions, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Outside diff range and nitpick comments (4)
server/api/router/series.ts (3)
41-74
: Well-implementedcreateNewSeries
function with proper transaction handling.The function correctly uses a database transaction to ensure atomicity when creating or retrieving a series and updating the post. This implementation addresses the previous suggestion to use transactions for maintaining data consistency.
Consider adding error handling within the transaction to catch and handle any potential database errors:
return await ctx.db.transaction(async (tx) => { try { // Existing transaction code } catch (error) { console.error('Error in createNewSeries:', error); throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Failed to create or update series' }); } });This addition would provide more robust error handling and help with debugging in case of unexpected issues.
76-99
: Well-implementedunlinkSeries
function with proper transaction handling.The function correctly uses a database transaction to ensure atomicity when unlinking a series from a post and potentially deleting the series. The logic for checking other posts in the same series before deletion is sound.
As with the
createNewSeries
function, consider adding error handling within the transaction:return await ctx.db.transaction(async (tx) => { try { // Existing transaction code } catch (error) { console.error('Error in unlinkSeries:', error); throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Failed to unlink series' }); } });This addition would provide more robust error handling and help with debugging in case of unexpected issues.
101-127
: Well-structured main logic covering all required scenarios.The implementation successfully handles all the cases mentioned in the PR objectives, including creating, updating, and removing series associations. The flow of execution is clear and follows the expected behavior.
To improve readability, consider extracting the main logic into a separate function:
async function handleSeriesUpdate(currentPost, seriesName) { if (seriesName) { if (currentPost?.seriesId) { if (currentPost?.series?.title !== seriesName) { await unlinkSeries(currentPost.seriesId); await createNewSeries(seriesName); } } else { await createNewSeries(seriesName); } } else if (currentPost.seriesId !== null) { await unlinkSeries(currentPost.seriesId); } } // In the main mutation function await handleSeriesUpdate(currentPost, seriesName);This refactoring would make the main mutation function more concise and easier to understand at a glance.
server/api/router/post.ts (1)
448-448
: LGTM: Series information included in editDraft queryThe addition of
series: true
ensures that series information is fetched when editing a draft, which is consistent with the new series feature. This change supports the EDIT case mentioned in the PR summary.For improved clarity and consistency with other parts of the code, consider using an object to specify which fields of the series you want to include:
- series: true + series: { + columns: { + id: true, + name: true + } + }This explicit approach can help prevent over-fetching and makes it clear which series data is being used.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (5)
- app/(app)/create/[[...paramsArr]]/_client.tsx (4 hunks)
- drizzle/0011_add_series_update_post.sql (1 hunks)
- server/api/router/post.ts (3 hunks)
- server/api/router/series.ts (1 hunks)
- server/db/schema.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- drizzle/0011_add_series_update_post.sql
🧰 Additional context used
🔇 Additional comments (11)
server/api/router/series.ts (3)
1-9
: LGTM: Imports and router definition are well-structured.The imports and router definition are appropriate for the functionality being implemented. The use of
protectedProcedure
ensures that the update mutation is only accessible to authenticated users.
10-40
: Great job implementing input validation and error handling!The code now includes proper input validation for
seriesName
and handles cases where the post is not found or the user is not authorized. These improvements address the concerns raised in previous reviews and enhance the robustness of the implementation.
1-129
: Overall, excellent implementation of the series update functionality.The code successfully implements the series feature as described in the PR objectives. It handles all the required cases for creating, updating, and removing series associations. The use of database transactions ensures data consistency, and the implementation of input validation and error handling addresses previous review concerns.
The suggested minor improvements for error handling within transactions and code structure refinement will further enhance the robustness and readability of the code.
Great job on addressing the previous review comments and delivering a solid implementation!
server/api/router/post.ts (3)
17-17
: LGTM: Import statement updated correctlyThe addition of
series
to the import statement is consistent with the new feature for linking related articles under a specific series.
190-212
: LGTM: Deletion logic improved with transaction and series cleanupThe changes effectively address the following:
- Wrapping the deletion logic in a transaction ensures atomicity.
- Proper cleanup of orphaned series records is implemented.
- The logic aligns with the PR objectives for handling series in CREATE, EDIT, and DELETE cases.
These improvements address the concerns raised in the past review comment about ensuring atomicity and preventing race conditions.
Line range hint
1-524
: Overall implementation looks good, suggest final verificationThe changes effectively implement the series feature as described in the PR objectives. The code addresses the CREATE, EDIT, and DELETE cases for series management, and performance considerations have been taken into account with the use of transactions.
To ensure completeness:
- Verify that all edge cases for series management are covered, especially for the EDIT case where a series name might be changed or removed.
- Consider adding unit tests to cover the new series-related functionality.
- Ensure that the frontend components are updated to utilize the new series information returned by the
editDraft
query.To help with verification, you can run the following script to check for any remaining TODOs or FIXMEs related to the series feature:
This will help ensure that all planned tasks for the series feature have been completed.
✅ Verification successful
All checks passed successfully! The series feature implementation is complete with no outstanding TODOs or FIXMEs.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for any remaining TODOs or FIXMEs related to the series feature echo "Checking for remaining TODOs or FIXMEs related to the series feature:" rg -i "TODO|FIXME" -g "!*.md" | rg -i "series"Length of output: 195
server/db/schema.ts (3)
38-56
: LGTM! Theseries
table is well-defined.The table structure looks good, with appropriate field types and constraints. The
updatedAt
field now includes the default value and auto-update logic as suggested in a past review comment.
172-172
: LGTM! TheseriesId
field is correctly added to thepost
table.The foreign key constraint has been properly implemented as suggested in a past review comment. The "set null" on delete and "cascade" on update actions are appropriate for this relationship.
298-304
: LGTM! TheseriesRelations
are well-defined.The relations for the
series
table have been correctly implemented as suggested in a past review comment. Theposts
relation allows for multiple posts in a series, and theuser
relation correctly links a series to its creator.app/(app)/create/[[...paramsArr]]/_client.tsx (2)
225-225
: Validate and sanitizeseriesName
before saving
566-578
: Improve user experience by adding autocomplete for series names
const { mutate: seriesUpdate, status: seriesStatus } = api.series.update.useMutation({ | ||
onError(error) { | ||
toast.error("Error auto-saving"); | ||
Sentry.captureException(error); | ||
} | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improve error message for seriesUpdate
mutation
In the onError
handler for seriesUpdate
, the error message displayed is "Error auto-saving." To provide clearer feedback to the user, consider changing it to "Error updating series."
Apply this diff to update the error message:
const { mutate: seriesUpdate, status: seriesStatus } = api.series.update.useMutation({
onError(error) {
- toast.error("Error auto-saving");
+ toast.error("Error updating series.");
Sentry.captureException(error);
}
});
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const { mutate: seriesUpdate, status: seriesStatus } = api.series.update.useMutation({ | |
onError(error) { | |
toast.error("Error auto-saving"); | |
Sentry.captureException(error); | |
} | |
}); | |
const { mutate: seriesUpdate, status: seriesStatus } = api.series.update.useMutation({ | |
onError(error) { | |
toast.error("Error updating series."); | |
Sentry.captureException(error); | |
} | |
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RangerCreaky: Great to hear that you've fixed the issue!
(^◡^)/
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
try { | ||
await save({ ...formData, id: postId }); | ||
} catch (error) { | ||
toast.error("Error saving post."); | ||
Sentry.captureException(error); | ||
} | ||
try { | ||
await seriesUpdate({ postId, seriesName: formData.seriesName }); | ||
toast.success("Saved"); | ||
} catch (error) { | ||
toast.error("Error updating series."); | ||
Sentry.captureException(error); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Show success message after both mutations complete successfully
Currently, the success message "Saved" is displayed after the seriesUpdate
mutation succeeds, regardless of whether the save
mutation was successful. To ensure accurate user feedback, consider displaying the success message only after both save
and seriesUpdate
mutations have completed successfully.
You can adjust the code as follows:
let saveSuccess = false;
try {
await save({ ...formData, id: postId });
saveSuccess = true;
} catch (error) {
toast.error("Error saving post.");
Sentry.captureException(error);
}
let seriesUpdateSuccess = false;
try {
await seriesUpdate({ postId, seriesName: formData.seriesName });
seriesUpdateSuccess = true;
} catch (error) {
toast.error("Error updating series.");
Sentry.captureException(error);
}
+ if (saveSuccess && seriesUpdateSuccess) {
+ toast.success("Saved");
+ }
This way, the success message is only shown if both operations succeed.
Committable suggestion was skipped due to low confidence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in the third commit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RangerCreaky, thank you for addressing the issue!
(^.^/)
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
<label htmlFor="seriesName"> | ||
Series Name | ||
</label> | ||
<input | ||
id="seriesName" | ||
type="text" | ||
placeholder="The name of my series" | ||
defaultValue={data?.series?.title || ""} | ||
{...register("seriesName")} | ||
/> | ||
<p className="mt-2 text-sm text-neutral-600 dark:text-neutral-400"> | ||
This text is case-sensitive so make sure you type it exactly as you did in previous articles to ensure they are connected | ||
</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Normalize series names to prevent case-sensitivity issues
Requiring users to type the series name exactly as before due to case sensitivity can lead to inconsistencies and user errors. To enhance user experience and ensure consistent linkage of related articles, consider normalizing series names by converting them to a standard case (e.g., all lowercase) before saving and when querying.
This approach will help prevent issues where articles are not linked correctly because of differences in capitalization, improving the reliability of the series feature.
let saveSuccess = false; | ||
try { | ||
await save({ ...formData, id: postId }); | ||
saveSuccess = true; | ||
} catch (error) { | ||
toast.error("Error saving post."); | ||
Sentry.captureException(error); | ||
} | ||
|
||
let seriesUpdateSuccess = false; | ||
try { | ||
if(formData?.seriesName){ | ||
await seriesUpdate({ postId, seriesName: formData.seriesName }); | ||
} | ||
seriesUpdateSuccess = true; | ||
} catch (error) { | ||
toast.error("Error updating series."); | ||
Sentry.captureException(error); | ||
} | ||
|
||
if(saveSuccess && seriesUpdateSuccess){ | ||
toast.success("Saved"); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure series update handles series removal correctly
Currently, the seriesUpdate
mutation is called only when formData.seriesName
exists. This means that if a user removes the series name from a post, the seriesUpdate
function is not invoked, and the series association may not be properly removed from the post.
To handle series removal correctly, consider calling seriesUpdate
regardless of whether formData.seriesName
exists. The mutation can then handle adding, updating, or removing the series association based on the value of seriesName
.
Apply this diff to adjust the logic:
let seriesUpdateSuccess = false;
try {
- if(formData?.seriesName){
await seriesUpdate({ postId, seriesName: formData.seriesName });
- }
seriesUpdateSuccess = true;
} catch (error) {
toast.error("Error updating series.");
Sentry.captureException(error);
}
Ensure that the seriesUpdate
mutation on the server side appropriately handles cases where seriesName
is undefined
or an empty string, removing the series association when necessary.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
let saveSuccess = false; | |
try { | |
await save({ ...formData, id: postId }); | |
saveSuccess = true; | |
} catch (error) { | |
toast.error("Error saving post."); | |
Sentry.captureException(error); | |
} | |
let seriesUpdateSuccess = false; | |
try { | |
if(formData?.seriesName){ | |
await seriesUpdate({ postId, seriesName: formData.seriesName }); | |
} | |
seriesUpdateSuccess = true; | |
} catch (error) { | |
toast.error("Error updating series."); | |
Sentry.captureException(error); | |
} | |
if(saveSuccess && seriesUpdateSuccess){ | |
toast.success("Saved"); | |
} | |
let saveSuccess = false; | |
try { | |
await save({ ...formData, id: postId }); | |
saveSuccess = true; | |
} catch (error) { | |
toast.error("Error saving post."); | |
Sentry.captureException(error); | |
} | |
let seriesUpdateSuccess = false; | |
try { | |
await seriesUpdate({ postId, seriesName: formData.seriesName }); | |
seriesUpdateSuccess = true; | |
} catch (error) { | |
toast.error("Error updating series."); | |
Sentry.captureException(error); | |
} | |
if(saveSuccess && seriesUpdateSuccess){ | |
toast.success("Saved"); | |
} |
The new PR raised is #1146 |
✨ Codu Pull Request 💻
Fixes #1081
Pull Request details
Cases considered
CREATE
When the user creates a new record will be created in the series table
EDIT
If the user deletes the post which had a series name then the series will be removed from the series table, if no other post has that series.
Any Breaking changes
Note to Reviewers
Please let me know,
Associated Screenshots
UI
Working Video