265 lines
11 KiB
JavaScript
265 lines
11 KiB
JavaScript
import { verifyToken, getUserFromToken, hasRole } from '../utils/auth.js'
|
|
import { readMembers } from '../utils/members.js'
|
|
import { readUsers, migrateUserRoles } from '../utils/auth.js'
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
try {
|
|
const token = getCookie(event, 'auth_token')
|
|
|
|
if (!token) {
|
|
throw createError({
|
|
statusCode: 401,
|
|
message: 'Nicht authentifiziert.'
|
|
})
|
|
}
|
|
|
|
const decoded = verifyToken(token)
|
|
|
|
if (!decoded) {
|
|
throw createError({
|
|
statusCode: 401,
|
|
message: 'Ungültiges Token.'
|
|
})
|
|
}
|
|
|
|
const currentUser = await getUserFromToken(token)
|
|
|
|
// Get manual members and registered users
|
|
const manualMembers = await readMembers()
|
|
const registeredUsers = await readUsers()
|
|
|
|
// Debug: Log alle geladenen Mitglieder (decryptet)
|
|
console.log('--- DEBUG: Decrypted manualMembers ---')
|
|
if (Array.isArray(manualMembers)) {
|
|
for (const m of manualMembers) {
|
|
console.log(JSON.stringify(m, null, 2))
|
|
}
|
|
console.log('--- Gesamt:', manualMembers.length, 'Mitglieder ---')
|
|
} else {
|
|
console.log('manualMembers ist kein Array:', manualMembers)
|
|
}
|
|
|
|
// Merge members: combine manual + registered, detect duplicates
|
|
const mergedMembers = []
|
|
|
|
// Create lookup maps for O(1) matching instead of O(n) findIndex
|
|
const emailToIndexMap = new Map() // email -> index in mergedMembers
|
|
const nameToIndexMap = new Map() // name -> index in mergedMembers
|
|
|
|
// First, add manual members that are active/accepted (filter out pending applications)
|
|
for (let i = 0; i < manualMembers.length; i++) {
|
|
const member = manualMembers[i]
|
|
// Normalize acceptance flags: accept if member.active===true or member.status==='accepted' or member.accepted===true
|
|
const isAccepted = member.active === true || (member.status && String(member.status).toLowerCase() === 'accepted') || member.accepted === true
|
|
if (!isAccepted) {
|
|
// Skip applications that are not yet accepted
|
|
continue
|
|
}
|
|
const normalizedEmail = member.email?.toLowerCase().trim() || ''
|
|
const fullName = `${member.firstName || ''} ${member.lastName || ''}`.trim()
|
|
const normalizedName = fullName.toLowerCase()
|
|
|
|
const memberIndex = mergedMembers.length
|
|
// Ensure visibility flags are booleans for manual entries
|
|
const vis = member.visibility || {}
|
|
member.visibility = {
|
|
// Default: visible to all logged-in members unless explicitly hidden
|
|
showEmail: vis.showEmail === undefined ? true : Boolean(vis.showEmail),
|
|
showPhone: vis.showPhone === undefined ? true : Boolean(vis.showPhone),
|
|
// Address remains private by default
|
|
showAddress: vis.showAddress === undefined ? false : Boolean(vis.showAddress)
|
|
}
|
|
|
|
mergedMembers.push({
|
|
...member,
|
|
name: fullName, // Computed for display
|
|
source: 'manual',
|
|
editable: true,
|
|
hasLogin: false
|
|
})
|
|
|
|
// Build lookup maps (only for manual members)
|
|
if (normalizedEmail) {
|
|
// Only add if not already present (prefer first occurrence)
|
|
if (!emailToIndexMap.has(normalizedEmail)) {
|
|
emailToIndexMap.set(normalizedEmail, memberIndex)
|
|
}
|
|
}
|
|
if (normalizedName) {
|
|
// Only add if not already present (prefer first occurrence)
|
|
if (!nameToIndexMap.has(normalizedName)) {
|
|
nameToIndexMap.set(normalizedName, memberIndex)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then add registered users (only active ones)
|
|
for (const user of registeredUsers) {
|
|
if (!user.active) continue
|
|
|
|
const normalizedEmail = user.email?.toLowerCase().trim() || ''
|
|
const normalizedName = user.name?.toLowerCase().trim() || ''
|
|
|
|
// Check if this user matches an existing manual member using O(1) lookup
|
|
let matchedManualIndex = -1
|
|
|
|
// Try to match by email first (O(1) lookup)
|
|
if (normalizedEmail && emailToIndexMap.has(normalizedEmail)) {
|
|
matchedManualIndex = emailToIndexMap.get(normalizedEmail)
|
|
// Verify it's still a manual member (safety check)
|
|
if (mergedMembers[matchedManualIndex]?.source !== 'manual') {
|
|
matchedManualIndex = -1
|
|
}
|
|
}
|
|
|
|
// If no email match, try name (O(1) lookup)
|
|
if (matchedManualIndex === -1 && normalizedName && nameToIndexMap.has(normalizedName)) {
|
|
matchedManualIndex = nameToIndexMap.get(normalizedName)
|
|
// Verify it's still a manual member and email doesn't conflict (safety check)
|
|
const candidate = mergedMembers[matchedManualIndex]
|
|
if (candidate?.source === 'manual') {
|
|
// Additional safety: if candidate has email, make sure it doesn't conflict
|
|
const candidateEmail = candidate.email?.toLowerCase().trim() || ''
|
|
if (!candidateEmail || candidateEmail === normalizedEmail) {
|
|
// Safe to match by name
|
|
} else {
|
|
// Email mismatch - don't match by name alone
|
|
matchedManualIndex = -1
|
|
}
|
|
} else {
|
|
matchedManualIndex = -1
|
|
}
|
|
}
|
|
|
|
if (matchedManualIndex !== -1) {
|
|
// Merge with existing manual member
|
|
const migratedUser = migrateUserRoles({ ...user })
|
|
const roles = Array.isArray(migratedUser.roles) ? migratedUser.roles : (migratedUser.role ? [migratedUser.role] : ['mitglied'])
|
|
mergedMembers[matchedManualIndex] = {
|
|
...mergedMembers[matchedManualIndex],
|
|
hasLogin: true,
|
|
loginEmail: user.email,
|
|
loginRoles: roles,
|
|
loginRole: roles[0] || 'mitglied', // Rückwärtskompatibilität
|
|
lastLogin: user.lastLogin,
|
|
isMannschaftsspieler: user.isMannschaftsspieler === true || mergedMembers[matchedManualIndex].isMannschaftsspieler === true
|
|
}
|
|
// If the registered user has visibility preferences, apply them (coerce to booleans)
|
|
if (user.visibility && typeof user.visibility === 'object') {
|
|
const vis = mergedMembers[matchedManualIndex].visibility || {}
|
|
mergedMembers[matchedManualIndex].visibility = {
|
|
showEmail: user.visibility.showEmail === undefined ? Boolean(vis.showEmail) : Boolean(user.visibility.showEmail),
|
|
showPhone: user.visibility.showPhone === undefined ? Boolean(vis.showPhone) : Boolean(user.visibility.showPhone),
|
|
showAddress: user.visibility.showAddress === undefined ? Boolean(vis.showAddress) : Boolean(user.visibility.showAddress)
|
|
}
|
|
}
|
|
} else {
|
|
// Add as new member (from login system)
|
|
const migratedUser = migrateUserRoles({ ...user })
|
|
const roles = Array.isArray(migratedUser.roles) ? migratedUser.roles : (migratedUser.role ? [migratedUser.role] : ['mitglied'])
|
|
// Registered-only user: default to privacy-preserving visibility (hidden) unless user explicitly set visibility elsewhere
|
|
// Use stored visibility from user if present, otherwise default to false
|
|
const userVis = user.visibility || {}
|
|
mergedMembers.push({
|
|
id: user.id,
|
|
name: user.name,
|
|
email: user.email,
|
|
phone: user.phone || '',
|
|
address: '',
|
|
visibility: {
|
|
showEmail: userVis.showEmail === undefined ? true : Boolean(userVis.showEmail),
|
|
showPhone: userVis.showPhone === undefined ? true : Boolean(userVis.showPhone),
|
|
showAddress: userVis.showAddress === undefined ? false : Boolean(userVis.showAddress)
|
|
},
|
|
notes: `Rolle(n): ${roles.join(', ')}`,
|
|
source: 'login',
|
|
editable: false,
|
|
hasLogin: true,
|
|
loginEmail: user.email,
|
|
loginRoles: roles,
|
|
loginRole: roles[0] || 'mitglied', // Rückwärtskompatibilität
|
|
lastLogin: user.lastLogin,
|
|
isMannschaftsspieler: user.isMannschaftsspieler === true
|
|
})
|
|
}
|
|
}
|
|
|
|
// Sort by name
|
|
mergedMembers.sort((a, b) => a.name.localeCompare(b.name))
|
|
// Die Mitgliederliste ist nur für authentifizierte Nutzer sichtbar (siehe oben).
|
|
// Respektiere individuelle Sichtbarkeitspräferenzen (user.visibility)
|
|
const currentUserToken = token
|
|
const isViewerAuthenticated = !!currentUser
|
|
// Only 'vorstand' may override member visibility
|
|
const isPrivilegedViewer = currentUser ? hasRole(currentUser, 'vorstand') : false
|
|
|
|
// Filtere den Admin-Account heraus
|
|
const filteredMembers = mergedMembers.filter(m => m.email?.toLowerCase() !== 'admin@harheimertc.de')
|
|
const sanitizedMembers = filteredMembers.map(member => {
|
|
// Default: show email/phone/address to other logged-in members unless member.visibility explicitly hides them
|
|
const visibility = member.visibility || {}
|
|
|
|
const showEmail = visibility.showEmail === undefined ? true : Boolean(visibility.showEmail)
|
|
const showPhone = visibility.showPhone === undefined ? true : Boolean(visibility.showPhone)
|
|
const showAddress = visibility.showAddress === undefined ? false : Boolean(visibility.showAddress)
|
|
|
|
// Determine if contact info existed but was hidden to the viewer
|
|
const hadEmail = !!member.email
|
|
const hadPhone = !!member.phone
|
|
const hadAddress = !!member.address
|
|
const hadBirthday = !!member.geburtsdatum
|
|
const emailVisible = (isPrivilegedViewer || (isViewerAuthenticated && showEmail))
|
|
const phoneVisible = (isPrivilegedViewer || (isViewerAuthenticated && showPhone))
|
|
const addressVisible = (isPrivilegedViewer || (isViewerAuthenticated && showAddress))
|
|
const birthdayVisible = (isPrivilegedViewer || (isViewerAuthenticated && (member.visibility && member.visibility.showBirthday !== undefined ? Boolean(member.visibility.showBirthday) : true)))
|
|
const contactHidden = (!emailVisible && hadEmail) || (!phoneVisible && hadPhone) || (!addressVisible && hadAddress)
|
|
|
|
return {
|
|
id: member.id,
|
|
name: member.name,
|
|
firstName: member.firstName || '',
|
|
lastName: member.lastName || '',
|
|
source: member.source,
|
|
editable: member.editable,
|
|
hasLogin: member.hasLogin,
|
|
loginRoles: member.loginRoles,
|
|
loginRole: member.loginRole,
|
|
lastLogin: member.lastLogin,
|
|
isMannschaftsspieler: member.isMannschaftsspieler,
|
|
notes: member.notes || '',
|
|
// Sichtbarkeits-Flags explizit mitgeben
|
|
showEmail: visibility.showEmail === undefined ? true : Boolean(visibility.showEmail),
|
|
showPhone: visibility.showPhone === undefined ? true : Boolean(visibility.showPhone),
|
|
showAddress: visibility.showAddress === undefined ? false : Boolean(visibility.showAddress),
|
|
showBirthday: visibility.showBirthday === undefined ? true : Boolean(visibility.showBirthday),
|
|
// Privileged viewers (vorstand) always see contact fields
|
|
email: emailVisible ? member.email : undefined,
|
|
phone: phoneVisible ? member.phone : undefined,
|
|
address: addressVisible ? member.address : undefined,
|
|
// Birthday: expose only day + month and only if allowed; do not expose year or age
|
|
birthday: (birthdayVisible && hadBirthday) ? (function(){
|
|
try {
|
|
const d = new Date(member.geburtsdatum)
|
|
if (isNaN(d.getTime())) return undefined
|
|
const day = `${d.getDate()}`.padStart(2, '0')
|
|
const month = `${d.getMonth()+1}`.padStart(2, '0')
|
|
return `${day}.${month}`
|
|
} catch (_e) {
|
|
return undefined
|
|
}
|
|
})() : undefined,
|
|
geburtsdatum: member.geburtsdatum || undefined // Originalfeld für das Edit-Formular
|
|
}
|
|
})
|
|
|
|
return {
|
|
success: true,
|
|
members: sanitizedMembers
|
|
}
|
|
} catch (error) {
|
|
console.error('Fehler beim Abrufen der Mitgliederliste:', error)
|
|
throw error
|
|
}
|
|
})
|
|
|