Add local dev mode with HTTP-only nginx and DB seeding)
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 5m11s

This commit is contained in:
2026-03-07 17:36:54 +00:00
parent 5a45f1f427
commit 9fa953c969
10 changed files with 201 additions and 4 deletions

View File

@@ -38,6 +38,9 @@ func main() {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if os.Getenv("SEED_DB") == "true" {
services.SeedDatabase(db)
}
services.InitWebSocket(db) services.InitWebSocket(db)
// SPOTIFY // SPOTIFY

65
backend/services/seed.go Normal file
View File

@@ -0,0 +1,65 @@
package services
import (
"log"
"time"
"adam-french.co.uk/backend/models"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
func SeedDatabase(db *gorm.DB) {
var user models.User
if db.First(&user).Error == nil {
log.Println("Database already has data, skipping seed")
return
}
log.Println("Seeding database with test data...")
hashedPassword, err := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost)
if err != nil {
log.Fatal("Failed to hash seed password:", err)
}
testUser := models.User{
Username: "testuser",
Password: hashedPassword,
Admin: true,
}
db.Create(&testUser)
posts := []models.Post{
{Title: "Welcome to my blog", Content: "This is the first test post with some example content.", AuthorID: testUser.ID},
{Title: "Learning Go", Content: "Go is a great language for building web servers and APIs.", AuthorID: testUser.ID},
{Title: "Vue 3 Tips", Content: "The composition API makes Vue components much more flexible.", AuthorID: testUser.ID},
}
db.Create(&posts)
link1 := "https://example.com/project"
link2 := "https://example.com/book"
activities := []models.Activity{
{Type: "project", Name: "coding"},
{Type: "hobby", Name: "reading", Link: &link1},
{Type: "fitness", Name: "exercise"},
}
db.Create(&activities)
favorites := []models.Favorite{
{Type: "language", Name: "Go"},
{Type: "book", Name: "Designing Data-Intensive Applications", Link: &link2},
{Type: "framework", Name: "Vue"},
}
db.Create(&favorites)
now := time.Now()
rowingEntries := []models.Rowing{
{Date: now.AddDate(0, 0, -14), Time: 1800, Distance: 5000, TimePer500m: 120.0, Calories: 300},
{Date: now.AddDate(0, 0, -7), Time: 1750, Distance: 5200, TimePer500m: 118.5, Calories: 315},
{Date: now, Time: 1700, Distance: 5400, TimePer500m: 116.2, Calories: 330},
}
db.Create(&rowingEntries)
log.Println("Database seeded successfully")
}

9
docker-compose.dev.yml Normal file
View File

@@ -0,0 +1,9 @@
services:
nginx:
environment:
- DEV_MODE=true
ports:
- 80:80
certbot:
profiles:
- disabled

View File

@@ -26,6 +26,7 @@ RUN mkdir -p /etc/nginx/html \
COPY nginx.conf.template /etc/nginx/nginx.conf.template COPY nginx.conf.template /etc/nginx/nginx.conf.template
COPY nginx_setup.conf.template /etc/nginx/nginx_setup.conf.template COPY nginx_setup.conf.template /etc/nginx/nginx_setup.conf.template
COPY nginx_dev.conf.template /etc/nginx/nginx_dev.conf.template
COPY robots.txt /etc/nginx/html/robots.txt COPY robots.txt /etc/nginx/html/robots.txt
COPY entrypoint.sh /entrypoint.sh COPY entrypoint.sh /entrypoint.sh

View File

@@ -1,8 +1,13 @@
#!/bin/sh #!/bin/sh
set -e set -e
# Check if certificate exists # Check if dev mode, certificate exists, or setup mode
if [ -f "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" ] && [ -f "/etc/letsencrypt/live/$DOMAIN/privkey.pem" ]; then if [ "$DEV_MODE" = "true" ]; then
echo "Dev mode. Using HTTP-only nginx config."
envsubst '${DOMAIN} ${BACKEND_HOST} ${BACKEND_PORT} ${BACKEND_ENDPOINT} ${ICECAST_HOST} ${ICECAST_PORT} ${GITEA_HOST} ${GITEA_PORT}' \
< /etc/nginx/nginx_dev.conf.template \
> /etc/nginx/nginx.conf
elif [ -f "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" ] && [ -f "/etc/letsencrypt/live/$DOMAIN/privkey.pem" ]; then
echo "Certificates found. Using production nginx config." echo "Certificates found. Using production nginx config."
envsubst '${DOMAIN} ${BACKEND_HOST} ${BACKEND_PORT} ${BACKEND_ENDPOINT} ${ICECAST_HOST} ${ICECAST_PORT} ${GITEA_HOST} ${GITEA_PORT}' \ envsubst '${DOMAIN} ${BACKEND_HOST} ${BACKEND_PORT} ${BACKEND_ENDPOINT} ${ICECAST_HOST} ${ICECAST_PORT} ${GITEA_HOST} ${GITEA_PORT}' \
< /etc/nginx/nginx.conf.template \ < /etc/nginx/nginx.conf.template \

View File

@@ -0,0 +1,93 @@
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;
charset utf-8;
log_format compact
'$remote_addr "$request" $status rt=$request_time';
access_log /var/log/nginx/access.log compact;
types {
text/javascript mjs;
}
server {
listen 80;
server_name $DOMAIN www.$DOMAIN;
root /etc/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location = /img/stamps/mine.gif {
add_header Access-Control-Allow-Origin *;
}
location $BACKEND_ENDPOINT {
return 301 $BACKEND_ENDPOINT/;
}
location $BACKEND_ENDPOINT/ws {
rewrite ^$BACKEND_ENDPOINT/(.*)$ /$1 break;
proxy_pass http://$BACKEND_HOST:$BACKEND_PORT/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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 $BACKEND_ENDPOINT/ {
rewrite ^$BACKEND_ENDPOINT/(.*)$ /$1 break;
proxy_pass http://$BACKEND_HOST:$BACKEND_PORT/;
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 /radio {
return 301 /radio/;
}
location /radio/ {
proxy_pass http://$ICECAST_HOST:$ICECAST_PORT/;
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 /gitea {
return 301 /gitea/;
}
location /gitea/ {
proxy_pass http://$GITEA_HOST:$GITEA_PORT/;
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;
}
}
}

View File

@@ -4,7 +4,7 @@ import { ref, onMounted } from "vue";
import Header from "@/components/text/Header.vue"; import Header from "@/components/text/Header.vue";
const url = const url =
"https://www.adam-french.co.uk/gitea/api/v1/users/adamf/activities/feeds?limit=1"; "/gitea/api/v1/users/adamf/activities/feeds?limit=1";
const feed = ref(null); const feed = ref(null);
const isLoading = ref(true); const isLoading = ref(true);

View File

@@ -17,7 +17,7 @@ const METRICS = [
onMounted(async () => { onMounted(async () => {
try { try {
const res = await axios.get("https://www.adam-french.co.uk/api/rowing"); const res = await axios.get("/api/rowing");
rows.value = res.data.slice().reverse(); // API returns DESC, reverse to chronological rows.value = res.data.slice().reverse(); // API returns DESC, reverse to chronological
} catch (e) { } catch (e) {
error.value = e.message; error.value = e.message;

View File

@@ -13,4 +13,11 @@ export default defineConfig({
"@": fileURLToPath(new URL("./src", import.meta.url)), "@": fileURLToPath(new URL("./src", import.meta.url)),
}, },
}, },
server: {
proxy: {
"/api": "http://localhost:8080",
"/gitea": "http://localhost:3000",
"/radio": "http://localhost:8000",
},
},
}); });

View File

@@ -61,6 +61,16 @@ certbot ── SSL Certificate Management
Public endpoints for posts, users, favorites, activities, rowing, Spotify, notes, and WebSocket messaging. Protected endpoints for creating/updating/deleting content require JWT authentication via `/auth/login`. Public endpoints for posts, users, favorites, activities, rowing, Spotify, notes, and WebSocket messaging. Protected endpoints for creating/updating/deleting content require JWT authentication via `/auth/login`.
## Local Testing (Dev Mode)
Run the full stack over plain HTTP without SSL certificates:
```
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build
```
This uses an HTTP-only nginx config with all routing (SPA, backend proxy, radio, gitea) and disables certbot. Visit `http://localhost` to test.
## Future Ideas ## Future Ideas
- More Rust to WASM - More Rust to WASM
@@ -96,6 +106,10 @@ BACKEND_HOST=
BACKEND_SECRET= BACKEND_SECRET=
BACKEND_ENDPOINT= BACKEND_ENDPOINT=
CLAUDE_API_KEY=
SEED_DB=
OBSIDIAN_DIR= OBSIDIAN_DIR=
SPOTIFY_CLIENT_ID= SPOTIFY_CLIENT_ID=