diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..19cbc48
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+public
+vendor
+streaking
+nohup.out
+log
+pid
+tmp
diff --git a/Gopkg.lock b/Gopkg.lock
new file mode 100644
index 0000000..42c3c25
--- /dev/null
+++ b/Gopkg.lock
@@ -0,0 +1,165 @@
+# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
+
+
+[[projects]]
+ name = "cloud.google.com/go"
+ packages = ["compute/metadata"]
+ revision = "056a55f54a6cc77b440b31a56a5e7c3982d32811"
+ version = "v0.22.0"
+
+[[projects]]
+ name = "github.com/dgrijalva/jwt-go"
+ packages = ["."]
+ revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
+ version = "v3.2.0"
+
+[[projects]]
+ name = "github.com/go-sql-driver/mysql"
+ packages = ["."]
+ revision = "a0583e0143b1624142adab07e0e97fe106d99561"
+ version = "v1.3"
+
+[[projects]]
+ name = "github.com/golang/protobuf"
+ packages = ["proto"]
+ revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
+ version = "v1.1.0"
+
+[[projects]]
+ name = "github.com/gorilla/context"
+ packages = ["."]
+ revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
+ version = "v1.1"
+
+[[projects]]
+ name = "github.com/gorilla/securecookie"
+ packages = ["."]
+ revision = "e59506cc896acb7f7bf732d4fdf5e25f7ccd8983"
+ version = "v1.1.1"
+
+[[projects]]
+ name = "github.com/gorilla/sessions"
+ packages = ["."]
+ revision = "ca9ada44574153444b00d3fd9c8559e4cc95f896"
+ version = "v1.1"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/jmoiron/sqlx"
+ packages = [
+ ".",
+ "reflectx"
+ ]
+ revision = "2aeb6a910c2b94f2d5eb53d9895d80e27264ec41"
+
+[[projects]]
+ name = "github.com/labstack/echo"
+ packages = [
+ ".",
+ "middleware"
+ ]
+ revision = "6d227dfea4d2e52cb76856120b3c17f758139b4e"
+ version = "3.3.5"
+
+[[projects]]
+ name = "github.com/labstack/echo-contrib"
+ packages = ["session"]
+ revision = "7d9d9632a4aadf9b026436e43d5f96eca2d895a6"
+ version = "0.5.2"
+
+[[projects]]
+ name = "github.com/labstack/gommon"
+ packages = [
+ "bytes",
+ "color",
+ "log",
+ "random"
+ ]
+ revision = "588f4e8bddc6cb45c27b448e925c7fd6a5545434"
+ version = "0.2.5"
+
+[[projects]]
+ name = "github.com/mattn/go-colorable"
+ packages = ["."]
+ revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
+ version = "v0.0.9"
+
+[[projects]]
+ name = "github.com/mattn/go-isatty"
+ packages = ["."]
+ revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
+ version = "v0.0.3"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/valyala/bytebufferpool"
+ packages = ["."]
+ revision = "e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/valyala/fasttemplate"
+ packages = ["."]
+ revision = "dcecefd839c4193db0d35b88ec65b4c12d360ab0"
+
+[[projects]]
+ branch = "master"
+ name = "golang.org/x/crypto"
+ packages = [
+ "acme",
+ "acme/autocert"
+ ]
+ revision = "2d027ae1dddd4694d54f7a8b6cbe78dca8720226"
+
+[[projects]]
+ branch = "master"
+ name = "golang.org/x/net"
+ packages = [
+ "context",
+ "context/ctxhttp"
+ ]
+ revision = "2491c5de3490fced2f6cff376127c667efeed857"
+
+[[projects]]
+ branch = "master"
+ name = "golang.org/x/oauth2"
+ packages = [
+ ".",
+ "facebook",
+ "github",
+ "google",
+ "internal",
+ "jws",
+ "jwt"
+ ]
+ revision = "cdc340f7c179dbbfa4afd43b7614e8fcadde4269"
+
+[[projects]]
+ branch = "master"
+ name = "golang.org/x/sys"
+ packages = ["unix"]
+ revision = "d0faeb539838e250bd0a9db4182d48d4a1915181"
+
+[[projects]]
+ name = "google.golang.org/appengine"
+ packages = [
+ ".",
+ "internal",
+ "internal/app_identity",
+ "internal/base",
+ "internal/datastore",
+ "internal/log",
+ "internal/modules",
+ "internal/remote_api",
+ "internal/urlfetch",
+ "urlfetch"
+ ]
+ revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
+ version = "v1.0.0"
+
+[solve-meta]
+ analyzer-name = "dep"
+ analyzer-version = 1
+ inputs-digest = "7c8ab5114679d6a2cf7c42a21489869cca06a450557467f8e67c2e7d16a66d8d"
+ solver-name = "gps-cdcl"
+ solver-version = 1
diff --git a/Gopkg.toml b/Gopkg.toml
new file mode 100644
index 0000000..815656c
--- /dev/null
+++ b/Gopkg.toml
@@ -0,0 +1,54 @@
+# Gopkg.toml example
+#
+# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
+# for detailed Gopkg.toml documentation.
+#
+# required = ["github.com/user/thing/cmd/thing"]
+# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
+#
+# [[constraint]]
+# name = "github.com/user/project"
+# version = "1.0.0"
+#
+# [[constraint]]
+# name = "github.com/user/project2"
+# branch = "dev"
+# source = "github.com/myfork/project2"
+#
+# [[override]]
+# name = "github.com/x/y"
+# version = "2.4.0"
+#
+# [prune]
+# non-go = false
+# go-tests = true
+# unused-packages = true
+
+
+[[constraint]]
+ name = "github.com/go-sql-driver/mysql"
+ version = "1.3.0"
+
+[[constraint]]
+ name = "github.com/gorilla/sessions"
+ version = "1.1.0"
+
+[[constraint]]
+ branch = "master"
+ name = "github.com/jmoiron/sqlx"
+
+[[constraint]]
+ name = "github.com/labstack/echo"
+ version = "3.3.5"
+
+[[constraint]]
+ name = "github.com/labstack/echo-contrib"
+ version = "0.5.2"
+
+[[constraint]]
+ branch = "master"
+ name = "golang.org/x/oauth2"
+
+[prune]
+ go-tests = true
+ unused-packages = true
diff --git a/api.go b/api.go
new file mode 100644
index 0000000..759c21a
--- /dev/null
+++ b/api.go
@@ -0,0 +1,192 @@
+package main
+
+import (
+ "bh/streaking/models"
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gorilla/sessions"
+ "github.com/jmoiron/sqlx"
+ "github.com/labstack/echo"
+ "github.com/labstack/echo-contrib/session"
+)
+
+type handler struct {
+ db *sqlx.DB
+}
+
+type successResponse struct {
+ Success bool `json:"success"`
+}
+
+// GET /me
+func (h *handler) getUser(c echo.Context) error {
+ um := models.Users{DB: h.db}
+ gm := models.Goals{DB: h.db}
+ sm := models.Streaks{DB: h.db}
+
+ sess, _ := session.Get("session", c)
+ sess.Options = &sessions.Options{
+ Path: "/",
+ MaxAge: 86400 * 7,
+ HttpOnly: true,
+ }
+ uid := sess.Values["user"]
+
+ var us []models.User
+ var gs []models.Goal
+ var ss []models.Streak
+ var err error
+
+ if us, err = um.Read(map[string]interface{}{"id": uid}); err != nil {
+ fmt.Println(err)
+ return err
+ }
+ if gs, err = gm.Read(map[string]interface{}{"user_id": uid}); err != nil {
+ fmt.Println(err)
+ return err
+ }
+ if ss, err = sm.Read(map[string]interface{}{"user_id": uid}); err != nil {
+ fmt.Println(err)
+ return err
+ }
+
+ return c.JSON(http.StatusOK, &struct {
+ Users models.User `json:"user"`
+ Goals []models.Goal `json:"goals"`
+ Streaks []models.Streak `json:"streaks"`
+ }{us[0], gs, ss})
+}
+
+// POST /me/goals
+func (h *handler) createGoal(c echo.Context) error {
+ g := models.Goal{}
+ if err := c.Bind(&g); err != nil {
+ return err
+ }
+
+ sess, _ := session.Get("session", c)
+ sess.Options = &sessions.Options{
+ Path: "/",
+ MaxAge: 86400 * 7,
+ HttpOnly: true,
+ }
+ uid := sess.Values["user"].(int)
+ g.UserID = uid
+
+ gm := models.Goals{DB: h.db}
+ if err := gm.Create(g); err != nil {
+ return err
+ }
+
+ return c.JSON(http.StatusOK, successResponse{true})
+}
+
+// POST /me/streaks
+func (h *handler) createStreak(c echo.Context) error {
+ s := models.Streak{}
+ if err := c.Bind(&s); err != nil {
+ return err
+ }
+
+ sm := models.Streaks{DB: h.db}
+
+ if err := sm.Create(s); err != nil {
+ return err
+ }
+
+ return c.JSON(http.StatusOK, successResponse{true})
+}
+
+// PUT /me/goals/:goal_id
+func (h *handler) updateGoal(c echo.Context) error {
+ g := models.Goal{}
+ if err := c.Bind(&g); err != nil {
+ return err
+ }
+
+ sess, _ := session.Get("session", c)
+ sess.Options = &sessions.Options{
+ Path: "/",
+ MaxAge: 86400 * 7,
+ HttpOnly: true,
+ }
+ uid := sess.Values["user"].(int)
+ gid, err := strconv.Atoi(c.Param("goal_id"))
+
+ if err != nil {
+ return err
+ }
+ g.UserID = uid
+
+ gm := models.Goals{DB: h.db}
+ if err := gm.Update(gid, g); err != nil {
+ return err
+ }
+
+ return c.JSON(http.StatusOK, successResponse{true})
+}
+
+// PUT /me/streaks/:streak_id
+func (h *handler) updateStreak(c echo.Context) error {
+ s := models.Streak{}
+ if err := c.Bind(&s); err != nil {
+ return err
+ }
+
+ sid, err := strconv.Atoi(c.Param("streak_id"))
+ if err != nil {
+ return err
+ }
+
+ sm := models.Streaks{DB: h.db}
+ if err := sm.Update(sid, s); err != nil {
+ fmt.Println(err)
+ return err
+ }
+
+ return c.JSON(http.StatusOK, successResponse{true})
+}
+
+// DELTE /users/:user_id
+func (h *handler) deleteUser(c echo.Context) error {
+ sess, _ := session.Get("session", c)
+ sess.Options = &sessions.Options{
+ Path: "/",
+ MaxAge: 86400 * 7,
+ HttpOnly: true,
+ }
+ uid := sess.Values["user"].(int)
+
+ um := models.Users{DB: h.db}
+ um.Delete(uid)
+
+ return c.JSON(http.StatusOK, successResponse{true})
+}
+
+// DELETE /me/goals/:goal_id
+func (h *handler) deleteGoal(c echo.Context) error {
+ gid, err := strconv.Atoi(c.Param("goal_id"))
+ if err != nil {
+ return err
+ }
+
+ gm := models.Goals{DB: h.db}
+ gm.Delete(gid)
+
+ return c.JSON(http.StatusOK, successResponse{true})
+}
+
+// DELETE /me/streaks/:streak_id
+func (h *handler) deleteStreak(c echo.Context) error {
+ sid, err := strconv.Atoi(c.Param("streak_id"))
+ if err != nil {
+ return err
+ }
+
+ sm := models.Streaks{DB: h.db}
+ sm.Delete(sid)
+
+ return c.JSON(http.StatusOK, successResponse{true})
+}
diff --git a/auth/facebook/handlers.go b/auth/facebook/handlers.go
new file mode 100644
index 0000000..749a56c
--- /dev/null
+++ b/auth/facebook/handlers.go
@@ -0,0 +1,64 @@
+package facebook
+
+import (
+ "bh/streaking/auth"
+ "bh/streaking/models"
+ "encoding/json"
+ "log"
+ "os"
+
+ "github.com/jmoiron/sqlx"
+
+ "github.com/labstack/echo"
+ "golang.org/x/oauth2"
+ "golang.org/x/oauth2/facebook"
+)
+
+var settings = auth.Settings{
+ OauthConf: &oauth2.Config{
+ ClientID: "226042608152816",
+ ClientSecret: "617e257795853d28e562ebecd14e400f",
+ RedirectURL: os.Getenv("BASE_URL") + "/callback/facebook",
+ Scopes: []string{"public_profile", "email"},
+ Endpoint: facebook.Endpoint,
+ },
+ OauthStateString: "thisshouldberandom",
+ BaseURL: "https://graph.facebook.com/me?access_token=",
+ GetUser: getUser,
+}
+
+func getUser(res string) models.User {
+ temp := new(struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Email string `json:"email"`
+ })
+ if err := json.Unmarshal([]byte(res), &temp); err != nil {
+ log.Fatal(err)
+ }
+
+ if temp.Name == "" {
+ temp.Name = "NO_NAME_GIVEN"
+ }
+ if temp.Email == "" {
+ temp.Email = "NO_EMAIL_GIVEN"
+ }
+
+ return models.User{
+ Name: temp.Name,
+ Email: temp.Email,
+ Source: "FACEBOOK",
+ ExternalID: temp.ID,
+ }
+}
+
+// HandleLogin - handle facebook login
+func HandleLogin() echo.HandlerFunc {
+ return auth.BuildLoginHandler(settings)
+}
+
+// HandleCallback - handle facebook callback
+func HandleCallback(db *sqlx.DB) echo.HandlerFunc {
+ settings.DB = db
+ return auth.BuildCallbackHandler(settings)
+}
diff --git a/auth/github/handlers.go b/auth/github/handlers.go
new file mode 100644
index 0000000..7d5c5b2
--- /dev/null
+++ b/auth/github/handlers.go
@@ -0,0 +1,65 @@
+package github
+
+import (
+ "bh/streaking/auth"
+ "bh/streaking/models"
+ "encoding/json"
+ "log"
+ "os"
+ "strconv"
+
+ "github.com/jmoiron/sqlx"
+
+ "github.com/labstack/echo"
+ "golang.org/x/oauth2"
+ "golang.org/x/oauth2/github"
+)
+
+var settings = auth.Settings{
+ OauthConf: &oauth2.Config{
+ ClientID: "27664cbca31fbcd886db",
+ ClientSecret: "9535df4affb9bd25ec44f6d00a32480a4fd9a078",
+ RedirectURL: os.Getenv("BASE_URL") + "/callback/github",
+ Scopes: []string{"public_profile"},
+ Endpoint: github.Endpoint,
+ },
+ OauthStateString: "thisshouldberandom",
+ BaseURL: "https://api.github.com/user?access_token=",
+ GetUser: getUser,
+}
+
+func getUser(res string) models.User {
+ temp := new(struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ Email string `json:"email"`
+ })
+ if err := json.Unmarshal([]byte(res), &temp); err != nil {
+ log.Fatal(err)
+ }
+
+ if temp.Name == "" {
+ temp.Name = "NO_NAME_GIVEN"
+ }
+ if temp.Email == "" {
+ temp.Email = "NO_EMAIL_GIVEN"
+ }
+
+ return models.User{
+ Name: temp.Name,
+ Email: temp.Email,
+ Source: "GITHUB",
+ ExternalID: strconv.Itoa(temp.ID),
+ }
+}
+
+// HandleLogin - handle facebook login
+func HandleLogin() echo.HandlerFunc {
+ return auth.BuildLoginHandler(settings)
+}
+
+// HandleCallback - handle facebook callback
+func HandleCallback(db *sqlx.DB) echo.HandlerFunc {
+ settings.DB = db
+ return auth.BuildCallbackHandler(settings)
+}
diff --git a/auth/google/handlers.go b/auth/google/handlers.go
new file mode 100644
index 0000000..567e341
--- /dev/null
+++ b/auth/google/handlers.go
@@ -0,0 +1,64 @@
+package google
+
+import (
+ "bh/streaking/auth"
+ "bh/streaking/models"
+ "encoding/json"
+ "log"
+ "os"
+
+ "github.com/jmoiron/sqlx"
+
+ "github.com/labstack/echo"
+ "golang.org/x/oauth2"
+ "golang.org/x/oauth2/google"
+)
+
+var settings = auth.Settings{
+ OauthConf: &oauth2.Config{
+ ClientID: "443546063879-ocoa94kseo25apobl1dol3kqi2vkaqq1.apps.googleusercontent.com",
+ ClientSecret: "WtR0ABtcDhWwfVcV3FR14SUI",
+ RedirectURL: os.Getenv("BASE_URL") + "/callback/google",
+ Scopes: []string{"profile", "email"},
+ Endpoint: google.Endpoint,
+ },
+ OauthStateString: "thisshouldberandom",
+ BaseURL: "https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=",
+ GetUser: getUser,
+}
+
+func getUser(res string) models.User {
+ temp := new(struct {
+ ID string `json:"sub"`
+ Name string `json:"name"`
+ Email string `json:"email"`
+ })
+ if err := json.Unmarshal([]byte(res), &temp); err != nil {
+ log.Fatal(err)
+ }
+
+ if temp.Name == "" {
+ temp.Name = "NO_NAME_GIVEN"
+ }
+ if temp.Email == "" {
+ temp.Email = "NO_EMAIL_GIVEN"
+ }
+
+ return models.User{
+ Name: temp.Name,
+ Email: temp.Email,
+ Source: "GOOGLE",
+ ExternalID: temp.ID,
+ }
+}
+
+// HandleLogin - handle facebook login
+func HandleLogin() echo.HandlerFunc {
+ return auth.BuildLoginHandler(settings)
+}
+
+// HandleCallback - handle facebook callback
+func HandleCallback(db *sqlx.DB) echo.HandlerFunc {
+ settings.DB = db
+ return auth.BuildCallbackHandler(settings)
+}
diff --git a/auth/init.go b/auth/init.go
new file mode 100644
index 0000000..d2f1143
--- /dev/null
+++ b/auth/init.go
@@ -0,0 +1,180 @@
+package auth
+
+import (
+ "bh/streaking/models"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "net/url"
+ "strings"
+
+ "github.com/jmoiron/sqlx"
+
+ "github.com/gorilla/sessions"
+ "github.com/labstack/echo"
+ "github.com/labstack/echo-contrib/session"
+ "golang.org/x/oauth2"
+)
+
+type errorResponse struct {
+ Message bool `json:"success"`
+}
+
+var skipRoutes = map[string]bool{
+ "/login": true,
+ "/logout": true,
+ "/login/facebook": true,
+ "/login/github": true,
+ "/login/google": true,
+ "/callback/facebook": true,
+ "/callback/github": true,
+ "/callback/google": true,
+ "/logo.png": true,
+ "/logo_side.png": true,
+ "/favicon.ico": true,
+}
+
+// Settings - settings for various login schemes
+type Settings struct {
+ OauthConf *oauth2.Config
+ OauthStateString string
+ BaseURL string
+ DB *sqlx.DB
+ GetUser func(string) models.User
+}
+
+// BuildLoginHandler build login handler given oauth conf and oauth state string
+func BuildLoginHandler(settings Settings) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ URL, err := url.Parse(settings.OauthConf.Endpoint.AuthURL)
+ if err != nil {
+ log.Fatal("Parse: ", err)
+ }
+ parameters := url.Values{}
+ parameters.Add("client_id", settings.OauthConf.ClientID)
+ parameters.Add("redirect_uri", settings.OauthConf.RedirectURL)
+ parameters.Add("scope", strings.Join(settings.OauthConf.Scopes, " "))
+ parameters.Add("response_type", "code")
+ parameters.Add("invalid", "offline")
+ parameters.Add("state", settings.OauthStateString)
+ URL.RawQuery = parameters.Encode()
+ url := URL.String()
+ return c.Redirect(http.StatusTemporaryRedirect, url)
+ }
+}
+
+// BuildCallbackHandler build callback handler given oauth conf and oauth state string
+func BuildCallbackHandler(settings Settings) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ // pull state and code params out of request
+ query := new(struct {
+ State string `query:"state"`
+ Code string `query:"code"`
+ })
+ if err := c.Bind(query); err != nil {
+ return err
+ }
+
+ // ensure state matches what we set, to prevent phishing attacks
+ if query.State != settings.OauthStateString {
+ fmt.Printf("invalid oauth state, expected '%s', got '%s'\n", settings.OauthStateString, query.State)
+ return c.Redirect(http.StatusTemporaryRedirect, "/")
+ }
+
+ // exchange code for access token (oauth step)
+ token, err := settings.OauthConf.Exchange(oauth2.NoContext, query.Code)
+ if err != nil {
+ fmt.Printf("oauthConf.Exchange() failed with '%s'\n", err)
+ return c.Redirect(http.StatusTemporaryRedirect, "/")
+ }
+
+ // grab user info with said access token
+ resp, err := http.Get(settings.BaseURL + url.QueryEscape(token.AccessToken))
+ if err != nil {
+ fmt.Printf("Get: %s\n", err)
+ return c.Redirect(http.StatusTemporaryRedirect, "/")
+ }
+ defer resp.Body.Close()
+ response, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ fmt.Printf("ReadAll: %s\n", err)
+ return c.Redirect(http.StatusTemporaryRedirect, "/")
+ }
+
+ // parse user object out of user info response and insert/get from db
+ u := settings.GetUser(string(response))
+ um := models.Users{DB: settings.DB}
+ if err := um.Create(u); err != nil {
+ log.Fatal(err)
+ }
+ users, err := um.Read(map[string]interface{}{
+ "name": u.Name,
+ "email": u.Email,
+ "source": u.Source,
+ "external_id": u.ExternalID,
+ })
+ if err != nil {
+ return err
+ }
+ if len(users) != 1 {
+ return fmt.Errorf("invalid user records: %v", users)
+ }
+ user := users[0]
+ fmt.Println(user)
+
+ // set inserted user id in session
+ sess, _ := session.Get("session", c)
+ sess.Options = &sessions.Options{
+ Path: "/",
+ MaxAge: 86400 * 7,
+ HttpOnly: true,
+ }
+ sess.Values["user"] = user.ID
+ sess.Save(c.Request(), c.Response())
+
+ // should redirect to app url
+ return c.Redirect(http.StatusFound, "/")
+ }
+}
+
+// CheckLogIn - middleware to ensure user is logged in
+func CheckLogIn(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ // skip check for some routes
+ if skipRoutes[c.Request().URL.Path] == true {
+ return next(c)
+ }
+
+ sess, _ := session.Get("session", c)
+ sess.Options = &sessions.Options{
+ Path: "/",
+ MaxAge: 86400 * 7,
+ HttpOnly: true,
+ }
+ user := sess.Values["user"]
+
+ if user == nil {
+ if c.Request().URL.Path == "/" {
+ return c.Redirect(http.StatusFound, "/login")
+ }
+ return echo.NewHTTPError(http.StatusUnauthorized, "please log in pls")
+ }
+
+ return next(c)
+ }
+}
+
+// Logout - log user out
+func Logout(c echo.Context) error {
+ sess, _ := session.Get("session", c)
+ sess.Options = &sessions.Options{
+ Path: "/",
+ MaxAge: 86400 * 7,
+ HttpOnly: true,
+ }
+ sess.Values["user"] = nil
+ sess.Save(c.Request(), c.Response())
+
+ return c.Redirect(http.StatusFound, "/login")
+}
diff --git a/db.sql b/db.sql
deleted file mode 100644
index 3d40aca..0000000
--- a/db.sql
+++ /dev/null
@@ -1 +0,0 @@
--- there will be things here
diff --git a/db/clean.sh b/db/clean.sh
new file mode 100755
index 0000000..b653ec0
--- /dev/null
+++ b/db/clean.sh
@@ -0,0 +1,4 @@
+#! /bin/bash
+
+mysql -u streaking -pstreaking < migrate.sql
+mysql -u streaking -pstreaking < seed.sql
\ No newline at end of file
diff --git a/db/init.sh b/db/init.sh
new file mode 100755
index 0000000..db8a06d
--- /dev/null
+++ b/db/init.sh
@@ -0,0 +1,5 @@
+#! /bin/bash
+
+mysql -u root -p < init.sql
+mysql -u streaking -pstreaking < migrate.sql
+mysql -u streaking -pstreaking < seed.sql
\ No newline at end of file
diff --git a/db/init.sql b/db/init.sql
new file mode 100644
index 0000000..02ab54e
--- /dev/null
+++ b/db/init.sql
@@ -0,0 +1,4 @@
+CREATE DATABASE IF NOT EXISTS streaking;
+DROP USER IF EXISTS streaking;
+CREATE USER 'streaking'@'%' IDENTIFIED BY 'streaking';
+GRANT ALL ON `streaking`.* TO 'streaking'@'%' IDENTIFIED BY 'streaking';
diff --git a/db/migrate.sql b/db/migrate.sql
new file mode 100644
index 0000000..440ac18
--- /dev/null
+++ b/db/migrate.sql
@@ -0,0 +1,94 @@
+/*
+ Streaking - productivity/etc streak tracking
+ Brent Hamilton
+
+