Nano Banana Pro
Agent skill for nano-banana-pro
Diese Repo enthält eine NestJS-API, die als leichtgewichtiger Ingestion-/Control-Layer auf EC2 (hinter einem ALB) läuft.
Sign in to like and favorite skills
Projekt-Zweck ([ACCOUNT_ID>]L;[ACCOUNT_ID>]R)
[ACCOUNT_ID>]iese Repo enthält eine [ACCOUNT_ID>]estJS-[ACCOUNT_ID>]P[ACCOUNT_ID>], die als leichtgewichtiger [ACCOUNT_ID>]ngestion-/[ACCOUNT_ID>]ontrol-Layer auf E[ACCOUNT_ID>]2 (hinter einem [ACCOUNT_ID>]LB) läuft.
[ACCOUNT_ID>]ie [ACCOUNT_ID>]P[ACCOUNT_ID>] nimmt P[ACCOUNT_ID>]F-Jobs an und delegiert die Verarbeitung asynchron an einen Worker (E[ACCOUNT_ID>]S Fargate).
[ACCOUNT_ID>]ateien werden direkt vom [ACCOUNT_ID>]lient in S3 hochgeladen (presigned [ACCOUNT_ID>]RLs), die [ACCOUNT_ID>]P[ACCOUNT_ID>] verwaltet Jobs/Status über SQS + [ACCOUNT_ID>]ynamo[ACCOUNT_ID>]B.
Wichtig: Hier wird nur die [ACCOUNT_ID>]P[ACCOUNT_ID>] gebaut. [ACCOUNT_ID>]er Worker-[ACCOUNT_ID>]ode läuft in einem separaten Service/Repo.
⸻
[ACCOUNT_ID>]rchitektur (High Level)
[ACCOUNT_ID>]lient → P[ACCOUNT_ID>]S[ACCOUNT_ID>] /upload-url → presigned P[ACCOUNT_ID>][ACCOUNT_ID>] → S3 (inbox/uploads/…)
[ACCOUNT_ID>]lient → P[ACCOUNT_ID>]S[ACCOUNT_ID>] /extract { job[ACCOUNT_ID>]d, s3Key } → [ACCOUNT_ID>]P[ACCOUNT_ID>] → [ACCOUNT_ID>]ynamo[ACCOUNT_ID>]B(status=queued) + SQS(Message)
Worker (E[ACCOUNT_ID>]S) liest SQS, holt P[ACCOUNT_ID>]F aus S3 inbox, extrahiert [ACCOUNT_ID>]ext, schreibt Ergebnis nach S3 results, setzt [ACCOUNT_ID>][ACCOUNT_ID>]B status=done
[ACCOUNT_ID>]lient → GE[ACCOUNT_ID>] /status/:job[ACCOUNT_ID>]d (optional GE[ACCOUNT_ID>] /download/:job[ACCOUNT_ID>]d → presigned GE[ACCOUNT_ID>])
⸻
[ACCOUNT_ID>]nforderungen an die [ACCOUNT_ID>]P[ACCOUNT_ID>] (diese Repo)
Endpunkte (müssen existieren)
• P[ACCOUNT_ID>]S[ACCOUNT_ID>] /upload-url → Body { filename: string }
→ Response { job[ACCOUNT_ID>]d, key, url } (presigned P[ACCOUNT_ID>][ACCOUNT_ID>] für S3 inbox/uploads/)
• P[ACCOUNT_ID>]S[ACCOUNT_ID>] /extract → Body { job[ACCOUNT_ID>]d: string, s3Key: string, ocr?: boolean }
→ schreibt [ACCOUNT_ID>][ACCOUNT_ID>]B {status:'queued'} und sendet SQS-Message {job[ACCOUNT_ID>]d,s3Key,options}
→ Response { job[ACCOUNT_ID>]d } (202 [ACCOUNT_ID>]ccepted semantisch, faktisch 200 ist ok)
• GE[ACCOUNT_ID>] /status/:job[ACCOUNT_ID>]d → Response { job[ACCOUNT_ID>]d, status, resultKey?, error? }
• GE[ACCOUNT_ID>] /download/:job[ACCOUNT_ID>]d → Response { job[ACCOUNT_ID>]d, url } (presigned GE[ACCOUNT_ID>] für results/)
• GE[ACCOUNT_ID>] /health → { ok: true } ([ACCOUNT_ID>]LB Health [ACCOUNT_ID>]heck)
[ACCOUNT_ID>]echnische Leitplanken
• [ACCOUNT_ID>]ode 18, [ACCOUNT_ID>]estJS, [ACCOUNT_ID>]WS S[ACCOUNT_ID>]K v3 (@aws-sdk/\*)
• Global ValidationPipe (whitelist: true, transform: true)
• Server bindet an 0.0.0.0 ([ACCOUNT_ID>]LB)
• [ACCOUNT_ID>]dempotenz über job[ACCOUNT_ID>]d ([ACCOUNT_ID>]lient liefert/erhält die gleiche [ACCOUNT_ID>][ACCOUNT_ID>])
• Keine Fremd-H[ACCOUNT_ID>][ACCOUNT_ID>]P-[ACCOUNT_ID>]ownloads in der [ACCOUNT_ID>]P[ACCOUNT_ID>] (keine SSRF). Es werden nur S3-Keys akzeptiert.
• S3 Prefix-Policy: [ACCOUNT_ID>]ur Pfade unter uploads/ (inbox) und results/ (results) verwenden.
Environment Variablen (müssen gelesen werden)
• [ACCOUNT_ID>]WS[ACCOUNT_ID>]REG[ACCOUNT_ID>][ACCOUNT_ID>][ACCOUNT_ID>] (z. B. eu-north-1)
• [ACCOUNT_ID>][ACCOUNT_ID>]B[ACCOUNT_ID>]X[ACCOUNT_ID>]B[ACCOUNT_ID>][ACCOUNT_ID>]KE[ACCOUNT_ID>] (z. B. leitnerai-inbox-7634-8705-3303)
• RES[ACCOUNT_ID>]L[ACCOUNT_ID>]S[ACCOUNT_ID>]B[ACCOUNT_ID>][ACCOUNT_ID>]KE[ACCOUNT_ID>] (z. B. leitnerai-results-7634-8705-3303)
• Q[ACCOUNT_ID>]E[ACCOUNT_ID>]E[ACCOUNT_ID>][ACCOUNT_ID>]RL (z. B. https://sqs.eu-north-1.amazonaws.com/763487053303/leitnerai-jobs)
• [ACCOUNT_ID>][ACCOUNT_ID>]BLE[ACCOUNT_ID>][ACCOUNT_ID>][ACCOUNT_ID>]ME (z. B. leitnerai-jobs)
[ACCOUNT_ID>]iese werden auf E[ACCOUNT_ID>]2 via user[ACCOUNT_ID>]data.sh in /etc/environment gesetzt.
⸻
[ACCOUNT_ID>]mplementierungsdetails (erwartete Struktur)
[ACCOUNT_ID>][ACCOUNT_ID>][ACCOUNT_ID>]s
• src/dto/upload-url.dto.ts
export class [ACCOUNT_ID>]pload[ACCOUNT_ID>]rl[ACCOUNT_ID>]to { filename: string }
• src/dto/extract.dto.ts
export class Extract[ACCOUNT_ID>]to { job[ACCOUNT_ID>]d: string; s3Key: string; ocr?: boolean; language?: string }
Service-Funktionen ([ACCOUNT_ID>]ppService)
• create[ACCOUNT_ID>]pload[ACCOUNT_ID>]rl(filename) → presigned P[ACCOUNT_ID>][ACCOUNT_ID>] (uploads/<job[ACCOUNT_ID>]d[ACCOUNT_ID>]\[ACCOUNT_ID>]<safe[ACCOUNT_ID>]ame[ACCOUNT_ID>].pdf)
• enqueueJob(job[ACCOUNT_ID>]d, s3Key, ocr?, language?)
• Head[ACCOUNT_ID>]bject auf [ACCOUNT_ID>][ACCOUNT_ID>]B[ACCOUNT_ID>]X[ACCOUNT_ID>]B[ACCOUNT_ID>][ACCOUNT_ID>]KE[ACCOUNT_ID>]/s3Key (existiert?)
• [ACCOUNT_ID>][ACCOUNT_ID>]B Put[ACCOUNT_ID>]tem: { job[ACCOUNT_ID>]d, status:'queued', s3Key, created[ACCOUNT_ID>]t }
• SQS SendMessage: { job[ACCOUNT_ID>]d, s3Key, bucket, resultsBucket, language, options:{ocr, language} }
• getStatus(job[ACCOUNT_ID>]d) → [ACCOUNT_ID>][ACCOUNT_ID>]B Get[ACCOUNT_ID>]tem → { status, resultKey?, error? }
• getResult[ACCOUNT_ID>]ownload[ACCOUNT_ID>]rl(job[ACCOUNT_ID>]d) → presigned GE[ACCOUNT_ID>] für RES[ACCOUNT_ID>]L[ACCOUNT_ID>]S[ACCOUNT_ID>]B[ACCOUNT_ID>][ACCOUNT_ID>]KE[ACCOUNT_ID>]/resultKey
• get[ACCOUNT_ID>]ssetSigned[ACCOUNT_ID>]rl(key) → presigned GE[ACCOUNT_ID>] für RES[ACCOUNT_ID>]L[ACCOUNT_ID>]S[ACCOUNT_ID>]B[ACCOUNT_ID>][ACCOUNT_ID>]KE[ACCOUNT_ID>]/key (nur results/*)
• health() → { ok: true }
• Bestehende [ACCOUNT_ID>]emo-Route /host bleibt zu [ACCOUNT_ID>]ebugzwecken erhalten.
[ACCOUNT_ID>]ontroller ([ACCOUNT_ID>]pp[ACCOUNT_ID>]ontroller)
• [ACCOUNT_ID>]bige Routes exakt abbilden; 400 werfen bei fehlenden/invaliden Params.
• GE[ACCOUNT_ID>] /asset?key=results/... → signierten [ACCOUNT_ID>]ownload zurückgeben
⸻
[ACCOUNT_ID>][ACCOUNT_ID>]M (Erwartungen an [ACCOUNT_ID>]nstanzrolle – [ACCOUNT_ID>]nfo für [ACCOUNT_ID>]ev)
[ACCOUNT_ID>]ie E[ACCOUNT_ID>]2-[ACCOUNT_ID>]nstanzrolle der [ACCOUNT_ID>]P[ACCOUNT_ID>] braucht:
• sqs:SendMessage auf leitnerai-jobs
• dynamodb:Put[ACCOUNT_ID>]tem/Get[ACCOUNT_ID>]tem/[ACCOUNT_ID>]pdate[ACCOUNT_ID>]tem auf leitnerai-jobs ([ACCOUNT_ID>]able)
• s3:Head[ACCOUNT_ID>]bject (und optional Get[ACCOUNT_ID>]bject) auf **[ACCOUNT_ID>][ACCOUNT_ID>]B[ACCOUNT_ID>]X[ACCOUNT_ID>]B[ACCOUNT_ID>][ACCOUNT_ID>]KE[ACCOUNT_ID>]/\*`
• s3:Get[ACCOUNT_ID>]bject auf **RES[ACCOUNT_ID>]L[ACCOUNT_ID>]S[ACCOUNT_ID>]B[ACCOUNT_ID>][ACCOUNT_ID>]KE[ACCOUNT_ID>]/\*(für/download`)
[ACCOUNT_ID>]ie Worker-Rolle ist nicht [ACCOUNT_ID>]eil dieser Repo.
⸻
[ACCOUNT_ID>]eployment (E[ACCOUNT_ID>]2/[ACCOUNT_ID>]LB via Launch [ACCOUNT_ID>]emplate)
• user[ACCOUNT_ID>]data.sh erledigt [ACCOUNT_ID>]ode 18, npm ci && npm run build && npm prune --production, PM2 Setup.
• [ACCOUNT_ID>]ach Änderungen am L[ACCOUNT_ID>]: [ACCOUNT_ID>]SG → Launch [ACCOUNT_ID>]emplate Version = Latest setzen, dann [ACCOUNT_ID>]nstance Refresh (Warmup ~300s).
• [ACCOUNT_ID>]LB Health [ACCOUNT_ID>]heck zeigt auf H[ACCOUNT_ID>][ACCOUNT_ID>]P /health (200–399).
• Security Groups: E[ACCOUNT_ID>]2 3000/tcp nur von [ACCOUNT_ID>]LB-SG; [ACCOUNT_ID>]LB 80/tcp von 0.0.0.0/0.
⸻
Entwicklungs-[ACCOUNT_ID>]asks für die K[ACCOUNT_ID>] (konkret) 1. [ACCOUNT_ID>]P[ACCOUNT_ID>]-Endpunkte implementieren
• [ACCOUNT_ID>][ACCOUNT_ID>][ACCOUNT_ID>]s + Validation
• Service-Methoden mit [ACCOUNT_ID>]WS S[ACCOUNT_ID>]K v3
• [ACCOUNT_ID>]ontroller-Routen gemäß oben
• main.ts: ValidationPipe + listen(3000,'0.0.0.0') 2. Fehlerbehandlung
• Bei Head[ACCOUNT_ID>]bject 404 → BadRequestException("[ACCOUNT_ID>]bject not found")
• [ACCOUNT_ID>]nerwartete [ACCOUNT_ID>]WS-Fehler → 500 loggen (keine sensiblen [ACCOUNT_ID>]aten im Body)
• /download/:job[ACCOUNT_ID>]d nur bei status='done' + vorhandenem resultKey 3. Sicherheit
• s3Key muss mit uploads/ beginnen
• Keine Weiterleitung an externe [ACCOUNT_ID>]RLs
• Response keine internen Pfade/Stacktraces 4. [ACCOUNT_ID>]ests (leicht)
• [ACCOUNT_ID>]nit-[ACCOUNT_ID>]est für [ACCOUNT_ID>]ontroller-Service-Verkabelung (Mock-[ACCOUNT_ID>]lients)
• E2E-[ACCOUNT_ID>]est für /health und / (Hello World) 5. [ACCOUNT_ID>]ocs (RE[ACCOUNT_ID>][ACCOUNT_ID>]ME)
• Kurzer [ACCOUNT_ID>]bschnitt „[ACCOUNT_ID>]P[ACCOUNT_ID>] Routes & Flow“ ([ACCOUNT_ID>]opy der Ziele + c[ACCOUNT_ID>]RL-Beispiele)
⸻
[ACCOUNT_ID>]kzeptanzkriterien ([ACCOUNT_ID>]efinition of [ACCOUNT_ID>]one)
• P[ACCOUNT_ID>]S[ACCOUNT_ID>] /upload-url liefert {job[ACCOUNT_ID>]d, key, url} ([ACCOUNT_ID>]RL 15 Min gültig, [ACCOUNT_ID>]ontent-[ACCOUNT_ID>]ype application/pdf)
• P[ACCOUNT_ID>]S[ACCOUNT_ID>] /extract schreibt [ACCOUNT_ID>][ACCOUNT_ID>]B-[ACCOUNT_ID>]tem (queued) und sendet SQS-Message
• GE[ACCOUNT_ID>] /status/:job[ACCOUNT_ID>]d gibt queued|processing|done|failed zurück
• GE[ACCOUNT_ID>] /download/:job[ACCOUNT_ID>]d gibt presigned GE[ACCOUNT_ID>]-[ACCOUNT_ID>]RL nur bei done
• GE[ACCOUNT_ID>] /health → { ok: true }
• [ACCOUNT_ID>]P[ACCOUNT_ID>] lauscht auf 0.0.0.0:3000
• Keine dev[ACCOUNT_ID>]ependencies zur Laufzeit erforderlich (build + npm prune --production)
• [ACCOUNT_ID>]ode lintet & baut: npm run build ok
⸻
Lokale Entwicklung (Schnellstart)
npm ci
npm run start:dev
# E[ACCOUNT_ID>]V lokal per .env oder Shell setzen (für [ACCOUNT_ID>]WS-[ACCOUNT_ID>]ufrufe lokale creds nötig)
Für lokale [ACCOUNT_ID>]ests ohne [ACCOUNT_ID>]WS: Service-[ACCOUNT_ID>]lients mocken ([ACCOUNT_ID>]nMemory-Stubs).
⸻
Später (außerhalb dieses Repos)
• Worker-Service (E[ACCOUNT_ID>]S): SQS → S3(inbox) lesen → extrahieren → S3(results) schreiben → [ACCOUNT_ID>][ACCOUNT_ID>]B status='done' + resultKey
• [ACCOUNT_ID>]uto Scaling: E[ACCOUNT_ID>]S-Service nach Queue-Länge
• S3 Lifecycle: inbox/ nach 7–30 [ACCOUNT_ID>]agen löschen; results/ nach Bedarf
• Monitoring: [ACCOUNT_ID>]larme auf [ACCOUNT_ID>]LQ [ACCOUNT_ID>] 0, 5xx-Rate, E[ACCOUNT_ID>]S [ACCOUNT_ID>]askFailures
⸻
Kontaktpunkte im [ACCOUNT_ID>]ode (heute vorhanden)
• src/app.service.ts → wird erweitert um [ACCOUNT_ID>]WS-Methoden
• src/app.controller.ts → wird um neue Routen ergänzt
• src/main.ts → ValidationPipe + 0.0.0.0
• user[ACCOUNT_ID>]data.sh → belassen (Build-Flow ok)
⸻
Bitte beim [ACCOUNT_ID>]mplementieren Platzhalter (<[ACCOUNT_ID>][ACCOUNT_ID>][ACCOUNT_ID>][ACCOUNT_ID>][ACCOUNT_ID>][ACCOUNT_ID>][ACCOUNT_ID>][ACCOUNT_ID>][ACCOUNT_ID>][ACCOUNT_ID>][ACCOUNT_ID>], Bucket-[ACCOUNT_ID>]amen) nicht in den [ACCOUNT_ID>]ode hardcoden – alles über E[ACCOUNT_ID>]V beziehen.
Projekt-Zweck (TL;DR)
Diese Repo enthält eine NestJS-API, die als leichtgewichtiger Ingestion-/Control-Layer auf EC2 (hinter einem ALB) läuft. Die API nimmt PDF-Jobs an und delegiert die Verarbeitung asynchron an einen Worker (ECS Fargate). Dateien werden direkt vom Client in S3 hochgeladen (presigned URLs), die API verwaltet Jobs/Status über SQS + DynamoDB.
Wichtig: Hier wird nur die API gebaut. Der Worker-Code läuft in einem separaten Service/Repo.
⸻
Architektur (High Level)
Client → POST /upload-url → presigned PUT → S3 (inbox/uploads/…) Client → POST /extract { jobId, s3Key } → API → DynamoDB(status=queued) + SQS(Message) Worker (ECS) liest SQS, holt PDF aus S3 inbox, extrahiert Text, schreibt Ergebnis nach S3 results, setzt DDB status=done Client → GET /status/:jobId (optional GET /download/:jobId → presigned GET)
⸻
Anforderungen an die API (diese Repo)
Endpunkte (müssen existieren) • POST /upload-url → Body { filename: string } → Response { jobId, key, url } (presigned PUT für S3 inbox/uploads/) • POST /extract → Body { jobId: string, s3Key: string, ocr?: boolean } → schreibt DDB {status:'queued'} und sendet SQS-Message {jobId,s3Key,options} → Response { jobId } (202 Accepted semantisch, faktisch 200 ist ok) • GET /status/:jobId → Response { jobId, status, resultKey?, error? } • GET /download/:jobId → Response { jobId, url } (presigned GET für results/) • GET /health → { ok: true } (ALB Health Check)
Technische Leitplanken • Node 18, NestJS, AWS SDK v3 (@aws-sdk/*) • Global ValidationPipe (whitelist: true, transform: true) • Server bindet an 0.0.0.0 (ALB) • Idempotenz über jobId (Client liefert/erhält die gleiche ID) • Keine Fremd-HTTP-Downloads in der API (keine SSRF). Es werden nur S3-Keys akzeptiert. • S3 Prefix-Policy: Nur Pfade unter uploads/ (inbox) und results/ (results) verwenden.
Environment Variablen (müssen gelesen werden) • AWS_REGION (z. B. eu-north-1) • INBOX_BUCKET (z. B. leitnerai-inbox-7634-8705-3303) • RESULTS_BUCKET (z. B. leitnerai-results-7634-8705-3303) • QUEUE_URL (z. B. https://sqs.eu-north-1.amazonaws.com/763487053303/leitnerai-jobs) • TABLE_NAME (z. B. leitnerai-jobs)
Diese werden auf EC2 via user_data.sh in /etc/environment gesetzt.
⸻
Implementierungsdetails (erwartete Struktur)
DTOs • src/dto/upload-url.dto.ts
export class UploadUrlDto { filename: string }
• src/dto/extract.dto.ts
export class ExtractDto { jobId: string; s3Key: string; ocr?: boolean; language?: string }
Service-Funktionen (AppService)
• createUploadUrl(filename) → presigned PUT (uploads/
Controller (AppController) • Obige Routes exakt abbilden; 400 werfen bei fehlenden/invaliden Params. • GET /asset?key=results/... → signierten Download zurückgeben
⸻
IAM (Erwartungen an Instanzrolle – Info für Dev)
Die EC2-Instanzrolle der API braucht: • sqs:SendMessage auf leitnerai-jobs • dynamodb:PutItem/GetItem/UpdateItem auf leitnerai-jobs (Table) • s3:HeadObject (und optional GetObject) auf **INBOX_BUCKET/*
• s3:GetObject auf **RESULTS_BUCKET/\*(für/download)
Die Worker-Rolle ist nicht Teil dieser Repo.
⸻
Deployment (EC2/ALB via Launch Template) • user_data.sh erledigt Node 18, npm ci && npm run build && npm prune --production, PM2 Setup. • Nach Änderungen am LT: ASG → Launch Template Version = Latest setzen, dann Instance Refresh (Warmup ~300s). • ALB Health Check zeigt auf HTTP /health (200–399). • Security Groups: EC2 3000/tcp nur von ALB-SG; ALB 80/tcp von 0.0.0.0/0.
⸻
Entwicklungs-Tasks für die KI (konkret) 1. API-Endpunkte implementieren • DTOs + Validation • Service-Methoden mit AWS SDK v3 • Controller-Routen gemäß oben • main.ts: ValidationPipe + listen(3000,'0.0.0.0') 2. Fehlerbehandlung • Bei HeadObject 404 → BadRequestException("Object not found") • Unerwartete AWS-Fehler → 500 loggen (keine sensiblen Daten im Body) • /download/:jobId nur bei status='done' + vorhandenem resultKey 3. Sicherheit • s3Key muss mit uploads/ beginnen • Keine Weiterleitung an externe URLs • Response keine internen Pfade/Stacktraces 4. Tests (leicht) • Unit-Test für Controller-Service-Verkabelung (Mock-Clients) • E2E-Test für /health und / (Hello World) 5. Docs (README) • Kurzer Abschnitt „API Routes & Flow“ (Copy der Ziele + cURL-Beispiele)
⸻
Akzeptanzkriterien (Definition of Done) • POST /upload-url liefert {jobId, key, url} (URL 15 Min gültig, Content-Type application/pdf) • POST /extract schreibt DDB-Item (queued) und sendet SQS-Message • GET /status/:jobId gibt queued|processing|done|failed zurück • GET /download/:jobId gibt presigned GET-URL nur bei done • GET /health → { ok: true } • API lauscht auf 0.0.0.0:3000 • Keine devDependencies zur Laufzeit erforderlich (build + npm prune --production) • Code lintet & baut: npm run build ok
⸻
Lokale Entwicklung (Schnellstart)
npm ci npm run start:dev
Für lokale Tests ohne AWS: Service-Clients mocken (InMemory-Stubs).
⸻
Später (außerhalb dieses Repos) • Worker-Service (ECS): SQS → S3(inbox) lesen → extrahieren → S3(results) schreiben → DDB status='done' + resultKey • Auto Scaling: ECS-Service nach Queue-Länge • S3 Lifecycle: inbox/ nach 7–30 Tagen löschen; results/ nach Bedarf • Monitoring: Alarme auf DLQ > 0, 5xx-Rate, ECS TaskFailures
⸻
Kontaktpunkte im Code (heute vorhanden) • src/app.service.ts → wird erweitert um AWS-Methoden • src/app.controller.ts → wird um neue Routen ergänzt • src/main.ts → ValidationPipe + 0.0.0.0 • user_data.sh → belassen (Build-Flow ok)
⸻
Bitte beim Implementieren Platzhalter (<ACCOUNT_ID>, Bucket-Namen) nicht in den Code hardcoden – alles über ENV beziehen.