fix: atomic burn-after-read claim (#58)
SoftDelete now reports whether it performed the delete (conditional UPDATE ... WHERE deleted_at IS NULL via RowsAffected). RegisterRead returns an admitted flag: legacy burn pastes admit exactly one reader (atomic soft-delete win), and burn-after-N pastes increment reads_used via a conditional UPDATE guarded on reads_used < reads_limit, so concurrent readers cannot both consume the final read. API/HTML/raw read paths return 404 when the reader loses the burn claim. Adds concurrency regression tests: 24 parallel readers of a burn paste (exactly one receives content) and 30 parallel readers vs a 3-read budget (exactly 3 admitted).
This commit is contained in:
@@ -40,6 +40,12 @@ func (a *apiServer) handleCreateCan(w http.ResponseWriter, r *http.Request) {
|
||||
writeErr(w, 400, "invalid expires_in")
|
||||
return
|
||||
}
|
||||
// #60: clamp at the API boundary like the pastes API does —
|
||||
// reject zero/negative and durations past the 1-year UI cap.
|
||||
if !store.ValidExpiry(d) {
|
||||
writeErr(w, 400, "expires_in must be between 1 minute and 1 year")
|
||||
return
|
||||
}
|
||||
t := now + int64(d.Seconds())
|
||||
expiresAt = &t
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user