Enhance deploy-test.sh with functions for Node.js version management, dependency installation, and public document synchronization. Implement checks for Node.js version requirements and improve error handling for document syncing. Update environment configuration in harheimertc.test.config.cjs to support development and test environments. Modify email recipient logic in contact and email service APIs to prevent notifications in test environments. Add tests to verify behavior in test conditions.
This commit is contained in:
@@ -3,7 +3,7 @@ name: Code Analysis and Production Deploy
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ main, dev ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
@@ -119,3 +119,34 @@ jobs:
|
|||||||
-p "${{ vars.PROD_PORT }}" \
|
-p "${{ vars.PROD_PORT }}" \
|
||||||
"${{ vars.PROD_USER }}@${{ vars.PROD_HOST }}" \
|
"${{ vars.PROD_USER }}@${{ vars.PROD_HOST }}" \
|
||||||
"bash -lc 'cd /var/www/harheimertc && ./deploy-production.sh'"
|
"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'"
|
||||||
|
|||||||
@@ -67,6 +67,77 @@ has_tracked_files_under() {
|
|||||||
git ls-files "$prefix" | head -n 1 | grep -q .
|
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)..."
|
echo "0. Ensuring persistent data directories (recommended)..."
|
||||||
# IMPORTANT: Only symlink server/data if it's not tracked by git.
|
# IMPORTANT: Only symlink server/data if it's not tracked by git.
|
||||||
if has_tracked_files_under "server/data"; then
|
if has_tracked_files_under "server/data"; then
|
||||||
@@ -143,7 +214,9 @@ git clean -fd \
|
|||||||
|
|
||||||
# Pull latest changes
|
# Pull latest changes
|
||||||
echo " Pulling 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 "ERROR: git pull fehlgeschlagen."
|
||||||
echo ""
|
echo ""
|
||||||
echo "Häufige Ursache: SSH-Key für den aktuellen User fehlt."
|
echo "Häufige Ursache: SSH-Key für den aktuellen User fehlt."
|
||||||
@@ -170,7 +243,9 @@ fi
|
|||||||
# 3. Install dependencies
|
# 3. Install dependencies
|
||||||
echo ""
|
echo ""
|
||||||
echo "3. Installing dependencies..."
|
echo "3. Installing dependencies..."
|
||||||
npm install
|
use_project_node
|
||||||
|
ensure_node_version
|
||||||
|
install_dependencies
|
||||||
|
|
||||||
# 4. Remove old build (but keep data!)
|
# 4. Remove old build (but keep data!)
|
||||||
echo ""
|
echo ""
|
||||||
@@ -204,7 +279,7 @@ fi
|
|||||||
if [ ! -d "node_modules" ]; then
|
if [ ! -d "node_modules" ]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "WARNING: node_modules fehlt. Installiere Dependencies..."
|
echo "WARNING: node_modules fehlt. Installiere Dependencies..."
|
||||||
npm install
|
install_dependencies
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 5. Build
|
# 5. Build
|
||||||
@@ -217,7 +292,7 @@ echo " (This may take a few minutes...)"
|
|||||||
echo " Checking dependencies..."
|
echo " Checking dependencies..."
|
||||||
if [ ! -f "node_modules/.package-lock.json" ] && [ ! -f "package-lock.json" ]; then
|
if [ ! -f "node_modules/.package-lock.json" ] && [ ! -f "package-lock.json" ]; then
|
||||||
echo " WARNING: package-lock.json fehlt. Führe npm install aus..."
|
echo " WARNING: package-lock.json fehlt. Führe npm install aus..."
|
||||||
npm install
|
install_dependencies
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build mit expliziter Fehlerbehandlung und Output-Capture
|
# Build mit expliziter Fehlerbehandlung und Output-Capture
|
||||||
@@ -234,6 +309,10 @@ if [ "$BUILD_EXIT_CODE" -ne 0 ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
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
|
# Prüfe auf Warnungen im Build-Output, die auf Probleme hinweisen
|
||||||
if echo "$BUILD_OUTPUT" | grep -qi "error\|failed\|missing"; then
|
if echo "$BUILD_OUTPUT" | grep -qi "error\|failed\|missing"; then
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ try {
|
|||||||
// Helper function to create env object
|
// Helper function to create env object
|
||||||
function createEnv(port) {
|
function createEnv(port) {
|
||||||
return {
|
return {
|
||||||
NODE_ENV: 'production',
|
NODE_ENV: process.env.NODE_ENV || 'development',
|
||||||
|
APP_ENV: process.env.APP_ENV || 'test',
|
||||||
PORT: port,
|
PORT: port,
|
||||||
// Secrets/Config (loaded from .env above, if present)
|
// Secrets/Config (loaded from .env above, if present)
|
||||||
ENCRYPTION_KEY: process.env.ENCRYPTION_KEY,
|
ENCRYPTION_KEY: process.env.ENCRYPTION_KEY,
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ async function loadConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function collectRecipients(config) {
|
async function collectRecipients(config) {
|
||||||
|
const isProduction = process.env.NODE_ENV === 'production' && process.env.APP_ENV !== 'test'
|
||||||
|
|
||||||
|
if (!isProduction) {
|
||||||
|
return ['tsschulz@tsschulz.de']
|
||||||
|
}
|
||||||
|
|
||||||
const recipients = []
|
const recipients = []
|
||||||
|
|
||||||
// Vorstand
|
// Vorstand
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ async function loadConfig() {
|
|||||||
* @returns {Array<string>} Email addresses
|
* @returns {Array<string>} Email addresses
|
||||||
*/
|
*/
|
||||||
function getEmailRecipients(data, config) {
|
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) {
|
if (!isProduction) {
|
||||||
return ['tsschulz@tsschulz.de']
|
return ['tsschulz@tsschulz.de']
|
||||||
@@ -215,4 +215,4 @@ export async function sendRegistrationNotification(data) {
|
|||||||
console.error('sendRegistrationNotification failed:', error.message || error)
|
console.error('sendRegistrationNotification failed:', error.message || error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ import resetPasswordHandler from '../server/api/auth/reset-password.post.js'
|
|||||||
import statusHandler from '../server/api/auth/status.get.js'
|
import statusHandler from '../server/api/auth/status.get.js'
|
||||||
|
|
||||||
describe('Auth API Endpoints', () => {
|
describe('Auth API Endpoints', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
delete process.env.NODE_ENV
|
||||||
|
delete process.env.APP_ENV
|
||||||
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Setze SMTP-Credentials für Tests
|
// Setze SMTP-Credentials für Tests
|
||||||
process.env.SMTP_USER = 'test@example.com'
|
process.env.SMTP_USER = 'test@example.com'
|
||||||
@@ -171,6 +176,30 @@ describe('Auth API Endpoints', () => {
|
|||||||
})
|
})
|
||||||
expect(nodemailer.default.createTransport).toHaveBeenCalled()
|
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', () => {
|
describe('POST /api/auth/reset-password', () => {
|
||||||
|
|||||||
@@ -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 { createEvent, mockSuccessReadBody } from './setup'
|
||||||
import fsPromises from 'fs/promises'
|
import fsPromises from 'fs/promises'
|
||||||
import { promises as fs } from 'fs'
|
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'
|
import spielplaeneHandler from '../server/api/spielplaene.get.js'
|
||||||
|
|
||||||
describe('Öffentliche API-Endpunkte', () => {
|
describe('Öffentliche API-Endpunkte', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
delete process.env.NODE_ENV
|
||||||
|
delete process.env.APP_ENV
|
||||||
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Setze SMTP-Credentials für Tests
|
// Setze SMTP-Credentials für Tests
|
||||||
process.env.SMTP_USER = 'test@example.com'
|
process.env.SMTP_USER = 'test@example.com'
|
||||||
@@ -58,6 +63,21 @@ describe('Öffentliche API-Endpunkte', () => {
|
|||||||
expect(response.success).toBe(true)
|
expect(response.success).toBe(true)
|
||||||
expect(nodemailer.default.createTransport).toHaveBeenCalled()
|
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', () => {
|
describe('GET /api/galerie', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user