feat(buddy): per-user session history and journal after shared sit (#119)#125
Conversation
Add optional buddy_session_id on sessions with a partial unique index so each user gets one personal row per completed buddy sit. New record-personal-session API creates that row (idempotent), stores in-sit thoughts, updates participant metadata, and increments current day like solo sessions. Harden POST /api/thoughts/batch with session ownership checks and replace prior completion notes (time_in_session = -1) to avoid duplicate journal entries. Filter session detail thoughts by user id. Web app: when the shared timer finishes, save the personal session, leave the buddy room, and route through the existing CompletionScreen for the post-sit note. Solo flow unchanged. Includes incremental SQL reference for existing databases. Made-with: Cursor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughAdds per-user personal-session recording for buddy sessions: DB schema extends Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Frontend as BuddySessionRoom (Component)
participant Page as App Page (State)
participant API as recordPersonalSession API
participant DB as Database
User->>Frontend: Shared timer completes
Frontend->>Frontend: compute clearPercent, thoughts, mindStateLog
Frontend->>API: POST /api/buddy/sessions/{id}/record-personal-session
API->>API: auth, validate sessionId, requireBuddySessionCompletedForPersonalRecord
API->>API: normalize inputs (date, clearPercent, thoughts)
API->>DB: BEGIN transaction
API->>DB: SELECT users.currentDay
API->>DB: INSERT INTO sessions (with buddy_session_id)
API->>DB: UPDATE users (increment currentDay, updatedAt)
API->>DB: UPDATE buddy_sessions (increment revision, updatedAt)
API->>DB: UPDATE participant (participantCompletedAt, lastSeenAt)
API->>DB: INSERT thoughts (if any)
API->>DB: COMMIT
API-->>Frontend: { session, already? }
Frontend->>Page: onPersonalRecordComplete(payload)
Page->>Page: set completionData, clear buddySessionId, refresh user (api.me)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 📋 Issue PlannerLet us write the prompt for your AI agent so you can ship faster (with fewer bugs). View plan for ticket: ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/app/api/buddy/sessions/[id]/record-personal-session/route.ts (1)
59-61: Date format validation could be stricter.The regex validates the format but not actual date validity (e.g.,
"2023-99-99"would pass). While PostgreSQL'sdatecolumn will reject invalid dates, a stricter validation provides clearer error messages.♻️ Proposed stricter validation
function sessionDateOk(s: string): boolean { - return /^\d{4}-\d{2}-\d{2}$/.test(s); + if (!/^\d{4}-\d{2}-\d{2}$/.test(s)) return false; + const d = new Date(s + "T00:00:00Z"); + return !Number.isNaN(d.getTime()) && d.toISOString().startsWith(s); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/buddy/sessions/`[id]/record-personal-session/route.ts around lines 59 - 61, The sessionDateOk function currently only checks format; update it to also verify the date is real by parsing and comparing components: split the input in sessionDateOk into year/month/day, construct a Date (or use a reliable date util), ensure the Date is valid (not NaN) and that the Date's year, month, and day match the parsed values (so values like "2023-99-99" fail), then return true only if both the regex format and the constructed Date match the input.src/components/BuddySessionRoom.tsx (1)
37-53: Add guard against division by zero.If
totalSecondsis 0, line 52 will divide by zero, returningNaN. While a completed buddy session should have a positive duration in practice, a defensive check would make this more robust.🛡️ Proposed fix
function calcBuddyClearPercent( mindStateLog: Array<{ time: number; state: string }>, totalSeconds: number, ): number { - if (mindStateLog.length === 0) return 100; + if (mindStateLog.length === 0 || totalSeconds <= 0) return 100; let clearTime = 0;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/BuddySessionRoom.tsx` around lines 37 - 53, The calcBuddyClearPercent function can divide by zero when totalSeconds is 0; add a defensive guard at the top (e.g., if totalSeconds <= 0) to return 100 (matching the empty-log behavior) or otherwise avoid the division, then proceed as before using endTime for the denominator; update function calcBuddyClearPercent to check totalSeconds and early-return to prevent NaN from Math.round((clearTime / endTime) * 100).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@drizzle/sessions_buddy_session_id_incremental.sql`:
- Around line 6-7: The ALTER TABLE adding the foreign key constraint
sessions_buddy_session_id_buddy_sessions_id_fk on table "sessions" is not
idempotent and will fail on reruns; wrap the constraint creation so it first
checks for existence (e.g., query pg_constraint/pg_class to see if
sessions_buddy_session_id_buddy_sessions_id_fk already exists) or enclose the
ALTER TABLE in a DO $$ BEGIN ... EXCEPTION WHEN duplicate_object THEN NULL; END
$$ block; modify the statement that creates the FK constraint (the ALTER TABLE
"sessions" ... FOREIGN KEY ("buddy_session_id") REFERENCES
"public"."buddy_sessions"("id") ...) to use one of these guards so the migration
becomes rerunnable.
In `@src/app/api/thoughts/batch/route.ts`:
- Around line 56-86: The current flow deletes existing completion notes
(timeInSession === -1) outside a transaction and then inserts rows, risking data
loss if the insert fails and allowing multiple -1 rows in one batch; wrap the
delete + insert into a single transactional operation using the database
transaction API (db.transaction or equivalent) so the delete and insert are
atomic, and before inserting deduplicate normalized to ensure at most one item
with timeInSession === -1 (e.g., keep the last/first -1 entry) when building the
values for db.insert(thoughts).returning(...); ensure you reference and operate
on the same sessionId, auth.userId, and thoughts table inside that transaction.
---
Nitpick comments:
In `@src/app/api/buddy/sessions/`[id]/record-personal-session/route.ts:
- Around line 59-61: The sessionDateOk function currently only checks format;
update it to also verify the date is real by parsing and comparing components:
split the input in sessionDateOk into year/month/day, construct a Date (or use a
reliable date util), ensure the Date is valid (not NaN) and that the Date's
year, month, and day match the parsed values (so values like "2023-99-99" fail),
then return true only if both the regex format and the constructed Date match
the input.
In `@src/components/BuddySessionRoom.tsx`:
- Around line 37-53: The calcBuddyClearPercent function can divide by zero when
totalSeconds is 0; add a defensive guard at the top (e.g., if totalSeconds <= 0)
to return 100 (matching the empty-log behavior) or otherwise avoid the division,
then proceed as before using endTime for the denominator; update function
calcBuddyClearPercent to check totalSeconds and early-return to prevent NaN from
Math.round((clearTime / endTime) * 100).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: cb733939-8b3f-443a-bf56-eafced2099b0
📒 Files selected for processing (11)
drizzle/sessions_buddy_session_id_incremental.sqlsrc/app/api/buddy/sessions/[id]/record-personal-session/route.tssrc/app/api/sessions/[dayNumber]/route.tssrc/app/api/thoughts/batch/route.tssrc/app/app/page.tsxsrc/components/BuddySessionRoom.tsxsrc/db/schema.tssrc/lib/api.tssrc/lib/buddyPolicyCodes.tssrc/lib/buddySession.tssrc/lib/buddySessionControlsPolicy.ts
Drizzle Kit does not load Next.js env files; read .env.local and .env so POSTGRES_URL is set when running npx drizzle-kit push from the repo root. Made-with: Cursor
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
drizzle.config.ts (1)
32-33: Consider loadingNODE_ENV-specific env files for CLI parityLine 32–Line 33 only load
.env.localand.env. If Drizzle is run in test/CI contexts, supporting.env.<NODE_ENV>variants can reduce surprises.♻️ Suggested enhancement
+const nodeEnv = process.env.NODE_ENV; +if (nodeEnv) { + loadEnvFile(`.env.${nodeEnv}.local`); + loadEnvFile(`.env.${nodeEnv}`); +} loadEnvFile(".env.local"); loadEnvFile(".env");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@drizzle.config.ts` around lines 32 - 33, Add loading for NODE_ENV-specific env files so CLI runs mirror runtime environments: compute a safe NODE_ENV (e.g., fallback to "development" if process.env.NODE_ENV is undefined) and call loadEnvFile for the environment variants (e.g., `.env.${NODE_ENV}.local` and `.env.${NODE_ENV}`) in the appropriate precedence around the existing loadEnvFile(".env.local") and loadEnvFile(".env") calls so `.env.<NODE_ENV>` files are loaded when present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@drizzle.config.ts`:
- Around line 19-25: The code extracts env values into val from trimmed.slice(eq
+ 1) but leaves inline comments (e.g. "POSTGRES_URL=... # local") and also
mishandles quoted values with trailing comments; update the logic that populates
val so it strips inline comments outside of quotes and correctly handles quoted
values with trailing comments: after computing val (and before removing
surrounding quotes), if val starts with a quote character (' or "), locate the
matching closing unescaped quote (not just endsWith) and extract the substring
between them; otherwise for unquoted values, cut at the first unescaped '#' and
trim the remainder; ensure you continue to preserve existing unescaping/trim
behavior for val, referencing the variables trimmed, eq and val in your change.
---
Nitpick comments:
In `@drizzle.config.ts`:
- Around line 32-33: Add loading for NODE_ENV-specific env files so CLI runs
mirror runtime environments: compute a safe NODE_ENV (e.g., fallback to
"development" if process.env.NODE_ENV is undefined) and call loadEnvFile for the
environment variants (e.g., `.env.${NODE_ENV}.local` and `.env.${NODE_ENV}`) in
the appropriate precedence around the existing loadEnvFile(".env.local") and
loadEnvFile(".env") calls so `.env.<NODE_ENV>` files are loaded when present.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 7b92260d-3be7-4fc2-89b3-d7b1427073b2
📒 Files selected for processing (1)
drizzle.config.ts
- Make sessions_buddy_session_id incremental FK rerunnable (pg_constraint guard) - Parse .env values: strip trailing inline comments; quoted values via lastIndexOf - Run thoughts batch delete+insert in one transaction; at most one completion note Made-with: Cursor
Summary
Implements per-user persistence after a completed buddy session: each participant gets their own
sessionsrow linked to the sharedbuddy_sessionsid, optional in-sit thoughts persisted with that row, and the existing soloCompletionScreenfor a personal completion note.POST /api/thoughts/batchnow verifies session ownership and replaces prior completion notes (time_in_session = -1) to avoid duplicate journal entries.Schema
sessions.buddy_session_id→buddy_sessions.id,ON DELETE SET NULL(user_id, buddy_session_id)wherebuddy_session_id IS NOT NULL(idempotency)drizzle/sessions_buddy_session_id_incremental.sql; prefernpx drizzle-kit pushfor dev DBsDeferred / follow-ups
buddy_session_id) — not in this PRCloses #119
Test plan
npm run buildpassesrecord-personal-sessionreturnsalready: truewithout double day incrementMade with Cursor
Summary by CodeRabbit
New Features
Behavior & Validation
UX
Messages