Add smart member list with manual+login merge and duplicate detection
This commit is contained in:
55
server/api/members.delete.js
Normal file
55
server/api/members.delete.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import { verifyToken, getUserById } from '../utils/auth.js'
|
||||
import { deleteMember } from '../utils/members.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 user = await getUserById(decoded.id)
|
||||
|
||||
// Only admin and vorstand can delete members
|
||||
if (!user || (user.role !== 'admin' && user.role !== 'vorstand')) {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
message: 'Keine Berechtigung zum Löschen von Mitgliedern.'
|
||||
})
|
||||
}
|
||||
|
||||
const body = await readBody(event)
|
||||
const { id } = body
|
||||
|
||||
if (!id) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: 'Mitglieds-ID ist erforderlich.'
|
||||
})
|
||||
}
|
||||
|
||||
await deleteMember(id)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Mitglied erfolgreich gelöscht.'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Löschen des Mitglieds:', error)
|
||||
throw error
|
||||
}
|
||||
})
|
||||
|
||||
113
server/api/members.get.js
Normal file
113
server/api/members.get.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import { verifyToken } from '../utils/auth.js'
|
||||
import { readMembers, readUsers } from '../utils/members.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.'
|
||||
})
|
||||
}
|
||||
|
||||
// Get manual members and registered users
|
||||
const manualMembers = await readMembers()
|
||||
const registeredUsers = await readUsers()
|
||||
|
||||
// Merge members: combine manual + registered, detect duplicates
|
||||
const mergedMembers = []
|
||||
const processedEmails = new Set()
|
||||
const processedNames = new Set()
|
||||
|
||||
// First, add all manual members
|
||||
for (const member of manualMembers) {
|
||||
const normalizedEmail = member.email?.toLowerCase().trim() || ''
|
||||
const normalizedName = member.name?.toLowerCase().trim() || ''
|
||||
|
||||
mergedMembers.push({
|
||||
...member,
|
||||
source: 'manual',
|
||||
editable: true,
|
||||
hasLogin: false
|
||||
})
|
||||
|
||||
if (normalizedEmail) processedEmails.add(normalizedEmail)
|
||||
if (normalizedName) processedNames.add(normalizedName)
|
||||
}
|
||||
|
||||
// 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
|
||||
let matchedManualIndex = -1
|
||||
|
||||
// Try to match by email first
|
||||
if (normalizedEmail) {
|
||||
matchedManualIndex = mergedMembers.findIndex(
|
||||
m => m.source === 'manual' && m.email?.toLowerCase().trim() === normalizedEmail
|
||||
)
|
||||
}
|
||||
|
||||
// If no email match, try name
|
||||
if (matchedManualIndex === -1 && normalizedName) {
|
||||
matchedManualIndex = mergedMembers.findIndex(
|
||||
m => m.source === 'manual' && m.name?.toLowerCase().trim() === normalizedName
|
||||
)
|
||||
}
|
||||
|
||||
if (matchedManualIndex !== -1) {
|
||||
// Merge with existing manual member
|
||||
mergedMembers[matchedManualIndex] = {
|
||||
...mergedMembers[matchedManualIndex],
|
||||
hasLogin: true,
|
||||
loginEmail: user.email,
|
||||
loginRole: user.role,
|
||||
lastLogin: user.lastLogin
|
||||
}
|
||||
} else {
|
||||
// Add as new member (from login system)
|
||||
mergedMembers.push({
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
phone: user.phone || '',
|
||||
address: '',
|
||||
notes: `Rolle: ${user.role}`,
|
||||
source: 'login',
|
||||
editable: false,
|
||||
hasLogin: true,
|
||||
loginEmail: user.email,
|
||||
loginRole: user.role,
|
||||
lastLogin: user.lastLogin
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by name
|
||||
mergedMembers.sort((a, b) => a.name.localeCompare(b.name))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
members: mergedMembers
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Mitgliederliste:', error)
|
||||
throw error
|
||||
}
|
||||
})
|
||||
|
||||
62
server/api/members.post.js
Normal file
62
server/api/members.post.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import { verifyToken, getUserById } from '../utils/auth.js'
|
||||
import { saveMember } from '../utils/members.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 user = await getUserById(decoded.id)
|
||||
|
||||
// Only admin and vorstand can edit members
|
||||
if (!user || (user.role !== 'admin' && user.role !== 'vorstand')) {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
message: 'Keine Berechtigung zum Bearbeiten von Mitgliedern.'
|
||||
})
|
||||
}
|
||||
|
||||
const body = await readBody(event)
|
||||
const { id, name, email, phone, address, notes } = body
|
||||
|
||||
if (!name) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: 'Name ist erforderlich.'
|
||||
})
|
||||
}
|
||||
|
||||
await saveMember({
|
||||
id: id || undefined,
|
||||
name,
|
||||
email: email || '',
|
||||
phone: phone || '',
|
||||
address: address || '',
|
||||
notes: notes || ''
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Mitglied erfolgreich gespeichert.'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Speichern des Mitglieds:', error)
|
||||
throw error
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
[
|
||||
{
|
||||
"id": "1",
|
||||
"name": "Admin",
|
||||
"email": "admin@harheimertc.de",
|
||||
"phone": "069-12345678",
|
||||
"role": "admin",
|
||||
"memberSince": "2020-01-01",
|
||||
"active": true
|
||||
"id": "m1",
|
||||
"name": "Max Mustermann",
|
||||
"email": "max@example.com",
|
||||
"phone": "069 123456",
|
||||
"address": "Musterstraße 1, 60437 Frankfurt",
|
||||
"notes": "Herren 1"
|
||||
},
|
||||
{
|
||||
"id": "m2",
|
||||
"name": "Anna Schmidt",
|
||||
"email": "",
|
||||
"phone": "069 234567",
|
||||
"address": "Hauptstraße 5, 60437 Frankfurt",
|
||||
"notes": "Damen"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
97
server/utils/members.js
Normal file
97
server/utils/members.js
Normal file
@@ -0,0 +1,97 @@
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
// Handle both dev and production paths
|
||||
const getDataPath = (filename) => {
|
||||
const cwd = process.cwd()
|
||||
|
||||
// In production (.output/server), working dir is .output
|
||||
if (cwd.endsWith('.output')) {
|
||||
return path.join(cwd, '../server/data', filename)
|
||||
}
|
||||
|
||||
// In development, working dir is project root
|
||||
return path.join(cwd, 'server/data', filename)
|
||||
}
|
||||
|
||||
const MEMBERS_FILE = getDataPath('members.json')
|
||||
const USERS_FILE = getDataPath('users.json')
|
||||
|
||||
// Read manual members from file
|
||||
export async function readMembers() {
|
||||
try {
|
||||
const data = await fs.readFile(MEMBERS_FILE, 'utf-8')
|
||||
return JSON.parse(data)
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return []
|
||||
}
|
||||
console.error('Fehler beim Lesen der Mitgliederdaten:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// Write manual members to file
|
||||
export async function writeMembers(members) {
|
||||
try {
|
||||
await fs.writeFile(MEMBERS_FILE, JSON.stringify(members, null, 2), 'utf-8')
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Schreiben der Mitgliederdaten:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Read registered users from file
|
||||
export async function readUsers() {
|
||||
try {
|
||||
const data = await fs.readFile(USERS_FILE, 'utf-8')
|
||||
return JSON.parse(data)
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return []
|
||||
}
|
||||
console.error('Fehler beim Lesen der Benutzerdaten:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// Get member by ID
|
||||
export async function getMemberById(id) {
|
||||
const members = await readMembers()
|
||||
return members.find(m => m.id === id)
|
||||
}
|
||||
|
||||
// Add or update manual member
|
||||
export async function saveMember(memberData) {
|
||||
const members = await readMembers()
|
||||
|
||||
if (memberData.id) {
|
||||
// Update existing
|
||||
const index = members.findIndex(m => m.id === memberData.id)
|
||||
if (index !== -1) {
|
||||
members[index] = { ...members[index], ...memberData }
|
||||
} else {
|
||||
throw new Error('Mitglied nicht gefunden')
|
||||
}
|
||||
} else {
|
||||
// Add new
|
||||
const newMember = {
|
||||
id: `m${Date.now()}`,
|
||||
...memberData
|
||||
}
|
||||
members.push(newMember)
|
||||
}
|
||||
|
||||
await writeMembers(members)
|
||||
return true
|
||||
}
|
||||
|
||||
// Delete manual member
|
||||
export async function deleteMember(id) {
|
||||
const members = await readMembers()
|
||||
const filtered = members.filter(m => m.id !== id)
|
||||
await writeMembers(filtered)
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user