datestuff

This commit is contained in:
lubiana 2025-07-25 16:27:30 +02:00
parent 1a6d7291c4
commit e11dd4ed1f
Signed by: lubiana
SSH key fingerprint: SHA256:vW1EA0fRR3Fw+dD/sM0K+x3Il2gSry6YRYHqOeQwrfk
5 changed files with 326 additions and 27 deletions

114
main.go
View file

@ -37,6 +37,8 @@ type ChecklistItem struct {
LockUntil *time.Time `json:"lock_until,omitempty"`
Checklist string `json:"checklist_uuid"`
Dependencies []int `json:"dependencies,omitempty"`
NotBefore *time.Time `json:"not_before,omitempty"`
NotAfter *time.Time `json:"not_after,omitempty"`
}
type ItemLock struct {
@ -78,6 +80,8 @@ func getChecklistDB(uuid string) (*sql.DB, error) {
content TEXT NOT NULL,
checked INTEGER NOT NULL,
parent_id INTEGER,
not_before TEXT,
not_after TEXT,
FOREIGN KEY(parent_id) REFERENCES items(id)
);`,
`CREATE TABLE IF NOT EXISTS dependencies (
@ -173,7 +177,7 @@ func loadChecklistItems(uuid string) ([]ChecklistItem, error) {
}
defer db.Close()
rows, err := db.Query(`SELECT id, content, checked, parent_id FROM items`)
rows, err := db.Query(`SELECT id, content, checked, parent_id, not_before, not_after FROM items`)
if err != nil {
return nil, err
}
@ -183,7 +187,9 @@ func loadChecklistItems(uuid string) ([]ChecklistItem, error) {
var it ChecklistItem
var checked int
var parentID sql.NullInt64
err = rows.Scan(&it.ID, &it.Content, &checked, &parentID)
var notBefore sql.NullString
var notAfter sql.NullString
err = rows.Scan(&it.ID, &it.Content, &checked, &parentID, &notBefore, &notAfter)
if err != nil {
return nil, err
}
@ -192,6 +198,16 @@ func loadChecklistItems(uuid string) ([]ChecklistItem, error) {
v := int(parentID.Int64)
it.ParentID = &v
}
if notBefore.Valid {
if t, err := time.Parse(time.RFC3339, notBefore.String); err == nil {
it.NotBefore = &t
}
}
if notAfter.Valid {
if t, err := time.Parse(time.RFC3339, notAfter.String); err == nil {
it.NotAfter = &t
}
}
it.Checklist = uuid
// Load dependencies for this item
@ -227,15 +243,25 @@ func addChecklist(name string) (string, error) {
return uuidStr, err
}
func addItem(uuid, content string, parentID *int) (ChecklistItem, error) {
func addItem(uuid, content string, parentID *int, notBefore *time.Time, notAfter *time.Time) (ChecklistItem, error) {
db, err := getChecklistDB(uuid)
if err != nil {
return ChecklistItem{}, err
}
defer db.Close()
res, err := db.Exec(`INSERT INTO items (content, checked, parent_id) VALUES (?, 0, ?)`,
content, parentID)
var notBeforeStr, notAfterStr *string
if notBefore != nil {
s := notBefore.Format(time.RFC3339)
notBeforeStr = &s
}
if notAfter != nil {
s := notAfter.Format(time.RFC3339)
notAfterStr = &s
}
res, err := db.Exec(`INSERT INTO items (content, checked, parent_id, not_before, not_after) VALUES (?, 0, ?, ?, ?)`,
content, parentID, notBeforeStr, notAfterStr)
if err != nil {
return ChecklistItem{}, err
}
@ -245,11 +271,13 @@ func addItem(uuid, content string, parentID *int) (ChecklistItem, error) {
Content: content,
Checked: false,
ParentID: parentID,
NotBefore: notBefore,
NotAfter: notAfter,
Checklist: uuid,
}, nil
}
func updateItem(uuid string, id int, content *string, checked *bool, parentID *int, dependencies *[]int) (ChecklistItem, error) {
func updateItem(uuid string, id int, content *string, checked *bool, parentID *int, dependencies *[]int, notBefore *time.Time, notAfter *time.Time) (ChecklistItem, error) {
log.Printf("updateItem called with uuid: %s, id: %d", uuid, id)
log.Printf("Parameters: content=%v, checked=%v, parentID=%v, dependencies=%v", content, checked, parentID, dependencies)
@ -262,7 +290,7 @@ func updateItem(uuid string, id int, content *string, checked *bool, parentID *i
log.Printf("Database connection successful for uuid: %s", uuid)
// If trying to check an item, validate dependencies first
// If trying to check an item, validate dependencies and date constraints first
if checked != nil && *checked {
log.Printf("Validating dependencies for item %d", id)
deps, err := loadItemDependencies(db, id)
@ -283,8 +311,32 @@ func updateItem(uuid string, id int, content *string, checked *bool, parentID *i
return ChecklistItem{}, fmt.Errorf("cannot complete item: dependency %d is not completed", depID)
}
}
// Validate date constraints
now := time.Now()
var notBeforeStr, notAfterStr sql.NullString
err = db.QueryRow(`SELECT not_before, not_after FROM items WHERE id = ?`, id).Scan(&notBeforeStr, &notAfterStr)
if err != nil {
log.Printf("Failed to get date constraints: %v", err)
return ChecklistItem{}, err
}
if notBeforeStr.Valid {
if notBefore, err := time.Parse(time.RFC3339, notBeforeStr.String); err == nil {
if now.Before(notBefore) {
return ChecklistItem{}, fmt.Errorf("cannot complete item: not before %s", notBefore.Format("2006-01-02 15:04:05"))
}
}
}
if notAfterStr.Valid {
if notAfter, err := time.Parse(time.RFC3339, notAfterStr.String); err == nil {
if now.After(notAfter) {
return ChecklistItem{}, fmt.Errorf("cannot complete item: not after %s", notAfter.Format("2006-01-02 15:04:05"))
}
}
}
} else {
log.Printf("Skipping dependency validation - checked is %v", checked)
log.Printf("Skipping dependency and date validation - checked is %v", checked)
}
log.Printf("About to check dependencies parameter")
@ -331,6 +383,14 @@ func updateItem(uuid string, id int, content *string, checked *bool, parentID *i
set = append(set, "parent_id = ?")
args = append(args, *parentID)
}
if notBefore != nil {
set = append(set, "not_before = ?")
args = append(args, notBefore.Format(time.RFC3339))
}
if notAfter != nil {
set = append(set, "not_after = ?")
args = append(args, notAfter.Format(time.RFC3339))
}
if len(set) > 0 {
q := "UPDATE items SET " + strings.Join(set, ", ") + " WHERE id = ?"
args = append(args, id)
@ -347,7 +407,7 @@ func updateItem(uuid string, id int, content *string, checked *bool, parentID *i
// Return updated item
log.Printf("Querying updated item %d", id)
rows, err := db.Query(`SELECT id, content, checked, parent_id FROM items WHERE id = ?`, id)
rows, err := db.Query(`SELECT id, content, checked, parent_id, not_before, not_after FROM items WHERE id = ?`, id)
if err != nil {
log.Printf("Failed to query updated item: %v", err)
return ChecklistItem{}, err
@ -358,7 +418,9 @@ func updateItem(uuid string, id int, content *string, checked *bool, parentID *i
var it ChecklistItem
var checkedInt int
var parentID sql.NullInt64
err = rows.Scan(&it.ID, &it.Content, &checkedInt, &parentID)
var notBefore sql.NullString
var notAfter sql.NullString
err = rows.Scan(&it.ID, &it.Content, &checkedInt, &parentID, &notBefore, &notAfter)
if err != nil {
log.Printf("Failed to scan updated item: %v", err)
return ChecklistItem{}, err
@ -368,6 +430,16 @@ func updateItem(uuid string, id int, content *string, checked *bool, parentID *i
v := int(parentID.Int64)
it.ParentID = &v
}
if notBefore.Valid {
if t, err := time.Parse(time.RFC3339, notBefore.String); err == nil {
it.NotBefore = &t
}
}
if notAfter.Valid {
if t, err := time.Parse(time.RFC3339, notAfter.String); err == nil {
it.NotAfter = &t
}
}
it.Checklist = uuid
// Load dependencies
@ -477,15 +549,17 @@ func handleAddItem(w http.ResponseWriter, r *http.Request) {
}
type Req struct {
Content string `json:"content"`
ParentID *int `json:"parent_id"`
Content string `json:"content"`
ParentID *int `json:"parent_id"`
NotBefore *time.Time `json:"not_before"`
NotAfter *time.Time `json:"not_after"`
}
var req Req
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || strings.TrimSpace(req.Content) == "" {
http.Error(w, "Missing content", 400)
return
}
item, err := addItem(uuid, req.Content, req.ParentID)
item, err := addItem(uuid, req.Content, req.ParentID, req.NotBefore, req.NotAfter)
if err != nil {
http.Error(w, "Failed to add item", 500)
return
@ -521,19 +595,21 @@ func handleUpdateItem(w http.ResponseWriter, r *http.Request) {
}
type Req struct {
Content *string `json:"content"`
Checked *bool `json:"checked"`
ParentID *int `json:"parent_id"`
Dependencies *[]int `json:"dependencies"`
Content *string `json:"content"`
Checked *bool `json:"checked"`
ParentID *int `json:"parent_id"`
Dependencies *[]int `json:"dependencies"`
NotBefore *time.Time `json:"not_before"`
NotAfter *time.Time `json:"not_after"`
}
var req Req
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Bad body", 400)
return
}
item, err := updateItem(uuid, id, req.Content, req.Checked, req.ParentID, req.Dependencies)
item, err := updateItem(uuid, id, req.Content, req.Checked, req.ParentID, req.Dependencies, req.NotBefore, req.NotAfter)
if err != nil {
if strings.Contains(err.Error(), "cannot complete item: dependency") {
if strings.Contains(err.Error(), "cannot complete item:") {
http.Error(w, err.Error(), 400)
} else {
http.Error(w, "Not found", 404)