Compare commits
No commits in common. "7aa3c4fb3fe32d8e55dcbde0f96132875a6681bc" and "0bab92db02933b9e891a6d455d4e5d38bdfac202" have entirely different histories.
7aa3c4fb3f
...
0bab92db02
69
db.go
69
db.go
@ -1,7 +1,6 @@
|
|||||||
package peachy
|
package peachy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@ -30,10 +29,8 @@ type DB struct {
|
|||||||
conn *sqlite.Conn
|
conn *sqlite.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db DB) Close() {
|
func (db *DB) Close() {
|
||||||
if db.conn != nil {
|
|
||||||
db.conn.Close()
|
db.conn.Close()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var tableNameMatcher = match.MustCompile("(%s%s)")
|
var tableNameMatcher = match.MustCompile("(%s%s)")
|
||||||
@ -75,7 +72,7 @@ func setupConn(conn *sqlite.Conn) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Open(path string) (db DB, err error) {
|
func Open(path string) (db *DB, err error) {
|
||||||
var conn *sqlite.Conn
|
var conn *sqlite.Conn
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -87,11 +84,11 @@ func Open(path string) (db DB, err error) {
|
|||||||
switch sqlite.ErrCode(err) {
|
switch sqlite.ErrCode(err) {
|
||||||
case sqlite.ResultOK:
|
case sqlite.ResultOK:
|
||||||
case sqlite.ResultCantOpen:
|
case sqlite.ResultCantOpen:
|
||||||
return db, ErrFileNotExist
|
return nil, ErrFileNotExist
|
||||||
case sqlite.ResultNotADB:
|
case sqlite.ResultNotADB:
|
||||||
return db, ErrInvalidDB
|
return nil, ErrInvalidDB
|
||||||
default:
|
default:
|
||||||
return db, DBError{err}
|
return nil, DBError{err}
|
||||||
}
|
}
|
||||||
|
|
||||||
var goodAppID bool
|
var goodAppID bool
|
||||||
@ -101,20 +98,16 @@ func Open(path string) (db DB, err error) {
|
|||||||
return nil
|
return nil
|
||||||
}})
|
}})
|
||||||
if !goodAppID {
|
if !goodAppID {
|
||||||
return db, ErrInvalidDB
|
return nil, ErrInvalidDB
|
||||||
}
|
}
|
||||||
err = setupConn(conn)
|
err = setupConn(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return db, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return DB{conn: conn}, nil
|
return &DB{conn: conn}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed metadata_schema.sql
|
func Create(path string) (db *DB, err error) {
|
||||||
var metadataSchema string
|
|
||||||
|
|
||||||
func Create(path string) (db DB, err error) {
|
|
||||||
fmt.Println("metadata schema:\n", metadataSchema)
|
|
||||||
var conn *sqlite.Conn
|
var conn *sqlite.Conn
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -124,29 +117,23 @@ func Create(path string) (db DB, err error) {
|
|||||||
|
|
||||||
finfo, _ := os.Stat(path)
|
finfo, _ := os.Stat(path)
|
||||||
if finfo != nil {
|
if finfo != nil {
|
||||||
return db, ErrFileExists
|
return nil, ErrFileExists
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err = sqlite.OpenConn(path, sqlite.OpenCreate|sqlite.OpenReadWrite|sqlite.OpenWAL)
|
conn, err = sqlite.OpenConn(path, sqlite.OpenCreate|sqlite.OpenReadWrite|sqlite.OpenWAL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return db, fmt.Errorf("could not create database: %w", err)
|
return nil, fmt.Errorf("could not create database: %w", err)
|
||||||
}
|
}
|
||||||
query := fmt.Sprintf("PRAGMA application_id=%d", AppID)
|
query := fmt.Sprintf("PRAGMA application_id=%d", AppID)
|
||||||
err = sqlitex.ExecuteTransient(conn, query, nil)
|
err = sqlitex.ExecuteTransient(conn, query, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return db, DBError{err}
|
return nil, DBError{err}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = sqlitex.ExecuteScript(conn, metadataSchema, nil)
|
|
||||||
if err != nil {
|
|
||||||
return db, DBError{err}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = setupConn(conn)
|
err = setupConn(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return db, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return DB{conn: conn}, nil
|
return &DB{conn: conn}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func quoteName(name string) string {
|
func quoteName(name string) string {
|
||||||
@ -174,7 +161,7 @@ const addAbstractCompositeTypeQuery = `CREATE TABLE %s (
|
|||||||
%s_value_id INTEGER NOT NULL REFERENCES %s(id)
|
%s_value_id INTEGER NOT NULL REFERENCES %s(id)
|
||||||
);`
|
);`
|
||||||
|
|
||||||
func (db DB) AddCompositeType(name string, kind CompositeKind) error {
|
func (db *DB) AddCompositeType(name string, kind CompositeKind) error {
|
||||||
var kindStr string
|
var kindStr string
|
||||||
switch kind {
|
switch kind {
|
||||||
case Record:
|
case Record:
|
||||||
@ -214,7 +201,7 @@ FROM sqlite_schema
|
|||||||
WHERE parse_table_name(name, 'class') IN ('record-value', 'variant-value')
|
WHERE parse_table_name(name, 'class') IN ('record-value', 'variant-value')
|
||||||
ORDER BY parse_table_name(name, 'name') ASC;`
|
ORDER BY parse_table_name(name, 'name') ASC;`
|
||||||
|
|
||||||
func (db DB) GetCompositeTypes() ([]CompositeType, error) {
|
func (db *DB) GetCompositeTypes() ([]CompositeType, error) {
|
||||||
var results []CompositeType
|
var results []CompositeType
|
||||||
err := sqlitex.Execute(db.conn, getCompositeTypesQuery, &sqlitex.ExecOptions{
|
err := sqlitex.Execute(db.conn, getCompositeTypesQuery, &sqlitex.ExecOptions{
|
||||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||||
@ -233,27 +220,3 @@ func (db DB) GetCompositeTypes() ([]CompositeType, error) {
|
|||||||
}
|
}
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db DB) SiteName() (string, error) {
|
|
||||||
var name string
|
|
||||||
err := sqlitex.Execute(db.conn, "SELECT name FROM site_metadata", &sqlitex.ExecOptions{
|
|
||||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
|
||||||
name = stmt.ColumnText(0)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("SiteName: %w", err)
|
|
||||||
}
|
|
||||||
return name, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db DB) SetSiteName(name string) error {
|
|
||||||
err := sqlitex.Execute(db.conn, "UPDATE site_metadata SET name = ?", &sqlitex.ExecOptions{
|
|
||||||
Args: []any{name},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("SetSiteName: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
35
db_test.go
35
db_test.go
@ -6,7 +6,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.codemonkeysoftware.net/b/peachy-go"
|
"git.codemonkeysoftware.net/b/peachy-go"
|
||||||
"github.com/shoenig/test"
|
|
||||||
"github.com/shoenig/test/must"
|
"github.com/shoenig/test/must"
|
||||||
"zombiezen.com/go/sqlite"
|
"zombiezen.com/go/sqlite"
|
||||||
)
|
)
|
||||||
@ -15,6 +14,12 @@ func pending(t *testing.T) {
|
|||||||
t.Fatalf("test not implemented")
|
t.Fatalf("test not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func closeIfExists(db *peachy.DB) {
|
||||||
|
if db != nil {
|
||||||
|
db.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestOpen(t *testing.T) {
|
func TestOpen(t *testing.T) {
|
||||||
t.Run("Open succeeds on DB created by Create", func(t *testing.T) {
|
t.Run("Open succeeds on DB created by Create", func(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
@ -24,7 +29,8 @@ func TestOpen(t *testing.T) {
|
|||||||
db.Close()
|
db.Close()
|
||||||
|
|
||||||
db, err = peachy.Open(dbPath)
|
db, err = peachy.Open(dbPath)
|
||||||
defer db.Close()
|
defer closeIfExists(db)
|
||||||
|
must.NotNil(t, db)
|
||||||
must.NoError(t, err)
|
must.NoError(t, err)
|
||||||
|
|
||||||
// TODO Make sure the DB works once it has any methods.
|
// TODO Make sure the DB works once it has any methods.
|
||||||
@ -37,14 +43,16 @@ func TestOpen(t *testing.T) {
|
|||||||
conn.Close()
|
conn.Close()
|
||||||
|
|
||||||
db, err := peachy.Open(dbPath)
|
db, err := peachy.Open(dbPath)
|
||||||
defer db.Close()
|
defer closeIfExists(db)
|
||||||
|
must.Nil(t, db)
|
||||||
must.ErrorIs(t, err, peachy.ErrInvalidDB)
|
must.ErrorIs(t, err, peachy.ErrInvalidDB)
|
||||||
})
|
})
|
||||||
t.Run("Open fails on nonexistent file", func(t *testing.T) {
|
t.Run("Open fails on nonexistent file", func(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
dbPath := filepath.Join(dir, "test.db")
|
dbPath := filepath.Join(dir, "test.db")
|
||||||
db, err := peachy.Open(dbPath)
|
db, err := peachy.Open(dbPath)
|
||||||
defer db.Close()
|
defer closeIfExists(db)
|
||||||
|
must.Nil(t, db)
|
||||||
must.ErrorIs(t, err, peachy.ErrFileNotExist)
|
must.ErrorIs(t, err, peachy.ErrFileNotExist)
|
||||||
must.FileNotExists(t, dbPath)
|
must.FileNotExists(t, dbPath)
|
||||||
})
|
})
|
||||||
@ -54,27 +62,12 @@ func TestOpen(t *testing.T) {
|
|||||||
must.NoError(t, err)
|
must.NoError(t, err)
|
||||||
|
|
||||||
db, err := peachy.Open(path)
|
db, err := peachy.Open(path)
|
||||||
defer db.Close()
|
defer closeIfExists(db)
|
||||||
|
must.Nil(t, db)
|
||||||
must.ErrorIs(t, err, peachy.ErrInvalidDB, must.Sprint(sqlite.ErrCode(err)))
|
must.ErrorIs(t, err, peachy.ErrInvalidDB, must.Sprint(sqlite.ErrCode(err)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSiteName(t *testing.T) {
|
|
||||||
db, err := peachy.Create(":memory:")
|
|
||||||
must.NoError(t, err)
|
|
||||||
|
|
||||||
initial, err := db.SiteName()
|
|
||||||
test.NoError(t, err)
|
|
||||||
test.EqOp(t, "", initial)
|
|
||||||
|
|
||||||
const expected = "aoeuhtns"
|
|
||||||
test.NoError(t, db.SetSiteName(expected))
|
|
||||||
|
|
||||||
actual, err := db.SiteName()
|
|
||||||
test.NoError(t, err)
|
|
||||||
test.EqOp(t, expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompositeTypes(t *testing.T) {
|
func TestCompositeTypes(t *testing.T) {
|
||||||
db, err := peachy.Create(":memory:")
|
db, err := peachy.Create(":memory:")
|
||||||
must.NoError(t, err)
|
must.NoError(t, err)
|
||||||
|
3
desktop/app/ServiceExample.go
Normal file
3
desktop/app/ServiceExample.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
// TODO: Add your code here
|
@ -1,5 +0,0 @@
|
|||||||
package fields
|
|
||||||
|
|
||||||
const (
|
|
||||||
SiteName = "sitename"
|
|
||||||
)
|
|
@ -2,13 +2,9 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"git.codemonkeysoftware.net/b/peachy-go"
|
|
||||||
"git.codemonkeysoftware.net/b/peachy-go/desktop/routes"
|
"git.codemonkeysoftware.net/b/peachy-go/desktop/routes"
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
)
|
)
|
||||||
@ -16,67 +12,15 @@ import (
|
|||||||
//go:embed all:static
|
//go:embed all:static
|
||||||
var assets embed.FS
|
var assets embed.FS
|
||||||
|
|
||||||
const ListenPort = "9245"
|
|
||||||
const WindowNameMain = "mainwindow"
|
|
||||||
|
|
||||||
var globalDB peachy.DB
|
|
||||||
|
|
||||||
func closeDB() {
|
|
||||||
globalDB.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetURl is a workaround for WebviewWindow.SetURL not adding the base URL
|
|
||||||
// to relative URLs.
|
|
||||||
// TODO Report this or something.
|
|
||||||
func SetURL(win application.Window, path string) application.Window {
|
|
||||||
slog.Info("setting URL the hard way")
|
|
||||||
u, _ := url.JoinPath("http://wails.localhost:"+ListenPort+"/", path)
|
|
||||||
return win.SetURL(u)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setDB(db peachy.DB) error {
|
|
||||||
slog.Info("opened DB, setting URL")
|
|
||||||
globalDB = db
|
|
||||||
SetURL(application.Get().GetWindowByName(WindowNameMain), "/site")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createDB(filename string) error {
|
|
||||||
db, err := peachy.Create(filename)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to create database: %w", err)
|
|
||||||
}
|
|
||||||
return setDB(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
func openDB(filename string) error {
|
|
||||||
db, err := peachy.Open(filename)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to open database: %w", err)
|
|
||||||
}
|
|
||||||
return setDB(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
func showError(err error) {
|
|
||||||
if err != nil {
|
|
||||||
application.ErrorDialog().SetMessage(err.Error()).Show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbFileFilter() []application.FileFilter {
|
|
||||||
return []application.FileFilter{
|
|
||||||
{DisplayName: "Peachy database", Pattern: "*.pit"},
|
|
||||||
{DisplayName: "All Files", Pattern: "*.*"},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
r := routes.NewChiRouter(ListenPort, &globalDB)
|
|
||||||
|
|
||||||
|
r := routes.NewChiRouter()
|
||||||
|
|
||||||
|
// Create the application
|
||||||
app := application.New(application.Options{
|
app := application.New(application.Options{
|
||||||
Name: "Peachy",
|
Name: "Peachy",
|
||||||
Description: "A desktop CMS for static sites",
|
Description: "A demo of using raw HTML & CSS", // Description of the application
|
||||||
Assets: application.AssetOptions{
|
Assets: application.AssetOptions{ // Assets to embed (our static files)
|
||||||
Handler: application.AssetFileServerFS(assets),
|
Handler: application.AssetFileServerFS(assets),
|
||||||
Middleware: func(next http.Handler) http.Handler {
|
Middleware: func(next http.Handler) http.Handler {
|
||||||
r.NotFound(next.ServeHTTP)
|
r.NotFound(next.ServeHTTP)
|
||||||
@ -87,12 +31,9 @@ func main() {
|
|||||||
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
app.OnShutdown(func() {
|
|
||||||
closeDB()
|
|
||||||
})
|
|
||||||
|
|
||||||
|
// V3 introduces multiple windows, so we need to create a window
|
||||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||||
Name: WindowNameMain,
|
|
||||||
Title: "Your Project",
|
Title: "Your Project",
|
||||||
Mac: application.MacWindow{
|
Mac: application.MacWindow{
|
||||||
Backdrop: application.MacBackdropTranslucent,
|
Backdrop: application.MacBackdropTranslucent,
|
||||||
@ -111,24 +52,31 @@ func main() {
|
|||||||
menu.FindByRole(application.NewFile).OnClick(func(ctx *application.Context) {
|
menu.FindByRole(application.NewFile).OnClick(func(ctx *application.Context) {
|
||||||
d := application.SaveFileDialog()
|
d := application.SaveFileDialog()
|
||||||
d.SetOptions(&application.SaveFileDialogOptions{
|
d.SetOptions(&application.SaveFileDialogOptions{
|
||||||
Filters: dbFileFilter(),
|
Filters: []application.FileFilter{
|
||||||
|
{DisplayName: "Peachy database", Pattern: "*.pit"},
|
||||||
|
{DisplayName: "All Files", Pattern: "*.*"},
|
||||||
|
},
|
||||||
AllowOtherFileTypes: true,
|
AllowOtherFileTypes: true,
|
||||||
Title: "Create new database",
|
Title: "Create new database",
|
||||||
})
|
})
|
||||||
path, _ := d.PromptForSingleSelection()
|
path, err := d.PromptForSingleSelection()
|
||||||
|
if err != nil {
|
||||||
|
application.InfoDialog().SetMessage("Error: " + err.Error()).Show()
|
||||||
|
}
|
||||||
if path != "" {
|
if path != "" {
|
||||||
showError(createDB(path))
|
application.InfoDialog().SetMessage(path).Show()
|
||||||
|
} else {
|
||||||
|
application.InfoDialog().SetMessage("No file selected").Show()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
menu.FindByRole(application.Open).OnClick(func(ctx *application.Context) {
|
menu.FindByRole(application.Open).OnClick(func(ctx *application.Context) {
|
||||||
d := application.OpenFileDialog()
|
path, _ := application.OpenFileDialog().
|
||||||
d.SetOptions(&application.OpenFileDialogOptions{
|
CanChooseFiles(true).
|
||||||
Filters: dbFileFilter(),
|
PromptForSingleSelection()
|
||||||
CanChooseFiles: true,
|
|
||||||
})
|
|
||||||
path, _ := d.PromptForSingleSelection()
|
|
||||||
if path != "" {
|
if path != "" {
|
||||||
showError(openDB(path))
|
application.InfoDialog().SetMessage(path).Show()
|
||||||
|
} else {
|
||||||
|
application.InfoDialog().SetMessage("No file selected").Show()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
app.SetMenu(menu)
|
app.SetMenu(menu)
|
||||||
|
@ -4,39 +4,37 @@ import (
|
|||||||
h "git.codemonkeysoftware.net/b/hatmill"
|
h "git.codemonkeysoftware.net/b/hatmill"
|
||||||
a "git.codemonkeysoftware.net/b/hatmill/attribute"
|
a "git.codemonkeysoftware.net/b/hatmill/attribute"
|
||||||
e "git.codemonkeysoftware.net/b/hatmill/element"
|
e "git.codemonkeysoftware.net/b/hatmill/element"
|
||||||
"git.codemonkeysoftware.net/b/peachy-go"
|
|
||||||
"git.codemonkeysoftware.net/b/peachy-go/desktop/fields"
|
|
||||||
"git.codemonkeysoftware.net/b/peachy-go/hx"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func HomePage(db *peachy.DB) (func() h.ParentElement, error) {
|
func hxGet(url string) h.Attrib {
|
||||||
siteName, err := db.SiteName()
|
return h.Attrib{
|
||||||
if err != nil {
|
Key: "hx-get",
|
||||||
return nil, err
|
Value: a.String(url),
|
||||||
}
|
}
|
||||||
return func() h.ParentElement {
|
}
|
||||||
|
|
||||||
|
func hxTrigger(event string) h.Attrib {
|
||||||
|
return h.Attrib{
|
||||||
|
Key: "hx-trigger",
|
||||||
|
Value: a.String(event),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hxTarget(target string) h.Attrib {
|
||||||
|
return h.Attrib{
|
||||||
|
Key: "hx-target",
|
||||||
|
Value: a.String(target),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HomePage() h.ParentElement {
|
||||||
return e.Div()(
|
return e.Div()(
|
||||||
e.Input(
|
|
||||||
a.Type("text"),
|
|
||||||
a.Value(siteName),
|
|
||||||
a.Placeholder("site name"),
|
|
||||||
a.Name(fields.SiteName),
|
|
||||||
hx.Trigger("change"),
|
|
||||||
hx.Target("this"),
|
|
||||||
hx.Patch("/site"),
|
|
||||||
hx.Swap(hx.OuterHTML),
|
|
||||||
),
|
|
||||||
e.Button(
|
e.Button(
|
||||||
a.Type("button"),
|
a.Type("button"),
|
||||||
hx.Get("/hello"),
|
hxGet("/hello"),
|
||||||
hx.Trigger("click"),
|
hxTrigger("click"),
|
||||||
hx.Target("#hello"),
|
hxTarget("#hello"),
|
||||||
)(h.Text("Click Here!")),
|
)(h.Text("Click Here!")),
|
||||||
e.Div(a.Id("hello"))(),
|
e.Div(a.Id("hello"))(),
|
||||||
)
|
)
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Splash() h.Term {
|
|
||||||
return h.Terms{}
|
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,7 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.codemonkeysoftware.net/b/peachy-go"
|
|
||||||
"git.codemonkeysoftware.net/b/peachy-go/desktop/components"
|
"git.codemonkeysoftware.net/b/peachy-go/desktop/components"
|
||||||
"git.codemonkeysoftware.net/b/peachy-go/desktop/fields"
|
|
||||||
"git.codemonkeysoftware.net/b/peachy-go/desktop/pages"
|
"git.codemonkeysoftware.net/b/peachy-go/desktop/pages"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
@ -16,11 +14,13 @@ import (
|
|||||||
/*
|
/*
|
||||||
Create a new chi router, configure it and return it.
|
Create a new chi router, configure it and return it.
|
||||||
*/
|
*/
|
||||||
func NewChiRouter(port string, db *peachy.DB) *chi.Mux {
|
func NewChiRouter() *chi.Mux {
|
||||||
|
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
|
|
||||||
|
// Useful middleware, see : https://pkg.go.dev/github.com/go-chi/httplog/v2@v2.1.1#NewLogger
|
||||||
logger := httplog.NewLogger("app-logger", httplog.Options{
|
logger := httplog.NewLogger("app-logger", httplog.Options{
|
||||||
|
// All log
|
||||||
LogLevel: slog.LevelInfo,
|
LogLevel: slog.LevelInfo,
|
||||||
Concise: true,
|
Concise: true,
|
||||||
})
|
})
|
||||||
@ -40,40 +40,29 @@ func NewChiRouter(port string, db *peachy.DB) *chi.Mux {
|
|||||||
})
|
})
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Serve static files.
|
||||||
r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||||
|
|
||||||
|
// Home page
|
||||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
HXRender(w, r, pages.Splash)
|
// Render home page
|
||||||
})
|
HXRender(w, r, pages.HomePage)
|
||||||
|
|
||||||
r.Get("/site", func(w http.ResponseWriter, r *http.Request) {
|
// 200 OK status
|
||||||
page, err := pages.HomePage(db)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("internal error", "path", r.URL.Path, "error", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
HXRender(w, r, page)
|
|
||||||
})
|
|
||||||
|
|
||||||
r.Patch("/site", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
r.ParseForm()
|
|
||||||
if r.Form.Has(fields.SiteName) {
|
|
||||||
err := db.SetSiteName(r.Form.Get(fields.SiteName))
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("internal error", "path", r.URL.Path, "error", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Hello page
|
||||||
r.Get("/hello", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/hello", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Render hello
|
||||||
HXRender(w, r, components.HelloWorld)
|
HXRender(w, r, components.HelloWorld)
|
||||||
|
|
||||||
|
// 200 OK status
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
})
|
})
|
||||||
|
|
||||||
go http.ListenAndServe(":"+port, r)
|
// Listen to port 3000.
|
||||||
|
go http.ListenAndServe(":9245", r)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
12
go.mod
12
go.mod
@ -9,21 +9,21 @@ require (
|
|||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/shoenig/test v1.11.0
|
github.com/shoenig/test v1.11.0
|
||||||
pgregory.net/rapid v1.1.0
|
pgregory.net/rapid v1.1.0
|
||||||
zombiezen.com/go/sqlite v1.4.0
|
zombiezen.com/go/sqlite v1.3.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
golang.org/x/sys v0.22.0 // indirect
|
golang.org/x/sys v0.21.0 // indirect
|
||||||
modernc.org/libc v1.55.3 // indirect
|
modernc.org/libc v1.41.0 // indirect
|
||||||
modernc.org/mathutil v1.6.0 // indirect
|
modernc.org/mathutil v1.6.0 // indirect
|
||||||
modernc.org/memory v1.8.0 // indirect
|
modernc.org/memory v1.7.2 // indirect
|
||||||
modernc.org/sqlite v1.33.1 // indirect
|
modernc.org/sqlite v1.29.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace git.codemonkeysoftware.net/b/gigaparsec v0.0.0-20240917174243-4fa3c2a46611 => ../gigaparsec
|
replace git.codemonkeysoftware.net/b/gigaparsec v0.0.0-20240917174243-4fa3c2a46611 => ../gigaparsec
|
||||||
|
40
go.sum
40
go.sum
@ -52,8 +52,8 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l
|
|||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
|
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
|
||||||
@ -169,8 +169,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
@ -204,31 +204,15 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk=
|
||||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY=
|
||||||
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
|
|
||||||
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
|
|
||||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
|
||||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
|
||||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
|
||||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
|
||||||
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
|
|
||||||
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
|
|
||||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
modernc.org/sqlite v1.29.1 h1:19GY2qvWB4VPw0HppFlZCPAbmxFU41r+qjKZQdQ1ryA=
|
||||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
modernc.org/sqlite v1.29.1/go.mod h1:hG41jCYxOAOoO6BRK66AdRlmOcDzXf7qnwlwjUIOqa0=
|
||||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
|
||||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
|
||||||
modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
|
|
||||||
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
|
|
||||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
|
||||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
|
||||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
|
||||||
pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
|
pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
|
||||||
pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||||
zombiezen.com/go/sqlite v1.4.0 h1:N1s3RIljwtp4541Y8rM880qgGIgq3fTD2yks1xftnKU=
|
zombiezen.com/go/sqlite v1.3.0 h1:98g1gnCm+CNz6AuQHu0gqyw7gR2WU3O3PJufDOStpUs=
|
||||||
zombiezen.com/go/sqlite v1.4.0/go.mod h1:0w9F1DN9IZj9AcLS9YDKMboubCACkwYCGkzoy3eG5ik=
|
zombiezen.com/go/sqlite v1.3.0/go.mod h1:yRl27//s/9aXU3RWs8uFQwjkTG9gYNGEls6+6SvrclY=
|
||||||
|
54
hx/hx.go
54
hx/hx.go
@ -1,54 +0,0 @@
|
|||||||
package hx
|
|
||||||
|
|
||||||
import (
|
|
||||||
h "git.codemonkeysoftware.net/b/hatmill"
|
|
||||||
a "git.codemonkeysoftware.net/b/hatmill/attribute"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Get(url string) h.Attrib {
|
|
||||||
return h.Attrib{
|
|
||||||
Key: "hx-get",
|
|
||||||
Value: a.String(url),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Patch(url string) h.Attrib {
|
|
||||||
return h.Attrib{
|
|
||||||
Key: "hx-patch",
|
|
||||||
Value: a.String(url),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Trigger(event string) h.Attrib {
|
|
||||||
return h.Attrib{
|
|
||||||
Key: "hx-trigger",
|
|
||||||
Value: a.String(event),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Target(target string) h.Attrib {
|
|
||||||
return h.Attrib{
|
|
||||||
Key: "hx-target",
|
|
||||||
Value: a.String(target),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type SwapKind string
|
|
||||||
|
|
||||||
const (
|
|
||||||
InnerHTML SwapKind = "innerHTML"
|
|
||||||
OuterHTML SwapKind = "outerHTML"
|
|
||||||
AfterBegin SwapKind = "afterbegin"
|
|
||||||
BeforeBegin SwapKind = "beforebegin"
|
|
||||||
BeforeEnd SwapKind = "beforeend"
|
|
||||||
AfterEnd SwapKind = "afterend"
|
|
||||||
Delete SwapKind = "delete"
|
|
||||||
None SwapKind = "none"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Swap(kind SwapKind) h.Attrib {
|
|
||||||
return h.Attrib{
|
|
||||||
Key: "hx-target",
|
|
||||||
Value: a.String(kind),
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
CREATE TABLE site_metadata (
|
|
||||||
name TEXT NOT NULL
|
|
||||||
);
|
|
||||||
INSERT INTO site_metadata(name) VALUES ('');
|
|
Loading…
Reference in New Issue
Block a user