Füge Geburtstags-Widget hinzu und implementiere Geburtstagsladefunktion; erweitere Sichtbarkeitseinstellungen für Geburtstage in Profil und API
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 49s
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 49s
This commit is contained in:
90
server/api/birthdays.get.js
Normal file
90
server/api/birthdays.get.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import { readMembers, normalizeDate } from '../utils/members.js'
|
||||
import { readUsers, migrateUserRoles, getUserFromToken, verifyToken } from '../utils/auth.js'
|
||||
|
||||
// Helper: returns array of upcoming birthdays within daysAhead (inclusive)
|
||||
function getUpcomingBirthdays(entries, daysAhead = 28) {
|
||||
const now = new Date()
|
||||
const results = []
|
||||
|
||||
// iterate entries with geburtsdatum and name
|
||||
for (const e of entries) {
|
||||
const raw = e.geburtsdatum
|
||||
if (!raw) continue
|
||||
const parsed = new Date(raw)
|
||||
if (isNaN(parsed.getTime())) continue
|
||||
|
||||
// Build next occurrence for this year
|
||||
const thisYear = now.getFullYear()
|
||||
const occ = new Date(thisYear, parsed.getMonth(), parsed.getDate())
|
||||
|
||||
// If already passed this year, consider next year
|
||||
if (occ < now) {
|
||||
occ.setFullYear(thisYear + 1)
|
||||
}
|
||||
|
||||
const diffDays = Math.ceil((occ - now) / (1000 * 60 * 60 * 24))
|
||||
if (diffDays >= 0 && diffDays <= daysAhead) {
|
||||
results.push({
|
||||
name: e.name || `${e.firstName || ''} ${e.lastName || ''}`.trim(),
|
||||
dayMonth: `${String(occ.getDate()).padStart(2, '0')}.${String(occ.getMonth()+1).padStart(2, '0')}`,
|
||||
date: occ,
|
||||
diffDays
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by upcoming date
|
||||
results.sort((a, b) => a.date - b.date)
|
||||
return results
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
// Determine viewer for visibility rules; token optional
|
||||
const token = getCookie(event, 'auth_token')
|
||||
let currentUser = null
|
||||
if (token) {
|
||||
const decoded = verifyToken(token)
|
||||
if (decoded) {
|
||||
currentUser = await getUserFromToken(token)
|
||||
}
|
||||
}
|
||||
|
||||
const manualMembers = await readMembers()
|
||||
const registeredUsers = await readUsers()
|
||||
|
||||
// Build unified list of candidates with geburtsdatum and visibility
|
||||
const candidates = []
|
||||
|
||||
for (const m of manualMembers) {
|
||||
const isAccepted = m.active === true || (m.status && String(m.status).toLowerCase() === 'accepted') || m.accepted === true
|
||||
if (!isAccepted) continue
|
||||
const vis = m.visibility || {}
|
||||
const showBirthday = vis.showBirthday === undefined ? true : Boolean(vis.showBirthday)
|
||||
candidates.push({ name: `${m.firstName || ''} ${m.lastName || ''}`.trim(), geburtsdatum: m.geburtsdatum, visibility: { showBirthday }, source: 'manual' })
|
||||
}
|
||||
|
||||
for (const u of registeredUsers) {
|
||||
if (!u.active) continue
|
||||
const vis = u.visibility || {}
|
||||
const showBirthday = vis.showBirthday === undefined ? true : Boolean(vis.showBirthday)
|
||||
candidates.push({ name: u.name, geburtsdatum: u.geburtsdatum, visibility: { showBirthday }, source: 'login' })
|
||||
}
|
||||
|
||||
// Respect visibility: if viewer is vorstand they see all birthdays
|
||||
const isPrivilegedViewer = currentUser ? (Array.isArray(currentUser.roles) ? currentUser.roles.includes('vorstand') : currentUser.role === 'vorstand') : false
|
||||
|
||||
const filtered = candidates.filter(c => c.geburtsdatum && (isPrivilegedViewer || c.visibility.showBirthday === true))
|
||||
|
||||
const upcoming = getUpcomingBirthdays(filtered, 28)
|
||||
|
||||
// Return only next 4 weeks entries with name and dayMonth
|
||||
return {
|
||||
success: true,
|
||||
birthdays: upcoming.map(b => ({ name: b.name, dayMonth: b.dayMonth, inDays: b.diffDays }))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Geburtstage:', error)
|
||||
throw error
|
||||
}
|
||||
})
|
||||
@@ -194,9 +194,11 @@ export default defineEventHandler(async (event) => {
|
||||
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 {
|
||||
@@ -214,6 +216,18 @@ export default defineEventHandler(async (event) => {
|
||||
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,
|
||||
// Flag for UI: data existed but is hidden to the current viewer
|
||||
contactHidden
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export default defineEventHandler(async (event) => {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
phone: user.phone || '',
|
||||
visibility: user.visibility || {}
|
||||
visibility: Object.assign({ showBirthday: true }, (user.visibility || {}))
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -60,7 +60,7 @@ export default defineEventHandler(async (event) => {
|
||||
user.phone = phone || ''
|
||||
|
||||
// Optional visibility preferences (what to show to other logged-in members)
|
||||
// Expected shape: { showEmail: boolean, showPhone: boolean, showAddress: boolean }
|
||||
// Expected shape: { showEmail: boolean, showPhone: boolean, showAddress: boolean, showBirthday: boolean }
|
||||
const visibility = body.visibility || body.visibilityPreferences || null
|
||||
if (visibility && typeof visibility === 'object') {
|
||||
user.visibility = user.visibility || {}
|
||||
@@ -68,6 +68,7 @@ export default defineEventHandler(async (event) => {
|
||||
if (visibility.showEmail !== undefined) user.visibility.showEmail = Boolean(visibility.showEmail)
|
||||
if (visibility.showPhone !== undefined) user.visibility.showPhone = Boolean(visibility.showPhone)
|
||||
if (visibility.showAddress !== undefined) user.visibility.showAddress = Boolean(visibility.showAddress)
|
||||
if (visibility.showBirthday !== undefined) user.visibility.showBirthday = Boolean(visibility.showBirthday)
|
||||
}
|
||||
|
||||
// Handle password change
|
||||
|
||||
Reference in New Issue
Block a user