fix(db,topics): time.Time params for TIMESTAMP + comment-aware SQL split
Two fixes surfaced by sim e2e test (which otherwise passed full
lifecycle: created → signup_open → 3 signups → allocator → debating
→ arguments → verdict gate (409 early, 201 after debate_end_at) →
completed).
1) MySQL TIMESTAMP rejects RFC3339-with-Z strings — passing those as
sqlx parameters fails with "Incorrect datetime value". Changed
CreateTopicInput lifecycle fields from string to time.Time; the
handler parses+UTCs in validateLifecycleTimes (which now returns
the parsed array along with the validation result) and passes
time.Time to the store. The mysql driver formats correctly.
2) splitSQL was naive `strings.Split(s, ";")` which split inside
comments — the 001 migration had a few `--` lines containing `;`
("signup_close_at; immutable", etc) which broke. Migration text
tidied to not use `;` inside comments, AND splitSQL upgraded to
skip both `-- ...` and `/* ... */` comment regions before splitting.
Sim verified — clean apply on fresh MySQL.
This commit is contained in:
@@ -114,7 +114,8 @@ func (h *TopicsHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
if body.VerdictSchemaID == "" {
|
||||
body.VerdictSchemaID = "free-form"
|
||||
}
|
||||
if err := validateLifecycleTimes(body); err != nil {
|
||||
parsed, err := validateLifecycleTimes(body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -123,10 +124,10 @@ func (h *TopicsHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
Summary: body.Summary,
|
||||
Visibility: models.Visibility(body.Visibility),
|
||||
VerdictSchemaID: body.VerdictSchemaID,
|
||||
SignupOpenAt: body.SignupOpenAt,
|
||||
SignupCloseAt: body.SignupCloseAt,
|
||||
DebateStartAt: body.DebateStartAt,
|
||||
DebateEndAt: body.DebateEndAt,
|
||||
SignupOpenAt: parsed[0],
|
||||
SignupCloseAt: parsed[1],
|
||||
DebateStartAt: parsed[2],
|
||||
DebateEndAt: parsed[3],
|
||||
CreatorUserID: caller.ID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -141,7 +142,10 @@ func (h *TopicsHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
// signup_open < signup_close <= debate_start < debate_end
|
||||
//
|
||||
// All four timestamps must be parsable as RFC3339; failure → 400.
|
||||
func validateLifecycleTimes(b createTopicBody) error {
|
||||
// Returns the parsed times in order [signup_open, signup_close, debate_start, debate_end]
|
||||
// so the caller can pass typed values to the store (MySQL TIMESTAMP doesn't
|
||||
// accept ISO8601 strings directly; the driver handles time.Time properly).
|
||||
func validateLifecycleTimes(b createTopicBody) ([4]time.Time, error) {
|
||||
type p struct {
|
||||
name string
|
||||
raw string
|
||||
@@ -152,24 +156,24 @@ func validateLifecycleTimes(b createTopicBody) error {
|
||||
{"debate_start_at", b.DebateStartAt},
|
||||
{"debate_end_at", b.DebateEndAt},
|
||||
}
|
||||
parsed := make([]time.Time, 4)
|
||||
var parsed [4]time.Time
|
||||
for i, x := range parts {
|
||||
t, err := time.Parse(time.RFC3339, x.raw)
|
||||
if err != nil {
|
||||
return errors.New(x.name + ": must be RFC3339")
|
||||
return parsed, errors.New(x.name + ": must be RFC3339")
|
||||
}
|
||||
parsed[i] = t
|
||||
parsed[i] = t.UTC()
|
||||
}
|
||||
if !parsed[0].Before(parsed[1]) {
|
||||
return errors.New("signup_open_at must be before signup_close_at")
|
||||
return parsed, errors.New("signup_open_at must be before signup_close_at")
|
||||
}
|
||||
if parsed[1].After(parsed[2]) {
|
||||
return errors.New("signup_close_at must be <= debate_start_at")
|
||||
return parsed, errors.New("signup_close_at must be <= debate_start_at")
|
||||
}
|
||||
if !parsed[2].Before(parsed[3]) {
|
||||
return errors.New("debate_start_at must be before debate_end_at")
|
||||
return parsed, errors.New("debate_start_at must be before debate_end_at")
|
||||
}
|
||||
return nil
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
// PUT /api/topics/{id}/visibility — admin-only flip (Phase 2 stub: any
|
||||
|
||||
Reference in New Issue
Block a user