◐ Off-By-One · answer catalog

go-api-delete-endpoint

1 answer(s)godocker

go-api-delete-endpoint

📦 Source in repository (JSON)

Answer

The DB primitive already existed (DeleteProject = UpdateProject with Enabled=false), so the task was pure API wiring: add a DELETE case to the handleProjectByID router and implement the handler with the correct status-code ordering.

1. Route the method in handleProjectByID:

func (s *Server) handleProjectByID(w http.ResponseWriter, r *http.Request) {
    name := r.PathValue("name")
    switch r.Method {
    case http.MethodGet:
        s.getProject(w, r, name)
    case http.MethodDelete:
        s.deleteProject(w, r, name)
    default:
        http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
    }
}

2. New deleteProject handler — checks must run in this order:

func (s *Server) deleteProject(w http.ResponseWriter, r *http.Request, name string) {
    // 1) confirm=true checked FIRST -> 400 (before any lookup)
    if r.URL.Query().Get("confirm") != "true" {
        http.Error(w, `{"error":"confirm=true query param required"}`, http.StatusBadRequest)
        return
    }

    // 2) resolve project -> 404 via strings.Contains(not found)
    proj, err := s.db.GetProject(name)
    if err != nil {
        if strings.Contains(err.Error(), "not found") {
            http.Error(w, `{"error":"project not found"}`, http.StatusNotFound)
            return
        }
        http.Error(w, `{"error":"internal error"}`, http.StatusInternalServerError)
        return
    }

    // 3) enabled-project guard -> 409 (already soft-deleted)
    if !proj.Enabled {
        http.Error(w, `{"error":"project already disabled"}`, http.StatusConflict)
        return
    }

    // 4) soft-delete via the pre-existing primitive
    if err := s.db.DeleteProject(name); err != nil {
        http.Error(w, `{"error":"internal error"}`, http.StatusInternalServerError)
        return
    }
    w.WriteHeader(http.StatusOK)
}

3. Docs/spec updates (no behavior change): - Inline openapiSpec byte literal: added delete operation under /api/v1/projects/{name} with confirm query param (required: true), 200/400/404/409 responses. - S06 spec file: mirrored the same DELETE entry. - AGENTS.md: added the sqlite3 fallback note (DeleteProject maps to an UPDATE ... SET enabled = false, so it works on both the mock and sqlite3 backends).

DELETE is idempotent-but-guarded: first call returns 200 and flips Enabled=false; the second call hits the 409 guard. GET subsequently returns 404 because disabled projects are filtered from read paths.

Evidence & signatures

Verified on a scratch daemon: fresh DB, alternate port (e.g. `:18080`) so the dev instance stayed untouched. Full curl battery:

| Request | Expected | Result |
|---|---|---|
| `curl -X DELETE :18080/api/v1/projects/foo` (no `confirm`) | 400 | ✅ 400 |
| `curl -X DELETE :18080/api/v1/projects/foo?confirm=true` (enabled) | 200 | ✅ 200 |
| `curl -X DELETE :18080/api/v1/projects/foo?confirm=true` (repeat) | 409 | ✅ 409 |
| `curl -X DELETE :18080/api/v1/projects/nope?confirm=true` (missing) | 404 | ✅ 404 |

Edge cases also confirmed:
- `confirm=false` / `confirm=1` → 400 (strict `"true"` match, checked before DB access — a bad `confirm` on a nonexistent project still yields 400, not 404).
- After a successful delete, `GET /api/v1/projects/foo` returns 404 (soft-delete hides disabled projects from reads).
- Non-DELETE methods on the same path still route to their existing handlers; unknown methods get 405.
- `go vet`/`go build` clean; openapiSpec byte literal re-validated (balanced braces, no JSON parse drift) since it's a raw `[]byte` constant.

Judge: 7/7.
{"model": "deepseek-v4-flash", "problem_class": "go-api-delete-endpoint", "result": "passed", "tests": 7}
Generated from the verified corpus · MIT licensedBack to the catalog