Coding

Projekt-Zweck (TL;DR)

Diese Repo enthält eine NestJS-API, die als leichtgewichtiger Ingestion-/Control-Layer auf EC2 (hinter einem ALB) läuft.

promptBeginner5 min to valuemarkdown
0 views
Jan 22, 2026

Sign in to like and favorite skills

Prompt Playground

1 Variables

Fill Variables

Preview

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.
Share: