Loading...
Loading...
Critical guardrail for Supabase database operations ensuring multi-tenant isolation with publication_id filtering, proper use of supabaseAdmin, avoiding SELECT *, error handling patterns, and secure server-side database access. Use when writing database queries, working with supabase, accessing newsletter_campaigns, articles, rss_posts, or any tenant-scoped data.
npx skill4agent add venture-formations/aiprodaily supabase-database-opssupabaseAdmin.from()publication_id// ✅ CORRECT - publication_id filter present
const { data, error } = await supabaseAdmin
.from('newsletter_campaigns')
.select('id, status, date')
.eq('publication_id', newsletterId) // ✅ REQUIRED
.eq('id', campaignId)
.single()
// ❌ WRONG - Missing publication_id filter (DATA LEAKAGE!)
const { data, error } = await supabaseAdmin
.from('newsletter_campaigns')
.select('id, status, date')
.eq('id', campaignId) // ❌ Can access other tenants' data!
.single()newsletter_campaignsarticlessecondary_articlesrss_postspost_ratingsrss_feedsapp_settingsadvertisementscampaign_advertisementsarchived_articlesarchived_rss_postsnewsletters// ✅ CORRECT - Server-side API route or Server Action
import { supabaseAdmin } from '@/lib/supabase'
export async function POST(request: NextRequest) {
const { data } = await supabaseAdmin
.from('newsletter_campaigns')
.select('*')
.eq('publication_id', newsletterId)
return NextResponse.json({ data })
}
// ❌ WRONG - Never in client components
'use client'
import { supabaseAdmin } from '@/lib/supabase' // ❌ Security risk!
export default function ClientComponent() {
// This exposes service role key to browser
const { data } = await supabaseAdmin.from('...').select()
}app/api/**/*.ts'use server''use client''use client'// ✅ CORRECT - Specific fields
const { data } = await supabaseAdmin
.from('articles')
.select('id, headline, article_text, is_active')
.eq('publication_id', newsletterId)
.eq('campaign_id', campaignId)
// ❌ WRONG - Fetches all columns (performance impact)
const { data } = await supabaseAdmin
.from('articles')
.select('*')
.eq('publication_id', newsletterId)
.eq('campaign_id', campaignId)// ✅ CORRECT - Check for errors
const { data, error } = await supabaseAdmin
.from('newsletter_campaigns')
.select('id, status')
.eq('publication_id', newsletterId)
.eq('id', campaignId)
.single()
if (error) {
console.error('[DB] Query failed:', error.message)
throw new Error('Failed to fetch campaign')
}
if (!data) {
console.log('[DB] No campaign found')
return null
}
// Now safe to use data
return data
// ❌ WRONG - No error handling
const { data } = await supabaseAdmin
.from('newsletter_campaigns')
.select('id, status')
.eq('id', campaignId)
.single()
return data.status // ❌ Crashes if error or data is nullconst { data, error } = await supabaseAdmin
.from('table_name')
.select('field1, field2, field3')
.eq('publication_id', newsletterId) // ✅ ALWAYS for tenant tables
.eq('other_field', value)
.single() // or .maybeSingle() if record might not exist
if (error) {
console.error('[DB] Query error:', error.message)
throw new Error(`Database query failed: ${error.message}`)
}
if (!data) {
console.log('[DB] No record found')
return null
}
return dataconst { data, error } = await supabaseAdmin
.from('articles')
.insert({
publication_id: newsletterId, // ✅ REQUIRED
campaign_id: campaignId,
headline: 'Article headline',
article_text: 'Content here',
is_active: false
})
.select()
.single()
if (error) {
console.error('[DB] Insert failed:', error.message)
throw new Error('Failed to create article')
}
return dataconst { data, error } = await supabaseAdmin
.from('articles')
.update({
is_active: true,
updated_at: new Date().toISOString()
})
.eq('id', articleId)
.eq('publication_id', newsletterId) // ✅ REQUIRED - prevents updating other tenants
.select()
.single()
if (error) {
console.error('[DB] Update failed:', error.message)
throw new Error('Failed to update article')
}
return dataconst { error } = await supabaseAdmin
.from('rss_posts')
.delete()
.eq('id', postId)
.eq('publication_id', newsletterId) // ✅ REQUIRED - prevents deleting other tenants' data
if (error) {
console.error('[DB] Delete failed:', error.message)
throw new Error('Failed to delete post')
}const { data, error } = await supabaseAdmin
.from('newsletter_campaigns')
.select(`
id,
status,
date,
articles (
id,
headline,
is_active
),
secondary_articles (
id,
headline,
is_active
)
`)
.eq('publication_id', newsletterId) // ✅ REQUIRED on parent table
.eq('id', campaignId)
.single()// This query can access ANY campaign from ANY tenant!
const { data } = await supabaseAdmin
.from('newsletter_campaigns')
.select('*')
.eq('id', campaignId) // ❌ Missing publication_id'use client'
// ❌ Exposes service role key to browser
export default function MyComponent() {
const { data } = await supabaseAdmin.from('...').select()
}// ❌ No error check - will crash on failure
const { data } = await supabaseAdmin.from('...').select().single()
const status = data.status // Crashes if data is null// ❌ Fetches unnecessary data, impacts performance
const { data } = await supabaseAdmin
.from('articles')
.select('*')publication_idsupabaseAdmin.single().maybeSingle()[DB]publication_idsupabaseAdminSELECT *publication_idsupabaseAdmin