func hapiConversation(w http.ResponseWriter, r *http.Request,
	ps httprouter.Params) {
	if os.Getenv("ABOT_ENV") != "test" {
		if !isAdmin(w, r) {
			return
		}
		if !isLoggedIn(w, r) {
			return
		}
	}
	var msgs []struct {
		Sentence  string
		AbotSent  bool
		CreatedAt time.Time
	}
	var name, location string
	var signedUp time.Time
	uid := ps.ByName("uid")
	fid := ps.ByName("fid")
	fidT := ps.ByName("fidt")
	offset := ps.ByName("off")
	if uid != "0" {
		q := `WITH t AS (
			SELECT sentence, abotsent, createdat FROM messages
			WHERE userid=$1
			ORDER BY createdat DESC LIMIT 30 OFFSET $2
		      ) SELECT * FROM t ORDER BY createdat ASC`
		if err := db.Select(&msgs, q, uid, offset); err != nil {
			writeErrorInternal(w, err)
			return
		}
		q = `SELECT createdat FROM users WHERE id=$1`
		if err := db.Get(&signedUp, q, uid); err != nil {
			writeErrorInternal(w, err)
			return
		}
		var val []byte
		q = `SELECT value FROM states WHERE userid=$1 AND key=$2`
		err := db.Get(&val, q, uid, prefs.Name)
		if err != sql.ErrNoRows {
			writeErrorInternal(w, err)
			if err = json.Unmarshal(val, &name); err != nil {
				return
			}
		}
		err = db.Get(&val, q, uid, prefs.Location)
		if err != sql.ErrNoRows {
			writeErrorInternal(w, err)
			if err = json.Unmarshal(val, &location); err != nil {
				return
			}
		}
	} else {
		q := `WITH t AS (
			SELECT sentence, abotsent, createdat FROM messages
		        WHERE flexid=$1 AND flexidtype=$2
		        ORDER BY createdat DESC LIMIT 30 OFFSET $3
		      ) SELECT * FROM t ORDER BY createdat ASC`
		if err := db.Select(&msgs, q, fid, fidT, offset); err != nil {
			writeErrorInternal(w, err)
			return
		}
		q = `SELECT createdat FROM messages
		     WHERE flexid=$1 AND flexidtype=$2 ORDER BY createdat ASC`
		if err := db.Get(&signedUp, q, fid, fidT); err != nil {
			writeErrorInternal(w, err)
			return
		}
		var val []byte
		q = `SELECT value FROM states
		     WHERE flexid=$1 AND flexidtype=$2 AND key=$3`
		err := db.Get(&val, q, fid, fidT, prefs.Name)
		if err != sql.ErrNoRows {
			writeErrorInternal(w, err)
			if err = json.Unmarshal(val, &name); err != nil {
				return
			}
		}
		err = db.Get(&val, q, fid, fidT, prefs.Location)
		if err != nil && err != sql.ErrNoRows {
			writeErrorInternal(w, err)
			if err = json.Unmarshal(val, &location); err != nil {
				return
			}
		}
	}
	resp := struct {
		Name      string
		CreatedAt time.Time
		Location  string
		Messages  []struct {
			Sentence  string
			AbotSent  bool
			CreatedAt time.Time
		}
	}{
		Name:      name,
		CreatedAt: signedUp,
		Location:  location,
		Messages:  msgs,
	}
	byt, err := json.Marshal(resp)
	if err != nil {
		writeErrorInternal(w, err)
		return
	}
	_, err = w.Write(byt)
	if err != nil {
		log.Info("failed to write response.", err)
	}
}
hapiConversation returns a conversation for a specific user or flexID.
package core
import (
	"crypto/hmac"
	"crypto/sha512"
	"database/sql"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"html/template"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"
	"golang.org/x/crypto/bcrypt"
	w "golang.org/x/net/websocket"
	"github.com/itsabot/abot/core/log"
	"github.com/itsabot/abot/core/websocket"
	"github.com/itsabot/abot/shared/datatypes"
	"github.com/itsabot/abot/shared/interface/emailsender"
	"github.com/itsabot/abot/shared/prefs"
	"github.com/julienschmidt/httprouter"
)
var tmplLayout *template.Template
var ws = websocket.NewAtomicWebSocketSet()
var ErrInvalidUserPass = errors.New("Invalid username/password combination")
var regexNum = regexp.MustCompile(`\D+`)
func newRouter() *httprouter.Router {
	router := httprouter.New()
	router.ServeFiles("/public/*filepath", http.Dir("public"))
	if os.Getenv("ABOT_ENV") != "production" {
		initCMDGroup(router)
	}
	router.HandlerFunc("GET", "/", hIndex)
	router.HandlerFunc("POST", "/", hMain)
	router.HandlerFunc("OPTIONS", "/", hOptions)
	router.NotFound = http.HandlerFunc(hIndex)
	router.HandlerFunc("POST", "/api/login.json", hapiLoginSubmit)
	router.HandlerFunc("POST", "/api/logout.json", hapiLogoutSubmit)
	router.HandlerFunc("POST", "/api/signup.json", hapiSignupSubmit)
	router.HandlerFunc("POST", "/api/forgot_password.json", hapiForgotPasswordSubmit)
	router.HandlerFunc("POST", "/api/reset_password.json", hapiResetPasswordSubmit)
	router.HandlerFunc("GET", "/api/admin_exists.json", hapiAdminExists)
	router.HandlerFunc("GET", "/api/user/profile.json", hapiProfile)
	router.HandlerFunc("PUT", "/api/user/profile.json", hapiProfileView)
	router.HandlerFunc("GET", "/api/admin/plugins.json", hapiPlugins)
	router.HandlerFunc("GET", "/api/admin/conversations_need_training.json", hapiConversationsNeedTraining)
	router.Handle("GET", "/api/admin/conversations/:uid/:fid/:fidt/:off", hapiConversation)
	router.HandlerFunc("PATCH", "/api/admin/conversations.json", hapiConversationsUpdate)
	router.HandlerFunc("POST", "/api/admins/send_message.json", hapiSendMessage)
	router.HandlerFunc("GET", "/api/admins.json", hapiAdmins)
	router.HandlerFunc("PUT", "/api/admins.json", hapiAdminsUpdate)
	router.HandlerFunc("GET", "/api/admin/remote_tokens.json", hapiRemoteTokens)
	router.HandlerFunc("POST", "/api/admin/remote_tokens.json", hapiRemoteTokensSubmit)
	router.HandlerFunc("DELETE", "/api/admin/remote_tokens.json", hapiRemoteTokensDelete)
	router.HandlerFunc("PUT", "/api/admin/settings.json", hapiSettingsUpdate)
	router.HandlerFunc("GET", "/api/admin/dashboard.json", hapiDashboard)
	return router
}
func hIndex(w http.ResponseWriter, r *http.Request) {
	var err error
	env := os.Getenv("ABOT_ENV")
	if env != "production" && env != "test" {
		p := filepath.Join("assets", "html", "layout.html")
		tmplLayout, err = template.ParseFiles(p)
		if err != nil {
			writeErrorInternal(w, err)
			return
		}
		if err = compileAssets(); err != nil {
			writeErrorInternal(w, err)
			return
		}
	}
	data := struct {
		IsProd     bool
		ItsAbotURL string
	}{
		IsProd:     os.Getenv("ABOT_ENV") == "production",
		ItsAbotURL: os.Getenv("ITSABOT_URL"),
	}
	if err = tmplLayout.Execute(w, data); err != nil {
		writeErrorInternal(w, err)
	}
}
func hMain(w http.ResponseWriter, r *http.Request) {
	errMsg := "Something went wrong with my wiring... I'll get that fixed up soon."
	ret, err := ProcessText(r)
	if err != nil {
		if len(ret) > 0 {
			ret = errMsg
		}
		log.Info("failed to process text.", err)
	}
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Access-Control-Allow-Origin")
	_, err = fmt.Fprint(w, ret)
	if err != nil {
		writeErrorInternal(w, err)
	}
}
func hOptions(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Access-Control-Allow-Origin")
	w.WriteHeader(http.StatusOK)
}
func hapiLogoutSubmit(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie("id")
	if err != nil {
		writeError(w, err)
		return
	}
	uid := cookie.Value
	if uid == "null" {
		http.Error(w, "id was null", http.StatusBadRequest)
		return
	}
	q := `DELETE FROM sessions WHERE userid=$1`
	if _, err = db.Exec(q, uid); err != nil {
		writeError(w, err)
		return
	}
	w.WriteHeader(http.StatusOK)
}
func hapiLoginSubmit(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Email    string
		Password string
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeErrorInternal(w, err)
		return
	}
	var u struct {
		ID       uint64
		Password []byte
		Admin    bool
	}
	q := `SELECT id, password, admin FROM users WHERE email=$1`
	err := db.Get(&u, q, req.Email)
	if err == sql.ErrNoRows {
		writeErrorAuth(w, ErrInvalidUserPass)
		return
	} else if err != nil {
		writeErrorInternal(w, err)
		return
	}
	if u.ID == 0 {
		writeErrorAuth(w, ErrInvalidUserPass)
		return
	}
	err = bcrypt.CompareHashAndPassword(u.Password, []byte(req.Password))
	if err == bcrypt.ErrMismatchedHashAndPassword || err == bcrypt.ErrHashTooShort {
		writeErrorAuth(w, ErrInvalidUserPass)
		return
	} else if err != nil {
		writeErrorInternal(w, err)
		return
	}
	user := &dt.User{
		ID:    u.ID,
		Email: req.Email,
		Admin: u.Admin,
	}
	csrfToken, err := createCSRFToken(user)
	if err != nil {
		writeErrorInternal(w, err)
		return
	}
	header, token, err := getAuthToken(user)
	if err != nil {
		writeErrorInternal(w, err)
		return
	}
	resp := struct {
		ID        uint64
		Email     string
		Scopes    []string
		AuthToken string
		IssuedAt  int64
		CSRFToken string
	}{
		ID:        user.ID,
		Email:     user.Email,
		Scopes:    header.Scopes,
		AuthToken: token,
		IssuedAt:  header.IssuedAt,
		CSRFToken: csrfToken,
	}
	writeBytes(w, resp)
}
func hapiSignupSubmit(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Name     string
		Email    string
		Password string
		FID      string
		Admin bool
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeErrorInternal(w, err)
		return
	}
	if len(req.Name) == 0 {
		writeErrorBadRequest(w, errors.New("You must enter a name."))
		return
	}
	if len(req.Email) == 0 ||
		!strings.ContainsAny(req.Email, "@") ||
		!strings.ContainsAny(req.Email, ".") {
		writeErrorBadRequest(w, errors.New("You must enter a valid email."))
		return
	}
	if len(req.Password) < 8 {
		writeErrorBadRequest(w, errors.New("Your password must be at least 8 characters."))
		return
	}
	req.FID = regexNum.ReplaceAllString(req.FID, "")
	if len(req.FID) < 10 {
		writeErrorBadRequest(w, errors.New("Your phone number must be at least 10 digits."))
		return
	}
	if req.FID[0] != '1' {
		if len(req.FID) >= 11 {
			writeErrorBadRequest(w, errors.New("Invalid country code. Currently only American numbers are supported."))
			return
		}
		req.FID = "+1" + req.FID
	} else {
		req.FID = "+" + req.FID
	}
	var admin bool
	if req.Admin {
		var count int
		q := `SELECT COUNT(*) FROM users WHERE admin=TRUE`
		if err := db.Get(&count, q); err != nil {
			writeErrorInternal(w, err)
			return
		}
		if count > 0 {
			writeErrorBadRequest(w, errors.New("invalid param Admin"))
			return
		}
		admin = true
	}
	user := &dt.User{
		Name:  req.Name,
		Email: req.Email,
		Password: req.Password,
		Trainer:  false,
		Admin:    admin,
	}
	err := user.Create(db, dt.FlexIDType(2), req.FID)
	if err != nil {
		writeErrorInternal(w, err)
		return
	}
	csrfToken, err := createCSRFToken(user)
	if err != nil {
		writeErrorInternal(w, err)
		return
	}
	header, token, err := getAuthToken(user)
	if err != nil {
		writeErrorInternal(w, err)
		return
	}
	resp := struct {
		ID        uint64
		Email     string
		Scopes    []string
		AuthToken string
		IssuedAt  int64
		CSRFToken string
	}{
		ID:        user.ID,
		Email:     user.Email,
		Scopes:    header.Scopes,
		AuthToken: token,
		IssuedAt:  header.IssuedAt,
		CSRFToken: csrfToken,
	}
	resp.ID = user.ID
	log.Info("user signed up. id", user.ID)
	writeBytes(w, resp)
}
func hapiProfile(w http.ResponseWriter, r *http.Request) {
	if os.Getenv("ABOT_ENV") != "test" {
		if !isLoggedIn(w, r) {
			return
		}
	}
	cookie, err := r.Cookie("id")
	if err != nil {
		writeErrorInternal(w, err)
		return
	}
	uid := cookie.Value
	var user struct {
		Name   string
		Email  string
		Phones []dt.Phone
	}
	q := `SELECT name, email FROM users WHERE id=$1`
	err = db.Get(&user, q, uid)
	if err != nil {
		writeErrorInternal(w, err)
		return
	}
	q = `SELECT flexid FROM userflexids
	     WHERE flexidtype=2 AND userid=$1
	     LIMIT 10`
	err = db.Select(&user.Phones, q, uid)
	if err != nil && err != sql.ErrNoRows {
		writeErrorInternal(w, err)
		return
	}
	writeBytes(w, user)
}
func hapiProfileView(w http.ResponseWriter, r *http.Request) {
	if os.Getenv("ABOT_ENV") != "test" {
		if !isLoggedIn(w, r) {
			return
		}
		if !isValidCSRF(w, r) {
			return
		}
	}
	cookie, err := r.Cookie("id")
	if err != nil {
		writeErrorInternal(w, err)
		return
	}
	uid := cookie.Value
	q := `SELECT authorizationid FROM users WHERE id=$1`
	var authID sql.NullInt64
	if err = db.Get(&authID, q, uid); err != nil {
		writeErrorInternal(w, err)
		return
	}
	if !authID.Valid {
		goto Response
	}
	q = `UPDATE authorizations SET authorizedat=$1 WHERE id=$2`
	_, err = db.Exec(q, time.Now(), authID)
	if err != nil {
		writeErrorInternal(w, err)
		return
	}
Response:
	w.WriteHeader(http.StatusOK)
}
func hapiForgotPasswordSubmit(w http.ResponseWriter, r *http.Request) {
	var req struct{ Email string }
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeErrorInternal(w, err)
		return
	}
	var user dt.User
	q := `SELECT id, name, email FROM users WHERE email=$1`
	err := db.Get(&user, q, req.Email)
	if err == sql.ErrNoRows {
		writeError(w, errors.New("Sorry, there's no record of that email. Are you sure that's the email you used to sign up with and that you typed it correctly?"))
		return
	}
	if err != nil {
		writeError(w, err)
		return
	}
	secret := RandSeq(40)
	q = `INSERT INTO passwordresets (userid, secret) VALUES ($1, $2)`
	if _, err = db.Exec(q, user.ID, secret); err != nil {
		writeError(w, err)
		return
	}
	if len(emailsender.Drivers()) == 0 {
		writeError(w, errors.New("Sorry, this feature is not enabled. To be enabled, an email driver must be imported."))
		return
	}
	w.WriteHeader(http.StatusOK)
}
func hapiResetPasswordSubmit(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Password string
		Secret   string
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeErrorInternal(w, err)
		return
	}
	if len(req.Password) < 8 {
		writeError(w, errors.New("Your password must be at least 8 characters"))
		return
	}
	var uid uint64
	q := `SELECT userid FROM passwordresets
	      WHERE secret=$1 AND
	      createdat >= CURRENT_TIMESTAMP - interval '30 minutes'`
	err := db.Get(&uid, q, req.Secret)
	if err == sql.ErrNoRows {
		writeError(w, errors.New("Sorry, that information doesn't match our records."))
		return
	}
	if err != nil {
		writeError(w, err)
		return
	}
	hpw, err := bcrypt.GenerateFromPassword([]byte(req.Password), 10)
	if err != nil {
		writeError(w, err)
		return
	}
	tx, err := db.Begin()
	if err != nil {
		writeError(w, err)
		return
	}
	q = `UPDATE users SET password=$1 WHERE id=$2`
	if _, err = tx.Exec(q, hpw, uid); err != nil {
		writeError(w, err)
		return
	}
	q = `DELETE FROM passwordresets WHERE secret=$1`
	if _, err = tx.Exec(q, req.Secret); err != nil {
		writeError(w, err)
		return
	}
	if err = tx.Commit(); err != nil {
		writeError(w, err)
		return
	}
	w.WriteHeader(http.StatusOK)
}
func hapiAdminExists(w http.ResponseWriter, r *http.Request) {
	var count int
	q := `SELECT COUNT(*) FROM users WHERE admin=TRUE LIMIT 1`
	if err := db.Get(&count, q); err != nil {
		writeErrorInternal(w, err)
		return
	}
	byt, err := json.Marshal(count > 0)
	if err != nil {
		writeErrorInternal(w, err)
		return
	}
	_, err = w.Write(byt)
	if err != nil {
		log.Info("failed writing response header.", err)
	}
}
func hapiPlugins(w http.ResponseWriter, r *http.Request) {
	if os.Getenv("ABOT_ENV") != "test" {
		if !isAdmin(w, r) {
			return
		}
		if !isLoggedIn(w, r) {
			return
		}
	}
	var settings []struct {
		Name       string
		Value      string
		PluginName string
	}
	q := `SELECT name, value, pluginname FROM settings`
	if err := db.Select(&settings, q); err != nil {
		writeErrorInternal(w, err)
		return
	}
	type respT struct {
		ID         uint64
		Name       string
		Icon       string
		Maintainer string
		Settings   map[string]string
	}
	var resp []respT
	for _, plugin := range PluginsGo {
		data := respT{
			ID:         plugin.ID,
			Name:       plugin.Name,
			Icon:       plugin.Icon,
			Maintainer: plugin.Maintainer,
			Settings:   map[string]string{},
		}
		for k, v := range plugin.Settings {
			data.Settings[k] = v.Default
		}
		for _, setting := range settings {
			if setting.PluginName != plugin.Name {
				continue
			}
			data.Settings[setting.Name] = setting.Value
		}
		resp = append(resp, data)
	}
	writeBytes(w, resp)
}
func hapiConversationsNeedTraining(w http.ResponseWriter, r *http.Request) {
	if os.Getenv("ABOT_ENV") != "test" {
		if !isAdmin(w, r) {
			return
		}
		if !isLoggedIn(w, r) {
			return
		}
	}
	msgs := []struct {
		Sentence   string
		FlexID     *string
		CreatedAt  time.Time
		UserID     uint64
		FlexIDType *int
	}{}
	q := `SELECT * FROM (
		SELECT DISTINCT ON (flexid)
			userid, flexid, flexidtype, sentence, createdat
		FROM messages
		WHERE needstraining=TRUE AND trained=FALSE AND abotsent=FALSE AND sentence<>''
	) t ORDER BY createdat DESC`
	err := db.Select(&msgs, q)
	if err == sql.ErrNoRows {
		w.WriteHeader(http.StatusOK)
	}
	if err != nil {
		writeErrorInternal(w, err)
		return
	}
	byt, err := json.Marshal(msgs)
	if err != nil {
		writeErrorInternal(w, err)
		return
	}
	_, err = w.Write(byt)
	if err != nil {
		log.Info("failed to write response.", err)
	}
}