Compare commits

...

3 Commits

Author SHA1 Message Date
ae
6da8421b2e
docs: future feat todo 2025-04-22 19:39:25 +03:00
ae
b19520eb6e
feat: fullstack dockerization 2025-04-22 19:38:31 +03:00
ae
094a16a768
fix: prevent loading container bg color flickering 2025-04-22 19:37:16 +03:00
14 changed files with 114 additions and 26 deletions

View File

@ -17,5 +17,4 @@ FRONTEND_URL=""
# Frontend
VITE_VIEW_COOKIE_PATH="/"
VITE_VIEW_COOKIE_DOMAIN=$DOMAIN
VITE_COKOIE_SAME_SITE="strict"

View File

@ -40,7 +40,7 @@ services:
LOG_LEVEL: debug
APP_ENV: development
DOMAIN: localhost
FRONTEND_URL: http://localhost
FRONTEND_URL: http://localhost:5173
networks:
notatest:

View File

@ -38,18 +38,25 @@ services:
LOG_LEVEL: ${LOG_LEVEL:-info}
APP_ENV: ${APP_ENV:-production}
DOMAIN: ${DOMAIN:-localhost}
FRONTEND_URL: ${FRONTEND_URL:-http://localhost:5173}
FRONTEND_URL: ${FRONTEND_URL:-http://localhost:3000}
notatest-web:
build: ${PWD}/web
image: notatest/web
container_name: notatest-web
build:
context: ${PWD}/web
dockerfile: ${PWD}/web/Dockerfile
image: notatest-web:latest
networks:
- notatest
# Add potential reverse proxy's local network here
ports:
- 3000:80 # Defined in nginx.conf
- 3000:80 # Container port defined in nginx.conf (bound to 3000 for dev.)
depends_on:
- notatest-server
environment:
VITE_VIEW_COOKIE_PATH: ${VITE_VIEW_COOKIE_PATH:-/}
VITE_VIEW_COOKIE_DOMAIN: ${DOMAIN:-localhost}
VITE_COOKIE_SAME_SITE: ${VITE_COOKIE_SAME_SITE:-strict}
networks:
notatest:

10
scripts/run_full_dev.sh Executable file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
DEV_COMPOSE_FILE="docker-compose.yml"
[ "$( docker container inspect -f '{{.State.Status}}' notatest-psql )" = "running" ] || docker compose up notatest-psql -d
# Shutdown, rebuild, & restart
docker compose stop notatest-server notatest-web && docker compose rm -f notatest-server notatest-web
docker compose -f $DEV_COMPOSE_FILE build
docker compose -f $DEV_COMPOSE_FILE up notatest-server notatest-web

View File

@ -37,6 +37,8 @@ type Config struct {
AppEnv string `env:"APP_ENV" envDefault:"production"`
}
// TODO: add flag to enable/disable registration of new users
func init() {
config = Config{}
if err := env.Parse(&config); err != nil {
@ -52,6 +54,7 @@ func init() {
FrontendURL: config.FrontendURL,
}
log.Debug().Msgf("IsProd=%t, Domain='%s', FrontendURL='%s'", svcConfig.IsProd, svcConfig.Domain, svcConfig.FrontendURL)
log.Debug().Msg("Initialization completed")
}

19
web/Dockerfile Normal file
View File

@ -0,0 +1,19 @@
# Build stage
FROM node:23.11-alpine3.20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Final stage (optimized image size + NGINX)
FROM nginx:alpine
WORKDIR /app
COPY --from=builder /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@ -1,15 +1,48 @@
server {
listen 80; # TLS termination is handled by Traefik
listen 80;
root /usr/share/nginx/html;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
# TLS termination should be handled by a reverse proxy (e.g. Traefik)
# General security headers
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# Block access to dotfiles
location ~ /\.(?!well-known) {
deny all;
access_log off;
log_not_found off;
}
# Enable gzip compression for all assets
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
location /api {
proxy_pass http://notatest-server:8080; # Internal Docker DNS
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
try_files $uri $uri/ /index.html;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 7d;
add_header Cache-Control "public, no-transform";
}
}
# Route 404 errors to front page
error_page 404 /;
}

11
web/package-lock.json generated
View File

@ -12,6 +12,7 @@
},
"devDependencies": {
"@sveltejs/adapter-auto": "^4.0.0",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/forms": "^0.5.9",
@ -830,6 +831,16 @@
"@sveltejs/kit": "^2.0.0"
}
},
"node_modules/@sveltejs/adapter-static": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz",
"integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"@sveltejs/kit": "^2.0.0"
}
},
"node_modules/@sveltejs/kit": {
"version": "2.20.7",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.7.tgz",

View File

@ -15,6 +15,7 @@
},
"devDependencies": {
"@sveltejs/adapter-auto": "^4.0.0",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/forms": "^0.5.9",

View File

@ -27,7 +27,8 @@
@layer base {
body {
@apply bg-[var(--light-background)] text-[var(--light-text)] transition-colors duration-200;
/* @apply bg-[var(--light-background)] text-[var(--light-text)] transition-colors duration-200; */
@apply bg-[var(--light-background)] text-[var(--light-text)];
}
.dark body {

View File

@ -1,6 +1,5 @@
// TODO: this can be set to always be "/api" after Dockerization as the backend requests
// will automatically be proxied to the correct destination
export const API_BASE_ADDR = import.meta.env.PROD ? "/api" : "http://localhost:8080/api"
// export const API_BASE_ADDR = import.meta.env.PROD ? "/api" : "http://localhost:8080/api"
export const API_BASE_ADDR = "/api"
// Lifetimes of *in-memory* authentication tokens in milliseconds
export const AT_EXP_MS = 15 * 60 * 1000 // 15 min.

View File

@ -0,0 +1,2 @@
export const ssr = false
export const prerender = false

View File

@ -1,18 +1,21 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import adapter from "@sveltejs/adapter-static"
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs/kit/integrations
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
adapter: adapter()
adapter: adapter({
pages: "build",
assets: "build",
fallback: "index.html",
precompress: false
})
},
ssr: false,
prerender: {
entries: []
}
};
}
export default config;
export default config

View File

@ -1,5 +1,5 @@
import tailwindcss from "@tailwindcss/vite"
import { sveltekit } from "@sveltejs/kit/vite"
import tailwindcss from "@tailwindcss/vite"
import { defineConfig } from "vite"
export default defineConfig({