Skip to content

Conversation

@NiallJoeMaher
Copy link
Contributor

A few fun changes here.

  • Made the new settings screen responsive so it can be used as a template for other forms.
  • Added indexes to speed up fetching of posts and user profiles
  • Fixed some schema issues that gets drizzle kit studio working again so you can inspect data
  • Refactored the email verification system to ensure it can't be spammed and to stop it locking up emails

New settings screen on desktop
New settings screen on mobile

Also optimizes some indexes for fetching data and then also fixes schema to get drizzlekit working
@NiallJoeMaher NiallJoeMaher requested a review from a team as a code owner October 11, 2024 21:13
@vercel
Copy link

vercel bot commented Oct 11, 2024

@NiallJoeMaher is attempting to deploy a commit to the Codú Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 11, 2024

Walkthrough

The pull request introduces extensive modifications to the email management system and the Settings component. Key changes include a redesigned form layout with new UI components, improved email verification processes using direct database interactions, and the creation of new database tables for tracking email changes. The Settings component has been updated for better user experience, while the email verification logic now includes enhanced error handling and logging. Additionally, the database schema has been restructured to support these new functionalities, ensuring a more efficient and organized approach to email updates.

Changes

File Change Summary
app/(app)/settings/_client.tsx Restructured Settings component with new UI components, removed classNames, improved grid layout, added subheadings and descriptive text, refined email update handling with cooldown mechanism, and streamlined error handling.
app/api/verify-email/route.ts Updated GET method to use direct database queries for token management, improved error handling and response construction, consolidated token checks, and added logging and email change history insertion.
drizzle/0010_email-tokens-and-indexes.sql Created EmailChangeHistory and EmailChangeRequest tables, removed old token tables, added foreign key constraints, and created indexes to optimize performance.
server/api/router/profile.ts Enhanced updateEmail procedure with database checks for existing emails, added rate-limiting, streamlined error handling, and encapsulated verification email sending in a try-catch block.
server/db/schema.ts Added new relations for session and account tables, updated existing relations, added new tables for emailChangeRequest and emailChangeHistory, and improved naming conventions for clarity.
utils/emailToken.ts Removed functions related to email verification token management, introduced getBaseUrl for generating verification links, and updated sendVerificationEmail to use the new base URL function.

Possibly related PRs

🐰 In the meadow, changes bloom,
New emails fly, dispelling gloom.
With forms so bright and clear in sight,
We hop along, all feels just right!
A history kept, a request in hand,
In this garden of code, we make our stand! 🌷


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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Outside diff range and nitpick comments (6)
drizzle/0010_email-tokens-and-indexes.sql (2)

1-9: LGTM! Consider adding column length limits.

The EmailChangeHistory table structure is well-designed for tracking email changes, including useful fields for security auditing. However, consider adding length limits to the text fields (e.g., userId, oldEmail, newEmail, ipAddress, userAgent) to prevent potential storage issues.

Example of adding a length limit:

"userId" varchar(255) NOT NULL,

11-19: LGTM! Consider adding column length limits and an index on userId.

The EmailChangeRequest table is well-structured for managing email change requests. The unique constraint on the token and the inclusion of an expiration time are good security practices.

Consider the following improvements:

  1. Add length limits to the text fields to prevent potential storage issues:
    "userId" varchar(255) NOT NULL,
    "newEmail" varchar(255) NOT NULL,
    "token" varchar(64) NOT NULL,
  2. Add an index on the userId column to improve query performance when looking up requests for a specific user:
    CREATE INDEX IF NOT EXISTS "EmailChangeRequest_userId_index" ON "EmailChangeRequest" USING btree ("userId");
server/api/router/profile.ts (3)

1-1: Organize Import Statements for Clarity

Consider grouping and ordering the import statements for better readability. Group related imports together, and separate third-party modules from local modules.

Apply this diff to improve import organization:

+import { z } from "zod";
+import { TRPCError } from "@trpc/server";
+import { nanoid } from "nanoid";
+import { and, eq, gte } from "drizzle-orm";

import { user, emailChangeHistory } from "@/server/db/schema";
+import { emailChangeRequest } from "@/server/db/schema";

import {
  createTRPCRouter,
  publicProcedure,
  protectedProcedure,
} from "../trpc";
import {
  isUserSubscribedToNewsletter,
  manageNewsletterSubscription,
} from "@/server/lib/newsletter";
import { getPresignedUrl } from "@/server/common/getPresignedUrl";
import { generateEmailToken, sendVerificationEmail } from "@/utils/emailToken";
import {
  saveSettingsSchema,
  getProfileSchema,
  uploadPhotoUrlSchema,
  updateProfilePhotoUrlSchema,
} from "@/schema/profile";
import { emailTokenReqSchema } from "@/schema/token";
import { TOKEN_EXPIRATION_TIME } from "@/config/constants";

20-20: Remove Unused Import: generateEmailToken

The function generateEmailToken seems to be imported but not used in this file. Removing unused imports can help keep the codebase clean.

Apply this diff to remove the unused import:

-import { generateEmailToken, sendVerificationEmail } from "@/utils/emailToken";
+import { sendVerificationEmail } from "@/utils/emailToken";

190-196: Enhance Error Logging for Failed Email Dispatch

When logging errors, include more context to aid debugging, such as the user's ID or email. Ensure sensitive information is not logged.

Apply this diff to improve error logging:

   try {
     await sendVerificationEmail(newEmail, token);
   } catch (error) {
-    console.error("Failed to send verification email:", error);
+    console.error(`Failed to send verification email to ${newEmail}:`, error);
     throw new TRPCError({
       code: "INTERNAL_SERVER_ERROR",
       message: "Failed to send verification email",
     });
   }
app/(app)/settings/_client.tsx (1)

85-86: Consider enhancing email validation with a more robust method

The current regular expression for email validation is simple and may not catch all invalid email formats. For improved accuracy, consider using a more comprehensive validation method or an established library like validator.js.

For example, using the validator library:

+import isEmail from 'validator/lib/isEmail';

-const isValidEmail = (email: string) => /\S+@\S+\.\S+/.test(email);
+const isValidEmail = (email: string) => isEmail(email);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between d912fe7 and c8bb708.

⛔ Files ignored due to path filters (3)
  • drizzle/meta/0010_snapshot.json is excluded by !**/*.json
  • drizzle/meta/_journal.json is excluded by !**/*.json
  • package-lock.json is excluded by !**/package-lock.json, !**/*.json
📒 Files selected for processing (5)
  • app/(app)/settings/_client.tsx (7 hunks)
  • app/api/verify-email/route.ts (1 hunks)
  • drizzle/0010_email-tokens-and-indexes.sql (1 hunks)
  • server/api/router/profile.ts (3 hunks)
  • server/db/schema.ts (7 hunks)
🧰 Additional context used
🔇 Additional comments (18)
drizzle/0010_email-tokens-and-indexes.sql (3)

23-33: LGTM! Note the CASCADE delete behavior.

The foreign key constraints are correctly implemented, ensuring referential integrity between the new tables and the user table. The exception handling for duplicate constraints is a good practice for migration scripts.

Please confirm that the CASCADE delete behavior is intended. This means that when a user is deleted, all their email change history and requests will also be deleted. If this is not the desired behavior, consider using SET NULL or RESTRICT instead.

#!/bin/bash
# Verify the foreign key constraint details
echo "Checking foreign key constraints for EmailChangeHistory:"
psql -c "SELECT conname, confdeltype FROM pg_constraint WHERE conname = 'EmailChangeHistory_userId_user_id_fk';"
echo "Checking foreign key constraints for EmailChangeRequest:"
psql -c "SELECT conname, confdeltype FROM pg_constraint WHERE conname = 'EmailChangeRequest_userId_user_id_fk';"

# Output explanation:
# confdeltype: a = no action, r = restrict, c = cascade, n = set null, d = set default

35-39: LGTM! Verify impact on write performance.

The addition of these indexes is a good optimization for read performance on frequently queried columns. The use of IF NOT EXISTS ensures idempotency, which is excellent for migration scripts.

While these indexes will improve read performance, they may have an impact on write performance. Please verify that this trade-off is acceptable for your use case. Consider running performance tests to ensure that write-heavy operations are not significantly impacted.

#!/bin/bash
# Verify the existing indexes and their size
echo "Checking indexes on Comment table:"
psql -c "SELECT indexname, pg_size_pretty(pg_relation_size(indexname::regclass)) AS index_size FROM pg_indexes WHERE tablename = 'Comment';"
echo "Checking indexes on Notification table:"
psql -c "SELECT indexname, pg_size_pretty(pg_relation_size(indexname::regclass)) AS index_size FROM pg_indexes WHERE tablename = 'Notification';"
echo "Checking indexes on Post table:"
psql -c "SELECT indexname, pg_size_pretty(pg_relation_size(indexname::regclass)) AS index_size FROM pg_indexes WHERE tablename = 'Post';"
echo "Checking indexes on user table:"
psql -c "SELECT indexname, pg_size_pretty(pg_relation_size(indexname::regclass)) AS index_size FROM pg_indexes WHERE tablename = 'user';"

# This will help you understand the current state of indexes and their sizes

21-22: Verify intentional removal of tables and consider data migration.

The removal of EmailVerificationToken and verificationToken tables suggests a significant change in the email verification process. While this is consistent with the new email-related tables, it's important to ensure that no critical data is being lost.

Please confirm:

  1. Is the removal of these tables intentional?
  2. Has any important data from these tables been migrated to the new structure?

If data migration is needed, consider running a script to transfer relevant data before dropping the tables.

app/api/verify-email/route.ts (1)

24-29: ⚠️ Potential issue

Reconsider authentication requirement for email verification

Currently, the email verification process requires the user to be authenticated (session and session.user must exist). This might cause issues if a user attempts to verify their email without being logged in, which is a common scenario. Consider modifying the verification logic to allow unauthenticated users to verify their email addresses.

⛔ Skipped due to learnings
Learnt from: NiallJoeMaher
PR: codu-code/codu#1069
File: app/verify_email/_client.tsx:36-42
Timestamp: 2024-10-06T07:41:04.094Z
Learning: In the email verification process in `app/verify_email/_client.tsx`, we use a GET route that automatically validates the token when the user clicks the email link, instead of making a POST request. This ensures that the user clicked their email.
server/api/router/profile.ts (1)

178-179: Use Secure Token Generation

Ensure that the token generation method produces cryptographically secure tokens. Using nanoid with a secure random generator is recommended.

[security]

Run the following script to verify the token generation method:

app/(app)/settings/_client.tsx (1)

96-101: ⚠️ Potential issue

Include 'cooldown' in useEffect dependency array to ensure proper countdown

The useEffect hook responsible for the cooldown timer at lines 96-101 is missing a dependency array. Without including [cooldown] as a dependency, the effect might not re-run when cooldown changes, leading to the countdown not functioning as intended.

Apply the following change to include cooldown in the dependency array:

 useEffect(() => {
   if (cooldown > 0) {
     const timer = setTimeout(() => setCooldown(cooldown - 1), 1000);
     return () => clearTimeout(timer);
   }
- });
+ }, [cooldown]);

Likely invalid or redundant comment.

server/db/schema.ts (12)

31-38: Added sessionRelations for the session table

The new relation correctly associates sessions with users, ensuring referential integrity and simplifying queries involving session data.


63-66: Added accountRelations for the account table

This addition establishes a clear relationship between accounts and users, which is essential for account management and authentication processes.


158-159: Added indexes on slug and userId in Post table to enhance query performance

The new indexes slugIndex and userIdIndex will improve the efficiency of queries filtering posts by slug and userId, respectively.


227-227: Added index on username in User table for faster lookups

Introducing usernameIndex optimizes queries involving usernames, benefiting user authentication and profile retrieval operations.


244-249: Added notificationsCreated and notificationsReceived relations to User

These relations enhance the notification system by clearly distinguishing between notifications sent by the user (notificationsCreated) and those received (notificationsReceived).


251-253: Added sessions and email change relations to User

Including sessions, emailChangeRequests, and emailChangeHistory relations allows for comprehensive tracking of user sessions and email change activities, improving account security and management.


319-319: Added index on postId in Comment table to improve query speed

The postIdIndex will enhance performance when retrieving comments associated with a specific post.


466-476: Updated relation names in flaggedRelations for better readability

Renaming relations to flaggedByUser and flaggedContent enhances clarity, making it easier to understand the relationships between users and flagged items.


479-538: Added notifierId field and relations to Notification table

Including notifierId enables the tracking of which user generated a notification, enhancing the traceability and functionality of the notification system.


540-559: Implemented EmailChangeRequest table and relations

The new EmailChangeRequest table securely manages pending email change requests, storing tokens and expiration dates to prevent abuse and enhance user security.


561-577: Implemented EmailChangeHistory table and relations

The EmailChangeHistory table records email changes, providing an audit trail that improves account security and aids in troubleshooting any issues related to email updates.


233-235: ⚠️ Potential issue

Verify the correctness of the bannedUsers relation in userRelations

The bannedUsers relation is defined as a one relation:

bannedUsers: one(banned_users, {
  fields: [user.id],
  references: [banned_users.userId],
}),

If a user can have multiple ban records, consider changing this to a many relation to accurately represent the one-to-many relationship between users and their ban records.

.update(user)
.set({
email: request.newEmail,
emailVerified: new Date().toISOString(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure correct data type for emailVerified field

The emailVerified field is being set using new Date().toISOString(), which returns a string. If the database schema expects a Date object, consider passing new Date() instead to ensure type consistency.

Apply this diff to fix the issue:

- emailVerified: new Date().toISOString(),
+ emailVerified: new Date(),
📝 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.

Suggested change
emailVerified: new Date().toISOString(),
emailVerified: new Date(),

userId,
oldEmail: currentUser.email ?? "",
newEmail: request.newEmail,
ipAddress: req.headers.get("x-forwarded-for")?.split(",")[0].trim() ?? "",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

IP Address Extraction Validation Needs Improvement

The current implementation directly accesses the x-forwarded-for header without ensuring its presence or handling potential malformed values. Additionally, there is no indication of using trusted proxies or middleware to obtain the client's IP address reliably.

  • File: app/api/verify-email/route.ts at line 74

🔗 Analysis chain

Validate client IP address extraction

When extracting the client's IP address from the x-forwarded-for header, you're assuming the header is present and contains a comma-separated list of IPs. To enhance reliability:

  • Ensure the header exists before splitting and trimming.
  • Consider using a trusted proxy or middleware to obtain the client's IP.
  • Handle scenarios where the header might be missing or malformed.

You can verify the presence of the header with the following script:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for usage of 'x-forwarded-for' header in codebase

# Test: Search for instances where 'x-forwarded-for' is used
rg --type typescript '"x-forwarded-for"'

Length of output: 77


Script:

#!/bin/bash
# Description: Check for usage of 'x-forwarded-for' header in TypeScript codebase

# Test: Search for instances where 'x-forwarded-for' is used
rg --type ts '"x-forwarded-for"'

Length of output: 145


Script:

#!/bin/bash
# Description: Check for trusted proxy or middleware configurations handling client IP extraction in TypeScript codebase

# Test: Search for usage of 'trust proxy' or related middleware configurations
rg --type ts 'trust proxy|middleware'

Length of output: 589

Comment on lines +137 to +145
const { newEmail } = input;
const userId = ctx.session.user.id;

if (!newEmail) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Invalid request",
});
}
if (!newEmail) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Invalid request",
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Simplify Null or Undefined Checks

The check for !newEmail can be more precise. If newEmail is expected to be a string, consider validating its format using zod schemas or regular expressions.

Apply this diff to enhance email validation:

-  if (!newEmail) {
+  if (!newEmail || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(newEmail)) {
     throw new TRPCError({
       code: "BAD_REQUEST",
-      message: "Invalid request",
+      message: "Invalid email address",
     });
   }
📝 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.

Suggested change
const { newEmail } = input;
const userId = ctx.session.user.id;
if (!newEmail) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Invalid request",
});
}
if (!newEmail) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Invalid request",
});
}
const { newEmail } = input;
const userId = ctx.session.user.id;
if (!newEmail || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(newEmail)) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Invalid email address",
});
}

Comment on lines 147 to 157
const existingUser = await ctx.db.query.user.findFirst({
where: eq(user.email, newEmail),
});

if (ifEmailExists) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Email already exists",
});
}
if (existingUser) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Email already in use",
});
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent Timing Attacks in User Existence Checks

When checking if the new email is already in use, the error message reveals whether an email is registered in the system. This can be a security concern, as it might allow attackers to validate email addresses. Consider returning a generic error message instead.

[security]

Apply this diff to return a generic error:

   if (existingUser) {
     throw new TRPCError({
       code: "BAD_REQUEST",
-      message: "Email already in use",
+      message: "Unable to process the request",
     });
   }
📝 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.

Suggested change
// Check if the new email is already in use
const existingUser = await ctx.db.query.user.findFirst({
where: eq(user.email, newEmail),
});
if (ifEmailExists) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Email already exists",
});
}
if (existingUser) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Email already in use",
});
}
// Check if the new email is already in use
const existingUser = await ctx.db.query.user.findFirst({
where: eq(user.email, newEmail),
});
if (existingUser) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Unable to process the request",
});
}

Comment on lines +182 to +187
userId,
newEmail,
token,
expiresAt,
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle Possible Database Insertion Errors

After inserting the new email change request, it's good practice to handle any potential errors or confirm that the insertion was successful.

Apply this diff to handle insertion results:

   await ctx.db.insert(emailChangeRequest).values({
     userId,
     newEmail,
     token,
     expiresAt,
   });
+  // Optionally check if the insertion was successful
+  // If needed, include error handling logic here

Committable suggestion was skipped due to low confidence.

Comment on lines 159 to 175
const recentRequest = await ctx.db.query.emailChangeRequest.findFirst({
where: and(
eq(emailChangeRequest.userId, userId),
gte(
emailChangeRequest.createdAt,
new Date(Date.now() - 5 * 60 * 1000),
), // 5 minutes
),
});

if (recentRequest) {
throw new TRPCError({
code: "TOO_MANY_REQUESTS",
message: "Please wait before requesting another email change",
});
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize Rate Limiting Logic

The rate-limiting logic could be simplified by using a database constraint or by moving the time calculation into the database query for better performance.

Apply this diff to optimize rate limiting:

-  const recentRequest = await ctx.db.query.emailChangeRequest.findFirst({
-    where: and(
-      eq(emailChangeRequest.userId, userId),
-      gte(
-        emailChangeRequest.createdAt,
-        new Date(Date.now() - 5 * 60 * 1000),
-      ), // 5 minutes
-    ),
-  });
+  const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
+  const recentRequest = await ctx.db.query.emailChangeRequest.findFirst({
+    where: and(
+      eq(emailChangeRequest.userId, userId),
+      gte(emailChangeRequest.createdAt, fiveMinutesAgo), // 5 minutes
+    ),
+  });

Alternatively, consider adding a unique index on (userId, newEmail) with a condition on createdAt to enforce rate limiting at the database level.

📝 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.

Suggested change
// Rate limiting: Check for recent requests
const recentRequest = await ctx.db.query.emailChangeRequest.findFirst({
where: and(
eq(emailChangeRequest.userId, userId),
gte(
emailChangeRequest.createdAt,
new Date(Date.now() - 5 * 60 * 1000),
), // 5 minutes
),
});
if (recentRequest) {
throw new TRPCError({
code: "TOO_MANY_REQUESTS",
message: "Please wait before requesting another email change",
});
}
// Rate limiting: Check for recent requests
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
const recentRequest = await ctx.db.query.emailChangeRequest.findFirst({
where: and(
eq(emailChangeRequest.userId, userId),
gte(emailChangeRequest.createdAt, fiveMinutesAgo), // 5 minutes
),
});
if (recentRequest) {
throw new TRPCError({
code: "TOO_MANY_REQUESTS",
message: "Please wait before requesting another email change",
});
}

Comment on lines +369 to +374
onChange={(e) => {
setNewEmail(e.target.value);
if (sendForVerification) {
setEmailError(""); // Clear error when user starts typing again
}
}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Clear email error whenever the user modifies the email input

Currently, emailError is cleared only when sendForVerification is true. To enhance user experience and provide immediate feedback, consider clearing emailError whenever the user changes the newEmail input, regardless of sendForVerification.

Update the onChange handler to remove the condition:

 onChange={(e) => {
   setNewEmail(e.target.value);
-  if (sendForVerification) {
     setEmailError(""); // Clear error when user starts typing again
-  }
 }}
📝 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.

Suggested change
onChange={(e) => {
setNewEmail(e.target.value);
if (sendForVerification) {
setEmailError(""); // Clear error when user starts typing again
}
}}
onChange={(e) => {
setNewEmail(e.target.value);
setEmailError(""); // Clear error when user starts typing again
}}

Comment on lines +377 to +379
{emailError && sendForVerification && (
<ErrorMessage>{emailError}</ErrorMessage>
)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Display email error messages regardless of verification status

The error message for the email input is shown only when both emailError and sendForVerification are true. To ensure users are promptly aware of input errors, display the error message whenever emailError is set, regardless of sendForVerification.

Modify the condition to display the error message based solely on emailError:

-{emailError && sendForVerification && (
+{emailError && (
   <ErrorMessage>{emailError}</ErrorMessage>
 )}
📝 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.

Suggested change
{emailError && sendForVerification && (
<ErrorMessage>{emailError}</ErrorMessage>
)}
{emailError && (
<ErrorMessage>{emailError}</ErrorMessage>
)}

Comment on lines +415 to +425
export const banned_usersRelations = relations(banned_users, ({ one }) => ({
bannedBy: one(user, {
fields: [banned_users.bannedById],
references: [user.id],
relationName: "bans",
}),
);
user: one(user, {
fields: [banned_users.userId],
references: [user.id],
}),
}));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider renaming relations in banned_usersRelations for clarity

The use of relationName: "bans" for the bannedBy relation may cause confusion due to its similarity with the bans relation in userRelations. To avoid ambiguity, consider more descriptive names such as bannedByUser or banInitiator.

Copy link
Member

@John-Paul-Larkin John-Paul-Larkin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fabulous!

@vercel
Copy link

vercel bot commented Oct 12, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
codu ✅ Ready (Inspect) Visit Preview 💬 Add feedback Oct 12, 2024 9:20am

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (7)
server/api/router/profile.ts (3)

137-157: Improved input validation and security, consider enhancing email format check

The changes improve input validation and security by checking for the existence of newEmail and using a generic error message when an email is already in use. This aligns with the PR objectives of refactoring the email verification system.

However, consider implementing a more precise email format check as suggested in a previous review:

-  if (!newEmail) {
+  if (!newEmail || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(newEmail)) {
     throw new TRPCError({
       code: "BAD_REQUEST",
-      message: "Invalid request",
+      message: "Invalid email address",
     });
   }

This change would provide more specific feedback to users and prevent invalid email formats from being processed further.


159-173: Effective rate limiting implemented, consider optimization

The implementation of rate limiting to prevent frequent email change requests within a two-minute window is a good addition. It aligns with the PR objectives of preventing spamming and avoiding locking up email accounts.

However, consider optimizing the rate limiting logic as suggested in a previous review:

-  const twoMinutesAgo = new Date(Date.now() - 2 * 60 * 1000);
-  const recentRequest = await ctx.db.query.emailChangeRequest.findFirst({
-    where: and(
-      eq(emailChangeRequest.userId, userId),
-      gte(emailChangeRequest.createdAt, twoMinutesAgo), // 2 minutes
-    ),
-  });
+  const recentRequest = await ctx.db.query.emailChangeRequest.findFirst({
+    where: and(
+      eq(emailChangeRequest.userId, userId),
+      gte(emailChangeRequest.createdAt, new Date(Date.now() - 2 * 60 * 1000)),
+    ),
+  });

This change would simplify the code and potentially improve performance by moving the time calculation into the database query.


175-185: Token generation and database insertion implemented, consider error handling

The implementation of token generation, expiration date calculation, and insertion of the new email change request into the database is correct and aligns with the PR objectives.

However, consider adding error handling for the database insertion as suggested in a previous review:

   await ctx.db.insert(emailChangeRequest).values({
     userId,
     newEmail,
     token,
     expiresAt,
   });
+  // Handle potential insertion errors
+  .catch((error) => {
+    console.error("Failed to insert email change request:", error);
+    throw new TRPCError({
+      code: "INTERNAL_SERVER_ERROR",
+      message: "Failed to process email change request",
+    });
+  });

This addition would improve the robustness of the email change process by explicitly handling potential database errors.

app/(app)/settings/_client.tsx (4)

53-53: LGTM: Improved form handling and email verification

The changes to the form state handling, including the addition of isSubmitting and cooldown, improve the overall functionality and align with the PR objective of refactoring email verification to prevent spamming. The cooldown mechanism is a good addition to prevent excessive requests.

Consider extracting the email validation regex to a constant or utility function for better maintainability:

const EMAIL_REGEX = /\S+@\S+\.\S+/;
const isValidEmail = (email: string) => EMAIL_REGEX.test(email);

This would make it easier to update the email validation logic in the future if needed.

Also applies to: 70-70, 77-77, 85-86, 96-102


240-344: LGTM: Improved layout and consistency for personal information sections

The redesigned personal information sections (name, username, bio, location, website) now use a consistent grid layout and new UI components, resulting in better visual hierarchy and improved user experience. This change aligns well with the PR objectives of enhancing the settings screen and making it responsive.

For consistency, consider adding a placeholder prop to the name and username inputs, similar to the location and websiteUrl inputs:

<Input
  id="name"
  type="text"
  placeholder="Your full name"
  autoComplete="given-name"
  invalid={!!errors?.name}
  {...register("name")}
/>

<Input
  id="username"
  type="text"
  placeholder="Your username"
  autoComplete="username"
  invalid={!!errors?.username}
  {...register("username")}
/>

This would provide a consistent user experience across all input fields.


348-398: LGTM with suggestions: Improved email update section

The redesigned email update section with separate current and new email inputs, along with the cooldown mechanism, aligns well with the PR objective of refactoring email verification to prevent spamming. The improved error handling enhances the user experience.

However, there are a few suggestions for improvement:

  1. Consider using a more descriptive variable name instead of sendForVerification, such as isVerificationSent or isAwaitingVerification.

  2. The error message display condition can be simplified:

{emailError && <ErrorMessage>{emailError}</ErrorMessage>}
  1. The button text could be more consistent:
{loading ? "Sending..." : cooldown > 0 ? `Wait ${cooldown}s` : "Send Verification Email"}
  1. Consider adding a success message when the verification email is sent successfully.

These changes would further improve the user experience and code readability.


Line range hint 1-444: Great job on the settings screen redesign!

The extensive refactoring of the Settings component has resulted in a significantly improved user interface that aligns well with the PR objectives. Key improvements include:

  1. Consistent use of new UI components throughout the form.
  2. Improved layout with a responsive grid structure.
  3. Enhanced visual hierarchy and spacing.
  4. Better error handling and user feedback.
  5. Implementation of a cooldown mechanism for email verification.

These changes collectively contribute to a more organized, visually appealing, and user-friendly settings screen.

For future enhancements, consider implementing form state persistence (e.g., using localStorage) to prevent loss of user input if they accidentally navigate away from the page. This could further improve the user experience, especially for longer forms.

Overall, excellent work on improving both the functionality and aesthetics of the settings screen!

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between d42a9f3 and 85f4047.

📒 Files selected for processing (2)
  • app/(app)/settings/_client.tsx (7 hunks)
  • server/api/router/profile.ts (3 hunks)
🧰 Additional context used
🔇 Additional comments (7)
server/api/router/profile.ts (3)

1-1: LGTM: Import changes align with new functionality

The new imports for emailChangeHistory, and, gte, emailChangeRequest, and z are appropriate for the email change functionality and database operations. The modified import for generateEmailToken and sendVerificationEmail also aligns with the refactoring of the email verification system.

Also applies to: 18-18, 20-20, 22-23


187-198: LGTM: Robust email sending implementation

The implementation of sending the verification email, including the try-catch block and error handling, is well done. It aligns with the PR objectives of refactoring the email verification system and improves the robustness of the process. The error logging will help with debugging in case of email sending failures.


137-198: Overall: Solid implementation meeting PR objectives

The changes to the updateEmail procedure in server/api/router/profile.ts successfully address the PR objectives:

  1. The email verification system has been refactored to prevent spamming and avoid locking up email accounts.
  2. Input validation, rate limiting, and error handling have been implemented to enhance security and robustness.
  3. The changes support the overall goal of updating the settings screen and fixing issues related to the Drizzle Kit Studio.

The suggested improvements (email format check, rate limiting optimization, and database insertion error handling) are minor and aimed at further enhancing the code quality. Overall, this is a well-implemented feature that significantly improves the email change process.

app/(app)/settings/_client.tsx (4)

Line range hint 15-27: LGTM: Improved imports for enhanced UI components

The addition of new UI component imports, such as Loader2, Subheading, Divider, and Text, indicates an improved and more organized UI structure. This change aligns well with the PR objective of enhancing the settings screen.


196-237: LGTM: Improved profile picture upload section

The redesigned profile picture upload section uses new UI components, resulting in a more visually appealing and user-friendly interface. This change aligns well with the PR objective of enhancing the settings screen. The functionality remains intact while improving the overall user experience.


400-432: LGTM: Improved notification and newsletter settings

The redesigned notification and newsletter settings using the Switch component provide a more intuitive and visually appealing interface. The improved layout with better spacing and visual hierarchy enhances the overall user experience. This change aligns well with the PR objective of enhancing the settings screen.


434-444: LGTM: Improved form submission and reset buttons

The redesigned and repositioned form submission and reset buttons provide a better user experience. Disabling the "Save Changes" button during form submission prevents multiple submissions and provides clear visual feedback to the user. The positioning of these buttons at the end of the form follows common UI patterns, making it intuitive for users.

@NiallJoeMaher NiallJoeMaher merged commit 126ddf5 into codu-code:develop Oct 12, 2024
@NiallJoeMaher NiallJoeMaher deleted the chore/cleanup-settings-and-drizzlekit branch October 12, 2024 09:25
@coderabbitai coderabbitai bot mentioned this pull request Oct 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants