feat: Todo MVP — REST API + frontend#1
Conversation
Co-Authored-By: Al Gorithm <noreply@conduction.nl>
Co-Authored-By: Al Gorithm <noreply@conduction.nl>
…oints Co-Authored-By: Al Gorithm <noreply@conduction.nl>
Co-Authored-By: Al Gorithm <noreply@conduction.nl>
Co-Authored-By: Al Gorithm <noreply@conduction.nl>
|
[WARNING] No |
|
[WARNING] No |
|
[WARNING] Mass Assignment — PATCH endpoint passes unfiltered request body to Object.assign export function update(id, { completed }) {
const todo = todos.find((t) => t.id === id);
if (!todo) return null;
if (completed !== undefined) todo.completed = Boolean(completed);
return todo;
}This eliminates both the mass-assignment risk and any prototype-pollution surface. |
|
[WARNING] Dead import — |
|
[WARNING] Missing HTTP Security Headers
import helmet from helmet;
app.use(helmet());Or set the individual headers manually if you prefer to avoid the dependency. |
|
[SUGGESTION] |
|
[SUGGESTION] PATCH endpoint does not validate that if (typeof completed !== 'boolean') {
return res.status(400).json({ error: 'completed must be a boolean' });
} |
|
[SUGGESTION] No input length validation on if (!title || typeof title !== 'string' || title.length > 255) {
return res.status(400).json({ error: 'title is required and must be ≤ 255 characters' });
}Also consider reducing the JSON body size limit: |
|
[SUGGESTION] Frontend fetch calls swallow errors silently |
|
[SUGGESTION] No rate limiting on any API endpoint import rateLimit from 'express-rate-limit';
const limiter = rateLimit({ windowMs: 60_000, max: 60 });
app.use('/api', limiter);This is optional for an MVP but should be in place before any public exposure. |
|
[SUGGESTION] Deprecated dependencies — |
|
Hydra Security Review — Scanner Summary
No automated findings were raised. The findings in this review are from manual code inspection of the PR diff. No false positives to suppress — all scanner results were empty. |
{ "pass": true, "blocking": [] }Hydra Security Verdict — Clyde Barcode, Conduction B.V. No CRITICAL findings. Two WARNINGs and two SUGGESTIONs were raised from manual review; none are blocking for an MVP merge but the mass-assignment sink in
|
Hydra Code Review — Juan Claude van DammeSummary
Tests: 8/8 passed ( Verdict{ "pass": false, "blocking": ["No lint script defined in package.json", "No package-lock.json committed — npm ci fails", "Dead import: createRequire imported but never used in src/server.js:5"] }No CRITICAL issues; all spec scenarios are implemented and all tests pass. The three WARNING items must be resolved before merge: the missing lint setup (a Hydra pipeline requirement), the missing lockfile (reproducibility), and the unused import (will block linting once ESLint is added). |
Summary
Implements the Todo MVP as described in the OpenSpec change 'spec'. A Node.js/Express REST API backed by an in-memory store is created alongside a vanilla HTML/JS single-page frontend. All four required CRUD endpoints are covered with supertest integration tests; every test passes.
Spec Reference
https://github.com/algorithm-conduction/todo-app/blob/hydra/spec/openspec/design.md
Changes
package.json— Node.js project manifest; declares express, supertest, vitest; sets"type": "module"for ESMsrc/store.js— In-memory todo store withlist,create,update,remove, andresethelpers; EUPL-1.2 headersrc/server.js— Express app exposingGET/POST /api/todosandPATCH/DELETE /api/todos/:id; serves static frontend; EUPL-1.2 headerpublic/index.html— Single-page frontend: add, toggle-complete, and delete todos via fetch API calls; EUPL-1.2 headertest/api.test.js— Vitest + supertest integration tests covering all four CRUD scenarios from specs/api.md; EUPL-1.2 headerTest Coverage
test/api.test.js— POST creates todo (201, id/title/completed:false); POST rejects missing title (400); GET returns array with existing todos (200); GET returns empty array; PATCH marks todo completed (200); PATCH returns 404 for unknown id; DELETE removes todo (204) and absent from list; DELETE returns 404 for unknown id