add clans

This commit is contained in:
Alicia 2019-02-23 21:24:59 +00:00
parent 3f0bf528a7
commit 697cb30f0f
8 changed files with 733 additions and 0 deletions

107
ccreate.go Normal file
View File

@ -0,0 +1,107 @@
package main
import (
"fmt"
"database/sql"
"strconv"
"strings"
"regexp"
"github.com/gin-gonic/gin"
)
func ccreate(c *gin.Context) {
ccreateResp(c)
}
func ccreateSubmit(c *gin.Context) {
if getContext(c).User.ID == 0 {
resp403(c)
return
}
// check registrations are enabled
if !ccreationEnabled() {
ccreateResp(c, errorMessage{T(c, "Sorry, it's not possible to create a clan at the moment. Please try again later.")})
return
}
// check username is valid by our criteria
username := strings.TrimSpace(c.PostForm("username"))
if !cnameRegex.MatchString(username) {
ccreateResp(c, errorMessage{T(c, "Your clans name must contain alphanumerical characters, spaces, or any of <code>_[]-</code>.")})
return
}
// check whether name already exists
if db.QueryRow("SELECT 1 FROM clans WHERE name = ?", c.PostForm("username")).
Scan(new(int)) != sql.ErrNoRows {
ccreateResp(c, errorMessage{T(c, "A clan with that name already exists!")})
return
}
// check whether tag already exists
if db.QueryRow("SELECT 1 FROM clans WHERE tag = ?", c.PostForm("tag")).
Scan(new(int)) != sql.ErrNoRows {
ccreateResp(c, errorMessage{T(c, "A clan with that tag already exists!")})
return
}
// recaptcha verify
tag := "0"
if c.PostForm("tag") != "" {
tag = c.PostForm("tag")
}
// The actual registration.
res, err := db.Exec(`INSERT INTO clans(name, description, icon, tag)
VALUES (?, ?, ?, ?);`,
username, c.PostForm("password"), c.PostForm("email"), tag)
if err != nil {
ccreateResp(c, errorMessage{T(c, "Whoops, an error slipped in. Clan might have been created, though. I don't know.")})
fmt.Println(err)
return
}
lid, _ := res.LastInsertId()
db.Exec("INSERT INTO `user_clans`(user, clan, perms) VALUES (?, ?, 8);", getContext(c).User.ID, lid)
addMessage(c, successMessage{T(c, "Clan created.")})
getSession(c).Save()
c.Redirect(302, "/c/"+strconv.Itoa(int(lid)))
}
func ccreateResp(c *gin.Context, messages ...message) {
resp(c, 200, "clans/create.html", &baseTemplateData{
TitleBar: "Create Clan",
KyutGrill: "register.jpg",
Scripts: []string{"https://www.google.com/recaptcha/api.js"},
Messages: messages,
FormData: normaliseURLValues(c.Request.PostForm),
})
}
func ccreationEnabled() bool {
var enabled bool
db.QueryRow("SELECT value_int FROM system_settings WHERE name = 'ccreation_enabled'").Scan(&enabled)
return enabled
}
// Check User In Query Is Same As User In Y Cookie
func ccin(s string, ss []string) bool {
for _, x := range ss {
if x == s {
return true
}
}
return false
}
var cnameRegex = regexp.MustCompile(`^[A-Za-z0-9 '_\[\]-]{2,15}$`)

302
clan.go Normal file
View File

@ -0,0 +1,302 @@
package main
import (
"database/sql"
"strconv"
"fmt"
"github.com/gin-gonic/gin"
"math/rand"
"time"
)
// TODO: replace with simple ResponseInfo containing userid
type clanData struct {
baseTemplateData
ClanID int
}
func leaveClan(c *gin.Context) {
i := c.Param("cid")
// login check
if getContext(c).User.ID == 0 {
resp403(c)
return
}
if db.QueryRow("SELECT 1 FROM user_clans WHERE user = ? AND clan = ? AND perms = 8", getContext(c).User.ID, i).
Scan(new(int)) == sql.ErrNoRows {
// check if a nigga the clan
if db.QueryRow("SELECT 1 FROM user_clans WHERE user = ? AND clan = ?", getContext(c).User.ID, i).
Scan(new(int)) == sql.ErrNoRows {
addMessage(c, errorMessage{T(c, "Unexpected Error...")})
return
}
// idk how the fuck this gonna work but fuck it
db.Exec("DELETE FROM user_clans WHERE user = ? AND clan = ?", getContext(c).User.ID, i)
addMessage(c, successMessage{T(c, "Left clan.")})
getSession(c).Save()
c.Redirect(302, "/c/"+i)
} else {
//check if user even in clan!!!
if db.QueryRow("SELECT 1 FROM user_clans WHERE user = ? AND clan = ?", getContext(c).User.ID, i).
Scan(new(int)) == sql.ErrNoRows {
addMessage(c, errorMessage{T(c, "Unexpected Error...")})
return
}
// delete invites
db.Exec("DELETE FROM clans_invites WHERE clan = ?", i)
// delete all members out of clan :c
db.Exec("DELETE FROM user_clans WHERE clan = ?", i)
// delete clan :c
db.Exec("DELETE FROM clans WHERE id = ?", i)
addMessage(c, successMessage{T(c, "Disbanded Clan.")})
getSession(c).Save()
c.Redirect(302, "/clans?mode=0")
}
}
func clanPage(c *gin.Context) {
var (
clanID int
clanName string
clanDescription string
clanIcon string
)
// ctx := getContext(c)
i := c.Param("cid")
if _, err := strconv.Atoi(i); err != nil {
err := db.QueryRow("SELECT id, name, description, icon FROM clans WHERE name = ? LIMIT 1", i).Scan(&clanID, &clanName, &clanDescription, &clanIcon)
if err != nil && err != sql.ErrNoRows {
c.Error(err)
}
} else {
err := db.QueryRow(`SELECT id, name, description, icon FROM clans WHERE id = ? LIMIT 1`, i).Scan(&clanID, &clanName, &clanDescription, &clanIcon)
switch {
case err == nil:
case err == sql.ErrNoRows:
err := db.QueryRow("SELECT id, name, description, icon FROM clans WHERE name = ? LIMIT 1", i).Scan(&clanID, &clanName, &clanDescription, &clanIcon)
if err != nil && err != sql.ErrNoRows {
c.Error(err)
}
default:
c.Error(err)
}
}
data := new(clanData)
data.ClanID = clanID
defer resp(c, 200, "clansample.html", data)
if data.ClanID == 0 {
data.TitleBar = "Clan not found"
data.Messages = append(data.Messages, warningMessage{T(c, "That clan could not be found.")})
return
}
if getContext(c).User.Privileges&1 > 0 {
if db.QueryRow("SELECT 1 FROM clans WHERE clan = ?", clanID).Scan(new(string)) != sql.ErrNoRows {
var bg string
db.QueryRow("SELECT background FROM clans WHERE id = ?", clanID).Scan(&bg)
data.KyutGrill = bg
data.KyutGrillAbsolute = true
}
}
data.TitleBar = T(c, "%s's Clan Page", clanName)
data.DisableHH = true
// data.Scripts = append(data.Scripts, "/static/profile.js")
}
func checkCount(rows *sql.Rows) (count int) {
for rows.Next() {
err:= rows.Scan(&count)
if err != nil {
panic(err)
}
}
return count
}
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func randSeq(n int) string {
rand.Seed(time.Now().UnixNano()+int64(3))
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
func createInvite(c *gin.Context) {
ctx := getContext(c)
if string(c.PostForm("password")) == "" && string(c.PostForm("email")) == "" && string(c.PostForm("tag")) == "" && string(c.PostForm("bg")) == "" {
if ctx.User.ID == 0 {
resp403(c)
return
}
// big perms check lol ok
var perms int
db.QueryRow("SELECT perms FROM user_clans WHERE user = ? AND perms = 8 LIMIT 1", ctx.User.ID).Scan(&perms)
// delete old invite
var clan int
db.QueryRow("SELECT clan FROM user_clans WHERE user = ? AND perms = 8 LIMIT 1", ctx.User.ID).Scan(&clan)
if clan == 0 {
resp403(c)
return
}
db.Exec("DELETE FROM clans_invites WHERE clan = ?", clan)
var s string
s = randSeq(8)
db.Exec("INSERT INTO clans_invites(clan, invite) VALUES (?, ?)", clan, s)
} else {
// big perms check lol ok
var perms int
db.QueryRow("SELECT perms FROM user_clans WHERE user = ? AND perms = 8 LIMIT 1", ctx.User.ID).Scan(&perms)
// delete old invite
var clan int
db.QueryRow("SELECT clan FROM user_clans WHERE user = ? AND perms = 8 LIMIT 1", ctx.User.ID).Scan(&clan)
if clan == 0 {
resp403(c)
return
}
tag := "0"
if c.PostForm("tag") != "" {
tag = c.PostForm("tag")
}
if db.QueryRow("SELECT 1 FROM clans WHERE tag = ? AND id != ?", c.PostForm("tag"), clan).
Scan(new(int)) != sql.ErrNoRows {
resp403(c)
addMessage(c, errorMessage{T(c, "A clan with that tag already exists...")})
return
}
db.Exec("UPDATE clans SET description = ?, icon = ?, tag = ?, background = ? WHERE id = ?", c.PostForm("password"), c.PostForm("email"), tag, c.PostForm("bg"), clan)
}
addMessage(c, successMessage{T(c, "Success")})
getSession(c).Save()
c.Redirect(302, "/settings/clansettings")
}
func clanInvite(c *gin.Context) {
i := c.Param("inv")
res := resolveInvite(i)
s := strconv.Itoa(res)
if res != 0 {
// check if a nigga logged in
if getContext(c).User.ID == 0 {
resp403(c)
return
}
// restricted stuff
if getContext(c).User.Privileges & 1 != 1 {
resp403(c)
return
}
// check if clan even exists?
if db.QueryRow("SELECT 1 FROM clans WHERE id = ?", res).
Scan(new(int)) == sql.ErrNoRows {
addMessage(c, errorMessage{T(c, "Clan doesn't exist.")})
getSession(c).Save()
c.Redirect(302, "/c/"+s)
return
}
// check if a nigga in a clan already
if db.QueryRow("SELECT 1 FROM user_clans WHERE user = ?", getContext(c).User.ID).
Scan(new(int)) != sql.ErrNoRows {
addMessage(c, errorMessage{T(c, "Seems like you're already in a Clan")})
getSession(c).Save()
c.Redirect(302, "/c/"+s)
return
}
// idk how the fuck this gonna work but fuck it
var count int
var limit int
// members check
db.QueryRow("SELECT COUNT(*) FROM user_clans WHERE clan = ? ", res).Scan(&count)
db.QueryRow("SELECT mlimit FROM clans WHERE id = ? ", res).Scan(&limit)
if count >= limit {
addMessage(c, errorMessage{T(c, "Sorry, this clan is full.")})
getSession(c).Save()
c.Redirect(302, "/c/"+s)
return
}
// join
db.Exec("INSERT INTO `user_clans`(user, clan, perms) VALUES (?, ?, 1);", getContext(c).User.ID, res)
addMessage(c, successMessage{T(c, "Joined clan.")})
getSession(c).Save()
c.Redirect(302, "/c/"+s)
} else {
resp403(c)
addMessage(c, errorMessage{T(c, "nah nigga")})
}
}
func clanKick(c *gin.Context) {
if getContext(c).User.ID == 0 {
resp403(c)
return
}
if db.QueryRow("SELECT 1 FROM user_clans WHERE user = ? AND perms = 8", getContext(c).User.ID).
Scan(new(int)) == sql.ErrNoRows {
resp403(c)
return
}
member, err := strconv.ParseInt(c.PostForm("member"), 10, 32)
if err != nil {
fmt.Println(err)
}
if member == 0 {
resp403(c)
return
}
if db.QueryRow("SELECT 1 FROM user_clans WHERE user = ? AND perms = 1", member).
Scan(new(int)) == sql.ErrNoRows {
resp403(c)
return
}
db.Exec("DELETE FROM user_clans WHERE user = ?", member)
addMessage(c, successMessage{T(c, "Success.")})
getSession(c).Save()
c.Redirect(302, "/settings/clansettings")
}
func resolveInvite(c string)(int) {
var clanid int
row := db.QueryRow("SELECT clan FROM clans_invites WHERE invite = ?", c)
err := row.Scan(&clanid)
if err != nil {
fmt.Println(err)
}
fmt.Println(clanid)
return clanid
}

View File

@ -278,6 +278,10 @@ func generateEngine() *gin.Engine {
r.GET("/register/verify", verifyAccount)
r.GET("/register/welcome", welcome)
r.GET("/clans/create", ccreate)
r.POST("/clans/create", ccreateSubmit)
r.GET("/c/:cid", clanPage)
r.GET("/u/:user", userProfile)
r.GET("/b/:bid", beatmapInfo)

59
templates/clan_group.html Normal file
View File

@ -0,0 +1,59 @@
{{/*###
NoCompile=true
*/}}
{{ define "clanGroup" }}
{{ with . }}
<div class="ui one column center aligned stackable grid">
{{ $teamJSON := teamJSON }}
{{ range .members }}
{{/* ignore fokabot */}}
{{ if ne (int .id) 999 }}
{{ $tj := index $teamJSON (print .id)}}
<div class="column">
<div class="ui left aligned fluid card">
<div class="image">
<img src="{{ config "AvatarURL" }}/{{ .id }}" alt="Avatar">
</div>
<div class="content">
<a class="header" href="/u/{{ .id }}">{{ country .country false }}{{ .username }}</a>
{{ with $tj.real_name }}
<div class="meta">
<a>{{ . }}</a>
</div>
{{ end }}
{{ with $tj.role }}
<div class="description">
{{ . }}
</div>
{{ end }}
</div>
<div class="extra content">
<div title="Registered">
<i class="sign in icon"></i>
{{ time .registered_on }}
</div>
<div title="Latest activity">
<i class="sign out icon"></i>
{{ time .latest_activity }}
</div>
</div>
{{ if or $tj.twitter $tj.mail $tj.github }}
<div class="extra content">
<div class="center aligned">
{{ range $k, $v := $tj }}
{{ if and $v (in $k "github" "twitter" "mail") }}
<a href="{{ servicePrefix $k }}{{ $v }}" title="{{ capitalise $k }}">
<i class="{{ $k }} icon"></i>
</a>
{{ end }}
{{ end }}
</div>
</div>
{{ end }}
</div>
</div>
{{ end }}
{{ end }}
</div>
{{ end }}
{{ end }}

View File

@ -0,0 +1,41 @@
{{/*###
NoCompile=true
*/}}
{{ define "clanMembers" }}
{{ with . }}
<div class="ui three column center aligned stackable grid">
{{ $teamJSON := teamJSON }}
{{ range .members }}
{{/* ignore fokabot */}}
{{ if ne (int .id) 999 }}
{{ $tj := index $teamJSON (print .id)}}
<div class="column">
<div class="ui left aligned fluid card">
<div class="image">
<img src="{{ config "AvatarURL" }}/{{ .id }}" alt="Avatar">
</div>
<div class="content">
<a class="header" href="/u/{{ .id }}">{{ country .country false }}{{ .username }}</a>
{{ with $tj.real_name }}
<div class="meta">
<a>{{ . }}</a>
</div>
{{ end }}
</div>
<div class="extra content">
<div title="Registered">
<i class="sign in icon"></i>
{{ time .registered_on }}
</div>
<div title="Latest activity">
<i class="sign out icon"></i>
{{ time .latest_activity }}
</div>
</div>
</div>
</div>
{{ end }}
{{ end }}
</div>
{{ end }}
{{ end }}

44
templates/clanboard.html Normal file
View File

@ -0,0 +1,44 @@
{{/*###
Handler=/clans
TitleBar=Clans
KyutGrill=leaderboard2.jpg
*/}}
{{ define "tpl" }}
<div class="ui container">
{{ $favMode := _or (atoi (.Gin.Query "mode")) }}
<script>
var favouriteMode = {{ $favMode }};
var page = {{ .Gin.Query "p" | atoint | atLeastOne }};
</script>
<div class="ui four item menu" id="mode-menu">
<a class="0 item" href="/clans?mode=0">osu!</a>
<a class="1 item" href="/clans?mode=1">osu!taiko</a>
<a class="2 item" href="/clans?mode=2">osu!catch</a>
<a class="3 item" href="/clans?mode=3">osu!mania</a>
</div>
<table class="ui fixed table">
<thead>
{{ template "simplepag" 4 }}
<tr>
<th class="four wide">{{ .T "Rank" }} </th>
<th class="four wide">{{ .T "Clan" }} </th>
<th class="four wide">{{ .T "PP/Score" }} </th>
<th class="four wide">{{ .T "Playcount" }} </th>
</tr>
</table>
<table class="ui fixed table">
<thead>
{{ range (.Get "clans/stats/all?m=$d" $favMode).clans }}
{{ if .name }}
<tr>
<th class="four wide">#{{ .rank }} </th>
<th class="four wide"> <a href="/c/{{ .id }}?mode={{ $favMode }}">{{ .name }}</a></th>
<th class="four wide">{{ .chosen_mode.pp }}pp ({{ humanize .chosen_mode.total_score }})</th>
<th class="four wide">{{ .chosen_mode.playcount }}</th>
</tr>
{{end}}
{{end}}
</thead>
</table>
</div>
{{ end }}

View File

@ -0,0 +1,48 @@
{{ define "tpl" }}
<div class="ui container">
{{ $regEnabled := qb "SELECT value_int FROM system_settings WHERE name = 'ccreation_enabled'" }}
{{ $isClan := qb "SELECT user, clan FROM user_clans WHERE user = ?" .Context.User.ID }}
{{ if not .Context.User.ID }}
<div class="ui segment">
{{ .T "You need to login!" }}
</div>
{{ else if not ($regEnabled.value_int.Bool) }}
<div class="ui error message">
{{ .T "Sorry, it's not possible to create a clan at the moment. Please try again later." }}
</div>
{{ else if ($isClan) }}
<div class="ui segment">
{{ .T "You're already in a Clan." }}
</div>
{{ else }}
<div class="tiny container">
<div class="ui raised segments">
<div class="ui segment">
<form id="register-form" class="ui form" method="post" action="/clans/create">
<div class="field">
<label>{{ .T "Name (2 to 15 characters, alphanumeric, spaces, <code>_[]-</code>)" | html }}</label>
<input tabindex="1" type="text" name="username" placeholder="{{ .T "Name" }}" value="{{ .FormData.username }}" required pattern="{2,15}$">
</div>
<div class="field">
<label class="left aligned">{{ "Clantag (2 to 6 characters)" }}</label>
<input tabindex="2" type="text" name="tag" placeholder="{{ "Clantag" }}" value="{{ .FormData.tag }}" pattern="{2,6}$">
</div>
<div class="field">
<label>{{ .T "Description (optional)" }}</label>
<input tabindex="3" type="text" name="password" placeholder="{{ .T "Description" }}" value="{{ .FormData.password }}">
</div>
<div class="field">
<label>{{ .T "Icon [URL] (optional)" }}</label>
<input tabindex="4" type="url" name="email" placeholder="{{ .T "Icon URL" }}" value="{{ .FormData.email }}">
</div>
{{ ieForm .Gin }}
</form>
</div>
<div class="ui right aligned segment">
<button tabindex="5" class="ui primary button" type="submit" form="register-form">{{ .T "Submit" }}</button>
</div>
</div>
</div>
{{ end }}
</div>
{{ end }}

128
templates/clansample.html Normal file
View File

@ -0,0 +1,128 @@
{{/*###
Include=clan_members.html
*/}}
{{ define "tpl" }}
<div class="ui container">
{{ if .ClanID }}
{{ $gqm := .Gin.Query "mode" }}
{{ $global := . }}
{{ $favouritemode := $gqm }}
{{ range (.Get "clans?id=%d" .ClanID).clans }}
<div class="ui top attached segment overflow auto aligned">
<div class="magic table">
{{ if .icon }}
<div class="table element">
<img src="{{ .icon }}" alt="icon" class="clan icon" style="border-radius:5%; height:90px;">
</div>
{{ end }}
<div class="table element">
<h1 class="ui header">
{{ $global.T "%s" .name | html }}
</h1>
<div class="subtitle">
{{ $global.T "(%s)" (.tag | htmlescaper) | html }}
</div>
</div>
</div>
</div>
<div class="ui four item bottom attached menu" id="mode-menu">
<a class="mode item" href="/c/{{ .id }}?mode=0">osu!</a>
<a class="mode item" href="/c/{{ .id }}?mode=1">osu!taiko</a>
<a class="mode item" href="/c/{{ .id }}?mode=2">osu!catch</a>
<a class="mode item" href="/c/{{ .id }}?mode=3">osu!mania</a>
</div>
<div class="ui segment">
<div class="ui two column divided stackable grid">
<div class="row">
<div class="column">
{{ $global.T "%s" (.description | htmlescaper) | html }}
{{ end }}
<br><br><table class="ui very basic two column compact table nopad">
<tbody>
{{ with (.Get "clans/stats?id=%d&m=%d" .ClanID (.Gin.Query "mode"))}}
<td></td>
<tr>
<td><b>{{ $global.T "Global Rank" }}</b></td>
<td class="right aligned">#{{ humanize .rank }}</td>
</tr>
<tr>
<td><b>{{ $global.T "PP" }}</b></td>
<td class="right aligned">{{ humanize .chosen_mode.pp }}</td>
</tr>
<tr>
<td><b>{{ $global.T "Ranked Score" }}</b></td>
<td class="right aligned">{{ humanize .chosen_mode.ranked_score }}</td>
</tr>
<tr>
<td><b>{{ $global.T "Total Score" }}</b></td>
<td class="right aligned">{{ humanize .chosen_mode.total_score }}</td>
</tr>
<tr>
<td><b>{{ $global.T "Total Playcount" }}</b></td>
<td class="right aligned">{{ humanize .chosen_mode.playcount }}</td>
</tr>
<tr>
<td><b>{{ $global.T "Total Replays Watched" }}</b></td>
<td class="right aligned">{{ humanize .chosen_mode.replays_watched }}</td>
</tr>
<tr>
<td><b>{{ $global.T "Total Hits" }}</b></td>
<td class="right aligned">{{ humanize .chosen_mode.total_hits }}</td>
</tr>
{{end}}
{{ range (.Get "clans?id=%d" .ClanID).clans }}
</tbody>
</table>{{ end }}
</div>
<div class="column">
{{ if $global.Context.User.ID }}
{{ $d := qb "SELECT user, clan, perms FROM user_clans WHERE user = ? LIMIT 1" .Context.User.ID }}
{{ $p := qb "SELECT user, clan, perms FROM user_clans WHERE user = ? AND perms = 8 LIMIT 1" .Context.User.ID }}
{{ $tc := qb "SELECT user, clan, perms FROM user_clans WHERE user = ? AND clan = ? LIMIT 1" .Context.User.ID .ClanID }}
{{ $uc := or $d.clan.Int -1 }}
{{ if $d }}
{{ if $tc }}
{{ if $p }}
<form id="register-form" class="ui form" method="post" action="/c/{{ .ClanID }}">
{{ ieForm .Gin }}
</form>
<div class="ui one item menu"><button tabindex="1" class="ui primary button" style="background-color:rgba(255,0,0,.5)" type="submit" form="register-form">{{ .T "Disband Clan" }}</button></div>
{{ else }}
<form id="register-form" class="ui form" method="post" action="/c/{{ .ClanID }}">
{{ ieForm .Gin }}
</form>
<div class="ui one item menu"><button tabindex="1" class="ui primary button" style="background-color:rgba(255,0,0,.3)" type="submit" form="register-form">{{ .T "Leave Clan" }}</button></div>
{{ end }}
{{ else }}
<div class="ui one item menu" id="join-menu"><a class="item" style="background-color:rgba(255,0,0,.3)">Already joined a clan</a></div>
{{end}}
{{ else }}
{{ end }}
{{ else }}
<div class="ui one item menu" id="join-menu"><a class="item" href="/login" style="background-color:rgba(0,128,255,.3)">Please login to join a clan</a></div>
{{ end }}
<h1 class="ui heading">{{ .T "Clan Owner" }}</h1>
<p>
{{ .T "The leader of the clan." }}<br>
</p>
{{ template "clanMembers" (.Get "clans/members?id=%d&r=%d" .ClanID 8) }}
</div>
</div>
</div>
<div class="ui aligned segment">
<h1 class="ui heading">{{ .T "Members" }}</h1>
<p>
{{ .T "The members of the clan." }}<br>
</p>
{{ template "clanMembers" (.Get "clans/members?id=%d&r=%d" .ClanID 1) }}
</div>
</div>
{{ end }}
</div>
{{ end }}