Remove nginx/webapp container; single Go server serves SPA + API

- Add root Dockerfile: node build → copy dist into Go embed path → distroless binary
- Update docker-compose: one service (api on :9050), DB renamed ralph→pamietnik
- Remove references to RALPH/reisejournal across all docs and configs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Christoph K.
2026-04-06 10:32:04 +02:00
parent d1436abca8
commit a49416854e
28 changed files with 87 additions and 51 deletions

View File

@@ -3,7 +3,7 @@ name: code-reviewer
description: Prüft Codequalität, Lesbarkeit und Konsistenz. Vor Commits einsetzen. description: Prüft Codequalität, Lesbarkeit und Konsistenz. Vor Commits einsetzen.
--- ---
Du bist Code-Reviewer für das Projekt Pamietnik (RALPH). Du bist Code-Reviewer für das Projekt Pamietnik.
## Checkliste Go (Backend) ## Checkliste Go (Backend)

View File

@@ -3,7 +3,7 @@ name: dokumentar
description: Pflegt Markdown-Dokumentation und Mermaid-Diagramme. Bei neuen Features, Architekturänderungen oder wenn Doku und Code auseinanderlaufen. description: Pflegt Markdown-Dokumentation und Mermaid-Diagramme. Bei neuen Features, Architekturänderungen oder wenn Doku und Code auseinanderlaufen.
--- ---
Du bist Dokumentar für das Projekt Pamietnik (RALPH). Du bist Dokumentar für das Projekt Pamietnik.
## Zu pflegende Dokumente ## Zu pflegende Dokumente

View File

@@ -3,7 +3,7 @@ name: programmierer
description: Schreibt und ändert Code für Features und Bug-Fixes. Für Go-Backend und Android/Kotlin. Einsetzen bei konkreten Implementierungsaufgaben. description: Schreibt und ändert Code für Features und Bug-Fixes. Für Go-Backend und Android/Kotlin. Einsetzen bei konkreten Implementierungsaufgaben.
--- ---
Du bist Programmierer für das Projekt Pamietnik (RALPH). Du bist Programmierer für das Projekt Pamietnik.
## Stack ## Stack

View File

@@ -3,7 +3,7 @@ name: security-reviewer
description: Prüft OWASP Top 10, Dependency-Schwachstellen und Secrets. Vor Releases und bei Änderungen an externen APIs oder Auth-Code einsetzen. description: Prüft OWASP Top 10, Dependency-Schwachstellen und Secrets. Vor Releases und bei Änderungen an externen APIs oder Auth-Code einsetzen.
--- ---
Du bist Security-Reviewer für das Projekt Pamietnik (RALPH). Du bist Security-Reviewer für das Projekt Pamietnik.
## Prüf-Befehle ## Prüf-Befehle

View File

@@ -3,7 +3,7 @@ name: software-architekt
description: Analysiert Struktur, Abhängigkeiten und Architekturentscheidungen. Einsetzen vor größeren Änderungen, neuen Modulen oder wenn Komponenten-Grenzen unklar sind. description: Analysiert Struktur, Abhängigkeiten und Architekturentscheidungen. Einsetzen vor größeren Änderungen, neuen Modulen oder wenn Komponenten-Grenzen unklar sind.
--- ---
Du bist Software-Architekt für das Projekt Pamietnik (RALPH): Go-Backend + Android-App (Kotlin/Compose). Du bist Software-Architekt für das Projekt Pamietnik: Go-Backend + Android-App (Kotlin/Compose).
## Deine Aufgaben ## Deine Aufgaben

View File

@@ -3,7 +3,7 @@ name: tester
description: Schreibt und führt Unit- und Integrationstests aus. Nach jeder Code-Änderung einsetzen. description: Schreibt und führt Unit- und Integrationstests aus. Nach jeder Code-Änderung einsetzen.
--- ---
Du bist Tester für das Projekt Pamietnik (RALPH). Du bist Tester für das Projekt Pamietnik.
## Test-Befehle ## Test-Befehle

View File

@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview ## Project Overview
**Pamietnik** (Codename: RALPH) is a life/travel journal consisting of three components: **Pamietnik** is a life/travel journal consisting of three components:
- `app/` — Android app (Kotlin + Jetpack Compose) - `app/` — Android app (Kotlin + Jetpack Compose)
- `backend/` — Go REST API + server-side rendered Web UI - `backend/` — Go REST API + server-side rendered Web UI
- `README.md` — single source of truth for architecture, requirements, and backlog - `README.md` — single source of truth for architecture, requirements, and backlog
@@ -28,7 +28,7 @@ go run ./cmd/server # starts API on :8080 (default)
``` ```
**Env vars** (with defaults): **Env vars** (with defaults):
- `DATABASE_URL``postgres://ralph:ralph@localhost:5432/ralph?sslmode=disable` - `DATABASE_URL``postgres://pamietnik:pamietnik@localhost:5432/pamietnik?sslmode=disable`
- `LISTEN_ADDR``:8080` - `LISTEN_ADDR``:8080`
- `UPLOAD_DIR``./uploads` - `UPLOAD_DIR``./uploads`
@@ -93,7 +93,7 @@ cd app
### Android Architecture ### Android Architecture
``` ```
de.jacek.reisejournal/ de.jacek.pamietnik/
domain/ Trackpoint domain model domain/ Trackpoint domain model
data/ Room entities, DAOs, local DB data/ Room entities, DAOs, local DB
service/ Background location foreground service service/ Background location foreground service

24
Dockerfile Normal file
View File

@@ -0,0 +1,24 @@
# Stage 1: Build Vite SPA
FROM node:22-alpine AS webapp-builder
WORKDIR /webapp
COPY webapp/package.json webapp/package-lock.json ./
RUN npm ci
COPY webapp/ ./
RUN npm run build
# Stage 2: Build Go server
FROM golang:1.25-alpine AS go-builder
WORKDIR /app
COPY backend/go.mod backend/go.sum ./
RUN go mod download
COPY backend/ ./
# Inject built SPA into embed path
COPY --from=webapp-builder /webapp/dist ./internal/api/webapp/
RUN CGO_ENABLED=0 GOOS=linux go build -o /server ./cmd/server
RUN CGO_ENABLED=0 GOOS=linux go build -o /createuser ./cmd/createuser
# Stage 3: Minimal runtime image
FROM gcr.io/distroless/static-debian12
COPY --from=go-builder /server /server
COPY --from=go-builder /createuser /createuser
ENTRYPOINT ["/server"]

View File

@@ -497,7 +497,7 @@ Nachteile:
Widerruf/Revocation, Rotation und Lebensdauer-Management erhöhen Komplexität. Widerruf/Revocation, Rotation und Lebensdauer-Management erhöhen Komplexität.
Empfehlung für RALPH Empfehlung für Pamietnik
Website: Session Cookie. Website: Session Cookie.
JWT optional später, falls API-first/SSO nötig wird. JWT optional später, falls API-first/SSO nötig wird.

View File

@@ -7,11 +7,11 @@ plugins {
} }
android { android {
namespace = "de.jacek.reisejournal" namespace = "de.jacek.pamietnik"
compileSdk = 35 compileSdk = 35
defaultConfig { defaultConfig {
applicationId = "de.jacek.reisejournal" applicationId = "de.jacek.pamietnik"
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 35
versionCode = 1 versionCode = 1

View File

@@ -1,4 +1,4 @@
package de.jacek.reisejournal package de.jacek.pamietnik
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -11,6 +11,6 @@ class ExampleInstrumentedTest {
@Test @Test
fun useAppContext() { fun useAppContext() {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("de.jacek.reisejournal", appContext.packageName) assertEquals("de.jacek.pamietnik", appContext.packageName)
} }
} }

View File

@@ -1,12 +1,12 @@
package de.jacek.reisejournal package de.jacek.pamietnik
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import de.jacek.reisejournal.ui.navigation.NavGraph import de.jacek.pamietnik.ui.navigation.NavGraph
import de.jacek.reisejournal.ui.theme.RalphTheme import de.jacek.pamietnik.ui.theme.RalphTheme
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {

View File

@@ -1,4 +1,4 @@
package de.jacek.reisejournal package de.jacek.pamietnik
import android.app.Application import android.app.Application
import androidx.hilt.work.HiltWorkerFactory import androidx.hilt.work.HiltWorkerFactory
@@ -7,7 +7,7 @@ import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject import javax.inject.Inject
@HiltAndroidApp @HiltAndroidApp
class RalphApp : Application(), Configuration.Provider { class PamietnikApp : Application(), Configuration.Provider {
@Inject @Inject
lateinit var workerFactory: HiltWorkerFactory lateinit var workerFactory: HiltWorkerFactory

View File

@@ -1,4 +1,4 @@
package de.jacek.reisejournal.domain package de.jacek.pamietnik.domain
data class Trackpoint( data class Trackpoint(
val eventId: String, // UUID, client-generated val eventId: String, // UUID, client-generated

View File

@@ -1,4 +1,4 @@
package de.jacek.reisejournal.ui.home package de.jacek.pamietnik.ui.home
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -16,7 +16,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import de.jacek.reisejournal.R import de.jacek.pamietnik.R
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable

View File

@@ -1,4 +1,4 @@
package de.jacek.reisejournal.ui.home package de.jacek.pamietnik.ui.home
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel

View File

@@ -1,10 +1,10 @@
package de.jacek.reisejournal.ui.navigation package de.jacek.pamietnik.ui.navigation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import de.jacek.reisejournal.ui.home.HomeScreen import de.jacek.pamietnik.ui.home.HomeScreen
const val HOME_ROUTE = "home" const val HOME_ROUTE = "home"

View File

@@ -1,4 +1,4 @@
package de.jacek.reisejournal.ui.theme package de.jacek.pamietnik.ui.theme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color

View File

@@ -1,4 +1,4 @@
package de.jacek.reisejournal.ui.theme package de.jacek.pamietnik.ui.theme
import android.os.Build import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme

View File

@@ -1,4 +1,4 @@
package de.jacek.reisejournal.ui.theme package de.jacek.pamietnik.ui.theme
import androidx.compose.material3.Typography import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle

View File

@@ -1,4 +1,4 @@
package de.jacek.reisejournal package de.jacek.pamietnik
import org.junit.Test import org.junit.Test
import org.junit.Assert.* import org.junit.Assert.*

View File

@@ -25,5 +25,5 @@ dependencyResolutionManagement {
} }
} }
rootProject.name = "reisejournal" rootProject.name = "pamietnik"
include(":app") include(":app")

Submodule backend updated: 55d2ffc203...d97196790d

View File

@@ -2,7 +2,7 @@
## 1. Einführung und Ziele ## 1. Einführung und Ziele
**Pamietnik** (Codename RALPH) ist ein persönliches Lebens- und Reisejournal bestehend aus drei Komponenten: einer Android-App, einer Web-App und einem Go-Backend-Server. **Pamietnik** ist ein persönliches Journal bestehend aus drei Komponenten: einer Android-App, einer Web-App und einem Go-Backend-Server.
### Fachliches Zielbild ### Fachliches Zielbild
@@ -194,7 +194,7 @@ backend/
### Ebene 2 — Android-Pakete ### Ebene 2 — Android-Pakete
``` ```
app/app/src/main/kotlin/de/jacek/reisejournal/ app/app/src/main/kotlin/de/jacek/pamietnik/
├── domain/ Trackpoint Domain Model ├── domain/ Trackpoint Domain Model
├── data/ Room Entities, DAOs, lokale DB ├── data/ Room Entities, DAOs, lokale DB
├── service/ Background Location Foreground Service ├── service/ Background Location Foreground Service

View File

@@ -2,34 +2,30 @@ services:
postgres: postgres:
image: postgres:16-alpine image: postgres:16-alpine
environment: environment:
POSTGRES_USER: ralph POSTGRES_USER: pamietnik
POSTGRES_PASSWORD: ralph POSTGRES_PASSWORD: pamietnik
POSTGRES_DB: ralph POSTGRES_DB: pamietnik
volumes: volumes:
- pgdata:/var/lib/postgresql/data - pgdata:/var/lib/postgresql/data
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U ralph"] test: ["CMD-SHELL", "pg_isready -U pamietnik"]
interval: 5s interval: 5s
timeout: 5s timeout: 5s
retries: 5 retries: 5
api: api:
build: ./backend build:
context: .
dockerfile: Dockerfile
ports:
- "9050:8080"
environment: environment:
DATABASE_URL: postgres://ralph:ralph@postgres:5432/ralph?sslmode=disable DATABASE_URL: postgres://pamietnik:pamietnik@postgres:5432/pamietnik?sslmode=disable
LISTEN_ADDR: :8080 LISTEN_ADDR: :8080
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
restart: unless-stopped restart: unless-stopped
webapp:
build: ./webapp
ports:
- "9050:80"
depends_on:
- api
restart: unless-stopped
volumes: volumes:
pgdata: pgdata:

View File

@@ -1,6 +1,6 @@
# Pamietnik Webapp # Pamietnik Webapp
Eigenständige Single-Page-Application für das Pamietnik-Reisejournal. Kommuniziert über REST mit dem Go-Backend. Eigenständige Single-Page-Application für das Pamietnik. Kommuniziert über REST mit dem Go-Backend.
## Technologie ## Technologie

View File

@@ -2,6 +2,16 @@ server {
listen 80; listen 80;
# API und Auth-Endpunkte zum Backend proxieren # API und Auth-Endpunkte zum Backend proxieren
location /healthz {
proxy_pass http://api:8080;
proxy_set_header Host $host;
}
location /readyz {
proxy_pass http://api:8080;
proxy_set_header Host $host;
}
location /v1/ { location /v1/ {
proxy_pass http://api:8080; proxy_pass http://api:8080;
proxy_set_header Host $host; proxy_set_header Host $host;

View File

@@ -50,9 +50,15 @@ async function get<T>(path: string): Promise<T> {
export const api = { export const api = {
getDays(from?: string, to?: string): Promise<DaySummary[]> { getDays(from?: string, to?: string): Promise<DaySummary[]> {
const params = new URLSearchParams() const now = new Date()
if (from) params.set('from', from) const defaultTo = now.toISOString().slice(0, 10)
if (to) params.set('to', to) const past = new Date(now)
past.setDate(past.getDate() - 90)
const defaultFrom = past.toISOString().slice(0, 10)
const params = new URLSearchParams({
from: from ?? defaultFrom,
to: to ?? defaultTo,
})
return get<DaySummary[]>(`/v1/days?${params}`) return get<DaySummary[]>(`/v1/days?${params}`)
}, },
getTrackpoints(date: string): Promise<Trackpoint[]> { getTrackpoints(date: string): Promise<Trackpoint[]> {