- Updated queue and QueueEvents initialization to be lazy, preventing unnecessary Redis connections during the build process. - Refactored queue access methods to ensure consistent connection handling across the application. - Enhanced error handling for Redis connections during production builds to reduce log noise.
130 lines
4.2 KiB
TypeScript
130 lines
4.2 KiB
TypeScript
import type {
|
|
SuggestionAction,
|
|
SuggestionConfigDashboard,
|
|
SuggestionConfigDashboardPatch,
|
|
SuggestionDashboard,
|
|
SuggestionListQuery
|
|
} from '@nexumi/shared';
|
|
import type { Suggestion } from '@prisma/client';
|
|
import { prisma } from '../prisma';
|
|
import { addJobAndAwait, getSuggestionsQueue, getSuggestionsQueueEvents } from '../queues';
|
|
|
|
export class SuggestionActionTimeoutError extends Error {
|
|
constructor() {
|
|
super('The bot did not confirm the suggestion update in time. Reload the list shortly to check the result.');
|
|
}
|
|
}
|
|
|
|
async function ensureSuggestionConfig(guildId: string) {
|
|
await prisma.guild.upsert({ where: { id: guildId }, update: {}, create: { id: guildId } });
|
|
return prisma.suggestionConfig.upsert({ where: { guildId }, update: {}, create: { guildId } });
|
|
}
|
|
|
|
function toConfigDashboard(
|
|
config: Awaited<ReturnType<typeof ensureSuggestionConfig>>
|
|
): SuggestionConfigDashboard {
|
|
return {
|
|
enabled: config.enabled,
|
|
openChannelId: config.openChannelId ?? '',
|
|
approvedChannelId: config.approvedChannelId ?? '',
|
|
deniedChannelId: config.deniedChannelId ?? '',
|
|
createThread: config.createThread
|
|
};
|
|
}
|
|
|
|
export async function getSuggestionConfigDashboard(guildId: string): Promise<SuggestionConfigDashboard> {
|
|
const config = await ensureSuggestionConfig(guildId);
|
|
return toConfigDashboard(config);
|
|
}
|
|
|
|
export async function updateSuggestionConfigDashboard(
|
|
guildId: string,
|
|
patch: SuggestionConfigDashboardPatch
|
|
): Promise<SuggestionConfigDashboard> {
|
|
await ensureSuggestionConfig(guildId);
|
|
|
|
const { openChannelId, approvedChannelId, deniedChannelId, ...rest } = patch;
|
|
const updated = await prisma.suggestionConfig.update({
|
|
where: { guildId },
|
|
data: {
|
|
...rest,
|
|
...(openChannelId !== undefined ? { openChannelId: openChannelId === '' ? null : openChannelId } : {}),
|
|
...(approvedChannelId !== undefined
|
|
? { approvedChannelId: approvedChannelId === '' ? null : approvedChannelId }
|
|
: {}),
|
|
...(deniedChannelId !== undefined
|
|
? { deniedChannelId: deniedChannelId === '' ? null : deniedChannelId }
|
|
: {})
|
|
}
|
|
});
|
|
return toConfigDashboard(updated);
|
|
}
|
|
|
|
function toDashboard(suggestion: Suggestion): SuggestionDashboard {
|
|
return {
|
|
id: suggestion.id,
|
|
authorId: suggestion.authorId,
|
|
content: suggestion.content,
|
|
status: suggestion.status as SuggestionDashboard['status'],
|
|
upvotes: suggestion.upvotes,
|
|
downvotes: suggestion.downvotes,
|
|
staffReason: suggestion.staffReason,
|
|
createdAt: suggestion.createdAt.toISOString()
|
|
};
|
|
}
|
|
|
|
export async function listSuggestions(
|
|
guildId: string,
|
|
query: SuggestionListQuery
|
|
): Promise<SuggestionDashboard[]> {
|
|
const suggestions = await prisma.suggestion.findMany({
|
|
where: { guildId, ...(query.status ? { status: query.status } : {}) },
|
|
orderBy: { createdAt: 'desc' },
|
|
take: query.limit
|
|
});
|
|
return suggestions.map(toDashboard);
|
|
}
|
|
|
|
/**
|
|
* Staff actions (approve/deny/consider/implement) need to move or edit the
|
|
* suggestion's Discord embed and DM the author, which requires the bot's
|
|
* discord.js Client. The WebUI enqueues a `suggestionStatusUpdate` job on
|
|
* the same `suggestions` BullMQ queue the bot worker consumes (see
|
|
* `apps/bot/src/modules/suggestions/service.ts#runSuggestionStatusUpdateJob`)
|
|
* and waits (bounded) for it to finish.
|
|
*/
|
|
export async function applySuggestionAction(
|
|
guildId: string,
|
|
suggestionId: string,
|
|
action: SuggestionAction,
|
|
_actorUserId: string
|
|
): Promise<SuggestionDashboard> {
|
|
const existing = await prisma.suggestion.findFirst({ where: { id: suggestionId, guildId } });
|
|
if (!existing) {
|
|
throw new SuggestionActionTimeoutError();
|
|
}
|
|
|
|
const { confirmed } = await addJobAndAwait<{ id: string }>(
|
|
getSuggestionsQueue(),
|
|
getSuggestionsQueueEvents(),
|
|
'suggestionStatusUpdate',
|
|
{
|
|
suggestionId,
|
|
guildId,
|
|
status: action.status,
|
|
reason: action.staffReason ?? ''
|
|
},
|
|
{ removeOnComplete: true, removeOnFail: 25 }
|
|
);
|
|
|
|
if (!confirmed) {
|
|
throw new SuggestionActionTimeoutError();
|
|
}
|
|
|
|
const updated = await prisma.suggestion.findUnique({ where: { id: suggestionId } });
|
|
if (!updated) {
|
|
throw new SuggestionActionTimeoutError();
|
|
}
|
|
return toDashboard(updated);
|
|
}
|