ref: 0f6b28b1933ccf1de2ad1493d3f941b760c07cdd
parent: 3500f3abe40407f49e03ddd9cef4b35e09510060
parent: 1b4520b8cf130ca34181064c5876cee2479dd9ea
author: halfwit <michaelmisch1985@gmail.com>
date: Thu Aug 6 05:34:21 PDT 2020
Merge remote-tracking branch 'forms/master' into master
--- /dev/null
+++ b/.github/workflows/go.yml
@@ -1,0 +1,35 @@
+name: Testing
+
+on:
+ pull_request:
+ branches: [ master ]
+
+jobs:
+
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Configure git for private modules
+ env:
+ TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
+ run: git config --global url."https://halfwit:${TOKEN}@github.com".insteadOf "https://github.com"
+ - name: Set up Go 1.13
+ uses: actions/setup-go@v1
+ with:
+ go-version: 1.13
+ id: go
+
+ - name: Check out code into the Go module directory
+ uses: actions/checkout@v2
+
+ - name: Get dependencies
+ run: |
+ go get -v -t -d ./...
+ if [ -f Gopkg.toml ]; then
+ curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
+ dep ensure
+ fi
+
+ - name: Run Tests
+ run: go test -v ./...
--- /dev/null
+++ b/README.md
@@ -1,0 +1,10 @@
+# forms
+
+Contains form validation code
+
+ - Any code that has validation scopes which are poorly defined, especially dates, must be tested with sanity checks.
+ - Dates for symptoms cannot have an onset in the future
+ - Birthdates cannot be older than 117 years
+ - binary choices, such as true false, must test to show other values cannot be entered
+
+ - The util has helper functions for writing your unit tests. See existing tests for canonical usage
--- /dev/null
+++ b/doctor/application.go
@@ -1,0 +1,79 @@
+package forms
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/albrow/forms"
+ "github.com/olmaxmedical/plugins"
+ "github.com/olmaxmedical/router"
+ "golang.org/x/text/message"
+)
+
+func init() {
+ b := &router.Form{
+ Access: router.GuestAuth,
+ Path: "doctor/application",
+ Validator: application,
+ Redirect: "/index.html",
+ After: plugins.EmailForm | plugins.Countries | plugins.Services | plugins.FormToken,
+ }
+ router.AddPost(b)
+}
+
+func application(r *http.Request, p *message.Printer) []string {
+ var errors []string
+
+ data, err := forms.ParseMax(r, r.ContentLength)
+ if err != nil {
+ errors = append(errors, fmt.Sprintf("validation error %v", err))
+ return errors
+ }
+
+ val := data.Validator()
+ val.Require("gender").Message(p.Sprint("Please select a biological gender"))
+
+ if r.PostFormValue("gender") != "male" && r.PostFormValue("gender") != "female" {
+ val.AddError("gender", p.Sprint("Invalid selection for gender"))
+ }
+
+ val.RequireFile("cv").Message(p.Sprint("Empty or missing CV"))
+ val.AcceptFileExts("cv", "application/msword,applicationvnd.openxmlformats-officedocument.wordprocessingml.document,application/pdf").Message(p.Sprint("unsupported filetype for cv"))
+ val.RequireFile("diploma").Message(p.Sprint("Empty or missing Diploma/Board Certification"))
+ val.AcceptFileExts("diploma", "application/msword,applicationvnd.openxmlformats-officedocument.wordprocessingml.document,application/pdf").Message(p.Sprint("unsupported filetype for diploma"))
+
+ for i := 1; i < 12; i++ {
+ num := fmt.Sprintf("q%d", i)
+
+ sel, ok := r.Form[num]
+ if !ok {
+ val.AddError(num, p.Sprintf("No selection for question %d", i))
+ continue
+ }
+
+ if sel[0] == "Yes" || sel[0] == "yes" || sel[0] == "no" || sel[0] == "No" {
+ continue
+ }
+
+ val.AddError(num, p.Sprintf("Invalid selection for question %d", i))
+ }
+
+ val.Require("email").Message(p.Sprintf("Valid email required"))
+ val.MatchEmail("email").Message(p.Sprintf("Invalid email"))
+ val.Require("name").Message(p.Sprintf("Full name required"))
+ val.MinLength("name", 2).Message(p.Sprintf("Full name must be at least 2 characters"))
+
+ if r.PostFormValue("redFlag") != "on" {
+ val.AddError("redFlag", p.Sprint("Invalid selection for confirm element"))
+ }
+
+ if val.HasErrors() {
+ errors = append(errors, val.Messages()...)
+ }
+
+ r.Form["pagetitle"] = []string{"Application for doctor"}
+ r.Form["sendto"] = []string{"olmaxmedical@gmail.com"}
+ delete(r.Form, "redFlag")
+
+ return errors
+}
--- /dev/null
+++ b/doctor/application_test.go
@@ -1,0 +1,94 @@
+package forms
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "mime/multipart"
+ "net/http"
+ "testing"
+
+ "github.com/olmaxmedical/forms/util"
+ "golang.org/x/text/message"
+)
+
+func TestApplication(t *testing.T) {
+ fields := map[string]string{
+ "qs": "no",
+ "gender": "male",
+ "email": "foo@bar.ca",
+ "name": "Doctor Octopus",
+ "redFlag": "on",
+ }
+
+ if e := testApplication(t, fields); e != nil {
+ t.Error(e)
+ }
+
+ fields["gender"] = "pineapple"
+ if e := testApplication(t, fields); e == nil {
+ t.Error("invalid field accepted")
+ }
+
+ fields["gender"] = "male"
+ fields["email"] = "foo@bar@ca"
+ if e := testApplication(t, fields); e == nil {
+ t.Error("invalid field accepted")
+ }
+
+ fields["email"] = "foo@bar.ca"
+ fields["qs"] = "true"
+ if e := testApplication(t, fields); e == nil {
+ t.Error("invalid field accepted")
+ }
+}
+
+func testApplication(t *testing.T, fields map[string]string) error {
+ var reqBody bytes.Buffer
+
+ mpw := multipart.NewWriter(&reqBody)
+ files := map[string]string{
+ "cv": "resume.pdf",
+ "diploma": "certificate.pdf",
+ }
+
+ for key, value := range files {
+ if e := util.WriteFile(mpw, key, "../resources/"+value); e != nil {
+ panic(e)
+ }
+ }
+
+ for key, value := range fields {
+ if key == "qs" {
+ for i := 0; i < 12; i++ {
+ key = fmt.Sprintf("q%d", i)
+ if e := mpw.WriteField(key, value); e != nil {
+ panic(e)
+ }
+ }
+ continue
+ }
+
+ if e := mpw.WriteField(key, value); e != nil {
+ panic(e)
+ }
+ }
+
+ request := util.BuildMultiRequest(mpw, &reqBody)
+ printer := message.NewPrinter(message.MatchLanguage("en"))
+ return runTest(request, printer)
+}
+
+func runTest(request *http.Request, printer *message.Printer) error {
+ for _, err := range application(request, printer) {
+ switch err {
+ case "unsupported filetype for cv":
+ case "unsupported filetype for diploma":
+ default:
+ return errors.New(err)
+ }
+
+ }
+
+ return nil
+}
--- /dev/null
+++ b/doctor/profile.go
@@ -1,0 +1,53 @@
+package forms
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/albrow/forms"
+ "github.com/olmaxmedical/plugins"
+ "github.com/olmaxmedical/router"
+ "golang.org/x/text/message"
+)
+
+func init() {
+ b := &router.Form{
+ Access: router.DoctorAuth,
+ Path: "doctor/profile",
+ Validator: profile,
+ Redirect: "/doctor/profile.html",
+ After: plugins.FormToken | plugins.AddAppointment,
+ }
+ router.AddPost(b)
+}
+
+func profile(r *http.Request, p *message.Printer) []string {
+ var errors []string
+ data, err := forms.ParseMax(r, r.ContentLength)
+ if err != nil {
+ errors = append(errors, "Internal server error")
+ return errors
+ }
+ val := data.Validator()
+ val.Require("BTCperU").Message(p.Sprint("Please enter a rate (Bitcoin/15min)"))
+ bcu := data.GetFloat("BTCperU")
+ if 0.0 > bcu || bcu > 1.0 {
+ val.AddError("BTCperU", p.Sprint("BTC/15min rate out of range"))
+ }
+ val.Require("startDate").Message(p.Sprint("Start date required"))
+ _, err = time.Parse("2006-01-02T15:04:05", r.Form.Get("startDate"))
+ if err != nil {
+ val.AddError("startDate", p.Sprint("Invalid start-date entered"))
+ }
+
+ val.Require("endDate").Message(p.Sprint("End date required"))
+ _, err = time.Parse("2006-01-02T15:04:05", r.Form.Get("endDate"))
+ if err != nil {
+ val.AddError("endDate", p.Sprint("Invalid end-date entered"))
+ }
+
+ if val.HasErrors() {
+ errors = append(errors, val.Messages()...)
+ }
+ return errors
+}
--- /dev/null
+++ b/doctor/profile_test.go
@@ -1,0 +1,31 @@
+package forms
+
+import (
+ "net/url"
+ "testing"
+
+ "github.com/olmaxmedical/forms/util"
+)
+
+func TestProfile(t *testing.T) {
+ values := url.Values{}
+
+ values.Add("BTCperU", "0.1234")
+ values.Add("startDate", "2020-04-04T00:00:00")
+ values.Add("endDate", "2020-06-06T00:00:00")
+
+ if e := util.TestValues(values, profile); e != nil {
+ t.Error(e)
+ }
+
+ values.Set("BTCperU", "-1")
+ if e := util.TestValues(values, profile); e == nil {
+ t.Error("invalid BTC rate allowed")
+ }
+
+ values.Set("BTCperU", "0.1234")
+ values.Set("startDate", "1995-30-30T23:23:59")
+ if e := util.TestValues(values, profile); e == nil {
+ t.Error("invalid date allowed")
+ }
+}
--- /dev/null
+++ b/go.mod
@@ -1,0 +1,10 @@
+module github.com/olmaxmedical/forms
+
+go 1.14
+
+require (
+ github.com/albrow/forms v0.3.4-0.20170215231405-c4277021bca2
+ github.com/olmaxmedical/plugins v0.0.1
+ github.com/olmaxmedical/router v0.0.1
+ golang.org/x/text v0.3.2
+)
--- /dev/null
+++ b/go.sum
@@ -1,0 +1,34 @@
+github.com/albrow/forms v0.3.4-0.20170215231405-c4277021bca2 h1:SnKbJhjY6YOX6cC0JL19DzxJ4nMZoTFqRJ006tgpclw=
+github.com/albrow/forms v0.3.4-0.20170215231405-c4277021bca2/go.mod h1:jvrM3b0gPuIRiY1E/KmKfPk2XXDEKj7yFB+g9g0BItQ=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/olmaxmedical/database v0.0.0/go.mod h1:BPYEBAP3GYeSeqg5hOaH7GC8+P/VnD2OuHYounbVjQs=
+github.com/olmaxmedical/database v0.0.1 h1:cuocVljXq7cPRS9HygFg2B/WSdzWAZEHrc5uMnN+A0A=
+github.com/olmaxmedical/database v0.0.1/go.mod h1:/5Tl6/p0jpvLpj4GaoFki3wRG/3b+ipNNhM5Dyi6Zf8=
+github.com/olmaxmedical/email v0.0.1 h1:bhOERmPiUmFJqC133s+FFXucSI3dNnfDKsboDYFEbkc=
+github.com/olmaxmedical/email v0.0.1/go.mod h1:bz6en9uc6h9fyu3MW2jTwYW19ZclQ22JkcIxsl3/epc=
+github.com/olmaxmedical/plugins v0.0.1 h1:fON9AAfH635gLKNbytrUdRoRCjhI0ZPP7VFzCVZDVWA=
+github.com/olmaxmedical/plugins v0.0.1/go.mod h1:bHkYv5oh6bk5y1jCZDO9Bk5IyYMPWgZDjMrEifxVgbU=
+github.com/olmaxmedical/router v0.0.1 h1:x3fZ9u00xwKxLvPj8Au0QhIxpmxRv+q2lVMdBmyRQjM=
+github.com/olmaxmedical/router v0.0.1/go.mod h1:28e377pByZCQMBAdERDBhX4wYeTsJgc6U4yOEiz7MsA=
+github.com/olmaxmedical/session v0.0.1 h1:2xdSjpEg89+ClRFLPp/gR3lNxl4JjPAUd2Hsds++FFs=
+github.com/olmaxmedical/session v0.0.1/go.mod h1:XOVyHL+cKa5t2fLDIJtFxwEzJOa3r1hUkwlL4aybdqA=
+github.com/pariz/gountries v0.0.0-20191029140926-233bc78cf5b5 h1:842t0ixg/A4my8/Q3oDNdHIsKYIx02NDlWVEhaiBToo=
+github.com/pariz/gountries v0.0.0-20191029140926-233bc78cf5b5/go.mod h1:U0ETmPPEsfd7CpUKNMYi68xIOL8Ww4jPZlaqNngcwqs=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/scorredoira/email v0.0.0-20191107070024-dc7b732c55da h1:hhmnjfzz7szp75AyXxn8tDfEA0oU4REQLmpuW6zNAOY=
+github.com/scorredoira/email v0.0.0-20191107070024-dc7b732c55da/go.mod h1:Q5ljvYIBpukMH+wgB8kcPV1i9NX8TqU++8GgBKq3pt0=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
--- /dev/null
+++ b/login.go
@@ -1,0 +1,39 @@
+package forms
+
+import (
+ "net/http"
+
+ "github.com/albrow/forms"
+ "github.com/olmaxmedical/plugins"
+ "github.com/olmaxmedical/router"
+ "golang.org/x/text/message"
+)
+
+func init() {
+ b := &router.Form{
+ Access: router.GuestAuth,
+ Path: "login",
+ Validator: login,
+ After: plugins.ValidateLogin,
+ Redirect: "/profile.html",
+ }
+ router.AddPost(b)
+}
+
+func login(r *http.Request, p *message.Printer) []string {
+ var errors []string
+ data, err := forms.Parse(r)
+ if err != nil {
+ errors = append(errors, p.Sprint("Internal server error"))
+ return errors
+ }
+ val := data.Validator()
+ val.Require("email").Message(p.Sprint("Username required"))
+ val.MatchEmail("email").Message(p.Sprint("User name must be a valid email"))
+ val.Require("pass").Message(p.Sprint("Password required"))
+ val.MinLength("pass", 8).Message(p.Sprint("Password must be at least 8 characters"))
+ if val.HasErrors() {
+ errors = append(errors, val.Messages()...)
+ }
+ return errors
+}
--- /dev/null
+++ b/newpassword.go
@@ -1,0 +1,42 @@
+package forms
+
+import (
+ "net/http"
+
+ "github.com/albrow/forms"
+ "github.com/olmaxmedical/plugins"
+ "github.com/olmaxmedical/router"
+ "golang.org/x/text/message"
+)
+
+func init() {
+ b := &router.Form{
+ Access: router.GuestAuth,
+ Path: "newpassword",
+ Validator: newPassword,
+ Redirect: "/login.html",
+ After: plugins.ResetPassword | plugins.FormToken,
+ }
+ router.AddPost(b)
+}
+
+func newPassword(r *http.Request, p *message.Printer) []string {
+ var errors []string
+ data, err := forms.Parse(r)
+ if err != nil {
+ errors = append(errors, "Internal server error")
+ return errors
+ }
+ val := data.Validator()
+ val.Require("password").Message(p.Sprintf("Password required"))
+ val.MinLength("password", 8).Message(p.Sprintf("Password must be at least 8 characters"))
+ val.Require("reenter").Message(p.Sprintf("Re-enter same password"))
+ val.MinLength("reenter", 8).Message(p.Sprintf("Password must be at least 8 characters"))
+ if val.HasErrors() {
+ errors = append(errors, val.Messages()...)
+ }
+ if data.Get("reenter") != data.Get("password") {
+ errors = append(errors, p.Sprint("Passwords do not match"))
+ }
+ return errors
+}
--- /dev/null
+++ b/patient/offer.go
@@ -1,0 +1,53 @@
+package forms
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/albrow/forms"
+ "github.com/olmaxmedical/plugins"
+ "github.com/olmaxmedical/router"
+ "golang.org/x/text/message"
+)
+
+func init() {
+ b := &router.Form{
+ Access: router.PatientAuth,
+ Path: "patient/offer",
+ Validator: offer,
+ After: plugins.Search | plugins.Services, //|plugins.Offer
+ Redirect: "results.html",
+ }
+ router.AddPost(b)
+}
+
+func offer(r *http.Request, p *message.Printer) []string {
+ var errors []string
+ data, err := forms.ParseMax(r, r.ContentLength)
+ if err != nil {
+ errors = append(errors, p.Sprint("Internal server error"))
+ return errors
+ }
+ val := data.Validator()
+ val.Require("Amount").Message(p.Sprint("Please enter a target rate (Bitcoin/15min)"))
+ bcu := data.GetFloat("Amount")
+ if 0.0 > bcu || bcu > 1.0 {
+ val.AddError("Amount", p.Sprint("BTC/15min rate out of range"))
+ }
+ val.Require("startDate").Message(p.Sprint("Start date required"))
+ _, err = time.Parse("2006-01-02T15:04:05", r.Form.Get("startDate"))
+ if err != nil {
+ val.AddError("startDate", p.Sprint("Invalid start-date entered"))
+ }
+
+ val.Require("endDate").Message(p.Sprint("End date required"))
+ _, err = time.Parse("2006-01-02T15:04:05", r.Form.Get("endDate"))
+ if err != nil {
+ val.AddError("endDate", p.Sprint("Invalid end-date entered"))
+ }
+
+ if val.HasErrors() {
+ errors = append(errors, val.Messages()...)
+ }
+ return errors
+}
--- /dev/null
+++ b/patient/offer_test.go
@@ -1,0 +1,31 @@
+package forms
+
+import (
+ "net/url"
+ "testing"
+
+ "github.com/olmaxmedical/forms/util"
+)
+
+func TestOffer(t *testing.T) {
+ values := url.Values{}
+
+ values.Add("Amount", "0.1234")
+ values.Add("startDate", "2020-04-04T00:00:00")
+ values.Add("endDate", "2020-06-06T00:00:00")
+
+ if e := util.TestValues(values, offer); e != nil {
+ t.Error(e)
+ }
+
+ values.Set("Amount", "-1")
+ if e := util.TestValues(values, offer); e == nil {
+ t.Error("invalid BTC rate allowed")
+ }
+
+ values.Set("Amount", "0.1234")
+ values.Set("startDate", "1995-30-30T23:23:59")
+ if e := util.TestValues(values, offer); e == nil {
+ t.Error("invalid date allowed")
+ }
+}
--- /dev/null
+++ b/patient/profile.go
@@ -1,0 +1,35 @@
+package forms
+
+import (
+ "net/http"
+
+ "github.com/albrow/forms"
+ "github.com/olmaxmedical/router"
+ "golang.org/x/text/message"
+)
+
+func init() {
+ b := &router.Form{
+ Access: router.PatientAuth,
+ Path: "patient/profile",
+ Validator: profile,
+ After: 0,
+ Redirect: "/patient/profile.html",
+ }
+ router.AddPost(b)
+}
+
+func profile(r *http.Request, p *message.Printer) []string {
+ var errors []string
+ data, err := forms.Parse(r)
+ if err != nil {
+ errors = append(errors, p.Sprint("Internal server error"))
+ return errors
+ }
+ val := data.Validator()
+ //
+ if val.HasErrors() {
+ errors = append(errors, val.Messages()...)
+ }
+ return errors
+}
--- /dev/null
+++ b/patient/symptoms.go
@@ -1,0 +1,94 @@
+package forms
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/albrow/forms"
+ "github.com/olmaxmedical/plugins"
+ "github.com/olmaxmedical/router"
+ "golang.org/x/text/message"
+)
+
+func init() {
+ b := &router.Form{
+ Access: router.PatientAuth,
+ Path: "patient/symptoms",
+ Validator: symptoms,
+ After: plugins.EmailForm,
+ Redirect: "patient/profile.html",
+ }
+ router.AddPost(b)
+}
+
+func symptoms(r *http.Request, p *message.Printer) []string {
+ var errors []string
+
+ data, err := forms.Parse(r)
+ if err != nil {
+ errors = append(errors, p.Sprint("Internal server error"))
+ return errors
+ }
+
+ val := data.Validator()
+
+ // NOTE(halfwit) This is the current record oldest person
+ // Anything older than this is most definitely invalid
+ oldest := time.Date(1901, 1, 1, 0, 0, 0, 0, time.UTC)
+
+ // NOTE(halfwit) There's potential that symptoms started that day
+ // and the client is in a different time zone, use our tomorrow as a gate
+ youngest := time.Now().Add(time.Hour * 24)
+
+ val.Require("bday").Message(p.Sprint("Birth date required"))
+ if d, e := time.Parse("2006-01-02T15:04:05", r.Form.Get("bday")); e != nil || oldest.After(d) || youngest.Before(d) {
+ val.AddError("bday", p.Sprint("Invalid birth date"))
+ }
+
+ val.Require("onset").Message(p.Sprint("Please enter the date and time your symptoms started"))
+ if d, e := time.Parse("2006-01-02T15:04:05", r.Form.Get("onset")); e != nil || oldest.After(d) || youngest.Before(d) {
+ val.AddError("bday", p.Sprint("Invalid date"))
+ }
+
+ val.Require("gender").Message(p.Sprint("Please select a biological gender"))
+ if r.PostFormValue("gender") != "male" && r.PostFormValue("gender") != "female" {
+ val.AddError("gender", p.Sprint("Invalid selection for gender"))
+ }
+
+ val.GreaterOrEqual("duration", 0).Message(p.Sprint("Invalid value entered for how long symptoms have lasted"))
+ val.Require("reason").Message(p.Sprint("Please provide the reason for visit"))
+ val.Require("location").Message(p.Sprint("Please list the area the symptom(s) appear"))
+ val.Require("characteristic").Message(p.Sprint("Please provide a description of your symptoms"))
+ val.Require("aggreAlevi").Message(p.Sprint("Please note anything which improves/worsens your symptoms"))
+
+ for _, i := range []string{
+ "feversChills",
+ "wtGainLoss",
+ "vision",
+ "lung",
+ "heart",
+ "bowel",
+ "renal",
+ "musSkel",
+ "neuro",
+ "psych",
+ } {
+ sel, ok := r.Form[i]
+ if !ok {
+ val.AddError(i, p.Sprintf("No selection for %s", i))
+ continue
+ }
+
+ if sel[0] == "Yes" || sel[0] == "yes" || sel[0] == "no" || sel[0] == "No" {
+ continue
+ }
+
+ val.AddError(i, p.Sprintf("Invalid selection for %s", i))
+ }
+
+ r.Form["pagetitle"] = []string{"Client symptoms"}
+ if val.HasErrors() {
+ errors = append(errors, val.Messages()...)
+ }
+ return errors
+}
--- /dev/null
+++ b/patient/symptoms_test.go
@@ -1,0 +1,53 @@
+package forms
+
+import (
+ "net/url"
+ "testing"
+ "time"
+
+ "github.com/olmaxmedical/forms/util"
+)
+
+func TestSymptoms(t *testing.T) {
+ values := url.Values{}
+
+ values.Add("bday", "1990-01-01T01:01:01")
+ values.Add("onset", "2001-01-01T01:01:01")
+ values.Add("gender", "male")
+ values.Add("duration", "1")
+ values.Add("reason", "test")
+ values.Add("location", "test")
+ values.Add("characteristic", "test")
+ values.Add("aggreAlevi", "test")
+ for _, i := range []string{
+ "feversChills",
+ "wtGainLoss",
+ "vision",
+ "lung",
+ "heart",
+ "bowel",
+ "renal",
+ "musSkel",
+ "neuro",
+ "psych",
+ } {
+ values.Add(i, "yes")
+ }
+
+ if e := util.TestValues(values, symptoms); e != nil {
+ t.Error(e)
+ }
+
+ values.Set("bday", "1891-01-01T01:01:01")
+
+ if e := util.TestValues(values, symptoms); e == nil {
+ t.Error("forms parsing: invalid date accepted")
+ }
+
+ values.Set("bday", "1990-01-01T01:01:01")
+ values.Set("onset", time.Now().Add(time.Hour+48).String())
+
+ if e := util.TestValues(values, symptoms); e == nil {
+ t.Error("form parsing: invalid onset accepted")
+ }
+}
--- /dev/null
+++ b/resetpassword.go
@@ -1,0 +1,37 @@
+package forms
+
+import (
+ "net/http"
+
+ "github.com/albrow/forms"
+ "github.com/olmaxmedical/plugins"
+ "github.com/olmaxmedical/router"
+ "golang.org/x/text/message"
+)
+
+func init() {
+ b := &router.Form{
+ Access: router.GuestAuth,
+ Path: "resetpassword",
+ Validator: reset,
+ Redirect: "/login.html",
+ After: plugins.ResetPassword,
+ }
+ router.AddPost(b)
+}
+
+func reset(r *http.Request, p *message.Printer) []string {
+ var errors []string
+ data, err := forms.Parse(r)
+ if err != nil {
+ errors = append(errors, "Internal server error")
+ return errors
+ }
+ val := data.Validator()
+ val.Require("email").Message(p.Sprintf("Valid email required"))
+ val.MatchEmail("email").Message(p.Sprintf("Invalid email"))
+ if val.HasErrors() {
+ errors = append(errors, val.Messages()...)
+ }
+ return errors
+}
binary files /dev/null b/resources/certificate.pdf differ
binary files /dev/null b/resources/resume.pdf differ
--- /dev/null
+++ b/signup.go
@@ -1,0 +1,43 @@
+package forms
+
+import (
+ "net/http"
+
+ "github.com/albrow/forms"
+ "github.com/olmaxmedical/plugins"
+ "github.com/olmaxmedical/router"
+ "golang.org/x/text/message"
+)
+
+func init() {
+ b := &router.Form{
+ Access: router.GuestAuth,
+ Path: "signup",
+ Validator: signin,
+ Redirect: "/login.html",
+ After: plugins.SendSignup,
+ }
+ router.AddPost(b)
+}
+
+func signin(r *http.Request, p *message.Printer) []string {
+ var errors []string
+ data, err := forms.Parse(r)
+ if err != nil {
+ errors = append(errors, "Internal server error")
+ return errors
+ }
+ val := data.Validator()
+ val.Require("fname").Message(p.Sprintf("First name required"))
+ val.MinLength("fname", 2).Message(p.Sprintf("First name must be at least 2 characters"))
+ val.Require("lname").Message(p.Sprintf("Last name required"))
+ val.MinLength("lname", 2).Message(p.Sprintf("Last name must be at least 2 characters"))
+ val.Require("email").Message(p.Sprintf("Valid email required"))
+ val.MatchEmail("email").Message(p.Sprintf("Invalid email"))
+ val.Require("pass").Message(p.Sprintf("Password required"))
+ val.MinLength("pass", 8).Message(p.Sprintf("Password must be at least 8 characters"))
+ if val.HasErrors() {
+ errors = append(errors, val.Messages()...)
+ }
+ return errors
+}
--- /dev/null
+++ b/util/testing.go
@@ -1,0 +1,54 @@
+package util
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "mime/multipart"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "os"
+
+ "golang.org/x/text/message"
+)
+
+func BuildMultiRequest(mpw *multipart.Writer, buff *bytes.Buffer) *http.Request {
+ mpw.Close()
+
+ req := httptest.NewRequest("POST", "/", buff)
+ req.Header.Add("Content-Type", "multipart/form-data; boundary="+mpw.Boundary())
+
+ return req
+}
+
+func WriteFile(mpw *multipart.Writer, key, path string) error {
+ file, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+
+ defer file.Close()
+
+ fw, err := mpw.CreateFormFile(key, path)
+ if err != nil {
+ return err
+ }
+
+ _, err = io.Copy(fw, file)
+ return err
+}
+
+func TestValues(values url.Values, fn func(*http.Request, *message.Printer) []string) error {
+ req := httptest.NewRequest("GET", "/", nil)
+
+ req.PostForm = values
+ req.Header.Set("Content-Type", "form-urlencoded")
+
+ printer := message.NewPrinter(message.MatchLanguage("en"))
+ for _, err := range fn(req, printer) {
+ return errors.New(err)
+ }
+
+ return nil
+}