changeset 55:c1fc20f0ceaf

Add functionality for listing and deleting reminders
author Lewin Bormann <lbo@spheniscida.de>
date Sat, 10 Dec 2016 21:15:28 +0100
parents b56ef3fe4bff
children 22ac80ac041b
files handler_remind.go handler_todo.go handlers.go remind.go sql/remind.go
diffstat 5 files changed, 128 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/handler_remind.go	Sat Dec 10 20:35:48 2016 +0100
+++ b/handler_remind.go	Sat Dec 10 21:15:28 2016 +0100
@@ -119,14 +119,17 @@
 )
 
 // Handler for /remind messages
-// Allowed formats: hh:mm (today), +XXs, +XXm, +XXh, +XXd, "yyyy-mm-dd hh:mm",
-// {Mo,Di,Mi,Do,Fr,Sa,So} hh:mm,
+// If no time or text is given, lists the reminders with option to cancel
 func reminderHandler(ctx context.Context, msg message) (replyContent, error) {
-	// TODO: This doesn't work with multi-part formats like dateRE or dateOfWeekRE!
-	// first part is time
+	// list reminders
+	if msg.Text == "" {
+		return listReminders(ctx, msg.Chat.ID)
+	}
+
+	// restStart is the index of the first character of the remaining text
 	alertTime, restStart := parseReminderString(strings.Trim(msg.Text, " "))
 
-	if restStart == 0 || alertTime.IsZero() {
+	if alertTime.IsZero() {
 		return replyContent{text: formatHelpText}, nil
 	}
 
@@ -152,5 +155,66 @@
 		return replyContent{text: "_Ich konnte leider keine Erinnerung setzen._"}, nil
 	}
 
-	return replyContent{text: fmt.Sprintf("*✓* Erinnerung #%d in %v", id, alertTime.Sub(time.Now()))}, nil
+	remaining := alertTime.Sub(time.Now())
+	hours := uint64(remaining.Seconds()) / 3600
+	minutes := (uint64(remaining.Seconds()) % 3600) / 60
+	return replyContent{text: fmt.Sprintf("*✓* Erinnerung #%d in %d:%02d", id, hours, minutes)}, nil
 }
+
+func listReminders(ctx context.Context, chatID int64) (replyContent, error) {
+	db, err := backend.Reminders()
+
+	if err != nil {
+		log.Println("Couldn't get Reminders object:", err)
+		return replyContent{text: "_Konnte mich nicht mit der Datenbank verbinden..._"}, nil
+	}
+
+	rms, err := db.ListActives(chatID)
+
+	if err != nil {
+		log.Println("Couldn't get active reminders:", err, chatID)
+		return replyContent{text: "_Konnte keine Erinnerungen finden..._"}, nil
+	}
+
+	// create buttons
+	buttons := make([][]inlineKeyboardButton, len(rms))
+
+	for i := range rms {
+		t := rms[i].Due.Add(time.Duration(*flagLocalOffset) * time.Second)
+		date := fmt.Sprintf("%02d-%02d-%02d %02d:%02d", t.Year(), int(t.Month()), t.Day(), t.Hour(), t.Minute())
+		buttons[i] = []inlineKeyboardButton{inlineKeyboardButton{
+			Text:          fmt.Sprintf("[%s] %s (%s)", date, rms[i].Text, rms[i].Owner),
+			Callback_Data: fmt.Sprintf("%s:%d", cancelReminderCallback, rms[i].ReminderID),
+		}}
+	}
+
+	return replyContent{text: "Erinnerung anklicken, um sie zu löschen", buttons: inlineKeyboardMarkup{Inline_Keyboard: buttons}}, nil
+}
+
+func deleteReminderCallback(ctx context.Context, token string, cbq callbackQuery) (replyContent, error) {
+	log.Println("Callback for removing reminder", token)
+
+	db, err := backend.Reminders()
+
+	if err != nil {
+		log.Println("Couldn't get Reminders object:", err)
+		return replyContent{text: "_Konnte mich nicht mit der Datenbank verbinden..._"}, nil
+	}
+
+	id, err := strconv.ParseUint(token, 10, 32)
+
+	if err != nil {
+		log.Println("Bad token", token)
+		return replyContent{text: "_Interner Fehler D:_"}, nil
+	}
+
+	n, err := db.MarkRemindersDone([]uint{uint(id)})
+
+	if err != nil {
+		return replyContent{text: "_Konnte Erinnerung nicht entfernen..._"}, nil
+	} else if n == 0 {
+		return replyContent{text: "Diese Erinnerung wurde bereits gelöscht!"}, nil
+	}
+
+	return replyContent{text: fmt.Sprintf("*✓* Erinnerung #%d gelöscht", id)}, nil
+}
--- a/handler_todo.go	Sat Dec 10 20:35:48 2016 +0100
+++ b/handler_todo.go	Sat Dec 10 21:15:28 2016 +0100
@@ -30,7 +30,7 @@
 			return replyContent{text: "_Erstellung fehlgeschlagen:_ " + err.Error()}, err
 		}
 
-		return replyContent{text: fmt.Sprintf("Aufgabe #%d erstellt", id)}, nil
+		return replyContent{text: fmt.Sprintf("*✓* Aufgabe #%d erstellt", id)}, nil
 	} else { // Query open items
 		todos, err := todo.GetOpenTodos("") // default list
 
--- a/handlers.go	Sat Dec 10 20:35:48 2016 +0100
+++ b/handlers.go	Sat Dec 10 21:15:28 2016 +0100
@@ -17,7 +17,8 @@
 	statusCmd  = "status"
 	todoCmd    = "todo"
 
-	todoDoneCallback = "markdone"
+	todoDoneCallback       = "markdone"
+	cancelReminderCallback = "cancelrm"
 )
 
 type handler struct {
@@ -38,7 +39,8 @@
 	}
 
 	callbackHandlers = map[string]callbackHandler{
-		todoDoneCallback: todoDoneHandler,
+		todoDoneCallback:       todoDoneHandler,
+		cancelReminderCallback: deleteReminderCallback,
 	}
 )
 
--- a/remind.go	Sat Dec 10 20:35:48 2016 +0100
+++ b/remind.go	Sat Dec 10 21:15:28 2016 +0100
@@ -51,7 +51,9 @@
 		return err
 	}
 
-	return reminders.MarkRemindersDone([]uint{rm.ReminderID})
+	_, err = reminders.MarkRemindersDone([]uint{rm.ReminderID})
+
+	return err
 }
 
 type reminders struct {
--- a/sql/remind.go	Sat Dec 10 20:35:48 2016 +0100
+++ b/sql/remind.go	Sat Dec 10 21:15:28 2016 +0100
@@ -18,8 +18,12 @@
 	// Parameters: n/a
 	// Returns: id INTEGER, due TIMESTAMP, owner TEXT, description TEXT, chat_id INTEGER, orig_msg_id INTEGER
 	selectDueReminders = `SELECT id, due, owner, description, chat_id, orig_msg_id FROM reminders WHERE NOT done AND now() > due ORDER BY due ASC`
+	// Parameter: 1 = chat ID
+	// Returns: id INTEGER, due TIMESTAMP, owner TEXT, description TEXT
+	listFutureReminders = `SELECT id, due, owner, description FROM reminders WHERE NOT done AND due > now() AND chat_id = $1 ORDER BY due ASC`
 	// Parameters: 1 = reminder ID
-	markReminderDone = `UPDATE reminders SET done = true WHERE id = $1`
+	// Returns: n/a
+	markReminderDone = `UPDATE reminders SET done = true WHERE id = $1 AND NOT done`
 )
 
 type Reminder struct {
@@ -48,7 +52,8 @@
 	return r.db.prewarm([]string{
 		insertReminder,
 		selectDueReminders,
-		markReminderDone})
+		markReminderDone,
+		listFutureReminders})
 }
 
 func (r Reminders) Remove(reminderID uint) error {
@@ -66,6 +71,38 @@
 	}
 }
 
+func (r Reminders) ListActives(chatID int64) ([]Reminder, error) {
+	if stmt, ok := r.db.prepared[listFutureReminders]; ok {
+		rows, err := stmt.Query(chatID)
+
+		if err != nil {
+			log.Println("Couldn't query due reminders:", err)
+			return nil, err
+		}
+
+		defer rows.Close()
+
+		rm := make([]Reminder, 0, 16)
+
+		for rows.Next() {
+			reminder := Reminder{}
+			err := rows.Scan(&reminder.ReminderID, &reminder.Due, &reminder.Owner, &reminder.Text)
+
+			if err != nil {
+				log.Println("Couldn't scan reminder row:", err)
+				return nil, err
+			}
+
+			rm = append(rm, reminder)
+		}
+
+		return rm, nil
+	} else {
+		log.Println("Couldn't find prepared statement for", selectDueReminders)
+		return nil, errors.New("couldn't find prepared statement")
+	}
+}
+
 func (r Reminders) InsertReminder(rm Reminder) (uint, error) {
 	if stmt, ok := r.db.prepared[insertReminder]; ok {
 		row := stmt.QueryRow(pq.FormatTimestamp(rm.Due), rm.Text, rm.Owner, rm.ChatID, rm.ReplyTo)
@@ -120,20 +157,26 @@
 }
 
 // If this returns an error, it can be called with the same IDs again.
-func (r Reminders) MarkRemindersDone(ids []uint) error {
+// Returns how many rows were affected.
+func (r Reminders) MarkRemindersDone(ids []uint) (int, error) {
 	if stmt, ok := r.db.prepared[markReminderDone]; ok {
+		var i int64
 		for _, id := range ids {
-			_, err := stmt.Exec(id)
+			rows, err := stmt.Exec(id)
 
 			if err != nil {
 				log.Println("Couldn't mark reminder", id, "as done:", err)
-				return err
+				return int(i), err
+			}
+
+			if n, err := rows.RowsAffected(); err == nil {
+				i += n
 			}
 		}
 
-		return nil
+		return int(i), nil
 	} else {
 		log.Println("Couldn't find prepared statement for", markReminderDone)
-		return errors.New("couldn't find prepared statement")
+		return 0, errors.New("couldn't find prepared statement")
 	}
 }