From d1f37d7b0e374a6352ef315853ba8996829cbc8f Mon Sep 17 00:00:00 2001 From: Emanuel Turis Date: Fri, 20 Oct 2023 07:46:32 +0300 Subject: [PATCH] Changes --- .gitignore | 1 + go.mod | 16 ++- go.sum | 68 +++++++++++ handler.go | 72 ++++++++++++ main.go | 12 +- sqlite3.db | Bin 8192 -> 8192 bytes static/index.css | 265 ++++++++++++++++++++++++++++++++++++++++++- task.go | 20 +++- templates/count.html | 15 +++ templates/form.html | 98 ++++++++++++++++ templates/index.html | 16 ++- tmpl.go | 35 ++++++ 12 files changed, 607 insertions(+), 11 deletions(-) create mode 100644 handler.go create mode 100644 templates/count.html create mode 100644 templates/form.html create mode 100644 tmpl.go diff --git a/.gitignore b/.gitignore index 07e6e47..c58b395 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /node_modules +/tmp diff --git a/go.mod b/go.mod index 73a5376..93ac7e8 100644 --- a/go.mod +++ b/go.mod @@ -4,4 +4,18 @@ go 1.21.1 require github.com/go-chi/chi/v5 v5.0.10 -require github.com/mattn/go-sqlite3 v1.14.17 // indirect +require github.com/mattn/go-sqlite3 v1.14.17 + +require ( + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/huandu/xstrings v1.4.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/spf13/cast v1.5.1 // indirect + golang.org/x/crypto v0.14.0 // indirect +) diff --git a/go.sum b/go.sum index c744973..cbc041f 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,72 @@ +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handler.go b/handler.go new file mode 100644 index 0000000..211cfc3 --- /dev/null +++ b/handler.go @@ -0,0 +1,72 @@ +package main + +import ( + "log" + "net/http" + "strconv" + + "github.com/go-chi/chi/v5" +) + +func handleGetTasks(w http.ResponseWriter, _ *http.Request) { + items, err := fetchTasks() + if err != nil { + log.Printf("error fetching tasks %v", err) + return + } + count, err := fetchCount() + if err != nil { + log.Printf("error fetching count %v", err) + } + completedCount, err := fetchCompletedCount() + if err != nil { + log.Printf("error fetching completed count %v", err) + } + data := Tasks{ + Items: items, + Count: count, + CompletedCount: completedCount, + } + tmpl.ExecuteTemplate(w, "Base", data) +} + +func handleCreateTask(w http.ResponseWriter, r *http.Request) { + title := r.FormValue("title") + if title == "" { + tmpl.ExecuteTemplate(w, "Form", nil) + return + } + item, err := insertTask(title) + if err != nil { + log.Printf("error inserting task %v", err) + return + } + count, err := fetchCount() + if err != nil { + log.Printf("error fetching count %v", err) + return + } + w.WriteHeader(http.StatusCreated) + tmpl.ExecuteTemplate(w, "Form", nil) + tmpl.ExecuteTemplate(w, "Item", map[string]any{"Item": item, "SwapOOB": true}) + tmpl.ExecuteTemplate(w, "TotalCount", map[string]any{"Count": count, "SwapOOB": true}) +} + +func handleToggleTask(w http.ResponseWriter, r *http.Request) { + id, err := strconv.Atoi(chi.URLParam(r, "id")) + if err != nil { + log.Printf("error parsing id into int %v", err) + return + } + _, err = toggleTask(id) + if err != nil { + log.Printf("error toggling task %v", err) + return + } + completedCount, err := fetchCompletedCount() + if err != nil { + log.Printf("error fetching completed count %v", err) + return + } + tmpl.ExecuteTemplate(w, "CompletedCount", map[string]any{"Count": completedCount, "SwapOOB": true}) +} diff --git a/main.go b/main.go index dc3765c..084f397 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,6 @@ package main import ( - "html/template" "log" "net/http" @@ -19,12 +18,15 @@ func main() { if err != nil { log.Panic(err) } + err = parseTemplates() + if err != nil { + log.Panic(err) + } r := chi.NewRouter() r.Use(middleware.Logger) r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("./static")))) - r.Get("/", func(w http.ResponseWriter, _ *http.Request) { - tmpl, _ := template.New("").ParseFiles("templates/index.html") - tmpl.ExecuteTemplate(w, "Base", nil) - }) + r.Get("/", handleGetTasks) + r.Post("/tasks", handleCreateTask) + r.Put("/tasks/{id}/toggle", handleToggleTask) http.ListenAndServe("localhost:3000", r) } diff --git a/sqlite3.db b/sqlite3.db index 4cb73643a20db4cd470754c30ad3bf74a27223f9..32c61a4d4d8b02b6e4f06728a8b62a254f6c0c52 100644 GIT binary patch delta 198 zcmZp0XmFSy%_uTa#+gxMW5N=C2`>I@2L8|d&-icfpW@%ezmk78e>Z :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-x-2\.5 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.625rem * var(--tw-space-x-reverse)); + margin-left: calc(0.625rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-y-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} + +.space-y-2\.5 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.625rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.625rem * var(--tw-space-y-reverse)); +} + +.rounded { + border-radius: 0.25rem; +} + +.rounded-lg { + border-radius: 0.5rem; +} + +.border { + border-width: 1px; +} + +.border-gray-300 { + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); +} + +.bg-blue-700 { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity)); +} + +.bg-gray-100 { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); +} + +.bg-gray-50 { + --tw-bg-opacity: 1; + background-color: rgb(249 250 251 / var(--tw-bg-opacity)); +} + +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.p-2 { + padding: 0.5rem; +} + +.p-2\.5 { + padding: 0.625rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.px-5 { + padding-left: 1.25rem; + padding-right: 1.25rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.py-2\.5 { + padding-top: 0.625rem; + padding-bottom: 0.625rem; +} + +.text-center { + text-align: center; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.font-medium { + font-weight: 500; +} + +.text-blue-500 { + --tw-text-opacity: 1; + color: rgb(59 130 246 / var(--tw-text-opacity)); +} + +.text-blue-700 { + --tw-text-opacity: 1; + color: rgb(29 78 216 / var(--tw-text-opacity)); +} + +.text-gray-500 { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); +} + +.text-gray-900 { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +.text-red-500 { + --tw-text-opacity: 1; + color: rgb(239 68 68 / var(--tw-text-opacity)); +} + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.transition { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.hover\:bg-blue-800:hover { + --tw-bg-opacity: 1; + background-color: rgb(30 64 175 / var(--tw-bg-opacity)); +} + +.hover\:text-blue-700:hover { + --tw-text-opacity: 1; + color: rgb(29 78 216 / var(--tw-text-opacity)); +} + +.hover\:text-gray-700:hover { + --tw-text-opacity: 1; + color: rgb(55 65 81 / var(--tw-text-opacity)); +} + +.hover\:text-red-700:hover { + --tw-text-opacity: 1; + color: rgb(185 28 28 / var(--tw-text-opacity)); +} + +.focus\:border-blue-500:focus { + --tw-border-opacity: 1; + border-color: rgb(59 130 246 / var(--tw-border-opacity)); +} + +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.focus\:ring-4:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.focus\:ring-blue-300:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(147 197 253 / var(--tw-ring-opacity)); +} + +.focus\:ring-blue-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity)); +} + +.focus\:ring-blue-600:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(37 99 235 / var(--tw-ring-opacity)); } diff --git a/task.go b/task.go index 4c5170f..74ef3c4 100644 --- a/task.go +++ b/task.go @@ -16,7 +16,7 @@ type Tasks struct { func fetchTasks() ([]Item, error) { var items []Item - rows, err := DB.Query("select id, title, completed from tasks order by postition;") + rows, err := DB.Query("select id, title, completed from tasks order by position;") if err != nil { return []Item{}, err } @@ -59,6 +59,15 @@ func fetchCount() (int, error) { return count, nil } +func fetchCompletedCount() (int, error) { + var count int + err := DB.QueryRow("select count(*) from tasks where completed = 1;").Scan(&count) + if err != nil { + return 0, err + } + return count, nil +} + func insertTask(title string) (Item, error) { count, err := fetchCount() if err != nil { @@ -126,3 +135,12 @@ func orderTasks(ctx context.Context, values []int) error { } return nil } + +func toggleTask(ID int) (Item, error) { + var item Item + err := DB.QueryRow("update tasks set completed = case when completed = 1 then 0 else 1 end where id = (?) returning id, title, completed", ID).Scan(&item.ID, &item.Title, &item.Completed) + if err != nil { + return Item{}, err + } + return item, nil +} diff --git a/templates/count.html b/templates/count.html new file mode 100644 index 0000000..dca6638 --- /dev/null +++ b/templates/count.html @@ -0,0 +1,15 @@ +{{ define "TotalCount" }} +

+ Total tasks: + {{ .Count }} +

+{{ end }} + +{{ define "CompletedCount" }} +

+ Completed tasks: {{ .Count }} +

+{{ end }} diff --git a/templates/form.html b/templates/form.html new file mode 100644 index 0000000..f860484 --- /dev/null +++ b/templates/form.html @@ -0,0 +1,98 @@ +{{ define "Form" }} +
+ + +
+{{ end }} + +{{ define "InnerItem" }} +
+
+ +

{{ .Item.Title }}

+
+ +
+ + + + + +
+
+{{ end }} + +{{ define "Item" }} + {{ if .SwapOOB }} +
+ {{ template "InnerItem" . }} +
+ {{ else }} + {{ template "InnerItem" . }} + {{ end }} +{{ end }} diff --git a/templates/index.html b/templates/index.html index 83c61af..f5caf65 100644 --- a/templates/index.html +++ b/templates/index.html @@ -9,7 +9,21 @@ -

Hello, world!

+
+
+ {{ template "TotalCount" dict "Count" .Count "SwapOOB" false }} + {{ template "CompletedCount" dict "Count" .CompletedCount "SwapOOB" false }} +
+ + {{ template "Form" }} + + +
+ {{ range .Items }} + {{ template "Item" dict "Item" . "SwapOOB" false }} + {{ end }} +
+
{{ end }} diff --git a/tmpl.go b/tmpl.go new file mode 100644 index 0000000..8e81034 --- /dev/null +++ b/tmpl.go @@ -0,0 +1,35 @@ +package main + +import ( + "html/template" + "io/fs" + "os" + "path/filepath" + "strings" + + "github.com/Masterminds/sprig/v3" +) + +var tmpl *template.Template + +func parseTemplates() error { + t := template.New("").Funcs(sprig.FuncMap()) + err := filepath.Walk("templates", func(path string, _ fs.FileInfo, err error) error { + if strings.Contains(path, ".html") { + tmplBytes, err := os.ReadFile(path) + if err != nil { + return err + } + _, err = t.New(path).Funcs(sprig.FuncMap()).Parse(string(tmplBytes)) + if err != nil { + return err + } + } + return err + }) + if err != nil { + return err + } + tmpl = t + return nil +}