-
Notifications
You must be signed in to change notification settings - Fork 1
Fix/tutor profile registering #39
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
base: main
Are you sure you want to change the base?
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughServer and client were updated for role-aware UI and tutor profile flows: TRPC now resolves viewer role from the DB; Sidebar and Settings query that role to render a tutor/student badge and conditional Signals link; onboarding state is reset on mount; tutor profile creation enforces new validation, persists preferredSessionTypes, and runs a mutation with toasts and navigation. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client Component
participant TRPC as TRPC Client
participant Server as TRPC Resolver
participant DB as Database
Client->>TRPC: api.signal.getViewerRole.useQuery()
TRPC->>Server: invoke getViewerRole
Server->>DB: lookup user by ctx.clerkUid
alt user found
DB-->>Server: user with role
Server-->>TRPC: return role (TUTOR/STUDENT)
else not found
DB-->>Server: null
Server-->>TRPC: return default STUDENT
end
TRPC-->>Client: role -> isTutor
Client->>Client: render badge / conditional UI
sequenceDiagram
participant User as User
participant Page as Tutor Summary Page
participant Mutation as tutor.createProfile
participant Server as Server
participant Toast as Toast
participant Store as Onboarding Store
participant Router as Router
User->>Page: Click "Finish"
Page->>Mutation: handleFinish(data)
Mutation->>Server: submit profile
alt success
Server-->>Mutation: created
Mutation->>Toast: show success
Mutation->>Store: reset()
Mutation->>Router: navigate("/dashboard")
else error
Server-->>Mutation: error
Mutation->>Toast: show error
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
Comment |
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: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/server/api/routers/signal.ts (2)
128-139: Wrap signal acceptance in a database transaction to prevent race conditions.The race condition is confirmed. Since signal status check, conversation creation, and signal update are separate operations, multiple tutors can simultaneously accept the same pending signal, creating duplicate conversations.
The Conversation schema has no unique constraint on user pairs, so both operations will succeed. The suggested transactional fix is appropriate and follows existing patterns in the codebase (see student.ts:35, tutor.ts:25). Implement the proposed solution to re-check signal status within the transaction before accepting:
- // TODO: Add transactional handling, concurrency locks to avoid double-accept - // Create or reuse a conversation between tutor (me.id) and student (signal.studentId) - const conversation = await ctx.db.conversation.create({ - data: { - User: { - connect: [ - { id: me.id }, - { id: signal.studentId }, - ], - }, - }, - }); - - // Update signal status - await ctx.db.signal.update({ - where: { id: input.signalId }, - data: { status: "accepted" }, - }); + // Use transaction to prevent race conditions + const conversation = await ctx.db.$transaction(async (tx) => { + // Re-check signal status within transaction + const currentSignal = await tx.signal.findUnique({ + where: { id: input.signalId }, + select: { status: true, studentId: true }, + }); + + if (!currentSignal || currentSignal.status !== "pending") { + throw new TRPCError({ + code: "CONFLICT", + message: "Signal already handled by another tutor" + }); + } + + // Create conversation + const conv = await tx.conversation.create({ + data: { + User: { + connect: [ + { id: me.id }, + { id: currentSignal.studentId }, + ], + }, + }, + }); + + // Update signal status + await tx.signal.update({ + where: { id: input.signalId }, + data: { status: "accepted" }, + }); + + return conv; + });
111-114: Inconsistent user lookup confirmed—fix line 112 to useclerkUid.The verification confirms the review comment is correct.
ctx.user.idreturns the Clerk user ID (fromcurrentUser()), whileUser.idis the database primary key. Line 13 ingetViewerRolecorrectly queries byclerkUid: ctx.user.id, but line 112 inacceptSignalincorrectly queries byid: ctx.user.id, which will fail to find the user.Fix required:
const me = await ctx.db.user.findUnique({ - where: { id: ctx.user.id }, + where: { clerkUid: ctx.user.id }, select: { id: true, role: true }, });
🧹 Nitpick comments (3)
src/app/onboarding/tutor/summary/page.tsx (1)
21-30: Consider reordering reset before navigation.The mutation setup is solid. One minor suggestion: call
reset()beforerouter.push()to ensure the store is cleaned up before navigation begins, which is more logical for state management flow.Apply this diff:
onSuccess: () => { toast.success("Profile created successfully!"); - reset(); router.push("/dashboard"); + reset(); },Also note: The error handler displays
error.messagedirectly, which assumes server errors are always user-friendly. Consider adding a fallback message if needed.src/app/dashboard/_components/sidebar.tsx (1)
33-35: Consider showing a loading indicator for the role badge.While the role is being fetched, the badge will always display "👤 Student". Since the sidebar is always visible, this could be confusing for tutors who see the wrong badge briefly.
- <div className="mt-2 inline-block px-2.5 py-1 rounded-full text-xs font-semibold bg-indigo-100 text-indigo-900"> - {isTutor ? "🎓 Tutor" : "👤 Student"} - </div> + <div className="mt-2 inline-block px-2.5 py-1 rounded-full text-xs font-semibold bg-indigo-100 text-indigo-900"> + {isLoading ? "..." : isTutor ? "🎓 Tutor" : "👤 Student"} + </div>src/server/api/routers/signal.ts (1)
111-117: Consider refactoring to reuse the role check logic.The
acceptSignalmutation duplicates the role-checking pattern fromgetViewerRole. This introduces maintenance burden and potential inconsistency.Consider extracting a shared helper or calling the existing resolver internally:
// Option 1: Extract helper function async function getUserRole(ctx: Context) { const user = await ctx.db.user.findUnique({ where: { clerkUid: ctx.user.id }, select: { id: true, role: true }, }); return { user, role: user?.role ?? "STUDENT" }; }Then use it in both
getViewerRoleandacceptSignal:getViewerRole: protectedProcedure.query(async ({ ctx }) => { - const user = await ctx.db.user.findUnique({ - where: { clerkUid: ctx.user.id }, - select: { id: true, role: true }, - }); - return user?.role ?? "STUDENT"; + const { role } = await getUserRole(ctx); + return role; }),acceptSignal: protectedProcedure .input(z.object({ signalId: z.string() })) .mutation(async ({ ctx, input }) => { - const me = await ctx.db.user.findUnique({ - where: { id: ctx.user.id }, - select: { id: true, role: true }, - }); - if (me?.role !== "TUTOR") { + const { user: me, role } = await getUserRole(ctx); + if (role !== "TUTOR") { throw new TRPCError({ code: "FORBIDDEN", message: "Only tutors can accept signals" }); }Note: Line 112 queries by
idwhilegetViewerRolequeries byclerkUid. Ensure both lookups target the same user correctly.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/app/dashboard/_components/sidebar.tsx(2 hunks)src/app/dashboard/settings/page.tsx(1 hunks)src/app/onboarding/page.tsx(1 hunks)src/app/onboarding/tutor/summary/page.tsx(3 hunks)src/server/api/routers/signal.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
src/app/onboarding/page.tsx (1)
src/stores/onboarding.ts (1)
useOnboardingStore(96-220)
src/app/onboarding/tutor/summary/page.tsx (2)
src/stores/onboarding.ts (1)
useOnboardingStore(96-220)src/trpc/react.tsx (1)
api(25-25)
src/app/dashboard/_components/sidebar.tsx (1)
src/trpc/react.tsx (1)
api(25-25)
src/server/api/routers/signal.ts (1)
src/server/api/trpc.ts (1)
protectedProcedure(104-104)
src/app/dashboard/settings/page.tsx (1)
src/trpc/react.tsx (1)
api(25-25)
🔇 Additional comments (4)
src/app/onboarding/page.tsx (1)
3-3: Clean implementation of reset-on-mount behavior.The addition of the reset logic correctly clears onboarding state when users land on this page, ensuring a fresh start for each onboarding attempt. The useEffect dependency array properly includes
reset.Consider verifying the UX implications: if a user is on a subsequent onboarding page (e.g.,
/onboarding/student/goals) and navigates back to this page to change their role selection, all their progress will be cleared. Confirm this matches the intended user flow.Also applies to: 10-15
src/app/onboarding/tutor/summary/page.tsx (3)
7-8: LGTM!The imports are correct and necessary for the profile creation mutation and user feedback.
18-18: LGTM!Correctly extracts the reset function to clear onboarding state after successful profile creation.
85-92: LGTM!Excellent loading state handling with proper disabled state and user feedback during submission.
| const { data: role } = api.signal.getViewerRole.useQuery(); | ||
| const isTutor = role === "TUTOR"; |
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 loading state and consider extracting the role query logic.
The role query pattern is duplicated in both sidebar.tsx and settings/page.tsx. During initial load, role will be undefined, causing the badge to always show "👤 Student" until data arrives.
Solution 1: Add loading state handling
- const { data: role } = api.signal.getViewerRole.useQuery();
- const isTutor = role === "TUTOR";
+ const { data: role, isLoading } = api.signal.getViewerRole.useQuery();
+ const isTutor = role === "TUTOR";Then render a loading indicator in the badge area while isLoading is true.
Solution 2 (recommended): Extract to a custom hook to reduce duplication
Create src/hooks/useViewerRole.ts:
import { api } from "@/trpc/react";
export function useViewerRole() {
const { data: role, isLoading, error } = api.signal.getViewerRole.useQuery();
const isTutor = role === "TUTOR";
return { role, isTutor, isLoading, error };
}Then use it in both components:
- const { data: role } = api.signal.getViewerRole.useQuery();
- const isTutor = role === "TUTOR";
+ const { isTutor, isLoading } = useViewerRole();🤖 Prompt for AI Agents
In src/app/dashboard/_components/sidebar.tsx around lines 26-27, the role query
result is used directly so during initial load role is undefined and the UI
shows "👤 Student" incorrectly; also the same query logic is duplicated
elsewhere. Fix by handling loading state: consume isLoading (and optionally
error) from api.signal.getViewerRole.useQuery and render a loading indicator in
the badge area while isLoading is true instead of defaulting to Student. To
remove duplication, extract the query into a reusable hook (e.g.,
src/hooks/useViewerRole.ts) that returns { role, isTutor, isLoading, error } and
import that hook in sidebar.tsx and settings/page.tsx.
| const handleFinish = () => { | ||
| createProfile({ | ||
| subjectInterests, | ||
| teachingLevels, | ||
| yearsOfExperience: yearsOfExperience ?? 0, | ||
| teachingStyle, | ||
| preferredSessionTypes, | ||
| }); | ||
| }; |
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.
🧩 Analysis chain
Verify validation for required profile fields.
The function correctly passes onboarding data to the mutation. However, there's no client-side validation before submission. For instance, a tutor could submit with empty subjectInterests or teachingLevels arrays.
Confirm that server-side validation handles these cases appropriately. If so, consider adding client-side validation to improve UX by catching issues before the network request.
Run this script to check the server-side validation logic:
🏁 Script executed:
#!/bin/bash
# Description: Locate and examine the tutor.createProfile mutation handler to verify validation
# Find the createProfile mutation implementation
ast-grep --pattern 'createProfile: $_()'
# Also search for the procedure/mutation definition
rg -A 20 'createProfile.*=.*' --type tsLength of output: 3160
I need to locate the server-side procedure implementation. Let me search for the tRPC router that defines the createProfile procedure:
Let me search for the tutor router file in the codebase:
Now let me search the codebase for the tutor router file:
🏁 Script executed:
#!/bin/bash
# Search for tutor router file and createProfile procedure
# Look for files containing tutor router
fd -e ts -e tsx | grep -E '(router|tutor)' | head -20
# Search for createProfile mutation definition
rg -A 30 'createProfile.*mutation\|createProfile.*:.*procedure' --type tsLength of output: 741
Perfect! Found the tutor router. Let me examine it to find the createProfile procedure and its validation:
🏁 Script executed:
#!/bin/bash
# Examine the tutor router file for createProfile procedure
cat src/server/api/routers/tutor.tsLength of output: 6297
Fix data loss and strengthen validation in createProfile procedure.
Server-side validation exists but has two critical issues:
-
Data loss bug: The client sends
preferredSessionTypes, but the server procedure (lines 51–56 insrc/server/api/routers/tutor.ts) does not save it to the database. AddpreferredSessionTypes: input.preferredSessionTypesto thetutorProfile.create()data object. -
Weak validation: Array fields allow empty arrays and
yearsOfExperiencehas no bounds. Add.min(1)to array schemas and.min(0)toyearsOfExperiencein the Zod input validation (lines 11–15).
🤖 Prompt for AI Agents
In src/server/api/routers/tutor.ts around lines 51–56 and the Zod input schema
around lines 11–15: the server currently drops preferredSessionTypes and allows
empty arrays and unbounded yearsOfExperience. In the tutorProfile.create() data
object add preferredSessionTypes: input.preferredSessionTypes so the field is
persisted, and tighten validation by adding .min(1) to each array schema (so
arrays cannot be empty) and .min(0) to yearsOfExperience in the Zod input schema
to enforce a non-negative value.
| getViewerRole: protectedProcedure.query(async ({ ctx }) => { | ||
| // Find user by Clerk ID and check if they have a tutor profile | ||
| const user = await ctx.db.user.findUnique({ | ||
| where: { clerkUid: ctx.user.id }, | ||
| select: { id: true, role: true }, | ||
| }); | ||
|
|
||
| createSignal: protectedProcedure | ||
| // If user exists in DB, use their role; otherwise default to STUDENT | ||
| return user?.role ?? "STUDENT"; | ||
| }), createSignal: protectedProcedure |
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.
🧩 Analysis chain
Fix formatting on line 19 and verify user existence handling.
Line 19 has a formatting issue where the closing parenthesis, comma, and next procedure declaration are on the same line.
Apply this diff to fix the formatting:
// If user exists in DB, use their role; otherwise default to STUDENT
return user?.role ?? "STUDENT";
- }), createSignal: protectedProcedure
+ }),
+
+ createSignal: protectedProcedureAdditional observation: The fallback to "STUDENT" when user is null handles the case where a Clerk user hasn't been synced to the database yet. Ensure your user sync logic (e.g., Clerk webhook) creates database records for all authenticated users, otherwise this fallback will mask missing user records.
---
🏁 Script executed:
```shell
#!/bin/bash
# Examine the signal.ts file to verify formatting and context
find . -name "signal.ts" -type f | head -20
Length of output: 34
🏁 Script executed:
#!/bin/bash
# Look at the full context around lines 10-19 and beyond
if [ -f "src/server/api/routers/signal.ts" ]; then
echo "=== Lines 1-30 of signal.ts ===" && \
sed -n '1,30p' src/server/api/routers/signal.ts && \
echo "" && \
echo "=== Lines 110-135 (for acceptSignal and TODO context) ===" && \
sed -n '110,135p' src/server/api/routers/signal.ts
fiLength of output: 2623
🏁 Script executed:
#!/bin/bash
# Search for TODO comments in signal.ts related to transactions/concurrency
if [ -f "src/server/api/routers/signal.ts" ]; then
rg -n "TODO|FIXME|transact|concur" src/server/api/routers/signal.ts -A2 -B2
fiLength of output: 291
🏁 Script executed:
#!/bin/bash
# Search for Clerk webhook handlers and user sync logic
find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" \) | xargs rg -l "clerk.*webhook|webhook.*user|user.*sync" 2>/dev/null | head -10Length of output: 1275
🏁 Script executed:
#!/bin/bash
# Check for user creation/sync logic in server directory
rg -n "user.*create|onCreate.*user|webhook" src/server --type ts -A3 -B1 2>/dev/null | head -50Length of output: 0
🏁 Script executed:
#!/bin/bash
# Verify the database schema for the user model
find . -type f \( -name "schema.prisma" -o -name "*schema*" \) | grep -E "(prisma|schema)" | head -5Length of output: 263
---
🏁 Script executed:
```shell
#!/bin/bash
# Examine the Prisma schema to verify user model and clerkUid field
cat prisma/schema.prisma | grep -A 20 "model User"
Length of output: 764
🏁 Script executed:
#!/bin/bash
# Search for Clerk webhook handlers in API routes (next.js app structure)
find . -type f \( -name "*.ts" -o -name "*.tsx" \) -path "*/api/*" | xargs rg -l "clerk" 2>/dev/null | grep -v node_modules | head -10Length of output: 162
🏁 Script executed:
#!/bin/bash
# Look for user creation in entire src directory with more context
rg -n "\.user\.create|\.user\.upsert" src/ --type ts -A 2 -B 2 2>/dev/null | head -40Length of output: 789
🏁 Script executed:
#!/bin/bash
# Search for any webhook or sync logic files
find src -type f -name "*.ts" | xargs rg -l "webhook|sync|clerk" 2>/dev/nullLength of output: 292
🏁 Script executed:
#!/bin/bash
# Check for middleware that might handle Clerk authentication setup
find src -type f -name "*.ts" | xargs rg -l "middleware|auth|clerk" 2>/dev/null | head -10Length of output: 220
---
🏁 Script executed:
```shell
#!/bin/bash
# Examine trpc.ts to understand ctx.user.id and how context is set up
cat src/server/api/trpc.ts | head -80
Length of output: 1996
🏁 Script executed:
#!/bin/bash
# Get full acceptSignal implementation context around line 111
sed -n '105,130p' src/server/api/routers/signal.tsLength of output: 1202
🏁 Script executed:
#!/bin/bash
# Check if ctx.user is a Clerk user or application user
rg -n "ctx\.user" src/server/api/trpc.ts -B 3 -A 3 | head -50Length of output: 350
Fix formatting on line 19 and correct the user lookup in acceptSignal.
Line 19 has a formatting issue where the closing parenthesis, comma, and next procedure are on the same line. Additionally, acceptSignal contains a critical bug: it queries the user by id: ctx.user.id, but ctx.user.id is the Clerk ID, not the Prisma user ID. The query must use clerkUid instead, matching the pattern in getViewerRole.
Apply these diffs:
Formatting fix (line 19):
return user?.role ?? "STUDENT";
- }), createSignal: protectedProcedure
+ }),
+
+ createSignal: protectedProcedureCritical bug fix in acceptSignal (line 111):
// Guard: Only tutors can accept signals - check DB role
const me = await ctx.db.user.findUnique({
- where: { id: ctx.user.id },
+ where: { clerkUid: ctx.user.id },
select: { id: true, role: true },
});Additional observation: Line 128 documents a known TODO about transactional handling and concurrency locks. This should be addressed separately to prevent race conditions on signal acceptance.
🤖 Prompt for AI Agents
In src/server/api/routers/signal.ts around lines 10 to 19 and line 111, fix
formatting by moving the closing parenthesis/comma so the protectedProcedure
entries are on separate lines (ensure the getViewerRole procedure ends with its
own closing parenthesis and comma on line 19), and in acceptSignal change the
user lookup to query by clerkUid: ctx.user.id (matching getViewerRole) instead
of id: ctx.user.id to use the Clerk ID correctly; leave the TODO about
transactional/concurrency locking (line 128) to be handled separately.
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: 1
🧹 Nitpick comments (1)
src/server/api/routers/tutor.ts (1)
55-66: Consider includingpreferredSessionTypesin embedding text.The embedding generation omits
preferredSessionTypes, which could reduce matching quality. If a student prefers online sessions and a tutor only offers in-person, the embedding won't capture this mismatch.Enhance the embedding text:
const embeddingText = ` This text describes a tutor profile for an AI tutoring match system. Subject Interests: ${input.subjectInterests.map((s) => `- ${s}`).join("\n")} Teaching Levels: ${input.teachingLevels.map((l) => `- ${l}`).join("\n")} Teaching Style: ${input.teachingStyle.join(", ")} + Preferred Session Types: + ${input.preferredSessionTypes.join(", ")} `;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/app/dashboard/settings/page.tsx(1 hunks)src/server/api/routers/tutor.ts(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/dashboard/settings/page.tsx (1)
src/trpc/react.tsx (1)
api(25-25)
🔇 Additional comments (6)
src/app/dashboard/settings/page.tsx (4)
1-6: LGTM! Imports are clean and necessary.All imports are used appropriately, and the "use client" directive is correctly placed for this client component that uses TRPC hooks.
9-10: Excellent! Previous review comment addressed.The loading and error states are now properly extracted, and the
isTutorcomputation is defensive (!!role && role === "TUTOR"), preventing the UI flash issue mentioned in the previous review.
16-28: Loading and error states are well-implemented.The loading skeleton provides visual feedback, and the error message is clear and user-friendly. The implementation is appropriate and follows standard UI patterns.
30-41: Tutor-specific UI is correctly gated.The conditional rendering logic ensures the "Signals" section only appears when the role is successfully loaded and the user is a tutor. The navigation and styling are consistent with the rest of the component.
src/server/api/routers/tutor.ts (2)
10-13: Validation constraints improve data quality.The added minimum constraints and custom error messages ensure that tutor profiles have meaningful data. The validation is appropriate and user-friendly.
67-67: Style improvement enhances readability.The explicit
{ where: ..., data: ... }format is clearer and more maintainable than a compact inline version.
| teachingLevels: z.array(z.string()).min(1, "At least one teaching level is required"), | ||
| yearsOfExperience: z.number().min(0, "Years of experience cannot be negative"), | ||
| teachingStyle: z.array(z.string()).min(1, "At least one teaching style is required"), | ||
| preferredSessionTypes: z.array(z.string()).min(1, "At least one session type is required"), |
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.
Critical: preferredSessionTypes is validated but never saved.
Line 14 requires preferredSessionTypes with a minimum of 1 item, but the tutorProfile.create data object (lines 45-53) doesn't include it. This causes data loss—users provide this required field during registration, but it's never persisted to the database.
Apply this diff to save the field:
const newProfile = await tx.tutorProfile.create({
data: {
userId: user.id,
subjectInterests: input.subjectInterests,
teachingLevels: input.teachingLevels,
yearsOfExperience: input.yearsOfExperience,
teachingStyle: input.teachingStyle,
+ preferredSessionTypes: input.preferredSessionTypes,
},
});Consider also including preferredSessionTypes in the embedding text (lines 55-63) to improve tutor matching quality.
Also applies to: 45-53
🤖 Prompt for AI Agents
In src/server/api/routers/tutor.ts around lines 14 and 45-63, the schema
requires preferredSessionTypes (line 14) but that field is not included in the
tutorProfile.create payload (lines 45-53) and therefore never persisted; update
the create data to include preferredSessionTypes so it is saved to the DB
(ensure the DB column/ORM accepts an array or appropriate type), and also append
preferredSessionTypes (e.g., joined as a comma-separated string) into the
embedding text construction (lines 55-63) so the value is included when
generating tutor embeddings for matching.
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.
i don't think this is accurate @coderabbitai
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.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find and examine the Prisma schema to check TutorProfile model
fd -e prisma -x cat {}
Length of output: 2963
@AJ-505 You're absolutely right, and I apologize for the confusion.
After checking the Prisma schema, I can see that the TutorProfile model doesn't have a preferredSessionTypes field at all in the database schema. The model only includes:
subjectIntereststeachingLevelsyearsOfExperienceteachingStyleembedding
The actual issue is a mismatch between the validation schema and the database schema:
- Line 14 validates and requires
preferredSessionTypesas input - But the database has no column to store it
This means either:
- The validation for
preferredSessionTypesshould be removed from the input schema (line 14), OR - A database migration is needed to add the
preferredSessionTypescolumn to theTutorProfiletable
Which approach would you prefer? If preferredSessionTypes was intended to be stored, you'll need to add it to the Prisma schema and run a migration.
🧬 Code Graph Analysis Results
-
File: src/server/api/trpc.ts
- Lines 78-78
export const createTRPCRouter = t.router;
- Lines 78-78
-
File: src/server/api/trpc.ts
- Lines 104-104
export const protectedProcedure = t.procedure.use(enforceUserIsAuthed);
- Lines 104-104
-
File: src/server/api/trpc.ts
- Lines 87-87
export const publicProcedure = t.procedure;
- Lines 87-87
-
File: src/server/bedrock.ts
- Lines 19-35
export const invokeModel = async (inputText: string) => { const command = new InvokeModelCommand({ modelId, contentType: "application/json", accept: "*/*", body: JSON.stringify({ inputText, dimensions: 512, normalize: true, }), }); const response = await bedrockClient.send(command); const body = JSON.parse(new TextDecoder().decode(response.body)) as { embedding: number[]; }; return body; };
- Lines 19-35
|
I could not make the migrations: I got the following error: Please do not merge until i am able to make the migration changes. |
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: 1
| -- AlterTable | ||
| ALTER TABLE "TutorProfile" ADD COLUMN "preferredSessionTypes" TEXT[]; |
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.
🧩 Analysis chain
Verify application code handles nullable preferredSessionTypes column.
The migration adds preferredSessionTypes as a nullable TEXT array with no DEFAULT value. Existing TutorProfile records will have NULL for this column upon migration completion. This can cause runtime errors if the application assumes the field always has a value.
Please verify that:
- The application code properly checks for NULL values before accessing or iterating over
preferredSessionTypes - The tutor profile creation/update logic correctly persists non-empty arrays to this column
- Consider whether a DEFAULT value (e.g.,
'{}'for an empty array) would be more appropriate to prevent NULL handling logic throughout the codebase
Run the following script to check how the field is referenced in application code:
Additionally, regarding the "P1017 Server has closed the connection" error mentioned in the PR comments: This appears to be a database connection issue rather than a SQL syntax error. Verify that your database server is running and the connection string is correct before re-running the migration.
🏁 Script executed:
#!/bin/bash
# Description: Verify application code handles preferredSessionTypes field and check for potential NULL issues
# Search for references to preferredSessionTypes in TypeScript/JavaScript files
rg -n "preferredSessionTypes" --type ts --type js -A 3 -B 1
# Search for array operations on the field to check if NULL is handled
rg -nP "preferredSessionTypes\s*\?" --type ts --type jsLength of output: 8178
🏁 Script executed:
#!/bin/bash
# Search for TutorProfile queries and direct database access to preferredSessionTypes
rg -n "TutorProfile" --type ts -A 5 -B 2 | grep -A 5 -B 2 "preferredSessionTypes"
# Look for schema.prisma to verify field definition
fd -e prisma -type f
# Search for any direct access patterns that might not handle NULL
rg -n "\\.preferredSessionTypes" --type ts -C 2Length of output: 2920
🏁 Script executed:
#!/bin/bash
# Check the Prisma schema for the field definition
if [ -f schema.prisma ]; then
cat schema.prisma | grep -A 5 -B 5 "preferredSessionTypes"
fi
# Also check if it might be in a different location
find . -name "*.prisma" -type f -exec grep -l "preferredSessionTypes" {} \;Length of output: 22
🏁 Script executed:
#!/bin/bash
# Get the Prisma schema definition for TutorProfile and preferredSessionTypes
cat ./prisma/schema.prisma | grep -A 30 "model TutorProfile"Length of output: 1067
Fix schema-migration mismatch: preferredSessionTypes must match nullability across both files.
The migration creates a nullable column (TEXT[] without NOT NULL), but the Prisma schema defines it as required (String[] without ?). This creates a data type mismatch: existing TutorProfile records will have NULL values that conflict with the non-nullable TypeScript type, causing runtime errors when code like Array.from(preferredSessionTypes) attempts to process NULL.
Choose one fix:
- Update migration to add
DEFAULT '{}'::text[]andNOT NULLconstraint to match schema requirement, OR - Make the schema field optional: change
preferredSessionTypes String[]topreferredSessionTypes String[]?
Option 1 is recommended to maintain the application's expectation of always having an array.
🤖 Prompt for AI Agents
In prisma/migrations/20251025132207_tutorprofile_updates/migration.sql around
lines 1-2, the migration adds preferredSessionTypes as a nullable TEXT[] while
the Prisma schema declares it non-nullable (String[]), causing a nullability
mismatch; update the migration to make the column non-nullable with a safe
default by altering the statement to add DEFAULT '{}'::text[] and NOT NULL so
existing rows get an empty array and the DB matches the Prisma schema (i.e.,
ensure the migration sets DEFAULT '{}'::text[] and adds NOT NULL for
"preferredSessionTypes").
Summary by CodeRabbit
New Features
Bug Fixes