Skip to content

Commit 1d2bf28

Browse files
author
Gerome El-assaad
committed
enhanced api & settings pages
1 parent 57537c9 commit 1d2bf28

44 files changed

Lines changed: 5539 additions & 652 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/actions/publish.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
'use server'
2+
3+
import { Duration, ms } from '@/lib/duration'
4+
import { Sandbox } from '@e2b/code-interpreter'
5+
import { kv } from '@vercel/kv'
6+
import { customAlphabet } from 'nanoid'
7+
8+
const nanoid = customAlphabet('1234567890abcdef', 7)
9+
10+
export async function publish(
11+
url: string,
12+
sbxId: string,
13+
duration: Duration,
14+
teamID: string | undefined,
15+
accessToken: string | undefined,
16+
) {
17+
const parsedUrl = new URL(url)
18+
if (!parsedUrl.hostname.endsWith('.e2b.app')) {
19+
throw new Error('URL must be on *.e2b.app domain')
20+
}
21+
22+
const expiration = ms(duration)
23+
if (expiration > ms('24h')) {
24+
throw new Error('Expiration must be 24 hours or less')
25+
}
26+
27+
await Sandbox.setTimeout(sbxId, expiration, {
28+
...(teamID && accessToken
29+
? {
30+
headers: {
31+
'X-Supabase-Team': teamID,
32+
'X-Supabase-Token': accessToken,
33+
},
34+
}
35+
: {}),
36+
})
37+
38+
if (process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN) {
39+
const id = nanoid()
40+
await kv.set(`fragment:${id}`, url, { px: expiration })
41+
42+
return {
43+
url: process.env.NEXT_PUBLIC_SITE_URL
44+
? `https://${process.env.NEXT_PUBLIC_SITE_URL}/s/${id}`
45+
: `/s/${id}`,
46+
}
47+
}
48+
49+
return {
50+
url,
51+
}
52+
}

app/api/auth/github/revoke/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { NextRequest, NextResponse } from 'next/server'
22
import { createServerClient } from '@/lib/supabase-server'
33

4+
export const runtime = 'nodejs'
5+
export const dynamic = 'force-dynamic'
6+
47
export async function POST(request: NextRequest) {
58
try {
69
const { access_token } = await request.json()

app/api/auth/github/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { NextRequest, NextResponse } from 'next/server'
22
import { createServerClient } from '@/lib/supabase-server'
33

4+
export const runtime = 'nodejs'
5+
export const dynamic = 'force-dynamic'
6+
47
export async function GET(request: NextRequest) {
58
const searchParams = request.nextUrl.searchParams
69
const code = searchParams.get('code')

app/api/billing/update/route.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { createServerClient } from '@/lib/supabase-server'
2+
import { NextRequest, NextResponse } from 'next/server'
3+
import { z } from 'zod'
4+
5+
export const runtime = 'nodejs'
6+
export const dynamic = 'force-dynamic'
7+
8+
const billingUpdateSchema = z.object({
9+
companyName: z.string().min(1, 'Company name is required'),
10+
companyEmail: z.string().email('Valid email is required'),
11+
taxId: z.string().optional(),
12+
address: z.string().min(1, 'Address is required'),
13+
city: z.string().min(1, 'City is required'),
14+
state: z.string().min(1, 'State/Province is required'),
15+
postalCode: z.string().min(1, 'Postal code is required'),
16+
country: z.string().min(1, 'Country is required'),
17+
paymentMethod: z.enum(['card', 'bank_transfer', 'invoice']),
18+
billingCycle: z.enum(['monthly', 'annual']),
19+
})
20+
21+
export async function POST(request: NextRequest) {
22+
try {
23+
const supabase = createServerClient()
24+
25+
const { data: { user }, error: authError } = await supabase.auth.getUser()
26+
if (authError || !user) {
27+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
28+
}
29+
30+
const body = await request.json()
31+
const validatedData = billingUpdateSchema.parse(body)
32+
33+
// Get user's team
34+
const { data: userTeam, error: teamError } = await supabase
35+
.from('users_teams')
36+
.select('team_id, teams(*)')
37+
.eq('user_id', user.id)
38+
.single()
39+
40+
if (teamError || !userTeam) {
41+
return NextResponse.json({ error: 'Team not found' }, { status: 404 })
42+
}
43+
44+
// Update billing information in the database
45+
const { error: updateError } = await supabase
46+
.from('billing_info')
47+
.upsert({
48+
team_id: userTeam.team_id,
49+
company_name: validatedData.companyName,
50+
company_email: validatedData.companyEmail,
51+
tax_id: validatedData.taxId,
52+
address: validatedData.address,
53+
city: validatedData.city,
54+
state: validatedData.state,
55+
postal_code: validatedData.postalCode,
56+
country: validatedData.country,
57+
payment_method: validatedData.paymentMethod,
58+
billing_cycle: validatedData.billingCycle,
59+
updated_at: new Date().toISOString(),
60+
}, {
61+
onConflict: 'team_id'
62+
})
63+
64+
if (updateError) {
65+
console.error('Error updating billing info:', updateError)
66+
return NextResponse.json({ error: 'Failed to update billing information' }, { status: 500 })
67+
}
68+
69+
return NextResponse.json({ success: true, message: 'Billing information updated successfully' })
70+
} catch (error) {
71+
console.error('Billing update error:', error)
72+
73+
if (error instanceof z.ZodError) {
74+
return NextResponse.json({
75+
error: 'Validation error',
76+
details: error.errors
77+
}, { status: 400 })
78+
}
79+
80+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
81+
}
82+
}
83+
84+
export async function GET(request: NextRequest) {
85+
try {
86+
const supabase = createServerClient()
87+
88+
const { data: { user }, error: authError } = await supabase.auth.getUser()
89+
if (authError || !user) {
90+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
91+
}
92+
93+
// Get user's team
94+
const { data: userTeam, error: teamError } = await supabase
95+
.from('users_teams')
96+
.select('team_id')
97+
.eq('user_id', user.id)
98+
.single()
99+
100+
if (teamError || !userTeam) {
101+
return NextResponse.json({ error: 'Team not found' }, { status: 404 })
102+
}
103+
104+
// Get billing information
105+
const { data: billingInfo, error: billingError } = await supabase
106+
.from('billing_info')
107+
.select('*')
108+
.eq('team_id', userTeam.team_id)
109+
.single()
110+
111+
if (billingError && billingError.code !== 'PGRST116') {
112+
console.error('Error fetching billing info:', billingError)
113+
return NextResponse.json({ error: 'Failed to fetch billing information' }, { status: 500 })
114+
}
115+
116+
return NextResponse.json({
117+
billingInfo: billingInfo || null
118+
})
119+
} catch (error) {
120+
console.error('Billing fetch error:', error)
121+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
122+
}
123+
}

app/api/chat/analytics/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ChatPersistence } from '@/lib/chat-persistence'
33
import { authenticateUser } from '@/lib/auth-utils'
44

55
export const runtime = 'nodejs'
6+
export const dynamic = 'force-dynamic'
67

78
export async function GET(request: NextRequest) {
89
try {

app/api/chat/route.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { Templates } from '@/lib/templates'
1111
import { streamObject, LanguageModel, CoreMessage } from 'ai'
1212

1313
export const maxDuration = 300
14+
export const runtime = 'nodejs'
15+
export const dynamic = 'force-dynamic'
1416

1517
const rateLimitMaxRequests = process.env.RATE_LIMIT_MAX_REQUESTS
1618
? parseInt(process.env.RATE_LIMIT_MAX_REQUESTS)

app/api/chat/sessions/[sessionId]/messages/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ChatPersistence } from '@/lib/chat-persistence'
33
import { authenticateUser } from '@/lib/auth-utils'
44

55
export const runtime = 'nodejs'
6+
export const dynamic = 'force-dynamic'
67

78
interface RouteParams {
89
params: {

app/api/chat/sessions/[sessionId]/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ChatPersistence } from '@/lib/chat-persistence'
33
import { authenticateUser } from '@/lib/auth-utils'
44

55
export const runtime = 'nodejs'
6+
export const dynamic = 'force-dynamic'
67

78
interface RouteParams {
89
params: {

app/api/chat/sessions/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { NextRequest, NextResponse } from 'next/server'
2-
import { ChatPersistence, ChatSession } from '@/lib/chat-persistence'
2+
import { SupabaseChatPersistence as ChatPersistence } from '@/lib/supabase-chat-persistence'
33
import { authenticateUser } from '@/lib/auth-utils'
44

55
export const runtime = 'nodejs'
6+
export const dynamic = 'force-dynamic'
67

78
export async function GET(request: NextRequest) {
89
try {

app/api/code/execute/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { NextResponse } from 'next/server'
22
import { evaluateCode } from '@/app/api/chat/codeInterpreter'
33

4+
export const runtime = 'nodejs'
5+
export const dynamic = 'force-dynamic'
6+
47
export async function POST(req: Request) {
58
try {
69
const { sessionID, code } = await req.json()

0 commit comments

Comments
 (0)