Full-stack implementation across 7 sprints: - Backend: 5 Prisma models (StrawPoll, Option, Vote, Comment, Challenge), 4 enums, POLLS_ADMIN role, admin CRUD routes, public voting/SSE/widget endpoints, BullMQ auto-close queue, rate limiting - Admin: StrawPollsPage with inline drawers (campaigns pattern), PollResults bar chart, sidebar under Advocacy - Public: dedicated poll lander with real-time SSE updates, browse page, anonymous voting with token dedup - MkDocs: straw-poll-widget.js hydration (inline vote + card link modes), GrapesJS block types - Social: feed activity (poll_voted), friend badge integration, challenge notifications, notification preferences - Feature flag: enablePolls toggle in Settings, FeatureGate, Zod schema Bunker Admin
143 lines
4.4 KiB
TypeScript
143 lines
4.4 KiB
TypeScript
import { prisma } from '../../config/database';
|
|
import { friendshipService } from './friendship.service';
|
|
|
|
export const integrationService = {
|
|
/** Get friends who have signed up for a specific shift */
|
|
async getFriendsAttendingShift(userId: string, shiftId: string) {
|
|
const friendIds = await friendshipService.getFriendIds(userId);
|
|
if (friendIds.length === 0) return { friends: [], count: 0 };
|
|
|
|
// Filter out friends who hid their activity
|
|
const hiddenIds = await this.getHiddenActivityUserIds(friendIds);
|
|
const visibleIds = friendIds.filter((id) => !hiddenIds.has(id));
|
|
if (visibleIds.length === 0) return { friends: [], count: 0 };
|
|
|
|
const signups = await prisma.shiftSignup.findMany({
|
|
where: {
|
|
shiftId,
|
|
userId: { in: visibleIds },
|
|
status: 'CONFIRMED',
|
|
},
|
|
include: {
|
|
user: { select: { id: true, name: true, email: true } },
|
|
},
|
|
});
|
|
|
|
return {
|
|
friends: signups.map((s) => ({
|
|
id: s.user!.id,
|
|
name: s.user!.name,
|
|
email: s.user!.email,
|
|
})),
|
|
count: signups.length,
|
|
};
|
|
},
|
|
|
|
/** Get friends who participated in a specific campaign (sent emails) */
|
|
async getFriendsInCampaign(userId: string, campaignId: string) {
|
|
const friendIds = await friendshipService.getFriendIds(userId);
|
|
if (friendIds.length === 0) return { friends: [], count: 0 };
|
|
|
|
const hiddenIds = await this.getHiddenActivityUserIds(friendIds);
|
|
const visibleIds = friendIds.filter((id) => !hiddenIds.has(id));
|
|
if (visibleIds.length === 0) return { friends: [], count: 0 };
|
|
|
|
// Get distinct users who sent emails for this campaign
|
|
const emails = await prisma.campaignEmail.findMany({
|
|
where: {
|
|
campaignId,
|
|
userId: { in: visibleIds },
|
|
},
|
|
distinct: ['userId'],
|
|
include: {
|
|
user: { select: { id: true, name: true, email: true } },
|
|
},
|
|
});
|
|
|
|
return {
|
|
friends: emails
|
|
.filter((e) => e.user)
|
|
.map((e) => ({
|
|
id: e.user!.id,
|
|
name: e.user!.name,
|
|
email: e.user!.email,
|
|
})),
|
|
count: emails.filter((e) => e.user).length,
|
|
};
|
|
},
|
|
|
|
/** Get friends with active canvass sessions (currently canvassing) */
|
|
async getFriendsActiveOnMap(userId: string) {
|
|
const friendIds = await friendshipService.getFriendIds(userId);
|
|
if (friendIds.length === 0) return { friends: [], count: 0 };
|
|
|
|
const hiddenIds = await this.getHiddenActivityUserIds(friendIds);
|
|
const visibleIds = friendIds.filter((id) => !hiddenIds.has(id));
|
|
if (visibleIds.length === 0) return { friends: [], count: 0 };
|
|
|
|
const sessions = await prisma.canvassSession.findMany({
|
|
where: {
|
|
userId: { in: visibleIds },
|
|
status: 'ACTIVE',
|
|
},
|
|
include: {
|
|
user: { select: { id: true, name: true, email: true } },
|
|
cut: { select: { id: true, name: true } },
|
|
},
|
|
});
|
|
|
|
return {
|
|
friends: sessions.map((s) => ({
|
|
id: s.user.id,
|
|
name: s.user.name,
|
|
email: s.user.email,
|
|
cutName: s.cut.name,
|
|
cutId: s.cut.id,
|
|
startedAt: s.startedAt,
|
|
})),
|
|
count: sessions.length,
|
|
};
|
|
},
|
|
|
|
/** Get friends who voted on a straw poll */
|
|
async getFriendsInStrawPoll(userId: string, pollId: string) {
|
|
const friendIds = await friendshipService.getFriendIds(userId);
|
|
if (friendIds.length === 0) return { friends: [], count: 0 };
|
|
|
|
const hiddenIds = await this.getHiddenActivityUserIds(friendIds);
|
|
const visibleIds = friendIds.filter((id) => !hiddenIds.has(id));
|
|
if (visibleIds.length === 0) return { friends: [], count: 0 };
|
|
|
|
const votes = await prisma.strawPollVote.findMany({
|
|
where: {
|
|
pollId,
|
|
userId: { in: visibleIds },
|
|
},
|
|
distinct: ['userId'],
|
|
include: {
|
|
user: { select: { id: true, name: true, email: true } },
|
|
},
|
|
});
|
|
|
|
return {
|
|
friends: votes
|
|
.filter((v) => v.user)
|
|
.map((v) => ({
|
|
id: v.user!.id,
|
|
name: v.user!.name,
|
|
email: v.user!.email,
|
|
})),
|
|
count: votes.filter((v) => v.user).length,
|
|
};
|
|
},
|
|
|
|
/** Helper: get user IDs that have showInFriendActivity disabled */
|
|
async getHiddenActivityUserIds(userIds: string[]): Promise<Set<string>> {
|
|
const hidden = await prisma.privacySettings.findMany({
|
|
where: { userId: { in: userIds }, showInFriendActivity: false },
|
|
select: { userId: true },
|
|
});
|
|
return new Set(hidden.map((p) => p.userId));
|
|
},
|
|
};
|