170 lines
4.6 KiB
Go
170 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type DodItem struct {
|
|
ID string `json:"id"`
|
|
RunID string `json:"run_id"`
|
|
ItemKey string `json:"item_key"`
|
|
Kind string `json:"kind"`
|
|
Expected string `json:"expected"`
|
|
Required bool `json:"required"`
|
|
Status string `json:"status"`
|
|
CreatedAt int64 `json:"created_at"`
|
|
}
|
|
|
|
type DodEvidence struct {
|
|
ID string `json:"id"`
|
|
DodItemID string `json:"dod_item_id"`
|
|
Kind string `json:"kind"`
|
|
PayloadPath *string `json:"payload_path,omitempty"`
|
|
PayloadURL *string `json:"payload_url,omitempty"`
|
|
PayloadText *string `json:"payload_text,omitempty"`
|
|
AttachedAt int64 `json:"attached_at"`
|
|
ValidatedAt *int64 `json:"validated_at,omitempty"`
|
|
ValidatedBy *string `json:"validated_by,omitempty"`
|
|
}
|
|
|
|
func createDodItem(db *sql.DB, runID, key, kind, expected string, required bool) (DodItem, error) {
|
|
id := "dod_" + uuid.New().String()[:12]
|
|
now := time.Now().Unix()
|
|
reqInt := 0
|
|
if required {
|
|
reqInt = 1
|
|
}
|
|
_, err := db.Exec(`INSERT INTO dod_items
|
|
(id, run_id, item_key, kind, expected, required, status, created_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
id, runID, key, kind, expected, reqInt, now)
|
|
if err != nil {
|
|
return DodItem{}, err
|
|
}
|
|
return DodItem{
|
|
ID: id, RunID: runID, ItemKey: key, Kind: kind, Expected: expected,
|
|
Required: required, Status: "pending", CreatedAt: now,
|
|
}, nil
|
|
}
|
|
|
|
func listDodItems(db *sql.DB, runID string) ([]DodItem, error) {
|
|
rows, err := db.Query(`SELECT id, run_id, item_key, kind, expected, required, status, created_at
|
|
FROM dod_items WHERE run_id = ? ORDER BY created_at`, runID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
out := []DodItem{}
|
|
for rows.Next() {
|
|
var it DodItem
|
|
var reqInt int
|
|
if err := rows.Scan(&it.ID, &it.RunID, &it.ItemKey, &it.Kind, &it.Expected, &reqInt, &it.Status, &it.CreatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
it.Required = reqInt != 0
|
|
out = append(out, it)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func attachEvidence(db *sql.DB, itemID, kind string, path, url, text *string) (DodEvidence, error) {
|
|
id := "ev_" + uuid.New().String()[:12]
|
|
now := time.Now().Unix()
|
|
_, err := db.Exec(`INSERT INTO dod_evidence
|
|
(id, dod_item_id, kind, payload_path, payload_url, payload_text, attached_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
id, itemID, kind, nullStr(path), nullStr(url), nullStr(text), now)
|
|
if err != nil {
|
|
return DodEvidence{}, err
|
|
}
|
|
// Auto-bump item status to 'done' on first evidence
|
|
_, _ = db.Exec(`UPDATE dod_items SET status = 'done' WHERE id = ? AND status = 'pending'`, itemID)
|
|
return DodEvidence{
|
|
ID: id, DodItemID: itemID, Kind: kind,
|
|
PayloadPath: path, PayloadURL: url, PayloadText: text,
|
|
AttachedAt: now,
|
|
}, nil
|
|
}
|
|
|
|
func validateEvidence(db *sql.DB, evID, validatedBy string) error {
|
|
now := time.Now().Unix()
|
|
res, err := db.Exec(`UPDATE dod_evidence
|
|
SET validated_at = ?, validated_by = ? WHERE id = ?`, now, validatedBy, evID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
n, _ := res.RowsAffected()
|
|
if n == 0 {
|
|
return sql.ErrNoRows
|
|
}
|
|
// Bump item status to validated
|
|
_, _ = db.Exec(`UPDATE dod_items SET status = 'validated'
|
|
WHERE id = (SELECT dod_item_id FROM dod_evidence WHERE id = ?)`, evID)
|
|
return nil
|
|
}
|
|
|
|
func listEvidence(db *sql.DB, itemID string) ([]DodEvidence, error) {
|
|
rows, err := db.Query(`SELECT id, dod_item_id, kind, payload_path, payload_url, payload_text,
|
|
attached_at, validated_at, validated_by
|
|
FROM dod_evidence WHERE dod_item_id = ? ORDER BY attached_at`, itemID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
out := []DodEvidence{}
|
|
for rows.Next() {
|
|
var ev DodEvidence
|
|
var path, url, text, valBy sql.NullString
|
|
var valAt sql.NullInt64
|
|
if err := rows.Scan(&ev.ID, &ev.DodItemID, &ev.Kind, &path, &url, &text,
|
|
&ev.AttachedAt, &valAt, &valBy); err != nil {
|
|
return nil, err
|
|
}
|
|
if path.Valid {
|
|
s := path.String
|
|
ev.PayloadPath = &s
|
|
}
|
|
if url.Valid {
|
|
s := url.String
|
|
ev.PayloadURL = &s
|
|
}
|
|
if text.Valid {
|
|
s := text.String
|
|
ev.PayloadText = &s
|
|
}
|
|
if valAt.Valid {
|
|
v := valAt.Int64
|
|
ev.ValidatedAt = &v
|
|
}
|
|
if valBy.Valid {
|
|
s := valBy.String
|
|
ev.ValidatedBy = &s
|
|
}
|
|
out = append(out, ev)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// dodGateOpen returns true when every required item has at least one validated evidence.
|
|
func dodGateOpen(db *sql.DB, runID string) (bool, error) {
|
|
row := db.QueryRow(`
|
|
SELECT COUNT(*) FROM dod_items
|
|
WHERE run_id = ? AND required = 1
|
|
AND status != 'validated'`, runID)
|
|
var n int
|
|
if err := row.Scan(&n); err != nil {
|
|
return false, err
|
|
}
|
|
return n == 0, nil
|
|
}
|
|
|
|
func nullStr(p *string) interface{} {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
return *p
|
|
}
|