Skip to content

Commit 0195dde

Browse files
committed
Add image_ref column and improve API handlers
1 parent b585825 commit 0195dde

5 files changed

Lines changed: 1602 additions & 32 deletions

File tree

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,6 @@
11
import { NextResponse } from 'next/server'
2-
import type { ProfileName } from '@sandchest/contract'
32
import { createClient } from '@/lib/simulate-sdk'
43

5-
function sdkErrorStatus(err: unknown): number {
6-
if (typeof err === 'object' && err !== null && 'status' in err) {
7-
const s = (err as { status: number }).status
8-
if (s >= 400 && s < 600) return s
9-
}
10-
return 500
11-
}
12-
13-
function sdkErrorMessage(err: unknown): string {
14-
if (err instanceof Error) return err.message
15-
return 'Unknown error'
16-
}
17-
184
export async function POST(request: Request) {
195
try {
206
const body = (await request.json()) as {
@@ -25,24 +11,49 @@ export async function POST(request: Request) {
2511
ttlSeconds?: number | undefined
2612
}
2713

28-
const client = createClient(body.apiKey, body.baseUrl)
29-
const sandbox = await client.create({
30-
image: body.image || undefined,
31-
profile: (body.profile as ProfileName) || undefined,
32-
ttlSeconds: body.ttlSeconds || undefined,
33-
waitReady: false,
14+
const baseUrl = body.baseUrl.replace(/\/$/, '')
15+
const apiBody: Record<string, unknown> = {
16+
profile: body.profile || 'small',
17+
ttl_seconds: body.ttlSeconds || 3600,
18+
}
19+
if (body.image) {
20+
apiBody.image = body.image
21+
}
22+
23+
const res = await fetch(`${baseUrl}/v1/sandboxes`, {
24+
method: 'POST',
25+
headers: {
26+
'Authorization': `Bearer ${body.apiKey}`,
27+
'Content-Type': 'application/json',
28+
'Accept': 'application/json',
29+
},
30+
body: JSON.stringify(apiBody),
3431
})
3532

33+
const text = await res.text()
34+
let data: Record<string, unknown>
35+
try {
36+
data = JSON.parse(text) as Record<string, unknown>
37+
} catch {
38+
return NextResponse.json(
39+
{ error: `API returned ${res.status}: ${text.slice(0, 500)}` },
40+
{ status: res.status || 500 },
41+
)
42+
}
43+
44+
if (!res.ok) {
45+
const msg = (data.message as string) || (data.error as string) || `API ${res.status}`
46+
return NextResponse.json({ error: msg }, { status: res.status })
47+
}
48+
3649
return NextResponse.json({
37-
id: sandbox.id,
38-
status: sandbox.status,
39-
replayUrl: sandbox.replayUrl,
50+
id: data.sandbox_id,
51+
status: data.status,
52+
replayUrl: data.replay_url,
4053
})
4154
} catch (err) {
42-
return NextResponse.json(
43-
{ error: sdkErrorMessage(err) },
44-
{ status: sdkErrorStatus(err) },
45-
)
55+
const message = err instanceof Error ? err.message : 'Unknown error'
56+
return NextResponse.json({ error: message }, { status: 500 })
4657
}
4758
}
4859

@@ -60,9 +71,7 @@ export async function DELETE(request: Request) {
6071

6172
return NextResponse.json({ ok: true })
6273
} catch (err) {
63-
return NextResponse.json(
64-
{ error: sdkErrorMessage(err) },
65-
{ status: sdkErrorStatus(err) },
66-
)
74+
const message = err instanceof Error ? err.message : 'Unknown error'
75+
return NextResponse.json({ error: message }, { status: 500 })
6776
}
6877
}

apps/api/src/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { HttpServer } from '@effect/platform'
1+
import { HttpMiddleware, HttpServer } from '@effect/platform'
22
import { NodeHttpServer, NodeRuntime } from '@effect/platform-node'
33
import { Duration, Effect, Layer } from 'effect'
44
import { createServer } from 'node:http'
55
import { createDatabase } from '@sandchest/db/client'
66
import { loadEnv } from './env.js'
77
import { ApiRouter } from './server.js'
8+
import { formatApiError } from './errors.js'
89
import { withAuth, withRequestId } from './middleware.js'
910
import { withConnectionDrain } from './middleware/connection-drain.js'
1011
import { withRateLimit } from './middleware/rate-limit.js'
@@ -37,8 +38,18 @@ const { PORT, DATABASE_URL, REDIS_URL, REDIS_FAMILY, DRAIN_TIMEOUT_MS, SANDCHEST
3738

3839
const db = createDatabase(DATABASE_URL)
3940

41+
/** Catches Effect defects (unexpected errors) and returns a proper JSON 500 response. */
42+
const withDefectHandler = HttpMiddleware.make((app) =>
43+
app.pipe(
44+
Effect.catchAllDefect(() =>
45+
Effect.succeed(formatApiError(new Error('internal defect'))),
46+
),
47+
),
48+
)
49+
4050
// Production pipeline: connection drain is outermost so it gates all requests
4151
const AppLive = ApiRouter.pipe(
52+
withDefectHandler,
4253
withConnectionDrain,
4354
withRateLimit,
4455
withAuth,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE `sandboxes` ADD `image_ref` varchar(1024) DEFAULT '' NOT NULL;

0 commit comments

Comments
 (0)