Skip to content

Commit 3fdab9f

Browse files
perf(auth): migrate 80 server components/actions to use getCurrentUserId() header
Replaces supabase.auth.getUser() (network call to Supabase Auth API) with getCurrentUserId() (reads x-user-id header set by middleware) across all server components and server actions. Only 16 files that need the full User object (email, metadata) retain the original getUser() call. This eliminates ~80 redundant auth API calls per user session, fixing the "Request rate limit reached" error from Supabase Auth. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b340200 commit 3fdab9f

76 files changed

Lines changed: 454 additions & 605 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/[locale]/(public)/checkout/actions.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { createClient as createServerClient } from '@/lib/supabase/server';
44
import { revalidatePath } from 'next/cache';
5+
import { getCurrentUserId } from '@/lib/supabase/tenant'
56

67
/**
78
* Mock enrollment for testing checkout flow
@@ -15,9 +16,8 @@ import { revalidatePath } from 'next/cache';
1516
export async function enrollUser(courseId?: string, planId?: string, paymentMethod: string = 'mock_test') {
1617
// 1. Authenticate user
1718
const supabase = await createServerClient();
18-
const { data: { user } } = await supabase.auth.getUser();
19-
20-
if (!user) {
19+
const userId = await getCurrentUserId()
20+
if (!userId) {
2121
throw new Error("You must be logged in to enroll.");
2222
}
2323

@@ -40,7 +40,7 @@ export async function enrollUser(courseId?: string, planId?: string, paymentMeth
4040
const { data: transaction, error: txError } = await supabase
4141
.from('transactions')
4242
.insert({
43-
user_id: user.id,
43+
user_id: userId,
4444
product_id: product.product_id,
4545
amount: product.price,
4646
currency: product.currency,
@@ -58,7 +58,7 @@ export async function enrollUser(courseId?: string, planId?: string, paymentMeth
5858
// Call enroll_user RPC function
5959
// This function checks for transaction and enrolls in linked courses
6060
const { error: enrollError } = await supabase.rpc('enroll_user', {
61-
_user_id: user.id,
61+
_user_id: userId,
6262
_product_id: product.product_id
6363
});
6464

@@ -86,7 +86,7 @@ export async function enrollUser(courseId?: string, planId?: string, paymentMeth
8686
const { data: transaction, error: txError } = await supabase
8787
.from('transactions')
8888
.insert({
89-
user_id: user.id,
89+
user_id: userId,
9090
plan_id: plan.plan_id,
9191
amount: plan.price,
9292
currency: plan.currency,
@@ -103,7 +103,7 @@ export async function enrollUser(courseId?: string, planId?: string, paymentMeth
103103

104104
// Call handle_new_subscription RPC function
105105
const { error: subError } = await supabase.rpc('handle_new_subscription', {
106-
_user_id: user.id,
106+
_user_id: userId,
107107
_plan_id: plan.plan_id,
108108
_transaction_id: transaction.transaction_id
109109
});
@@ -130,9 +130,8 @@ export async function enrollUser(courseId?: string, planId?: string, paymentMeth
130130
*/
131131
export async function enrollFree(courseId?: string, planId?: string) {
132132
const supabase = await createServerClient();
133-
const { data: { user } } = await supabase.auth.getUser();
134-
135-
if (!user) {
133+
const userId = await getCurrentUserId()
134+
if (!userId) {
136135
throw new Error("You must be logged in to enroll.");
137136
}
138137

@@ -188,7 +187,7 @@ export async function enrollFree(courseId?: string, planId?: string) {
188187
const { error } = await supabase
189188
.from('enrollments')
190189
.insert({
191-
user_id: user.id,
190+
user_id: userId,
192191
course_id: parseInt(courseId),
193192
product_id: productId,
194193
status: 'active',

app/[locale]/(public)/courses/[id]/page.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
} from "@/components/ui/accordion";
2323
import { Card, CardContent } from "@/components/ui/card";
2424
import { getTranslations } from 'next-intl/server';
25+
import { getCurrentUserId } from '@/lib/supabase/tenant'
2526

2627
export const dynamic = 'force-dynamic';
2728

@@ -37,8 +38,7 @@ export default async function CourseDetailsPage(props: { params: Promise<{ id: s
3738
const t = await getTranslations('coursePublicDetails');
3839
const supabase = await createClient();
3940

40-
const { data: { user } } = await supabase.auth.getUser();
41-
41+
const userId = await getCurrentUserId()
4242
// Fetch course with lessons and category
4343
const { data: course, error } = await supabase
4444
.from("courses")
@@ -82,11 +82,11 @@ export default async function CourseDetailsPage(props: { params: Promise<{ id: s
8282

8383
// Check user's enrollment
8484
let hasAccess = false;
85-
if (user) {
85+
if (userId) {
8686
const { data: enrollment } = await supabase
8787
.from('enrollments')
8888
.select('enrollment_id')
89-
.eq('user_id', user.id)
89+
.eq('user_id', userId)
9090
.eq('course_id', parseInt(params.id))
9191
.eq('status', 'active')
9292
.maybeSingle();
@@ -342,7 +342,7 @@ export default async function CourseDetailsPage(props: { params: Promise<{ id: s
342342

343343
{/* CTA */}
344344
<div className="space-y-3">
345-
{!user ? (
345+
{!userId ? (
346346
<Link href={`/auth/login?next=${encodeURIComponent(`/courses/${params.id}`)}`}>
347347
<Button className="w-full h-11 bg-cyan-500 hover:bg-cyan-400 text-black font-bold text-sm shadow-lg shadow-cyan-500/20">
348348
{isFree ? t('pricing.enrollFree') : t('pricing.enrollNow')}

app/[locale]/(public)/products/[productId]/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createClient } from '@/lib/supabase/server'
22
import { redirect } from 'next/navigation'
33
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
44
import { ManualPaymentButton } from '@/components/student/manual-payment-button'
5+
import { getCurrentUserId } from '@/lib/supabase/tenant'
56

67
export default async function ProductDetailPage({
78
params
@@ -25,8 +26,7 @@ export default async function ProductDetailPage({
2526
}
2627

2728
// Check if user is authenticated
28-
const { data: { user } } = await supabase.auth.getUser()
29-
29+
const userId = await getCurrentUserId()
3030
return (
3131
<div className="container mx-auto py-12">
3232
<div className="max-w-2xl mx-auto">
@@ -58,7 +58,7 @@ export default async function ProductDetailPage({
5858
)}
5959

6060
<div className="pt-4">
61-
{user ? (
61+
{userId ? (
6262
<ManualPaymentButton
6363
productId={product.product_id}
6464
productName={product.name}

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { AdminBreadcrumb } from '@/components/admin/admin-breadcrumb'
1212
import { getTranslations } from 'next-intl/server'
1313
import { format } from 'date-fns'
1414
import { es, enUS } from 'date-fns/locale'
15-
import { getCurrentTenantId } from '@/lib/supabase/tenant'
15+
import {getCurrentTenantId, getCurrentUserId } from '@/lib/supabase/tenant'
1616

1717
interface SearchParams {
1818
period?: string
@@ -32,11 +32,8 @@ export default async function AnalyticsPage({
3232
const dateLocale = locale === 'es' ? es : enUS
3333
const supabase = await createClient()
3434

35-
const {
36-
data: { user },
37-
} = await supabase.auth.getUser()
38-
39-
if (!user) {
35+
const userId = await getCurrentUserId()
36+
if (!userId) {
4037
redirect('/auth/login')
4138
}
4239

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,16 @@ import Link from 'next/link'
77
import { IconArrowLeft, IconFolderOpen } from '@tabler/icons-react'
88
import { CategoriesTable } from '@/components/admin/categories-table'
99
import { getTranslations } from 'next-intl/server'
10-
import { getCurrentTenantId } from '@/lib/supabase/tenant'
10+
import {getCurrentTenantId, getCurrentUserId } from '@/lib/supabase/tenant'
1111
import { AdminBreadcrumb } from '@/components/admin/admin-breadcrumb'
1212

1313
export default async function AdminCategoriesPage() {
1414
const t = await getTranslations('dashboard.admin.categories')
1515
const tBreadcrumbs = await getTranslations('dashboard.admin.breadcrumbs')
1616
const supabase = await createClient()
1717

18-
const {
19-
data: { user },
20-
} = await supabase.auth.getUser()
21-
22-
if (!user) {
18+
const userId = await getCurrentUserId()
19+
if (!userId) {
2320
redirect('/auth/login')
2421
}
2522

app/[locale]/dashboard/admin/community/moderation/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createClient } from '@/lib/supabase/server'
2-
import { getCurrentTenantId } from '@/lib/supabase/tenant'
2+
import {getCurrentTenantId, getCurrentUserId } from '@/lib/supabase/tenant'
33
import { getUserRole } from '@/lib/supabase/get-user-role'
44
import { redirect } from 'next/navigation'
55
import { getTranslations } from 'next-intl/server'
@@ -22,8 +22,8 @@ export default async function CommunityModerationPage() {
2222
redirect('/dashboard/admin')
2323
}
2424

25-
const { data: { user } } = await supabase.auth.getUser()
26-
if (!user) redirect('/auth/login')
25+
const userId = await getCurrentUserId()
26+
if (!userId) redirect('/auth/login')
2727

2828
// Fetch flagged content and muted users in parallel
2929
const [{ data: flags }, { data: mutedUsers }] = await Promise.all([

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

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createClient } from '@/lib/supabase/server'
22
import { createAdminClient } from '@/lib/supabase/admin'
3-
import { getCurrentTenantId } from '@/lib/supabase/tenant'
3+
import {getCurrentTenantId, getCurrentUserId } from '@/lib/supabase/tenant'
44
import { getUserRole } from '@/lib/supabase/get-user-role'
55
import { redirect } from 'next/navigation'
66
import { getTranslations } from 'next-intl/server'
@@ -24,11 +24,8 @@ export default async function AdminCommunityPage() {
2424
redirect('/dashboard/admin')
2525
}
2626

27-
const {
28-
data: { user },
29-
} = await supabase.auth.getUser()
30-
31-
if (!user) {
27+
const userId = await getCurrentUserId()
28+
if (!userId) {
3229
redirect('/auth/login')
3330
}
3431

@@ -81,7 +78,7 @@ export default async function AdminCommunityPage() {
8178
adminClient
8279
.from('community_reactions')
8380
.select('post_id, reaction_type')
84-
.eq('user_id', user.id)
81+
.eq('user_id', userId)
8582
.eq('tenant_id', tenantId),
8683
adminClient
8784
.from('community_flags')
@@ -125,7 +122,7 @@ export default async function AdminCommunityPage() {
125122
adminClient
126123
.from('community_poll_votes')
127124
.select('post_id, option_id')
128-
.eq('user_id', user.id)
125+
.eq('user_id', userId)
129126
.in('post_id', pollPostIds),
130127
])
131128

@@ -152,7 +149,7 @@ export default async function AdminCommunityPage() {
152149

153150
return (
154151
<div className="min-h-screen bg-background">
155-
<CommunityTour userId={user.id} userRole="admin" />
152+
<CommunityTour userId={userId} userRole="admin" />
156153
<header className="border-b bg-card">
157154
<div className="mx-auto max-w-3xl px-4 py-5 sm:px-6 lg:px-8">
158155
<div className="mb-4">
@@ -188,7 +185,7 @@ export default async function AdminCommunityPage() {
188185
initialPosts={enrichedPosts}
189186
initialHasMore={enrichedPosts.length >= 20}
190187
userRole={role}
191-
userId={user.id}
188+
userId={userId}
192189
tenantId={tenantId}
193190
/>
194191
</main>

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,16 @@ import {
1010
} from '@tabler/icons-react'
1111
import { CoursesTable } from '@/components/admin/courses-table'
1212
import { getTranslations } from 'next-intl/server'
13-
import { getCurrentTenantId } from '@/lib/supabase/tenant'
13+
import {getCurrentTenantId, getCurrentUserId } from '@/lib/supabase/tenant'
1414
import { AdminBreadcrumb } from '@/components/admin/admin-breadcrumb'
1515

1616
export default async function AdminCoursesPage() {
1717
const t = await getTranslations('dashboard.admin.courses')
1818
const tBreadcrumbs = await getTranslations('dashboard.admin.breadcrumbs')
1919
const supabase = await createClient()
2020

21-
const {
22-
data: { user },
23-
} = await supabase.auth.getUser()
24-
25-
if (!user) {
21+
const userId = await getCurrentUserId()
22+
if (!userId) {
2623
redirect('/auth/login')
2724
}
2825

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

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createClient } from '@/lib/supabase/server'
22
import { createAdminClient } from '@/lib/supabase/admin'
3-
import { getCurrentTenantId } from '@/lib/supabase/tenant'
3+
import {getCurrentTenantId, getCurrentUserId } from '@/lib/supabase/tenant'
44
import { redirect } from 'next/navigation'
55
import { getTranslations } from 'next-intl/server'
66
import { format } from 'date-fns'
@@ -34,19 +34,16 @@ export default async function AdminDashboardPage({
3434
const dateLocale = locale === 'es' ? es : enUS
3535
const supabase = await createClient()
3636

37-
const {
38-
data: { user },
39-
} = await supabase.auth.getUser()
40-
41-
if (!user) {
37+
const userId = await getCurrentUserId()
38+
if (!userId) {
4239
redirect('/auth/login')
4340
}
4441

4542
// Check if onboarding is completed
4643
const { data: adminProfile } = await supabase
4744
.from('profiles')
4845
.select('onboarding_completed')
49-
.eq('id', user.id)
46+
.eq('id', userId)
5047
.single()
5148

5249
if (adminProfile && !adminProfile.onboarding_completed) {
@@ -190,12 +187,12 @@ export default async function AdminDashboardPage({
190187
/>
191188

192189
{/* Guided Tour (client component) */}
193-
<AdminDashboardTour userId={user.id} />
190+
<AdminDashboardTour userId={userId} />
194191

195192
{/* Getting Started Checklist — prominent for new users */}
196193
<div data-tour="admin-checklist">
197194
<OnboardingChecklist
198-
storageKey={`admin-${user.id}`}
195+
storageKey={`admin-${userId}`}
199196
title={t('onboarding.title')}
200197
subtitle={t('onboarding.subtitle')}
201198
steps={[

app/[locale]/dashboard/admin/payment-requests/[requestId]/page.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { createClient } from '@/lib/supabase/server'
22
import { redirect } from 'next/navigation'
33
import { getTranslations } from 'next-intl/server'
44
import { getUserRole, isSuperAdmin } from '@/lib/supabase/get-user-role'
5-
import { getCurrentTenantId } from '@/lib/supabase/tenant'
5+
import {getCurrentTenantId, getCurrentUserId } from '@/lib/supabase/tenant'
66
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
77
import { Button } from '@/components/ui/button'
88
import { Badge } from '@/components/ui/badge'
@@ -29,11 +29,8 @@ export default async function PaymentRequestDetailPage({ params }: PageProps) {
2929
const tenantId = await getCurrentTenantId()
3030
const superAdmin = await isSuperAdmin()
3131

32-
const {
33-
data: { user },
34-
} = await supabase.auth.getUser()
35-
36-
if (!user) {
32+
const userId = await getCurrentUserId()
33+
if (!userId) {
3734
redirect('/auth/login')
3835
}
3936

0 commit comments

Comments
 (0)