Deleting a message that Slack no longer has counts as done, so the window rewinds instead of staying stuck. A reset button clears the mark by hand, and the empty-run notice now says since the last dispatch instead of seven days.
205 lines
5.2 KiB
TypeScript
205 lines
5.2 KiB
TypeScript
import { recordAudit } from '~/data/repositories/audit'
|
|
import {
|
|
clearSetting,
|
|
findLatestSentRun,
|
|
findReportRun,
|
|
markReportRunDeleted,
|
|
readSettings,
|
|
writeSetting,
|
|
} from '~/data/repositories/settings'
|
|
import { invalid, isUuid, readField, readFlag, type AdminFormState } from './admin-forms'
|
|
import { runWeeklyReport } from './report-settings'
|
|
import { deleteFromSlack, findMessageTs, resolveChannelId } from './slack'
|
|
import { reportText } from './weekly-report'
|
|
import type { Viewer } from './auth-access'
|
|
|
|
function looksLikeWebhook(value: string): boolean {
|
|
return value === '' || /^https:\/\/hooks\.slack\.com\/services\/[\w/+-]+$/u.test(value)
|
|
}
|
|
|
|
function looksLikeToken(value: string): boolean {
|
|
return value === '' || /^xox[bp]-[\w-]+$/u.test(value)
|
|
}
|
|
|
|
export async function performSaveSlack(viewer: Viewer, formData: FormData): Promise<AdminFormState> {
|
|
const webhook = readField(formData, 'webhook')
|
|
const token = readField(formData, 'botToken')
|
|
const channel = readField(formData, 'channel')
|
|
const keep = readFlag(formData, 'keep')
|
|
const keepToken = readFlag(formData, 'keepToken')
|
|
const includeInternal = readFlag(formData, 'includeInternal')
|
|
|
|
if (!keep && !looksLikeWebhook(webhook)) {
|
|
return invalid('webhookInvalid', undefined, { webhook: 'webhookInvalid' })
|
|
}
|
|
|
|
if (!keepToken && !looksLikeToken(token)) {
|
|
return invalid('tokenInvalid', undefined, { botToken: 'tokenInvalid' })
|
|
}
|
|
|
|
const stored = await readSettings(['slack.botToken'])
|
|
const withToken = keepToken ? Boolean(stored.get('slack.botToken')) : token !== ''
|
|
|
|
if (withToken && channel === '') {
|
|
return invalid('channelRequired', undefined, { channel: 'channelRequired' })
|
|
}
|
|
|
|
if (!keep) {
|
|
if (webhook === '') {
|
|
await clearSetting('slack.webhook')
|
|
} else {
|
|
await writeSetting('slack.webhook', webhook)
|
|
}
|
|
}
|
|
|
|
if (!keepToken) {
|
|
if (token === '') {
|
|
await clearSetting('slack.botToken')
|
|
} else {
|
|
await writeSetting('slack.botToken', token)
|
|
}
|
|
}
|
|
|
|
if (channel === '') {
|
|
await clearSetting('slack.channel')
|
|
} else {
|
|
await writeSetting('slack.channel', channel)
|
|
}
|
|
|
|
await writeSetting('slack.includeInternal', includeInternal ? 'true' : 'false')
|
|
|
|
await recordAudit({
|
|
actorId: viewer.id,
|
|
actorLabel: viewer.email,
|
|
action: 'settings.slack',
|
|
entity: 'setting',
|
|
entityId: 'slack',
|
|
data: {
|
|
webhookSet: keep ? 'unchanged' : webhook !== '',
|
|
botTokenSet: keepToken ? 'unchanged' : token !== '',
|
|
channel,
|
|
includeInternal,
|
|
},
|
|
})
|
|
|
|
return { status: 'ok', message: 'slackSaved' }
|
|
}
|
|
|
|
export async function performSendReport(viewer: Viewer, origin: string): Promise<AdminFormState> {
|
|
const outcome = await runWeeklyReport({ origin, trigger: `admin:${viewer.email}` })
|
|
|
|
await recordAudit({
|
|
actorId: viewer.id,
|
|
actorLabel: viewer.email,
|
|
action: 'settings.report',
|
|
entity: 'setting',
|
|
entityId: 'slack',
|
|
data: { reason: outcome.reason, total: outcome.run.total },
|
|
})
|
|
|
|
if (outcome.reason === 'sent') {
|
|
return { status: 'ok', message: 'reportSent' }
|
|
}
|
|
|
|
return invalid(outcome.reason === 'nothing_published' ? 'reportEmpty' : outcome.reason === 'not_configured' ? 'reportNoWebhook' : 'reportRejected')
|
|
}
|
|
|
|
export async function performDeleteMessage(viewer: Viewer, formData: FormData): Promise<AdminFormState> {
|
|
const id = readField(formData, 'runId')
|
|
|
|
if (!isUuid(id)) {
|
|
return invalid('runUnknown')
|
|
}
|
|
|
|
const run = await findReportRun(id)
|
|
|
|
if (!run) {
|
|
return invalid('runUnknown')
|
|
}
|
|
|
|
if (!run.sent) {
|
|
return invalid('runNotSent')
|
|
}
|
|
|
|
if (!run.messageTs) {
|
|
return invalid('runNotDeletable')
|
|
}
|
|
|
|
const values = await readSettings(['slack.botToken', 'slack.channel'])
|
|
const token = values.get('slack.botToken')?.trim()
|
|
const channel = run.channel ?? values.get('slack.channel')?.trim()
|
|
|
|
if (!token) {
|
|
return invalid('runNoToken')
|
|
}
|
|
|
|
if (!channel) {
|
|
return invalid('runNoChannel')
|
|
}
|
|
|
|
const resolved = await resolveChannelId(token, channel)
|
|
|
|
if (!resolved.ok) {
|
|
return invalid('slackSaid', { detail: resolved.detail })
|
|
}
|
|
|
|
let messageTs = run.messageTs
|
|
|
|
if (!messageTs) {
|
|
const lookup = await findMessageTs({
|
|
token,
|
|
channel: resolved.id,
|
|
text: reportText({ from: run.fromAt, to: run.toAt, total: run.total }),
|
|
around: run.createdAt,
|
|
})
|
|
|
|
if (!lookup.ok) {
|
|
return invalid('slackSaid', { detail: lookup.detail })
|
|
}
|
|
|
|
messageTs = lookup.messageTs
|
|
}
|
|
|
|
const result = await deleteFromSlack(token, resolved.id, messageTs)
|
|
|
|
if (!result.ok) {
|
|
return invalid('slackSaid', { detail: result.detail })
|
|
}
|
|
|
|
await markReportRunDeleted(run.id)
|
|
|
|
const previous = await findLatestSentRun()
|
|
|
|
if (previous) {
|
|
await writeSetting('slack.lastSentAt', previous.toAt.toISOString())
|
|
} else {
|
|
await clearSetting('slack.lastSentAt')
|
|
}
|
|
|
|
await recordAudit({
|
|
actorId: viewer.id,
|
|
actorLabel: viewer.email,
|
|
action: 'settings.reportDelete',
|
|
entity: 'setting',
|
|
entityId: run.id,
|
|
data: { channel: resolved.id, messageTs },
|
|
})
|
|
|
|
return { status: 'ok', message: 'messageDeleted' }
|
|
}
|
|
|
|
export async function performResetWindow(viewer: Viewer): Promise<AdminFormState> {
|
|
await clearSetting('slack.lastSentAt')
|
|
|
|
await recordAudit({
|
|
actorId: viewer.id,
|
|
actorLabel: viewer.email,
|
|
action: 'settings.reportReset',
|
|
entity: 'setting',
|
|
entityId: 'slack',
|
|
data: {},
|
|
})
|
|
|
|
return { status: 'ok', message: 'windowReset' }
|
|
}
|