add basic lastfm hook
This commit is contained in:
@@ -187,6 +187,7 @@ func main() {
|
|||||||
&db.Track{},
|
&db.Track{},
|
||||||
&db.Cover{},
|
&db.Cover{},
|
||||||
&db.User{},
|
&db.User{},
|
||||||
|
&db.Setting{},
|
||||||
)
|
)
|
||||||
// 🤫🤫🤫
|
// 🤫🤫🤫
|
||||||
orm.Exec(`
|
orm.Exec(`
|
||||||
|
|||||||
@@ -87,10 +87,13 @@ func setAdminRoutes(mux *http.ServeMux) {
|
|||||||
mux.HandleFunc("/admin/home", withUserWare(cont.ServeHome))
|
mux.HandleFunc("/admin/home", withUserWare(cont.ServeHome))
|
||||||
mux.HandleFunc("/admin/change_own_password", withUserWare(cont.ServeChangeOwnPassword))
|
mux.HandleFunc("/admin/change_own_password", withUserWare(cont.ServeChangeOwnPassword))
|
||||||
mux.HandleFunc("/admin/change_own_password_do", withUserWare(cont.ServeChangeOwnPasswordDo))
|
mux.HandleFunc("/admin/change_own_password_do", withUserWare(cont.ServeChangeOwnPasswordDo))
|
||||||
|
mux.HandleFunc("/admin/link_lastfm_callback", withUserWare(cont.ServeLinkLastFMCallback))
|
||||||
mux.HandleFunc("/admin/change_password", withAdminWare(cont.ServeChangePassword))
|
mux.HandleFunc("/admin/change_password", withAdminWare(cont.ServeChangePassword))
|
||||||
mux.HandleFunc("/admin/change_password_do", withAdminWare(cont.ServeChangePasswordDo))
|
mux.HandleFunc("/admin/change_password_do", withAdminWare(cont.ServeChangePasswordDo))
|
||||||
mux.HandleFunc("/admin/create_user", withAdminWare(cont.ServeCreateUser))
|
mux.HandleFunc("/admin/create_user", withAdminWare(cont.ServeCreateUser))
|
||||||
mux.HandleFunc("/admin/create_user_do", withAdminWare(cont.ServeCreateUserDo))
|
mux.HandleFunc("/admin/create_user_do", withAdminWare(cont.ServeCreateUserDo))
|
||||||
|
mux.HandleFunc("/admin/update_lastfm_api_key", withAdminWare(cont.ServeUpdateLastFMAPIKey))
|
||||||
|
mux.HandleFunc("/admin/update_lastfm_api_key_do", withAdminWare(cont.ServeUpdateLastFMAPIKeyDo))
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -55,3 +55,10 @@ type User struct {
|
|||||||
Password string
|
Password string
|
||||||
IsAdmin bool
|
IsAdmin bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setting represents the settings table
|
||||||
|
type Setting struct {
|
||||||
|
CrudBase
|
||||||
|
Key string `gorm:"primary_key;auto_increment:false"`
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -2,6 +2,7 @@ module github.com/sentriz/gonic
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.37.1 // indirect
|
cloud.google.com/go v0.37.1 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 // indirect
|
github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 // indirect
|
||||||
github.com/dhowden/tag v0.0.0-20181104225729-a9f04c2798ca
|
github.com/dhowden/tag v0.0.0-20181104225729-a9f04c2798ca
|
||||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
|
||||||
|
|||||||
@@ -74,6 +74,15 @@ func (c *Controller) ServeChangeOwnPasswordDo(w http.ResponseWriter, r *http.Req
|
|||||||
http.Redirect(w, r, "/admin/home", 303)
|
http.Redirect(w, r, "/admin/home", 303)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ServeLinkLastFMCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
|
token := r.URL.Query().Get("token")
|
||||||
|
if token == "" {
|
||||||
|
http.Error(w, "please provide a token", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = token
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Controller) ServeChangePassword(w http.ResponseWriter, r *http.Request) {
|
func (c *Controller) ServeChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||||
username := r.URL.Query().Get("user")
|
username := r.URL.Query().Get("user")
|
||||||
if username == "" {
|
if username == "" {
|
||||||
@@ -148,3 +157,36 @@ func (c *Controller) ServeCreateUserDo(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
http.Redirect(w, r, "/admin/home", 303)
|
http.Redirect(w, r, "/admin/home", 303)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ServeUpdateLastFMAPIKey(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var data templateData
|
||||||
|
var apiKey db.Setting
|
||||||
|
var secret db.Setting
|
||||||
|
c.DB.Where("key = ?", "lastfm_api_key").First(&apiKey)
|
||||||
|
c.DB.Where("key = ?", "lastfm_secret").First(&secret)
|
||||||
|
data.CurrentLastFMAPIKey = apiKey.Value
|
||||||
|
data.CurrentLastFMAPISecret = secret.Value
|
||||||
|
renderTemplate(w, r, "update_lastfm_api_key", &data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ServeUpdateLastFMAPIKeyDo(w http.ResponseWriter, r *http.Request) {
|
||||||
|
session := r.Context().Value("session").(*sessions.Session)
|
||||||
|
apiKey := r.FormValue("api_key")
|
||||||
|
secret := r.FormValue("secret")
|
||||||
|
err := utilities.ValidateAPIKey(apiKey, secret)
|
||||||
|
if err != nil {
|
||||||
|
session.AddFlash(err.Error())
|
||||||
|
session.Save(r, w)
|
||||||
|
http.Redirect(w, r, r.Header.Get("Referer"), 302)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.DB.
|
||||||
|
Where(db.Setting{Key: "lastfm_api_key"}).
|
||||||
|
Assign(db.Setting{Value: apiKey}).
|
||||||
|
FirstOrCreate(&db.Setting{})
|
||||||
|
c.DB.
|
||||||
|
Where(db.Setting{Key: "lastfm_secret"}).
|
||||||
|
Assign(db.Setting{Value: secret}).
|
||||||
|
FirstOrCreate(&db.Setting{})
|
||||||
|
http.Redirect(w, r, "/admin/home", 303)
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ func init() {
|
|||||||
filepath.Join("templates", "user.tmpl"),
|
filepath.Join("templates", "user.tmpl"),
|
||||||
filepath.Join("templates", "pages", "create_user.tmpl"),
|
filepath.Join("templates", "pages", "create_user.tmpl"),
|
||||||
))
|
))
|
||||||
|
templates["update_lastfm_api_key"] = template.Must(template.ParseFiles(
|
||||||
|
filepath.Join("templates", "layout.tmpl"),
|
||||||
|
filepath.Join("templates", "user.tmpl"),
|
||||||
|
filepath.Join("templates", "pages", "update_lastfm_api_key.tmpl"),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
@@ -55,13 +60,16 @@ type Controller struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type templateData struct {
|
type templateData struct {
|
||||||
Flashes []interface{}
|
Flashes []interface{}
|
||||||
User *db.User
|
User *db.User
|
||||||
SelectedUser *db.User
|
SelectedUser *db.User
|
||||||
AllUsers []*db.User
|
AllUsers []*db.User
|
||||||
ArtistCount uint
|
ArtistCount uint
|
||||||
AlbumCount uint
|
AlbumCount uint
|
||||||
TrackCount uint
|
TrackCount uint
|
||||||
|
CurrentLastFMAPIKey string
|
||||||
|
CurrentLastFMAPISecret string
|
||||||
|
RequestRoot string
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStrParam(r *http.Request, key string) string {
|
func getStrParam(r *http.Request, key string) string {
|
||||||
@@ -146,6 +154,11 @@ func renderTemplate(w http.ResponseWriter, r *http.Request,
|
|||||||
if ok {
|
if ok {
|
||||||
data.User = user
|
data.User = user
|
||||||
}
|
}
|
||||||
|
if r.URL.Host == "" {
|
||||||
|
data.RequestRoot = "http://localhost"
|
||||||
|
} else {
|
||||||
|
data.RequestRoot = fmt.Sprintf("%s://%s", r.URL.Scheme, r.URL.Host)
|
||||||
|
}
|
||||||
err := templates[name].ExecuteTemplate(w, "layout", data)
|
err := templates[name].ExecuteTemplate(w, "layout", data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("500 when executing: %v", err), 500)
|
http.Error(w, fmt.Sprintf("500 when executing: %v", err), 500)
|
||||||
|
|||||||
@@ -18,3 +18,10 @@ func ValidatePasswords(pOne, pTwo string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ValidateAPIKey(apiKey, secret string) error {
|
||||||
|
if apiKey == "" || secret == "" {
|
||||||
|
return fmt.Errorf("please enter both the api key and secret")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
55
lastfm/lastfm.go
Normal file
55
lastfm/lastfm.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package lastfm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
baseURL = "http://ws.audioscrobbler.com/2.0/"
|
||||||
|
client = &http.Client{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func getParamSignature(params url.Values, secret string) string {
|
||||||
|
toHash := ""
|
||||||
|
for k, v := range params {
|
||||||
|
toHash += k
|
||||||
|
toHash += v[0]
|
||||||
|
}
|
||||||
|
toHash += secret
|
||||||
|
hash := md5.Sum([]byte(toHash))
|
||||||
|
return hex.EncodeToString(hash[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSession(apiKey, secret, token string) (error, string) {
|
||||||
|
params := url.Values{}
|
||||||
|
// the first 3 parameters here must be in alphabetical order
|
||||||
|
params.Add("api_key", apiKey)
|
||||||
|
params.Add("method", "auth.getSession")
|
||||||
|
params.Add("token", token)
|
||||||
|
params.Add("api_sig", getParamSignature(params, secret))
|
||||||
|
req, _ := http.NewRequest("GET", baseURL, nil)
|
||||||
|
req.URL.RawQuery = params.Encode()
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error when making request to last.fm: %v", err), ""
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
decoder := xml.NewDecoder(resp.Body)
|
||||||
|
var lastfm LastFM
|
||||||
|
err = decoder.Decode(&lastfm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error when decoding last.fm response: %v", err), ""
|
||||||
|
}
|
||||||
|
if lastfm.Error != nil {
|
||||||
|
return fmt.Errorf("error when parsing last.fm response: %v", lastfm.Error.Value), ""
|
||||||
|
}
|
||||||
|
return nil, lastfm.Session.Key
|
||||||
|
}
|
||||||
21
lastfm/models.go
Normal file
21
lastfm/models.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package lastfm
|
||||||
|
|
||||||
|
import "encoding/xml"
|
||||||
|
|
||||||
|
type LastFM struct {
|
||||||
|
XMLName xml.Name `xml:"lfm"`
|
||||||
|
Status string `xml:"status,attr"`
|
||||||
|
Session *Session `xml:"session"`
|
||||||
|
Error *Error `xml:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
Name string `xml:"name"`
|
||||||
|
Key string `xml:"key"`
|
||||||
|
Subscriber uint `xml:"subscriber"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
Code uint `xml:"code,attr"`
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
@@ -8,6 +8,9 @@ form {
|
|||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* reset from awsm */
|
/* reset from awsm */
|
||||||
@@ -30,6 +33,10 @@ form input[type=password], form input[type=text] {
|
|||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form input[type=submit] {
|
||||||
|
width: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
#content > * {
|
#content > * {
|
||||||
margin: 2rem 0;
|
margin: 2rem 0;
|
||||||
}
|
}
|
||||||
@@ -81,12 +88,3 @@ form input[type=password], form input[type=text] {
|
|||||||
background-color: #fd1b1b1c;
|
background-color: #fd1b1b1c;
|
||||||
}
|
}
|
||||||
|
|
||||||
#login-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
#login-form input[type=submit] {
|
|
||||||
width: 8rem;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
{{ define "user" }}
|
{{ define "user" }}
|
||||||
<div class="padded box mono">
|
<div class="padded box mono">
|
||||||
<div class="box-title">
|
<div class="box-title">
|
||||||
<span><u>changing account password</u></span>
|
<u>changing account password</u>
|
||||||
</div>
|
</div>
|
||||||
<form id="login-form" action="/admin/change_own_password_do" method="post">
|
<form action="/admin/change_own_password_do" method="post">
|
||||||
<input type="password" id="password_one" name="password_one" placeholder="new password">
|
<input type="password" id="password_one" name="password_one" placeholder="new password">
|
||||||
<input type="password" id="password_two" name="password_two" placeholder="verify new password">
|
<input type="password" id="password_two" name="password_two" placeholder="verify new password">
|
||||||
<input type="submit" value="change">
|
<input type="submit" value="change">
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
{{ define "user" }}
|
{{ define "user" }}
|
||||||
<div class="padded box mono">
|
<div class="padded box mono">
|
||||||
<div class="box-title">
|
<div class="box-title">
|
||||||
<span><u>changing {{ .SelectedUser.Name }}'s password</u></span>
|
<u>changing {{ .SelectedUser.Name }}'s password</u>
|
||||||
</div>
|
</div>
|
||||||
<form id="login-form" action="/admin/change_password_do?user={{ .SelectedUser.Name }}" method="post">
|
<form action="/admin/change_password_do?user={{ .SelectedUser.Name }}" method="post">
|
||||||
<input type="password" id="password_one" name="password_one" placeholder="new password">
|
<input type="password" id="password_one" name="password_one" placeholder="new password">
|
||||||
<input type="password" id="password_two" name="password_two" placeholder="verify new password">
|
<input type="password" id="password_two" name="password_two" placeholder="verify new password">
|
||||||
<input type="submit" value="change">
|
<input type="submit" value="change">
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
{{ define "user" }}
|
{{ define "user" }}
|
||||||
<div class="padded box mono">
|
<div class="padded box mono">
|
||||||
<div class="box-title">
|
<div class="box-title">
|
||||||
<span><u>create new user</u></span>
|
<u>create new user</u>
|
||||||
</div>
|
</div>
|
||||||
<form id="login-form" action="/admin/create_user_do" method="post">
|
<form action="/admin/create_user_do" method="post">
|
||||||
<input type="text" id="username" name="username" placeholder="username">
|
<input type="text" id="username" name="username" placeholder="username">
|
||||||
<input type="password" id="password_one" name="password_one" placeholder="password">
|
<input type="password" id="password_one" name="password_one" placeholder="password">
|
||||||
<input type="password" id="password_two" name="password_two" placeholder="verify password">
|
<input type="password" id="password_two" name="password_two" placeholder="verify password">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{{ define "user" }}
|
{{ define "user" }}
|
||||||
<div class="padded box mono">
|
<div class="padded box mono">
|
||||||
<div class="box-title">
|
<div class="box-title">
|
||||||
<span><u>stats</u></span>
|
<u>stats</u>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<span class="pre">artists: {{ printf "%7v" .ArtistCount }}</span><br/>
|
<span class="pre">artists: {{ printf "%7v" .ArtistCount }}</span><br/>
|
||||||
@@ -13,32 +13,32 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="padded box mono">
|
<div class="padded box mono">
|
||||||
<div class="box-title">
|
<div class="box-title">
|
||||||
<span><u>last fm</u></span>
|
<u>last.fm</u>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<span class="pre">unlinked</span><br/>
|
<a href="/admin/update_lastfm_api_key">update last.fm api key</a><br/>
|
||||||
|
<a href="https://www.last.fm/api/auth/?api_key={{ .CurrentLastFMAPIKey }}&cb={{ .RequestRoot }}/admin/link_lastfm_callback">link account</a><br/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="padded box mono">
|
<div class="padded box mono">
|
||||||
{{ if .User.IsAdmin }}
|
{{ if .User.IsAdmin }}
|
||||||
{{/* admin panel to manage all users */}}
|
{{/* admin panel to manage all users */}}
|
||||||
<div class="box-title">
|
<div class="box-title">
|
||||||
<span><u>users</u></span>
|
<u>users</u>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
{{ range $user := .AllUsers }}
|
{{ range $user := .AllUsers }}
|
||||||
<span class="pre">{{ $user.Name }} <span class="light">created</span> <u>{{ $user.CreatedAt.Format "Jan 02, 2006" }}</u> <a href="/admin/change_password?user={{ $user.Name }}">change password</a></span><br/>
|
{{ $user.Name }} <span class="light">created</span> <u>{{ $user.CreatedAt.Format "Jan 02, 2006" }}</u> <a href="/admin/change_password?user={{ $user.Name }}">change password</a><br/>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<br>
|
|
||||||
<a href="/admin/create_user" class="button">create new</a>
|
<a href="/admin/create_user" class="button">create new</a>
|
||||||
</div>
|
</div>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
{{/* user panel to manage themselves */}}
|
{{/* user panel to manage themselves */}}
|
||||||
<div class="box-title">
|
<div class="box-title">
|
||||||
<span><u>your account</u></span>
|
<u>your account</u>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<a href="/admin/change_own_password" class="button">change password</a>
|
<a href="/admin/change_own_password" class="button">change password</a>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="padded box mono">
|
<div class="padded box mono">
|
||||||
<div class="box-title">
|
<div class="box-title">
|
||||||
<span><u>please login</u></span>
|
<u>please login</u>
|
||||||
</div>
|
</div>
|
||||||
<form id="login-form" action="/admin/login_do" method="post">
|
<form action="/admin/login_do" method="post">
|
||||||
<input type="text" id="username" name="username" placeholder="username">
|
<input type="text" id="username" name="username" placeholder="username">
|
||||||
<input type="password" id="password" name="password" placeholder="password">
|
<input type="password" id="password" name="password" placeholder="password">
|
||||||
<input type="submit" value="login">
|
<input type="submit" value="login">
|
||||||
|
|||||||
18
templates/pages/update_lastfm_api_key.tmpl
Normal file
18
templates/pages/update_lastfm_api_key.tmpl
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{{ define "title" }}home{{ end }}
|
||||||
|
|
||||||
|
{{ define "user" }}
|
||||||
|
<div class="padded box mono">
|
||||||
|
<div class="box-title">
|
||||||
|
<u>updating last.fm api key</u>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<span class="light">current key</span> <i>{{ if .CurrentLastFMAPIKey }}{{ .CurrentLastFMAPIKey }}{{ else }}not set{{ end }}</i><br/>
|
||||||
|
<span class="light">current secret</span> <i>{{ if .CurrentLastFMAPISecret }}{{ .CurrentLastFMAPISecret }}{{ else }}not set{{ end }}</i>
|
||||||
|
</div>
|
||||||
|
<form action="/admin/update_lastfm_api_key_do" method="post">
|
||||||
|
<input type="text" id="api_key" name="api_key" placeholder="new key">
|
||||||
|
<input type="text" id="secret" name="secret" placeholder="new secret">
|
||||||
|
<input type="submit" value="update">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="light right">
|
<div class="light right">
|
||||||
<span>welcome <u>{{ .User.Name }}</u> | <a href="/admin/home">home</a> | <a href="/admin/logout">logout</a></span>
|
welcome <u>{{ .User.Name }}</u> | <a href="/admin/home">home</a> | <a href="/admin/logout">logout</a>
|
||||||
</div>
|
</div>
|
||||||
{{ template "user" . }}
|
{{ template "user" . }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
Reference in New Issue
Block a user