Big refactor
This commit is contained in:
parent
9e03bdb2de
commit
cf950db825
4
Makefile
4
Makefile
|
|
@ -1,10 +1,10 @@
|
|||
.PHONY: build
|
||||
|
||||
build:
|
||||
go build bin/shelves.go
|
||||
go build cmd/shelves/main.go
|
||||
|
||||
run:
|
||||
go run bin/shelves.go
|
||||
go run cmd/shelves/main.go
|
||||
|
||||
watch:
|
||||
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
|
||||
|
||||
import (
|
||||
"git.soup.land/soup/shelves/internal/errorsx"
|
||||
"git.soup.land/soup/shelves/internal/urls"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"shelves/errorsx"
|
||||
"shelves/urls"
|
||||
)
|
||||
|
||||
func SeeOther(w http.ResponseWriter, location string) {
|
||||
|
|
@ -3,9 +3,9 @@ package httpx
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"git.soup.land/soup/shelves/internal/auth"
|
||||
"log"
|
||||
"net/http"
|
||||
"shelves/auth"
|
||||
"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"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"shelves/auth"
|
||||
"shelves/errorsx"
|
||||
"shelves/forms"
|
||||
"shelves/httpx"
|
||||
"shelves/templates"
|
||||
|
||||
"git.soup.land/soup/shelves/internal/auth"
|
||||
"git.soup.land/soup/shelves/internal/errorsx"
|
||||
"git.soup.land/soup/shelves/internal/forms"
|
||||
"git.soup.land/soup/shelves/internal/httpx"
|
||||
"git.soup.land/soup/shelves/internal/templates/components"
|
||||
)
|
||||
|
||||
type loginForm struct {
|
||||
|
|
@ -21,26 +22,32 @@ type loginFormErrors struct {
|
|||
password error
|
||||
}
|
||||
|
||||
func loginRenderView(f loginForm, e loginFormErrors) template.HTML {
|
||||
body := templates.Form{
|
||||
func loginRenderView(f loginForm, e loginFormErrors, ctx httpx.Ctx) template.HTML {
|
||||
formHtml := components.Form{
|
||||
Action: "/login",
|
||||
Fields: []templates.Field{
|
||||
templates.Field{
|
||||
Fields: []components.Field{
|
||||
components.Field{
|
||||
Label: "Password",
|
||||
Type: "password",
|
||||
Placeholder: "Password",
|
||||
Name: "password",
|
||||
Error: errorsx.String(e.password),
|
||||
},
|
||||
templates.Field{
|
||||
components.Field{
|
||||
Type: "hidden",
|
||||
Name: "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 {
|
||||
|
|
@ -66,7 +73,7 @@ func LoginGet(w http.ResponseWriter, r *http.Request) {
|
|||
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) {
|
||||
|
|
@ -84,7 +91,7 @@ func LoginPost(w http.ResponseWriter, r *http.Request) {
|
|||
})
|
||||
if failed {
|
||||
httpx.BadRequest(w)
|
||||
html(w, loginRenderView(form, errs))
|
||||
html(w, loginRenderView(form, errs, ctx))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +109,7 @@ func LoginPost(w http.ResponseWriter, r *http.Request) {
|
|||
httpx.BadRequest(w)
|
||||
|
||||
errs.password = errors.New("Incorrect password")
|
||||
html(w, loginRenderView(form, errs))
|
||||
html(w, loginRenderView(form, errs, ctx))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"git.soup.land/soup/shelves"
|
||||
"git.soup.land/soup/shelves/internal/httpx"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"shelves/httpx"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"shelves"
|
||||
)
|
||||
|
||||
func html(w http.ResponseWriter, s template.HTML) {
|
||||
|
|
@ -6,41 +6,42 @@ import (
|
|||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"shelves/auth"
|
||||
"shelves/errorsx"
|
||||
"shelves/forms"
|
||||
"shelves/httpx"
|
||||
"shelves/templates"
|
||||
|
||||
"git.soup.land/soup/shelves/internal/auth"
|
||||
"git.soup.land/soup/shelves/internal/errorsx"
|
||||
"git.soup.land/soup/shelves/internal/forms"
|
||||
"git.soup.land/soup/shelves/internal/httpx"
|
||||
"git.soup.land/soup/shelves/internal/templates/components"
|
||||
)
|
||||
|
||||
func settingsRenderView(f settingsForm, e settingsFormErrors) template.HTML {
|
||||
body := templates.Form{
|
||||
body := components.Form{
|
||||
Action: "/settings",
|
||||
Fields: []templates.Field{
|
||||
templates.Field{
|
||||
Fields: []components.Field{
|
||||
components.Field{
|
||||
Label: "Display name",
|
||||
Name: "displayName",
|
||||
Placeholder: "Jane Doe",
|
||||
Value: f.displayName,
|
||||
Error: errorsx.String(e.displayName),
|
||||
},
|
||||
templates.Field{
|
||||
components.Field{
|
||||
Label: "Password",
|
||||
Type: "password",
|
||||
Placeholder: "Password",
|
||||
Name: "password",
|
||||
Error: errorsx.String(e.password),
|
||||
},
|
||||
templates.Field{
|
||||
components.Field{
|
||||
Label: "Confirm password",
|
||||
Placeholder: "Confirm Password",
|
||||
Type: "password",
|
||||
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) {
|
||||
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 (
|
||||
"git.soup.land/soup/shelves/internal/templates"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
|
|
@ -28,6 +29,8 @@ type Form struct {
|
|||
Fields []Field
|
||||
}
|
||||
|
||||
var formTmpl = templates.MustParseEmbed("components/form.tmpl.html")
|
||||
|
||||
func (f Form) HTML() template.HTML {
|
||||
if f.Method == "" {
|
||||
f.Method = "POST"
|
||||
|
|
@ -37,5 +40,5 @@ func (f Form) HTML() template.HTML {
|
|||
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