dev #5

Merged
admin merged 3 commits from dev into main 2026-04-16 13:23:54 +02:00
7 changed files with 175 additions and 9 deletions
Showing only changes of commit 4d5fb43ebc - Show all commits

View File

@@ -3,7 +3,7 @@ name: Code Analysis and Production Deploy
on:
pull_request:
push:
branches: [ main ]
branches: [ main, dev ]
jobs:
analyze:
@@ -119,3 +119,34 @@ jobs:
-p "${{ vars.PROD_PORT }}" \
"${{ vars.PROD_USER }}@${{ vars.PROD_HOST }}" \
"bash -lc 'cd /var/www/harheimertc && ./deploy-production.sh'"
deploy-test:
runs-on: ubuntu-latest
needs: analyze
if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
steps:
- name: Prepare SSH
run: |
set -euo pipefail
mkdir -p ~/.ssh
printf "%s" "${{ secrets.PROD_SSH_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -p "${{ vars.PROD_PORT }}" "${{ vars.PROD_HOST }}" >> ~/.ssh/known_hosts
- name: Test SSH connection
run: |
ssh -i ~/.ssh/id_ed25519 \
-o StrictHostKeyChecking=no \
-o BatchMode=yes \
-p "${{ vars.PROD_PORT }}" \
"${{ vars.PROD_USER }}@${{ vars.PROD_HOST }}" \
"echo SSH OK"
- name: Run test deployment script
run: |
ssh -i ~/.ssh/id_ed25519 \
-o BatchMode=yes \
-p "${{ vars.PROD_PORT }}" \
"${{ vars.PROD_USER }}@${{ vars.PROD_HOST }}" \
"bash -lc 'cd /var/www/harheimertc.test && ./deploy-test.sh'"

View File

@@ -67,6 +67,77 @@ has_tracked_files_under() {
git ls-files "$prefix" | head -n 1 | grep -q .
}
install_dependencies() {
if [ -f "package-lock.json" ]; then
echo " Running: npm ci"
npm ci
else
echo " WARNING: package-lock.json fehlt. Führe npm install aus..."
npm install
fi
}
use_project_node() {
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
if [ -s "$NVM_DIR/nvm.sh" ]; then
# shellcheck disable=SC1090
. "$NVM_DIR/nvm.sh"
if [ -f ".nvmrc" ]; then
echo " Using Node version from .nvmrc..."
nvm use
fi
fi
}
ensure_node_version() {
if ! command -v node >/dev/null 2>&1; then
echo "ERROR: Node.js ist nicht im PATH."
exit 1
fi
local node_version
node_version="$(node -p 'process.versions.node')"
if ! node -e 'const [major, minor] = process.versions.node.split(".").map(Number); process.exit(major > 22 || (major === 22 && minor >= 12) ? 0 : 1)' >/dev/null 2>&1; then
echo "ERROR: Node.js >= 22.12.0 wird benötigt, aktuell ist $node_version aktiv."
echo "Bitte Node 22 installieren/aktivieren, z.B.:"
echo " nvm install 22"
echo " nvm alias default 22"
exit 1
fi
echo " Node.js $node_version"
}
sync_public_documents_to_build() {
if [ ! -d "public/documents" ]; then
echo " No public/documents directory to sync"
return 0
fi
if [ ! -d ".output/public" ]; then
echo "ERROR: .output/public fehlt, kann public/documents nicht synchronisieren."
exit 1
fi
mkdir -p ".output/public/documents"
cp -a "public/documents/." ".output/public/documents/"
echo " ✓ public/documents -> .output/public/documents synchronisiert"
local template_pdf="beitrittserklärung_template.pdf"
if [ -f "public/documents/$template_pdf" ]; then
local source_size output_size
source_size=$(stat -f%z "public/documents/$template_pdf" 2>/dev/null || stat -c%s "public/documents/$template_pdf" 2>/dev/null || echo "0")
output_size=$(stat -f%z ".output/public/documents/$template_pdf" 2>/dev/null || stat -c%s ".output/public/documents/$template_pdf" 2>/dev/null || echo "0")
if [ "$source_size" != "$output_size" ] || [ "$source_size" = "0" ]; then
echo "ERROR: .output/public/documents/$template_pdf stimmt nicht mit public/documents überein (Source: $source_size, Output: $output_size)."
exit 1
fi
echo "$template_pdf im Build verifiziert ($output_size bytes)"
fi
}
echo "0. Ensuring persistent data directories (recommended)..."
# IMPORTANT: Only symlink server/data if it's not tracked by git.
if has_tracked_files_under "server/data"; then
@@ -143,7 +214,9 @@ git clean -fd \
# Pull latest changes
echo " Pulling latest changes..."
if ! git pull --ff-only; then
git fetch origin dev
git checkout dev
if ! git pull --ff-only origin dev; then
echo "ERROR: git pull fehlgeschlagen."
echo ""
echo "Häufige Ursache: SSH-Key für den aktuellen User fehlt."
@@ -170,7 +243,9 @@ fi
# 3. Install dependencies
echo ""
echo "3. Installing dependencies..."
npm install
use_project_node
ensure_node_version
install_dependencies
# 4. Remove old build (but keep data!)
echo ""
@@ -204,7 +279,7 @@ fi
if [ ! -d "node_modules" ]; then
echo ""
echo "WARNING: node_modules fehlt. Installiere Dependencies..."
npm install
install_dependencies
fi
# 5. Build
@@ -217,7 +292,7 @@ echo " (This may take a few minutes...)"
echo " Checking dependencies..."
if [ ! -f "node_modules/.package-lock.json" ] && [ ! -f "package-lock.json" ]; then
echo " WARNING: package-lock.json fehlt. Führe npm install aus..."
npm install
install_dependencies
fi
# Build mit expliziter Fehlerbehandlung und Output-Capture
@@ -234,6 +309,10 @@ if [ "$BUILD_EXIT_CODE" -ne 0 ]; then
exit 1
fi
echo ""
echo " Synchronizing public documents into build output..."
sync_public_documents_to_build
# Prüfe auf Warnungen im Build-Output, die auf Probleme hinweisen
if echo "$BUILD_OUTPUT" | grep -qi "error\|failed\|missing"; then
echo ""

View File

@@ -10,7 +10,8 @@ try {
// Helper function to create env object
function createEnv(port) {
return {
NODE_ENV: 'production',
NODE_ENV: process.env.NODE_ENV || 'development',
APP_ENV: process.env.APP_ENV || 'test',
PORT: port,
// Secrets/Config (loaded from .env above, if present)
ENCRYPTION_KEY: process.env.ENCRYPTION_KEY,

View File

@@ -24,6 +24,12 @@ async function loadConfig() {
}
async function collectRecipients(config) {
const isProduction = process.env.NODE_ENV === 'production' && process.env.APP_ENV !== 'test'
if (!isProduction) {
return ['tsschulz@tsschulz.de']
}
const recipients = []
// Vorstand

View File

@@ -41,7 +41,7 @@ async function loadConfig() {
* @returns {Array<string>} Email addresses
*/
function getEmailRecipients(data, config) {
const isProduction = process.env.NODE_ENV === 'production'
const isProduction = process.env.NODE_ENV === 'production' && process.env.APP_ENV !== 'test'
if (!isProduction) {
return ['tsschulz@tsschulz.de']
@@ -215,4 +215,4 @@ export async function sendRegistrationNotification(data) {
console.error('sendRegistrationNotification failed:', error.message || error)
throw error
}
}
}

View File

@@ -62,6 +62,11 @@ import resetPasswordHandler from '../server/api/auth/reset-password.post.js'
import statusHandler from '../server/api/auth/status.get.js'
describe('Auth API Endpoints', () => {
afterEach(() => {
delete process.env.NODE_ENV
delete process.env.APP_ENV
})
beforeEach(() => {
// Setze SMTP-Credentials für Tests
process.env.SMTP_USER = 'test@example.com'
@@ -171,6 +176,30 @@ describe('Auth API Endpoints', () => {
})
expect(nodemailer.default.createTransport).toHaveBeenCalled()
})
it('benachrichtigt in Testumgebung nicht die Vorstand-Empfänger', async () => {
process.env.NODE_ENV = 'production'
process.env.APP_ENV = 'test'
const event = createEvent()
mockSuccessReadBody({
name: 'Max',
email: 'max@example.com',
password: '12345678',
phone: '123',
geburtsdatum: '2000-01-01'
})
authUtils.readUsers.mockResolvedValue([])
authUtils.hashPassword.mockResolvedValue('hashed')
authUtils.writeUsers.mockResolvedValue(true)
await registerHandler(event)
const transporter = nodemailer.default.createTransport.mock.results[0].value
expect(transporter.sendMail).toHaveBeenNthCalledWith(1, expect.objectContaining({
to: 'tsschulz@tsschulz.de'
}))
})
})
describe('POST /api/auth/reset-password', () => {

View File

@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createEvent, mockSuccessReadBody } from './setup'
import fsPromises from 'fs/promises'
import { promises as fs } from 'fs'
@@ -26,6 +26,11 @@ import termineHandler from '../server/api/termine.get.js'
import spielplaeneHandler from '../server/api/spielplaene.get.js'
describe('Öffentliche API-Endpunkte', () => {
afterEach(() => {
delete process.env.NODE_ENV
delete process.env.APP_ENV
})
beforeEach(() => {
// Setze SMTP-Credentials für Tests
process.env.SMTP_USER = 'test@example.com'
@@ -58,6 +63,21 @@ describe('Öffentliche API-Endpunkte', () => {
expect(response.success).toBe(true)
expect(nodemailer.default.createTransport).toHaveBeenCalled()
})
it('sendet in Testumgebung nicht an Vorstand-Empfänger', async () => {
process.env.NODE_ENV = 'production'
process.env.APP_ENV = 'test'
const event = createEvent()
mockSuccessReadBody({ name: 'Max', email: 'max@example.com', subject: 'Frage', message: 'Hallo' })
await contactHandler(event)
const transporter = nodemailer.default.createTransport.mock.results[0].value
expect(transporter.sendMail).toHaveBeenCalledWith(expect.objectContaining({
to: 'tsschulz@tsschulz.de'
}))
})
})
describe('GET /api/galerie', () => {