327 lines
8.0 KiB
Go
327 lines
8.0 KiB
Go
// MediNET - Meditation Assistant back-end
|
|
// https://gitlab.com/tslocum/medinet
|
|
// Written by Trevor 'tee' Slocum <tslocum@gmail.com>
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"html"
|
|
"log"
|
|
"math/rand"
|
|
"net/http"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
"github.com/jessevdk/go-flags"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type Config struct {
|
|
Om int
|
|
SQLHost string
|
|
SQLUser string
|
|
SQLPassword string
|
|
SQLDatabase string
|
|
}
|
|
|
|
var config *Config
|
|
|
|
func midnight(t time.Time) time.Time {
|
|
year, month, day := t.Date()
|
|
return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
|
|
}
|
|
|
|
func failOnError(err error) {
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
var opts struct {
|
|
ConfigFile string `short:"c" long:"config" description:"Configuration file"`
|
|
}
|
|
_, err := flags.Parse(&opts)
|
|
failOnError(err)
|
|
|
|
rand.Seed(time.Now().UTC().UnixNano())
|
|
|
|
if opts.ConfigFile == "" {
|
|
log.Fatal("Please specify configuration file with: medinet -c <config file>")
|
|
}
|
|
if _, err = os.Stat(opts.ConfigFile); err != nil {
|
|
log.Fatalf("Configuration file %s does not exist: %s", opts.ConfigFile, err)
|
|
}
|
|
|
|
config = new(Config)
|
|
if _, err = toml.DecodeFile(opts.ConfigFile, &config); err != nil {
|
|
log.Fatalf("Failed to read %s: %v", opts.ConfigFile, err)
|
|
}
|
|
|
|
if config.Om == 0 {
|
|
log.Fatal("Specify Om port in configuration file")
|
|
}
|
|
|
|
d, err := NewDatabase(config.SQLHost, config.SQLUser, config.SQLPassword, config.SQLDatabase)
|
|
failOnError(err)
|
|
|
|
http.HandleFunc("/om/community", func(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Fprintf(w, "COMMUNITY, %q", html.EscapeString(r.URL.RequestURI()))
|
|
})
|
|
|
|
http.HandleFunc("/om/sessions", func(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Fprintf(w, "COMMUNITY, %q", html.EscapeString(r.URL.RequestURI()))
|
|
})
|
|
|
|
http.HandleFunc("/om/account", func(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Fprintf(w, "COMMUNITY, %q", html.EscapeString(r.URL.RequestURI()))
|
|
})
|
|
|
|
http.HandleFunc("/om/forum", func(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Fprintf(w, "COMMUNITY, %q", html.EscapeString(r.URL.RequestURI()))
|
|
})
|
|
|
|
http.HandleFunc("/om/groups", func(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Fprintf(w, "COMMUNITY, %q", html.EscapeString(r.URL.RequestURI()))
|
|
})
|
|
|
|
http.HandleFunc("/om", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
r.ParseForm()
|
|
|
|
// Authenticate (signin)
|
|
// Send key, client will then call (connect)
|
|
token := r.FormValue("token")
|
|
if token != "" {
|
|
a, err := d.authenticate(token)
|
|
if err != nil {
|
|
log.Printf("ERROR! %v", errors.Wrap(err, "failed to authenticate token"))
|
|
return
|
|
} else if a == nil {
|
|
log.Printf("ERROR! %v", errors.New("failed to retrieve authenticated account"))
|
|
return
|
|
}
|
|
|
|
d.updateLastActive(a.ID)
|
|
w.Header().Set("x-MediNET", "connected")
|
|
w.Header().Set("x-MediNET-Key", a.Key)
|
|
|
|
return
|
|
}
|
|
|
|
key := r.URL.Query().Get("x")
|
|
if key == "" {
|
|
key = r.FormValue("x")
|
|
}
|
|
|
|
a, err := d.getAccount(key)
|
|
if err != nil {
|
|
log.Printf("ERROR! %v", err)
|
|
return
|
|
}
|
|
if a == nil {
|
|
w.Header().Set("x-MediNET", "signin")
|
|
log.Printf("Asking to sign in %s %q", key, html.EscapeString(r.URL.RequestURI()))
|
|
|
|
return
|
|
}
|
|
go d.updateLastActive(a.ID)
|
|
|
|
data := make(map[string]interface{})
|
|
data["status"] = "success"
|
|
|
|
action := r.FormValue("action")
|
|
|
|
tz, err := time.LoadLocation(r.URL.Query().Get("tz"))
|
|
if err != nil {
|
|
tz, err = time.LoadLocation("UTC")
|
|
if err != nil {
|
|
log.Printf("ERROR! %v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
apiver, err := strconv.Atoi(r.URL.Query().Get("v"))
|
|
if err != nil {
|
|
apiver = 0
|
|
}
|
|
|
|
av := r.URL.Query().Get("avn")
|
|
if av == "" {
|
|
av = r.URL.Query().Get("av")
|
|
onlynum := regexp.MustCompile("[0-9]+")
|
|
match := onlynum.FindAllString(av, -1)
|
|
if match != nil {
|
|
av = strings.Join(match, "")
|
|
}
|
|
}
|
|
appver, err := strconv.Atoi(av)
|
|
if err != nil {
|
|
appver = 0
|
|
}
|
|
|
|
// TODO: read from announcement table on successful connect
|
|
//data["announce"] = "First line\n\nSecond line"
|
|
|
|
switch action {
|
|
case "deletesession":
|
|
data["result"] = "notdeleted"
|
|
|
|
st := r.FormValue("session")
|
|
if st == "" {
|
|
break
|
|
}
|
|
started, err := strconv.Atoi(st)
|
|
if err != nil || started == 0 {
|
|
log.Println(errors.Wrap(err, "failed to read session started when deleting session"))
|
|
return
|
|
}
|
|
|
|
deleted, err := d.deleteSession(started, a.ID)
|
|
if err != nil {
|
|
log.Printf("ERROR! %v", err)
|
|
return
|
|
} else if deleted {
|
|
data["result"] = "deleted"
|
|
}
|
|
case "downloadsessions": // Confirmed working
|
|
sessions, err := d.getSessions(a.ID)
|
|
if err != nil {
|
|
log.Printf("ERROR! %v", err)
|
|
return
|
|
}
|
|
|
|
data["downloadsessions"] = sessions
|
|
case "uploadsessions":
|
|
data["result"] = "corrupt"
|
|
|
|
u := r.FormValue("uploadsessions")
|
|
if u == "" {
|
|
break
|
|
}
|
|
|
|
var uploadsessions []Session
|
|
err = json.Unmarshal([]byte(u), &uploadsessions)
|
|
if err != nil {
|
|
log.Printf("ERROR! %v", err)
|
|
return
|
|
}
|
|
|
|
data["result"] = "uploaded"
|
|
sessionsuploaded := 0
|
|
|
|
for _, session := range uploadsessions {
|
|
added, err := d.addSession(session, a.ID)
|
|
if err != nil {
|
|
log.Printf("ERROR! %v", err)
|
|
return
|
|
}
|
|
|
|
if added {
|
|
sessionsuploaded++
|
|
}
|
|
}
|
|
data["sessionsuploaded"] = sessionsuploaded
|
|
|
|
if sessionsuploaded > 0 {
|
|
started := 0
|
|
completed := 0
|
|
streakday := 0
|
|
for _, session := range uploadsessions {
|
|
if session.Started > started {
|
|
started = session.Started
|
|
completed = session.Completed
|
|
streakday = session.StreakDay
|
|
}
|
|
}
|
|
|
|
t := time.Now().In(tz)
|
|
if t.Hour() < 4 {
|
|
t = t.AddDate(0, 0, -1)
|
|
}
|
|
t = midnight(t).Add(STREAK_BUFFER * time.Second)
|
|
if int64(completed) >= t.Unix() { // Session was recently uploaded
|
|
streak, err := d.calculateStreak(a.ID, tz)
|
|
if err != nil {
|
|
log.Printf("ERROR! %v", err)
|
|
return
|
|
}
|
|
|
|
log.Printf("NEW SESSION %v - CALCULATED: %d, SUBMITTED: %d", t, streak, streakday)
|
|
|
|
if streak < streakday {
|
|
streak = streakday
|
|
} else if streak > streakday {
|
|
err = d.setSessionStreakDay(started, streak, a.ID)
|
|
if err != nil {
|
|
log.Printf("ERROR! %v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
err = d.setStreak(streak, a.ID, tz)
|
|
if err != nil {
|
|
log.Printf("ERROR! %v", err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// "postsession" is set when posting directly from complete screen or session list
|
|
p := r.FormValue("postsession")
|
|
if p != "" {
|
|
if sessionsuploaded > 0 {
|
|
data["result"] = "posted"
|
|
} else {
|
|
data["result"] = "alreadyposted"
|
|
}
|
|
}
|
|
}
|
|
|
|
w.Header().Set("x-MediNET", "connected")
|
|
|
|
// Send streak
|
|
if action == "connect" || action == "downloadsessions" || action == "uploadsessions" {
|
|
streakday, streakend, topstreak, err := d.getStreak(a.ID)
|
|
if err != nil {
|
|
log.Printf("ERROR! %v", err)
|
|
return
|
|
}
|
|
w.Header().Set("x-MediNET-Streak", fmt.Sprintf("%d,%d", streakday, streakend))
|
|
w.Header().Set("x-MediNET-MaxStreak", fmt.Sprintf("%d", topstreak))
|
|
}
|
|
|
|
json.NewEncoder(w).Encode(data)
|
|
|
|
j, err := json.Marshal(data)
|
|
if err != nil {
|
|
log.Printf("ERROR! %v", err)
|
|
return
|
|
}
|
|
|
|
log.Printf("App: %d API: %d Action: %s - %q", appver, apiver, action, html.EscapeString(r.URL.RequestURI()))
|
|
log.Printf("Account ID: %d, JSON: %s", a.ID, string(j))
|
|
})
|
|
|
|
log.Fatal(http.ListenAndServe(fmt.Sprintf("localhost:%d", config.Om), nil))
|
|
}
|