Sorted out how to do views with Go html/template, how to put code in subdirectories for a namespace, and documented why Go's modules are so weird.

main
Zed A. Shaw 20 hours ago
parent 144a76a67a
commit 55f59d88b6
  1. 2
      .air.toml
  2. 5
      Makefile
  3. 40
      README.md
  4. 9
      go.mod
  5. 8
      go.sum
  6. 68
      main.go
  7. 59
      tools/tools.go
  8. 5
      views/index.html
  9. 10
      views/layouts/main.html
  10. 1
      views/partials/footer.html
  11. 1
      views/partials/header.html

@ -4,7 +4,7 @@ tmp_dir = "tmp"
[build] [build]
args_bin = [] args_bin = []
bin = "fibertest.exe" bin = "webapp"
cmd = "make build" cmd = "make build"
delay = 1000 delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata"] exclude_dir = ["assets", "tmp", "vendor", "testdata"]

@ -1,11 +1,14 @@
build: build:
go build . go build .
html:
go tool qtc -dir templates
docs: docs:
go tool godoc -http=localhost:6060 -index -index_files godoc.idx go tool godoc -http=localhost:6060 -index -index_files godoc.idx
build_docs: build_docs:
go tool godoc -v -http=localhost:6060 -index -index_files godoc.idx -write_index go tool godoc -v -http=localhost:6060 -index -index_files godoc.idx -write_index
dev: dev:
go tool air go tool air

@ -22,3 +22,43 @@ go tool godoc -http=localhost:6060 -index
> ___NOTE:___ Google doesn't know how the internet works so you have to use `localhost:PORT` and not `127.0.0.1:PORT` when you run this. > ___NOTE:___ Google doesn't know how the internet works so you have to use `localhost:PORT` and not `127.0.0.1:PORT` when you run this.
After that it'll take some time to index everything but you can already start browsing the APIs you need, and your project's stuff is in the _Third Party_ section. After that it'll take some time to index everything but you can already start browsing the APIs you need, and your project's stuff is in the _Third Party_ section.
## Dealing With Module Bullshit
The way to think about Go's modules is that they don't have modules, they have "projects." Every
directory's .go files export all of their functions without any namespacing based on the file's
name. So if you put `FooBar` into `tools.go` and `Dipshit` in `fuckyou.go` then your namespace for
_all_ files has raw dogged `FooBar` and `Dipshit` without any reference to `tools` or `fuckyou`.
That's because your root directory is a whole __project__, not a module. To then create a namespace
you have to make a directory, place the files in that directory, and add `package mymod` at the top
of those files. You then have to import this as if it's an __entire fucking project__ everywhere
you want to use it:
```go
import 'mywholewebsite.com/rootproject/subproject'
```
In this case you have a directory `subproject` and in there are a bunch of .go files with `package subproject` at the top to indicate they are in that subproject. Thinking about Go's modules as separate projects helps to sort out this `import` statement.
1. mkdir tools
2. Create files in tools/ with `package tools`
3. `import "zedshaw.games/webapp/tools"` to get the subdirectory
## Why Did Go Do This?
That's because it comes from Google, and Google is famous for two things:
1. Using a monorepo.
2. Assuming everyone else uses a monorepo.
Don't believe me? Go look at the official first document [covering modules](https://go.dev/doc/tutorial/create-module) and you'll see they create two _totally separate projects at the root which then link to each other_. That's not how anyone else thinks when they make a single project to work on, but if you're suffering in monorepo hell you'll do it this way.
I'll also posit that Google has some weird incentive that measures numbers of projects in the
monorepo as some kind of metric for employee productivity, so everyone working there is motivated
to get as many little projects into the monorepo as possible, thus a great way to get them to adopt
Go is to make Go support pulling tons of random projects from the root of a monorepo.
So that's why you have to put your whole entire domain name into the import, and why you have all
the functions just raw dogged into your face when you make multiple files, and why subdirectories
are treated like whole little projects.

@ -1,12 +1,15 @@
module zedshaw-games/fibertest module zedshaw.games/webapp
go 1.24.2 go 1.24.2
require ( require (
github.com/Masterminds/squirrel v1.5.4 github.com/Masterminds/squirrel v1.5.4
github.com/go-playground/validator/v10 v10.26.0
github.com/gofiber/fiber/v2 v2.52.8 github.com/gofiber/fiber/v2 v2.52.8
github.com/gofiber/template/html/v2 v2.1.3
github.com/jmoiron/sqlx v1.4.0 github.com/jmoiron/sqlx v1.4.0
github.com/mattn/go-sqlite3 v1.14.28 github.com/mattn/go-sqlite3 v1.14.28
github.com/valyala/quicktemplate v1.8.0
) )
require ( require (
@ -35,9 +38,10 @@ require (
github.com/go-faster/errors v0.7.1 // indirect github.com/go-faster/errors v0.7.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.26.0 // indirect
github.com/go-sql-driver/mysql v1.9.2 // indirect github.com/go-sql-driver/mysql v1.9.2 // indirect
github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/glob v0.2.3 // indirect
github.com/gofiber/template v1.8.3 // indirect
github.com/gofiber/utils v1.1.0 // indirect
github.com/gohugoio/hugo v0.147.6 // indirect github.com/gohugoio/hugo v0.147.6 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
@ -78,7 +82,6 @@ require (
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d // indirect github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.58.0 // indirect github.com/valyala/fasthttp v1.58.0 // indirect
github.com/valyala/quicktemplate v1.8.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect
github.com/vertica/vertica-sql-go v1.3.3 // indirect github.com/vertica/vertica-sql-go v1.3.3 // indirect
github.com/ydb-platform/ydb-go-genproto v0.0.0-20241112172322-ea1f63298f77 // indirect github.com/ydb-platform/ydb-go-genproto v0.0.0-20241112172322-ea1f63298f77 // indirect

@ -135,6 +135,8 @@ github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
@ -150,6 +152,12 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gofiber/fiber/v2 v2.52.8 h1:xl4jJQ0BV5EJTA2aWiKw/VddRpHrKeZLF0QPUxqn0x4= github.com/gofiber/fiber/v2 v2.52.8 h1:xl4jJQ0BV5EJTA2aWiKw/VddRpHrKeZLF0QPUxqn0x4=
github.com/gofiber/fiber/v2 v2.52.8/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofiber/fiber/v2 v2.52.8/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc=
github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
github.com/gofiber/template/html/v2 v2.1.3 h1:n1LYBtmr9C0V/k/3qBblXyMxV5B0o/gpb6dFLp8ea+o=
github.com/gofiber/template/html/v2 v2.1.3/go.mod h1:U5Fxgc5KpyujU9OqKzy6Kn6Qup6Tm7zdsISR+VpnHRE=
github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e h1:QArsSubW7eDh8APMXkByjQWvuljwPGAGQpJEFn0F0wY= github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e h1:QArsSubW7eDh8APMXkByjQWvuljwPGAGQpJEFn0F0wY=
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ= github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ=

@ -1,15 +1,15 @@
package main package webapp
import ( import (
"log" "log"
"zedshaw.games/webapp/tools"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/template/html/v2"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
"github.com/go-playground/validator/v10"
) )
type Link struct { type Link struct {
@ -26,34 +26,6 @@ type Stream struct {
} }
func SelectJson[T any](db *sqlx.DB, c *fiber.Ctx, err error, sql string, args ...interface{}) error {
if(err != nil) {
log.Fatalln(err)
}
var result []T
err = db.Select(&result, sql, args...)
if(err != nil) {
log.Fatalln(err);
}
return c.JSON(&result);
}
func GetJson[T any](db *sqlx.DB, c *fiber.Ctx, err error, sql string, args ...interface{}) error {
if(err != nil) {
log.Fatalln(err)
}
var result T
err = db.Get(&result, sql, args...)
if(err != nil) {
log.Fatalln(err);
}
return c.JSON(&result);
}
func main() { func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile) log.SetFlags(log.LstdFlags | log.Lshortfile)
@ -62,41 +34,35 @@ func main() {
log.Fatalln(err) log.Fatalln(err)
} }
app := fiber.New() engine := html.New("./views", ".html")
app := fiber.New(fiber.Config{
Views: engine,
})
app.Use(logger.New()) app.Use(logger.New())
// handler that returns one json from a sql database // handler that returns one json from a sql database
app.Get("/api/stream/", func (c *fiber.Ctx) error { app.Get("/api/stream/", func (c *fiber.Ctx) error {
sql, args, err := sq.Select("*").From("stream").ToSql() sql, args, err := sq.Select("*").From("stream").ToSql()
return SelectJson[Stream](db, c, err, sql, args...) return tools.SelectJson[Stream](db, c, err, sql, args...)
}) })
app.Get("/api/stream/:id", func (c *fiber.Ctx) error { app.Get("/api/stream/:id", func (c *fiber.Ctx) error {
sql, args, err := sq.Select("*").From("stream").Where("id", c.Params("id")).ToSql() sql, args, err := sq.Select("*").From("stream").Where("id", c.Params("id")).ToSql()
return GetJson[Stream](db, c, err, sql, args...) return tools.GetJson[Stream](db, c, err, sql, args...)
}) })
app.Get("/api/stream/:id/links", func (c *fiber.Ctx) error { app.Get("/api/stream/:id/links", func (c *fiber.Ctx) error {
sql, args, err := sq.Select("*").From("stream_link").Where("stream_id", c.Params("id")).ToSql() sql, args, err := sq.Select("*").From("stream_link").Where("stream_id", c.Params("id")).ToSql()
return SelectJson[Link](db, c, err, sql, args...) return tools.SelectJson[Link](db, c, err, sql, args...)
}) })
app.Post("/api/link", func (c *fiber.Ctx) error { app.Post("/api/link", func (c *fiber.Ctx) error {
link := new(Link) link, err := tools.GetThing[Link](c)
if err := c.BodyParser(link); err != nil {
log.Println(err);
return c.Redirect("/live/")
}
var validate *validator.Validate if(err != nil) {
validate = validator.New(validator.WithRequiredStructEnabled()) log.Println(err)
c.Redirect("/live/")
if err := validate.Struct(link); err != nil {
validationErrors := err.(validator.ValidationErrors)
log.Println(validationErrors)
return c.Redirect("/live/")
} }
sql, args, err := sq.Insert("stream_link").Columns("stream_id", "url", "description").Values(link.StreamId, link.Url, link.Description).ToSql() sql, args, err := sq.Insert("stream_link").Columns("stream_id", "url", "description").Values(link.StreamId, link.Url, link.Description).ToSql()
@ -113,5 +79,11 @@ func main() {
app.Static("/", "./public") app.Static("/", "./public")
app.Get("/test/", func (c *fiber.Ctx) error {
return c.Render("index", fiber.Map{
"Title": "Hello, World!",
})
})
log.Fatal(app.Listen(":5001")) log.Fatal(app.Listen(":5001"))
} }

@ -0,0 +1,59 @@
package tools
import (
"log"
"github.com/gofiber/fiber/v2"
_ "github.com/mattn/go-sqlite3"
"github.com/jmoiron/sqlx"
"github.com/go-playground/validator/v10"
)
func SelectJson[T any](db *sqlx.DB, c *fiber.Ctx, err error, sql string, args ...interface{}) error {
if(err != nil) {
log.Fatalln(err)
}
var result []T
err = db.Select(&result, sql, args...)
if(err != nil) {
log.Fatalln(err);
}
return c.JSON(&result);
}
func GetJson[T any](db *sqlx.DB, c *fiber.Ctx, err error, sql string, args ...interface{}) error {
if(err != nil) {
log.Fatalln(err)
}
var result T
err = db.Get(&result, sql, args...)
if(err != nil) {
log.Fatalln(err);
}
return c.JSON(&result);
}
func GetThing[T any](c *fiber.Ctx) (*T, error) {
var result *T
result = new(T)
if err := c.BodyParser(result); err != nil {
log.Println(err);
return result, err
}
var validate *validator.Validate
validate = validator.New(validator.WithRequiredStructEnabled())
if err := validate.Struct(result); err != nil {
validationErrors := err.(validator.ValidationErrors)
log.Println(validationErrors)
return result, err
}
return result, nil
}

@ -0,0 +1,5 @@
{{template "partials/header" .}}
<h1>{{.Title}}</h1>
{{template "partials/footer" .}}

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>Main</title>
</head>
<body>
{{embed}}
</body>
</html>

@ -0,0 +1 @@
</h2>Footer</h2>

@ -0,0 +1 @@
<h1>Header</h1>
Loading…
Cancel
Save