diff --git a/.air.toml b/.air.toml deleted file mode 100644 index e6e0dbb..0000000 --- a/.air.toml +++ /dev/null @@ -1,52 +0,0 @@ -root = "." -testdata_dir = "testdata" -tmp_dir = "tmp" - -[build] - args_bin = [] - bin = "webapp" - cmd = "make build" - delay = 1000 - exclude_dir = ["assets", "pages", "static", "views", "public", "tmp", "vendor", "testdata"] - exclude_file = [] - exclude_regex = ["_test.go"] - exclude_unchanged = false - follow_symlink = false - full_bin = "" - include_dir = [] - include_ext = ["go", "tpl", "tmpl", "html", "css", "js"] - include_file = [] - kill_delay = "0s" - log = "build-errors.log" - poll = false - poll_interval = 0 - post_cmd = [] - pre_cmd = [] - rerun = false - rerun_delay = 500 - send_interrupt = false - stop_on_error = false - -[color] - app = "" - build = "yellow" - main = "magenta" - runner = "green" - watcher = "cyan" - -[log] - main_only = false - silent = false - time = false - -[misc] - clean_on_exit = false - -[proxy] - app_port = 7001 - enabled = true - proxy_port = 7002 - -[screen] - clear_on_rebuild = false - keep_scroll = true diff --git a/.ozai.json b/.ozai.json index 7aeb375..fbabbb5 100644 --- a/.ozai.json +++ b/.ozai.json @@ -14,7 +14,7 @@ "tailwind": { "URL": "/tailwind", "Command": "tailwindcss", - "Args": ["--input", "./static/input_style.css", "--output", "./static/style.css","--watch"] + "Args": ["--input", "./static/input_style.css", "--output", "./static/style.css","--watch=always"] } } } diff --git a/Makefile b/Makefile index 2d44efd..03b05cb 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,3 @@ -GO_IS_STUPID_EXE= - -ifeq '$(OS)' 'Windows_NT' - GO_IS_STUPID_EXE=.exe -endif - all: build ifeq '$(OS)' 'Windows_NT' powershell -ExecutionPolicy bypass "./tools/restart.ps1" @@ -18,12 +12,12 @@ site: go tool ssgod test: site - go test . -c -o runtests$(GO_IS_STUPID_EXE) - ./runtests$(GO_IS_STUPID_EXE) + go test MY/webapp/tests -c + ./tests.test test_only: - go test . -c -o runtests$(GO_IS_STUPID_EXE) - ./runtests$(GO_IS_STUPID_EXE) -test.run TestGamePage + go test . -c -o + ./tests.test -test.run TestSomePage migrate_up: goose sqlite3 db.sqlite3 -dir migrations up diff --git a/admin/handlers.go b/admin/handlers.go index be693cc..5a77249 100644 --- a/admin/handlers.go +++ b/admin/handlers.go @@ -169,4 +169,5 @@ func Setup(app *fiber.App) { app.Post("/api/admin/new/table/:table", PostApiInsert) app.Get("/api/admin/table/:table/:id", GetApiSelectOne) app.Post("/api/admin/table/:table/:id", PostApiUpdate) + app.Delete("/api/admin/table/:table/:id", DeleteApi) } diff --git a/api/auth.go b/api/auth.go index 82837e7..6d57933 100644 --- a/api/auth.go +++ b/api/auth.go @@ -15,6 +15,12 @@ import ( . "MY/webapp/common" ) +func GetApiAuthCheck(c *fiber.Ctx) error { + _, err := CheckAuthed(c, false) + // auth failure or not authed is determined by err, with nil meaning YES AUTHED + return c.JSON(fiber.Map{"is_authed": err == nil}) +} + func GetApiLogout(c *fiber.Ctx) error { err := LogoutUser(c) if err != nil { return IfErrNil(err, c) } diff --git a/api/handlers.go b/api/handlers.go index 8bb4ca0..e01c362 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -20,9 +20,10 @@ func Setup(app *fiber.App) { }) // api/auth.go + app.Get("/api/authcheck", GetApiAuthCheck) app.Get("/api/logout", GetApiLogout) - app.Post("/api/login", PostApiLogin) app.Post("/api/register", PostApiRegister) + app.Post("/api/login", PostApiLogin) } func Shutdown() { diff --git a/common/api.go b/common/api.go index 9d3f70b..452e850 100644 --- a/common/api.go +++ b/common/api.go @@ -48,7 +48,9 @@ func ReflectOnPost(typeOf reflect.Type, c *fiber.Ctx) (reflect.Value, error) { result_val = reflect.New(typeOf) result := result_val.Interface() - if err := c.BodyParser(result); err != nil { + if err := c.BodyParser(result) + + err != nil { log.Println(err); return result_val, err } @@ -56,7 +58,9 @@ func ReflectOnPost(typeOf reflect.Type, c *fiber.Ctx) (reflect.Value, error) { var validate *validator.Validate validate = validator.New(validator.WithRequiredStructEnabled()) - if err := validate.Struct(result); err != nil { + err := validate.Struct(result) + + if err != nil { validationErrors := err.(validator.ValidationErrors) log.Println(validationErrors) return result_val, err diff --git a/common/web.go b/common/web.go index 94adb91..fe2686c 100644 --- a/common/web.go +++ b/common/web.go @@ -1,70 +1,14 @@ package common import ( - "log" "strings" - "io/fs" - "path/filepath" - "os" "github.com/gofiber/fiber/v2" - "github.com/gofiber/template/html/v2" ) func Page(path string) (func(c *fiber.Ctx) error) { + page_id := strings.ReplaceAll(path, "/", "-") + "-page" + return func (c *fiber.Ctx) error { - return c.Render(path, fiber.Map{}) + return c.Render(path, fiber.Map{"PageId": page_id}) } } - -func RenderPages(pages_path string, target string, layout string) { - engine := html.New(pages_path, ".html") - engine.Load() - - err := filepath.WalkDir(pages_path, - func(path string, d fs.DirEntry, err error) error { - if !d.IsDir() { - if err != nil { return Fail(err, "path: %s", path); } - - dir := filepath.Dir(path) - err = os.MkdirAll(dir, 0750) - if err != nil { - return Fail(err, "making dir %s", dir); - } - - split_path := strings.Split(path, string(os.PathSeparator))[1:] - source_name := strings.Join(split_path, "/") // Render wants / even on windows - ext := filepath.Ext(source_name) - template_name, found := strings.CutSuffix(source_name, ext) - - if found && ext == ".html" && template_name != layout { - prefixed_path := append([]string{target}, split_path...) - - target_path := filepath.Join(prefixed_path...) - _, err := os.Stat(target_path) - - if os.IsNotExist(err) { - target_dir := filepath.Dir(target_path) - log.Println("MAKING: ", target_dir) - os.MkdirAll(target_dir, 0750) - } - - // TODO: compare time stamps and skip if not newer - - out, err := os.OpenFile(target_path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) - if err != nil { return Fail(err, "writing file %s", target_path) } - - // generate a data-testid for all pages based on template name - page_id := strings.ReplaceAll(template_name, "/", "-") + "-page" - err = engine.Render(out, template_name, fiber.Map{"PageId": page_id}, layout) - if err != nil { return Fail(err, "failed to render %s", path) } - - log.Printf("RENDER: %s -> %s", template_name, target_path) - out.Close() - } - } - - return nil - }) - - if err != nil { log.Fatalf("can't walk content") } -} diff --git a/data/models.go b/data/models.go index 021f6c5..89c668a 100644 --- a/data/models.go +++ b/data/models.go @@ -11,7 +11,7 @@ type Login struct { } type User struct { - Id int `db:"id" json:"id" validate:"numeric"` + Id int `db:"id" validate:"numeric"` Username string `db:"username" validate:"required,max=30"` Email string `db:"email" validate:"required,email,max=128"` Password string `db:"password" validate:"required,min=8,max=64"` @@ -21,8 +21,8 @@ type User struct { * Example of using the null library to do optional fields. */ type NullExample struct { - Id int `db:"id" json:"id" validate:"numeric"` - HasMaybe null.Int `db:"replying_to" json:"replying_to" validate:"omitempty,numeric"` + Id int `db:"id" validate:"numeric"` + HasMaybe null.Int `db:"replying_to" validate:"omitempty,numeric"` } func Models() map[string]reflect.Type { diff --git a/go.mod b/go.mod index 84a0f87..6c58db1 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module MY/webapp -go 1.24.2 +go 1.25.3 require ( github.com/BurntSushi/toml v1.5.0 diff --git a/pages/index.md b/pages/index.md index 72f48c3..113524e 100644 --- a/pages/index.md +++ b/pages/index.md @@ -61,12 +61,14 @@ Then on Linux/OSX you want to delete the `.git` with this: ```shell rm -rf .git +mv LICENSE LICENSE.old # it's MIT ``` And on Windows you use the ever more clear and totally easier: ```shell rm -recurse -force .git +mv LICENSE LICENSE.old # it's MIT ``` Once you have that you can make it your own: diff --git a/static/input_style.css b/static/input_style.css index 1864eca..71aa6c0 100644 --- a/static/input_style.css +++ b/static/input_style.css @@ -6,15 +6,15 @@ body { } main { - @apply flex flex-col gap-4 p-0 min-h-screen bg-gray-100 text-gray-950 dark:bg-gray-900 dark:text-gray-50; + @apply flex flex-col justify-stretch items-center gap-4 p-0 min-h-screen bg-gray-200 text-black dark:bg-gray-900 dark:text-gray-50; } header { - @apply flex flex-col justify-stretch; + @apply flex flex-col justify-stretch items-center bg-gray-950; } footer { - @apply bg-gray-950 text-gray-50 text-lg flex p-1; + @apply bg-gray-950 text-gray-50 text-lg flex justify-center p-1; } @utility sticky-bottom { @@ -22,7 +22,11 @@ footer { } nav { - @apply flex justify-center items-center bg-gray-950 *:text-gray-50 *:flex-1 *:text-xl w-full justify-evenly; + @apply flex lg:w-4xl justify-center items-center *:text-gray-50 *:flex-1 *:text-xl w-full justify-evenly; +} + +a { + @apply underline; } nav > a { @@ -44,23 +48,23 @@ pre > code { h1 { - @apply text-6xl mb-2 mt-4; + @apply text-5xl mb-2 mt-4 sm:text-6xl; } h2 { - @apply text-5xl mb-2 mt-4; + @apply text-4xl mb-2 mt-4 sm:text-5xl; } h3 { - @apply text-4xl mb-2 mt-4; + @apply text-3xl mb-2 mt-4 sm:text-4xl; } h4 { - @apply text-3xl mb-2 mt-4; + @apply text-2xl mb-2 mt-4 sm:text-3xl; } h5 { - @apply text-2xl mb-2 mt-4; + @apply text-2xl mb-2 mt-4 sm:text-2xl; } details { @@ -68,7 +72,7 @@ details { } aside { - @apply p-2 rounded-lg bg-gray-100 text-gray-950; + @apply flex rounded-none flex-col p-4 gap-4 bg-gray-300 text-gray-950 text-2xl dark:text-gray-50 font-semibold dark:bg-gray-800; } aside > mark { @@ -187,8 +191,24 @@ block { @apply flex flex-col p-4 gap-4; } +block.center-horizontal { + @apply items-center; +} + +block.center-vertical { + @apply justify-center; +} + bar { - @apply flex flex-row p-4 gap-4; + @apply flex flex-col sm:flex-row p-4 gap-4; +} + +bar.center-horizontal { + @apply justify-center; +} + +bar.center-vertical { + @apply items-center; } stack { @@ -232,3 +252,11 @@ table { @utility table-row { @apply *:border-2 *:border-black *:p-1 hover:bg-gray-100 hover:text-gray-950; } + +@utility two-panel { + @apply grid-rows-2 grid-cols-1 sm:grid-cols-2 sm:grid-rows-1; +} + +@utility debug { + @apply !border-1 !border-red-900; +} diff --git a/static/style.css b/static/style.css index e83117f..ea65572 100644 --- a/static/style.css +++ b/static/style.css @@ -36,6 +36,7 @@ --container-md: 28rem; --container-lg: 32rem; --container-xl: 36rem; + --container-4xl: 56rem; --text-sm: 0.875rem; --text-sm--line-height: calc(1.25 / 0.875); --text-lg: 1.125rem; @@ -53,6 +54,7 @@ --text-6xl: 3.75rem; --text-6xl--line-height: 1; --font-weight-light: 300; + --font-weight-semibold: 600; --font-weight-bold: 700; --radius-sm: 0.25rem; --radius-md: 0.375rem; @@ -456,6 +458,9 @@ .bg-green-400\! { background-color: var(--color-green-400) !important; } + .\!p-1 { + padding: calc(var(--spacing) * 1) !important; + } .\!p-4 { padding: calc(var(--spacing) * 4) !important; } @@ -582,6 +587,16 @@ } } } + .sm\:p-0 { + @media (width >= 40rem) { + padding: calc(var(--spacing) * 0); + } + } + .lg\:max-w-4xl { + @media (width >= 64rem) { + max-width: var(--container-4xl); + } + } .dark\:bg-gray-900 { @media (prefers-color-scheme: dark) { background-color: var(--color-gray-900); @@ -604,10 +619,12 @@ main { display: flex; min-height: 100vh; flex-direction: column; + align-items: center; + justify-content: stretch; gap: calc(var(--spacing) * 4); - background-color: var(--color-gray-100); + background-color: var(--color-gray-200); padding: calc(var(--spacing) * 0); - color: var(--color-gray-950); + color: var(--color-black); @media (prefers-color-scheme: dark) { background-color: var(--color-gray-900); } @@ -618,10 +635,13 @@ main { header { display: flex; flex-direction: column; + align-items: center; justify-content: stretch; + background-color: var(--color-gray-950); } footer { display: flex; + justify-content: center; background-color: var(--color-gray-950); padding: calc(var(--spacing) * 1); font-size: var(--text-lg); @@ -634,7 +654,6 @@ nav { align-items: center; justify-content: center; justify-content: space-evenly; - background-color: var(--color-gray-950); :is(& > *) { flex: 1; } @@ -645,6 +664,12 @@ nav { :is(& > *) { color: var(--color-gray-50); } + @media (width >= 64rem) { + width: var(--container-4xl); + } +} +a { + text-decoration-line: underline; } nav > a { display: flex; @@ -671,34 +696,54 @@ pre > code { padding: calc(var(--spacing) * 1); } h1 { - margin-top: calc(var(--spacing) * 4); - margin-bottom: calc(var(--spacing) * 2); - font-size: var(--text-6xl); - line-height: var(--tw-leading, var(--text-6xl--line-height)); -} -h2 { margin-top: calc(var(--spacing) * 4); margin-bottom: calc(var(--spacing) * 2); font-size: var(--text-5xl); line-height: var(--tw-leading, var(--text-5xl--line-height)); + @media (width >= 40rem) { + font-size: var(--text-6xl); + line-height: var(--tw-leading, var(--text-6xl--line-height)); + } } -h3 { +h2 { margin-top: calc(var(--spacing) * 4); margin-bottom: calc(var(--spacing) * 2); font-size: var(--text-4xl); line-height: var(--tw-leading, var(--text-4xl--line-height)); + @media (width >= 40rem) { + font-size: var(--text-5xl); + line-height: var(--tw-leading, var(--text-5xl--line-height)); + } } -h4 { +h3 { margin-top: calc(var(--spacing) * 4); margin-bottom: calc(var(--spacing) * 2); font-size: var(--text-3xl); line-height: var(--tw-leading, var(--text-3xl--line-height)); + @media (width >= 40rem) { + font-size: var(--text-4xl); + line-height: var(--tw-leading, var(--text-4xl--line-height)); + } +} +h4 { + margin-top: calc(var(--spacing) * 4); + margin-bottom: calc(var(--spacing) * 2); + font-size: var(--text-2xl); + line-height: var(--tw-leading, var(--text-2xl--line-height)); + @media (width >= 40rem) { + font-size: var(--text-3xl); + line-height: var(--tw-leading, var(--text-3xl--line-height)); + } } h5 { margin-top: calc(var(--spacing) * 4); margin-bottom: calc(var(--spacing) * 2); font-size: var(--text-2xl); line-height: var(--tw-leading, var(--text-2xl--line-height)); + @media (width >= 40rem) { + font-size: var(--text-2xl); + line-height: var(--tw-leading, var(--text-2xl--line-height)); + } } details { border-radius: var(--radius-lg); @@ -709,10 +754,23 @@ details { color: var(--color-gray-950); } aside { - border-radius: var(--radius-lg); - background-color: var(--color-gray-100); - padding: calc(var(--spacing) * 2); + display: flex; + flex-direction: column; + gap: calc(var(--spacing) * 4); + border-radius: 0; + background-color: var(--color-gray-300); + padding: calc(var(--spacing) * 4); + font-size: var(--text-2xl); + line-height: var(--tw-leading, var(--text-2xl--line-height)); + --tw-font-weight: var(--font-weight-semibold); + font-weight: var(--font-weight-semibold); color: var(--color-gray-950); + @media (prefers-color-scheme: dark) { + background-color: var(--color-gray-800); + } + @media (prefers-color-scheme: dark) { + color: var(--color-gray-50); + } } aside > mark { border-radius: var(--radius-sm); @@ -896,11 +954,26 @@ block { gap: calc(var(--spacing) * 4); padding: calc(var(--spacing) * 4); } +block.center-horizontal { + align-items: center; +} +block.center-vertical { + justify-content: center; +} bar { display: flex; - flex-direction: row; + flex-direction: column; gap: calc(var(--spacing) * 4); padding: calc(var(--spacing) * 4); + @media (width >= 40rem) { + flex-direction: row; + } +} +bar.center-horizontal { + justify-content: center; +} +bar.center-vertical { + align-items: center; } stack { display: grid; diff --git a/tests/admin_ui_tests.go b/tests/admin_ui_test.go similarity index 100% rename from tests/admin_ui_tests.go rename to tests/admin_ui_test.go diff --git a/views/admin/table/contents.html b/views/admin/table/contents.html index 297e768..2284ec4 100644 --- a/views/admin/table/contents.html +++ b/views/admin/table/contents.html @@ -19,10 +19,10 @@