diff --git a/assets/templates/admin.tmpl b/assets/templates/admin.tmpl index 121433499f6d8f60758abd6aa52f5de7f86009d8..07cde22c038d05e646871e162dc6f3c2c23d07ee 100644 --- a/assets/templates/admin.tmpl +++ b/assets/templates/admin.tmpl @@ -11,87 +11,54 @@ <div class="row"> <div class="col-12 tournament-container"> <div class="my-match-wrap"> - <!-- Turnuva 1 Başlangıç --> - <div class="my-match-box-wrap wow fadeInDown" data-wow-delay=".2s" - style="background-image: url('/static/img/bg/1.png');"> - <img src="/static/img/bg/my_match_box.png" alt="" class="match-box-bg"> - <ul> - <li> - <div class="tournament-image"> - </div> - </li> - <li> - <div class="my-match-info"> - <a href="#" target="_blank" class="live-btn">Canlı Yayın!</a> - <!-- Canlı yayın butonu --> - <h5>AoV 1vs1 Turnuvası</h5> - <span>20 Kasım 2021 20:00</span> - </div> - </li> - <li> - <a href="/" target="_blank" class="watch-stream"><i - class="fas fa-pencil-alt"></i> Düzenle</a> - </li> - </ul> - </div> - <!-- Turnuva 1 Son --> + {{range .tournaments}} + <div class="my-match-box-wrap wow fadeInDown" data-wow-delay=".2s" + style="background-image: url('/static/img/bg/1.png');"> + <img src="/static/img/bg/my_match_box.png" alt="" class="match-box-bg"> + <ul> + <li> + <div class="tournament-image"> + </div> + </li> + <li> + <div class="my-match-info"> + {{/* + TODO: send delete via hidden form + */}} + <a href="" target="_blank" class="live-btn">Delete</a> + <!-- Canlı yayın butonu --> + <h5>{{index .Attributes "title"}}</h5> + <span>{{.StartTimeFormatted}}</span> + </div> + </li> + <li> + <a href="/admin/{{.ID}}" target="_blank" class="watch-stream"><i + class="fas fa-pencil-alt"></i> Düzenle</a> + </li> + </ul> + </div> + {{end}} - <!-- Turnuva 2 Başlangıç --> - <div class="my-match-box-wrap wow fadeInDown" data-wow-delay=".4s" - style="background-image: url('/static/img/bg/2-box.png');"> - <img src="/static/img/bg/my_matchbox_2.png" alt="" class="match-box-bg"> - <ul> - <li> - <div class="tournament-image"> - - </div> - </li> - <li> - <div class="my-match-info"> - <h5>AoV 1vs1 Turnuvası</h5> - <span>20 Kasım 2021 20:00</span> - <span>Son Kayıt: 19 Kasım 23:59</span> - </div> - </li> - <li> - <a href="/" target="_blank" class="watch-stream"><i - class="fas fa-pencil-alt"></i> Düzenle</a> - </li> - </ul> - </div> - <!-- Turnuva 2 Son --> - - <!-- Turnuva 3 Başlangıç --> - <div class="my-match-box-wrap wow fadeInDown" data-wow-delay=".2s" - style="background-image: url('/static/img/bg/box-3.png');"> - <img src="/static/img/bg/my_matchbox_3.png" alt="" class="match-box-bg"> - <ul> - <li> - <div class="tournament-image"> - - </div> - </li> - <li> - <div class="my-match-info"> - <h5>AoV 1vs1 Turnuvası</h5> - <span>20 Kasım 2021 20:00</span> - <span>Son Kayıt: 19 Kasım 23:59</span> - </div> - </li> - <li> - <a href="/" target="_blank" class="watch-stream"><i - class="fas fa-pencil-alt"></i> Düzenle</a> - </li> - </ul> - </div> - <!-- Turnuva 3 Son --> </div> </div> </div> </div> </div> </div> + + <div class="main-menu menu-style-two"> + <nav> + <div id="mobile-menu" class="navbar-wrap d-none d-lg-flex"> + <ul> + <li> + <!--suppress HtmlUnknownTarget --> + <a href="/admin/new"> <i class="fas fa-plus"></i> Create new entry</a> + </li> + </ul> + </div> + </nav> + </div> </section> <!-- slider-area-end --> diff --git a/assets/templates/archetype.tmpl b/assets/templates/archetype.tmpl index 9ca0de51a01c2afbee1a1107542d63434789cb95..00755de5959af3cc2baac9183a685766fc67255d 100644 --- a/assets/templates/archetype.tmpl +++ b/assets/templates/archetype.tmpl @@ -55,7 +55,7 @@ <nav> <div id="mobile-menu" class="navbar-wrap d-none d-lg-flex"> <ul> - <li class="show"><a href="#"> <i class="fas fa-home"></i> Anasayfa</a></li> + <li class="show"><a href="/"> <i class="fas fa-home"></i> Anasayfa</a></li> <li><a href="https://www.instagram.com/arenaofvalor.turkiye/"> <i class="fab fa-instagram"></i> Instagram</a></li> <li><a href="https://discord.gg/aovtr"> <i class="fab fa-discord"></i> @@ -64,7 +64,6 @@ </li> </ul> </div> - </nav> </div> <div class="mobile-menu"></div> diff --git a/assets/templates/index.tmpl b/assets/templates/index.tmpl index b4d2a7eff49d811fe70143af80625f0ea9f9c6c1..5137f5683eca42bd0d227f2c6057772cf006d39a 100644 --- a/assets/templates/index.tmpl +++ b/assets/templates/index.tmpl @@ -22,15 +22,18 @@ </li> <li> <div class="my-match-info"> - <a href="https://www.twitch.tv/arenaofvalortr" target="_blank" - class="live-btn">Canlı Yayın!</a> - <h5>{{.Name}}</h5> - <span>{{.Date}}</span> + {{if .TimePresent}} + <a href="https://www.twitch.tv/arenaofvalortr" target="_blank" + class="live-btn">Canlı Yayın!</a> + {{end}} + <h5>{{index .Attributes "title"}}</h5> + <span>{{.StartTimeFormatted}}</span> </div> </li> <li> - <a href="/enroll/{{.ID}}" target="_blank" class="watch-stream"><i - class="fas fa-podcast"></i> Kayıt Ol</a> + <a href="/enroll/{{.ID}}" target="_blank" class="watch-stream" + {{if .EnrollmentOverdue}} style="pointer-events: none; cursor: default;" {{end}}> + <i class="fas fa-podcast"></i> Kayıt Ol</a> </li> </ul> </div> diff --git a/assets/templates/tournament-config.tmpl b/assets/templates/tournament-config.tmpl index 9f222cf4c03a24994ef6f2e08de9a0f6f46e1859..d7977c8750db2356aa096b68660b83e5ddaa08c4 100644 --- a/assets/templates/tournament-config.tmpl +++ b/assets/templates/tournament-config.tmpl @@ -12,32 +12,49 @@ <h2>TURNUVA <span>EKLE</span></h2> </div> <div class="contact-form"> - <form action="#"> + <form method="post" action=""> <div class="row"> <div class="col-md-12"> - <input type="text" placeholder="Turnuva Başlığı"> + <label> + <input type="text" name="Title" value="{{.title}}" + placeholder="Turnuva Başlığı"> + </label> </div> <div class="col-md-6"> - <input type="date" placeholder="Turnuva Tarihi"> + <label> + Turnuva Tarihi + <input type="date" name="StartTimeString" value="{{.start_time}}" + placeholder="Turnuva Tarihi"> + </label> </div> <div class="col-md-6"> - <input type="text" placeholder="Son Kayıt Tarihi"> + <label> + Son Kayıt Tarihi + <input type="date" name="DeadlineString" value="{{.deadline}}" + placeholder="Son Kayıt Tarihi"> + </label> </div> - <!-- oyuncular --> <div class="col-md-6"> - <select id="cars" name="cars"> - <option value="volvo">1 v 1</option> - <option value="saab">2 v 2</option> - <option value="saab">3 v 3</option> - <option value="fiat">4 v 4</option> - <option value="audi">5 v 5</option> + <label for="size">Oyuncular</label><select id="size" name="TeamSize"> + <option value="0"{{if eq .team_size 0}} selected="selected" {{end}}>1 v 1 + </option> + <option value="1"{{if eq .team_size 1}} selected="selected" {{end}}>2 v 2 + </option> + <option value="2"{{if eq .team_size 2}} selected="selected" {{end}}>3 v 3 + </option> + <option value="3"{{if eq .team_size 3}} selected="selected" {{end}}>4 v 4 + </option> + <option value="4"{{if eq .team_size 4}} selected="selected" {{end}}>5 v 5 + </option> </select> </div> <div class="col-md-6"> - <input type="text" placeholder="Turnuva Ödülü"> + <label> + <input type="text" name="Prize" placeholder="Turnuva Ödülü"> + </label> </div> </div> - <button>EKLE</button> + <button type="submit">EKLE</button> </form> </div> </div> diff --git a/routes.go b/routes.go index 7f77cabef142ed90be570e470326aa5235be2559..d08e2f1c71b5ecbfed0582b6fad989b5eb3c1968 100644 --- a/routes.go +++ b/routes.go @@ -7,29 +7,22 @@ import ( "github.com/google/uuid" "log" "net/http" - "time" + "strconv" ) -type tournament struct { - Title string `json:"name"` - ID uuid.UUID `json:"id"` - StartTime time.Time `json:"start_time"` - EndTime time.Time `json:"end_time"` - TeamSize int `json:"team_size"` -} - func registerRoutes() { router.GET("/", func(context *gin.Context) { authText, authRef := getAuthButton(oauth.GetSelf(context)) - if t, err := getTournaments(); err != nil { + if ts, err := getTournaments(); err != nil { log.Printf("error retrieving tournaments: %s", err) context.String(http.StatusInternalServerError, "Internal Server Error") return } else { + populateTournaments(ts) context.HTML(http.StatusOK, "index.tmpl", gin.H{ "auth_text": authText, "auth_ref": authRef, - "tournaments": t, + "tournaments": ts, }) } }) @@ -60,6 +53,7 @@ func registerRoutes() { } } + populateTournament(t) context.HTML(http.StatusOK, "form.tmpl", gin.H{ "tournament": t, }) @@ -94,6 +88,7 @@ func registerRoutes() { context.String(http.StatusInternalServerError, "Internal Server Error") return } else { + populateTournaments(t) context.HTML(http.StatusOK, "admin.tmpl", gin.H{ "auth_text": authText, "auth_ref": authRef, @@ -102,7 +97,88 @@ func registerRoutes() { } }) - router.POST("/admin", func(context *gin.Context) { + router.GET("/admin/new", 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 + } + + authText, authRef := getAuthButton(user) + + context.HTML(http.StatusOK, "tournament-config.tmpl", gin.H{ + "auth_text": authText, + "auth_ref": authRef, + }) + }) + + router.POST("/admin/new", 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 + } + + payload := tournamentPayload{} + if err := context.Bind(&payload); err != nil { + if config.System.Verbose { + log.Printf("error binding creation form: %s", err) + } + context.String(http.StatusBadRequest, "Bad Request") + return + } + + if !validatePayloadTournament(&payload) { + if config.System.Verbose { + log.Print("invalid data submitted to creation form") + log.Print(payload) + } + context.String(http.StatusBadRequest, "Bad Request") + return + } + + if err := makeTournament(&tournament{ + StartTime: payload.StartTime(), + Deadline: payload.Deadline(), + Attributes: map[string]string{ + "title": payload.Title, + "prize": payload.Prize, + "size": strconv.Itoa(payload.TeamSize), + }, + }); err != nil { + log.Printf("error making tournament: %s", err) + context.String(http.StatusInternalServerError, "Internal Server Error") + } else { + context.Redirect(http.StatusFound, "/admin") + } + }) + + router.GET("/admin/:id", 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 + } + + // TODO: render page + }) + + router.POST("/admin/:id", func(context *gin.Context) { user := oauth.GetSelf(context) if user == nil { context.Redirect(http.StatusTemporaryRedirect, "/auth/login") diff --git a/tournament.go b/tournament.go index 520b8dfc4f3aaf07017bee4e2597e9c8680a4ae3..9195db5280152a39d39d489e3fbfd63c2805f089 100644 --- a/tournament.go +++ b/tournament.go @@ -3,17 +3,75 @@ package main import ( "github.com/google/uuid" json "github.com/json-iterator/go" + "strconv" + "strings" "sync" + "time" ) // TODO: leveldb.ErrNotFound +type tournament struct { + ID uuid.UUID `json:"id"` + StartTime time.Time `json:"start_time"` + Deadline time.Time `json:"deadline"` + Attributes map[string]string `json:"xattr"` + + StartTimeFormatted string `json:"-"` + TimePresent bool `json:"-"` + EnrollmentOverdue bool `json:"-"` +} + +type tournamentPayload struct { + Title string + StartTimeString string + st *time.Time + DeadlineString string + dl *time.Time + TeamSize int + Prize string + Delete bool +} + +func (t *tournamentPayload) StartTime() time.Time { + if t.st == nil { + date := parseDate(t.StartTimeString) + t.st = &date + } + return *t.st +} + +func (t *tournamentPayload) Deadline() time.Time { + if t.dl == nil { + date := parseDate(t.DeadlineString) + t.dl = &date + } + return *t.dl +} + +func parseDate(date string) time.Time { + v := strings.SplitN(date, "-", 3) + if len(v) != 3 { + return time.Time{} + } + d := make([]int, 3) + + for i, s := range v { + if n, err := strconv.Atoi(s); err != nil { + return time.Time{} + } else { + d[i] = n + } + } + return time.Date(d[0], time.Month(d[1]), d[2], 0, 0, 0, 0, time.Local) +} + var tournamentLock = sync.RWMutex{} var ( tournamentsCache []*tournament tournamentsCacheInvalid = true - tournamentCache map[string]*tournament + tournamentCache = make(map[string]*tournament) ) func getTournaments() ([]*tournament, error) { @@ -80,6 +138,18 @@ func getTournament(id uuid.UUID) (*tournament, []byte, error) { } } +func populateTournaments(ts []*tournament) { + for _, t := range ts { + populateTournament(t) + } +} + +func populateTournament(t *tournament) { + t.StartTimeFormatted = t.StartTime.Format("Mon, 02 Jan 2006") + t.TimePresent = time.Now().After(t.StartTime) + t.EnrollmentOverdue = time.Now().After(t.Deadline) +} + func makeTournament(t *tournament) error { t.ID = uuid.New() @@ -152,3 +222,12 @@ func destroyTournament(id uuid.UUID) error { return nil } + +func validatePayloadTournament(payload *tournamentPayload) bool { + return payload.TeamSize >= 0 && + payload.TeamSize <= 4 && + len(payload.Title) < 2048 && + len(payload.Prize) < 4096 && + payload.StartTime().After(payload.Deadline()) && + payload.Deadline().After(time.Now()) +}