From 68729564781ccc726a3e8a23e0cca504a1018c86 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Thu, 5 Mar 2026 00:25:05 -0500 Subject: [PATCH] Big documentation update. --- README.md | 537 ++++++++++++++++++++++++++++++++++++++++- common/email/api.go | 3 + features/init.go | 2 + pages/index.md | 269 +-------------------- tools/cmd/fgen/main.go | 1 + tools/cmd/qmgr/main.go | 5 +- 6 files changed, 542 insertions(+), 275 deletions(-) diff --git a/README.md b/README.md index 3cf16ff..6b264b6 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,535 @@ # Go Web Starter Kit -This is a fairly complete web development starter kit in Go. It tries to -be as simple as possible without leaving out modern features like reactive UIs and database -migrations. A primary thing that's included is working authentication, since that's the main thing -holding people back when they first start, and also the easiest to get wrong. +This is a fairly complete web development starter kit in Go. It tries to be as simple as possible +without leaving out modern features. The goal is to create a setup that a normal person can learn +and study core web technologies like the DOM and web servers. I would classify it as, "Old school +with modern things." -In fact, if you look at how I do it in this first version it is _WRONG_ so do not use this in -production yet until I can make it correct. Just use it to learn for now. +## The Stack + +Currently I'm using the following components in a tastefully crafted stack similar to a Crepe Cake: + +* [goose](https://github.com/pressly/goose) -- This manages the database migrations. +* [sqlx](https://github.com/jmoiron/sqlx) -- This is the database driver. +* [squirrel](https://github.com/Masterminds/squirrel) -- This is the SQL query generator. +* [Fiber](https://gofiber.io/) -- This is the main web API and does most everything you need. +* [tailwind](https://tailwindcss.com/) -- This makes your sites perty and is easy to use, but I + reject the `@apply` slander and use `@apply` to give you nice things. +* [ssgod](https://lcthw.dev/go/ssgod) -- A static site generator I wrote that _only_ does static site generation and nothing else...unlike Hugo. +* [ozai](https://lcthw.dev/go/ozai) -- A process manager that replaced Air because Air went crazy like Hugo. +* [chromedp](https://github.com/chromedp/chromedp) -- This does your automated testing for you. + +I then add a few little "sweetener" APIs on top of this for simple things like, making the tests +nicer or making it easier to quickly return a page from a view. ## Getting Started -Programmers hate duplication so if you want the instructions read the [index.md file in -pages](pages/index.md). +First, install a couple tools that'll be used everywhere, and that have huge dependencies you don't +actually need in your project: + +```shell +go install golang.org/x/pkgsite/cmd/pkgsite@latest +go install github.com/pressly/goose/v3/cmd/goose@latest +``` + +To start your own project do this: + +```shell +git clone https://lcthw.dev/go/go-web-starter-kit.git my-project +cd my-project +``` + +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: + +```shell +git init . +go mod tidy +cp config_example.json config.json +make migrate_up +sqlite3 db.sqlite3 ".read tools/pragmas.sql" +make +make dev +``` + +This gets the site running and ready for development. You only need _to do this once._. The next +day that you want to work, cd to your project and only run `make dev`: + +```shell +make dev +``` + +## Linux Bug? + +I found that on Ubuntu the `tailwindcss` command "wouldn't run." Turns out when `tailwindcss` forks +it does the right thing and closes all possible open file descriptors, _but_ Ubuntu has set the hard +limit to 1 billion: + +```shell +$ ulimit -Hn +1073741816 +``` + +This is a _lot_ of files to try to close, so `tailwindcss` is actually stuck doing that. You can +fix this with: + +```shell +ulimit -n 65536 +``` + +You can pick any reasonable number, and then `tailwindcss` works like expected. + +## Configuration + +There's 3 basic moving parts to the whole system: `webapp`, `ssgod`, `tailwind`, and `ozai`. + +You can configure the `webapp` using the `config.json` which you copied over from `config_example.json`. The options are fairly self-explanatory. + +You can configure [ssgod](https://lcthw.dev/go/ssgod) using the +`ssgod.json` file. You'll notice that there's _two_ layouts with this setup, one for `webapp` +and another for `ssgod`. You can just point `ssgod` at your `views/layouts/main.html` file if you +want, but it seems people want two different layouts for their static site vs. "the app." + +You can configure `tailwind` using the `static/input_style.css` and editing the `Makefile` to have +it run how you want. I use the `@apply` heavily because I'm a programmer and no programmer would +ever tell someone they should sprinkle repetitive bullshit all over their HTML. Resist the +authority. Use `@apply`. + +Finally, you can configure other processes to run with [Ozai](https://lcthw.dev/go/ozai) in the +`.ozai.json` file. Why so many different formats? I'm still learning which is best to use and while +I like TOML I think I may go with `.json` files to reduce dependencies. You tell me though. + +## Ozai Autorestarts + +Ozai is a simpler tool that doesn't do auto restart. Instead it will create a little webserver at +`http://127.0.0.1:9999` with endpoints you can hit with curl to force a restart. Look in the +.ozai.json file to see what's configured. This makes it easy to simple add a `curl` call in your +build process and kick everything over. Look in the `Makefile` for how I do this. + +This works better than the way `Air` does it for a few reasons: + +1. You can run your build like normal in your own IDE or build tool and see your errors directly. + When you use other autobuild tools they have the errors so you can't access them. +2. You get an immediate restart after your build succeeds, rather than waiting for an external tool + to detect you made changes, run the build, and then restart your browser. +3. You avoid the "auto reload deadlock" where you run a build, the server is shutdown, but you hit + refresh too soon, so now your browser waits for an error, then you have to refresh again. Way +easier to just not use any of that. +4. No more silent failures hiding inside your terminal that you can't see. Just manually restart + it. + +For more, checkout the [Ozai Project](https://lcthw.dev/go/ozai"). + +## Tour of Directories + +The directories are organized in a way that separates "App Stuff" from "Content Stuff". + +* `admin` App The code for the built-in database admin tool. +* `bin` App Binaries that run your app like `webapp`. +* `features` App Where you put your features. +* `common`App Common helper functions. +* `config` App The configuration for your site, which is `config.json` loaded into Go structs. +* `data` App Your database stuff. You put your SQL models in here, and then add them to the bottom `Map` to +make them magically show up in the `/admin/table/` tool. +* `migrations` App When you use [goose](https://github.com/pressly/goose) it puts the migrations in here. +* `tests` App Your tests go in here. I use [chromedp](https://github.com/chromedp/chromedp) but the `chromedp` +API is too low level (and weird) to be useful so look in `tests/tools.go` for what I use. +* `tools` App Various admin automation tools go here. +* `views` Content This is where you put the _App_ contents, not the static content. Sadly, this + should be in `features/` but Fiber makes that too difficult. +* `emails` Content This where you put templates for emails you'll send. +* `pages` Content These are templates that are generated once and then served statically. +* `static` Static This is static content like `.js`, images, and `.css`. + +The following directories are considered "build junk" and you _should not_ commit them to your git. + +* `public` Junk This is generated by `ssgod` and then served by your `webapp`. Don't + edit anything in here, and instead edit in `pages` or `static`. +* `tmp` Junk This is made during the build/testing process. + +## Dev Workflow + +The first thing I do is run each of these commands in different terminals, either with tmux or just +using my fingers to run multiple tabs in PowerShell: + +```shell +make dev +``` + +### 1. Create a Feature + +The first step is to create a `feature`, which is nothing more than some files in a directory that +you edit to make your `pages/` actually work. You can create a feature like this: + +```shell +./bin/fgen -name myfeatue +``` + +This will create the files and directories you need to make a new feature: + +`features/myfeature/` +: This contains all of the go code. + +`views/myfeature/` +: This is where your HTML goes. + +`tests/myfeature/` +: This is where your tests go. + +Next you add it to `features/init.go` to enable it: + +```diff +@@ -8,2 +8,3 @@ import ( + "MY/webapp/features/fakepay" ++ "MY/webapp/features/myfeature" + ) +@@ -15,2 +16,3 @@ func Setup(app *fiber.App) { + features_fakepay.Setup(app) ++ features_myfeature.Setup(app) + } +``` + +> __NOTE__: I'm going to automate this in the future, so expect this file to be generated. + +After that run `make` to build everything again and you should have a new feature at +`http://127.0.0.1:7001/myfeature/` +with your browser. + +### 2. Create Initial Views + +The design of this system is that you do _not_ have to edit 16 files just get an idea going. You +can have an idea and create a single `.html` or `.md` file in `views/myfeature` to see it happen. This lets +you work out all of the URLs, page flows, layouts, and basic design before writing any more code. + +This is generally a better way to work than creating the backend models first as it constrains what +you create to only exactly what you need for the UI you'll implement. + +What I do is make a new file in `myfeature/views/`, cook up a quick 2D layout that's mostly what I want, then +make it link to other `myfeature/views/` to figure out the feature. Once I have a rough UI for the feature +worked out I move on to the next step. + +### Using the Tailwind Starter + +I've included [tailwind](https://tailwindcss.com/) in this setup, since it's decent at getting +things working quickly right in the HTML. The only thing I've fully rejected is the idea that +"@apply is bad m'kay." No, `@apply` makes it so I don't rage at all the idiots who think 92 +repeated CSS classes on every `div` is "good practice." + +> __NOTE__ You can view a sample page with all of these in [/examples/sample.html](/examples/sample.html) and you can look at the other files in `examples` to get an idea of what you can do. Some of these just use tailwind, others use my starter kit. + +In my setup there's a `static/input_style.css` that is used to generate a `static/style.css` and it +comes preconfigured with many of the things you'll need to get a page up and running. In theory +you can prototype a first page with just HTML and a bit of tailwind as needed. It also uses many of +the stock HTML tags like `aside` and `mark` so you can keep things nice. + +I've added 4 additional layout tags for you to use: + +* `grid` -- For stuff in a grid. Add `class="grid-cols-2"` from tailwind to change its layout. +* `block` -- A simple pre-configured vertical flex box. Put it around vertical stuff. +* `bar` -- A simple pre-configured horizontal flex box. Pit it around horizontal stuff. +* `stack` -- A simple way to stack stuff on top of other stuff. You know, like in every single 2d compositional system since the 1500s. +* `shape` -- A simple 2d box to use for placeholders where you'll put images. It's a lot quicker to drop a little `shape` than to go find an image. I think it also makes it easier to focus on the design. + + +### 3. Create Fake Data + +Create a fake set of data that comes from your page prototype. For example, if I have a User +Profile page then I might add this: + +```html + +``` + +This will give you something to work with while you sort out how the interactive parts of your page +will work. If you don't have any then you can skip this part. + +### 4. Refine the UI Interactions + +Using the fake data determine the user interactions for the view. Remember that this system is +designed to minimize the amount of work you need on the front-end, so all you're doing here is +simple things like validating a form, getting data, disabling/enabling fields, and anything that +stays _on one page_. If you find yourself trying to cram multiple pages into one then you've gone +too far. + +With my simple use-profile example I could create the function that renders the table on page load: + +```javascript +$boot(async() => { + const tmpl = $id('table-row'); + const target = $id('table-items'); + + for(let key in data) { + $append(target, + $render(tmpl, {key, value: data[key]})); + } +}); +``` + +This comes from my `jzed.js` file that's a minimalist old-school DOM helper. I also include +[lit.js](https://lit.dev/) if you prefer to create little components for things. + +### 5. Move the Fake Data to `api.go` + +You should be spending a lot of time working out the data and interactions in the nice convenience +of the page you're working on, but eventually that data needs to come from the server. To continue +the _User Profile_ example we can create a handler in `api.go` that returns this same fake data: + +```go +package features_myfeature + +import ( + "github.com/gofiber/fiber/v2" +) + +type UserProfile struct { + UserName string + Email string +} + +func GetApiUserProfile(c *fiber.Ctx) error { + profile := UserProfile{ + UserName: "Zed", + Email: "zed@zed.com", + } + + return c.JSON(profile) +} + +func SetupApi(app *fiber.App) { + app.Get("/api/myfeature/user_profile", GetApiUserProfile) +} +``` + +This is just enough code to get a simple JSON API going that we can load from the `index.html` +you've been editing. To grab the data just do this: + +```javascript + const data = await GetJson('/api/myfeature/user_profile'); +``` + +In place of your prototype data. Doing that will get the data from the `api.go` code you just +wrote. + +### 6. Store the Data in `db.go` + +Once you've sorted out how the data is going to come from your API you can start to store it in the +database. The first step for that is to move your data into `db.go`: + +```go +package features_myfeature + +type UserProfile struct { + UserName string + Email string +} + +func GetUserProfile(id int) (UserProfile, error) { + profile := UserProfile{ + UserName: "John", + Email: "zed@zed.com", + } + + return profile, nil +} +``` + +The `UserProfile` is removed from `api.go` and placed in `db.go`. You then call your +`GetUserProfile` function to get it: + +```go +import ( + "github.com/gofiber/fiber/v2" + . "MY/webapp/common" +) + +func GetApiUserProfile(c *fiber.Ctx) error { + profile, err := GetUserProfile(1) + if err != nil { return IfErrNil(err, c) } + + return c.JSON(profile) +} +``` + +> __NOTE__: This is intended as a simplified way to _grow_ a feature from idea to working backend. +> Eventually you would want to create a separate module for your data models, create your tests for +> them, and other important design considerations. + +### 7. Move Hard Stuff to `views.go` + +There's some things that are too hard to do in JavaScript, but are very easy to do in `views.go` +usign the basic templates available. An example of this is defining the structure of a `` +tag. Doing this in JavaScript is a massive pain, but doing it with a template in `views.go` is +easy. Another example would be setting the `id` for some data object the page should display. It's +far easier to do that right in the template. + +I won't get into how this is done, but look at any of the examples in `features/` and `admin/` to +see me do this. + +### 8. Put it In The Database + +Once you have your data API worked out you can then use +[squirrel](https://github.com/Masterminds/squirrel), [goose](https://github.com/pressly/goose), and +[sqlx](https://github.com/jmoiron/sqlx) to make it store everything in the database. + +First you create a migration for your data: + +```shell +goose sqlite3 db.sqlite3 create create_user_profile sql -dir migrations +``` + +You then edit the `_create_user_profile.sql` file to create the new table: + +```sql +-- +goose Up +-- +goose StatementBegin +CREATE TABLE user_profile ( + username TEXT + email TEXT +); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE user_profile; +-- +goose StatementEnd +``` + +You use `goose` to migrate the database up: + +```shell +goose sqlite3 db.sqlite3 -dir migrations up +``` + +If you make a mistake, you can migrate "down" to undo your last migration: + +```shell +goose sqlite3 db.sqlite3 -dir migrations down +``` + +I also have convenient `Makefile` actions for this: + +```shell +make migrate_up +make migrate_down +``` + +### 9. Rewrite `db.go` to Store It + +The next step needs refinement, so I'll only briefly describe it now: + +1. You have your data, so annotate it with `db: ""` struct tags. +2. Use the `squirrel` and `sqlx` APIs to store it. +3. Add your usual CRUD operations. + +I'm going to improve this so most of this can be automated, so stay tuned. For an example of doing +this look in `admin/table/db.go`. + +### 10. Refine, Refine, Refine + +Finally, nothing in this process is meant to be a lock-step one and done operation. This is simply a +mechanism to help people go from abstract idea to concrete working code with all the stages +necessary. Once you have everything "working" you'll need to refine it. The key to refinement is to +think about how this feature works with other features: + +1. Are you repeating data that some other feature has? Just use that feature's data operations then. +2. Are you repeating components that another feature has? Try making a `lit.js` component you both use. +3. Should you move the data used by many features into a single place? Look at `data/model.go`. + +You should also go over the API and page interactions in your feature, and when it's good move onto +the next feature. The only way to learn this step is to do it a lot, but in general, less is more. +If you've got 10k lines of code in a feature then rethink it. + +## Note on The Process for Pros + +This process is mostly for people who are learning how to build a web application. Once you've done +it this way for a few web apps then you can do it however works best for your idea. + +Some people's ideas are about the data, so starting with the UI like this may not work. If your +idea is about the data, start with the `api.go` and `db.go`, and then work the tests. + +Generally though, I've found that people who start with the backend are mostly only starting there +because that's what they know. They also tend to create glorious crystal palaces that are +completely unnecessary because they aren't grounded in the reality of a UI actual people need to +use. + +When you start with the UI can you can prototype all of the interactions, figure out the data, and +aim for only what you need. Keep doing this and you'll slowly build a nice data model with less +cruft for the application you're creating. + + +## Additional Features + +### Sending Emails + +Coming soon... + +### Receiving Payments + +Coming soon... + +## FAQ + +Here's some questions I get when people see this. + +### Isn't Fiber Banned?! + +First off, nobody has the right to "ban" a project. If you're hanging out in the Go discord and +you've seen Fiber on some ban list then whoever did that is a fucking asshole. Just ignore that +bullshit and use the best tech you can. + +Second, if you're only supposed to use the Go standard library then why [is there a whole fucking +document on the official go website explaining how to use Gin to make an API?](https://go.dev/doc/tutorial/web-service-gin). It seems like whoever is claiming that you are only allowed to use the Go standard library is in disagreement with the _actual fucking Go project_. + +Finally, you don't have to do what you're told, especially when the person telling you what to do is +some random asshole hiding in a chat room being a random asshole. Use what you want, and Fiber is +in that sweet spot of being a nice API that isn't slow dogshit like [Gin](https://www.techempower.com/benchmarks/). + +### Why Not Gin? + +Gin is a piece of crap, that's why. Look at the [Techempower Benchmarks](https://www.techempower.com/benchmarks/) to get an idea of how bad Gin is. It's at the _bottom_ of the list, performing even _worse_ that some of the slowest _Ruby_ web frameworks out there. Make a _compiled language_ like Go perform that bad takes a special kind of incompetence. + +### Why Not Only the Go Standard Library? + +Because I like nice things, and while the Go Standard Library has everything you _need_, it +frequently is missing things you _want_. This project is about adding the things you'll probably +want without getting to abstract and far away from learning the core web technologies. + +### Are Your Tags "Accessible"?! What about SEO?! + +Yes, I actually tested all of this with [NV Access](https://www.nvaccess.org/) and the only thing NV +Access seems to have a problem with is the `pre` and `code` tags. That's because apparently those +are deemed as "images" so they get ignored...which is about the dumbest fucking shit I've ever +heard. I'm working on a way to fix that. + +Anyone telling you that my `grid`, `bar`, `block`, or `stack` tags impact accessibility most likely +has never ran NV Access one time so they're just wrong. Additionally, it's better to have these be +_ignored_ by screen readers because they're just 2D layout junk, so they're kind of irrelevant to a +linear 1D screen reader. + +Finally, SEO isn't impacted because Google has literally said HTML quality doesn't matter for SEO +ranking. Think about it, if Google went around dictating exact HTML they would get in so much +Monopoly trouble. Instead they tend to focus more on content and organization, and I believe the +official quote is, "Making everyone use the same HTML would make the web boring." + +However, take this with a grain of salt because the same Google people who've said this also said +that backlinks didn't impact performance and many SEO experts say this is totally not true. Do your +own testing, and if it impacts your SEO than just change them after you get your initial design up. + diff --git a/common/email/api.go b/common/email/api.go index a587cc3..77f5119 100644 --- a/common/email/api.go +++ b/common/email/api.go @@ -97,6 +97,7 @@ func (router *Router) DeliverEmail(msg EmailMessage) error { email_msg.SetGenHeader(mail.HeaderUserAgent, config.Settings.Email.UserAgent) } + log.Println("template is", msg.Template) text, html, err := router.Render(msg.Template, nil) if err != nil { return err } @@ -135,6 +136,8 @@ func (router *Router) HandleEmailRequest() error { } func (sender *Sender) QueueEmail(msg EmailMessage) error { + + log.Println("email msg", msg) msg_json, err := json.Marshal(msg) if err != nil { return err } diff --git a/features/init.go b/features/init.go index a283162..ac71ee2 100644 --- a/features/init.go +++ b/features/init.go @@ -6,6 +6,7 @@ import ( "MY/webapp/features/paypal" "MY/webapp/features/shopping" "MY/webapp/features/fakepay" + "MY/webapp/features/myfeature" ) func Setup(app *fiber.App) { @@ -13,4 +14,5 @@ func Setup(app *fiber.App) { features_paypal.Setup(app) features_shopping.Setup(app) features_fakepay.Setup(app) + features_myfeature.Setup(app) } diff --git a/pages/index.md b/pages/index.md index 93bf624..732dcce 100644 --- a/pages/index.md +++ b/pages/index.md @@ -1,267 +1,4 @@ -# Go Web Starter Kit +# Welcome! -This is a fairly complete web development starter kit in Go. It tries to -be as simple as possible without leaving out modern features like reactive UIs and database -migrations. A primary thing that's included is working authentication, since that's the main thing -holding people back when they first start, and also the easiest to get wrong. - -In fact, if you look at how I do it in this first version it is _WRONG_ so do not use this in -production yet until I can make it correct. Just use it to learn for now. - -## The Stack - -Currently I'm using the following components in a tastefully crafted stack similar to a Crepe Cake: - -* [goose](https://github.com/pressly/goose) -- This manages the database migrations. -* [sqlx](https://github.com/jmoiron/sqlx) -- This is the database driver. -* [squirrel](https://github.com/Masterminds/squirrel) -- This is the SQL query generator. -* [Fiber](https://gofiber.io/) -- This is the main web API and does most everything you need. -* [tailwind](https://tailwindcss.com/) -- This makes your sites perty and is easy to use, but I - reject the `@apply` slander and use `@apply` to give you nice things. -* [Alpine.js](https://alpinejs.dev/) -- This gives you just enough reactivity to not be annoyed, but - not so much that you hate the web. -* [ssgod](https://lcthw.dev/go/ssgod) -- A static site - generator I wrote that _only_ does static site generation. -* [chromedp](https://github.com/chromedp/chromedp) -- This does your automated testing for you. - -I then add a few little "sweetener" APIs on top of this for simple things like, making the tests -nicer or making it easier to quickly return a page from a view. - -### Isn't Fiber Banned?! - -First off, nobody has the right to "ban" a project. If you're hanging out in the Go discord and -you've seen Fiber on some ban list then whoever did that is a fucking asshole. Just ignore that -bullshit and use the best tech you can. - -Second, if you're only supposed to use the Go standard library then why [is there a whole fucking -document on the official go website explaining how to use Gin to make an API?](https://go.dev/doc/tutorial/web-service-gin). It seems like whoever is claiming that you are only allowed to use the Go standard library is in disagreement with the _actual fucking Go project_. - -Finally, you don't have to do what you're told, especially when the person telling you what to do is -some random asshole hiding in a chat room being a random asshole. Use what you want, and Fiber is -in that sweet spot of being a nice API that isn't slow dogshit like [Gin](https://www.techempower.com/benchmarks/). - -## Getting Started - -First, install a couple tools that'll be used everywhere, and that have huge dependencies you don't -actually need in your project: - -```shell -go install golang.org/x/pkgsite/cmd/pkgsite@latest -go install github.com/pressly/goose/v3/cmd/goose@latest -``` - -You can get use this project working by doing this: - -```shell -git clone https://lcthw.dev/go/go-web-starter-kit.git my-project -cd my-project -``` - -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: - -```shell -git init . -go mod tidy -cp config_example.toml config.toml -make migrate_up -sqlite3 db.sqlite3 ".read tools/pragmas.sql" -make build -make dev -``` - -This gets the site running and ready for development. You only need _to do this once._. After -this, when you're ready to work just do: - -```shell -make dev -``` - -## Linux Bug? - -I found that on Ubuntu the `tailwindcss` command "wouldn't run." Turns out when `tailwindcss` forks -it does the right thing and closes all possible open file descriptors, _but_ Ubuntu has set the hard -limit to 1 billion: - -```shell -$ ulimit -Hn -1073741816 -``` - -This is a _lot_ of files to try to close, so `tailwindcss` is actually stuck doing that. You can -fix this with: - -```shell -ulimit -n 65536 -``` - -You can pick any reasonable number, and then `tailwindcss` works like expected. - -## Configuration - -There's 3 basic moving parts to the whole system: `webapp.exe`, `ssgod`, `tailwind`, and `ozai`. - -You can configure the `webapp.exe` using the `config.toml` which you copied over from `config_example.toml`. The options are fairly self-explanatory. - -You can configure [ssgod](https://lcthw.dev/go/ssgod) using the -`ssgod.toml` file. You'll notice that there's _two_ layouts with this setup, one for `webapp.exe` -and another for `ssgod`. You can just point `ssgod` at your `views/layouts/main.html` file if you -want, but it seems people want two different layouts for their static site vs. "the app." - -You can configure `tailwind` using the `static/input_style.css` and editing the `Makefile` to have -it run how you want. I use the `@apply` heavily because I'm a programmer and no programmer would -ever tell someone they should sprinkle repetitive bullshit all over their HTML. Resist the -authority. Use `@apply`. - -Finally, you can configure other processes to run with [Ozai](https://lcthw.dev/go/ozai) in the -`.ozai.json` file. Why so many different formats? I'm still learning which is best to use and while -I like TOML I think I may go with `.json` files to reduce dependencies. You tell me though. - -## Ozai Autorestarts - -Ozai is a simpler tool that doesn't do auto restart. Instead it will create a little webserver at -`http://127.0.0.1:9999` with endpoints you can hit with curl to force a restart. Look in the -.ozai.json file to see what's configured. This makes it easy to simple add a `curl` call in your -build process and kick everything over. Look in the `Makefile` for how I do this. - -This works better than the way `Air` does it for a few reasons: - -1. You can run your build like normal in your own IDE or build tool and see your errors directly. - When you use other autobuild tools they have the errors so you can't access them. -2. You get an immediate restart after your build succeeds, rather than waiting for an external tool - to detect you made changes, run the build, and then restart your browser. -3. You avoid the "auto reload deadlock" where you run a build, the server is shutdown, but you hit - refresh too soon, so now your browser waits for an error, then you have to refresh again. Way -easier to just not use any of that. -4. No more silent failures hiding inside your terminal that you can't see. Just manually restart - it. - -For more, checkout the [Ozai Project](https://lcthw.dev/go/ozai"). - -## Tour of Directories - -The directories are organized in a way that separates "App Stuff" from "Content Stuff". - -* `admin` App The code for the built-in database admin tool. -* `api` App Where you put your JSON API and `views` handlers. -* `common`App Common helper functions. -* `config` App The configuration for your site, which is `config.toml` loaded into Go structs. -* `data` App Your database stuff. You put your SQL models in here, and then add them to the bottom `Map` to -make them magically show up in the `/admin/table/` tool. -* `migrations` App When you use [goose](https://github.com/pressly/goose) it puts the migrations in here. -* `tests` App Your tests go in here. I use [chromedp](https://github.com/chromedp/chromedp) but the `chromedp` -API is too low level (and weird) to be useful so look in `tests/tools.go` for what I use. -* `tools` App I put my little automation tools here. I'll be adding an `admin` tool that will let you admin the -database from the CLI, plus probably a tool to run all the things and manage them. -* `views` App This is where you put the _App_ contents, not the static content. See the _Dev Workflow_ section -on how to work to make dev faster but also use static files in production when things are working. - -These directories then control you _static_ content, but are also used by the _App_ content. For -example, `static/` contains the `static/input_style.css` which is turned into `static/style.css`. -The `static/style.css` is used by everything to--you guessed it--style your whole site. - -* `pages` Static This is the static content you want generated by `ssgod`. See _Dev Workflow_ for how I - use this. -* `static` Static This is static content like `.js`, images, and `.css` files that are only _copied_ over - by `ssgod`. - -The following directories are considered "build junk" and you _should not_ commit them to your git. - -* `public` Junk This is generated by `ssgod` and then served by your `webapp.exe`. Don't - edit anything in here, and instead edit in `pages` or `static`. -* `tmp` Junk This is made during the build/testing process. - -## Dev Workflow - -> __Warning__ Parts of this are not optimal. I have to work on how to make this easier, parmarily -> how to run all the things with one command and manage them. - -The first thing I do is run each of these commands in different terminals, either with tmux or just -using my fingers to run multiple tabs in PowerShell: - -```shell -make dev -``` - -### Working on Pages - -Once those are running in different terminals I mostly have everything I need to work in my editor -of choice and have things autobuild. I then put my content into `pages/` and manually reload after -`ssgod`/`tailwind` autoruns. - -### Working on Views - -Another way is to put your content into views, and then add this one line -of code to your `api/handlers.go`. For example, if I want to create `/mypage/` I do this: - -```go -# make the file with an editor on Windows -echo "Test" > views/mypage.html - -# edit api/handlers.go -app.Get("/mypage/", Page("mypage")) -``` - -> __Warning__ On windows the above `echo` command creates garbage in the output because Windows is -> weird. Just make the file with a text editor. - -## Using the Tailwind Starter - -I've included [tailwind](https://tailwindcss.com/) in this setup, since it's decent at getting -things working quickly right in the HTML. The only thing I've fully rejected is the idea that -"@apply is bad m'kay." No, `@apply` makes it so I don't rage at all the idiots who think 92 -repeated CSS classes on every `div` is "good practice." - -> __NOTE__ You can view a sample page with all of these in [/examples/sample.html](/examples/sample.html) and you can look at the other files in `examples` to get an idea of what you can do. Some of these just use tailwind, others use my starter kit. - -In my setup there's a `static/input_style.css` that is used to generate a `static/style.css` and it -comes preconfigured with many of the things you'll need to get a page up and running. In theory -you can prototype a first page with just HTML and a bit of tailwind as needed. It also uses many of -the stock HTML tags like `aside` and `mark` so you can keep things nice. - -I've added 4 additional layout tags for you to use: - -* `grid` -- For stuff in a grid. Add `class="grid-cols-2"` from tailwind to change its layout. -* `block` -- A simple pre-configured vertical flex box. Put it around vertical stuff. -* `bar` -- A simple pre-configured horizontal flex box. Pit it around horizontal stuff. -* `stack` -- A simple way to stack stuff on top of other stuff. You know, like in every single 2d compositional system since the 1500s. -* `shape` -- A simple 2d box to use for placeholders where you'll put images. It's a lot quicker to drop a little `shape` than to go find an image. I think it also makes it easier to focus on the design. - -### Is that "Accessible"?! What about SEO?! - -Yes, I actually tested all of this with [NV Access](https://www.nvaccess.org/) and the only thing NV -Access seems to have a problem with is the `pre` and `code` tags. That's because apparently those -are deemed as "images" so they get ignored...which is about the dumbest fucking shit I've ever -heard. I'm working on a way to fix that. - -Anyone telling you that my `grid`, `bar`, `block`, or `stack` tags impact accessibility most likely -has never ran NV Access one time so they're just wrong. Additionally, it's better to have these be -_ignored_ by screen readers because they're just 2D layout junk, so they're kind of irrelevant to a -linear 1D screen reader. - -Finally, SEO isn't impacted because Google has literally said HTML quality doesn't matter for SEO -ranking. Think about it, if Google went around dictating exact HTML they would get in so much -Monopoly trouble. Instead they tend to focus more on content and organization, and I believe the -official quote is, "Making everyone use the same HTML would make the web boring." - -However, take this with a grain of salt because the same Google people who've said this also said -that backlinks didn't impact performance and many SEO experts say this is totally not true. Do your -own testing, and if it impact your SEO than just change them after you get your initial design up. - -## Conclusion - -Hopefully that gets you started in this project. I'll be improving the usability of this as I use it -myself, but if you have suggestions please email me at help@learncodethehardway.com. +In the `pages` directory you can write your pages using HTML or markdown. Just end the file with +`.md` like this one and it'll be rendered with Markdown. diff --git a/tools/cmd/fgen/main.go b/tools/cmd/fgen/main.go index 0d85cb2..483f603 100644 --- a/tools/cmd/fgen/main.go +++ b/tools/cmd/fgen/main.go @@ -19,6 +19,7 @@ type Config struct { } func WriteTemplate(config Config, from string, to string) error { + fmt.Println("CREATE", to) source, err := templates.ReadFile(from) if err != nil { return err } diff --git a/tools/cmd/qmgr/main.go b/tools/cmd/qmgr/main.go index ef205cb..d849815 100644 --- a/tools/cmd/qmgr/main.go +++ b/tools/cmd/qmgr/main.go @@ -4,6 +4,7 @@ import ( "MY/webapp/config" email "MY/webapp/common/email" "context" + "fmt" ) func main() { @@ -15,9 +16,11 @@ func main() { To: "tina.recip@example.com", From: "toni.sender@example.com", Subject: "This is my first mail.", - Template: "signup", + Template: "signup.md", } + fmt.Println("sending", msg) + sender := email.NewSender(ctx) defer sender.Close()