Add FeWo Docker deployment and expand report themes
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
TZ=Europe/Berlin
|
||||||
|
|
||||||
|
REPORT_RENDERER_BIND_IP=127.0.0.1
|
||||||
|
REPORT_RENDERER_PORT=8090
|
||||||
|
|
||||||
|
RENDER_TIMEOUT_SECONDS=60
|
||||||
|
MAX_PAYLOAD_BYTES=2097152
|
||||||
|
|
||||||
|
GOTENBERG_IMAGE=gotenberg/gotenberg:8
|
||||||
|
GOTENBERG_PLATFORM=linux/amd64
|
||||||
|
|
||||||
|
N8N_NETWORK=docker-postgres-backend
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
name: monthly-report-kit
|
||||||
|
|
||||||
|
services:
|
||||||
|
report-renderer:
|
||||||
|
build:
|
||||||
|
context: ./source/renderer
|
||||||
|
container_name: monthly-report-renderer
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
environment:
|
||||||
|
TZ: ${TZ}
|
||||||
|
GOTENBERG_URL: http://gotenberg:3000
|
||||||
|
RENDER_TIMEOUT_SECONDS: ${RENDER_TIMEOUT_SECONDS}
|
||||||
|
MAX_PAYLOAD_BYTES: ${MAX_PAYLOAD_BYTES}
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- "${REPORT_RENDERER_BIND_IP}:${REPORT_RENDERER_PORT}:8080"
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
gotenberg:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
- CMD
|
||||||
|
- python
|
||||||
|
- -c
|
||||||
|
- >-
|
||||||
|
import urllib.request;
|
||||||
|
urllib.request.urlopen(
|
||||||
|
'http://127.0.0.1:8080/health',
|
||||||
|
timeout=3
|
||||||
|
).read()
|
||||||
|
interval: 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
start_period: 20s
|
||||||
|
|
||||||
|
networks:
|
||||||
|
report_internal:
|
||||||
|
n8n_backend:
|
||||||
|
aliases:
|
||||||
|
- report-renderer
|
||||||
|
|
||||||
|
gotenberg:
|
||||||
|
image: ${GOTENBERG_IMAGE}
|
||||||
|
platform: ${GOTENBERG_PLATFORM}
|
||||||
|
container_name: monthly-report-gotenberg
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
init: true
|
||||||
|
|
||||||
|
entrypoint:
|
||||||
|
- /usr/bin/gotenberg
|
||||||
|
|
||||||
|
command:
|
||||||
|
- --chromium-disable-javascript=true
|
||||||
|
- --api-timeout=90s
|
||||||
|
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
- CMD-SHELL
|
||||||
|
- >-
|
||||||
|
bash -c 'exec 3<>/dev/tcp/127.0.0.1/3000;
|
||||||
|
printf "HEAD /health HTTP/1.0\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n" >&3;
|
||||||
|
IFS= read -r status <&3;
|
||||||
|
case "$$status" in
|
||||||
|
*" 200 "*) exit 0 ;;
|
||||||
|
*) exit 1 ;;
|
||||||
|
esac'
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 20
|
||||||
|
start_period: 20s
|
||||||
|
|
||||||
|
networks:
|
||||||
|
report_internal:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
report_internal:
|
||||||
|
name: monthly-report-internal
|
||||||
|
internal: true
|
||||||
|
|
||||||
|
n8n_backend:
|
||||||
|
external: true
|
||||||
|
name: ${N8N_NETWORK}
|
||||||
Executable
+67
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
TARGET_DIR="${TARGET_DIR:-/srv/docker/monthly-report-kit}"
|
||||||
|
N8N_CONTAINER="${N8N_CONTAINER:-n8n}"
|
||||||
|
POSTGRES_CONTAINER="${POSTGRES_CONTAINER:-infra-postgres}"
|
||||||
|
POSTGRES_DATABASE="${POSTGRES_DATABASE:-fewomonitor}"
|
||||||
|
POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-postgres}"
|
||||||
|
|
||||||
|
[[ -f "$TARGET_DIR/.env" ]] || {
|
||||||
|
echo "FEHLER: $TARGET_DIR/.env fehlt" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$TARGET_DIR/.env"
|
||||||
|
set +a
|
||||||
|
|
||||||
|
TEST_HOST="$REPORT_RENDERER_BIND_IP"
|
||||||
|
case "$TEST_HOST" in
|
||||||
|
0.0.0.0|"::"|"[::]")
|
||||||
|
TEST_HOST="127.0.0.1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "1/5 Compose-Status"
|
||||||
|
(
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
docker compose ps
|
||||||
|
)
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "2/5 Gotenberg-Plattform"
|
||||||
|
docker image inspect "$GOTENBERG_IMAGE" \
|
||||||
|
--format 'Image: {{.Os}}/{{.Architecture}}'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "3/5 Lokaler Renderer-Healthcheck"
|
||||||
|
curl -fsS \
|
||||||
|
"http://${TEST_HOST}:${REPORT_RENDERER_PORT}/health"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "4/5 Zugriff aus dem n8n-Container"
|
||||||
|
docker exec "$N8N_CONTAINER" node -e "
|
||||||
|
fetch('http://report-renderer:8080/health')
|
||||||
|
.then(async response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('HTTP ' + response.status);
|
||||||
|
}
|
||||||
|
console.log(await response.text());
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "5/5 Reporting-Tabellen"
|
||||||
|
docker exec "$POSTGRES_CONTAINER" \
|
||||||
|
psql \
|
||||||
|
-U "$POSTGRES_ADMIN_USER" \
|
||||||
|
-d "$POSTGRES_DATABASE" \
|
||||||
|
-c '\dt public.report_customer_config' \
|
||||||
|
-c '\dt public.monthly_report_runs'
|
||||||
Executable
+40
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
TARGET_DIR="${TARGET_DIR:-/srv/docker/monthly-report-kit}"
|
||||||
|
SOURCE_DIR="$TARGET_DIR/source"
|
||||||
|
REPO_REF="${REPO_REF:-main}"
|
||||||
|
|
||||||
|
[[ -d "$SOURCE_DIR/.git" ]] || {
|
||||||
|
echo "FEHLER: Kein Git-Repository unter $SOURCE_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ -f "$TARGET_DIR/.env" ]] || {
|
||||||
|
echo "FEHLER: $TARGET_DIR/.env fehlt" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$TARGET_DIR/.env"
|
||||||
|
set +a
|
||||||
|
|
||||||
|
: "${GOTENBERG_IMAGE:?GOTENBERG_IMAGE fehlt in .env}"
|
||||||
|
: "${GOTENBERG_PLATFORM:?GOTENBERG_PLATFORM fehlt in .env}"
|
||||||
|
|
||||||
|
git -C "$SOURCE_DIR" fetch --prune origin
|
||||||
|
git -C "$SOURCE_DIR" checkout "$REPO_REF"
|
||||||
|
git -C "$SOURCE_DIR" pull --ff-only origin "$REPO_REF"
|
||||||
|
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
docker compose config >/dev/null
|
||||||
|
|
||||||
|
docker pull \
|
||||||
|
--platform "$GOTENBERG_PLATFORM" \
|
||||||
|
"$GOTENBERG_IMAGE"
|
||||||
|
|
||||||
|
docker compose build --pull report-renderer
|
||||||
|
docker compose up -d --force-recreate
|
||||||
|
|
||||||
|
"$TARGET_DIR/scripts/test-monthly-report-kit.sh"
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.report_customer_config (
|
||||||
|
customer_id VARCHAR(64) PRIMARY KEY,
|
||||||
|
customer_name VARCHAR(160) NOT NULL,
|
||||||
|
property_name VARCHAR(160) NOT NULL,
|
||||||
|
location VARCHAR(160) NOT NULL,
|
||||||
|
contact_line VARCHAR(180) NOT NULL DEFAULT '',
|
||||||
|
logo_data_uri TEXT,
|
||||||
|
logo_text VARCHAR(5) NOT NULL,
|
||||||
|
theme VARCHAR(40) NOT NULL DEFAULT 'modern',
|
||||||
|
primary_color CHAR(7) NOT NULL DEFAULT '#1F5D66',
|
||||||
|
secondary_color CHAR(7) NOT NULL DEFAULT '#2C7C82',
|
||||||
|
accent_color CHAR(7) NOT NULL DEFAULT '#DBA25D',
|
||||||
|
text_color CHAR(7) NOT NULL DEFAULT '#24343A',
|
||||||
|
muted_color CHAR(7) NOT NULL DEFAULT '#718087',
|
||||||
|
surface_color CHAR(7) NOT NULL DEFAULT '#F2F6F5',
|
||||||
|
font_family VARCHAR(100) NOT NULL DEFAULT 'Arial',
|
||||||
|
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Upgrade-Pfad für die ursprüngliche Repo-Struktur.
|
||||||
|
ALTER TABLE public.report_customer_config
|
||||||
|
ALTER COLUMN theme TYPE VARCHAR(40);
|
||||||
|
|
||||||
|
ALTER TABLE public.report_customer_config
|
||||||
|
DROP CONSTRAINT IF EXISTS report_customer_config_theme_check;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'report_customer_config_theme_not_empty'
|
||||||
|
AND conrelid = 'public.report_customer_config'::regclass
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.report_customer_config
|
||||||
|
ADD CONSTRAINT report_customer_config_theme_not_empty
|
||||||
|
CHECK (length(trim(theme)) > 0) NOT VALID;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
ALTER TABLE public.report_customer_config
|
||||||
|
VALIDATE CONSTRAINT report_customer_config_theme_not_empty;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.monthly_report_runs (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
customer_id VARCHAR(64) NOT NULL
|
||||||
|
REFERENCES public.report_customer_config(customer_id),
|
||||||
|
report_year INTEGER NOT NULL
|
||||||
|
CHECK (report_year BETWEEN 2020 AND 2100),
|
||||||
|
report_month INTEGER NOT NULL
|
||||||
|
CHECK (report_month BETWEEN 1 AND 12),
|
||||||
|
status VARCHAR(20) NOT NULL
|
||||||
|
CHECK (status IN ('queued', 'processing', 'completed', 'failed')),
|
||||||
|
report_version VARCHAR(20) NOT NULL,
|
||||||
|
prompt_version VARCHAR(20) NOT NULL,
|
||||||
|
template_version VARCHAR(20) NOT NULL,
|
||||||
|
claude_model VARCHAR(80),
|
||||||
|
storage_path TEXT,
|
||||||
|
checksum_sha256 CHAR(64),
|
||||||
|
error_message TEXT,
|
||||||
|
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
completed_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
UNIQUE (customer_id, report_year, report_month, report_version)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_monthly_report_runs_status
|
||||||
|
ON public.monthly_report_runs(status, report_year, report_month);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_monthly_report_runs_customer_period
|
||||||
|
ON public.monthly_report_runs(customer_id, report_year, report_month);
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.set_report_customer_config_updated_at()
|
||||||
|
RETURNS trigger
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = NOW();
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS trg_report_customer_config_updated_at
|
||||||
|
ON public.report_customer_config;
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_report_customer_config_updated_at
|
||||||
|
BEFORE UPDATE ON public.report_customer_config
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION public.set_report_customer_config_updated_at();
|
||||||
|
|
||||||
|
ALTER TABLE public.report_customer_config OWNER TO fewo;
|
||||||
|
ALTER TABLE public.monthly_report_runs OWNER TO fewo;
|
||||||
|
ALTER SEQUENCE IF EXISTS public.monthly_report_runs_id_seq OWNER TO fewo;
|
||||||
|
ALTER FUNCTION public.set_report_customer_config_updated_at() OWNER TO fewo;
|
||||||
|
|
||||||
|
GRANT SELECT, INSERT, UPDATE, DELETE
|
||||||
|
ON public.report_customer_config, public.monthly_report_runs
|
||||||
|
TO fewo;
|
||||||
|
|
||||||
|
GRANT USAGE, SELECT, UPDATE
|
||||||
|
ON SEQUENCE public.monthly_report_runs_id_seq
|
||||||
|
TO fewo;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
ALTER TABLE public.report_customer_config
|
||||||
|
ALTER COLUMN theme TYPE VARCHAR(40);
|
||||||
|
|
||||||
|
ALTER TABLE public.report_customer_config
|
||||||
|
DROP CONSTRAINT IF EXISTS report_customer_config_theme_check;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'report_customer_config_theme_not_empty'
|
||||||
|
AND conrelid = 'public.report_customer_config'::regclass
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.report_customer_config
|
||||||
|
ADD CONSTRAINT report_customer_config_theme_not_empty
|
||||||
|
CHECK (length(trim(theme)) > 0) NOT VALID;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
ALTER TABLE public.report_customer_config
|
||||||
|
VALIDATE CONSTRAINT report_customer_config_theme_not_empty;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -6,7 +6,8 @@ CREATE TABLE IF NOT EXISTS report_customer_config (
|
|||||||
contact_line VARCHAR(180) NOT NULL DEFAULT '',
|
contact_line VARCHAR(180) NOT NULL DEFAULT '',
|
||||||
logo_data_uri TEXT,
|
logo_data_uri TEXT,
|
||||||
logo_text VARCHAR(5) NOT NULL,
|
logo_text VARCHAR(5) NOT NULL,
|
||||||
theme VARCHAR(20) NOT NULL DEFAULT 'modern' CHECK (theme IN ('modern', 'classic')),
|
theme VARCHAR(40) NOT NULL DEFAULT 'modern'
|
||||||
|
CHECK (length(trim(theme)) > 0),
|
||||||
primary_color CHAR(7) NOT NULL DEFAULT '#1F5D66',
|
primary_color CHAR(7) NOT NULL DEFAULT '#1F5D66',
|
||||||
secondary_color CHAR(7) NOT NULL DEFAULT '#2C7C82',
|
secondary_color CHAR(7) NOT NULL DEFAULT '#2C7C82',
|
||||||
accent_color CHAR(7) NOT NULL DEFAULT '#DBA25D',
|
accent_color CHAR(7) NOT NULL DEFAULT '#DBA25D',
|
||||||
|
|||||||
Reference in New Issue
Block a user