mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
added backlog
This commit is contained in:
parent
e6a71e4e0c
commit
776c88409c
18 changed files with 462 additions and 97 deletions
|
|
@ -2,7 +2,13 @@
|
|||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(backlog task *)",
|
||||
"Bash(backlog mcp *)"
|
||||
"Bash(backlog mcp *)",
|
||||
"Read(//home/vscode/.config/nvim/**)",
|
||||
"Read(//home/vscode/.config/nvim/lua/plugins/**)",
|
||||
"Bash(npx tsc *)"
|
||||
]
|
||||
}
|
||||
},
|
||||
"enabledMcpjsonServers": [
|
||||
"backlog"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
FROM library/python:3.12-bullseye
|
||||
FROM library/python:3.12-bookworm
|
||||
|
||||
ARG USER=vscode
|
||||
ARG USER_UID=1000
|
||||
|
|
@ -43,8 +43,19 @@ RUN npm install -g backlog.md
|
|||
# RUN apt-get install terraform
|
||||
# RUN terraform -install-autocomplete
|
||||
|
||||
# Install Neovim (latest) + LazyVim deps
|
||||
RUN curl -fsSL https://github.com/neovim/neovim/releases/latest/download/nvim-linux-x86_64.tar.gz \
|
||||
| tar -xz -C /opt \
|
||||
&& ln -s /opt/nvim-linux-x86_64/bin/nvim /usr/local/bin/nvim \
|
||||
&& apt update && apt install -y --no-install-recommends \
|
||||
ripgrep fd-find git make unzip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Claude
|
||||
USER ${USER}
|
||||
# Bootstrap LazyVim starter config
|
||||
RUN git clone https://github.com/LazyVim/starter /home/${USER}/.config/nvim \
|
||||
&& rm -rf /home/${USER}/.config/nvim/.git
|
||||
RUN curl -fsSL https://claude.ai/install.sh | bash \
|
||||
&& export PATH="/home/${USER}/.local/bin:${PATH}" \
|
||||
&& claude plugin marketplace add JuliusBrussee/caveman \
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@
|
|||
},
|
||||
"extensions": [
|
||||
"esbenp.prettier-vscode",
|
||||
"Anthropic.claude-code"
|
||||
"Anthropic.claude-code",
|
||||
"asvetliakov.vscode-neovim"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,12 @@ services:
|
|||
command: sleep infinity
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "6420:6420"
|
||||
volumes:
|
||||
- ..:/workspaces/assessment-model
|
||||
- ~/.gitconfig:/home/vscode/.gitconfig:ro
|
||||
environment:
|
||||
- SSH_AUTH_SOCK=${SSH_AUTH_SOCK:-}
|
||||
networks:
|
||||
- frontend-net
|
||||
|
||||
|
|
|
|||
16
CLAUDE.md
16
CLAUDE.md
|
|
@ -15,3 +15,19 @@
|
|||
- Tasks live as markdown under `backlog/tasks/`. Committed to git. Read them for context on outstanding manual work (env vars, IAM, infra) owed by humans.
|
||||
- To start the web UI during development: `backlog browser` (port 6420, forwarded by devcontainer).
|
||||
- Do NOT mirror Backlog.md tasks into Claude's internal todo system. Use one or the other — Backlog for durable cross-session work, internal todos for within-turn progress tracking.
|
||||
|
||||
## Development workflow (spec-driven)
|
||||
|
||||
Follow this loop for all feature work:
|
||||
|
||||
1. **Decompose** — split user request into small Backlog tasks with acceptance criteria. One task = one PR = one session.
|
||||
2. **Plan first** — before writing code, research codebase and write implementation plan inside the task. Stop and wait for user approval.
|
||||
3. **Implement** — only after plan approved. One task at a time.
|
||||
4. **Verify** — run tests/lint, confirm output matches acceptance criteria.
|
||||
|
||||
**Hard rules:**
|
||||
- Never start coding without an approved plan in the task.
|
||||
- Never work on multiple tasks in one session.
|
||||
- If task too big to finish in one session, split it first.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
---
|
||||
id: TASK-1
|
||||
title: Add BULK_ADDRESS2UPRN_COMBINER_QUEUE_NAME to staging and prod env
|
||||
status: To Do
|
||||
assignee:
|
||||
- Jun-te Kim
|
||||
created_date: '2026-04-18 19:01'
|
||||
labels:
|
||||
- env
|
||||
- infra
|
||||
dependencies: []
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Dev .env.local has it; non-dev envs still missing. Combine route returns 500 'Server misconfiguration' without it.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 Value set in staging env: bulk-address2uprn-combiner-queue-staging (or matching stage suffix)
|
||||
- [ ] #2 Value set in prod env: bulk-address2uprn-combiner-queue-prod
|
||||
- [ ] #3 Deploy redeployed; /api/portfolio/{pid}/bulk-uploads/{uid}/combine returns 200 not 500
|
||||
<!-- AC:END -->
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
id: TASK-10
|
||||
title: Redirect to confirm-matches when combined_output_s3_uri populated
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-04-20'
|
||||
updated_date: '2026-04-20'
|
||||
labels:
|
||||
- frontend
|
||||
- bulk-upload
|
||||
- ui
|
||||
dependencies:
|
||||
- TASK-8
|
||||
priority: medium
|
||||
ordinal: 5000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
`OnboardingProgress.tsx` currently fires client-side combine POST when task terminal. Once backend auto-chains (backend-task-5) OR frontend triggers via backend route (task-7), the polling should watch for `bulk_address_uploads.combined_output_s3_uri` to be set. When present, show "Review matches →" CTA (or auto-redirect to confirm-matches page, task-8).
|
||||
|
||||
May require a new GET endpoint that returns `{status, combined_output_s3_uri}` for polling. Or extend existing task summary with upload fields.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 Polling stops once combined_output_s3_uri populated
|
||||
- [ ] #2 UI surfaces a clear CTA to review matches
|
||||
- [ ] #3 No duplicate combiner fires across refreshes
|
||||
<!-- AC:END -->
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
---
|
||||
id: TASK-2
|
||||
title: 'Grant sqs:SendMessage IAM on combiner queue to assessment-model runtime'
|
||||
status: To Do
|
||||
assignee:
|
||||
- Jun-te Kim
|
||||
created_date: '2026-04-18 19:01'
|
||||
labels:
|
||||
- infra
|
||||
- iam
|
||||
dependencies: []
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Combine route sends to bulk-address2uprn-combiner-queue-<stage>. Runtime role needs sqs:SendMessage + sqs:GetQueueUrl on that queue ARN.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 IAM policy updated in terraform for staging + prod
|
||||
- [ ] #2 Verified via AWS console or 'aws sqs get-queue-url' using runtime creds
|
||||
<!-- AC:END -->
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
---
|
||||
id: TASK-3
|
||||
title: Deploy bulk_address2uprn_combiner Lambda + queue via terraform to staging/prod
|
||||
status: To Do
|
||||
assignee:
|
||||
- Jun-te Kim
|
||||
created_date: '2026-04-18 19:01'
|
||||
labels:
|
||||
- infra
|
||||
- terraform
|
||||
dependencies: []
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Lambda source at /workspaces/home/github/Model/backend/bulk_address2uprn_combiner/. Uses lambda_with_sqs module. Needs S3_BUCKET_NAME=retrofit_sap_data_bucket_name and DB creds envs. Confirm queue name convention bulk-address2uprn-combiner-queue-<stage>.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 Lambda + queue exist in staging
|
||||
- [ ] #2 Lambda + queue exist in prod
|
||||
- [ ] #3 Lambda has read on ara_raw_outputs/ and write on bulk_final_outputs/ in retrofit_sap_data bucket
|
||||
<!-- AC:END -->
|
||||
|
|
@ -1,27 +1,36 @@
|
|||
---
|
||||
id: TASK-4
|
||||
title: Smoke-test combiner end-to-end on dev
|
||||
title: Smoke-test full bulk upload flow end-to-end on dev
|
||||
status: To Do
|
||||
assignee:
|
||||
- Jun-te Kim
|
||||
created_date: '2026-04-18 19:02'
|
||||
updated_date: '2026-04-20'
|
||||
labels:
|
||||
- qa
|
||||
- bulk-upload
|
||||
dependencies: []
|
||||
dependencies:
|
||||
- TASK-6
|
||||
- TASK-7
|
||||
- TASK-8
|
||||
- TASK-9
|
||||
- TASK-10
|
||||
priority: medium
|
||||
ordinal: 9000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
After env var + IAM ready, run a real bulk upload -> map columns -> onboard -> wait for terminal complete. Confirm combiner fires.
|
||||
After frontend + backend refactor ships, run the full flow on dev: upload xlsx → map columns → start onboarding → poll task progress → combiner fires → combined_output_s3_uri populated → review matches page renders → confirm rows → addresses appear in portfolio.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 POST /combine returns 200 with {taskId, subTaskId}
|
||||
- [ ] #2 CloudWatch for bulk_address2uprn_combiner shows the subtask picked up
|
||||
- [ ] #1 Frontend no longer needs POSTCODE_SPLITTER_QUEUE_NAME or BULK_ADDRESS2UPRN_COMBINER_QUEUE_NAME env vars
|
||||
- [ ] #2 Backend logs show both splitter + combiner triggered via HTTP route
|
||||
- [ ] #3 bulk_final_outputs/{task_id}/combined_<timestamp>.csv exists in retrofit_sap_data bucket
|
||||
- [ ] #4 bulk_address_uploads.combined_output_s3_uri populated for the test upload
|
||||
- [ ] #4 bulk_address_uploads.combined_output_s3_uri populated
|
||||
- [ ] #5 Confirm-matches page renders with match rows
|
||||
- [ ] #6 After confirm submit, addresses persist into portfolio
|
||||
<!-- AC:END -->
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
---
|
||||
id: TASK-6
|
||||
title: Refactor onboard route to call backend trigger-splitter endpoint
|
||||
status: In Progress
|
||||
assignee: []
|
||||
created_date: '2026-04-20'
|
||||
updated_date: '2026-04-20 12:55'
|
||||
labels:
|
||||
- frontend
|
||||
- bulk-upload
|
||||
- refactor
|
||||
dependencies:
|
||||
- BACKEND-TASK-1
|
||||
priority: high
|
||||
ordinal: 1000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Currently `src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/onboard/route.ts` builds an SQS message and sends it to `POSTCODE_SPLITTER_QUEUE_NAME`. Move the SQS send to the backend. Frontend still transforms XLSX → CSV + uploads to S3, then calls backend HTTP `POST /v1/bulk-uploads/trigger-splitter`. Drop `POSTCODE_SPLITTER_QUEUE_NAME`, `sendToQueue`, and SQS IAM dependency.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 `onboard/route.ts` no longer imports `sendToQueue` or reads `POSTCODE_SPLITTER_QUEUE_NAME`
|
||||
- [x] #2 Transformed CSV still uploaded to `bulk_onboarding_inputs/{portfolioId}/{uploadId}.csv`
|
||||
- [x] #3 Backend trigger-splitter endpoint called with correct payload
|
||||
- [ ] #4 DB updates (bulk_address_uploads.status="processing", tasks.status, subTasks.inputs) still happen on success
|
||||
- [ ] #5 4xx/5xx from backend → return 502 to client with useful message
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
|
||||
### File changed
|
||||
`src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/onboard/route.ts`
|
||||
|
||||
### What stays the same
|
||||
- Auth check via `getServerSession`
|
||||
- Upload record + status validation
|
||||
- `transformFile()` XLSX → CSV
|
||||
- S3 upload of transformed CSV to `bulk_onboarding_inputs/{portfolioId}/{uploadId}.csv`
|
||||
- DB writes: `bulkAddressUploads.status = "processing"`, `tasks.status = "in progress"`, `subTasks.inputs`
|
||||
|
||||
### What changes
|
||||
|
||||
**Remove:**
|
||||
```ts
|
||||
import { sendToQueue } from "@/app/utils/sqs";
|
||||
// POSTCODE_SPLITTER_QUEUE_NAME env var check
|
||||
```
|
||||
|
||||
**Add — call backend trigger-splitter:**
|
||||
```ts
|
||||
const fastapiUrl = process.env.FASTAPI_API_URL;
|
||||
const fastapiKey = process.env.FASTAPI_API_KEY;
|
||||
if (!fastapiUrl || !fastapiKey) {
|
||||
return NextResponse.json({ error: "Server misconfiguration" }, { status: 500 });
|
||||
}
|
||||
|
||||
const sessionToken =
|
||||
request.cookies.get("__Secure-next-auth.session-token")?.value ??
|
||||
request.cookies.get("next-auth.session-token")?.value;
|
||||
|
||||
const triggerRes = await fetch(`${fastapiUrl}/v1/bulk-uploads/trigger-splitter`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": fastapiKey,
|
||||
Authorization: `Bearer ${sessionToken}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
task_id: body.taskId,
|
||||
sub_task_id: body.subTaskId,
|
||||
s3_uri: s3Uri,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!triggerRes.ok) {
|
||||
const errText = await triggerRes.text().catch(() => "");
|
||||
console.error("Backend trigger-splitter failed:", triggerRes.status, errText);
|
||||
return NextResponse.json({ error: "Failed to trigger address matching" }, { status: 502 });
|
||||
}
|
||||
```
|
||||
|
||||
**Order of ops** (no change to structure, just swap):
|
||||
1. Validate body + upload record
|
||||
2. Read source file from S3
|
||||
3. Transform XLSX → CSV
|
||||
4. Upload transformed CSV to S3
|
||||
5. ~~sendToQueue~~ → fetch backend trigger-splitter
|
||||
6. DB updates (status, taskId, subTask inputs) — only after step 5 succeeds
|
||||
|
||||
### Env vars required
|
||||
- `FASTAPI_API_URL` — already set, already used in `plan/trigger/route.ts`
|
||||
- `FASTAPI_API_KEY` — already set
|
||||
|
||||
### Env vars removed
|
||||
- `POSTCODE_SPLITTER_QUEUE_NAME` — no longer read by frontend (can remove from .env.local + staging/prod)
|
||||
|
||||
<!-- SECTION:NOTES:END -->
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
id: TASK-7
|
||||
title: Refactor combine route to call backend trigger-combiner endpoint
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-04-20'
|
||||
updated_date: '2026-04-20'
|
||||
labels:
|
||||
- frontend
|
||||
- bulk-upload
|
||||
- refactor
|
||||
dependencies:
|
||||
- BACKEND-TASK-2
|
||||
priority: high
|
||||
ordinal: 2000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
`src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/combine/route.ts` sends SQS directly to `BULK_ADDRESS2UPRN_COMBINER_QUEUE_NAME`. Replace with call to backend `POST /bulk-uploads/{task_id}/combine`. Drop queue name env var + SendMessage IAM dependency on frontend.
|
||||
|
||||
If backend auto-chains combiner on splitter completion (backend-task-5), this route may simply proxy a manual "re-combine" action or be removed entirely. Decide during implementation.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 `combine/route.ts` no longer imports `sendToQueue` or reads `BULK_ADDRESS2UPRN_COMBINER_QUEUE_NAME`
|
||||
- [ ] #2 Backend trigger-combiner endpoint called with task_id
|
||||
- [ ] #3 Frontend still updates subTasks row with inputs on success (or delegates to backend)
|
||||
- [ ] #4 Decision logged: proxy vs delete-after-auto-chain
|
||||
<!-- AC:END -->
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
id: TASK-8
|
||||
title: Add confirm-matches page for bulk upload address→UPRN review
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-04-20'
|
||||
updated_date: '2026-04-20'
|
||||
labels:
|
||||
- frontend
|
||||
- bulk-upload
|
||||
- ui
|
||||
dependencies:
|
||||
- TASK-9
|
||||
- BACKEND-TASK-3
|
||||
priority: high
|
||||
ordinal: 3000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
New route `/portfolio/{slug}/bulk-upload/{uploadId}/confirm-matches`. Loads combined CSV from backend (via frontend proxy route, see task-9) and renders review table: original address input | matched UPRN | matched address | confidence. User can accept/reject per row, then POST confirmed rows to backend to persist into portfolio `addresses`.
|
||||
|
||||
Status transitions: `complete` (combiner done) → show "Review matches" CTA on upload detail page → confirm-matches page → on submit, move upload to `confirmed` or similar.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 Page renders match rows in a scrollable table
|
||||
- [ ] #2 Row actions: accept (default on), reject
|
||||
- [ ] #3 Submit posts accepted rows to backend confirm-matches route
|
||||
- [ ] #4 After submit, redirect to portfolio addresses list
|
||||
- [ ] #5 No useEffect/useMemo (per CLAUDE.md) — use Server Components + Route Handlers where possible
|
||||
<!-- AC:END -->
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
id: TASK-9
|
||||
title: Add proxy API routes for combined-results and confirm-matches
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-04-20'
|
||||
updated_date: '2026-04-20'
|
||||
labels:
|
||||
- frontend
|
||||
- bulk-upload
|
||||
- api
|
||||
dependencies:
|
||||
- BACKEND-TASK-3
|
||||
- BACKEND-TASK-4
|
||||
priority: high
|
||||
ordinal: 4000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Two Next.js route handlers that proxy to backend:
|
||||
- `GET /api/portfolio/{portfolioId}/bulk-uploads/{uploadId}/combined-results` → backend GET `/bulk-uploads/{uploadId}/combined-results`. Returns parsed match rows for the confirm UI.
|
||||
- `POST /api/portfolio/{portfolioId}/bulk-uploads/{uploadId}/confirm-matches` → backend POST `/bulk-uploads/{uploadId}/confirm-matches`. Body: accepted rows.
|
||||
|
||||
Both must: check session, pass through portfolio scope, translate backend errors to sane frontend responses.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 Both routes auth-gated via getServerSession
|
||||
- [ ] #2 Params typed as Promise per Next.js 15 convention
|
||||
- [ ] #3 Backend 4xx/5xx surfaced with appropriate HTTP code + message
|
||||
- [ ] #4 Upload id is validated against bulk_address_uploads row before proxying
|
||||
<!-- AC:END -->
|
||||
1
run_backlog_browser.sh
Normal file
1
run_backlog_browser.sh
Normal file
|
|
@ -0,0 +1 @@
|
|||
backlog browser
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import { db } from "@/app/db/db";
|
||||
import { bulkAddressUploads } from "@/app/db/schema/bulk_address_uploads";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ portfolioId: string; uploadId: string }> }
|
||||
) {
|
||||
const session = await getServerSession(AuthOptions);
|
||||
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
|
||||
const { uploadId } = await params;
|
||||
|
||||
const [upload] = await db
|
||||
.select({ taskId: bulkAddressUploads.taskId })
|
||||
.from(bulkAddressUploads)
|
||||
.where(eq(bulkAddressUploads.id, uploadId))
|
||||
.limit(1);
|
||||
|
||||
if (!upload) return NextResponse.json({ error: "Not found" }, { status: 404 });
|
||||
if (!upload.taskId) return NextResponse.json({ error: "Task not started" }, { status: 409 });
|
||||
|
||||
const fastapiUrl = process.env.FASTAPI_API_URL;
|
||||
const fastapiKey = process.env.FASTAPI_API_KEY;
|
||||
if (!fastapiUrl || !fastapiKey) {
|
||||
console.error("FASTAPI_API_URL or FASTAPI_API_KEY not set");
|
||||
return NextResponse.json({ error: "Server misconfiguration" }, { status: 500 });
|
||||
}
|
||||
|
||||
const sessionToken =
|
||||
request.cookies.get("__Secure-next-auth.session-token")?.value ??
|
||||
request.cookies.get("next-auth.session-token")?.value;
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const offset = searchParams.get("offset") ?? "0";
|
||||
const limit = searchParams.get("limit") ?? "500";
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${fastapiUrl}/v1/bulk-uploads/${upload.taskId}/combined-results?offset=${offset}&limit=${limit}`,
|
||||
{
|
||||
headers: {
|
||||
"x-api-key": fastapiKey,
|
||||
Authorization: `Bearer ${sessionToken}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text().catch(() => "");
|
||||
console.error("Backend combined-results failed:", res.status, errText);
|
||||
return NextResponse.json(
|
||||
{ error: res.status === 409 ? "Combiner not finished" : "Failed to fetch results" },
|
||||
{ status: res.status === 409 ? 409 : 502 }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
return NextResponse.json(data, { status: 200 });
|
||||
} catch (err) {
|
||||
console.error("Failed to reach backend combined-results:", err);
|
||||
return NextResponse.json({ error: "Failed to fetch results" }, { status: 502 });
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
import { db } from "@/app/db/db";
|
||||
import { bulkAddressUploads } from "@/app/db/schema/bulk_address_uploads";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ portfolioId: string; uploadId: string }> }
|
||||
) {
|
||||
const session = await getServerSession(AuthOptions);
|
||||
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
|
||||
const { uploadId } = await params;
|
||||
|
||||
const [upload] = await db
|
||||
.select({ taskId: bulkAddressUploads.taskId })
|
||||
.from(bulkAddressUploads)
|
||||
.where(eq(bulkAddressUploads.id, uploadId))
|
||||
.limit(1);
|
||||
|
||||
if (!upload) return NextResponse.json({ error: "Not found" }, { status: 404 });
|
||||
if (!upload.taskId) return NextResponse.json({ error: "Task not started" }, { status: 409 });
|
||||
|
||||
const fastapiUrl = process.env.FASTAPI_API_URL;
|
||||
const fastapiKey = process.env.FASTAPI_API_KEY;
|
||||
if (!fastapiUrl || !fastapiKey) {
|
||||
console.error("FASTAPI_API_URL or FASTAPI_API_KEY not set");
|
||||
return NextResponse.json({ error: "Server misconfiguration" }, { status: 500 });
|
||||
}
|
||||
|
||||
const sessionToken =
|
||||
request.cookies.get("__Secure-next-auth.session-token")?.value ??
|
||||
request.cookies.get("next-auth.session-token")?.value;
|
||||
|
||||
let body;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${fastapiUrl}/v1/bulk-uploads/${upload.taskId}/confirm-matches`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": fastapiKey,
|
||||
Authorization: `Bearer ${sessionToken}`,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text().catch(() => "");
|
||||
console.error("Backend confirm-matches failed:", res.status, errText);
|
||||
return NextResponse.json({ error: "Failed to confirm matches" }, { status: 502 });
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
return NextResponse.json(data, { status: 200 });
|
||||
} catch (err) {
|
||||
console.error("Failed to reach backend confirm-matches:", err);
|
||||
return NextResponse.json({ error: "Failed to confirm matches" }, { status: 502 });
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ import { getServerSession } from "next-auth";
|
|||
import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
|
||||
import { z } from "zod";
|
||||
import { createS3Client } from "@/app/utils/s3";
|
||||
import { sendToQueue } from "@/app/utils/sqs";
|
||||
import S3 from "aws-sdk/clients/s3";
|
||||
import * as XLSX from "xlsx";
|
||||
|
||||
|
|
@ -131,20 +130,41 @@ export async function POST(
|
|||
}
|
||||
|
||||
const s3Uri = `s3://${outputBucket}/${transformedKey}`;
|
||||
const queueName = process.env.POSTCODE_SPLITTER_QUEUE_NAME;
|
||||
if (!queueName) {
|
||||
console.error("POSTCODE_SPLITTER_QUEUE_NAME not set");
|
||||
|
||||
const fastapiUrl = process.env.FASTAPI_API_URL;
|
||||
const fastapiKey = process.env.FASTAPI_API_KEY;
|
||||
if (!fastapiUrl || !fastapiKey) {
|
||||
console.error("FASTAPI_API_URL or FASTAPI_API_KEY not set");
|
||||
return NextResponse.json({ error: "Server misconfiguration" }, { status: 500 });
|
||||
}
|
||||
|
||||
const sessionToken =
|
||||
request.cookies.get("__Secure-next-auth.session-token")?.value ??
|
||||
request.cookies.get("next-auth.session-token")?.value;
|
||||
|
||||
try {
|
||||
await sendToQueue(
|
||||
{ task_id: body.taskId, sub_task_id: body.subTaskId, s3_uri: s3Uri },
|
||||
{ queueName }
|
||||
);
|
||||
const triggerRes = await fetch(`${fastapiUrl}/v1/bulk-uploads/trigger-splitter`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": fastapiKey,
|
||||
Authorization: `Bearer ${sessionToken}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
task_id: body.taskId,
|
||||
sub_task_id: body.subTaskId,
|
||||
s3_uri: s3Uri,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!triggerRes.ok) {
|
||||
const errText = await triggerRes.text().catch(() => "");
|
||||
console.error("Backend trigger-splitter failed:", triggerRes.status, errText);
|
||||
return NextResponse.json({ error: "Failed to trigger address matching" }, { status: 502 });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to send SQS message:", err);
|
||||
return NextResponse.json({ error: "Failed to queue onboarding job" }, { status: 500 });
|
||||
console.error("Failed to reach backend trigger-splitter:", err);
|
||||
return NextResponse.json({ error: "Failed to trigger address matching" }, { status: 502 });
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue