package main
import (
"bytes"
"html/template"
"net/http"
"strconv"
"git.neuromancer.ovh/bastien-mrq/gitfed/internal/i18n"
"git.neuromancer.ovh/bastien-mrq/gitfed/internal/store"
)
var notificationsTpl = newTpl("notifications", `
{{.Flash}}
<div class="gf-page-head"><h1>{{t .Lang "notif.title"}}</h1></div>
<p class="muted" style="margin-top:-0.6rem;">{{t .Lang "notif.note"}}</p>
<div class="gf-card gf-repo-list">
{{range .Notifications}}
<div class="gf-repo-row">
<div class="gf-repo-icon">{{icon .IconName}}</div>
<div class="gf-repo-main">
<div class="name">{{.Repo}}</div>
{{if eq .Kind "mr_opened"}}
<div class="meta">{{t $.Lang "notif.mr_opened" .Actor .MRNumber .Title}}</div>
{{else if eq .Kind "mr_comment"}}
<div class="meta">{{t $.Lang "notif.mr_comment" .Actor .MRNumber .Title}}</div>
{{else}}
<div class="meta">{{t $.Lang "notif.granted_by"}} {{.Actor}} · {{.FromDomain}} · {{roleLabel $.Lang .Role}}</div>
{{end}}
</div>
{{if .MRURL}}<a href="{{.MRURL}}" class="gf-btn">{{t $.Lang "notif.view"}}</a>{{end}}
{{if eq (print .Status) "pending"}}
<form class="inline" method="post" action="/notifications/accept">
<input type="hidden" name="id" value="{{.ID}}">
<button type="submit" class="gf-btn primary">{{t $.Lang "notif.accept"}}</button>
</form>
<form class="inline" method="post" action="/notifications/dismiss">
<input type="hidden" name="id" value="{{.ID}}">
<button type="submit" class="linklike" title="{{t $.Lang "notif.dismiss"}}" aria-label="{{t $.Lang "notif.dismiss"}}">{{icon "close"}}</button>
</form>
{{else if eq (print .Status) "accepted"}}
<span class="badge trusted">{{t $.Lang "notif.status_accepted"}}</span>
{{else}}
<span class="badge plain">{{t $.Lang "notif.status_dismissed"}}</span>
{{end}}
</div>
{{else}}
<div class="gf-repo-row"><span class="muted">{{t .Lang "notif.empty"}}</span></div>
{{end}}
</div>
`)
// notificationView adds display-only fields to a store.Notification: a
// link to the merge request it's about (mr_opened/mr_comment only) and
// which sprite icon fits its kind — kept out of the store type since
// they're purely a rendering concern.
type notificationView struct {
store.Notification
}
func (n notificationView) IconName() string {
if n.EffectiveKind() != store.NotificationGrant {
return "branch"
}
return "bell"
}
func (n notificationView) MRURL() string {
if n.EffectiveKind() == store.NotificationGrant {
return ""
}
// Repo names can contain "/" (owner/repo-style namespacing) and the
// {repo...} route wildcard expects that literally, so this isn't
// PathEscape'd — same convention as every repo link built directly in
// a template (e.g. repoTpl's "/r/{{.Repo.Name}}").
return "/repo-mr/" + n.Repo + "?number=" + strconv.Itoa(n.MRNumber)
}
func (s *server) handleNotifications(w http.ResponseWriter, r *http.Request) {
sess, _ := s.currentSession(r)
lang := s.lang(r)
notifs, err := s.ops.ListNotifications(sess.Principal)
if err != nil {
s.serverError(w, r, err)
return
}
views := make([]notificationView, len(notifs))
for i, n := range notifs {
views[i] = notificationView{n}
}
var buf bytes.Buffer
_ = notificationsTpl.Execute(&buf, struct {
Notifications []notificationView
Lang string
Flash template.HTML
}{views, string(lang), flash(r)})
s.render(w, r, i18n.T(lang, "notif.title"), "", template.HTML(buf.String()))
}
func (s *server) handleAcceptNotification(w http.ResponseWriter, r *http.Request) {
sess, _ := s.currentSession(r)
if err := s.ops.AcceptNotification(sess.Principal, r.FormValue("id")); err != nil {
s.serverError(w, r, err)
return
}
lang := s.lang(r)
redirectWithMsg(w, r, "/notifications", i18n.T(lang, "notif.msg_accepted"), false)
}
func (s *server) handleDismissNotification(w http.ResponseWriter, r *http.Request) {
sess, _ := s.currentSession(r)
if err := s.ops.DismissNotification(sess.Principal, r.FormValue("id")); err != nil {
s.serverError(w, r, err)
return
}
http.Redirect(w, r, "/notifications", http.StatusSeeOther)
}