3 Commits

Author SHA1 Message Date
aee8705fa3 Merge pull request 'dev' (#7) from dev into main
All checks were successful
Code Analysis and Production Deploy / analyze (push) Has been skipped
Code Analysis and Production Deploy / deploy-production (push) Successful in 1m56s
Code Analysis and Production Deploy / deploy-test (push) Has been skipped
Reviewed-on: #7
2026-04-16 14:06:43 +02:00
99c03dccf2 Merge pull request 'Remove package version change requirement for main PRs in code-analysis.yml to streamline workflow.' (#6) from dev into main
All checks were successful
Code Analysis and Production Deploy / analyze (push) Successful in 2m47s
Code Analysis and Production Deploy / deploy-production (push) Successful in 1m55s
Code Analysis and Production Deploy / deploy-test (push) Has been skipped
Reviewed-on: #6
2026-04-16 13:41:06 +02:00
d450175871 Merge pull request 'dev' (#5) from dev into main
All checks were successful
Code Analysis and Production Deploy / analyze (push) Successful in 2m47s
Code Analysis and Production Deploy / deploy-production (push) Successful in 1m58s
Code Analysis and Production Deploy / deploy-test (push) Has been skipped
Reviewed-on: #5
2026-04-16 13:23:53 +02:00
9 changed files with 46 additions and 245 deletions

View File

@@ -64,37 +64,6 @@ install_dependencies() {
fi fi
} }
install_dependencies_if_needed() {
local cache_dir=".deploy-cache"
local lock_hash_file="$cache_dir/package-lock.sha256"
local current_lock_hash=""
local previous_lock_hash=""
if [ ! -f "package-lock.json" ]; then
echo " package-lock.json fehlt, führe npm install aus..."
install_dependencies
return 0
fi
mkdir -p "$cache_dir"
current_lock_hash="$(sha256sum package-lock.json | awk '{print $1}')"
if [ -f "$lock_hash_file" ]; then
previous_lock_hash="$(cat "$lock_hash_file" 2>/dev/null || true)"
fi
if [ ! -d "node_modules" ]; then
echo " node_modules fehlt, installiere Dependencies..."
install_dependencies
elif [ "$current_lock_hash" != "$previous_lock_hash" ]; then
echo " package-lock.json geändert, führe npm ci aus..."
install_dependencies
else
echo " package-lock.json unverändert, überspringe npm ci"
fi
printf '%s\n' "$current_lock_hash" > "$lock_hash_file"
}
use_project_node() { use_project_node() {
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
if [ -s "$NVM_DIR/nvm.sh" ]; then if [ -s "$NVM_DIR/nvm.sh" ]; then
@@ -270,7 +239,7 @@ echo ""
echo "3. Installing dependencies..." echo "3. Installing dependencies..."
use_project_node use_project_node
ensure_node_version ensure_node_version
install_dependencies_if_needed install_dependencies
# 4. Remove old build (but keep data!) # 4. Remove old build (but keep data!)
echo "" echo ""
@@ -293,16 +262,11 @@ if [ -d ".output" ]; then
echo " ✓ .output gelöscht" echo " ✓ .output gelöscht"
fi fi
# .nuxt standardmäßig behalten (beschleunigt Folge-Builds deutlich). # Auch .nuxt Cache löschen für sauberen Build
# Für erzwungenen Clean-Build: CLEAN_NUXT_CACHE=1 ./deploy-production.sh if [ -d ".nuxt" ]; then
if [ "${CLEAN_NUXT_CACHE:-0}" = "1" ]; then echo " Removing .nuxt cache..."
if [ -d ".nuxt" ]; then rm -rf .nuxt
echo " CLEAN_NUXT_CACHE=1 gesetzt: entferne .nuxt cache..." echo " ✓ .nuxt gelöscht"
rm -rf .nuxt
echo " ✓ .nuxt gelöscht"
fi
else
echo " Behalte .nuxt cache für schnelleren Build (CLEAN_NUXT_CACHE=1 für Clean-Build)"
fi fi
# Prüfe, ob node_modules vorhanden ist (für npm run build) # Prüfe, ob node_modules vorhanden ist (für npm run build)
@@ -325,20 +289,17 @@ if [ ! -f "node_modules/.package-lock.json" ] && [ ! -f "package-lock.json" ]; t
install_dependencies install_dependencies
fi fi
# Build mit expliziter Fehlerbehandlung und gleichzeitiger Log-Datei # Build mit expliziter Fehlerbehandlung und Output-Capture
BUILD_LOG_FILE=".deploy-cache/build-$(date +%Y%m%d-%H%M%S).log" BUILD_OUTPUT=$(npm run build 2>&1)
mkdir -p ".deploy-cache" BUILD_EXIT_CODE=$?
if npm run build 2>&1 | tee "$BUILD_LOG_FILE"; then
BUILD_EXIT_CODE=0 # Zeige Build-Output
else echo "$BUILD_OUTPUT"
BUILD_EXIT_CODE=$?
fi
if [ "$BUILD_EXIT_CODE" -ne 0 ]; then if [ "$BUILD_EXIT_CODE" -ne 0 ]; then
echo "" echo ""
echo "ERROR: Build fehlgeschlagen mit Exit-Code $BUILD_EXIT_CODE" echo "ERROR: Build fehlgeschlagen mit Exit-Code $BUILD_EXIT_CODE"
echo "Bitte prüfen Sie die Build-Ausgabe oben auf Fehler." echo "Bitte prüfen Sie die Build-Ausgabe oben auf Fehler."
echo "Build-Log: $BUILD_LOG_FILE"
exit 1 exit 1
fi fi
@@ -347,11 +308,10 @@ echo " Synchronizing public documents into build output..."
sync_public_documents_to_build 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 rg -i "error|failed|missing" "$BUILD_LOG_FILE" >/dev/null 2>&1; then if echo "$BUILD_OUTPUT" | grep -qi "error\|failed\|missing"; then
echo "" echo ""
echo "WARNING: Build-Output enthält möglicherweise Fehler oder Warnungen." echo "WARNING: Build-Output enthält möglicherweise Fehler oder Warnungen."
echo "Bitte prüfen Sie die Ausgabe oben." echo "Bitte prüfen Sie die Ausgabe oben."
echo "Build-Log: $BUILD_LOG_FILE"
fi fi
# Prüfe, ob der Build erfolgreich war - mehrere Checks # Prüfe, ob der Build erfolgreich war - mehrere Checks

View File

@@ -77,37 +77,6 @@ install_dependencies() {
fi fi
} }
install_dependencies_if_needed() {
local cache_dir=".deploy-cache"
local lock_hash_file="$cache_dir/package-lock.sha256"
local current_lock_hash=""
local previous_lock_hash=""
if [ ! -f "package-lock.json" ]; then
echo " package-lock.json fehlt, führe npm install aus..."
install_dependencies
return 0
fi
mkdir -p "$cache_dir"
current_lock_hash="$(sha256sum package-lock.json | awk '{print $1}')"
if [ -f "$lock_hash_file" ]; then
previous_lock_hash="$(cat "$lock_hash_file" 2>/dev/null || true)"
fi
if [ ! -d "node_modules" ]; then
echo " node_modules fehlt, installiere Dependencies..."
install_dependencies
elif [ "$current_lock_hash" != "$previous_lock_hash" ]; then
echo " package-lock.json geändert, führe npm ci aus..."
install_dependencies
else
echo " package-lock.json unverändert, überspringe npm ci"
fi
printf '%s\n' "$current_lock_hash" > "$lock_hash_file"
}
use_project_node() { use_project_node() {
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
if [ -s "$NVM_DIR/nvm.sh" ]; then if [ -s "$NVM_DIR/nvm.sh" ]; then
@@ -276,7 +245,7 @@ echo ""
echo "3. Installing dependencies..." echo "3. Installing dependencies..."
use_project_node use_project_node
ensure_node_version ensure_node_version
install_dependencies_if_needed install_dependencies
# 4. Remove old build (but keep data!) # 4. Remove old build (but keep data!)
echo "" echo ""
@@ -299,16 +268,11 @@ if [ -d ".output" ]; then
echo " ✓ .output gelöscht" echo " ✓ .output gelöscht"
fi fi
# .nuxt standardmäßig behalten (beschleunigt Folge-Builds deutlich). # Auch .nuxt Cache löschen für sauberen Build
# Für erzwungenen Clean-Build: CLEAN_NUXT_CACHE=1 ./deploy-test.sh if [ -d ".nuxt" ]; then
if [ "${CLEAN_NUXT_CACHE:-0}" = "1" ]; then echo " Removing .nuxt cache..."
if [ -d ".nuxt" ]; then rm -rf .nuxt
echo " CLEAN_NUXT_CACHE=1 gesetzt: entferne .nuxt cache..." echo " ✓ .nuxt gelöscht"
rm -rf .nuxt
echo " ✓ .nuxt gelöscht"
fi
else
echo " Behalte .nuxt cache für schnelleren Build (CLEAN_NUXT_CACHE=1 für Clean-Build)"
fi fi
# Prüfe, ob node_modules vorhanden ist (für npm run build) # Prüfe, ob node_modules vorhanden ist (für npm run build)
@@ -331,20 +295,17 @@ if [ ! -f "node_modules/.package-lock.json" ] && [ ! -f "package-lock.json" ]; t
install_dependencies install_dependencies
fi fi
# Build mit expliziter Fehlerbehandlung und gleichzeitiger Log-Datei # Build mit expliziter Fehlerbehandlung und Output-Capture
BUILD_LOG_FILE=".deploy-cache/build-$(date +%Y%m%d-%H%M%S).log" BUILD_OUTPUT=$(npm run build 2>&1)
mkdir -p ".deploy-cache" BUILD_EXIT_CODE=$?
if npm run build 2>&1 | tee "$BUILD_LOG_FILE"; then
BUILD_EXIT_CODE=0 # Zeige Build-Output
else echo "$BUILD_OUTPUT"
BUILD_EXIT_CODE=$?
fi
if [ "$BUILD_EXIT_CODE" -ne 0 ]; then if [ "$BUILD_EXIT_CODE" -ne 0 ]; then
echo "" echo ""
echo "ERROR: Build fehlgeschlagen mit Exit-Code $BUILD_EXIT_CODE" echo "ERROR: Build fehlgeschlagen mit Exit-Code $BUILD_EXIT_CODE"
echo "Bitte prüfen Sie die Build-Ausgabe oben auf Fehler." echo "Bitte prüfen Sie die Build-Ausgabe oben auf Fehler."
echo "Build-Log: $BUILD_LOG_FILE"
exit 1 exit 1
fi fi
@@ -353,11 +314,10 @@ echo " Synchronizing public documents into build output..."
sync_public_documents_to_build 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 rg -i "error|failed|missing" "$BUILD_LOG_FILE" >/dev/null 2>&1; then if echo "$BUILD_OUTPUT" | grep -qi "error\|failed\|missing"; then
echo "" echo ""
echo "WARNING: Build-Output enthält möglicherweise Fehler oder Warnungen." echo "WARNING: Build-Output enthält möglicherweise Fehler oder Warnungen."
echo "Bitte prüfen Sie die Ausgabe oben." echo "Bitte prüfen Sie die Ausgabe oben."
echo "Build-Log: $BUILD_LOG_FILE"
fi fi
# Prüfe, ob der Build erfolgreich war - mehrere Checks # Prüfe, ob der Build erfolgreich war - mehrere Checks

View File

@@ -1,19 +1,12 @@
// https://nuxt.com/docs/api/configuration/nuxt-config // https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({ export default defineNuxtConfig({
devtools: { enabled: process.env.NODE_ENV !== 'production' }, devtools: { enabled: true },
modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt'], modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt'],
nitro: { nitro: {
preset: 'node-server', preset: 'node-server',
dev: process.env.NODE_ENV !== 'production', dev: process.env.NODE_ENV !== 'production'
sourceMap: false
},
vite: {
build: {
reportCompressedSize: false
}
}, },
// Erzwinge Dev-Port und Host zuverlässig für `npm run dev` // Erzwinge Dev-Port und Host zuverlässig für `npm run dev`

12
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "harheimertc-website", "name": "harheimertc-website",
"version": "1.1.4", "version": "1.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "harheimertc-website", "name": "harheimertc-website",
"version": "1.1.4", "version": "1.1.0",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@pinia/nuxt": "^0.11.2", "@pinia/nuxt": "^0.11.2",
@@ -36,7 +36,7 @@
"eslint-plugin-vue": "^10.6.2", "eslint-plugin-vue": "^10.6.2",
"globals": "^16.5.0", "globals": "^16.5.0",
"lucide-vue-next": "^0.344.0", "lucide-vue-next": "^0.344.0",
"postcss": "^8.5.12", "postcss": "^8.4.0",
"supertest": "^7.1.0", "supertest": "^7.1.0",
"tailwindcss": "^3.4.0", "tailwindcss": "^3.4.0",
"vitest": "^4.0.16", "vitest": "^4.0.16",
@@ -11366,9 +11366,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.12", "version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",

View File

@@ -1,6 +1,6 @@
{ {
"name": "harheimertc-website", "name": "harheimertc-website",
"version": "1.1.6", "version": "1.1.2",
"description": "Moderne Webseite für den Harheimer Tischtennis Club", "description": "Moderne Webseite für den Harheimer Tischtennis Club",
"private": true, "private": true,
"type": "module", "type": "module",
@@ -50,7 +50,7 @@
"eslint-plugin-vue": "^10.6.2", "eslint-plugin-vue": "^10.6.2",
"globals": "^16.5.0", "globals": "^16.5.0",
"lucide-vue-next": "^0.344.0", "lucide-vue-next": "^0.344.0",
"postcss": "^8.5.12", "postcss": "^8.4.0",
"supertest": "^7.1.0", "supertest": "^7.1.0",
"tailwindcss": "^3.4.0", "tailwindcss": "^3.4.0",
"vitest": "^4.0.16", "vitest": "^4.0.16",

View File

@@ -106,31 +106,9 @@
<!-- Active Users --> <!-- Active Users -->
<div> <div>
<div class="flex flex-col gap-3 mb-4 sm:flex-row sm:items-end sm:justify-between"> <h2 class="text-2xl font-display font-bold text-gray-900 mb-4">
<h2 class="text-2xl font-display font-bold text-gray-900"> Aktive Benutzer ({{ activeUsers.length }})
Aktive Benutzer ({{ sortedActiveUsers.length }}) </h2>
</h2>
<div class="flex items-center gap-2">
<label
for="user-sort-order"
class="text-sm font-medium text-gray-700"
>
Sortierung
</label>
<select
id="user-sort-order"
v-model="nameSortMode"
class="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-primary-600"
>
<option value="firstLast">
Vorname Nachname
</option>
<option value="lastFirst">
Nachname, Vorname
</option>
</select>
</div>
</div>
<div class="bg-white rounded-xl shadow-lg overflow-hidden"> <div class="bg-white rounded-xl shadow-lg overflow-hidden">
<table class="min-w-full divide-y divide-gray-200"> <table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50"> <thead class="bg-gray-50">
@@ -157,13 +135,13 @@
</thead> </thead>
<tbody class="bg-white divide-y divide-gray-200"> <tbody class="bg-white divide-y divide-gray-200">
<tr <tr
v-for="user in sortedActiveUsers" v-for="user in activeUsers"
:key="user.id" :key="user.id"
class="hover:bg-gray-50" class="hover:bg-gray-50"
> >
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900"> <div class="text-sm font-medium text-gray-900">
{{ getDisplayName(user) }} {{ user.name }}
</div> </div>
</td> </td>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
@@ -275,7 +253,7 @@
> >
<div class="bg-white rounded-xl shadow-2xl max-w-md w-full p-6"> <div class="bg-white rounded-xl shadow-2xl max-w-md w-full p-6">
<h2 class="text-2xl font-display font-bold text-gray-900 mb-4"> <h2 class="text-2xl font-display font-bold text-gray-900 mb-4">
Rollen bearbeiten: {{ getDisplayName(editingUser) }} Rollen bearbeiten: {{ editingUser.name }}
</h2> </h2>
<div class="space-y-3 mb-6"> <div class="space-y-3 mb-6">
@@ -372,7 +350,6 @@ const errorMessage = ref('')
const showRoleModal = ref(false) const showRoleModal = ref(false)
const editingUser = ref(null) const editingUser = ref(null)
const selectedRoles = ref([]) const selectedRoles = ref([])
const nameSortMode = ref('firstLast')
const pendingUsers = computed(() => { const pendingUsers = computed(() => {
return allUsers.value return allUsers.value
@@ -387,61 +364,6 @@ const activeUsers = computed(() => {
return allUsers.value.filter(u => u.active === true) return allUsers.value.filter(u => u.active === true)
}) })
const splitNameParts = (name = '') => {
const trimmed = (name || '').trim()
if (!trimmed) {
return { firstName: '', lastName: '' }
}
if (trimmed.includes(',')) {
const [lastNameRaw, ...firstNameRaw] = trimmed.split(',')
return {
firstName: firstNameRaw.join(',').trim(),
lastName: (lastNameRaw || '').trim()
}
}
const parts = trimmed.split(/\s+/).filter(Boolean)
if (parts.length <= 1) {
return { firstName: parts[0] || '', lastName: '' }
}
return {
firstName: parts[0],
lastName: parts.slice(1).join(' ')
}
}
const getDisplayName = (user) => {
const { firstName, lastName } = splitNameParts(user?.name || '')
if (nameSortMode.value === 'lastFirst') {
if (!lastName) {
return firstName
}
return `${lastName}, ${firstName}`.trim()
}
return `${firstName} ${lastName}`.trim()
}
const sortedActiveUsers = computed(() => {
return [...activeUsers.value].sort((a, b) => {
const nameA = splitNameParts(a.name)
const nameB = splitNameParts(b.name)
if (nameSortMode.value === 'lastFirst') {
const lastNameCompare = nameA.lastName.localeCompare(nameB.lastName, 'de', { sensitivity: 'base' })
if (lastNameCompare !== 0) return lastNameCompare
return nameA.firstName.localeCompare(nameB.firstName, 'de', { sensitivity: 'base' })
}
const firstNameCompare = nameA.firstName.localeCompare(nameB.firstName, 'de', { sensitivity: 'base' })
if (firstNameCompare !== 0) return firstNameCompare
return nameA.lastName.localeCompare(nameB.lastName, 'de', { sensitivity: 'base' })
})
})
const formatDate = (dateString) => { const formatDate = (dateString) => {
return new Date(dateString).toLocaleString('de-DE', { return new Date(dateString).toLocaleString('de-DE', {
year: 'numeric', year: 'numeric',

View File

@@ -3,24 +3,9 @@ import path from 'path'
import { getUserFromToken } from '../../utils/auth.js' import { getUserFromToken } from '../../utils/auth.js'
async function readPackageVersion() { async function readPackageVersion() {
const cwd = process.cwd() const packageJsonPath = path.join(process.cwd(), 'package.json')
const candidatePaths = [ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'))
path.join(cwd, 'package.json'), return String(packageJson.version || '')
path.join(cwd, '../package.json')
]
for (const packageJsonPath of candidatePaths) {
try {
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'))
if (packageJson?.version) {
return String(packageJson.version)
}
} catch (_error) {
// Try next candidate path (e.g. .output runtime)
}
}
return ''
} }
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {

View File

@@ -15,11 +15,8 @@ export default defineEventHandler(async (event) => {
const cwd = process.cwd() const cwd = process.cwd()
const filename = 'mannschaften.csv' const filename = 'mannschaften.csv'
// Prefer CMS write target first (server/data/public-data), // Prefer server/data, then .output/public/data, then public/data
// then legacy locations.
const candidates = [ const candidates = [
path.join(cwd, 'server/data/public-data', filename),
path.join(cwd, '../server/data/public-data', filename),
path.join(cwd, '.output/server/data', filename), path.join(cwd, '.output/server/data', filename),
path.join(cwd, 'server/data', filename), path.join(cwd, 'server/data', filename),
path.join(cwd, '.output/public/data', filename), path.join(cwd, '.output/public/data', filename),

View File

@@ -236,22 +236,6 @@ export async function getRecipientsByGroup(targetGroup) {
email: m.email, email: m.email,
name: `${m.firstName || ''} ${m.lastName || ''}`.trim() || m.name || '' name: `${m.firstName || ''} ${m.lastName || ''}`.trim() || m.name || ''
})) }))
// Zusätzlich aktive Trainer aus users.json anschreiben
users
.filter(u => {
if (!u.active || !u.email || !u.email.trim()) return false
const roles = Array.isArray(u.roles) ? u.roles : (u.role ? [u.role] : [])
return roles.includes('trainer')
})
.forEach(u => {
if (!recipients.find(r => r.email.toLowerCase().trim() === u.email.toLowerCase().trim())) {
recipients.push({
email: u.email.trim(),
name: u.name || ''
})
}
})
break break
case 'mannschaftsspieler': case 'mannschaftsspieler':