ref: acc962a0381815da8dd9f4c42e0ca244084be432
parent: 3dbf2f21c2fd84309d71826d053906e54b40c2f3
author: Moses Olson, MD <52055478+nemo-olmax@users.noreply.github.com>
date: Mon Aug 12 06:51:47 PDT 2019
Add files via upload
--- /dev/null
+++ b/router/countries.go
@@ -1,0 +1,62 @@
+package router
+
+import (
+ "sort"
+ "strings"
+
+ "github.com/pariz/gountries"
+)
+
+type Country struct {
+ ID string
+ Name string
+}
+
+type countries struct {
+ list []gountries.Country
+}
+
+var country *countries
+
+func init() {
+ var l []gountries.Country
+ c := gountries.New()
+ for _, c := range c.FindAllCountries() {
+ l = append(l, c)
+ }
+ country = &countries{
+ list: l,
+ }
+ sort.Sort(country)
+}
+
+func (c *countries) Len() int {
+ return len(c.list)
+}
+
+func (c *countries) Less(i, j int) bool {
+ switch strings.Compare(c.list[i].Name.Common, c.list[j].Name.Common) {
+ case -1:
+ return true
+ default:
+ return false
+ }
+}
+
+func (c *countries) Swap(i, j int) {
+ tmp := c.list[i]
+ c.list[i] = c.list[j]
+ c.list[j] = tmp
+}
+
+// TODO: Filter out any countries we don't support
+func listcountries() []Country {
+ var c []Country
+ for _, item := range country.list {
+ c = append(c, Country{
+ ID: item.Name.Common,
+ Name: item.Name.Common,
+ })
+ }
+ return c
+}
--- /dev/null
+++ b/router/doctor.go
@@ -1,0 +1,37 @@
+package router
+
+type doctor struct {
+ Image string
+ AlmaMater string
+ Name string
+ Residency string
+ Current string
+ Country string
+ Specialty string
+ Rate string
+}
+
+func listdoctors() []doctor {
+ return []doctor{
+ {
+ Image: "AbuzamzamMD.jpg",
+ AlmaMater: "University of Southern California School of Medicine",
+ Residency: "University of Southern California, San Diego. Internal Medicine Residency",
+ Name: "Mark Abuzamzam, MD",
+ Current: "Current Faculty at University of California Irvine Program Director",
+ Country: "United States of America",
+ Specialty: "Internal Medicine and Addictions Medicine",
+ Rate: "0.0013 BTC",
+ },
+ {
+ Image: "WoodfinMD.jpg",
+ Name: "Martha Woodfin, MD",
+ AlmaMater: "University Seoul School of Medicine",
+ Residency: "University of Las Vegas Nevada, Pediatric Medicine Residency",
+ Current: "Current Staff at Mercy Hospital Jackson NC",
+ Country: "United States of America",
+ Specialty: "Internal Medicine",
+ Rate: "0.0011 BTC",
+ },
+ }
+}
\ No newline at end of file
--- /dev/null
+++ b/router/footer.go
@@ -1,0 +1,25 @@
+package router
+
+import (
+ "golang.org/x/text/message"
+)
+
+func footer(p *message.Printer) map[string]string {
+ return map[string]string{
+ "faq": p.Sprintf("FAQ"),
+ "help": p.Sprintf("Help"),
+ "banner": p.Sprintf("Over 12B patients served"),
+ "pay": p.Sprintf("Payment Methods"),
+ "fees": p.Sprintf("Prices and Fees"),
+ "verify": p.Sprintf("Verification"),
+ "appt": p.Sprintf("Appointments"),
+ "legal": p.Sprintf("Legal"),
+ "privacy": p.Sprintf("Privacy Policy"),
+ "aboutHeader": p.Sprintf("About Us"),
+ "about": p.Sprintf("Olmax"),
+ "partHead": p.Sprintf("Partnering"),
+ "partner": p.Sprintf("Become A Partner"),
+ "provider": p.Sprintf("Are You A Doctor?"),
+ "copy": p.Sprintf("Copyright 2017, 2018, 2019"),
+ }
+}
--- /dev/null
+++ b/router/forms.go
@@ -1,0 +1,122 @@
+package router
+
+import (
+ "fmt"
+ "net/http"
+ "golang.org/x/text/message"
+ "olmax/db"
+ "olmax/session"
+)
+
+var formlist map[string]*Form
+
+type After uint8
+
+const (
+ ValidateLogin After = 1 << iota
+ SendSignup
+ SendReset
+ SetPassword
+)
+
+type Form struct {
+ Access Access
+ After After
+ Path string
+ Redirect string
+ Validator func(r *http.Request, p *message.Printer) []string
+}
+
+func init() {
+ formlist = make(map[string]*Form)
+}
+
+func AddPost(f *Form) {
+ formlist[f.Path + ".html"] = f
+}
+
+func parseform(p *page, w http.ResponseWriter, r *http.Request) []string {
+ var errors []string
+ form, ok := formlist[p.path]
+ if ! ok {
+ errors = append(errors, "No such page")
+ return errors
+ }
+ e := form.Validator(r, p.printer)
+ if len(e) > 0 {
+ errors = append(errors, e...)
+ }
+ var msg string
+ if form.After&ValidateLogin != 0 {
+ e = validateLogin(p.printer, p.session, r)
+ if len(e) > 0 {
+ errors = append(errors, e...)
+ }
+ }
+ if form.After&SendSignup != 0 {
+ msg = signupEmail(p.printer, r)
+ }
+ if form.After&SendReset != 0 {
+ msg = resetPassword(p.printer, r)
+ }
+ if form.After&SetPassword != 0 {
+ e = setPassword(p.printer, p.session, r)
+ if len(e) > 0 {
+ errors = append(errors, e...)
+ }
+ }
+ if msg != "" {
+ fmt.Fprintf(w, "%s\n", msg)
+ return errors
+ }
+ if len(errors) == 0 {
+ http.Redirect(w, r, form.Redirect, 302)
+ }
+ return errors
+}
+
+func setPassword(p *message.Printer, us session.Session, r *http.Request) []string {
+ var errors []string
+ pass := r.PostFormValue("password")
+ repeat := r.PostFormValue("reenter")
+ if pass != repeat {
+ errors = append(errors, p.Sprint("Passwords do not match"))
+ return errors
+ }
+ token := r.PostFormValue("token")
+ if ! db.FindEntry(token) {
+ errors = append(errors, p.Sprint("Session expired"))
+ return errors
+ }
+ db.UpdateUserPassword(token, pass)
+ return errors
+}
+
+func validateLogin(p *message.Printer, us session.Session, r *http.Request) []string {
+ var errors []string
+ user := r.PostFormValue("email")
+ pass := r.PostFormValue("pass")
+ if db.ValidateLogin(user, pass) {
+ us.Set("username", user)
+ us.Set("login", "true")
+ return errors
+ }
+ errors = append(errors, p.Sprint("Invalid username or password"))
+ return errors
+}
+
+func signupEmail(p *message.Printer, r *http.Request) string {
+ first := r.PostFormValue("fname")
+ last := r.PostFormValue("lname")
+ email := r.PostFormValue("email")
+ pass := r.PostFormValue("pass")
+ em := sendsignup(first, last, email, pass, p)
+ return p.Sprintf("An email has been sent to the following email with instructions on finalizing your account creation: %s\n", em)
+}
+
+func resetPassword(p *message.Printer, r *http.Request) string{
+ em := sendreset(r.PostFormValue("email"), p)
+ return p.Sprintf("An email has been sent to the following email with a link to reset your password: %s\n", em)
+}
+
+
--- /dev/null
+++ b/router/header.go
@@ -1,0 +1,31 @@
+package router
+
+import (
+ "golang.org/x/text/message"
+)
+
+func header(p *message.Printer, status string) map[string]string {
+ return map[string]string{
+ // These go away, in the layout.go they'll be called these values added
+ "doctors": p.Sprintf("Doctors"),
+ "provider": p.Sprintf("Become A Provider"),
+ "whodoctor": p.Sprintf("Who can become a doctor"),
+ "howworks": p.Sprintf("How It Works"),
+ "contact": p.Sprintf("Contact Us"),
+ "faq": p.Sprintf("FAQ"),
+ "pricing": p.Sprintf("Pricing"),
+ "appts": p.Sprintf("Appointments"),
+ "proc": p.Sprintf("Payment Procedures"),
+ "payments": p.Sprintf("Payment Methods"),
+ "fees": p.Sprintf("Prices and Fees"),
+ "verify": p.Sprintf("Verification"),
+ "phone": p.Sprintf("Call toll free"),
+ "number": p.Sprintf("1(555)555-1234"),
+ "email": p.Sprintf("Email"),
+ "login": p.Sprintf("Login"),
+ "logout": p.Sprintf("Logout"),
+ "signup": p.Sprintf("Sign Up"),
+ "profile": p.Sprintf("Profile"),
+ "status": status,
+ }
+}
--- /dev/null
+++ b/router/pages.go
@@ -1,0 +1,133 @@
+package router
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "html/template"
+ "path"
+ "strings"
+
+ "golang.org/x/text/message"
+)
+
+var pagecache map[string]*Page
+var countrylist []Country
+
+func init() {
+ pagecache = make(map[string]*Page)
+ countrylist = listcountries()
+}
+
+type Access uint8
+
+const (
+ GuestAuth Access = 1 << iota
+ PatientAuth
+ DoctorAuth
+)
+
+type IncludeExtra uint8
+
+const (
+ ListDoctors IncludeExtra = 1 << iota
+ ListServices
+ ListCountries
+ OneTimeToken
+)
+
+type Page struct {
+ Access Access
+ Extra IncludeExtra
+ Css string
+ Path string
+ Data func(p *message.Printer) map[string]interface{}
+ tmpl *template.Template
+}
+
+func Add(p *Page) {
+ pagecache[p.Path+".html"] = p
+}
+
+// Walk all our templates and finally return applicable errors as an array
+func ValidateAndCache() []error {
+ var errs []error
+ hd := path.Join("templates", "header.tpl")
+ fd := path.Join("templates", "footer.tpl")
+ ld := path.Join("templates", "layout.tpl")
+ printer := message.NewPrinter(message.MatchLanguage("en"))
+ for _, item := range pagecache {
+ var err error
+ tp := path.Join("templates", item.Path) + ".tpl"
+
+ t := template.New(path.Base(tp))
+ item.tmpl, err = t.ParseFiles(tp, hd, fd, ld)
+ if err != nil {
+ errs = append(errs, fmt.Errorf("parsing in %s - %v", path.Dir(item.Path), err))
+ continue
+ }
+ p := &page{
+ printer: printer,
+ path: item.Path + ".html",
+ }
+ _, err = getdata(p, "")
+ if err != nil {
+ errs = append(errs, err)
+ }
+ }
+ return errs
+}
+
+func getdata(p *page, in string) ([]byte, error) {
+ cache, ok := pagecache[p.path]
+ if ! ok {
+ return nil, fmt.Errorf("No such page: %s", p.path)
+ }
+ i := cache.Data(p.printer)
+ i["css"] = cache.Css
+ i["header"] = header(p.printer, p.status)
+ i["footer"] = footer(p.printer)
+ i["basedir"] = getBaseDir(cache.Path)
+ if cache.Extra&ListDoctors != 0 {
+ i["doctors"] = listdoctors()
+ }
+ if cache.Extra&ListServices != 0 {
+ i["specialties"] = specialties(p.printer)
+ }
+ if cache.Extra&ListCountries != 0 {
+ i["countrylist"] = countrylist
+ }
+ if p.session != nil && cache.Extra&OneTimeToken != 0 {
+ i["token"] = p.session.Get("token")
+ }
+ //if cache.Extra&ClientName != 0 {
+ // i["firstname"] = db.ClientName(p.session)
+ //}
+ //if cache.Extra&ClientSurname != 0 {
+ // i["surname"] = db.ClientSurname(p.session)
+ //}
+ //if cache.Extra&ClientUsername != 0 {
+ // i["username"] = db.ClientUsername(p.session)
+ //}
+ //if cache.Extra&FormErrors != 0 && p.session != nil {
+ // i["errors"] = p.session.Get("errors")
+ //}
+ if p.session != nil {
+ i["username"] = p.session.Get("username")
+ i["errors"] = p.session.Get("errors")
+ }
+ return cache.render(i)
+}
+
+func (page *Page) render(i map[string]interface{}) ([]byte, error) {
+ var buf bytes.Buffer
+ data := bufio.NewWriter(&buf)
+ err := page.tmpl.ExecuteTemplate(data, "layout", i)
+ data.Flush()
+ return buf.Bytes(), err
+
+}
+
+func getBaseDir(fp string) string {
+ return strings.Repeat("../", strings.Count(fp, "/"))
+}
--- /dev/null
+++ b/router/reset.go
@@ -1,0 +1,60 @@
+package router
+
+import (
+ "bytes"
+ "fmt"
+ "log"
+ "net/smtp"
+ "time"
+
+ "github.com/google/uuid"
+ "golang.org/x/text/message"
+ "olmax/db"
+)
+
+func nextResetToken(old, user string) string {
+ if db.FindTempEntry(old) {
+ db.RemoveTempEntry(old)
+ u, _ := uuid.NewRandom()
+ token := u.String()
+ db.CreateTempEntry("", "", user, "", token)
+ go func() {
+ time.Sleep(time.Minute * 10)
+ db.RemoveTempEntry(token)
+ }()
+ return token
+ }
+ return ""
+}
+
+func sendreset(email string, p *message.Printer) string {
+ u, _ := uuid.NewRandom()
+ token := u.String()
+ if db.UserExists(email) {
+ db.CreateTempEntry("", "", email, "", token)
+ resetemail(token, email, p)
+ go func() {
+ time.Sleep(time.Minute * 10)
+ db.RemoveTempEntry(token)
+ }()
+ }
+ return "<a href=\"https://mail.google.com/mail/u?authuser=" + email + "\"/>"
+}
+
+func resetemail(token string, sendto string, p *message.Printer) {
+ var msg bytes.Buffer
+ msg.WriteString("From: ")
+ msg.WriteString("olmaxmedical@gmail.com" + "\n")
+ msg.WriteString("To: ")
+ msg.WriteString(sendto + "\n")
+ msg.WriteString(p.Sprintf("Subject: Olmax Medical - Reset Your Password\n\n"))
+ msg.WriteString(p.Sprintf("Please click the following link to reset your password "))
+ msg.WriteString(fmt.Sprintf("%s/reset/%s\n", url, token))
+ err := smtp.SendMail("smtp.gmail.com:587",
+ smtp.PlainAuth("", "olmaxmedical@gmail.com", "hunter2", "smtp.gmail.com"),
+ "olmaxmedical@gmail.com", []string{sendto}, msg.Bytes(),
+ )
+ if err != nil {
+ log.Printf("smtp error: %v", err)
+ }
+}
--- /dev/null
+++ b/router/run.go
@@ -1,0 +1,170 @@
+package router
+
+import (
+ "fmt"
+ "net/http"
+
+ "golang.org/x/text/message"
+ "olmax/db"
+ "olmax/session"
+)
+
+type handle struct {
+ manager *session.Manager
+}
+
+func Route(manager *session.Manager) error {
+ d := &handle {
+ manager: manager,
+ }
+ css := http.FileServer(http.Dir("resources/css/"))
+ jss := http.FileServer(http.Dir("resources/scripts"))
+ img := http.FileServer(http.Dir("resources/images/"))
+ mux := http.NewServeMux()
+ mux.Handle("/css/", http.StripPrefix("/css/", css))
+ mux.Handle("/scripts/", http.StripPrefix("/scripts/", jss))
+ mux.Handle("/images/", http.StripPrefix("/images/", img))
+ mux.HandleFunc("/activate/", d.activate)
+ mux.HandleFunc("/reset/", d.reset)
+ mux.HandleFunc("/logout.html", d.logout)
+ mux.HandleFunc("/profile.html", d.profile)
+ mux.HandleFunc("/", d.normal)
+ return http.ListenAndServe(":80", mux)
+}
+
+func (d *handle) activate(w http.ResponseWriter, r *http.Request) {
+ if len(r.URL.Path) != 46 && r.URL.Path[:9] != "/activate" {
+ http.Error(w, "Bad Request", 400)
+ return
+ }
+ validateSignupToken(w, r, r.URL.Path[10:])
+}
+
+func (d *handle) reset(w http.ResponseWriter, r *http.Request) {
+ if len(r.URL.Path) != 43 && r.URL.Path[:6] != "/reset" {
+ http.Error(w, "Bad Request", 400)
+ return
+ }
+ p := userLang(r)
+ user, _, us := getUser(d, w, r)
+ token := nextResetToken(r.URL.Path[7:], user)
+ if token == "" {
+ us.Set("errors", [1]string{p.Sprint("Token expired")})
+ return
+ }
+ us.Set("token", token)
+ r.URL.Path = "/newpassword.html"
+ d.normal(w, r)
+}
+
+type page struct {
+ printer *message.Printer
+ session session.Session
+ user string
+ status string
+ path string
+}
+
+func (d *handle) normal(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path == "/" {
+ http.Redirect(w, r, "/index.html", 302)
+ return
+ }
+ user, status, us := getUser(d, w, r)
+ p := &page {
+ printer: userLang(r),
+ status: status,
+ user: user,
+ session: us,
+ path: r.URL.Path[1:],
+ }
+ switch r.Method {
+ case "GET":
+ get(p, w)
+ case "POST":
+ post(p, us, w, r);
+ }
+}
+
+func (d *handle) logout(w http.ResponseWriter, r *http.Request) {
+ d.manager.SessionDestroy(w, r)
+ http.Redirect(w, r, "/index.html", 302)
+
+}
+
+func post(p *page, us session.Session, w http.ResponseWriter, r *http.Request) {
+ errors := parseform(p, w, r)
+ if len(errors) > 0 {
+ us.Set("errors", errors)
+ get(p, w)
+ }
+}
+
+func get(p *page, w http.ResponseWriter) {
+ var data []byte
+ var err error
+ switch db.UserRole(p.user) {
+ case db.DoctorAuth:
+ data, err = getdata(p, "doctor")
+ case db.PatientAuth:
+ data, err = getdata(p, "patient")
+ default:
+ data, err = getdata(p, "guest")
+ }
+ if err != nil {
+ http.Error(w, "Service Unavailable", 503)
+ return
+ }
+ fmt.Fprintf(w, "%s", data)
+}
+
+func userLang(r *http.Request) *message.Printer {
+ accept := r.Header.Get("Accept-Language")
+ lang := r.FormValue("lang")
+ tag := message.MatchLanguage(lang, accept)
+ return message.NewPrinter(tag)
+}
+
+func getUser(d *handle, w http.ResponseWriter, r *http.Request) (string, string, session.Session) {
+ us := d.manager.SessionStart(w, r)
+ user, ok1 := us.Get("username").(string)
+ status, ok2 := us.Get("login").(string)
+ if ! ok1 || ! ok2 || status != "true" {
+ status = "false"
+ }
+ return user, status, us
+}
+
+// TODO: This will require actual client data from the database to populate the page
+func (d *handle) profile(w http.ResponseWriter, r *http.Request) {
+ user, status, us := getUser(d, w, r)
+ if status == "false" {
+ http.Error(w, "Unauthorized", 401)
+ return
+ }
+ p := &page {
+ printer: userLang(r),
+ status: status,
+ session: us,
+ user: user,
+ }
+ var data []byte
+ var err error
+ switch db.UserRole(user) {
+ case db.DoctorAuth:
+ p.path = "doctor/profile.html"
+ data, err = getdata(p, "doctor")
+ case db.PatientAuth:
+ p.path = "patient/profile.html"
+ data, err = getdata(p, "patient")
+ default:
+ http.Error(w, "Forbidden", 403)
+ return
+ }
+ if err != nil {
+ http.Error(w, "Service Unavailable", 503)
+ return
+ }
+ fmt.Fprintf(w, "%s", data)
+}
+
--- /dev/null
+++ b/router/signup.go
@@ -1,0 +1,59 @@
+package router
+
+import (
+ "bytes"
+ "fmt"
+ "log"
+ "net/http"
+ "net/smtp"
+ "time"
+
+ "github.com/google/uuid"
+ "golang.org/x/text/message"
+ "olmax/db"
+)
+
+var url = "http://192.168.1.101"
+
+func sendsignup(first, last, email, pass string, p *message.Printer) string {
+ if ! db.UserExists(email) {
+ u, _ := uuid.NewRandom()
+ token := u.String()
+ db.CreateTempEntry(first, last, email, pass, token)
+ signupemail(token, email, p)
+ go func() {
+ // Blow away the entry unconditionally after 10 minutes
+ time.Sleep(time.Minute * 10)
+ db.RemoveTempEntry(token)
+ }()
+ }
+ return "<a href=\"https://mail.google.com/mail/u?authuser=" + email + "\"/>"
+}
+
+func validateSignupToken(w http.ResponseWriter, r *http.Request, token string) {
+ if db.FindTempEntry(token) {
+ db.CreateEntry(token)
+ http.Redirect(w, r, "/login.html", 302)
+ return
+ }
+ http.Error(w, "Bad Request", 400)
+
+}
+
+func signupemail(token string, sendto string, p *message.Printer) {
+ var msg bytes.Buffer
+ msg.WriteString("From: ")
+ msg.WriteString("olmaxmedical@gmail.com" + "\n")
+ msg.WriteString("To: ")
+ msg.WriteString(sendto + "\n")
+ msg.WriteString(p.Sprintf("Subject: Olmax Medical - Verify your new account\n\n"))
+ msg.WriteString(p.Sprintf("Please click the following link to finalize your account creation "))
+ msg.WriteString(fmt.Sprintf("%s/activate/%s\n", url, token))
+ err := smtp.SendMail("smtp.gmail.com:587",
+ smtp.PlainAuth("", "olmaxmedical@gmail.com", "hunter2", "smtp.gmail.com"),
+ "olmaxmedical@gmail.com", []string{sendto}, msg.Bytes(),
+ )
+ if err != nil {
+ log.Printf("smtp error: %v", err)
+ }
+}
--- /dev/null
+++ b/router/specialties.go
@@ -1,0 +1,51 @@
+package router
+
+import (
+ "golang.org/x/text/message"
+)
+
+// TODO: inverse function to get the actual specialty back from a whitelist
+type Specialty struct {
+ ID string
+ Name string
+}
+
+func specialties(p *message.Printer) []Specialty {
+ return []Specialty{
+ {"acutepain", p.Sprintf("Acute Pain Medicine")},
+ {"anesthesiology", p.Sprintf("Anesthesiology")},
+ {"bariatric", p.Sprintf("Bariatric Surgery")},
+ {"cardiology", p.Sprintf("Cardiology")},
+ {"chiropractic", p.Sprintf("Chiropractics")},
+ {"chronic", p.Sprintf("Chronic Pain")},
+ {"critcare", p.Sprintf("Chronic Pain")},
+ {"dermatology", p.Sprintf("Dermatology")},
+ {"emergency", p.Sprintf("Emergency Medicine")},
+ {"endocrinology", p.Sprintf("Endocrinology")},
+ {"otolaringology", p.Sprintf("Ear Nose and Throat")},
+ {"familymedicine", p.Sprintf("Family Medicine")},
+ {"gastro", p.Sprintf("Gastrointestinology")},
+ {"headneck", p.Sprintf("Head and Neck")},
+ {"hematology", p.Sprintf("Hematology and Oncology")},
+ {"hepatology", p.Sprintf("Hepatology")},
+ {"hyperbaric", p.Sprintf("Hyperbaric")},
+ {"immunology", p.Sprintf("Immunology")},
+ {"diseases", p.Sprintf("Infectious Diseases")},
+ {"internal", p.Sprintf("Internal Medicine")},
+ {"neonatal", p.Sprintf("Neonatology")},
+ {"nephrology", p.Sprintf("Nephrology")},
+ {"neurology", p.Sprintf("Neurology")},
+ {"neurosurgery", p.Sprintf("Neurosurgery")},
+ {"obstetrics", p.Sprintf("Obstetrics and Gynecology")},
+ {"occupational", p.Sprintf("Occupational Medicine")},
+ {"opthamology", p.Sprintf("Opthamology")},
+ {"orthopedics", p.Sprintf("Orthopedic Surgery")},
+ {"palliative", p.Sprintf("Palliative Care")},
+ {"pediatrics", p.Sprintf("Pediatrics")},
+ {"podiatry", p.Sprintf("Podiatry")},
+ {"pulmonology", p.Sprintf("Pulmonology")},
+ {"radiology", p.Sprintf("Radiology")},
+ {"radiation", p.Sprintf("Radiaton Oncology")},
+ {"transplants", p.Sprintf("Transplant Surgery")},
+ }
+}