Skip to content

Commit 5f2ef7f

Browse files
Merge pull request #293 from guillermoscript/fix/column-queries-non-existent-columns
fix: correct non-existent column queries across student + admin pages
2 parents 4afdfaf + 5bcb325 commit 5f2ef7f

19 files changed

Lines changed: 1257 additions & 52 deletions

File tree

CLAUDE.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,10 @@ Pre-commit checklist: `npm run build` · tenant filter on every query · tested
217217
## Known Pitfalls
218218

219219
- **`product_courses` — never `.single()`**: a course can belong to multiple products.
220-
- **`lesson_completions` uses `user_id`**, not `student_id`.
220+
- **`lesson_completions` uses `user_id`**, not `student_id`. Has **no `tenant_id`** — never filter by it.
221+
- **`exercise_completions` has no `tenant_id`** — filter by `user_id` only.
222+
- **`exams` has no `passing_score` or `allow_retake`** — use 70 as default threshold, assume retakes allowed.
223+
- **`profiles` has no `email`** — get emails via `createAdminClient().auth.admin.getUserById()` if needed.
221224
- **`exam_submissions` order column** is `submission_date`, not `submitted_at`.
222225
- **Transaction status** is `'successful'`, not `'succeeded'`.
223226
- **Creating test users via SQL** won't fire `handle_new_user()` trigger — manually insert `profiles`, `user_roles`, and `auth.identities`. Use `NULL` for `phone` (unique constraint), `''` for nullable string columns.

app/[locale]/dashboard/admin/courses/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default async function AdminCoursesPage() {
3636
const authorIds = courses?.map((c) => c.author_id) || []
3737

3838
const [{ data: authors }, { data: enrollments }, { data: lessons }] = await Promise.all([
39-
supabase.from('profiles').select('id, full_name, email')
39+
supabase.from('profiles').select('id, full_name')
4040
.in('id', authorIds.length > 0 ? authorIds : ['none']),
4141
supabase.from('enrollments').select('course_id').eq('tenant_id', tenantId),
4242
supabase.from('lessons').select('course_id').eq('tenant_id', tenantId),

app/[locale]/dashboard/admin/subscriptions/page.tsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,7 @@ export default async function SubscriptionsPage({
7171
current_period_end,
7272
created,
7373
profiles!subscriptions_user_profile_fkey (
74-
full_name,
75-
email
74+
full_name
7675
),
7776
plans (
7877
plan_name,
@@ -92,14 +91,13 @@ export default async function SubscriptionsPage({
9291

9392
const { data: subscriptions, error } = await query
9493

95-
// Filter by search on client side (for user names/emails)
94+
// Filter by search on client side (by name or ID)
9695
const filteredSubscriptions = subscriptions?.filter((sub) => {
9796
if (!search) return true
9897
const profile = sub.profiles as any
9998
const searchLower = search.toLowerCase()
10099
return (
101100
profile?.full_name?.toLowerCase().includes(searchLower) ||
102-
profile?.email?.toLowerCase().includes(searchLower) ||
103101
sub.subscription_id.toString().includes(searchLower)
104102
)
105103
})
@@ -108,7 +106,7 @@ export default async function SubscriptionsPage({
108106
const activeCount =
109107
subscriptions?.filter((s) => s.subscription_status === 'active').length || 0
110108
const canceledCount =
111-
subscriptions?.filter((s) => s.subscription_status === 'cancelled').length || 0
109+
subscriptions?.filter((s) => s.subscription_status === 'canceled').length || 0
112110
const expiredCount =
113111
subscriptions?.filter((s) => s.subscription_status === 'expired').length || 0
114112

@@ -226,9 +224,9 @@ export default async function SubscriptionsPage({
226224
{t('filters.active')}
227225
</Button>
228226
</Link>
229-
<Link href="?status=cancelled">
227+
<Link href="?status=canceled">
230228
<Button
231-
variant={statusFilter === 'cancelled' ? 'default' : 'outline'}
229+
variant={statusFilter === 'canceled' ? 'default' : 'outline'}
232230
size="sm"
233231
>
234232
{t('filters.cancelled')}
@@ -259,7 +257,7 @@ export default async function SubscriptionsPage({
259257
const profile = subscription.profiles as any
260258
const plan = subscription.plans as any
261259
const isActive = subscription.subscription_status === 'active'
262-
const isCancelled = subscription.subscription_status === 'cancelled'
260+
const isCancelled = subscription.subscription_status === 'canceled'
263261
const isExpired = subscription.subscription_status === 'expired'
264262

265263
return (
@@ -300,9 +298,6 @@ export default async function SubscriptionsPage({
300298
</Badge>
301299
)}
302300
</div>
303-
<p className="mt-1 text-sm text-muted-foreground">
304-
{profile?.email}
305-
</p>
306301
<div className="mt-2 flex flex-wrap gap-x-4 gap-y-1 text-sm text-muted-foreground">
307302
<span className="flex items-center gap-1">
308303
<IconCrown className="h-4 w-4" />

app/[locale]/dashboard/admin/transactions/page.tsx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ export default async function AdminTransactionsPage({
4242
.from('transactions')
4343
.select('*')
4444
.eq('tenant_id', tenantId)
45-
.order('created_at', { ascending: false })
45+
.order('transaction_date', { ascending: false })
4646

4747
// Get user profiles for transactions — batch fetch
4848
const userIds = [...new Set((transactions || []).map((t) => t.user_id).filter(Boolean))]
4949
const { data: users } = userIds.length > 0
50-
? await supabase.from('profiles').select('id, full_name, email').in('id', userIds)
50+
? await supabase.from('profiles').select('id, full_name').in('id', userIds)
5151
: { data: [] }
5252

5353
const usersMap = new Map((users || []).map((u) => [u.id, u]))
@@ -175,12 +175,7 @@ export default async function AdminTransactionsPage({
175175
</code>
176176
</td>
177177
<td className="px-4 py-3">
178-
<div>
179-
<p className="font-medium">{user?.full_name || t('table.unknown')}</p>
180-
<p className="text-[11px] text-muted-foreground/70">
181-
{user?.email}
182-
</p>
183-
</div>
178+
<p className="font-medium">{user?.full_name || t('table.unknown')}</p>
184179
</td>
185180
<td className="px-4 py-3 font-semibold tabular-nums">
186181
{new Intl.NumberFormat(locale, { style: 'currency', currency: 'USD' }).format(transaction.amount)}
@@ -192,7 +187,9 @@ export default async function AdminTransactionsPage({
192187
? 'default'
193188
: transaction.status === 'pending'
194189
? 'secondary'
195-
: 'destructive'
190+
: transaction.status === 'archived' || transaction.status === 'refunded'
191+
? 'secondary'
192+
: 'destructive'
196193
}
197194
className={`text-[10px] ${transaction.status === 'successful' ? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-400' : ''}`}
198195
>
@@ -202,7 +199,7 @@ export default async function AdminTransactionsPage({
202199
{transaction.status === 'pending' && (
203200
<IconClock className="mr-1 h-3 w-3" />
204201
)}
205-
{transaction.status === 'failed' && (
202+
{(transaction.status === 'failed' || transaction.status === 'canceled') && (
206203
<IconX className="mr-1 h-3 w-3" />
207204
)}
208205
{tm(`recentActivity.status.${transaction.status}`)}
@@ -212,7 +209,7 @@ export default async function AdminTransactionsPage({
212209
{transaction.payment_method ? (t(`table.methods.${transaction.payment_method}`) || transaction.payment_method) : t('table.notAvailable')}
213210
</td>
214211
<td className="px-4 py-3 text-xs tabular-nums text-muted-foreground">
215-
{format(new Date(transaction.created_at), 'MMM d, yyyy HH:mm', { locale: dateLocale })}
212+
{format(new Date(transaction.transaction_date), 'MMM d, yyyy HH:mm', { locale: dateLocale })}
216213
</td>
217214
</tr>
218215
)

app/[locale]/dashboard/student/courses/[courseId]/exercises/[exerciseId]/page.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ const ArtifactExercise = dynamic(
4949
}
5050
)
5151

52+
const VideoExercise = dynamic(
53+
() => import('@/components/exercises/video-exercise'),
54+
{
55+
loading: () => (
56+
<div className="space-y-4 p-6">
57+
<Skeleton className="h-8 w-64" />
58+
<Skeleton className="h-64 w-full rounded-xl" />
59+
<Skeleton className="h-12 w-32 mx-auto" />
60+
</div>
61+
),
62+
}
63+
)
64+
5265
interface PageProps {
5366
params: Promise<{ courseId: string; exerciseId: string }>
5467
}
@@ -277,6 +290,16 @@ export default async function ExercisePage({ params }: PageProps) {
277290
dailyAttemptsUsed={dailyAttemptsUsed}
278291
maxDailyAttempts={maxDailyAttempts}
279292
/>
293+
) : exercise.exercise_type === 'video_evaluation' ? (
294+
<VideoExercise
295+
exercise={exercise}
296+
isExerciseCompleted={isExerciseCompleted}
297+
submissionHistory={submissionHistory}
298+
passingScore={passingScore}
299+
isExerciseCompletedSection={otherExercisesSection}
300+
dailyAttemptsUsed={dailyAttemptsUsed}
301+
maxDailyAttempts={maxDailyAttempts}
302+
/>
280303
) : (
281304
<EssayExercise
282305
exercise={exercise}

app/[locale]/dashboard/student/courses/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export default async function MyCoursesPage({ searchParams }: PageProps) {
6666
.in('course_id', courseIds).eq('tenant_id', tenantId),
6767
supabase.from('lessons').select('id, title, sequence, course_id')
6868
.in('course_id', courseIds).eq('tenant_id', tenantId),
69-
supabase.from('exams').select('exam_id, title, sequence, passing_score, allow_retake, course_id')
69+
supabase.from('exams').select('exam_id, title, sequence, course_id')
7070
.in('course_id', courseIds).eq('tenant_id', tenantId),
7171
supabase.from('lesson_completions').select('lesson_id, completed_at')
7272
.eq('user_id', userId),

app/[locale]/dashboard/student/profile/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ async function getProfileData(userId: string, tenantId: string) {
107107
.from('lesson_completions')
108108
.select('lesson_id')
109109
.eq('user_id', userId)
110-
.eq('tenant_id', tenantId)
111110
.in('lesson_id', allLessonIds)
112111
: { data: [] }
113112

app/[locale]/dashboard/student/progress/page.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ export default async function StudentProgressPage() {
5353
: Promise.resolve({ data: [] as { id: number; course_id: number; title: string }[] }),
5454
supabase.from('lesson_completions').select('lesson_id, completed_at').eq('user_id', userId),
5555
courseIds.length > 0
56-
? supabase.from('exams').select('exam_id, course_id, title, passing_score').in('course_id', courseIds)
57-
: Promise.resolve({ data: [] as { exam_id: number; course_id: number; title: string; passing_score: number }[] }),
56+
? supabase.from('exams').select('exam_id, course_id, title').in('course_id', courseIds)
57+
: Promise.resolve({ data: [] as { exam_id: number; course_id: number; title: string }[] }),
5858
supabase.from('exam_submissions').select('exam_id, score, submission_date')
5959
.eq('student_id', userId).eq('tenant_id', tenantId),
6060
])
@@ -95,7 +95,6 @@ export default async function StudentProgressPage() {
9595
examScores: courseExams.map((e) => ({
9696
title: e.title,
9797
score: examScoreMap.get(e.exam_id),
98-
passingScore: e.passing_score,
9998
})),
10099
}
101100
})
@@ -261,7 +260,7 @@ export default async function StudentProgressPage() {
261260
key={idx}
262261
variant={
263262
exam.score !== undefined
264-
? exam.score >= (exam.passingScore || 70)
263+
? exam.score >= 70
265264
? 'default'
266265
: 'destructive'
267266
: 'outline'

app/actions/admin/plans.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,6 @@ export async function updatePlan(
231231
.from('plan_courses')
232232
.delete()
233233
.eq('plan_id', planId)
234-
.eq('tenant_id', tenantId)
235234

236235
if (formData.courseIds && formData.courseIds.length > 0) {
237236
const courseLinks = formData.courseIds.map(courseId => ({
@@ -270,7 +269,6 @@ export async function updatePlan(
270269
.from('plan_courses')
271270
.delete()
272271
.eq('plan_id', planId)
273-
.eq('tenant_id', tenantId)
274272

275273
if (formData.courseIds && formData.courseIds.length > 0) {
276274
const courseLinks = formData.courseIds.map(courseId => ({

app/api/chat/aristotle/route.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,20 +70,18 @@ export async function POST(req: Request) {
7070
.eq('tenant_id', tenantId),
7171
supabase
7272
.from('exams')
73-
.select('exam_id, title, passing_score')
73+
.select('exam_id, title')
7474
.eq('course_id', numericCourseId)
7575
.eq('status', 'published')
7676
.eq('tenant_id', tenantId),
7777
supabase
7878
.from('lesson_completions')
7979
.select('lesson_id')
80-
.eq('user_id', user.id)
81-
.eq('tenant_id', tenantId),
80+
.eq('user_id', user.id),
8281
supabase
8382
.from('exercise_completions')
8483
.select('exercise_id, score')
85-
.eq('user_id', user.id)
86-
.eq('tenant_id', tenantId),
84+
.eq('user_id', user.id),
8785
supabase
8886
.from('exam_submissions')
8987
.select('exam_id, score')
@@ -169,7 +167,7 @@ export async function POST(req: Request) {
169167
return {
170168
exam_id: s.exam_id,
171169
score: s.score,
172-
passed: exam?.passing_score ? (s.score || 0) >= exam.passing_score : false,
170+
passed: (s.score || 0) >= 70,
173171
}
174172
})
175173

0 commit comments

Comments
 (0)