diff --git a/Dockerfile b/Dockerfile
index 3e3b3f1..cbf0426 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,57 +1,14 @@
-# Fetch stage
-FROM golang:latest AS fetch-stage
-
-COPY . /app
+FROM golang:1.25-alpine
WORKDIR /app
-RUN go mod tidy
+COPY go.mod go.sum ./
RUN go mod download
-# Generate stage
-FROM ghcr.io/a-h/templ:latest AS generate-stage
+COPY . .
-COPY --chown=65532:65532 . /app
+RUN go build -o server ./cmd/web/main.go
-WORKDIR /app
+EXPOSE 8080
-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:latest AS build-stage
-
-COPY --from=tailwind-build-stage /app /app
-
-WORKDIR /app
-
-RUN go mod tidy
-RUN go mod download
-
-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
-
-USER nonroot:nonroot
-
-ENTRYPOINT ["/entrypoint"]
+CMD ["./server"]
diff --git a/Dockerfile.old b/Dockerfile.old
new file mode 100644
index 0000000..3e3b3f1
--- /dev/null
+++ b/Dockerfile.old
@@ -0,0 +1,57 @@
+# Fetch stage
+FROM golang:latest AS fetch-stage
+
+COPY . /app
+
+WORKDIR /app
+
+RUN go mod tidy
+RUN go mod download
+
+# 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:latest AS build-stage
+
+COPY --from=tailwind-build-stage /app /app
+
+WORKDIR /app
+
+RUN go mod tidy
+RUN go mod download
+
+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
+
+USER nonroot:nonroot
+
+ENTRYPOINT ["/entrypoint"]
diff --git a/internal/app/server/auth_handler_v2.go b/internal/app/server/auth_handler_v2.go
index e31cfaa..5ab70f3 100644
--- a/internal/app/server/auth_handler_v2.go
+++ b/internal/app/server/auth_handler_v2.go
@@ -29,11 +29,13 @@ func (s *Server) GoogleCallbackHandlerV2(ctx *gin.Context) {
code string = ctx.Query("code")
)
+ domain := s.deps.EnvironmentConfig.FrontendDomain
+
if jwt, err := s.deps.AuthService.GoogleAuthSuccess(state, code); err != nil {
- url := fmt.Sprintf("http://localhost:5173/v2/web/login?error=%s", url.QueryEscape(err.Error()))
+ url := fmt.Sprintf("%s/v2/web/login?error=%s", domain, url.QueryEscape(err.Error()))
ctx.Redirect(http.StatusSeeOther, url)
} else {
- url := "http://localhost:5173/v2/web/home"
+ url := fmt.Sprintf("%s/v2/web/home", domain)
s.SetCookie(ctx, "jwt_token", jwt, time.Hour*24*7)
ctx.Redirect(http.StatusSeeOther, url)
}
diff --git a/internal/app/server/cookies.go b/internal/app/server/cookies.go
index 088b346..78ac8f6 100644
--- a/internal/app/server/cookies.go
+++ b/internal/app/server/cookies.go
@@ -1,6 +1,7 @@
package server
import (
+ "net/http"
"time"
"github.com/gin-gonic/gin"
@@ -20,7 +21,7 @@ func (s *Server) SetCookie(ctx *gin.Context, name, value string, duration time.D
path string = "/"
httpOnly bool = false // NOTE: Should use false so React can see it!
maxAge int
- secure bool = false
+ secure bool = true
domain string = ""
)
@@ -35,9 +36,12 @@ func (s *Server) SetCookie(ctx *gin.Context, name, value string, duration time.D
maxAge = int(time.Until(time.Now().Add(duration)).Seconds())
}
+ // TODO: This whole system is stupid now
if s.deps.EnvironmentConfig.Environment == "prod" {
secure = true
- domain = s.deps.EnvironmentConfig.Domain
+ // domain = "potion.gophernest"
+ // domain = s.deps.EnvironmentConfig.Domain
+ domain = ".gophernest.net"
} else if s.deps.EnvironmentConfig.Environment == "dev" {
secure = false
@@ -45,5 +49,6 @@ func (s *Server) SetCookie(ctx *gin.Context, name, value string, duration time.D
domain = "localhost"
}
+ ctx.SetSameSite(http.SameSiteNoneMode)
ctx.SetCookie(name, value, maxAge, path, domain, secure, httpOnly)
}
diff --git a/internal/app/server/server.go b/internal/app/server/server.go
index f2822af..a2a7b52 100644
--- a/internal/app/server/server.go
+++ b/internal/app/server/server.go
@@ -43,7 +43,7 @@ func Init(port int) *Server {
// Setup the CORS settings and active them
// server.config.AllowAllOrigins = true
- server.config.AllowOrigins = []string{"http://localhost:5173"}
+ server.config.AllowOrigins = []string{"http://localhost:5173", "https://potion.gophernest.net"}
server.config.AllowMethods = []string{"GET", "POST", "PUT", "DELETE"}
server.config.AllowHeaders = []string{"Origin", "Content-Type", "Authorization"}
server.config.AllowCredentials = true
diff --git a/internal/domain/server/server.go b/internal/domain/server/server.go
index 66093a0..7afc797 100644
--- a/internal/domain/server/server.go
+++ b/internal/domain/server/server.go
@@ -23,6 +23,7 @@ type EnvironmentConfig struct {
DatabaseUrl string
Environment string
Domain string
+ FrontendDomain string
}
// InjectedDependencies is a collection of dependencies that are injected into the application. They
@@ -81,16 +82,25 @@ func LoadEnvironment() (*EnvironmentConfig, error) {
}
var domain string
+ var frontendDomain string
if env == "dev" {
domain = os.Getenv("DOMAIN_DEV")
if domain == "" {
return nil, fmt.Errorf("DOMAIN_DEV environment variable is required when ENVIRONMENT is 'dev'.")
}
+ frontendDomain = os.Getenv("FRONTEND_DOMAIN_DEV")
+ if frontendDomain == "" {
+ return nil, fmt.Errorf("FRONTEND_DOMAIN_DEV environment variable is required when ENVIRONMENT is 'dev'.")
+ }
} else if env == "prod" {
domain = os.Getenv("DOMAIN_PROD")
if domain == "" {
return nil, fmt.Errorf("DOMAIN_PROD environment variable is required when ENVIRONMENT is 'prod'.")
}
+ frontendDomain = os.Getenv("FRONTEND_DOMAIN_PROD")
+ if frontendDomain == "" {
+ return nil, fmt.Errorf("FRONTEND_DOMAIN_PROD environment variable is required when ENVIRONMENT is 'dev'.")
+ }
} else {
return nil, fmt.Errorf("ENVIRONMENT environment variable is required and must be 'dev' or 'prod'.")
}
@@ -117,8 +127,11 @@ func LoadEnvironment() (*EnvironmentConfig, error) {
DatabaseUrl: dbUrl,
Environment: env,
Domain: domain,
+ FrontendDomain: frontendDomain,
}
+ fmt.Printf("Environment Config: %+v\n", cfg)
+
return cfg, nil
}
diff --git a/web/.gitignore b/web/.gitignore
index a547bf3..50c8dda 100644
--- a/web/.gitignore
+++ b/web/.gitignore
@@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
+
+.env
diff --git a/web/src/components/forms/IngredientItem.tsx b/web/src/components/forms/IngredientItem.tsx
index 768f955..a6174b7 100644
--- a/web/src/components/forms/IngredientItem.tsx
+++ b/web/src/components/forms/IngredientItem.tsx
@@ -75,8 +75,11 @@ export default function IngredientItem({ classes, ingredient, onChange, removeIn
controls.start(e)}
- className="p-1 md:p-0 cursor-pointer"
+ onPointerDown={(e) => {
+ e.preventDefault();
+ controls.start(e);
+ }}
+ className="p-1 md:p-0 cursor-pointer touch-none"
>
diff --git a/web/src/components/forms/IngredientSection.tsx b/web/src/components/forms/IngredientSection.tsx
index ebd52a1..1748a1a 100644
--- a/web/src/components/forms/IngredientSection.tsx
+++ b/web/src/components/forms/IngredientSection.tsx
@@ -41,7 +41,13 @@ export default function IngredientSection({ section, onChange, removeIngredientS
>
- controls.start(e)}>
+
{
+ e.preventDefault();
+ controls.start(e);
+ }}
+ >
diff --git a/web/src/components/forms/InstructionElement.tsx b/web/src/components/forms/InstructionElement.tsx
index d7bddb1..79c8c0b 100644
--- a/web/src/components/forms/InstructionElement.tsx
+++ b/web/src/components/forms/InstructionElement.tsx
@@ -52,7 +52,13 @@ export default function InstructionElement({ instruction, index, allowDelete, on
-
controls.start(e)}>
+
{
+ e.preventDefault();
+ controls.start(e);
+ }}
+ >
diff --git a/web/src/components/inputs/RecipeCreateFormInput.tsx b/web/src/components/inputs/RecipeCreateFormInput.tsx
index c98e553..b8a69cd 100644
--- a/web/src/components/inputs/RecipeCreateFormInput.tsx
+++ b/web/src/components/inputs/RecipeCreateFormInput.tsx
@@ -1,4 +1,4 @@
-import { useEffect, type ChangeEvent, type Dispatch, type InputHTMLAttributes, type SetStateAction } from "react";
+import { type ChangeEvent, type Dispatch, type InputHTMLAttributes, type SetStateAction } from "react";
import type { CreateRecipeFormDirtyEntries } from "../../pages/Create";
interface RecipeCreateFormInputProps
diff --git a/web/src/services/AuthService.ts b/web/src/services/AuthService.ts
index f8b3cee..1adb4b3 100644
--- a/web/src/services/AuthService.ts
+++ b/web/src/services/AuthService.ts
@@ -1,10 +1,13 @@
import axios from "axios";
import type { GetGoogleAuthUrlResponse, LogoutResponse } from "../types/api/auth";
import type { ApiError } from "../types/api/error";
+import { GetBackendUrl } from "./util";
+
+const BACKEND_URL = GetBackendUrl();
export async function GetGoogleAuthUrl(): Promise
{
- const response = await axios.get("http://localhost:3000/v2/api/auth/login");
+ const response = await axios.get(`${BACKEND_URL}/v2/api/auth/login`);
if (response.status !== 200) {
const err: ApiError = {
@@ -18,7 +21,7 @@ export async function GetGoogleAuthUrl(): Promise {
}
export async function Logout(): Promise {
- const response = await axios.get("http://localhost:3000/v2/api/auth/logout");
+ const response = await axios.get(`${BACKEND_URL}/v2/api/auth/logout`);
// This should never happen
if (response.status !== 204)
diff --git a/web/src/services/EngagementService.ts b/web/src/services/EngagementService.ts
index 382b4db..6677c20 100644
--- a/web/src/services/EngagementService.ts
+++ b/web/src/services/EngagementService.ts
@@ -2,9 +2,12 @@ import axios from "axios";
import type { ApiError } from "../types/api/error";
import type { Engagement } from "../types/engagement";
import type { EngagementFavoriteRecipeResponse, EngagementMakeRecipeResponse, EngagementShareRecipeResponse, EngagementViewRecipeResponse } from "../types/api/engagement";
+import { GetBackendUrl } from "./util";
+
+const BACKEND_URL = GetBackendUrl();
export async function EngagementViewRecipe(recipeId: number): Promise {
- const response = await axios.post(`http://localhost:3000/v2/api/engagement/view/${recipeId}`);
+ const response = await axios.post(`${BACKEND_URL}/v2/api/engagement/view/${recipeId}`);
if (response.status !== 200 || response.data.engagement === undefined) {
const err: ApiError = {
@@ -18,7 +21,7 @@ export async function EngagementViewRecipe(recipeId: number): Promise {
- const response = await axios.post(`http://localhost:3000/v2/api/engagement/share/${recipeId}`);
+ const response = await axios.post(`${BACKEND_URL}/v2/api/engagement/share/${recipeId}`);
if (response.status !== 200 || response.data.engagement === undefined) {
const err: ApiError = {
@@ -32,7 +35,7 @@ export async function EngagementShareRecipe(recipeId: number): Promise {
- const response = await axios.post(`http://localhost:3000/v2/api/engagement/favorite/${recipeId}`);
+ const response = await axios.post(`${BACKEND_URL}/v2/api/engagement/favorite/${recipeId}`);
if (response.status !== 200 || response.data.engagement === undefined) {
const err: ApiError = {
@@ -46,7 +49,7 @@ export async function EngagementFavoriteRecipe(recipeId: number): Promise {
- const response = await axios.post(`http://localhost:3000/v2/api/engagement/make/${recipeId}`);
+ const response = await axios.post(`${BACKEND_URL}/v2/api/engagement/make/${recipeId}`);
if (response.status !== 200 || response.data.engagement === undefined) {
const err: ApiError = {
diff --git a/web/src/services/RecipeService.ts b/web/src/services/RecipeService.ts
index 0ac614d..fd2a7c2 100644
--- a/web/src/services/RecipeService.ts
+++ b/web/src/services/RecipeService.ts
@@ -3,10 +3,13 @@ import type { CreateRecipeRequest, CreateRecipeResponse, GetRecipeOfTheWeekRespo
import type { Recipe } from "../types/recipe";
import type { ApiError } from "../types/api/error";
import type { SearchFilters } from "../types/search";
+import { GetBackendUrl } from "./util";
+
+const BACKEND_URL = GetBackendUrl();
export async function GetRecipeOfTheWeek(): Promise {
- const response = await axios.get("http://localhost:3000/v2/api/recipe/of-the-week");
+ const response = await axios.get(`${BACKEND_URL}/v2/api/recipe/of-the-week`);
if (response.status !== 200 || response.data.recipe === undefined) {
const err: ApiError = {
@@ -20,7 +23,7 @@ export async function GetRecipeOfTheWeek(): Promise {
}
export async function GetRecipe(id: number): Promise {
- const response = await axios.get(`http://localhost:3000/v2/api/recipe/${id}`);
+ const response = await axios.get(`${BACKEND_URL}/v2/api/recipe/${id}`);
if (response.status !== 200 || response.data.recipe === undefined) {
const err: ApiError = {
@@ -34,7 +37,7 @@ export async function GetRecipe(id: number): Promise {
}
export async function SearchRecipes(filters: SearchFilters): Promise {
- const response = await axios.post("http://localhost:3000/v2/api/recipe/search", filters);
+ const response = await axios.post(`${BACKEND_URL}/v2/api/recipe/search`, filters);
if (response.status !== 200 || response.data.recipes === undefined) {
const err: ApiError = {
@@ -48,7 +51,7 @@ export async function SearchRecipes(filters: SearchFilters): Promise {
- const response = await axios.post("http://localhost:3000/v2/api/recipe", data);
+ const response = await axios.post(`${BACKEND_URL}/v2/api/recipe`, data);
if (response.status !== 200 || response.data.recipe === undefined) {
const err: ApiError = {
diff --git a/web/src/services/UserService.ts b/web/src/services/UserService.ts
index cd785a3..b9f8b63 100644
--- a/web/src/services/UserService.ts
+++ b/web/src/services/UserService.ts
@@ -4,9 +4,12 @@ import type { User } from "../types/user";
import type { GetAuthenticateUserEngagementResponse, GetAuthenticateUserFavoritesResponse, GetAuthenticateUserMadeRecipesResponse, GetAuthenticateUserRecipesResponse, GetAuthenticateUserResponse, GetAuthenticateUserViewedRecipesResponse, GetUserResponse } from "../types/api/user";
import type { Recipe } from "../types/recipe";
import type { Engagement } from "../types/engagement";
+import { GetBackendUrl } from "./util";
+
+const BACKEND_URL = GetBackendUrl();
export async function GetUser(id: number): Promise {
- const response = await axios.get(`http://localhost:3000/v2/api/user/${id}`);
+ const response = await axios.get(`${BACKEND_URL}/v2/api/user/${id}`);
if (response.data.status !== 200 || response.data.user === undefined) {
const err: ApiError = {
@@ -20,7 +23,7 @@ export async function GetUser(id: number): Promise {
}
export async function GetAuthenticatedUser(): Promise {
- const response = await axios.get("http://localhost:3000/v2/api/user");
+ const response = await axios.get(`${BACKEND_URL}/v2/api/user`);
if (response.data.status !== 200 || response.data.user === undefined) {
const err: ApiError = {
@@ -34,7 +37,7 @@ export async function GetAuthenticatedUser(): Promise {
}
export async function GetAuthenticatedUserRecipes(): Promise {
- const response = await axios.get("http://localhost:3000/v2/api/user/recipes");
+ const response = await axios.get(`${BACKEND_URL}/v2/api/user/recipes`);
if (response.data.status !== 200 || response.data.recipes === undefined) {
const err: ApiError = {
@@ -48,7 +51,7 @@ export async function GetAuthenticatedUserRecipes(): Promise {
- const response = await axios.get("http://localhost:3000/v2/api/user/favorites");
+ const response = await axios.get(`${BACKEND_URL}/v2/api/user/favorites`);
if (response.data.status !== 200 || response.data.favorites === undefined) {
const err: ApiError = {
@@ -62,7 +65,7 @@ export async function GetAuthenticatedUserFavorites(): Promise {
- const response = await axios.get("http://localhost:3000/v2/api/user/engagement");
+ const response = await axios.get(`${BACKEND_URL}/v2/api/user/engagement`);
if (response.data.status !== 200 || response.data.engagement === undefined) {
const err: ApiError = {
@@ -76,7 +79,7 @@ export async function GetAuthenticatedUserEngagement(): Promise {
- const response = await axios.get("http://localhost:3000/v2/api/user/recipes/made");
+ const response = await axios.get(`${BACKEND_URL}/v2/api/user/recipes/made`);
if (response.data.status !== 200 || response.data.recipes === undefined) {
const err: ApiError = {
@@ -90,7 +93,7 @@ export async function GetAuthenticatedUserMadeRecipes(): Promise {
- const response = await axios.get("http://localhost:3000/v2/api/user/recipes/viewed");
+ const response = await axios.get(`${BACKEND_URL}/v2/api/user/recipes/viewed`);
if (response.data.status !== 200 || response.data.recipes === undefined) {
const err: ApiError = {
diff --git a/web/src/services/util.ts b/web/src/services/util.ts
new file mode 100644
index 0000000..e749dce
--- /dev/null
+++ b/web/src/services/util.ts
@@ -0,0 +1,15 @@
+const ENV = import.meta.env;
+
+export function GetBackendUrl(): string {
+ const env = ENV.VITE_ENVIRONMENT as string;
+ if (!env) return "";
+
+ switch (env.toLowerCase()) {
+ case "dev":
+ return ENV.VITE_DOMAIN_DEV as string;
+ case "prod":
+ return ENV.VITE_DOMAIN_PROD as string;
+ default:
+ return ""
+ }
+}