import jwt from "jsonwebtoken"; import Member from "../../models/member.js"; import Tag from "../../models/tag.js"; import { connectDB } from "../../utils/mongoose.js"; export default defineEventHandler(async (event) => { await connectDB(); // Check if user is authenticated const token = getCookie(event, "auth-token"); let isAuthenticated = false; if (token) { try { jwt.verify(token, useRuntimeConfig().jwtSecret); isAuthenticated = true; } catch (err) { isAuthenticated = false; } } const query = getQuery(event); const search = query.search || ""; const circle = query.circle || ""; const peerSupport = query.peerSupport || ""; const craftTag = query.craftTag || ""; const connectionTag = query.connectionTag || ""; const dbQuery = { showInDirectory: true, status: "active", }; if (circle) { dbQuery.circle = circle; } const andConditions = []; if (peerSupport === "true") { dbQuery["communityEcology.offerPeerSupport"] = true; } if (search) { // Escape regex metacharacters to prevent ReDoS const escaped = search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); andConditions.push({ $or: [ { name: { $regex: escaped, $options: "i" } }, { bio: { $regex: escaped, $options: "i" } }, ], }); } if (craftTag) { dbQuery.craftTags = craftTag; } if (connectionTag) { dbQuery["communityEcology.topics.tagSlug"] = connectionTag; } if (andConditions.length > 0) { dbQuery.$and = andConditions; } try { const members = await Member.find(dbQuery) .select( "name pronouns timeZone avatar studio bio location socialLinks privacy circle craftTags communityEcology createdAt", ) .sort({ createdAt: -1 }) .lean(); const filteredMembers = members.map((member) => { const privacy = member.privacy || {}; const filtered = { _id: member._id, name: member.name, circle: member.circle, createdAt: member.createdAt, }; const isVisible = (field) => { const privacySetting = privacy[field] || "members"; if (privacySetting === "public") return true; if (privacySetting === "members" && isAuthenticated) return true; return false; }; if (isVisible("avatar")) filtered.avatar = member.avatar; if (isVisible("pronouns")) filtered.pronouns = member.pronouns; if (isVisible("timeZone")) filtered.timeZone = member.timeZone; if (isVisible("studio")) filtered.studio = member.studio; if (isVisible("bio")) filtered.bio = member.bio; if (isVisible("location")) filtered.location = member.location; if (isVisible("socialLinks")) filtered.socialLinks = member.socialLinks; if (isVisible("craftTags")) filtered.craftTags = member.craftTags; if (isVisible("communityEcology")) { const ecology = member.communityEcology || {}; filtered.communityEcology = { topics: ecology.topics, offerPeerSupport: ecology.offerPeerSupport, availability: ecology.availability, ...(ecology.offerPeerSupport && { slackHandle: ecology.slackHandle, }), }; } return filtered; }); const [craftTags, cooperativeTags] = await Promise.all([ Tag.find({ pool: "craft", active: true }).sort({ label: 1 }).lean(), Tag.find({ pool: "cooperative", active: true }).sort({ label: 1 }).lean(), ]); return { members: filteredMembers, totalCount: filteredMembers.length, filters: { craftTags: craftTags.map((t) => ({ slug: t.slug, label: t.label })), cooperativeTags: cooperativeTags.map((t) => ({ slug: t.slug, label: t.label, })), }, }; } catch (error) { console.error("Directory fetch error:", error); throw createError({ statusCode: 500, message: "Failed to fetch member directory", }); } });