diff --git a/src/commands/thread.ts b/src/commands/thread.ts index 87763e7..e6038c6 100644 --- a/src/commands/thread.ts +++ b/src/commands/thread.ts @@ -1,3 +1,4 @@ +import assert from 'assert' import { ChannelType, ThreadChannel } from 'discord.js' import { SlashCommand, SlashCreator, CommandContext } from 'slash-create' import { commandLog } from '../communication/interaction' @@ -6,7 +7,7 @@ import { updateFeedbackThreadId, validatePendingSubmission } from '../db/submission' -import { fetchSubmissionForContext } from '../utils/commands' +import { fetchAnySubmissionForContext } from '../utils/commands' import { getAssignedGuilds } from '../utils/discordUtils' import { runCatching } from '../utils/request' @@ -21,7 +22,7 @@ export default class ThreadCommand extends SlashCommand { } async run (ctx: CommandContext): Promise { - const submission = await fetchSubmissionForContext(ctx) + const submission = await fetchAnySubmissionForContext(ctx) if (!submission) { return @@ -29,6 +30,9 @@ export default class ThreadCommand extends SlashCommand { let existingThread: ThreadChannel | undefined + // Running commands in a thread with a raw submission should be impossible + assert(submission.state !== 'RAW', 'submission was in raw state') + if (submission.state === 'ERROR') { commandLog.warning({ type: 'text', @@ -39,6 +43,21 @@ export default class ThreadCommand extends SlashCommand { return } + if (submission.state === 'ACCEPTED' || submission.state === 'DENIED') { + const { feedbackThread } = submission + if (!feedbackThread) { + commandLog.warning({ + type: 'text', + content: + 'Cannot add you, no thread exists.', + ctx + }) + return + } + + existingThread = feedbackThread + } + if (submission.state === 'PROCESSING' || submission.state === 'PAUSED') { // Allow thread creation in paused state, as reviewers may need to contact submitters about warnings existingThread = submission.feedbackThread @@ -85,6 +104,10 @@ export default class ThreadCommand extends SlashCommand { }), 'rethrow' ) + + // We checked for the states above, this just lets TS infer it so we don't have any casting. + assert(submission.state === 'PROCESSING' || submission.state === 'PAUSED' || submission.state === 'WARNING', 'impossible') + await updateFeedbackThreadId(submission, feedbackThread.id) await runCatching( diff --git a/src/db/submission.ts b/src/db/submission.ts index 1b6b28e..a24154a 100644 --- a/src/db/submission.ts +++ b/src/db/submission.ts @@ -212,6 +212,16 @@ export async function fetchSubmissionByMessageId ( // from button votes, whereby the author must exist const author = await fetchAuthor(data.authorId) + // May or may not exist, do not abort if it does not + let feedbackThread + if (data.feedbackThreadId) { + try { + feedbackThread = await fetchFeedbackThread(data.feedbackThreadId) + } catch { + logger.warn(`Feedback thread for submission (message ID ${id}) did not exist`) + } + } + const completed: CompletedSubmission = { ...data, state: data.state, @@ -221,7 +231,8 @@ export async function fetchSubmissionByMessageId ( source: data.sourceLinks, other: data.otherLinks }, - author + author, + feedbackThread } return completed @@ -385,11 +396,12 @@ async function resolvePrismaData ( logger.trace('API requests successful') - const submission: AnySubmission = { + const submission = { ...data, state: data.state, feedbackThread, + author: undefined, tech: data.techUsed, links: { other: data.otherLinks, diff --git a/src/types/submission.ts b/src/types/submission.ts index 3598db5..82e5b04 100644 --- a/src/types/submission.ts +++ b/src/types/submission.ts @@ -76,8 +76,9 @@ export interface CompletedSubmission extends BaseSubmission { id: Cuid submittedAt: Date - // Author may no longer exist when we fetch in this state - author?: GuildMember + // Author / thread may no longer exist when we fetch in this state + author: GuildMember | undefined + feedbackThread: ThreadChannel | undefined } // These type guards exist for more readable code, and better TS behavior diff --git a/src/utils/commands.ts b/src/utils/commands.ts index 5f5dc4d..05a5cd4 100644 --- a/src/utils/commands.ts +++ b/src/utils/commands.ts @@ -1,8 +1,43 @@ import { CommandContext } from 'slash-create' import { commandLog } from '../communication/interaction' import { internalLog } from '../communication/internal' -import { fetchSubmissionByThreadId } from '../db/submission' -import { PendingSubmission, ValidatedSubmission } from '../types/submission' +import { fetchAnySubmissionByThreadId, fetchSubmissionByThreadId } from '../db/submission' +import { AnySubmission, PendingSubmission, ValidatedSubmission } from '../types/submission' + +/** + * Fetches the submission for the given command context. + * This does NOT validate the state of the submission in any way + * This relies on the thread ID to perform the lookup. + */ +export async function fetchAnySubmissionForContext (ctx: CommandContext): Promise { + const id = ctx.channelID + + if (!id) { + return void commandLog.error({ + type: 'text', + content: 'Interaction came with no thread ID.', + ctx + }) + } + + const submission = await fetchAnySubmissionByThreadId(id) + + if (!submission) { + commandLog.error({ + type: 'text', + content: `Could not look up submission for channel ID ${id}`, + ctx + }) + + return void internalLog.error({ + type: 'text', + content: `Could not look up submission for channel ID ${id}`, + ctx: undefined + }) + } + + return submission +} /** * Fetches the submission for the given command context.