refactor(peer-support): delete provably dead code (Phase 1)
The Skills Exchange + Peer Support feature was replaced by Community
Connections on 2026-04-05, but several files and code paths were left
in place as backward-compat. None are reachable from the live UI:
- usePeerSupport.js composable: not imported anywhere
- PeerSupportBadge.vue: not imported anywhere
- peer-support.vue: stub redirect with no incoming links
- /api/peer-support.get.js: only consumed by usePeerSupport
- /api/members/me/peer-support.patch.js: same
- profile.patch.js offering/lookingFor write branches: profile form
no longer sends these fields (only writes communityConnections.*)
- PEER_SUPPORT_ENABLED/DISABLED activity types and renderers: only
written by the deleted peer-support.patch endpoint. The activityText
formatter has a fallback for unknown types so existing records
still display ("peer support enabled" with a generic icon).
Tests updated to drop peerSupportUpdateSchema coverage and the
offering/lookingFor passthrough assertion.
schemas.js cleanup deferred — concurrent communityConnections →
communityEcology rename is in flight in the working tree.
This commit is contained in:
parent
130e5bfa9f
commit
9577929e0d
11 changed files with 0 additions and 366 deletions
|
|
@ -1,101 +0,0 @@
|
|||
<template>
|
||||
<!-- Corner Sticker Badge -->
|
||||
<div
|
||||
v-if="type === 'sticker'"
|
||||
class="absolute top-2 right-2 z-10"
|
||||
:title="title"
|
||||
>
|
||||
<div
|
||||
class="relative transform rotate-3 hover:rotate-0 transition-transform"
|
||||
style="width: 60px; height: 66px"
|
||||
>
|
||||
<!-- Shield background -->
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 1000 1000"
|
||||
class="absolute inset-0 w-full h-full drop-shadow-lg"
|
||||
>
|
||||
<path
|
||||
d="M500 70 150 175.3v217.1C150 785 500 930 500 930s350-145 350-537.6V175.2L500 70Z"
|
||||
class="fill-candlelight-500"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<!-- Content on top of shield -->
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center">
|
||||
<Icon
|
||||
name="heroicons:chat-bubble-left-right-solid"
|
||||
class="w-6 h-6 text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Sparkle effect -->
|
||||
<div
|
||||
class="absolute top-0 right-1 w-2 h-2 bg-candlelight-300 rounded-full animate-pulse"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inline Badge -->
|
||||
<div
|
||||
v-else
|
||||
:class="[
|
||||
'inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full border text-xs font-medium transition-all',
|
||||
variant === 'default' &&
|
||||
'bg-candlelight-900/20 text-candlelight-400 border-candlelight-500/40 hover:bg-candlelight-900/30',
|
||||
variant === 'subtle' &&
|
||||
'bg-candlelight-900/10 text-candlelight-500 border-candlelight-500/20',
|
||||
variant === 'solid' &&
|
||||
'bg-candlelight-500 text-white border-candlelight-600 hover:bg-candlelight-600',
|
||||
]"
|
||||
:title="title"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:chat-bubble-left-right"
|
||||
:class="[
|
||||
'w-3.5 h-3.5',
|
||||
variant === 'default' && 'text-candlelight-400',
|
||||
variant === 'subtle' && 'text-candlelight-500',
|
||||
variant === 'solid' && 'text-white',
|
||||
]"
|
||||
/>
|
||||
<span>{{ label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
/**
|
||||
* Badge type - inline or corner sticker
|
||||
* @values inline, sticker
|
||||
*/
|
||||
type: {
|
||||
type: String,
|
||||
default: "inline",
|
||||
validator: (value) => ["inline", "sticker"].includes(value),
|
||||
},
|
||||
/**
|
||||
* Display variant of the badge (for inline type)
|
||||
* @values default, subtle, solid
|
||||
*/
|
||||
variant: {
|
||||
type: String,
|
||||
default: "default",
|
||||
validator: (value) => ["default", "subtle", "solid"].includes(value),
|
||||
},
|
||||
/**
|
||||
* Custom label text (defaults to "Offering Peer Support")
|
||||
*/
|
||||
label: {
|
||||
type: String,
|
||||
default: "Offering Peer Support",
|
||||
},
|
||||
/**
|
||||
* Tooltip/title text
|
||||
*/
|
||||
title: {
|
||||
type: String,
|
||||
default: "This member offers 1:1 peer support sessions",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
export const usePeerSupport = () => {
|
||||
const updateSettings = async (settings) => {
|
||||
return await $fetch('/api/members/me/peer-support', {
|
||||
method: 'PATCH',
|
||||
body: settings
|
||||
});
|
||||
};
|
||||
|
||||
const getSupporters = async (topic) => {
|
||||
return await $fetch('/api/peer-support', {
|
||||
query: topic ? { topic } : {}
|
||||
});
|
||||
};
|
||||
|
||||
return { updateSettings, getSupporters };
|
||||
};
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// Redirect to connections page
|
||||
definePageMeta({
|
||||
middleware: defineNuxtRouteMiddleware(() => {
|
||||
return navigateTo("/connections");
|
||||
}),
|
||||
});
|
||||
</script>
|
||||
|
|
@ -23,16 +23,6 @@ const formatters = {
|
|||
link: null,
|
||||
linkText: null
|
||||
}),
|
||||
peer_support_enabled: (m) => ({
|
||||
text: m.topics?.length
|
||||
? `Enabled peer support (${m.topics.join(', ')})`
|
||||
: 'Enabled peer support',
|
||||
icon: 'i-lucide-users'
|
||||
}),
|
||||
peer_support_disabled: () => ({
|
||||
text: 'Disabled peer support',
|
||||
icon: 'i-lucide-users'
|
||||
}),
|
||||
circle_changed: (m) => ({
|
||||
text: `Changed circle from ${circleLabel(m.from)} to ${circleLabel(m.to)}`,
|
||||
icon: 'i-lucide-refresh-cw'
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
import Member from '../../../models/member.js'
|
||||
import { connectDB } from '../../../utils/mongoose.js'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
await connectDB()
|
||||
const member = await requireAuth(event)
|
||||
|
||||
const body = await validateBody(event, peerSupportUpdateSchema)
|
||||
|
||||
// Build update object for peer support settings
|
||||
const updateData = {
|
||||
'peerSupport.enabled': body.enabled || false,
|
||||
'peerSupport.skillTopics': body.skillTopics || [],
|
||||
'peerSupport.supportTopics': body.supportTopics || [],
|
||||
'peerSupport.availability': body.availability || '',
|
||||
'peerSupport.personalMessage': body.personalMessage || '',
|
||||
'peerSupport.slackUsername': body.slackUsername || '',
|
||||
}
|
||||
|
||||
// If Slack username provided and peer support enabled, try to fetch Slack user ID
|
||||
if (body.enabled && body.slackUsername) {
|
||||
try {
|
||||
console.log(
|
||||
`[Peer Support] Attempting to fetch Slack user ID for: ${body.slackUsername}`,
|
||||
)
|
||||
|
||||
const { getSlackService } = await import('../../../utils/slack.ts')
|
||||
const slackService = getSlackService()
|
||||
|
||||
if (slackService) {
|
||||
console.log('[Peer Support] Slack service initialized, looking up user...')
|
||||
const slackUserId = await slackService.findUserIdByUsername(body.slackUsername)
|
||||
|
||||
if (slackUserId) {
|
||||
updateData['slackUserId'] = slackUserId
|
||||
console.log(
|
||||
`[Peer Support] ✓ Found Slack user ID for ${body.slackUsername}: ${slackUserId}`,
|
||||
)
|
||||
|
||||
console.log('[Peer Support] Opening DM channel...')
|
||||
const dmChannelId = await slackService.openDMChannel(slackUserId)
|
||||
|
||||
if (dmChannelId) {
|
||||
updateData['peerSupport.slackDMChannelId'] = dmChannelId
|
||||
console.log(`[Peer Support] ✓ Got DM channel ID: ${dmChannelId}`)
|
||||
} else {
|
||||
console.warn('[Peer Support] Could not get DM channel ID')
|
||||
}
|
||||
} else {
|
||||
console.warn(
|
||||
`[Peer Support] Could not find Slack user ID for username: ${body.slackUsername}`,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
console.log('[Peer Support] Slack service not configured, skipping user ID lookup')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Peer Support] Error fetching Slack user ID:', error.message)
|
||||
console.error('[Peer Support] Stack trace:', error.stack)
|
||||
// Continue anyway - we'll still save the username
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const updated = await Member.findByIdAndUpdate(
|
||||
member._id,
|
||||
{ $set: updateData },
|
||||
{ new: true, runValidators: true },
|
||||
)
|
||||
|
||||
if (!updated) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Member not found',
|
||||
})
|
||||
}
|
||||
|
||||
if (body.enabled) {
|
||||
logActivity(member._id, 'peer_support_enabled', {
|
||||
topics: [...(body.skillTopics || []), ...(body.supportTopics || [])]
|
||||
})
|
||||
} else {
|
||||
logActivity(member._id, 'peer_support_disabled', {})
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
peerSupport: updated.peerSupport,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Peer support update error:', error)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to update peer support settings',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
@ -31,8 +31,6 @@ export default defineEventHandler(async (event) => {
|
|||
"bioPrivacy",
|
||||
"locationPrivacy",
|
||||
"socialLinksPrivacy",
|
||||
"offeringPrivacy",
|
||||
"lookingForPrivacy",
|
||||
"craftTagsPrivacy",
|
||||
"communityConnectionsPrivacy",
|
||||
];
|
||||
|
|
@ -51,20 +49,6 @@ export default defineEventHandler(async (event) => {
|
|||
updateData.craftTags = body.craftTags;
|
||||
}
|
||||
|
||||
// Handle offering and lookingFor separately (nested objects)
|
||||
if (body.offering !== undefined) {
|
||||
updateData.offering = {
|
||||
text: body.offering.text || "",
|
||||
tags: body.offering.tags || [],
|
||||
};
|
||||
}
|
||||
if (body.lookingFor !== undefined) {
|
||||
updateData.lookingFor = {
|
||||
text: body.lookingFor.text || "",
|
||||
tags: body.lookingFor.tags || [],
|
||||
};
|
||||
}
|
||||
|
||||
// Handle privacy settings
|
||||
privacyFields.forEach((privacyField) => {
|
||||
if (body[privacyField] !== undefined) {
|
||||
|
|
@ -107,8 +91,6 @@ export default defineEventHandler(async (event) => {
|
|||
bio: member.bio,
|
||||
location: member.location,
|
||||
socialLinks: member.socialLinks,
|
||||
offering: member.offering,
|
||||
lookingFor: member.lookingFor,
|
||||
craftTags: member.craftTags,
|
||||
showInDirectory: member.showInDirectory,
|
||||
notifications: member.notifications,
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
import jwt from "jsonwebtoken";
|
||||
import Member from "../models/member.js";
|
||||
import { connectDB } from "../utils/mongoose.js";
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
await connectDB();
|
||||
|
||||
// Check if user is authenticated (optional for this endpoint)
|
||||
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 topic = query.topic;
|
||||
|
||||
// Build query for peer supporters
|
||||
const dbQuery = {
|
||||
"peerSupport.enabled": true,
|
||||
status: "active",
|
||||
};
|
||||
|
||||
// Filter by topic if specified
|
||||
if (topic) {
|
||||
dbQuery["peerSupport.topics"] = topic;
|
||||
}
|
||||
|
||||
try {
|
||||
const supporters = await Member.find(dbQuery)
|
||||
.select(
|
||||
"name avatar circle peerSupport slackUserId createdAt"
|
||||
)
|
||||
.sort({ createdAt: -1 })
|
||||
.lean();
|
||||
|
||||
// Get unique topics for filter options
|
||||
const allTopics = supporters
|
||||
.flatMap((supporter) => supporter.peerSupport?.topics || [])
|
||||
.filter((topic, index, self) => self.indexOf(topic) === index)
|
||||
.sort();
|
||||
|
||||
return {
|
||||
supporters,
|
||||
totalCount: supporters.length,
|
||||
filters: {
|
||||
availableTopics: allTopics,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Peer support fetch error:", error);
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: "Failed to fetch peer supporters",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -5,8 +5,6 @@ const ACTIVITY_TYPES = [
|
|||
'event_registered',
|
||||
'event_cancelled',
|
||||
'event_waitlisted',
|
||||
'peer_support_enabled',
|
||||
'peer_support_disabled',
|
||||
'circle_changed',
|
||||
'contribution_changed',
|
||||
'email_changed',
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ export const ACTIVITY_TYPES = {
|
|||
EVENT_REGISTERED: 'event_registered',
|
||||
EVENT_CANCELLED: 'event_cancelled',
|
||||
EVENT_WAITLISTED: 'event_waitlisted',
|
||||
PEER_SUPPORT_ENABLED: 'peer_support_enabled',
|
||||
PEER_SUPPORT_DISABLED: 'peer_support_disabled',
|
||||
CIRCLE_CHANGED: 'circle_changed',
|
||||
CONTRIBUTION_CHANGED: 'contribution_changed',
|
||||
EMAIL_CHANGED: 'email_changed',
|
||||
|
|
@ -29,8 +27,6 @@ export const ACTIVITY_TYPE_DEFAULTS = {
|
|||
event_registered: 'public',
|
||||
event_cancelled: 'member',
|
||||
event_waitlisted: 'member',
|
||||
peer_support_enabled: 'public',
|
||||
peer_support_disabled: 'member',
|
||||
circle_changed: 'member',
|
||||
contribution_changed: 'member',
|
||||
email_changed: 'member',
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@ describe('members profile PATCH endpoint', () => {
|
|||
bio: 'Updated bio',
|
||||
location: 'NYC',
|
||||
socialLinks: { twitter: '@test' },
|
||||
offering: { text: 'help', tags: ['code'] },
|
||||
lookingFor: { text: 'feedback', tags: ['design'] },
|
||||
showInDirectory: true
|
||||
}
|
||||
|
||||
|
|
@ -137,21 +135,5 @@ describe('members profile PATCH endpoint', () => {
|
|||
expect(setData).toHaveProperty('socialLinks')
|
||||
})
|
||||
|
||||
it('passes offering and lookingFor nested objects through', async () => {
|
||||
const event = createMockEvent({
|
||||
method: 'PATCH',
|
||||
path: '/api/members/profile',
|
||||
body: {
|
||||
offering: { text: 'mentoring', tags: ['code', 'design'] },
|
||||
lookingFor: { text: 'feedback', tags: ['art'] }
|
||||
}
|
||||
})
|
||||
|
||||
await profilePatchHandler(event)
|
||||
|
||||
const setData = Member.findByIdAndUpdate.mock.calls[0][1].$set
|
||||
expect(setData.offering).toEqual({ text: 'mentoring', tags: ['code', 'design'] })
|
||||
expect(setData.lookingFor).toEqual({ text: 'feedback', tags: ['art'] })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import {
|
|||
guestRegisterSchema,
|
||||
eventPaymentSchema,
|
||||
updateContributionSchema,
|
||||
peerSupportUpdateSchema,
|
||||
seriesTicketPurchaseSchema,
|
||||
seriesTicketEligibilitySchema,
|
||||
adminSeriesCreateSchema,
|
||||
|
|
@ -305,29 +304,6 @@ describe('updateContributionSchema', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('peerSupportUpdateSchema', () => {
|
||||
it('accepts valid peer support data', () => {
|
||||
const result = peerSupportUpdateSchema.safeParse({
|
||||
enabled: true,
|
||||
skillTopics: ['game design', 'business'],
|
||||
slackUsername: 'jane'
|
||||
})
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts empty object', () => {
|
||||
const result = peerSupportUpdateSchema.safeParse({})
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects non-array skillTopics', () => {
|
||||
const result = peerSupportUpdateSchema.safeParse({
|
||||
skillTopics: 'not-an-array'
|
||||
})
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
// --- Series schemas ---
|
||||
|
||||
describe('seriesTicketPurchaseSchema', () => {
|
||||
|
|
@ -505,7 +481,6 @@ describe('validateBody migration coverage', () => {
|
|||
'events/[id]/guest-register.post.js',
|
||||
'events/[id]/payment.post.js',
|
||||
'members/update-contribution.post.js',
|
||||
'members/me/peer-support.patch.js',
|
||||
'series/[id]/tickets/purchase.post.js',
|
||||
'series/[id]/tickets/check-eligibility.post.js',
|
||||
'admin/series.post.js',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue