diff --git a/.env-example b/.env-example index 332f0d4..2b4b511 100644 --- a/.env-example +++ b/.env-example @@ -1,4 +1,9 @@ # API key for chatgpt CHATGPT_KEY=api key for chatgpt +# Chat gpt model +CHATGPT_MODEL=gpt-4o # This is secret key for jwt signature JWT_SECRET=just a random string here +# Set to false to disable registration +ALLOW_REGISTER=true + diff --git a/Dockerfile.backend b/Dockerfile.backend index 7b74d34..86162f0 100644 --- a/Dockerfile.backend +++ b/Dockerfile.backend @@ -7,13 +7,21 @@ WORKDIR /app COPY go.mod go.sum ./ RUN go mod download -# ---- Production mode ---- -FROM base AS prod +# ---- Build mode ---- +FROM base AS build +WORKDIR /app # Copy code, and compile COPY . . RUN go build -o server main.go +# ---- Production mode ---- +FROM alpine@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715 AS prod +WORKDIR /app + +# Copy built binary +COPY --from=build /app/server . + # Expose 8080 port EXPOSE 8080 diff --git a/Dockerfile.frontend b/Dockerfile.frontend index 97292e3..d319957 100644 --- a/Dockerfile.frontend +++ b/Dockerfile.frontend @@ -3,12 +3,29 @@ FROM oven/bun:1.2.18-alpine@sha256:a7df687a2f684ee2f7404e2592039e192d75d26a04f84 WORKDIR /app # Install dependencies -COPY package.json bun.lock ./ +COPY frontend/package.json frontend/bun.lock ./ RUN bun install --frozen-lockfile +# ------ Build stage ------ +FROM deps AS build +WORKDIR /app + +# Copy code, and compile +COPY frontend/ . +RUN bun run build + # ------ Development stage ------ FROM deps AS dev # Run CMD ["bun", "run", "dev"] +# ------ Production stage ------ +FROM caddy:2.10.0-alpine@sha256:e2e3a089760c453bc51c4e718342bd7032d6714f15b437db7121bfc2de2654a6 AS prod +WORKDIR /app + +# Copy built code +COPY --from=build /app/dist . + +# Copy configuration for caddy +COPY caddy/prod /etc/caddy/Caddyfile diff --git a/backend/config/config.go b/backend/config/config.go index 91e7963..3864ea8 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -1,6 +1,9 @@ package config -import "os" +import ( + "log" + "os" +) func defaultValue(val string, def string) string { if val == "" { @@ -20,4 +23,8 @@ func LoadEnv() { Env["JWT_SECRET"] = defaultValue(os.Getenv("JWT_SECRET"), "just a random string here") Env["Environment"] = defaultValue(os.Getenv("Environment"), "dev") Env["CHATGPT_KEY"] = defaultValue(os.Getenv("CHATGPT_KEY"), "") + Env["REGISTER"] = defaultValue(os.Getenv("ALLOW_REGISTER"), "true") + log.Printf("[INFO] Register set to '%s'\n", Env["REGISTER"]) + Env["CHATGPT_MODEL"] = defaultValue(os.Getenv("CHATGPT_MODEL"), "gpt-4o") + log.Printf("[INFO] ChatGPT model set to '%s'\n", Env["CHATGPT_MODEL"]) } diff --git a/backend/controllers/user/user.go b/backend/controllers/user/user.go index d1b8e15..bec5300 100644 --- a/backend/controllers/user/user.go +++ b/backend/controllers/user/user.go @@ -25,6 +25,12 @@ type RegisterForm struct { } func Register(c *gin.Context) { + // Check for register environment + if config.Env["REGISTER"] != "true" { + res.Error(c, "Registration is disabled", http.StatusForbidden) + return + } + // Receive data from frontend, check if data is okay, hash password, call model var data RegisterForm if err := c.ShouldBindJSON(&data); err != nil { diff --git a/backend/utils/chatgpt/chatgpt.go b/backend/utils/chatgpt/chatgpt.go index 3d102d3..431e636 100644 --- a/backend/utils/chatgpt/chatgpt.go +++ b/backend/utils/chatgpt/chatgpt.go @@ -37,7 +37,7 @@ func GenerateCoverLetter(templateHTML string, jobHTML string) (GeneratedCover, e } payload := ChatRequest{ - Model: "gpt-4o", // o4-mini + Model: config.Env["CHATGPT_MODEL"], ResponseFormat: &ResponseFormat{Type: "json_object"}, Messages: []ChatMessage{ { diff --git a/caddy/Caddyfile b/caddy/dev similarity index 100% rename from caddy/Caddyfile rename to caddy/dev diff --git a/caddy/prod b/caddy/prod new file mode 100644 index 0000000..76c0fdb --- /dev/null +++ b/caddy/prod @@ -0,0 +1,20 @@ +{ + admin off + auto_https off +} + +:8080 { + encode + + # Proxy to backend + handle_path /api* { + reverse_proxy backend:8080 + } + + # Server static files + handle_path /* { + root * /app + try_files {path} /index.html + file_server + } +} diff --git a/development.yml b/development.yml index 69a87f5..722bb7b 100644 --- a/development.yml +++ b/development.yml @@ -25,8 +25,8 @@ services: condition: service_healthy frontend: build: - context: ./frontend - dockerfile: ../Dockerfile.frontend + context: . + dockerfile: Dockerfile.frontend target: dev # Development stage restart: unless-stopped container_name: cover-letter-frontend @@ -44,7 +44,7 @@ services: networks: - cover-letter-network volumes: - - ./caddy/Caddyfile:/etc/caddy/Caddyfile + - ./caddy/dev:/etc/caddy/Caddyfile ports: - 8000:8080 depends_on: diff --git a/frontend/src/layouts/Authorised.tsx b/frontend/src/layouts/Authorised.tsx index a956b79..607ee58 100644 --- a/frontend/src/layouts/Authorised.tsx +++ b/frontend/src/layouts/Authorised.tsx @@ -6,10 +6,9 @@ import type { TokenUserInfo } from "@/types/api"; interface Props { children: React.ReactNode; - className?: string; } -export default function Authorised({ children, className = "" }: Props) { +export default function Authorised({ children }: Props) { // Check authentication useQuery({ queryKey: ["user_info"], diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 1500f3c..7c23da2 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,4 +1,3 @@ -import { StrictMode } from "react"; import ReactDOM from "react-dom/client"; import { RouterProvider, createRouter } from "@tanstack/react-router"; diff --git a/frontend/src/routes/register.tsx b/frontend/src/routes/register.tsx index 20c8d69..d476c66 100644 --- a/frontend/src/routes/register.tsx +++ b/frontend/src/routes/register.tsx @@ -3,7 +3,7 @@ import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle } import { useAppForm } from "@/hooks/formHook"; import Guest from "@/layouts/Guest"; import requests from "@/lib/requests"; -import { createFileRoute, Link, redirect, useNavigate } from "@tanstack/react-router"; +import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"; import { useState } from "react"; import toast from "react-hot-toast"; import * as z from "zod/v4"; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 2951b96..8d614c6 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { defineConfig } from "vite"; import viteReact from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; diff --git a/production.yml b/production.yml new file mode 100644 index 0000000..04627e0 --- /dev/null +++ b/production.yml @@ -0,0 +1,60 @@ +services: + backend: + build: + context: ./backend + dockerfile: ../Dockerfile.backend + target: prod # Development mode with hot reload + restart: unless-stopped + container_name: cover-letter-backend + + env_file: + - .env + environment: + # - GIN_MODE=release # For production + - GIN_MODE=release + # - POSTGRES_DB=postgresql://username:password@host:port/database_name + - POSTGRES_DB=postgresql://postgres:postgres@db:5432/cover-letter + # - Environment=prod # For production + - Environment=prod + networks: + - cover-letter-network + depends_on: + db: # Wait for database to be ready (Pass healthcheck) + condition: service_healthy + frontend: + build: + context: . + dockerfile: Dockerfile.frontend + target: prod # Development stage + restart: unless-stopped + container_name: cover-letter-frontend + + ports: + - 8000:8080 + networks: + - cover-letter-network + db: + image: postgres:13.21-alpine3.22 + restart: unless-stopped + container_name: cover-letter-db + + networks: + - cover-letter-network + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + # - POSTGRES_HOST_AUTH_METHOD=trust # No password needed + - POSTGRES_DB=cover-letter + ports: + - 5432:5432 + volumes: + - ./data/db:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 2s + timeout: 2s + retries: 5 + +networks: + cover-letter-network: + driver: bridge diff --git a/scripts/var.sh b/scripts/var.sh index f785af2..0a72f8a 100755 --- a/scripts/var.sh +++ b/scripts/var.sh @@ -1,2 +1,3 @@ #!/bin/bash export file="development.yml" +# export file="production.yml"