oef/orm/contest.go
2021-01-05 12:28:40 +01:00

271 lines
6.4 KiB
Go

package orm
import (
"fmt"
"log"
"math/rand"
"sort"
"strconv"
"strings"
"net/http"
"time"
"git.andreafazzi.eu/andrea/oef/errors"
"git.andreafazzi.eu/andrea/oef/renderer"
"github.com/dgrijalva/jwt-go"
"github.com/jinzhu/gorm"
)
type Contest struct {
gorm.Model
Name string
Category string
Description string `gorm:"type:varchar(2048)"`
Date time.Time
StartTime time.Time
EndTime time.Time
Duration int // in minutes
NumQuestions int
NumAnswersPerQuestion int
ResetStartTime bool
Questions []*Question
Participants []*Participant `gorm:"many2many:subscriptions"`
Responses []*Response
StartedResponses []*Response
SavedResponses []*Response
Rank []*Response
prevStartTime time.Time
}
func (c *Contest) GetID() uint { return c.ID }
func (c *Contest) String() string {
return c.Name
}
func (c *Contest) Create(db *Database, args map[string]string, w http.ResponseWriter, r *http.Request) (interface{}, error) {
if r.Method == "GET" {
return nil, nil
} else {
contest := new(Contest)
err := r.ParseForm()
if err != nil {
return nil, err
}
date := r.FormValue("Date")
startTime := r.FormValue("StartTime")
endTime := r.FormValue("EndTime")
log.Println("Zero time", date)
if date == "" {
r.PostForm.Set("Date", time.Time{}.Format(time.RFC3339))
r.PostForm.Set("StartTime", time.Time{}.Format(time.RFC3339))
r.PostForm.Set("EndTime", time.Time{}.Format(time.RFC3339))
} else {
r.PostForm.Set("Date", fmt.Sprintf("%sT%s:00+01:00", date, startTime))
r.PostForm.Set("StartTime", fmt.Sprintf("%sT%s:00+01:00", date, startTime))
r.PostForm.Set("EndTime", fmt.Sprintf("%sT%s:00+01:00", date, endTime))
}
err = renderer.Decode(contest, r)
if err != nil {
return nil, err
}
contest, err = CreateContest(db, contest)
if err != nil {
return nil, err
}
return contest, nil
}
}
func (c *Contest) Read(db *Database, args map[string]string, w http.ResponseWriter, r *http.Request) (interface{}, error) {
var contest Contest
id := args["id"]
if err := db._db.
Preload("Participants").
Preload("Responses").
Preload("StartedResponses", func(tx *gorm.DB) *gorm.DB {
return db.DB().Where("start_time <> ?", time.Time{})
}).
Preload("SavedResponses", func(tx *gorm.DB) *gorm.DB {
return db.DB().Where("end_time <> ?", time.Time{})
}).
Preload("Rank", func(tx *gorm.DB) *gorm.DB {
return db.DB().Order("end_time ASC")
}).
Preload("Questions").
First(&contest, id).Error; err != nil {
return nil, err
}
for _, r := range contest.Rank {
if err := calcScoreAndDuration(db, r); err != nil {
return nil, err
}
}
sort.SliceStable(contest.Rank, func(i, j int) bool {
return (contest.Rank[i].Score >= contest.Rank[j].Score) && (contest.Rank[i].Duration < contest.Rank[j].Duration)
})
return &contest, nil
}
func (c *Contest) ReadAll(db *Database, args map[string]string, w http.ResponseWriter, r *http.Request) (interface{}, error) {
var contests []*Contest
claims := r.Context().Value("user").(*jwt.Token).Claims.(jwt.MapClaims)
if claims["admin"].(bool) {
if err := db._db.Order("created_at").Find(&contests).Error; err != nil {
return nil, err
} else {
return contests, nil
}
}
participant := &Participant{}
if err := db._db.Preload("Contests").Where("username = ?", claims["name"].(string)).First(&participant).Error; err != nil {
return nil, err
} else {
return participant.Contests, nil
}
}
func (c *Contest) Update(db *Database, args map[string]string, w http.ResponseWriter, r *http.Request) (interface{}, error) {
if r.Method == "GET" {
result, err := c.Read(db, args, w, r)
if err != nil {
return nil, err
}
contest := result.(*Contest)
return contest, nil
} else {
contest, err := c.Read(db, args, w, r)
if err != nil {
return nil, err
}
// FIXME: Should not be hard set.
contest.(*Contest).ResetStartTime = false
err = r.ParseForm()
if err != nil {
return nil, err
}
date := r.FormValue("Date")
startTime := r.FormValue("StartTime")
endTime := r.FormValue("EndTime")
if date == "" {
r.PostForm.Set("Date", time.Time{}.Format(time.RFC3339))
r.PostForm.Set("StartTime", time.Time{}.Format(time.RFC3339))
r.PostForm.Set("EndTime", time.Time{}.Format(time.RFC3339))
} else {
r.PostForm.Set("Date", fmt.Sprintf("%sT%s:00+01:00", date, startTime))
r.PostForm.Set("StartTime", fmt.Sprintf("%sT%s:00+01:00", date, startTime))
r.PostForm.Set("EndTime", fmt.Sprintf("%sT%s:00+01:00", date, endTime))
}
err = renderer.Decode(contest, r)
if err != nil {
return nil, err
}
_, err = SaveContest(db, contest)
if err != nil {
return nil, err
}
contest, err = c.Read(db, args, w, r)
if err != nil {
return nil, err
}
// Optionally reset participant start time
if contest.(*Contest).ResetStartTime {
err := db._db.Model(&Response{}).Where("contest_id=?", contest.(*Contest).ID).Update("start_time", time.Time{}).Error
if err != nil {
return nil, err
}
}
return contest.(*Contest), nil
}
}
func (model *Contest) Delete(db *Database, args map[string]string, w http.ResponseWriter, r *http.Request) (interface{}, error) {
contest, err := model.Read(db, args, w, r)
if err != nil {
return nil, err
}
if err := db._db.Unscoped().Delete(contest.(*Contest)).Error; err != nil {
return nil, err
}
return contest.(*Contest), nil
}
func CreateContest(db *Database, contest *Contest) (*Contest, error) {
if err := db._db.Create(contest).Error; err != nil {
return nil, err
}
return contest, nil
}
func SaveContest(db *Database, contest interface{}) (interface{}, error) {
if err := db._db.Omit("Contests").Save(contest).Error; err != nil {
return nil, err
}
return contest, nil
}
func (c *Contest) IsAlwaysActive() bool {
return c.StartTime.IsZero() || c.EndTime.IsZero() || c.Duration == 0
}
func (c *Contest) generateQuestionsOrder(db *gorm.DB) (string, error) {
var (
order []string
questions []*Question
)
if err := db.Find(&questions, Question{ContestID: c.ID}).Error; err != nil {
return "", err
}
if len(questions) == 0 {
return "", errors.ContestHasZeroQuestions
}
count := 0
generated := make(map[int]bool, 0)
rand.Seed(time.Now().UnixNano())
for count < len(questions) {
position := rand.Intn(len(questions))
if generated[position] {
continue
}
generated[position] = true
order = append(order, strconv.Itoa(int(questions[position].ID)))
count++
}
return strings.Join(order, " "), nil
}