diff --git a/main.go b/main.go
new file mode 100644
index 0000000..46b9fc9
--- /dev/null
+++ b/main.go
@@ -0,0 +1,254 @@
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "html/template"
+ "log"
+ "net/http"
+ "time"
+
+ bolt "go.etcd.io/bbolt"
+)
+
+const BucketKeyMoments = "moments"
+
+var indexPageTemplate = `
+
+
+
+
+ Add moments of your day
+
+
+
+
+
+
+
Remember moments: N minutes since your last memo
+
+
+
+
+{{ if .Moments }}
+
+
+ {{ range .Moments }}
+
+ {{ end }}
+
+
+
+{{ end }}
+
+
+ `
+
+// Moment is the main information this servers remembers
+type Moment struct {
+ Key string
+ Time time.Time
+ Memo string
+}
+
+func main() {
+ fmt.Println("Starting tracking backend server")
+
+ path := "./moments.db"
+ db, err := bolt.Open(path, 0666, nil)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ defer db.Close()
+
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ moments, err := loadMoments(db, time.Now().Format("2006-01-02"))
+ if err != nil {
+ log.Println(err)
+ }
+
+ type indexPageInfo struct {
+ Moments []Moment
+ LastMomentSeconds int64
+ }
+
+ indexPage := indexPageInfo{Moments: moments}
+
+ if len(moments) > 0 {
+ a := moments
+ for i := len(a)/2-1; i >= 0; i-- {
+ opp := len(a)-1-i
+ a[i], a[opp] = a[opp], a[i]
+ }
+ lastMoment := moments[0]
+ indexPage.LastMomentSeconds = lastMoment.Time.Unix()
+ }
+
+ t, err := template.New("index").Parse(indexPageTemplate)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ err = t.Execute(w, indexPage)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ })
+
+ http.HandleFunc("/moment", func(w http.ResponseWriter, r *http.Request) {
+ defer r.Body.Close()
+
+ if r.Method == http.MethodGet {
+ moments, err := loadMoments(db, "")
+ if err != nil {
+ log.Println(err)
+ }
+
+ err = json.NewEncoder(w).Encode(&moments)
+ if err != nil {
+ log.Println(err)
+ }
+ } else if r.Method == http.MethodPost {
+ // save input values
+ err := r.ParseForm()
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ memo := r.FormValue("memo")
+ timestamp := time.Now()
+ err = saveMemo(db, timestamp, memo)
+ if err != nil {
+ log.Println(err)
+ }
+ http.Redirect(w, r, "/", http.StatusFound)
+ } else {
+ http.Error(w, "Method Not Allowed", 405)
+ return
+ }
+ })
+
+ log.Fatal(http.ListenAndServe(":8096", nil))
+}
+
+func loadMoments(db *bolt.DB, today string) ([]Moment, error) {
+ var moments []Moment
+
+ err := db.View(func(tx *bolt.Tx) error {
+ // Assume bucket exists and has keys
+ b := tx.Bucket([]byte(BucketKeyMoments))
+
+ if b == nil {
+ // no bucket found, so moments should be empty
+ return nil
+ }
+
+ c := b.Cursor()
+
+ prefix := []byte(today)
+ for k, v := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, v = c.Next() {
+ var moment Moment
+ err := json.Unmarshal(v, &moment)
+ if err != nil {
+ return err
+ }
+ moments = append(moments, moment)
+ }
+
+ return nil
+ })
+
+ return moments, err
+}
+
+func saveMemo(db *bolt.DB, timestamp time.Time, memo string) error {
+ err := db.Update(func(tx *bolt.Tx) error {
+ bucket, err := tx.CreateBucketIfNotExists([]byte(BucketKeyMoments))
+ if err != nil {
+ return err
+ }
+
+ key := timestamp.Format(time.RFC3339)
+
+ m := Moment{
+ Key: key,
+ Memo: memo,
+ Time: timestamp,
+ }
+
+ buf, err := json.Marshal(m)
+ if err != nil {
+ return err
+ }
+
+ return bucket.Put([]byte(m.Key), buf)
+ })
+
+ return err
+}