diff --git a/assets/templates/admin.tmpl b/assets/templates/admin.tmpl index ad90885589a3f690fed17daf43b0353135fb856c..331cc7545767e832c55a21c52e8852be4b55dff4 100644 --- a/assets/templates/admin.tmpl +++ b/assets/templates/admin.tmpl @@ -23,6 +23,8 @@ </li> <li> <div class="my-match-info"> + <a class="live-btn" type="submit" style="cursor: default" + href="/admin/{{.ID}}/csv">Export</a> <form id="destroy-{{.ID}}" method="post" action="/admin/{{.ID}}"> <input type="hidden" name="Delete" value="true"> <a class="live-btn" type="submit" style="cursor: default" diff --git a/routes.go b/routes.go index bc09d6d5590ab9c952806d39619cc8de94567e2c..88a7d685f16d695021cfbe5d637550ba36fab4d6 100644 --- a/routes.go +++ b/routes.go @@ -257,6 +257,36 @@ func registerRoutes() { }) }) + router.GET("/admin/:id/csv", func(context *gin.Context) { + user := oauth.GetSelf(context) + if user == nil { + context.Redirect(http.StatusTemporaryRedirect, "/auth/login") + return + } + + if !administrators[user.ID] { + context.Redirect(http.StatusTemporaryRedirect, "/") + return + } + + t := contextTournament(context) + if t == nil { + return + } + + if c, err := csvTournament(t.ID); err != nil { + log.Printf("error rendering CSV for tournament %s: %s", t.ID.String(), err) + context.String(http.StatusInternalServerError, "Internal Server Error") + return + } else { + context.Writer.WriteHeader(http.StatusOK) + context.Header("Content-Disposition", "attachment; filename="+t.Attributes["title"]+".csv") + context.Header("Content-Type", "application/x-download") + context.Header("Content-Length", strconv.Itoa(len(c))) + _, _ = context.Writer.Write(c) + } + }) + router.POST("/admin/:id", func(context *gin.Context) { user := oauth.GetSelf(context) if user == nil { diff --git a/tournament.go b/tournament.go index 34e5f4fd2910db60d1252068b2a39042f8a80b42..78ccf86cb539c7d0fb97332063cb6e272980d4fe 100644 --- a/tournament.go +++ b/tournament.go @@ -1,6 +1,9 @@ package main import ( + "bytes" + "encoding/csv" + "fmt" "github.com/gin-gonic/gin" "github.com/google/uuid" json "github.com/json-iterator/go" @@ -289,3 +292,56 @@ func validatePayloadTournament(payload *tournamentPayload) bool { payload.StartTime().After(payload.Deadline()) && payload.Deadline().After(time.Now()) } + +func csvTournament(t uuid.UUID) ([]byte, error) { + var captains []string + if cs, err := getTournamentTeam(t); err != nil { + return nil, err + } else { + captains = cs + } + + if len(captains) == 0 { + return []byte{}, nil + } + + teams := make([]*teamPayload, len(captains)) + for i, captain := range captains { + if team, err := getTeam(t, captain); err != nil { + return nil, err + } else { + teams[i] = team + } + } + + width := 5 + len(teams[0].Players) + records := make([][]string, len(teams)+1) + records[0] = make([]string, width) + records[0][0] = "ID" + records[0][1] = "Team Name" + records[0][2] = "Submitter Discord ID" + records[0][3] = "Submitter First Name" + records[0][4] = "Submitter Last Name" + for i := 3; i < width; i++ { + records[0][i] = fmt.Sprintf("Member %d", i-2) + } + + for i, team := range teams { + records[i+1] = make([]string, width) + records[i+1][0] = strconv.Itoa(i + 1) + records[i+1][1] = team.Name + records[i+1][2] = team.Captain + records[i+1][3] = team.SubmitterFirstName + records[i+1][4] = team.SubmitterLastName + + for j, player := range team.Players { + records[i+1][j+5] = player + } + } + + buf := bytes.NewBuffer(nil) + if err := csv.NewWriter(buf).WriteAll(records); err != nil { + return nil, err + } + return buf.Bytes(), nil +} diff --git a/user.go b/user.go index 774424be79ad3c5c87d0b79acba9ae07d6cce9a0..5e042bc613fc261733caaad804939aa5598e9d40 100644 --- a/user.go +++ b/user.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "github.com/bwmarrin/discordgo" "github.com/gin-gonic/gin" "github.com/google/uuid" @@ -8,6 +9,7 @@ import ( "github.com/syndtr/goleveldb/leveldb" "log" "net/http" + "sync" ) type userEnrollment struct { @@ -30,6 +32,8 @@ type teamPayload struct { Tournament uuid.UUID `json:"tournament"` } +var userLock = sync.RWMutex{} + func putTeam(t *teamPayload) error { var b []byte if p, err := getBytesTournament(t.Tournament); err != nil { @@ -38,6 +42,9 @@ func putTeam(t *teamPayload) error { b = p } + userLock.Lock() + defer userLock.Unlock() + var enrollments []string if data, err := userDB.Get(b, nil); err != nil && !isNotFound(err) { return err @@ -57,7 +64,7 @@ func putTeam(t *teamPayload) error { } } - var tournaments = make(map[string]*teamPayload) + var tournaments = make(map[uuid.UUID]*teamPayload) if data, err := userDB.Get([]byte(t.Captain), nil); err != nil && !isNotFound(err) { return err } else { @@ -66,7 +73,7 @@ func putTeam(t *teamPayload) error { return err } } - tournaments[string(b)] = t + tournaments[t.Tournament] = t if data, err = json.Marshal(tournaments); err != nil { return err } else { @@ -79,6 +86,64 @@ func putTeam(t *teamPayload) error { return nil } +func destroyTeam(t uuid.UUID, captain string) error { + var b []byte + if p, err := getBytesTournament(t); err != nil { + return err + } else { + b = p + } + + userLock.Lock() + defer userLock.Unlock() + + var enrollments []string + if data, err := userDB.Get(b, nil); err != nil { + return err + } else { + if data != nil { + if err = json.Unmarshal(data, &enrollments); err != nil { + return err + } + } + for i, c := range enrollments { + if c == captain { + enrollments[i] = enrollments[len(enrollments)-1] + enrollments = enrollments[:len(enrollments)-1] + break + } + } + if data, err = json.Marshal(enrollments); err != nil { + return err + } else { + if err = userDB.Put(b, data, nil); err != nil { + return err + } + } + } + + var tournaments = make(map[uuid.UUID]*teamPayload) + if data, err := userDB.Get([]byte(captain), nil); err != nil { + return err + } else { + if data != nil { + if err = json.Unmarshal(data, &tournaments); err != nil { + return err + } + } + delete(tournaments, t) + if data, err = json.Marshal(tournaments); err != nil { + return err + } else { + if err = userDB.Put([]byte(captain), data, nil); err != nil { + return err + } + } + } + + return nil +} + func existsTeam(t uuid.UUID, captain string) (bool, error) { if teams, err := getTournamentTeam(t); err != nil { if isNotFound(err) { @@ -103,7 +168,7 @@ func getTeam(t uuid.UUID, captain string) (*teamPayload, error) { b = p } - var payload = make(map[string]*teamPayload) + var payload = make(map[uuid.UUID]*teamPayload) if data, err := userDB.Get([]byte(captain), nil); err != nil && !isNotFound(err) { return nil, err } else { @@ -112,7 +177,9 @@ func getTeam(t uuid.UUID, captain string) (*teamPayload, error) { return nil, err } } - if p, ok := payload[string(b)]; !ok { + if p, ok := payload[t]; !ok { + fmt.Println(payload) + fmt.Println(string(b)) return nil, leveldb.ErrNotFound } else { return p, nil