diff --git a/Makefile b/Makefile index d8f4935a..cb08798f 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ PHONY: all -run_with_docker: +dockerized: docker-compose -f compose/docker-compose.yml down docker-compose -f compose/docker-compose.yml up --build -d -run_outside_docker: +dev: killall main || echo "Process was not running." docker-compose -f compose/docker-compose_outside_docker.yml down docker-compose -f compose/docker-compose_outside_docker.yml up -d db diff --git a/main.go b/main.go index 6103d0ca..cfd6b72f 100644 --- a/main.go +++ b/main.go @@ -28,6 +28,7 @@ var ( models = []interface{}{ &orm.Question{}, &orm.Answer{}, + &orm.Contest{}, } ) diff --git a/orm/answer.go b/orm/answer.go index 539f5c4a..0d7b4e5e 100644 --- a/orm/answer.go +++ b/orm/answer.go @@ -62,7 +62,7 @@ func (a *Answer) Read(args map[string]string, r *http.Request) (interface{}, err func (a *Answer) ReadAll(args map[string]string, r *http.Request) (interface{}, error) { var answers []*Answer - if err := DB().Order("created_at").Find(&answers).Error; err != nil { + if err := DB().Preload("Question").Order("created_at").Find(&answers).Error; err != nil { return nil, err } return answers, nil @@ -90,6 +90,10 @@ func (a *Answer) Update(args map[string]string, r *http.Request) (interface{}, e if err != nil { return nil, err } + + // FIXME: Should not be hard set. + answer.(*Answer).Correct = false + err = renderer.Decode(answer, r) if err != nil { return nil, err diff --git a/orm/contest.go b/orm/contest.go new file mode 100644 index 00000000..df0c6ce4 --- /dev/null +++ b/orm/contest.go @@ -0,0 +1,136 @@ +package orm + +import ( + "fmt" + + "net/http" + "time" + + "git.andreafazzi.eu/andrea/oef/renderer" + "github.com/jinzhu/gorm" +) + +type Contest struct { + gorm.Model + + Name string + Category string + + Date *time.Time + StartTime *time.Time + EndTime *time.Time + + Questions []*Question +} + +func (c *Contest) GetID() uint { return c.ID } + +func (c *Contest) String() string { + return c.Name +} + +func (c *Contest) Create(args map[string]string, r *http.Request) (interface{}, error) { + if r.Method == "GET" { + return nil, nil + } else { + contest := new(Contest) + err := renderer.Decode(contest, r) + if err != nil { + return nil, err + } + contest, err = CreateContest(contest) + if err != nil { + return nil, err + } + return contest, nil + } +} + +func (c *Contest) Read(args map[string]string, r *http.Request) (interface{}, error) { + var contest Contest + + id := args["id"] + + if err := DB().Preload("Questions").First(&contest, id).Error; err != nil { + return nil, err + } + + return &contest, nil +} + +func (c *Contest) ReadAll(args map[string]string, r *http.Request) (interface{}, error) { + var contests []*Contest + if err := DB().Order("created_at").Find(&contests).Error; err != nil { + return nil, err + } + return contests, nil +} + +func (c *Contest) Update(args map[string]string, r *http.Request) (interface{}, error) { + if r.Method == "GET" { + result, err := c.Read(args, r) + if err != nil { + return nil, err + } + contest := result.(*Contest) + + return contest, nil + } else { + contest, err := c.Read(args, nil) + if err != nil { + return nil, err + } + + err = r.ParseForm() + if err != nil { + return nil, err + } + + date := r.FormValue("Date") + startTime := r.FormValue("StartTime") + endTime := r.FormValue("EndTime") + + r.PostForm.Set("Date", fmt.Sprintf("%sT%s:00+00:00", date, startTime)) + r.PostForm.Set("StartTime", fmt.Sprintf("%sT%s:00+00:00", date, startTime)) + r.PostForm.Set("EndTime", fmt.Sprintf("%sT%s:00+00:00", date, endTime)) + + err = r.ParseForm() + if err != nil { + return nil, err + } + + r.Form["Date"][0] = fmt.Sprintf("%sT%s+00:00", date, startTime) + + err = renderer.Decode(contest, r) + if err != nil { + return nil, err + } + _, err = SaveContest(contest) + if err != nil { + return nil, err + } + contest, err = c.Read(args, nil) + if err != nil { + return nil, err + } + return contest.(*Contest), nil + } +} + +func (c *Contest) Delete(args map[string]string, r *http.Request) (interface{}, error) { + return nil, nil +} + +func CreateContest(contest *Contest) (*Contest, error) { + if err := DB().Create(contest).Error; err != nil { + return nil, err + } + return contest, nil +} + +func SaveContest(contest interface{}) (interface{}, error) { + if err := DB().Omit("Contests").Save(contest).Error; err != nil { + return nil, err + } + return contest, nil +} diff --git a/orm/question.go b/orm/question.go index 437b1949..46f77f3b 100644 --- a/orm/question.go +++ b/orm/question.go @@ -10,9 +10,14 @@ import ( type Question struct { gorm.Model - Text string + Text string + Contest *Contest + ContestID uint `schema:"contest_id"` Answers []*Answer + + SelectedContest map[uint]string `gorm:"-"` + AllContests []*Contest `gorm:"-"` } func (q *Question) GetID() uint { return q.ID } @@ -23,7 +28,11 @@ func (q *Question) String() string { func (q *Question) Create(args map[string]string, r *http.Request) (interface{}, error) { if r.Method == "GET" { - return nil, nil + question := new(Question) + if err := DB().Find(&question.AllContests).Error; err != nil { + return nil, err + } + return question, nil } else { question := new(Question) err := renderer.Decode(question, r) @@ -43,7 +52,7 @@ func (q *Question) Read(args map[string]string, r *http.Request) (interface{}, e id := args["id"] - if err := DB().Preload("Answers").Where("id = ?", id).Find(&question).Error; err != nil { + if err := DB().Preload("Answers").Preload("Contest").First(&question, id).Error; err != nil { return nil, err } @@ -52,7 +61,7 @@ func (q *Question) Read(args map[string]string, r *http.Request) (interface{}, e func (q *Question) ReadAll(args map[string]string, r *http.Request) (interface{}, error) { var questions []*Question - if err := DB().Order("created_at").Find(&questions).Error; err != nil { + if err := DB().Preload("Contest").Order("created_at").Find(&questions).Error; err != nil { return nil, err } return questions, nil @@ -60,7 +69,21 @@ func (q *Question) ReadAll(args map[string]string, r *http.Request) (interface{} func (q *Question) Update(args map[string]string, r *http.Request) (interface{}, error) { if r.Method == "GET" { - return q.Read(args, r) + result, err := q.Read(args, r) + if err != nil { + return nil, err + } + + question := result.(*Question) + + if err := DB().Find(&question.AllContests).Error; err != nil { + return nil, err + } + + question.SelectedContest = make(map[uint]string) + question.SelectedContest[question.ContestID] = "selected" + + return question, nil } else { question, err := q.Read(args, nil) if err != nil { @@ -94,7 +117,7 @@ func CreateQuestion(question *Question) (*Question, error) { } func SaveQuestion(question interface{}) (interface{}, error) { - if err := DB().Omit("Answers").Save(question).Error; err != nil { + if err := DB().Omit("Answers", "Contest").Save(question).Error; err != nil { return nil, err } return question, nil diff --git a/renderer/funcmap.go b/renderer/funcmap.go index f55a9a53..78f4430e 100644 --- a/renderer/funcmap.go +++ b/renderer/funcmap.go @@ -21,6 +21,8 @@ var ( funcMap = template.FuncMap{ "query": query, "convertDate": convertDate, + "convertTime": convertTime, + "prettyDate": prettyDate, "modelPath": modelPath, "dict": dict, "yaml": yaml, @@ -113,6 +115,9 @@ func incr(value int) int { func callString(value interface{}) string { if value != nil { + if reflect.ValueOf(value).Kind() == reflect.String { + return value.(string) + } return reflect.ValueOf(value).MethodByName("String").Interface().(func() string)() } else { return "" @@ -185,13 +190,29 @@ func query(values ...string) template.URL { } func convertDate(value interface{}) string { - t, ok := value.(time.Time) + t, ok := value.(*time.Time) if !ok { return "" } return fmt.Sprintf("%d-%02d-%02d", t.Year(), t.Month(), t.Day()) } +func prettyDate(value interface{}) string { + t, ok := value.(*time.Time) + if !ok { + return "" + } + return fmt.Sprintf("%02d/%02d/%d", t.Day(), t.Month(), t.Year()) +} + +func convertTime(value interface{}) string { + t, ok := value.(*time.Time) + if !ok { + return "" + } + return fmt.Sprintf("%02d:%02d", t.Hour(), t.Minute()) +} + func modelPath(model string, action string, id uint) string { var q template.URL diff --git a/renderer/renderer.go b/renderer/renderer.go index c1e2c357..f1187b58 100644 --- a/renderer/renderer.go +++ b/renderer/renderer.go @@ -280,9 +280,13 @@ func Decode(dst interface{}, r *http.Request) error { if value == "" { return reflect.ValueOf(time.Time{}) } - if v, err := time.Parse("2006-01-02", value); err == nil { + + if v, err := time.Parse(time.RFC3339, value); err == nil { return reflect.ValueOf(v) + } else { + log.Println(value, err) } + return reflect.Value{} } diff --git a/template_generator/templates/add_update.tpl b/template_generator/templates/add_update.tpl index e1322f8b..7788bd4a 100644 --- a/template_generator/templates/add_update.tpl +++ b/template_generator/templates/add_update.tpl @@ -19,7 +19,7 @@ role="form" {{" id={{$form}}>"}} -{{" {{$options := ` { cancelTitle: \"Annulla\", saveTitle: \"Salva\", model: "}}"{{.Model}}"{{" `}} "}} +{{" {{$options := ` { cancelTitle: \"Annulla\", saveTitle: \"Salva\", model: "}}"{{.Model}}"{{" } `}} "}} {{" {{template \"submit_cancel_buttons\" dict \"options\" ($options|yaml) \"id\" (.Data|field \"ID\") \"update\" $update}} "}} diff --git a/templates/answers.html.tpl b/templates/answers.html.tpl index 55a349e5..8ceb8985 100644 --- a/templates/answers.html.tpl +++ b/templates/answers.html.tpl @@ -19,8 +19,11 @@ {{$element|string}}
- {{$options := `noElements: "no subelements"`}} - {{/*template "small" dict "options" ($options | yaml) "data" $element.SubElements*/}} + {{$options := `noElements: "nessuna domanda"`}} + {{template "small" dict "options" ($options | yaml) "data" $element.Question}} + {{if $element.Correct}} + {{template "small" dict "options" ($options | yaml) "data" "corretta"}} + {{end}}
{{end}} diff --git a/templates/answers_add_update.html.tpl b/templates/answers_add_update.html.tpl index e81c8712..f7488819 100644 --- a/templates/answers_add_update.html.tpl +++ b/templates/answers_add_update.html.tpl @@ -5,7 +5,7 @@ {{if $update}} -{{template "breadcrumb" toSlice "Risposte" (all "Answer") (.Data|string) (.Data.ID|show "Answer") "Aggiorna" "current"}} +{{template "breadcrumb" toSlice "Risposte" (all "Answer") (.Data|string|trim) (.Data.ID|show "Answer") "Aggiorna" "current"}} {{else}} {{template "breadcrumb" toSlice "Risposte" (all "Answer") "Aggiungi" "current"}} {{end}} @@ -25,6 +25,9 @@ {{$options := ` { name: "question_id", id: "question_id", label: "Domanda relativa a questa risposta", title: "Seleziona la domanda"}`}} {{template "select" dict "options" ($options|yaml) "data" (.Data|field "AllQuestions") "selected" (.Data|field "Selected") "update" $update "form" $form}} + {{$options := ` { name: "Correct",id: "answer_correct",label: "La risposta è corretta",placeholder: "",type: "checkbox",formClass: "form-check form-check-inline"} `}} + {{template "checkbox" dict "options" ($options|yaml) "value" (.Data|field "Correct") "update" $update}} + {{$options := ` { cancelTitle: "Annulla", saveTitle: "Salva", model: "Answer"} `}} {{template "submit_cancel_buttons" dict "options" ($options|yaml) "id" (.Data|field "ID") "update" $update}} diff --git a/templates/contests.html.tpl b/templates/contests.html.tpl new file mode 100644 index 00000000..f90025f1 --- /dev/null +++ b/templates/contests.html.tpl @@ -0,0 +1,33 @@ +{{ define "content" }} + + +
+{{$options := ` + title: "Gare" + buttonTitle: "Crea nuova gara" + `}} + + {{template "read_all_header" dict "options" ($options | yaml) "lengthData" (len .Data) "modelPath" (create "Contest")}} + {{template "search_input"}} + + {{if not .}} + {{template "display_no_elements"}} + {{else}} +
+ {{range $element := .Data}} + + + {{$element|string}} +
+ {{$options := `noElements: "nessuna data"`}} + {{template "small" dict "options" ($options | yaml) "data" ($element.Date|prettyDate)}} +
+
+ {{end}} + {{end}} +
+ +
+ + +{{ end }} diff --git a/templates/contests_add_update.html.tpl b/templates/contests_add_update.html.tpl new file mode 100644 index 00000000..9edbba04 --- /dev/null +++ b/templates/contests_add_update.html.tpl @@ -0,0 +1,46 @@ +{{ define "content" }} +
+ +{{$update := .Options.Get "update"}} + +{{if $update}} + +{{template "breadcrumb" toSlice "Contests" (all "Contest") (.Data|string) (.Data.ID|show "Contest") "Aggiorna" "current"}} +{{else}} +{{template "breadcrumb" toSlice "Contests" (all "Contest") "Aggiungi" "current"}} +{{end}} + +{{template "add_update_header" dict "update" $update "addTitle" "Crea nuovo ELEMENTO" "updateTitle" (printf "Aggiorna gara %s" (.Data|string))}} +{{$form := "form_add_update"}} +
+ + {{$options := ` { name: "Name",id: "contest_name",label: "Nome della gara",placeholder: "Inserire il nome della gara",type: "text",required: "true"} `}} + {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Name") "update" $update}} + +
+
+ {{$options := ` { name: "Date",id: "contest_date",label: "La gara si svolgerà il giorno",type: "date" } `}} + {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Date"|convertDate) "update" $update}} +
+
+ {{$options := ` { name: "StartTime",id: "contest_start_time",label: "Dalle ore",type: "time" } `}} + {{template "input" dict "options" ($options|yaml) "value" (.Data|field "StartTime"|convertTime) "update" $update}} +
+
+ {{$options := ` { name: "EndTime",id: "contest_end_time",label: "Alle ore",type: "time" } `}} + {{template "input" dict "options" ($options|yaml) "value" (.Data|field "EndTime"|convertTime) "update" $update}} +
+
+ + {{$options := ` { cancelTitle: "Annulla", saveTitle: "Salva", model: "Contest" }`}} + {{template "submit_cancel_buttons" dict "options" ($options|yaml) "id" (.Data|field "ID") "update" $update}} + +
+ +
+{{ end }} diff --git a/templates/contests_show.html.tpl b/templates/contests_show.html.tpl new file mode 100644 index 00000000..949c35a7 --- /dev/null +++ b/templates/contests_show.html.tpl @@ -0,0 +1,27 @@ +{{ define "content" }} + +
+ + {{template "breadcrumb" toSlice "Gare" (all "Contest") (.Data|string) "current"}} + {{template "show_header" dict "title" (.Data|string) "updatePath" (.Data.ID|update "Contest") "deletePath" (.Data.ID|delete "Contest")}} + +

Informazioni generali sulla gara

+

La gara si svolgerà il giorno {{.Data.Date|prettyDate}} dalle ore {{.Data.StartTime|convertTime}} alle ore {{.Data.EndTime|convertTime}}.

+
+
+ + {{$options := ` + title: "Domande" + model: "Question" + icon: "fa fa-question-circle" + `}} + + {{$noElements := "nessuna domanda associata alla gara"}} + {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Questions "noElements" $noElements}} + +
+
+ +
+ +{{ end }} diff --git a/templates/layout/base.html.tpl b/templates/layout/base.html.tpl index 7931a994..644231d0 100644 --- a/templates/layout/base.html.tpl +++ b/templates/layout/base.html.tpl @@ -12,7 +12,7 @@ diff --git a/templates/layout/error.html.tpl b/templates/layout/error.html.tpl index 5a6bdb0b..66f5932a 100644 --- a/templates/layout/error.html.tpl +++ b/templates/layout/error.html.tpl @@ -12,7 +12,7 @@