Mercurial > lbo > hg > goe_bot
changeset 28:be37dbe1c05d
Finish todo list implementation
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Sat, 10 Dec 2016 12:54:29 +0100 |
parents | 4a84e6d53800 |
children | 70822d7d17c5 |
files | handler_todo.go handlers.go sql/todo.go |
diffstat | 3 files changed, 138 insertions(+), 32 deletions(-) [+] |
line wrap: on
line diff
--- a/handler_todo.go Sat Dec 10 12:07:29 2016 +0100 +++ b/handler_todo.go Sat Dec 10 12:54:29 2016 +0100 @@ -5,6 +5,7 @@ "errors" "fmt" "log" + "strconv" _ "bitbucket.org/dermesser/goe_bot/sql" ) @@ -21,29 +22,70 @@ return replyContent{text: "_Zugriff fehlgeschlagen:_ " + err.Error()}, err } - id, err := todo.AddTodo(msg.Text, msg.From.First_Name+" "+msg.From.Last_Name) + // Add new item + if msg.Text != "" { + id, err := todo.AddTodo(msg.Text, msg.From.First_Name+" "+msg.From.Last_Name) + + if err != nil { + return replyContent{text: "_Erstellung fehlgeschlagen:_ " + err.Error()}, err + } + + return replyContent{text: fmt.Sprintf("Aufgabe #%d erstellt", id)}, nil + } else { // Query open items + todos, err := todo.GetOpenTodos("") // default list + + if err != nil { + return replyContent{text: "_Konnte keine Aufgaben abfragen:_ " + err.Error()}, err + } - if err != nil { - return replyContent{text: "_Erstellung fehlgeschlagen:_ " + err.Error()}, err + // create buttons + buttons := make([][]inlineKeyboardButton, len(todos)) + + for i := range todos { + text := fmt.Sprintf("%s (%s)", todos[i].Text, todos[i].Owner) + token := fmt.Sprintf("%s:%d", todoDoneCallback, todos[i].ID) + + row := []inlineKeyboardButton{ + inlineKeyboardButton{Callback_Data: token, Text: text}, + } + + buttons[i] = row + } + + return replyContent{text: "Aufgabe anklicken, um sie als erledigt zu markieren;\n_/todo aufgabe_ um Aufgabe anzulegen", + buttons: inlineKeyboardMarkup{Inline_Keyboard: buttons}}, nil } - - return replyContent{text: fmt.Sprintf("Aufgabe #%d erstellt", id)}, nil } -func todoButtonTest(ctx context.Context, msg message) (replyContent, error) { - buttonRows := [][]inlineKeyboardButton{} +// Callback handler; called when a button is pressed. +func todoDoneHandler(ctx context.Context, token string, cbq callbackQuery) (replyContent, error) { + log.Println("Received callback for", todoDoneCallback, token) - for _, b := range []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"} { - buttonRows = append(buttonRows, []inlineKeyboardButton{inlineKeyboardButton{Callback_Data: "markdone:" + b, Text: b}}) + todo, err := backend.Todo(cbq.Message.Chat.ID) + + if err != nil { + return replyContent{text: "_Zugriff fehlgeschlagen:_ " + err.Error()}, err } - return replyContent{text: "Please select", buttons: inlineKeyboardMarkup{buttonRows}}, nil -} + id, err := strconv.ParseUint(token, 10, 64) + + if err != nil { + return replyContent{text: "_Falsches Format; Zahl erwartet_ (" + token + ")"}, err + } + + affected, err := todo.MarkTodoDone(id) -func todoDoneHandler(ctx context.Context, token string, cbq callbackQuery) (replyContent, error) { - log.Println("Received callback for", token) + if err != nil { + return replyContent{text: "_Aktion fehlgeschlagen:_ " + err.Error()}, err + } + + reply := replyContent{} - reply := replyContent{text: "Verarbeitet!"} + if affected > 0 { + reply.text = "*✓*" + } else { + reply.text = "Aufgabe bereits erledigt!" + } return reply, nil }
--- a/handlers.go Sat Dec 10 12:07:29 2016 +0100 +++ b/handlers.go Sat Dec 10 12:54:29 2016 +0100 @@ -10,14 +10,13 @@ ) const ( - echoCmd = "echo" - fortuneCmd = "fortune" - helpCmd = "help" - quoteCmd = "quote" - remindCmd = "remind" - statusCmd = "status" - todoCmd = "todo" - todoTestCmd = "todo-" + echoCmd = "echo" + fortuneCmd = "fortune" + helpCmd = "help" + quoteCmd = "quote" + remindCmd = "remind" + statusCmd = "status" + todoCmd = "todo" todoDoneCallback = "markdone" ) @@ -32,13 +31,12 @@ var ( handlers = map[string]handler{ - echoCmd: {echoHandler, "Anfrage zurücksenden"}, - fortuneCmd: {missingHandler, "Glückskeks"}, - quoteCmd: {missingHandler, "Zitat speichern/abfragen"}, - remindCmd: {missingHandler, "Wecker"}, - statusCmd: {statusHandler, "Status anfragen"}, - todoCmd: {todoHandler, "Aufgabenliste"}, - todoTestCmd: {todoButtonTest, "TODO test (internal)"}, + echoCmd: {echoHandler, "Anfrage zurücksenden"}, + fortuneCmd: {missingHandler, "Glückskeks"}, + quoteCmd: {missingHandler, "Zitat speichern/abfragen"}, + remindCmd: {missingHandler, "Wecker"}, + statusCmd: {statusHandler, "Status anfragen"}, + todoCmd: {todoHandler, "Aufgabenliste"}, } callbackHandlers = map[string]callbackHandler{ @@ -126,12 +124,17 @@ } // Dispatches an incoming callbackQuery. The data field has the format callback_type:token. +// The token can't contain any colons. func dispatchCallback(ctx context.Context, cbq callbackQuery) (replyContent, error) { parts := strings.Split(cbq.Data, ":") // callback:token + if len(parts) != 2 { + log.Println("Bad callback data format:", cbq.Data) + return replyContent{text: "_Tut mir leid, ein interner Fehler ist aufgetreten_"}, errors.New("bad callback data format") + } + if handler, ok := callbackHandlers[parts[0]]; ok { rp, err := handler(ctx, parts[1], cbq) - return rp, err } else { log.Println("Didn't find callback handler for type", parts[0])
--- a/sql/todo.go Sat Dec 10 12:07:29 2016 +0100 +++ b/sql/todo.go Sat Dec 10 12:54:29 2016 +0100 @@ -1,4 +1,4 @@ -// This file contains logic for retrieving and manipulating todo lists. +// Thifile contains logic for retrieving and manipulating todo lists. package sql import ( @@ -15,7 +15,7 @@ selectOpenTodos = `SELECT id, text, owner FROM todo WHERE NOT done AND chat_id = $1 AND list = $2 ORDER BY ts ASC` // parameters: 1 = todo ID // returns: n/a - markTodoDone = `UPDATE todo SET done = true WHERE id = $1` + markTodoDone = `UPDATE todo SET done = true WHERE id = $1 AND NOT done` ) // Data access object for todo lists @@ -82,3 +82,64 @@ return 0, errors.New("prepared statement not found") } } + +type OpenTodo struct { + // to be used as callback data + ID uint64 + Text string + Owner string +} + +func (td Todo) GetOpenTodos(list string) ([]OpenTodo, error) { + if stmt, ok := td.db.prepared[selectOpenTodos]; ok { + rows, err := stmt.Query(td.chatID, "") + + if err != nil { + log.Println("Couldn't query open todos:", err) + return nil, err + } + + defer rows.Close() + + items := make([]OpenTodo, 0, 16) + + for rows.Next() { + todo := OpenTodo{} + err = rows.Scan(&todo.ID, &todo.Text, &todo.Owner) + + if err != nil { + log.Println("Error during scan (todo):", err) + return nil, err + } + + items = append(items, todo) + } + + return items, nil + } else { + log.Println("Couldn't find prepared statement for", selectOpenTodos) + return nil, errors.New("prepared statement not found") + } +} + +// Mark todo item as done. Returns number of affected items (0 if the item was already marked as done) +func (td Todo) MarkTodoDone(id uint64) (int, error) { + if stmt, ok := td.db.prepared[markTodoDone]; ok { + result, err := stmt.Exec(id) + + if err != nil { + log.Println("Couldn't mark", id, "as done:", err) + return 0, err + } + + // if 0 rows were affected, it's a duplicate action (ok). More rows can't be affected + // because of the primary key constraint. + + aff, err := result.RowsAffected() + + return int(aff), err + } else { + log.Println("Couldn't find prepared statement for", markTodoDone) + return 0, errors.New("prepared statement not found") + } +}