Todo list with Slack
introduction
This side project is a web application of todo list. It has the following components
- Go backend
- Polymer frontend
- Postgres database
- Slack slash command
A working example is hosted on heroku and below is a screenshot. The source code is here on Github.
The basic data types are Ticket and Todo, and each Ticket may contain multiple Todos.
The frontend only displays the data retrieved from the URL /data
.
To modify the database, one types commands in Slack chatroom, for example, to add ticket and todos
/ticket/add id:errand, detail:get groceries
/todo/add ticket_id:errand, item:apples
/todo/add ticket_id:errand, item:oranges
To end ticket and todo,
/ticket/end id:errand
/todo/end ticket_id:errand, idx:1
In this post, I will review a few tricks I learned from this project
HTTP error handling and authentication
In go, a type implements an interface by implementing its methods. This feature can be used to handle HTTP error uniformly, instead of writing error handling code for each HTTP handler function. For this purpose, note that any object satisfies the http.Handler interface by implementing the function
func ServeHTTP(w ResponseWriter, r *Request)
In my code, each HTTP handler function returns its error, as follows
func (app *application) AddTicket(w http.ResponseWriter, req *http.Request) error
func (app *application) UpdateTicket(w http.ResponseWriter, req *http.Request) error
func (app *application) EndTicket(w http.ResponseWriter, req *http.Request) error
func (app *application) DeleteTicket(w http.ResponseWriter, req *http.Request) error
Here I also make them methods of the application
so that they have access to the database.
The existence of return value disqualify them as http.Handler interface.
And a decorator is used to make them http.Handler as well as handle the error.
// logHandler decorates the http handlers with logging
type logHandler func(http.ResponseWriter, *http.Request) error
func (fn logHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if err := fn(w, req); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
Some of my HTTP handlers also require authentication. My strategy is again to decorate the function.
// auth decorates the logHandlers with authentication
func auth(fn logHandler) logHandler {
authf := func(w http.ResponseWriter, req *http.Request) error {
if req.Header.Get("Token") != os.Getenv("Token") {
return fmt.Errorf("Authentication failed.")
}
return fn(w, req)
}
return logHandler(authf)
}
To register the handlers, I use gorilla/mux
router := mux.NewRouter().StrictSlash(true)
router.Handle("/data", logHandler(app.Data)).Methods("GET")
router.Handle("/slack", logHandler(app.Slack)).Methods("POST")
router.Handle("/todo/delete", auth(logHandler(app.DeleteTodo))).Methods("DELETE")
Note here logHandler(app.Data)
is type conversion instead of function call whereas auth(...)
is a function call.
heroku deployment
Heroku allows deployment of up to 5 free web applications and I find the service much nicer than pythonanywhere. Automatic deployment can be set up such that all you need to do is to push the new code to github (and hope tests pass).
Heroku does not support sqlite3 but provides free postgres service. The registration and deployment setup are quite straightforward. You can read the details from their documents.
One tricky thing I found is about supporting multiple main.go
.
In my cmd
folder there are two subfolders with main.go
: one for the server and the other for database initialization.
What worked for me is to run the following command
godep save ./cmd/...
different front-end behaviors of different browsers on different platforms
For my (super light-weight) web application, I hit into two browser/platform-related issues.
One is that flex does not work for safari whereas chrome and firefox work fine. It seems the issue is due to safari and you can read more by googling “flex not working in safari”. The fix is as follows.
.flexchild {
@apply(--layout-flex);
-webkit-flex: 1 0 200px; /* hack for safari */
}
Here the @apply(--layout-flex);
is specific to Polymer iron-flex-layout
.
The other issue is that html symbols are displayed without color in safari on iphone and ipad.
And the fix is to add a special character after the symbol, i.e., change \u2714
to \u2714\uFE0E
.