import Member from '../../models/member.js' import Connection from '../../models/connection.js' import { requireAuth } from '../../utils/auth.js' export default defineEventHandler(async (event) => { const member = await requireAuth(event) const memberId = member._id const topics = member.communityConnections?.topics || [] if (!topics.length) { return { suggestions: [] } } const query = getQuery(event) const filterTag = query.tag || null const filterState = query.state || null const showHidden = query.showHidden === 'true' // Build the set of tag slugs to match against let myTopics = topics if (filterTag) { myTopics = myTopics.filter(t => t.tagSlug === filterTag) } if (filterState) { myTopics = myTopics.filter(t => t.state === filterState) } if (!myTopics.length) { return { suggestions: [] } } const mySlugs = myTopics.map(t => t.tagSlug) // Find active members sharing at least one topic slug const candidates = await Member.find({ _id: { $ne: memberId }, status: 'active', 'communityConnections.topics.tagSlug': { $in: mySlugs } }) .select('name avatar craftTags circle communityConnections privacy') .lean() if (!candidates.length) { return { suggestions: [] } } const candidateIds = candidates.map(c => c._id) // Find existing connections (pending or confirmed) to exclude const existingConnections = await Connection.find({ $or: [ { initiator: memberId, recipient: { $in: candidateIds } }, { recipient: memberId, initiator: { $in: candidateIds } } ] }) .select('initiator recipient hiddenBy status') .lean() // Build sets for exclusion const excludeIds = new Set() for (const conn of existingConnections) { const otherId = conn.initiator.toString() === memberId.toString() ? conn.recipient.toString() : conn.initiator.toString() // Exclude if confirmed or pending connection exists if (conn.status === 'confirmed' || conn.status === 'pending') { excludeIds.add(otherId) } // Exclude if current member has hidden this connection (unless showHidden) if (!showHidden && conn.hiddenBy?.some(id => id.toString() === memberId.toString())) { excludeIds.add(otherId) } } // Build topic lookup for current member (using filtered topics) const myTopicMap = {} for (const t of myTopics) { myTopicMap[t.tagSlug] = t.state } // Compute suggestions const suggestions = [] for (const candidate of candidates) { if (excludeIds.has(candidate._id.toString())) continue const theirTopics = candidate.communityConnections?.topics || [] const matchingTags = [] for (const theirTopic of theirTopics) { const myState = myTopicMap[theirTopic.tagSlug] if (!myState) continue matchingTags.push({ tagSlug: theirTopic.tagSlug, yourState: myState, theirState: theirTopic.state }) } if (!matchingTags.length) continue // Apply privacy filtering — only expose fields the member allows for other members const privacy = candidate.privacy || {} const filtered = { _id: candidate._id, name: candidate.name, circle: candidate.circle, } const avatarPrivacy = privacy.avatar || 'public' if (avatarPrivacy === 'public' || avatarPrivacy === 'members') { filtered.avatar = candidate.avatar } const craftTagsPrivacy = privacy.craftTags || 'members' if (craftTagsPrivacy === 'public' || craftTagsPrivacy === 'members') { filtered.craftTags = candidate.craftTags } suggestions.push({ member: filtered, matchingTags, matchCount: matchingTags.length }) } // Sort by overlap count descending suggestions.sort((a, b) => b.matchCount - a.matchCount) return { suggestions } })