From eec8ed00c3320d0f307ba258b5ae3d171de6b2f1 Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Mon, 21 Jul 2025 20:14:03 -0700 Subject: [PATCH] (FEAT): Application has been dockerized. The application can now be built, tailwind is built, AND templ components are generated! Which means, we can no longer push the tailwind generated CSS file OR the templ generated go files! Plus, this is the first step towards deployment and CI/CD! --- Dockerfile | 53 ++++- internal/app/server/server.go | 6 +- internal/templates/components/navbar.templ | 147 +++++++------ internal/templates/components/navbar_templ.go | 6 +- internal/templates/pages/home.templ | 198 +++++++++--------- web/static/css/tailwind.css | 13 +- 6 files changed, 239 insertions(+), 184 deletions(-) diff --git a/Dockerfile b/Dockerfile index aba010d..a018fdb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,54 @@ -# TEMPORARY SOLUTION -# Need a real way to build tailwind and build the templ files -FROM golang:1.24 +# Fetch stage +FROM golang:latest AS fetch-stage + +COPY go.mod go.sum /app WORKDIR /app -COPY . . - RUN go mod download -RUN CGO_ENABLED=0 GOOS=linux go build -o /app/app /app/cmd/web/main.go + +# Generate stage +FROM ghcr.io/a-h/templ:latest AS generate-stage + +COPY --chown=65532:65532 . /app + +WORKDIR /app + +RUN ["templ", "generate"] + +# Generate stage two + +FROM node:lts-alpine AS tailwind-build-stage + +COPY --from=generate-stage /app /app + +WORKDIR /app + +RUN npm install tailwindcss @tailwindcss/cli && npx @tailwindcss/cli -i ./web/static/css/main.css -o ./web/static/css/tailwind.css --minify -c ./tailwind.config.js + + +# Build stage +FROM golang:1.24 AS build-stage + +COPY --from=tailwind-build-stage /app /app + +WORKDIR /app + +RUN CGO_ENABLED=0 GOOS=linux go build -o /entrypoint /app/cmd/web/main.go + +# Deploy. +FROM gcr.io/distroless/static-debian11 AS release-stage + +WORKDIR / + +COPY --from=build-stage /entrypoint /entrypoint + +COPY --from=build-stage /app/web/static /web/static + EXPOSE 3000 -CMD [ "/app/app" ] +USER nonroot:nonroot + +ENTRYPOINT ["/entrypoint"] diff --git a/internal/app/server/server.go b/internal/app/server/server.go index 0084d93..9863e17 100644 --- a/internal/app/server/server.go +++ b/internal/app/server/server.go @@ -56,7 +56,7 @@ func Init(port int) *Server { func (s *Server) ConfigureAuth() *Server { err := godotenv.Load(".env") if err != nil { - panic("Could not load env file") + fmt.Printf("No .env file found or error loading .env: %v. Relying on system environment variables.", err) } redirect_domain := os.Getenv("DOMAIN") @@ -80,7 +80,7 @@ func (s *Server) ConfigureAuth() *Server { func (s *Server) ConnectDatabase() *Server { err := godotenv.Load(".env") if err != nil { - panic("Could not load env file") + fmt.Printf("No .env file found or error loading .env: %v. Relying on system environment variables.", err) } var connUrl string = os.Getenv("DATABASE_URL") @@ -107,7 +107,7 @@ func (s *Server) Start() { func (s *Server) Setup() *Server { err := godotenv.Load(".env") if err != nil { - panic("Could not load env file") + fmt.Printf("No .env file found or error loading .env: %v. Relying on system environment variables.", err) } jwtSecret := []byte(os.Getenv("JWT_SECRET")) diff --git a/internal/templates/components/navbar.templ b/internal/templates/components/navbar.templ index 2132139..63daaa7 100644 --- a/internal/templates/components/navbar.templ +++ b/internal/templates/components/navbar.templ @@ -4,75 +4,89 @@ import "strings" import "github.com/haydenhargreaves/Potion/internal/domain/server" templ navLink(current, name, url string) { - - { name } - + + { name } + } templ dropdownLink(name, url string) { - - { name } - + + { name } + } templ listIcon(current, name, url string) { - - - - - - + + + + + } templ Navbar(current string) { - + } templ hamburgerMenu() { - - + } - diff --git a/internal/templates/components/navbar_templ.go b/internal/templates/components/navbar_templ.go index 84bc6ac..0ae98db 100644 --- a/internal/templates/components/navbar_templ.go +++ b/internal/templates/components/navbar_templ.go @@ -63,7 +63,7 @@ func navLink(current, name, url string) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/components/navbar.templ`, Line: 10, Col: 8} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/components/navbar.templ`, Line: 15, Col: 8} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -114,7 +114,7 @@ func dropdownLink(name, url string) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/components/navbar.templ`, Line: 16, Col: 8} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/components/navbar.templ`, Line: 21, Col: 8} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -211,7 +211,7 @@ func Navbar(current string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\">

Potion

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\">

Potion

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/internal/templates/pages/home.templ b/internal/templates/pages/home.templ index afc26a8..657caf9 100644 --- a/internal/templates/pages/home.templ +++ b/internal/templates/pages/home.templ @@ -5,118 +5,124 @@ import "github.com/haydenhargreaves/Potion/internal/domain/server" import domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe" templ introSection() { -
-
- -

- Discover Your Next Favorite Meal -

-
-

- Welcome to your ultimate recipe hub! Whether you're a seasoned chef or just starting your culinary adventure, - we're here to inspire. Explore thousands of delicious recipes, from quick weeknight dinners to gourmet delights, - all at your fingertips. Find exactly what you're craving with our powerful search and intuitive filters, or - browse our trending dishes for fresh ideas. -

-
+
+
+ +

+ Discover Your Next Favorite Meal +

+
+

+ Welcome to your ultimate recipe hub! Whether you're a seasoned chef or just starting your culinary adventure, + we're here to inspire. Explore thousands of delicious recipes, from quick weeknight dinners to gourmet delights, + all at your fingertips. Find exactly what you're craving with our powerful search and intuitive filters, or + browse our trending dishes for fresh ideas. +

+
} templ searchSection() { -
- @components.BannerText("Craving Something Specific?") -
- @components.SearchBar(domainRecipe.SearchFilters{}, true, false, false) -
- -
+
+ @components.BannerText("Craving Something Specific?") +
+ @components.SearchBar(domainRecipe.SearchFilters{}, true, false, false) +
+ +
} templ highlightSection(liked bool) { -
- @components.BannerText("Recipe of the Week!") -

- Our 'Recipe of the Week' is the cream of the crop! We handpick it by looking at what recipes - our community loves most. This isn't just about how many people view a recipe; it's also about - how many times it's been made, liked, reviewed, and its average rating, all combined to find - the true fan favorite of the week. It's our way of highlighting the best recipes that truly - resonate with our users! -

-
- @components.RecipeCardLarge(false) -
-
+
+ @components.BannerText("Recipe of the Week!") +

+ Our 'Recipe of the Week' is the cream of the crop! We handpick it by looking at what recipes + our community loves most. This isn't just about how many people view a recipe; it's also about + how many times it's been made, liked, reviewed, and its average rating, all combined to find + the true fan favorite of the week. It's our way of highlighting the best recipes that truly + resonate with our users! +

+
+ @components.RecipeCardLarge(false) +
+
} templ listsSection(loggedIn bool, viewed, made []domainRecipe.Recipe) { -
- @components.BannerText("Take Another Look.") -
-

Recently viewed

- if loggedIn { -
- for _, recipe := range viewed { - @components.RecipeCardSmall(recipe) - } - @components.ContentCardSmall("View full history...", "/v1/web/history") -
- } else { - - } -

Make again

- if loggedIn { -
- for _, recipe := range made { - @components.RecipeCardSmall(recipe) - } - @components.ContentCardSmall("View full history...", "/v1/web/history") -
- } else { - - } -
-
+
+ @components.BannerText("Take Another Look.") +
+

Recently viewed

+ if loggedIn { +
+ for _, recipe := range viewed { + @components.RecipeCardSmall(recipe) + } + @components.ContentCardSmall("View full history...", "/v1/web/history") +
+ } else { + + } +

Make again

+ if loggedIn { +
+ for _, recipe := range made { + @components.RecipeCardSmall(recipe) + } + @components.ContentCardSmall("View full history...", "/v1/web/history") +
+ } else { + + } +
+
} templ ctaSection() { -
-

- Unleash Your Inner Chef! -

-

- Have a unique recipe idea? Want to share your culinary masterpiece with the world? - It's time to bring your creations to life! -

- +

+ Unleash Your Inner Chef! +

+

+ Have a unique recipe idea? Want to share your culinary masterpiece with the world? + It's time to bring your creations to life! +

+
- Create Your Recipe! - -
+ text-lg md:text-2xl font-bold uppercase tracking-wide" + > + Create Your Recipe! + + } templ HomePage(loggedIn bool, viewed, made []domainRecipe.Recipe) { -@components.Navbar("home") -
-
- @introSection() - @searchSection() - @highlightSection(false) - @listsSection(loggedIn, viewed, made) - @ctaSection() -
-
+ @components.Navbar("home") +
+
+ @introSection() + @searchSection() + @highlightSection(false) + @listsSection(loggedIn, viewed, made) + @ctaSection() +
+
} diff --git a/web/static/css/tailwind.css b/web/static/css/tailwind.css index 035fbd3..a1d4d52 100644 --- a/web/static/css/tailwind.css +++ b/web/static/css/tailwind.css @@ -9,6 +9,7 @@ monospace; --color-red-100: oklch(93.6% 0.032 17.717); --color-red-500: oklch(63.7% 0.237 25.331); + --color-red-800: oklch(44.4% 0.177 26.899); --color-green-500: oklch(72.3% 0.219 149.579); --color-blue-50: oklch(97% 0.014 254.604); --color-blue-100: oklch(93.2% 0.032 255.585); @@ -259,18 +260,12 @@ .z-20 { z-index: 20; } - .m-4 { - margin: calc(var(--spacing) * 4); - } .mx-2 { margin-inline: calc(var(--spacing) * 2); } .mx-4 { margin-inline: calc(var(--spacing) * 4); } - .mx-8 { - margin-inline: calc(var(--spacing) * 8); - } .mx-auto { margin-inline: auto; } @@ -662,9 +657,6 @@ .bg-red-100 { background-color: var(--color-red-100); } - .bg-red-500 { - background-color: var(--color-red-500); - } .bg-white { background-color: var(--color-white); } @@ -881,6 +873,9 @@ .text-red-500 { color: var(--color-red-500); } + .text-red-800 { + color: var(--color-red-800); + } .text-white { color: var(--color-white); }