Extend EventBus: RC notifications, CRM activity, Gancio migration, calendar source types
- Add 7 new RC notification types: campaign published, donations, subscriptions, SMS escalations, user approved, video published, ticketed events - Add CRM activity entries for subscription activated and email bounced - Migrate ticketed-events Gancio sync from inline calls to EventBus listener - Add meeting.created/deleted events from jitsi.routes.ts - Add SHIFT, MEETING, TICKETED_EVENT to CalendarItemSource enum (Prisma migration) - Update calendar-sync listener to use proper source types instead of MANUAL - Total: 45 listener subscriptions across 6 modules, zero inline sync calls remaining Bunker Admin
This commit is contained in:
parent
075a7c8c4a
commit
68434c51a6
@ -83,10 +83,16 @@ Service Handler (shift created, donation completed, etc.)
|
||||
- [x] Media video publish/unpublish/view (3 event types)
|
||||
- [x] Ticketed event publish/cancel (EventBus publishes alongside existing Gancio calls)
|
||||
- [x] Impact story publish (social.impact-story.published)
|
||||
- [ ] Meeting create/update/delete (not yet migrated — meetings module needs review)
|
||||
- [x] Meeting create/delete (jitsi.routes.ts — meeting.created, meeting.deleted)
|
||||
|
||||
### Phase 4b: Extended Listeners (2026-03-31)
|
||||
- [x] RC listener: +7 subscriptions (campaign.published, donations, subscriptions, SMS escalation, user.approved, video.published, ticketed-event.published)
|
||||
- [x] CRM listener: +2 subscriptions (subscription activated, email bounced)
|
||||
- [x] RC webhook service: +7 new formatter methods
|
||||
- [x] Prisma migration: SHIFT, MEETING, TICKETED_EVENT added to CalendarItemSource enum
|
||||
- [x] Calendar sync listener: uses proper source types (SHIFT, MEETING, TICKETED_EVENT)
|
||||
|
||||
### Phase 5: Future
|
||||
- [ ] Add SHIFT, MEETING, TICKETED_EVENT to CalendarItemSource enum (Prisma migration)
|
||||
- [ ] Migrate remaining Gancio calls (ticketed-events, meeting-planner) to EventBus
|
||||
- [ ] Add engagement scoring listener
|
||||
- [ ] Add Homepage dashboard data listener
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
-- Add SHIFT, MEETING, TICKETED_EVENT to CalendarItemSource enum
|
||||
ALTER TYPE "CalendarItemSource" ADD VALUE IF NOT EXISTS 'SHIFT';
|
||||
ALTER TYPE "CalendarItemSource" ADD VALUE IF NOT EXISTS 'MEETING';
|
||||
ALTER TYPE "CalendarItemSource" ADD VALUE IF NOT EXISTS 'TICKETED_EVENT';
|
||||
@ -4968,6 +4968,9 @@ enum CalendarItemSource {
|
||||
MANUAL
|
||||
ICS_FEED
|
||||
POLL
|
||||
SHIFT
|
||||
MEETING
|
||||
TICKETED_EVENT
|
||||
}
|
||||
|
||||
enum CalendarRecurrenceFrequency {
|
||||
|
||||
@ -9,6 +9,7 @@ import { prisma } from '../../config/database';
|
||||
import { siteSettingsService } from '../settings/settings.service';
|
||||
import { isServiceOnline } from '../../utils/health-check';
|
||||
import { generateSlug, generateModeratorToken } from './jitsi.utils';
|
||||
import { eventBus } from '../../services/event-bus.service';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@ -172,6 +173,14 @@ router.post(
|
||||
},
|
||||
});
|
||||
|
||||
eventBus.publish('meeting.created', {
|
||||
meetingId: meeting.id,
|
||||
title: meeting.title,
|
||||
scheduledAt: meeting.startTime?.toISOString() ?? new Date().toISOString(),
|
||||
jitsiRoomName: meeting.jitsiRoom,
|
||||
createdByUserId: meeting.createdByUserId,
|
||||
});
|
||||
|
||||
res.status(201).json(meeting);
|
||||
} catch (err) {
|
||||
logger.error('Create meeting failed:', err);
|
||||
@ -226,6 +235,12 @@ router.delete(
|
||||
}
|
||||
|
||||
await prisma.meeting.delete({ where: { id: meetingId } });
|
||||
|
||||
eventBus.publish('meeting.deleted', {
|
||||
meetingId: meeting.id,
|
||||
title: meeting.title,
|
||||
});
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
logger.error('Delete meeting failed:', err);
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { prisma } from '../../config/database';
|
||||
import { TicketedEventStatus, TicketedEventVisibility, EventFormat, Prisma } from '@prisma/client';
|
||||
import { logger } from '../../utils/logger';
|
||||
import { AppError } from '../../middleware/error-handler';
|
||||
import { unifiedCalendarService } from '../events/unified-calendar.service';
|
||||
import { siteSettingsService } from '../settings/settings.service';
|
||||
@ -385,8 +384,7 @@ export const ticketedEventsService = {
|
||||
include: { ticketTiers: true },
|
||||
});
|
||||
|
||||
// Gancio sync + calendar cache bust (fire-and-forget)
|
||||
this.syncToGancio(updated).catch(() => {});
|
||||
// Calendar cache bust (fire-and-forget)
|
||||
unifiedCalendarService.bustCache().catch(() => {});
|
||||
|
||||
eventBus.publish('ticketed-event.published', {
|
||||
@ -415,8 +413,18 @@ export const ticketedEventsService = {
|
||||
include: { ticketTiers: true },
|
||||
});
|
||||
|
||||
this.syncToGancio(updated).catch(() => {});
|
||||
unifiedCalendarService.bustCache().catch(() => {});
|
||||
|
||||
eventBus.publish('ticketed-event.published', {
|
||||
eventId: updated.id,
|
||||
title: updated.title,
|
||||
date: updated.date.toISOString().split('T')[0],
|
||||
startTime: updated.startTime,
|
||||
endTime: updated.endTime,
|
||||
location: updated.venueAddress || updated.venueName || undefined,
|
||||
gancioEventId: updated.gancioEventId ?? undefined,
|
||||
});
|
||||
|
||||
return updated;
|
||||
},
|
||||
|
||||
@ -456,10 +464,7 @@ export const ticketedEventsService = {
|
||||
data: { status: 'CANCELLED' },
|
||||
});
|
||||
|
||||
// Delete from Gancio if synced + bust calendar cache
|
||||
if (event.gancioEventId) {
|
||||
this.deleteFromGancio(event.gancioEventId).catch(() => {});
|
||||
}
|
||||
// Calendar cache bust (fire-and-forget)
|
||||
unifiedCalendarService.bustCache().catch(() => {});
|
||||
|
||||
eventBus.publish('ticketed-event.cancelled', {
|
||||
@ -501,7 +506,10 @@ export const ticketedEventsService = {
|
||||
}
|
||||
|
||||
if (event.gancioEventId) {
|
||||
this.deleteFromGancio(event.gancioEventId).catch(() => {});
|
||||
eventBus.publish('ticketed-event.cancelled', {
|
||||
eventId: event.id,
|
||||
title: event.title,
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.ticketedEvent.delete({ where: { id } });
|
||||
@ -783,78 +791,4 @@ export const ticketedEventsService = {
|
||||
};
|
||||
},
|
||||
|
||||
// --- Gancio Sync ---
|
||||
|
||||
async syncToGancio(event: {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string | null;
|
||||
venueAddress?: string | null;
|
||||
venueName?: string | null;
|
||||
eventFormat?: EventFormat;
|
||||
date: Date;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
gancioEventId?: number | null;
|
||||
}) {
|
||||
try {
|
||||
const { gancioClient } = await import('../../services/gancio.client');
|
||||
if (!gancioClient.enabled) return;
|
||||
|
||||
// Determine location based on event format
|
||||
let location: string | null;
|
||||
const format = event.eventFormat || 'IN_PERSON';
|
||||
if (format === 'ONLINE') {
|
||||
location = 'Online Event';
|
||||
} else if (format === 'HYBRID') {
|
||||
const venue = event.venueAddress || event.venueName || '';
|
||||
location = venue ? `${venue} (also streaming online)` : 'Online + In-Person';
|
||||
} else {
|
||||
location = event.venueAddress || event.venueName || null;
|
||||
}
|
||||
|
||||
const tags = ['ticketed', 'community'];
|
||||
if (format === 'ONLINE') tags.push('online');
|
||||
if (format === 'HYBRID') tags.push('hybrid');
|
||||
|
||||
if (event.gancioEventId) {
|
||||
await gancioClient.updateEvent(event.gancioEventId, {
|
||||
title: event.title,
|
||||
description: event.description,
|
||||
location,
|
||||
date: event.date,
|
||||
startTime: event.startTime,
|
||||
endTime: event.endTime,
|
||||
});
|
||||
} else {
|
||||
const gancioId = await gancioClient.createEvent({
|
||||
title: event.title,
|
||||
description: event.description,
|
||||
location,
|
||||
date: event.date,
|
||||
startTime: event.startTime,
|
||||
endTime: event.endTime,
|
||||
tags,
|
||||
});
|
||||
if (gancioId) {
|
||||
await prisma.ticketedEvent.update({
|
||||
where: { id: event.id },
|
||||
data: { gancioEventId: gancioId },
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn('Gancio sync failed for ticketed event:', err instanceof Error ? err.message : err);
|
||||
}
|
||||
},
|
||||
|
||||
async deleteFromGancio(gancioEventId: number) {
|
||||
try {
|
||||
const { gancioClient } = await import('../../services/gancio.client');
|
||||
if (!gancioClient.enabled) return;
|
||||
await gancioClient.deleteEvent(gancioEventId);
|
||||
} catch (err) {
|
||||
logger.warn(`Gancio delete failed for event ${gancioEventId}:`, err instanceof Error ? err.message : err);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -156,7 +156,7 @@ async function deleteBySource(sourceType: string, sourceId: string): Promise<voi
|
||||
export function registerCalendarSyncListener(): void {
|
||||
// Shift created → Calendar item
|
||||
eventBus.subscribe('shift.created', 'calendar:shift-created', async (payload) => {
|
||||
await upsertCalendarItem(payload.createdByUserId, 'MANUAL', payload.shiftId, {
|
||||
await upsertCalendarItem(payload.createdByUserId, 'SHIFT', payload.shiftId, {
|
||||
title: `Shift: ${payload.title}`,
|
||||
date: payload.date,
|
||||
startTime: payload.startTime,
|
||||
@ -175,7 +175,7 @@ export function registerCalendarSyncListener(): void {
|
||||
select: { userId: true },
|
||||
});
|
||||
if (!existing) return;
|
||||
await upsertCalendarItem(existing.userId, 'MANUAL', payload.shiftId, {
|
||||
await upsertCalendarItem(existing.userId, 'SHIFT', payload.shiftId, {
|
||||
title: `Shift: ${payload.title}`,
|
||||
date: payload.date,
|
||||
startTime: payload.startTime,
|
||||
@ -189,7 +189,7 @@ export function registerCalendarSyncListener(): void {
|
||||
|
||||
// Shift deleted → Remove calendar item
|
||||
eventBus.subscribe('shift.deleted', 'calendar:shift-deleted', async (payload) => {
|
||||
await deleteBySource('MANUAL', payload.shiftId);
|
||||
await deleteBySource('SHIFT', payload.shiftId);
|
||||
});
|
||||
|
||||
// Meeting created → Calendar item
|
||||
@ -199,7 +199,7 @@ export function registerCalendarSyncListener(): void {
|
||||
const endHour = parseInt(time.split(':')[0]) + 1;
|
||||
const endTime = `${String(endHour).padStart(2, '0')}:${time.split(':')[1]}`;
|
||||
|
||||
await upsertCalendarItem(payload.createdByUserId, 'MANUAL', payload.meetingId, {
|
||||
await upsertCalendarItem(payload.createdByUserId, 'MEETING', payload.meetingId, {
|
||||
title: `Meeting: ${payload.title}`,
|
||||
date,
|
||||
startTime: time,
|
||||
@ -223,7 +223,7 @@ export function registerCalendarSyncListener(): void {
|
||||
const endHour = parseInt(time.split(':')[0]) + 1;
|
||||
const endTime = `${String(endHour).padStart(2, '0')}:${time.split(':')[1]}`;
|
||||
|
||||
await upsertCalendarItem(existing.userId, 'MANUAL', payload.meetingId, {
|
||||
await upsertCalendarItem(existing.userId, 'MEETING', payload.meetingId, {
|
||||
title: `Meeting: ${payload.title}`,
|
||||
date,
|
||||
startTime: time,
|
||||
@ -236,7 +236,7 @@ export function registerCalendarSyncListener(): void {
|
||||
|
||||
// Meeting deleted → Remove calendar item
|
||||
eventBus.subscribe('meeting.deleted', 'calendar:meeting-deleted', async (payload) => {
|
||||
await deleteBySource('MANUAL', payload.meetingId);
|
||||
await deleteBySource('MEETING', payload.meetingId);
|
||||
});
|
||||
|
||||
// Ticketed event published → Calendar item
|
||||
@ -249,7 +249,7 @@ export function registerCalendarSyncListener(): void {
|
||||
select: { createdByUserId: true },
|
||||
});
|
||||
if (!event) return;
|
||||
await upsertCalendarItem(event.createdByUserId, 'MANUAL', payload.eventId, {
|
||||
await upsertCalendarItem(event.createdByUserId, 'TICKETED_EVENT', payload.eventId, {
|
||||
title: `Event: ${payload.title}`,
|
||||
date: payload.date,
|
||||
startTime: payload.startTime,
|
||||
@ -263,6 +263,6 @@ export function registerCalendarSyncListener(): void {
|
||||
|
||||
// Ticketed event cancelled → Remove calendar item
|
||||
eventBus.subscribe('ticketed-event.cancelled', 'calendar:ticketed-event-cancel', async (payload) => {
|
||||
await deleteBySource('MANUAL', payload.eventId);
|
||||
await deleteBySource('TICKETED_EVENT', payload.eventId);
|
||||
});
|
||||
}
|
||||
|
||||
@ -190,6 +190,27 @@ export function registerCrmActivityListener(): void {
|
||||
}
|
||||
});
|
||||
|
||||
// Subscription activated
|
||||
eventBus.subscribe('payment.subscription.activated', 'crm:subscription', async (payload) => {
|
||||
const contactId = await findContactByEmail(payload.email);
|
||||
if (!contactId) return;
|
||||
await createActivity(contactId, 'PURCHASE', `Subscribed to "${payload.planName}"`, undefined, {
|
||||
subscriptionId: payload.subscriptionId,
|
||||
planName: payload.planName,
|
||||
});
|
||||
});
|
||||
|
||||
// Listmonk email bounced → flag contact
|
||||
eventBus.subscribe('listmonk.email.bounced', 'crm:email-bounced', async (payload) => {
|
||||
const contactId = await findContactByEmail(payload.subscriberEmail);
|
||||
if (!contactId) return;
|
||||
await createActivity(contactId, 'NOTE_ADDED', `Email bounced (${payload.bounceType})`, `Email address may be invalid — bounced on campaign #${payload.campaignId}`, {
|
||||
listmonkCampaignId: payload.campaignId,
|
||||
bounceType: payload.bounceType,
|
||||
action: 'bounced',
|
||||
});
|
||||
});
|
||||
|
||||
// Listmonk email opened → activity
|
||||
eventBus.subscribe('listmonk.email.opened', 'crm:email-opened', async (payload) => {
|
||||
const contactId = await findContactByEmail(payload.subscriberEmail);
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Gancio EventBus Listener
|
||||
*
|
||||
* Syncs shift events to the Gancio public event calendar.
|
||||
* This replaces the inline gancioClient calls in shifts.service.ts.
|
||||
* Syncs shift and ticketed events to the Gancio public event calendar.
|
||||
* This replaces the inline gancioClient calls in shifts.service.ts and
|
||||
* ticketed-events.service.ts.
|
||||
*
|
||||
* Feature guard: GANCIO_SYNC_ENABLED=true (checked inside gancioClient)
|
||||
*/
|
||||
@ -90,4 +91,100 @@ export function registerGancioListener(): void {
|
||||
logger.debug('Gancio sync: shift delete failed:', err);
|
||||
}
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// TICKETED EVENT LISTENERS
|
||||
// =========================================================================
|
||||
|
||||
// Ticketed event published → Create or update Gancio event
|
||||
eventBus.subscribe('ticketed-event.published', 'gancio:ticketed-event-published', async (payload) => {
|
||||
try {
|
||||
const gancio = await getGancioClient();
|
||||
if (!gancio.enabled) return;
|
||||
|
||||
const { prisma } = await import('../../config/database');
|
||||
const event = await prisma.ticketedEvent.findUnique({
|
||||
where: { id: payload.eventId },
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
venueAddress: true,
|
||||
venueName: true,
|
||||
eventFormat: true,
|
||||
date: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
gancioEventId: true,
|
||||
},
|
||||
});
|
||||
if (!event) return;
|
||||
|
||||
// Determine location based on event format
|
||||
const format = event.eventFormat || 'IN_PERSON';
|
||||
let location: string | null;
|
||||
if (format === 'ONLINE') {
|
||||
location = 'Online Event';
|
||||
} else if (format === 'HYBRID') {
|
||||
const venue = event.venueAddress || event.venueName || '';
|
||||
location = venue ? `${venue} (also streaming online)` : 'Online + In-Person';
|
||||
} else {
|
||||
location = event.venueAddress || event.venueName || null;
|
||||
}
|
||||
|
||||
const tags = ['ticketed', 'community'];
|
||||
if (format === 'ONLINE') tags.push('online');
|
||||
if (format === 'HYBRID') tags.push('hybrid');
|
||||
|
||||
if (event.gancioEventId) {
|
||||
// Update existing Gancio event
|
||||
await gancio.updateEvent(event.gancioEventId, {
|
||||
title: event.title,
|
||||
description: event.description,
|
||||
location,
|
||||
date: event.date,
|
||||
startTime: event.startTime,
|
||||
endTime: event.endTime,
|
||||
});
|
||||
} else {
|
||||
// Create new Gancio event and store ID back
|
||||
const gancioId = await gancio.createEvent({
|
||||
title: event.title,
|
||||
description: event.description,
|
||||
location,
|
||||
date: event.date,
|
||||
startTime: event.startTime,
|
||||
endTime: event.endTime,
|
||||
tags,
|
||||
});
|
||||
if (gancioId) {
|
||||
await prisma.ticketedEvent.update({
|
||||
where: { id: event.id },
|
||||
data: { gancioEventId: gancioId },
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.debug('Gancio sync: ticketed event publish failed:', err);
|
||||
}
|
||||
});
|
||||
|
||||
// Ticketed event cancelled → Delete Gancio event
|
||||
eventBus.subscribe('ticketed-event.cancelled', 'gancio:ticketed-event-cancelled', async (payload) => {
|
||||
try {
|
||||
const gancio = await getGancioClient();
|
||||
if (!gancio.enabled) return;
|
||||
|
||||
const { prisma } = await import('../../config/database');
|
||||
const event = await prisma.ticketedEvent.findUnique({
|
||||
where: { id: payload.eventId },
|
||||
select: { gancioEventId: true },
|
||||
});
|
||||
if (!event?.gancioEventId) return;
|
||||
|
||||
await gancio.deleteEvent(event.gancioEventId);
|
||||
} catch (err) {
|
||||
logger.debug('Gancio sync: ticketed event cancel failed:', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -52,4 +52,67 @@ export function registerRocketChatListener(): void {
|
||||
representativeName: payload.representativeName,
|
||||
});
|
||||
});
|
||||
|
||||
// --- Campaigns ---
|
||||
|
||||
eventBus.subscribe('campaign.published', 'rocketchat:campaign-published', (payload) => {
|
||||
rocketchatWebhookService.onCampaignPublished({
|
||||
campaignTitle: payload.title,
|
||||
campaignSlug: payload.slug,
|
||||
});
|
||||
});
|
||||
|
||||
// --- Payments ---
|
||||
|
||||
eventBus.subscribe('payment.donation.completed', 'rocketchat:donation', (payload) => {
|
||||
rocketchatWebhookService.onDonationReceived({
|
||||
donorName: payload.name || payload.email,
|
||||
amount: (payload.amountCents / 100).toFixed(2),
|
||||
});
|
||||
});
|
||||
|
||||
eventBus.subscribe('payment.subscription.activated', 'rocketchat:subscription', (payload) => {
|
||||
rocketchatWebhookService.onSubscriptionActivated({
|
||||
userName: payload.name || payload.email,
|
||||
planName: payload.planName,
|
||||
});
|
||||
});
|
||||
|
||||
// --- SMS escalations (QUESTION/NEGATIVE responses) ---
|
||||
|
||||
eventBus.subscribe('sms.message.received', 'rocketchat:sms-escalation', (payload) => {
|
||||
if (payload.responseType === 'QUESTION' || payload.responseType === 'NEGATIVE') {
|
||||
rocketchatWebhookService.onSmsEscalation({
|
||||
phone: payload.phone,
|
||||
responseType: payload.responseType,
|
||||
body: payload.body,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// --- Users ---
|
||||
|
||||
eventBus.subscribe('user.approved', 'rocketchat:user-approved', (payload) => {
|
||||
rocketchatWebhookService.onUserApproved({
|
||||
userName: payload.name,
|
||||
role: payload.role,
|
||||
});
|
||||
});
|
||||
|
||||
// --- Media ---
|
||||
|
||||
eventBus.subscribe('media.video.published', 'rocketchat:video-published', (payload) => {
|
||||
rocketchatWebhookService.onVideoPublished({
|
||||
videoTitle: payload.title,
|
||||
});
|
||||
});
|
||||
|
||||
// --- Ticketed Events ---
|
||||
|
||||
eventBus.subscribe('ticketed-event.published', 'rocketchat:ticketed-event', (payload) => {
|
||||
rocketchatWebhookService.onTicketedEventPublished({
|
||||
eventTitle: payload.title,
|
||||
eventDate: payload.date,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -58,6 +58,63 @@ class RocketChatWebhookService {
|
||||
await this.notify('#campaigns', text, '#9b59b6');
|
||||
}
|
||||
|
||||
async onCampaignPublished(data: {
|
||||
campaignTitle: string;
|
||||
campaignSlug: string;
|
||||
}): Promise<void> {
|
||||
const text = `:newspaper: Campaign published: *${data.campaignTitle}* → /campaigns/${data.campaignSlug}`;
|
||||
await this.notify('#campaigns', text, '#27ae60');
|
||||
}
|
||||
|
||||
async onDonationReceived(data: {
|
||||
donorName: string;
|
||||
amount: string;
|
||||
}): Promise<void> {
|
||||
const text = `:money_with_wings: **${data.donorName}** donated **$${data.amount}**`;
|
||||
await this.notify('#campaigns', text, '#f1c40f');
|
||||
}
|
||||
|
||||
async onSubscriptionActivated(data: {
|
||||
userName: string;
|
||||
planName: string;
|
||||
}): Promise<void> {
|
||||
const text = `:star: **${data.userName}** subscribed to *${data.planName}*`;
|
||||
await this.notify('#campaigns', text, '#9b59b6');
|
||||
}
|
||||
|
||||
async onSmsEscalation(data: {
|
||||
phone: string;
|
||||
responseType: string;
|
||||
body: string;
|
||||
}): Promise<void> {
|
||||
const preview = data.body.length > 100 ? data.body.slice(0, 100) + '...' : data.body;
|
||||
const text = `:warning: SMS ${data.responseType} from ${data.phone}: "${preview}"`;
|
||||
await this.notify('#campaigns', text, '#e74c3c');
|
||||
}
|
||||
|
||||
async onUserApproved(data: {
|
||||
userName: string;
|
||||
role: string;
|
||||
}): Promise<void> {
|
||||
const text = `:white_check_mark: User approved: **${data.userName}** (${data.role})`;
|
||||
await this.notify('#campaigns', text, '#2ecc71');
|
||||
}
|
||||
|
||||
async onVideoPublished(data: {
|
||||
videoTitle: string;
|
||||
}): Promise<void> {
|
||||
const text = `:film_projector: New video published: *${data.videoTitle}*`;
|
||||
await this.notify('#campaigns', text, '#3498db');
|
||||
}
|
||||
|
||||
async onTicketedEventPublished(data: {
|
||||
eventTitle: string;
|
||||
eventDate: string;
|
||||
}): Promise<void> {
|
||||
const text = `:ticket: Event published: *${data.eventTitle}* (${data.eventDate})`;
|
||||
await this.notify('#campaigns', text, '#e67e22');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure default notification channels exist in Rocket.Chat.
|
||||
* Called during service startup.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user