Skip to content

[codex] address launch issue batch#152

Merged
Taleef7 merged 3 commits intomainfrom
codex/address-open-issues
Apr 27, 2026
Merged

[codex] address launch issue batch#152
Taleef7 merged 3 commits intomainfrom
codex/address-open-issues

Conversation

@Taleef7
Copy link
Copy Markdown
Owner

@Taleef7 Taleef7 commented Apr 26, 2026

Summary

  • Adds an admin lead queue for Phase 0 intake follow-up, with WhatsApp handoff, status filters, notes, dashboard navigation, and analytics counts.
  • Fixes the missed-session analytics path by making status=no_show an intentional grouped filter for both student and tutor no-shows.
  • Requires tutor code-of-conduct acknowledgement during initial tutor signup, with extracted Zod validation and regression tests.
  • Sanitizes audit log details in app actions and the tutor session RPC so free-text notes, Meet links, contact fields, and similar PII are not stored in audit_logs.details.
  • Updates README and planning docs to reflect the lead queue, grouped no-show handling, audit sanitization, and proxy.ts route protection.

Closes #113.
Closes #114.
Closes #115.
Closes #144.

Notes

Verification

  • npm test -> 50 passed, 1 skipped
  • npm run typecheck -> passed
  • npm run lint -> exit 0 with 4 existing warnings in unrelated files
  • npx supabase db reset -> applied all migrations including 20260426000001_sanitize_session_audit_details.sql
  • npm run build:local -> production build passed and included /admin/leads

@Taleef7 Taleef7 marked this pull request as ready for review April 26, 2026 20:41
Copilot AI review requested due to automatic review settings April 26, 2026 20:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses multiple launch-blocker items by adding an in-app admin lead queue, fixing grouped “no-show” session filtering for analytics → sessions navigation, requiring tutor code-of-conduct acknowledgement at signup, and sanitizing PII/free-text from audit log details across server actions and RPCs.

Changes:

  • Added /admin/leads with status filtering, WhatsApp handoff, admin notes, and dashboard/analytics counts.
  • Introduced a grouped status=no_show filter (mapping to both no_show_student + no_show_tutor) and aligned sessions UI/filtering with analytics links.
  • Centralized audit-log details sanitization (plus an updated tutor RPC) and extracted tutor signup Zod validation + tests.

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
supabase/migrations/20260426000001_sanitize_session_audit_details.sql Replaces tutor_update_session audit logging to avoid storing tutor free-text notes in audit_logs.details.
lib/validators/tutor-sign-up.ts Extracted tutor signup Zod schema including required conduct acknowledgement.
lib/validators/lead-admin.ts Adds Zod schema for lead status/admin-notes updates.
lib/validators/__tests__/tutor-sign-up.test.ts Adds regression tests for conduct acknowledgement requirement.
lib/validators/__tests__/lead-admin.test.ts Adds validation tests for lead admin updates.
lib/utils/session-filter.ts Implements grouped no_show filter mapping and filter option list.
lib/utils/__tests__/session-filter.test.ts Tests grouped no-show filter behavior and options exposure.
lib/services/sessions.ts Sanitizes audit details for session-related audit log inserts.
lib/services/payments.ts Sanitizes audit details for package expiry audit logging.
lib/audit/sanitize.ts Introduces sanitizeAuditDetails() redaction helper for audit detail payloads.
lib/audit/__tests__/sanitize.test.ts Adds tests covering sensitive-key and sensitive-value redaction behavior.
docs/plan-CorvEd.md Updates implementation plan checklist to reflect lead queue, grouped no-shows, and audit privacy hygiene.
docs/GAP_ANALYSIS.md Updates route protection reference from middleware.ts to proxy.ts.
app/dashboard/requests/actions.ts Sanitizes audit details for student request cancellation audit log entries.
app/auth/sign-up/tutor/page.tsx Uses extracted schema and adds required code-of-conduct checkbox to tutor signup flow.
app/admin/tutors/actions.ts Sanitizes audit details for tutor approval/revocation actions.
app/admin/sessions/page.tsx Replaces legacy status-filter resolver with grouped-filter utility and updates label rendering/link propagation.
app/admin/sessions/SessionFilters.tsx Uses shared SESSION_STATUS_FILTER_OPTIONS for status filter control.
app/admin/requests/actions.ts Sanitizes audit details for multiple admin request/match actions and simplifies assignTutor return behavior.
app/admin/payments/actions.ts Sanitizes audit details for payment audit events.
app/admin/page.tsx Adds “Leads” card and new lead count to admin dashboard.
app/admin/leads/page.tsx New admin lead queue page with filters, WhatsApp link, and inline status/notes updates.
app/admin/leads/actions.ts New server action to update lead status/notes with validation and audit logging.
app/admin/layout.tsx Adds Leads link to admin navigation.
app/admin/analytics/page.tsx Adds “New Leads” metric card and query.
app/admin/actions.ts Switches user-profile update audit logging to use sanitization (but introduces an audit actor attribution bug).
README.md Updates docs to reflect proxy-based route protection, lead queue, conduct acknowledgement timing, and audit sanitization.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/admin/actions.ts
Comment on lines 125 to +132
await admin.from("audit_logs").insert([
buildAdminUpdateUserProfileAuditEntry({
actorUserId: adminUserId,
targetUserId: userId,
displayName: parsed.data.display_name,
}),
{
actor_user_id: userId,
action: "admin_update_user_profile",
entity_type: "user_profiles",
entity_id: userId,
details: sanitizeAuditDetails({ display_name: parsed.data.display_name }),
},
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

In updateUserProfile, the audit log entry sets actor_user_id to the target userId rather than the admin performing the update. This breaks audit attribution (and conflicts with the existing buildAdminUpdateUserProfileAuditEntry behavior/test in lib/admin/users.ts). Capture the admin id returned by requireAdmin() and use it as actor_user_id (keeping entity_id as the edited profile’s user id).

Copilot uses AI. Check for mistakes.
Comment thread lib/utils/session-filter.ts Outdated
}

export function isSessionStatusFilter(value: string): value is SessionStatusFilter {
return value in SESSION_STATUS_FILTERS
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

isSessionStatusFilter uses the in operator against a plain object. Because in checks the prototype chain, values like __proto__ can return true and lead to unexpected runtime behavior when indexing SESSION_STATUS_FILTERS. Use an own-property check (e.g., Object.hasOwn(...) / hasOwnProperty.call) or make SESSION_STATUS_FILTERS an Object.create(null) map.

Suggested change
return value in SESSION_STATUS_FILTERS
return Object.prototype.hasOwnProperty.call(SESSION_STATUS_FILTERS, value)

Copilot uses AI. Check for mistakes.
Comment on lines 204 to 206
<a
href={`/admin/sessions?student=${student.userId}&child=${encodeURIComponent(student.forStudentName ?? '')}`}
href={`/admin/sessions?student=${student.userId}&child=${encodeURIComponent(student.forStudentName ?? '')}${filterStatus ? `&view=past&status=${encodeURIComponent(filterStatus)}` : ''}`}
className="border-2 border-[#121212] px-3 py-1.5 text-xs font-bold uppercase tracking-widest text-[#121212] hover:bg-[#F0F0F0]"
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

The student-picker “View →” link forces view=past whenever a status filter is present. This makes filters like status=scheduled land on an empty past-sessions view, even when the student has upcoming scheduled sessions. Consider preserving the current view (or omitting view so the default applies) and only defaulting to past for filters that are inherently past-only (e.g. no-shows).

Copilot uses AI. Check for mistakes.
Comment thread app/admin/leads/actions.ts Outdated
Comment on lines +43 to +55
await admin.from('audit_logs').insert([
{
actor_user_id: adminUserId,
action: 'lead_status_updated',
entity_type: 'lead',
entity_id: leadId,
details: sanitizeAuditDetails({
previous_status: previousLead.status,
status,
admin_notes,
}),
},
])
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

updateLeadStatus inserts an audit log entry but does not check the returned error. Most other admin actions in this PR treat audit logging failures as actionable (they throw when auditError is present). Consider checking the insert result and surfacing failures consistently so audit gaps don’t go unnoticed.

Copilot uses AI. Check for mistakes.
@Taleef7
Copy link
Copy Markdown
Owner Author

Taleef7 commented Apr 27, 2026

@copilot apply changes based on the comments in this thread

Copilot AI and others added 2 commits April 27, 2026 00:39
- Use adminUserId (from requireAdmin) as actor_user_id in updateUserProfile audit log
- Use Object.prototype.hasOwnProperty.call in isSessionStatusFilter
- Only force view=past for past-only status filters in sessions View link
- Check and throw on audit log error in updateLeadStatus

Agent-Logs-Url: https://github.com/Taleef7/CorvEd/sessions/ae5bca7b-7f33-498a-93a9-7185d1f632ca

Co-authored-by: Taleef7 <89072337+Taleef7@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 27, 2026

@copilot apply changes based on the comments in this thread

Applied all four review fixes in commits 974409d and 8835286:

  1. updateUserProfile audit attributionrequireAdmin() return value is now captured as adminUserId and used as actor_user_id in the audit log (keeping entity_id as the target user's id).
  2. isSessionStatusFilter prototype safety — replaced value in SESSION_STATUS_FILTERS with Object.prototype.hasOwnProperty.call(SESSION_STATUS_FILTERS, value).
  3. Sessions "View →" linkview=past is now only appended for genuinely past-only statuses (done, rescheduled, no_show, no_show_student, no_show_tutor), extracted into a PAST_ONLY_SESSION_STATUS_FILTERS constant in session-filter.ts. Filters like scheduled will now use the default/upcoming view.
  4. updateLeadStatus audit error handling — the audit_logs insert result is now checked; a missing audit entry throws rather than silently succeeding.

@Taleef7 Taleef7 merged commit 32f75c2 into main Apr 27, 2026
1 check passed
@Taleef7 Taleef7 deleted the codex/address-open-issues branch April 27, 2026 01:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants