Big refactor
This commit is contained in:
parent
9e03bdb2de
commit
cf950db825
4
Makefile
4
Makefile
|
|
@ -1,10 +1,10 @@
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go build bin/shelves.go
|
go build cmd/shelves/main.go
|
||||||
|
|
||||||
run:
|
run:
|
||||||
go run bin/shelves.go
|
go run cmd/shelves/main.go
|
||||||
|
|
||||||
watch:
|
watch:
|
||||||
fd | entr -cr make run
|
fd | entr -cr make run
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
|
|
||||||
"shelves/db"
|
|
||||||
"shelves/httpx"
|
|
||||||
"shelves/routes"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
db, migrateResult, err := db.Open("./shelves.db")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to open DB: %v", err)
|
|
||||||
}
|
|
||||||
if migrateResult.MigrationError != nil {
|
|
||||||
log.Printf("An error was encountered while upgrading the database schema. You are on version %v, but the latest is %v. The error was: %v", migrateResult.SchemaVerNew, migrateResult.SchemaVerLatest, migrateResult.MigrationError)
|
|
||||||
}
|
|
||||||
schemaNeedsUpdating := migrateResult.SchemaVerNew != migrateResult.SchemaVerLatest
|
|
||||||
if schemaNeedsUpdating {
|
|
||||||
log.Printf("Your database schema needs to be updated. The application will continue to run, but you may encounter errors.\n")
|
|
||||||
}
|
|
||||||
_ = db
|
|
||||||
|
|
||||||
routes := routes.Routes()
|
|
||||||
fmt.Println("Listening on localhost:8999")
|
|
||||||
http.ListenAndServe("localhost:8999", httpx.Log(httpx.WithCtx(db, routes)))
|
|
||||||
}
|
|
||||||
28
embeds.go
Normal file
28
embeds.go
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
package shelves
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed frontend/*
|
||||||
|
var fe embed.FS
|
||||||
|
|
||||||
|
var Frontend, _ = fs.Sub(fe, "frontend")
|
||||||
|
|
||||||
|
//go:embed internal/templates/views/*.tmpl.* internal/templates/components/*.tmpl.*
|
||||||
|
var templates embed.FS
|
||||||
|
var Templates, _ = fs.Sub(templates, "internal/templates")
|
||||||
|
|
||||||
|
func printEmbeddedFiles(efs fs.FS) error {
|
||||||
|
return fs.WalkDir(efs, ".", func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !d.IsDir() {
|
||||||
|
fmt.Println(path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
11
frontend.go
11
frontend.go
|
|
@ -1,11 +0,0 @@
|
||||||
package shelves
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"io/fs"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed frontend/*
|
|
||||||
var fe embed.FS
|
|
||||||
|
|
||||||
var Frontend, _ = fs.Sub(fe, "frontend")
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package httpx
|
package httpx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.soup.land/soup/shelves/internal/errorsx"
|
||||||
|
"git.soup.land/soup/shelves/internal/urls"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"shelves/errorsx"
|
|
||||||
"shelves/urls"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func SeeOther(w http.ResponseWriter, location string) {
|
func SeeOther(w http.ResponseWriter, location string) {
|
||||||
|
|
@ -3,9 +3,9 @@ package httpx
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"git.soup.land/soup/shelves/internal/auth"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"shelves/auth"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
26
internal/routes/home.go
Normal file
26
internal/routes/home.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.soup.land/soup/shelves/internal/httpx"
|
||||||
|
"git.soup.land/soup/shelves/internal/templates"
|
||||||
|
"git.soup.land/soup/shelves/internal/templates/components"
|
||||||
|
)
|
||||||
|
|
||||||
|
type homeTemplate struct {
|
||||||
|
components.Page
|
||||||
|
}
|
||||||
|
|
||||||
|
var homeTmpl = templates.MustParseEmbed("views/home.tmpl.html")
|
||||||
|
|
||||||
|
func HomeGet(w http.ResponseWriter, req *http.Request) {
|
||||||
|
ctx := httpx.GetCtx(req)
|
||||||
|
|
||||||
|
h := components.Page{
|
||||||
|
Title: "Home",
|
||||||
|
SessionInfo: ctx.SessionInfo,
|
||||||
|
}.HTML()
|
||||||
|
|
||||||
|
html(w, h)
|
||||||
|
}
|
||||||
|
|
@ -5,11 +5,12 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"shelves/auth"
|
|
||||||
"shelves/errorsx"
|
"git.soup.land/soup/shelves/internal/auth"
|
||||||
"shelves/forms"
|
"git.soup.land/soup/shelves/internal/errorsx"
|
||||||
"shelves/httpx"
|
"git.soup.land/soup/shelves/internal/forms"
|
||||||
"shelves/templates"
|
"git.soup.land/soup/shelves/internal/httpx"
|
||||||
|
"git.soup.land/soup/shelves/internal/templates/components"
|
||||||
)
|
)
|
||||||
|
|
||||||
type loginForm struct {
|
type loginForm struct {
|
||||||
|
|
@ -21,26 +22,32 @@ type loginFormErrors struct {
|
||||||
password error
|
password error
|
||||||
}
|
}
|
||||||
|
|
||||||
func loginRenderView(f loginForm, e loginFormErrors) template.HTML {
|
func loginRenderView(f loginForm, e loginFormErrors, ctx httpx.Ctx) template.HTML {
|
||||||
body := templates.Form{
|
formHtml := components.Form{
|
||||||
Action: "/login",
|
Action: "/login",
|
||||||
Fields: []templates.Field{
|
Fields: []components.Field{
|
||||||
templates.Field{
|
components.Field{
|
||||||
Label: "Password",
|
Label: "Password",
|
||||||
Type: "password",
|
Type: "password",
|
||||||
Placeholder: "Password",
|
Placeholder: "Password",
|
||||||
Name: "password",
|
Name: "password",
|
||||||
Error: errorsx.String(e.password),
|
Error: errorsx.String(e.password),
|
||||||
},
|
},
|
||||||
templates.Field{
|
components.Field{
|
||||||
Type: "hidden",
|
Type: "hidden",
|
||||||
Name: "redirectTo",
|
Name: "redirectTo",
|
||||||
Value: f.redirectTo,
|
Value: f.redirectTo,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}.HTML()
|
||||||
|
|
||||||
|
page := components.Page{
|
||||||
|
Title: "Login",
|
||||||
|
SessionInfo: ctx.SessionInfo,
|
||||||
|
Body: formHtml,
|
||||||
}
|
}
|
||||||
|
|
||||||
return templates.Page{Title: "Login", Body: body.HTML()}.HTML()
|
return page.HTML()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRedirectTo(r *http.Request) string {
|
func getRedirectTo(r *http.Request) string {
|
||||||
|
|
@ -66,7 +73,7 @@ func LoginGet(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
html(w, loginRenderView(loginForm{redirectTo: redirectTo}, loginFormErrors{}))
|
html(w, loginRenderView(loginForm{redirectTo: redirectTo}, loginFormErrors{}, ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
func loginParseForm(f *loginForm, e *loginFormErrors, vs url.Values, v *forms.Validator) {
|
func loginParseForm(f *loginForm, e *loginFormErrors, vs url.Values, v *forms.Validator) {
|
||||||
|
|
@ -84,7 +91,7 @@ func LoginPost(w http.ResponseWriter, r *http.Request) {
|
||||||
})
|
})
|
||||||
if failed {
|
if failed {
|
||||||
httpx.BadRequest(w)
|
httpx.BadRequest(w)
|
||||||
html(w, loginRenderView(form, errs))
|
html(w, loginRenderView(form, errs, ctx))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,7 +109,7 @@ func LoginPost(w http.ResponseWriter, r *http.Request) {
|
||||||
httpx.BadRequest(w)
|
httpx.BadRequest(w)
|
||||||
|
|
||||||
errs.password = errors.New("Incorrect password")
|
errs.password = errors.New("Incorrect password")
|
||||||
html(w, loginRenderView(form, errs))
|
html(w, loginRenderView(form, errs, ctx))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.soup.land/soup/shelves"
|
||||||
|
"git.soup.land/soup/shelves/internal/httpx"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"shelves/httpx"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"shelves"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func html(w http.ResponseWriter, s template.HTML) {
|
func html(w http.ResponseWriter, s template.HTML) {
|
||||||
|
|
@ -6,41 +6,42 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"shelves/auth"
|
|
||||||
"shelves/errorsx"
|
"git.soup.land/soup/shelves/internal/auth"
|
||||||
"shelves/forms"
|
"git.soup.land/soup/shelves/internal/errorsx"
|
||||||
"shelves/httpx"
|
"git.soup.land/soup/shelves/internal/forms"
|
||||||
"shelves/templates"
|
"git.soup.land/soup/shelves/internal/httpx"
|
||||||
|
"git.soup.land/soup/shelves/internal/templates/components"
|
||||||
)
|
)
|
||||||
|
|
||||||
func settingsRenderView(f settingsForm, e settingsFormErrors) template.HTML {
|
func settingsRenderView(f settingsForm, e settingsFormErrors) template.HTML {
|
||||||
body := templates.Form{
|
body := components.Form{
|
||||||
Action: "/settings",
|
Action: "/settings",
|
||||||
Fields: []templates.Field{
|
Fields: []components.Field{
|
||||||
templates.Field{
|
components.Field{
|
||||||
Label: "Display name",
|
Label: "Display name",
|
||||||
Name: "displayName",
|
Name: "displayName",
|
||||||
Placeholder: "Jane Doe",
|
Placeholder: "Jane Doe",
|
||||||
Value: f.displayName,
|
Value: f.displayName,
|
||||||
Error: errorsx.String(e.displayName),
|
Error: errorsx.String(e.displayName),
|
||||||
},
|
},
|
||||||
templates.Field{
|
components.Field{
|
||||||
Label: "Password",
|
Label: "Password",
|
||||||
Type: "password",
|
Type: "password",
|
||||||
Placeholder: "Password",
|
Placeholder: "Password",
|
||||||
Name: "password",
|
Name: "password",
|
||||||
Error: errorsx.String(e.password),
|
Error: errorsx.String(e.password),
|
||||||
},
|
},
|
||||||
templates.Field{
|
components.Field{
|
||||||
Label: "Confirm password",
|
Label: "Confirm password",
|
||||||
Placeholder: "Confirm Password",
|
Placeholder: "Confirm Password",
|
||||||
Type: "password",
|
Type: "password",
|
||||||
Name: "passwordConfirmation",
|
Name: "passwordConfirmation",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}.HTML()
|
||||||
|
|
||||||
return templates.PageBase{Title: "Set up", Body: body.HTML()}.HTML()
|
return components.Page{Title: "Set up", Body: body}.HTML()
|
||||||
}
|
}
|
||||||
|
|
||||||
func queryGetOwnerSettings(db *sql.DB) (settingsForm, error) {
|
func queryGetOwnerSettings(db *sql.DB) (settingsForm, error) {
|
||||||
1
internal/templates/components/component.go
Normal file
1
internal/templates/components/component.go
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
package components
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package templates
|
package components
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.soup.land/soup/shelves/internal/templates"
|
||||||
"html/template"
|
"html/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -28,6 +29,8 @@ type Form struct {
|
||||||
Fields []Field
|
Fields []Field
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var formTmpl = templates.MustParseEmbed("components/form.tmpl.html")
|
||||||
|
|
||||||
func (f Form) HTML() template.HTML {
|
func (f Form) HTML() template.HTML {
|
||||||
if f.Method == "" {
|
if f.Method == "" {
|
||||||
f.Method = "POST"
|
f.Method = "POST"
|
||||||
|
|
@ -37,5 +40,5 @@ func (f Form) HTML() template.HTML {
|
||||||
f.Fields[i].init()
|
f.Fields[i].init()
|
||||||
}
|
}
|
||||||
|
|
||||||
return Tmpls.HTML("form.tmpl.html", f)
|
return templates.HTML(formTmpl, "form", f)
|
||||||
}
|
}
|
||||||
5
internal/templates/components/form.tmpl.html
Normal file
5
internal/templates/components/form.tmpl.html
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{{define "field"}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "form"}}
|
||||||
|
{{end}}
|
||||||
23
internal/templates/components/page.go
Normal file
23
internal/templates/components/page.go
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package components
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.soup.land/soup/shelves/internal/auth"
|
||||||
|
"git.soup.land/soup/shelves/internal/templates"
|
||||||
|
"html/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Page struct {
|
||||||
|
SessionInfo auth.SessionInfo
|
||||||
|
|
||||||
|
Title string
|
||||||
|
Head template.HTML
|
||||||
|
Body template.HTML
|
||||||
|
BodyBefore template.HTML
|
||||||
|
BodyAfter template.HTML
|
||||||
|
}
|
||||||
|
|
||||||
|
var pageTmpl = templates.MustParseEmbed("components/page.tmpl.html")
|
||||||
|
|
||||||
|
func (p Page) HTML() template.HTML {
|
||||||
|
return templates.HTML(pageTmpl, "page", p)
|
||||||
|
}
|
||||||
22
internal/templates/templates.go
Normal file
22
internal/templates/templates.go
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.soup.land/soup/shelves"
|
||||||
|
"git.soup.land/soup/shelves/internal/errorsx"
|
||||||
|
"html/template"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var files = shelves.Templates
|
||||||
|
|
||||||
|
func MustParseEmbed(path string) *template.Template {
|
||||||
|
return template.Must(template.ParseFS(files, path))
|
||||||
|
}
|
||||||
|
|
||||||
|
func HTML(t *template.Template, name string, data any) template.HTML {
|
||||||
|
out := strings.Builder{}
|
||||||
|
err := t.ExecuteTemplate(&out, name, data)
|
||||||
|
out.WriteString(errorsx.String(err))
|
||||||
|
|
||||||
|
return template.HTML(out.String())
|
||||||
|
}
|
||||||
1
internal/templates/views/home.go
Normal file
1
internal/templates/views/home.go
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
package views
|
||||||
15
internal/templates/views/home.tmpl.html
Normal file
15
internal/templates/views/home.tmpl.html
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{{define "Title"}}Home{{end}}
|
||||||
|
|
||||||
|
{{define "Body"}}
|
||||||
|
<section>
|
||||||
|
<h1>Featured Shelves</h1>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h1>Featured Items</h1>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h1>Recent Activity</h1>
|
||||||
|
</section>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{template "page.tmpl.html" .}}
|
||||||
6
internal/templates/views/login.tmpl.html
Normal file
6
internal/templates/views/login.tmpl.html
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{{template "page.tmpl.html" .}}
|
||||||
|
|
||||||
|
{{define "Title"}}Login{{end}}
|
||||||
|
|
||||||
|
{{define "Body"}}
|
||||||
|
{{end}}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
package routes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"shelves/httpx"
|
|
||||||
"shelves/templates"
|
|
||||||
)
|
|
||||||
|
|
||||||
type homeTemplate struct {
|
|
||||||
templates.Page
|
|
||||||
}
|
|
||||||
|
|
||||||
func HomeGet(w http.ResponseWriter, req *http.Request) {
|
|
||||||
ctx := httpx.GetCtx(req)
|
|
||||||
tmpl := homeTemplate{
|
|
||||||
Page: templates.Page{SessionInfo: ctx.SessionInfo},
|
|
||||||
}
|
|
||||||
|
|
||||||
html(w, templates.Tmpls.HTML("home.tmpl.html", tmpl))
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
package templates
|
|
||||||
|
|
||||||
import (
|
|
||||||
"html/template"
|
|
||||||
"shelves/auth"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PageBase struct {
|
|
||||||
Title string
|
|
||||||
Head template.HTML
|
|
||||||
Body template.HTML
|
|
||||||
BodyBefore template.HTML
|
|
||||||
BodyAfter template.HTML
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pb PageBase) HTML() template.HTML {
|
|
||||||
return Tmpls.HTML("page_base.tmpl.html", pb)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Page struct {
|
|
||||||
SessionInfo auth.SessionInfo
|
|
||||||
|
|
||||||
Title string
|
|
||||||
Head template.HTML
|
|
||||||
Body template.HTML
|
|
||||||
BodyBefore template.HTML
|
|
||||||
BodyAfter template.HTML
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Page) HTML() template.HTML {
|
|
||||||
return Tmpls.HTML("page.tmpl.html", p)
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package templates
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed tmpls/*
|
|
||||||
var files embed.FS
|
|
||||||
|
|
||||||
type Template struct {
|
|
||||||
*template.Template
|
|
||||||
}
|
|
||||||
|
|
||||||
var Tmpls = Template{template.Must(template.ParseFS(files, "tmpls/*"))}
|
|
||||||
|
|
||||||
func (tmpl Template) HTML(name string, data any) template.HTML {
|
|
||||||
writer := &strings.Builder{}
|
|
||||||
err := tmpl.ExecuteTemplate(writer, name, data)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprint(writer, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return template.HTML(writer.String())
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
{{define "Title"}}Home{{end}}
|
|
||||||
|
|
||||||
{{template "page.tmpl.html" .}}
|
|
||||||
Loading…
Reference in a new issue