create basic frontend

This commit is contained in:
sentriz
2019-04-15 23:59:04 +01:00
parent 87efb3b3c5
commit 64fb0fdf82
17 changed files with 372 additions and 22 deletions

View File

@@ -194,11 +194,14 @@ func main() {
SELECT 'albums', 500000
WHERE NOT EXISTS (SELECT * FROM sqlite_sequence)
`)
orm.Exec(`
INSERT INTO users(username, password)
SELECT 'admin', 'admin'
WHERE NOT EXISTS (SELECT * FROM users)
`)
orm.FirstOrCreate(&db.User{}, db.User{
Name: "admin",
Password: "admin",
})
orm.FirstOrCreate(&db.User{}, db.User{
Name: "senan",
Password: "password",
})
startTime := time.Now()
tx = orm.Begin()
err := godirwalk.Walk(os.Args[1], &godirwalk.Options{

View File

@@ -9,6 +9,11 @@ import (
"github.com/sentriz/gonic/handler"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/wader/gormstore"
)
var (
dbCon = db.New()
)
type middleware func(next http.HandlerFunc) http.HandlerFunc
@@ -25,17 +30,15 @@ func newChain(wares ...middleware) middleware {
}
}
func main() {
address := ":5000"
func setSubsonicRoutes(mux *http.ServeMux) {
cont := handler.Controller{
DB: db.New(),
DB: dbCon,
}
withWare := newChain(
cont.LogConnection,
cont.EnableCORS,
cont.CheckParameters,
)
mux := http.NewServeMux()
mux.HandleFunc("/rest/ping", withWare(cont.Ping))
mux.HandleFunc("/rest/ping.view", withWare(cont.Ping))
mux.HandleFunc("/rest/stream", withWare(cont.Stream))
@@ -56,11 +59,31 @@ func main() {
mux.HandleFunc("/rest/getAlbumList2.view", withWare(cont.GetAlbumList))
mux.HandleFunc("/rest/getLicense", withWare(cont.GetLicence))
mux.HandleFunc("/rest/getLicense.view", withWare(cont.GetLicence))
mux.HandleFunc("/", withWare(cont.NotFound))
// mux.HandleFunc("/rest/getMusicDirectory", withWare(cont.GetMusicDirectory))
// mux.HandleFunc("/rest/getMusicDirectory.view", withWare(cont.GetMusicDirectory))
// mux.HandleFunc("/rest/getIndexes", withWare(cont.GetIndexes))
// mux.HandleFunc("/rest/getIndexes.view", withWare(cont.GetIndexes))
}
func setAdminRoutes(mux *http.ServeMux) {
cont := handler.Controller{
DB: dbCon,
SStore: gormstore.New(dbCon, []byte("saynothinboys")),
}
withWare := newChain(
cont.LogConnection,
cont.EnableCORS,
)
server := http.FileServer(http.Dir("static"))
mux.Handle("/admin/static/", http.StripPrefix("/admin/static/", server))
mux.HandleFunc("/admin/login", withWare(cont.ServeLogin))
mux.HandleFunc("/admin/authenticate", withWare(cont.ServeAuthenticate))
mux.HandleFunc("/admin/home", withWare(cont.ServeHome))
mux.HandleFunc("/admin/create_user", withWare(cont.ServeCreateUser))
mux.HandleFunc("/admin/logout", withWare(cont.ServeLogout))
}
func main() {
address := ":5000"
mux := http.NewServeMux()
setSubsonicRoutes(mux)
setAdminRoutes(mux)
server := &http.Server{
Addr: address,
Handler: mux,

View File

@@ -50,7 +50,7 @@ type Cover struct {
// User represents the users table
type User struct {
IDBase
Username string `gorm:"not null;unique_index"`
Base
Name string `gorm:"not null;unique_index"`
Password string
}

2
go.mod
View File

@@ -7,6 +7,7 @@ require (
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
github.com/go-sql-driver/mysql v1.4.1 // indirect
github.com/gofrs/uuid v3.2.0+incompatible // indirect
github.com/gorilla/sessions v1.1.3
github.com/jinzhu/gorm v1.9.2
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect
github.com/jinzhu/now v1.0.0 // indirect
@@ -16,6 +17,7 @@ require (
github.com/mozillazg/go-unidecode v0.1.1
github.com/myesui/uuid v1.0.0 // indirect
github.com/twinj/uuid v1.0.0
github.com/wader/gormstore v0.0.0-20190302154359-acb787ba3755
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a // indirect
google.golang.org/appengine v1.5.0 // indirect
)

8
go.sum
View File

@@ -49,6 +49,12 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57 h1:eqyIo2HjKhKe/mJzTG
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU=
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
@@ -100,6 +106,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/twinj/uuid v1.0.0 h1:fzz7COZnDrXGTAOHGuUGYd6sG+JMq+AoE7+Jlu0przk=
github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY=
github.com/wader/gormstore v0.0.0-20190302154359-acb787ba3755 h1:pNaEDfvqe9W2h4D+xm5f+lnZdao3Rob6O0b8SovpGbE=
github.com/wader/gormstore v0.0.0-20190302154359-acb787ba3755/go.mod h1:PbEnTGtqU8NGCALR62gu2+eQYO8zQDEvaMJiPaj5Hic=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=

77
handler/admin.go Normal file
View File

@@ -0,0 +1,77 @@
package handler
import (
"net/http"
"github.com/sentriz/gonic/db"
)
func (c *Controller) ServeLogin(w http.ResponseWriter, r *http.Request) {
session, _ := c.SStore.Get(r, "gonic")
renderTemplate(w, r, session, "login", &templateData{})
}
func (c *Controller) ServeAuthenticate(w http.ResponseWriter, r *http.Request) {
session, _ := c.SStore.Get(r, "gonic")
username := r.FormValue("username")
password := r.FormValue("password")
if username == "" || password == "" {
session.AddFlash("please provide both a username and password")
session.Save(r, w)
http.Redirect(w, r, "/admin/login", 303)
return
}
var user db.User
c.DB.Where("name = ?", username).First(&user)
if !(username == user.Name && password == user.Password) {
session.AddFlash("invalid username / password")
session.Save(r, w)
http.Redirect(w, r, "/admin/login", 303)
return
}
session.Values["authenticated"] = true
session.Values["user"] = user.ID
session.Save(r, w)
http.Redirect(w, r, "/admin/home", 303)
}
func (c *Controller) ServeHome(w http.ResponseWriter, r *http.Request) {
session, _ := c.SStore.Get(r, "gonic")
authed, _ := session.Values["authenticated"].(bool)
if !authed {
session.AddFlash("you are not authenticated")
session.Save(r, w)
http.Redirect(w, r, "/admin/login", 303)
return
}
var data templateData
var user db.User
c.DB.First(&user, session.Values["user"])
data.UserID = user.ID
data.Username = user.Name
c.DB.Table("album_artists").Count(&data.ArtistCount)
c.DB.Table("albums").Count(&data.AlbumCount)
c.DB.Table("tracks").Count(&data.TrackCount)
c.DB.Find(&data.Users)
renderTemplate(w, r, session, "home", &data)
}
func (c *Controller) ServeCreateUser(w http.ResponseWriter, r *http.Request) {
session, _ := c.SStore.Get(r, "gonic")
authed, _ := session.Values["authenticated"].(bool)
if !authed {
session.AddFlash("you are not authenticated")
session.Save(r, w)
http.Redirect(w, r, "/admin/login", 303)
return
}
renderTemplate(w, r, session, "create_user", &templateData{})
}
func (c *Controller) ServeLogout(w http.ResponseWriter, r *http.Request) {
session, _ := c.SStore.Get(r, "gonic")
delete(session.Values, "authenticated")
delete(session.Values, "user")
session.Save(r, w)
http.Redirect(w, r, "/admin/login", 303)
}

View File

@@ -4,16 +4,55 @@ import (
"encoding/json"
"encoding/xml"
"fmt"
"html/template"
"log"
"net/http"
"path/filepath"
"strconv"
"github.com/jinzhu/gorm"
"github.com/gorilla/sessions"
"github.com/sentriz/gonic/db"
"github.com/sentriz/gonic/subsonic"
"github.com/jinzhu/gorm"
"github.com/wader/gormstore"
)
var (
templates = make(map[string]*template.Template)
)
func init() {
templates["login"] = template.Must(template.ParseFiles(
filepath.Join("templates", "layout.tmpl"),
filepath.Join("templates", "admin", "login.tmpl"),
))
templates["home"] = template.Must(template.ParseFiles(
filepath.Join("templates", "layout.tmpl"),
filepath.Join("templates", "user.tmpl"),
filepath.Join("templates", "admin", "home.tmpl"),
))
templates["create_user"] = template.Must(template.ParseFiles(
filepath.Join("templates", "layout.tmpl"),
filepath.Join("templates", "user.tmpl"),
filepath.Join("templates", "admin", "create_user.tmpl"),
))
}
type Controller struct {
DB *gorm.DB
DB *gorm.DB
SStore *gormstore.Store
Templates map[string]*template.Template
}
type templateData struct {
Flashes []interface{}
UserID uint
Username string
ArtistCount uint
AlbumCount uint
TrackCount uint
Users []*db.User
}
func getStrParam(r *http.Request, key string) string {
@@ -40,7 +79,8 @@ func getIntParamOr(r *http.Request, key string, or int) int {
return val
}
func respondRaw(w http.ResponseWriter, r *http.Request, code int, sub *subsonic.Response) {
func respondRaw(w http.ResponseWriter, r *http.Request,
code int, sub *subsonic.Response) {
res := subsonic.MetaResponse{
Response: sub,
}
@@ -73,12 +113,26 @@ func respondRaw(w http.ResponseWriter, r *http.Request, code int, sub *subsonic.
}
}
func respond(w http.ResponseWriter, r *http.Request, sub *subsonic.Response) {
func respond(w http.ResponseWriter, r *http.Request,
sub *subsonic.Response) {
respondRaw(w, r, http.StatusOK, sub)
}
func respondError(w http.ResponseWriter, r *http.Request, code uint64, message string) {
func respondError(w http.ResponseWriter, r *http.Request,
code uint64, message string) {
respondRaw(w, r, http.StatusBadRequest, subsonic.NewError(
code, message,
))
}
func renderTemplate(w http.ResponseWriter, r *http.Request,
s *sessions.Session, name string, data *templateData) {
flashes := s.Flashes()
s.Save(r, w)
data.Flashes = flashes
err := templates[name].ExecuteTemplate(w, "layout", data)
if err != nil {
http.Error(w, fmt.Sprintf("500 when executing: %v", err), 500)
return
}
}

View File

@@ -62,7 +62,7 @@ func (c *Controller) CheckParameters(next http.HandlerFunc) http.HandlerFunc {
return
}
user := db.User{
Username: username,
Name: username,
}
err := c.DB.Where(user).First(&user).Error
if gorm.IsRecordNotFoundError(err) {

BIN
static/images/gonic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,77 @@
div {
margin: 0;
padding: 0;
}
body {
max-width: 800px;
margin: 0 auto;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
}
form {
max-width: unset;
}
#content > * {
margin: 1rem 0;
}
.right {
text-align: right;
}
.light {
color: #00000082;
}
.mono {
font-family: monospace;
}
.pre {
white-space: pre;
}
.box {
background-color: #0000000a;
}
.padded {
padding: 1rem;
}
#header {
/* background-color: #fdc71b08; */
background-image: linear-gradient(white, #fdad1b0d);
border-bottom: 1px solid;
padding-top: 3rem;
}
#header img {
max-width: 580px;
position: relative;
}
#footer {
background-color: #fdad1b0d;
border-top: 1px solid;
text-align: right;
}
#flashes {
background-color: #fd1b1b29;
}
#login-form {
display: flex;
flex-direction: column;
align-items: flex-end;
}
#login-form input[type=submit] {
width: 8rem;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,12 @@
{{ define "title" }}home{{ end }}
{{ define "user" }}
<div class="padded box mono">
<div>
<span><u>create new user</u></span>
</div>
<div class="right">
<span class="pre">howdy</span><br/>
</div>
</div>
{{ end }}

34
templates/admin/home.tmpl Normal file
View File

@@ -0,0 +1,34 @@
{{ define "title" }}home{{ end }}
{{ define "user" }}
<div class="padded box mono">
<div>
<span><u>stats</u></span>
</div>
<div class="right">
<span class="pre">artists: {{ printf "%7v" .ArtistCount }}</span><br/>
<span class="pre">albums: {{ printf "%7v" .AlbumCount }}</span><br/>
<span class="pre">tracks: {{ printf "%7v" .TrackCount }}</span>
</div>
</div>
<div class="padded box mono">
<div>
<span><u>last.fm</u></span>
</div>
<div class="right">
<span class="pre">unlinked</span><br/>
</div>
</div>
<div class="padded box mono">
<div>
<span><u>users</u></span>
</div>
<div class="right">
{{ range $user := .Users }}
<span class="pre">{{ $user.Name }} <span class="light">created</span> <u>{{ $user.CreatedAt.Format "Jan 02, 2006" }}</u> <a href="/admin/change_password">change password</a></span><br/>
{{ end }}
<br>
<a href="/admin/create_user" class="button">create new</a>
</div>
</div>
{{ end }}

View File

@@ -0,0 +1,12 @@
{{ define "title" }}gonic{{ end }}
{{ define "content" }}
<div class="light">
<span>login</span>
</div>
<form id="login-form" action="/admin/authenticate" method="post">
<input type="text" id="username" name="username" placeholder="username">
<input type="password" id="password" name="password" placeholder="password">
<input type="submit" value="login">
</form>
{{ end }}

30
templates/layout.tmpl Normal file
View File

@@ -0,0 +1,30 @@
{{ define "layout" }}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>{{ template "title" }}</title>
<link rel="stylesheet" href="/admin/static/stylesheets/awsm.css">
<link rel="stylesheet" href="/admin/static/stylesheets/main.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div>
<div id="header">
<img src="/admin/static/images/gonic.png">
</div>
<div id="content">
{{ if .Flashes }}
<div id="flashes">
<p><b>warning:</b> {{ index .Flashes 0 }}</p>
</div>
{{ end }}
{{ template "content" . }}
</div>
</div>
<div id="footer" class="padded">
<p>senan kelly, 2019 &#124; <a href="https://github.com/sentriz/gonic">github</a></p>
</div>
</body>
</html>
{{ end }}

8
templates/user.tmpl Normal file
View File

@@ -0,0 +1,8 @@
{{ define "title" }}home{{ end }}
{{ define "content" }}
<div class="light right">
<span>welcome <u>{{ .Username }}</u> &#124; <a href="/admin/logout">logout</a></span>
</div>
{{ template "user" . }}
{{ end }}