From e8d4d8d8abc05994a04aab9b742dee1176ab7757 Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Fri, 13 Jun 2025 14:59:39 -0700 Subject: [PATCH] (DOCS): Created the Project Structure guide. This serves as outsider notes as well as my own reference sheet. --- doc/ProjectStructure.md | 101 ++++++ .../TechnicalSpecification.md | 0 examples/hello.templ | 45 +++ examples/hello_templ.go | 296 ++++++++++++++++++ examples/main.go | 19 ++ flake.nix | 2 + tailwind.config.js | 5 + 7 files changed, 468 insertions(+) create mode 100644 doc/ProjectStructure.md rename TechnicalSpecification.md => doc/TechnicalSpecification.md (100%) create mode 100644 examples/hello.templ create mode 100644 examples/hello_templ.go create mode 100644 examples/main.go create mode 100644 tailwind.config.js diff --git a/doc/ProjectStructure.md b/doc/ProjectStructure.md new file mode 100644 index 0000000..5f0fd3f --- /dev/null +++ b/doc/ProjectStructure.md @@ -0,0 +1,101 @@ +# Project Structure + + +- [Project Structure](#project-structure) + - [Data Flow](#data-flow) + - [Directory Structure](#directory-structure) + - [Handler](#internalapphanders) + - [Services](#internalappservices) + - [Domain](#internaldomain) + - [Infrastructure](#internalinfrastructuredatabase) + - [Templates](#internaltemplates) + - [Static](#webstatic) + + +This document describes how the **Potion project** is build. Where each piece can +be found, and where new pieces should be built. + +This project attempts to follow the **Domain Driven Design** (DDD) pattern which +means that the software should be written in a way that represents the business +requirements and domain. The goal is to produce software that is technically +sound but also a strong reflection of the business domain. + +DDD implementation in this project means that each "domain" (users, recipes, +etc) are defined in their own domain segment and should be used throughout the +project. + + +## Data Flow + +Data flows through a strict pipe line, as described bellow: + +```md +**HTTP > handler > service > repository** +``` + +The data is first send from the client via **HTTP**. The data is routed to the +proper **handler** which will take control of request and eventually send the +response back to the client. + +The handler will then call the proper **service** which will complete any business +logic required before sending the data to the respective **repository.** This +repository (which implemented the proper *domain* interface) will communicate with +the database and perform and database actions, finally returning the new data +back to the service. + +Finally, the service will clean up the data and ensure the data fits into the +proper *domain* object and is then bubbled back up to the handler which sends the +response to the client. + +## Directory Structure + +The directory structure of this project intends to achieve the most efficient +structure while still meeting the goals defined above. + + +#### `internal/app/handers` + +**Handlers** are responsible for receiving requests and sending back responses. +These strongly mimic the controller in the well-known **Model View Controller** +(MVC) architecture. Very little logic takes place here. + + +#### `internal/app/services` + +**Services** are responsible for the majority of the software's business logic. When +a request is received by the handler, the service is called and executes all logic +required to achieve the end response. However, the service has no communication +and therefore relies on the repository for the database. + +#### `internal/domain` + +The **domain** is the definition of the data models and interfaces that are used by +the application. The data model of each domain object is defined here as well as +the respective service and repository interfaces are defined here. No logic will +be placed here, simply just definitions and interfaces. + +#### `internal/infrastructure/database` + +The **infrastructure** module is responsible for housing communication to outside +sources, such as the database. The database section (defined here) contains all +**migrations**: SQL definitions of database changes. It also contains all of the +**repository** implementations as defined by the domain interfaces. + +#### `internal/templates` +**Templates** are HTML templates which are served to the user as the web UI. This +directory has a very module structure; being broken down into many categories: + +- **Layouts:** Page layouts and other large wrappers. +- **Components:** Small scale components for use in various other templates. +- **Partials:** Smallest size elements: common buttons, headers, text elements. +- **Pages:** Individual pages which are composed and served to the user. + + +#### `web/static` + +**Static** documents, elements, or other assets are stored in the web directory. +Items such as images, icons, style sheets, or even JS scripts can be stored here. +This directory will be hosted by the server as a static directory for access by +the UI. + + diff --git a/TechnicalSpecification.md b/doc/TechnicalSpecification.md similarity index 100% rename from TechnicalSpecification.md rename to doc/TechnicalSpecification.md diff --git a/examples/hello.templ b/examples/hello.templ new file mode 100644 index 0000000..bc86f8e --- /dev/null +++ b/examples/hello.templ @@ -0,0 +1,45 @@ +package main + +script log(s string) { + console.log(s) +} + +templ hello(name string) { +
Hello, { name }
+} + +templ headerTemplate(name string) { +
+

{ name }

+
+} + +templ button(text string) { + +} + +templ component(testID string) { +

text

+ @headerTemplate("Hayden") +} + +templ page2() { +

+ if 5 == 5 { + "five is five" + } +

+ @component("testid-123") +} + +templ spread(attrs templ.Attributes) { +

+ Text +

+} diff --git a/examples/hello_templ.go b/examples/hello_templ.go new file mode 100644 index 0000000..1a745c2 --- /dev/null +++ b/examples/hello_templ.go @@ -0,0 +1,296 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.865 +package main + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +func log(s string) templ.ComponentScript { + return templ.ComponentScript{ + Name: `__templ_log_bfa8`, + Function: `function __templ_log_bfa8(s){console.log(s) +}`, + Call: templ.SafeScript(`__templ_log_bfa8`, s), + CallInline: templ.SafeScriptInline(`__templ_log_bfa8`, s), + } +} + +func hello(name string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
Hello, ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `examples/hello.templ`, Line: 8, Col: 19} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func headerTemplate(name string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var3 := templ.GetChildren(ctx) + if templ_7745c5c3_Var3 == nil { + templ_7745c5c3_Var3 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `examples/hello.templ`, Line: 13, Col: 12} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func button(text string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var5 := templ.GetChildren(ctx) + if templ_7745c5c3_Var5 == nil { + templ_7745c5c3_Var5 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, log("clicked")) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func component(testID string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var8 := templ.GetChildren(ctx) + if templ_7745c5c3_Var8 == nil { + templ_7745c5c3_Var8 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "

text

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = headerTemplate("Hayden").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func page2() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var10 := templ.GetChildren(ctx) + if templ_7745c5c3_Var10 == nil { + templ_7745c5c3_Var10 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if 5 == 5 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\"five is five\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = component("testid-123").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func spread(attrs templ.Attributes) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var11 := templ.GetChildren(ctx) + if templ_7745c5c3_Var11 == nil { + templ_7745c5c3_Var11 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "

Text

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/examples/main.go b/examples/main.go new file mode 100644 index 0000000..3b9a6d1 --- /dev/null +++ b/examples/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "net/http" + + "github.com/a-h/templ" +) + +func main() { + component := page2() + btn := button("Click me") + + http.Handle("/", templ.Handler(component)) + http.Handle("/button", templ.Handler(btn)) + + fmt.Println("Listening on :3000") + http.ListenAndServe(":3000", nil) +} diff --git a/flake.nix b/flake.nix index 5da46ef..7adae7e 100644 --- a/flake.nix +++ b/flake.nix @@ -22,6 +22,8 @@ go-tools htmx-lsp2 templ + tailwindcss_4 + tailwindcss-language-server ]; # Define the shell that will be executed. diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..18764bc --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,5 @@ +module.exports = { + content: ["./**/*.html", "./**/*.templ", "./**/*.go",], + theme: { extend: {}, }, + plugins: [], +}