mirror of
https://github.com/immich-app/yucca.git
synced 2026-09-30 21:37:50 +08:00
feat(futo-backups-bot): /claim-backups-role grants the customer role (#567)
This commit is contained in:
+13
-1
@@ -53,6 +53,18 @@ belongs to a known account. Staff can bypass it with **`/ticket user:<user>`**
|
||||
and the staff note records whether a linked account exists. Pre-signup
|
||||
questions stay in public channels or go through that override.
|
||||
|
||||
## Channels and the customer role
|
||||
|
||||
The FUTO Backups category is private (Team/FUTO/Yucca/Contributor/Support
|
||||
Crew see it; flipping it public later is the rollout switch). `#general` is
|
||||
open chat for everyone who sees the category plus customers. The
|
||||
**`/claim-backups-role`** command (also offered by a daily bot prompt in
|
||||
`#general`, skipped when the channel is quiet) runs the same link flow as
|
||||
support and then grants the plain **FUTO Backups** role — linked customers get it instantly,
|
||||
unlinked ones link first. The role unlocks `#customer` (customer chat, also visible to
|
||||
Admin/Team/Yucca/FUTO). `#support` stays visible to everyone;
|
||||
ticket threads live under it as before.
|
||||
|
||||
## Tickets: Discord is the source of truth
|
||||
|
||||
No ticket table. A ticket is a **private thread** under the support channel;
|
||||
@@ -95,7 +107,7 @@ uses the dev guild through `.env`.
|
||||
| Variable | Source |
|
||||
|---|---|
|
||||
| `DISCORD_BOT_TOKEN` | Secret ← `YUCCA_DISCORD_BOT_TOKEN` (manual item) |
|
||||
| `DISCORD_GUILD_ID`, `DISCORD_STAFF_ROLE_ID`, `DISCORD_SUPPORT_CHANNEL_ID` | Secret ← `YUCCA_DISCORD_SUPPORT_IDS` (written by core-infra-tf's discord apply) |
|
||||
| `DISCORD_GUILD_ID`, `DISCORD_STAFF_ROLE_ID`, `DISCORD_SUPPORT_CHANNEL_ID`, `DISCORD_GENERAL_CHANNEL_ID`, `DISCORD_CHAT_CHANNEL_ID`, `DISCORD_CUSTOMER_ROLE_ID` | Secret ← `YUCCA_DISCORD_SUPPORT_IDS` (written by core-infra-tf's discord apply) |
|
||||
| `INTERNAL_SECRET` | Secret ← TF-generated (`random_password`, shared with yucca-api) |
|
||||
| `TRANSCRIPT_S3_ACCESS_KEY_ID` / `..._SECRET_ACCESS_KEY` | Secret ← ceph-stack-minted `*_CEPH_S3_SVC_YUCCA_TRANSCRIPTS_*` |
|
||||
| `TRANSCRIPT_S3_ENDPOINT`, `TRANSCRIPT_S3_BUCKET` | cluster-settings |
|
||||
|
||||
@@ -4,4 +4,5 @@ export enum ComponentId {
|
||||
CloseTicket = 'close-ticket',
|
||||
TicketModal = 'ticket-modal',
|
||||
TicketDescription = 'ticket-description',
|
||||
ClaimRole = 'claim-role',
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ const schema = z.object({
|
||||
DISCORD_GUILD_ID: z.string().default(''),
|
||||
DISCORD_STAFF_ROLE_ID: z.string().default(''),
|
||||
DISCORD_SUPPORT_CHANNEL_ID: z.string().default(''),
|
||||
DISCORD_GENERAL_CHANNEL_ID: z.string().default(''),
|
||||
DISCORD_CHAT_CHANNEL_ID: z.string().default(''),
|
||||
DISCORD_CUSTOMER_ROLE_ID: z.string().default(''),
|
||||
|
||||
YUCCA_API_URL: z.url().default('http://localhost:3020'),
|
||||
INTERNAL_SECRET: z.string().default(''),
|
||||
@@ -20,6 +23,7 @@ const schema = z.object({
|
||||
|
||||
TICKET_RETENTION_DAYS: z.coerce.number().default(14),
|
||||
TICKET_USER_LIMIT: z.coerce.number().int().positive().default(3),
|
||||
CLAIM_PROMPT_MIN_MESSAGES: z.coerce.number().int().positive().default(25),
|
||||
|
||||
TRANSCRIPT_S3_ENDPOINT: z.string().default(''),
|
||||
TRANSCRIPT_S3_BUCKET: z.string().default(''),
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GatewayIntentBits,
|
||||
Guild,
|
||||
Interaction,
|
||||
Message,
|
||||
MessageCreateOptions,
|
||||
SlashCommandBuilder,
|
||||
TextChannel,
|
||||
@@ -57,11 +58,32 @@ export class DiscordRepository {
|
||||
.setName('staff-notes')
|
||||
.setDescription("Link this ticket's staff-notes thread (staff only)")
|
||||
.toJSON(),
|
||||
new SlashCommandBuilder()
|
||||
.setName('claim-backups-role')
|
||||
.setDescription('Claim the FUTO Backups customer role for your linked account')
|
||||
.toJSON(),
|
||||
]);
|
||||
}
|
||||
|
||||
async ensurePinnedSupportMessage(message: MessageCreateOptions, buttonId: string): Promise<void> {
|
||||
const channel = await this.supportChannel();
|
||||
async listRecentMessages(channelId: string, limit: number): Promise<Message[]> {
|
||||
const channel = await this.textChannel(channelId);
|
||||
const messages = await channel.messages.fetch({ limit });
|
||||
return [...messages.values()];
|
||||
}
|
||||
|
||||
async sendMessage(channelId: string, message: MessageCreateOptions): Promise<void> {
|
||||
const channel = await this.textChannel(channelId);
|
||||
await channel.send(message);
|
||||
}
|
||||
|
||||
async addRoleToMember(discordUserId: string, roleId: string): Promise<void> {
|
||||
const guild = await this.guild();
|
||||
const member = await guild.members.fetch(discordUserId);
|
||||
await member.roles.add(roleId);
|
||||
}
|
||||
|
||||
async ensurePinnedMessage(channelId: string, message: MessageCreateOptions, buttonId: string): Promise<void> {
|
||||
const channel = await this.textChannel(channelId);
|
||||
const [pinned, recent] = await Promise.all([
|
||||
channel.messages.fetchPinned(),
|
||||
channel.messages.fetch({ limit: 100 }),
|
||||
|
||||
@@ -66,6 +66,15 @@ const newModalInteraction = (description: string, overrides: object = {}) =>
|
||||
...overrides,
|
||||
}) as never;
|
||||
|
||||
const humanMessage = (ts: number) => ({ author: { bot: false }, createdTimestamp: ts, components: [] }) as never;
|
||||
const promptMessage = (ts: number, del = jest.fn().mockResolvedValue(void 0)) =>
|
||||
({
|
||||
author: { bot: true },
|
||||
createdTimestamp: ts,
|
||||
components: [{ components: [{ customId: ComponentId.ClaimRole }] }],
|
||||
delete: del,
|
||||
}) as never;
|
||||
|
||||
const asThread = (properties: object): ThreadChannel =>
|
||||
Object.assign(Object.create(ThreadChannel.prototype) as ThreadChannel, properties);
|
||||
|
||||
@@ -210,6 +219,73 @@ describe(SupportService.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('/claim-backups-role command', () => {
|
||||
it('grants the customer role to a linked user', async () => {
|
||||
mocks.api.getLink.mockResolvedValue(link);
|
||||
const interaction = newCommandInteraction(
|
||||
{},
|
||||
{ commandName: 'claim-backups-role', user: { id: '123456789', username: 'Someone' } },
|
||||
);
|
||||
|
||||
await sut.handleInteraction(interaction);
|
||||
|
||||
expect(mocks.discord.addRoleToMember).toHaveBeenCalledWith('123456789', 'customer-role');
|
||||
expect((interaction as { editReply: jest.Mock }).editReply).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ content: expect.stringContaining('chat-channel') }),
|
||||
);
|
||||
});
|
||||
|
||||
it('grants the role after linking for an unlinked user', async () => {
|
||||
mocks.api.getLink.mockResolvedValueOnce(null).mockResolvedValue(link);
|
||||
mocks.api.createLinkRequest.mockResolvedValue({ code: 'code-1', expiresAt: new Date(Date.now() + 2000) });
|
||||
const interaction = newCommandInteraction(
|
||||
{},
|
||||
{ commandName: 'claim-backups-role', user: { id: '123456789', username: 'Someone' } },
|
||||
);
|
||||
|
||||
await sut.handleInteraction(interaction);
|
||||
|
||||
expect(mocks.api.createLinkRequest).toHaveBeenCalledWith('123456789', 'Someone');
|
||||
expect(mocks.discord.addRoleToMember).toHaveBeenCalledWith('123456789', 'customer-role');
|
||||
expect((interaction as { editReply: jest.Mock }).editReply).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ content: expect.stringContaining('role claimed') }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('daily claim prompt', () => {
|
||||
it('skips a quiet channel', async () => {
|
||||
mocks.discord.listRecentMessages.mockResolvedValue([humanMessage(1), humanMessage(2)]);
|
||||
|
||||
await sut.postClaimPrompt();
|
||||
|
||||
expect(mocks.discord.sendMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('replaces the old prompt when the channel is active', async () => {
|
||||
const del = jest.fn().mockResolvedValue(void 0);
|
||||
const messages = [promptMessage(100, del), ...Array.from({ length: 30 }, (_, i) => humanMessage(200 + i))];
|
||||
mocks.discord.listRecentMessages.mockResolvedValue(messages);
|
||||
|
||||
await sut.postClaimPrompt();
|
||||
|
||||
expect(del).toHaveBeenCalled();
|
||||
expect(mocks.discord.sendMessage).toHaveBeenCalledWith(
|
||||
'general-channel',
|
||||
expect.objectContaining({ content: expect.stringContaining('chat-channel') }),
|
||||
);
|
||||
});
|
||||
|
||||
it('ignores activity that predates the last prompt', async () => {
|
||||
const messages = [...Array.from({ length: 30 }, (_, i) => humanMessage(10 + i)), promptMessage(100)];
|
||||
mocks.discord.listRecentMessages.mockResolvedValue(messages);
|
||||
|
||||
await sut.postClaimPrompt();
|
||||
|
||||
expect(mocks.discord.sendMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('/staff-notes command', () => {
|
||||
it('links the paired staff thread for staff inside a ticket', async () => {
|
||||
const thread = asThread({ id: 'thread-1', name: 'ticket-someone-6789-x', parentId: 'support-channel' });
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { LoggerRepository } from '@common/server/otel';
|
||||
import { Injectable, OnApplicationBootstrap } from '@nestjs/common';
|
||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
@@ -8,6 +9,7 @@ import {
|
||||
ChatInputCommandInteraction,
|
||||
EmbedBuilder,
|
||||
Interaction,
|
||||
Message,
|
||||
MessageFlags,
|
||||
ModalBuilder,
|
||||
ModalSubmitInteraction,
|
||||
@@ -61,7 +63,8 @@ export class SupportService implements OnApplicationBootstrap {
|
||||
} catch (error) {
|
||||
this.logger.error(error, 'failed to register slash commands — is the applications.commands scope granted?');
|
||||
}
|
||||
await this.discord.ensurePinnedSupportMessage(
|
||||
await this.discord.ensurePinnedMessage(
|
||||
env.DISCORD_SUPPORT_CHANNEL_ID,
|
||||
{
|
||||
content: 'Need help with FUTO Backups? Click below to open a private ticket with our staff.',
|
||||
components: [this.buttonRow(ComponentId.OpenTicket, 'Get support')],
|
||||
@@ -81,6 +84,9 @@ export class SupportService implements OnApplicationBootstrap {
|
||||
case ComponentId.CloseTicket: {
|
||||
return await this.onCloseRequested(interaction);
|
||||
}
|
||||
case ComponentId.ClaimRole: {
|
||||
return await this.onClaimRequested(interaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (interaction.isModalSubmit() && interaction.customId === ComponentId.TicketModal) {
|
||||
@@ -92,6 +98,9 @@ export class SupportService implements OnApplicationBootstrap {
|
||||
if (interaction.isChatInputCommand() && interaction.commandName === 'staff-notes') {
|
||||
return await this.onStaffNotesRequested(interaction);
|
||||
}
|
||||
if (interaction.isChatInputCommand() && interaction.commandName === 'claim-backups-role') {
|
||||
return await this.onClaimRequested(interaction);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error, 'failed to handle interaction');
|
||||
if (!interaction.isButton() && !interaction.isModalSubmit() && !interaction.isChatInputCommand()) {
|
||||
@@ -142,7 +151,79 @@ export class SupportService implements OnApplicationBootstrap {
|
||||
);
|
||||
}
|
||||
|
||||
private async startLinkFlow(interaction: ButtonInteraction) {
|
||||
private async onClaimRequested(interaction: ButtonInteraction | ChatInputCommandInteraction) {
|
||||
if (!env.DISCORD_CUSTOMER_ROLE_ID) {
|
||||
await interaction.reply({ content: 'Role claiming is not available yet.', flags: MessageFlags.Ephemeral });
|
||||
return;
|
||||
}
|
||||
|
||||
let link: DiscordLink | null;
|
||||
try {
|
||||
link = await withTimeout(this.api.getLink(interaction.user.id), LINK_LOOKUP_TIMEOUT_MS);
|
||||
} catch (error) {
|
||||
this.logger.error(error, 'link lookup failed');
|
||||
await interaction.reply({
|
||||
content: 'Role claiming is temporarily unavailable, please try again.',
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
if (!link) {
|
||||
return this.startLinkFlow(interaction, async () => {
|
||||
await this.discord.addRoleToMember(interaction.user.id, env.DISCORD_CUSTOMER_ROLE_ID);
|
||||
return { content: `Account linked and role claimed — welcome!${this.chatMention()}` };
|
||||
});
|
||||
}
|
||||
|
||||
await this.discord.addRoleToMember(interaction.user.id, env.DISCORD_CUSTOMER_ROLE_ID);
|
||||
await interaction.editReply({ content: `Role claimed — welcome!${this.chatMention()}` });
|
||||
}
|
||||
|
||||
@Cron(CronExpression.EVERY_DAY_AT_NOON)
|
||||
async postClaimPrompt() {
|
||||
if (!this.discord.enabled || !env.DISCORD_GENERAL_CHANNEL_ID || !env.DISCORD_CUSTOMER_ROLE_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
const messages = await this.discord.listRecentMessages(env.DISCORD_GENERAL_CHANNEL_ID, 50);
|
||||
const isPrompt = (message: Message) =>
|
||||
message.author.bot &&
|
||||
message.components.some(
|
||||
(row) =>
|
||||
'components' in row &&
|
||||
row.components.some((component) => 'customId' in component && component.customId === ComponentId.ClaimRole),
|
||||
);
|
||||
const lastPrompt = messages
|
||||
.filter((message) => isPrompt(message))
|
||||
.toSorted((a, b) => b.createdTimestamp - a.createdTimestamp)[0];
|
||||
const activity = messages.filter(
|
||||
(message) => !message.author.bot && (!lastPrompt || message.createdTimestamp > lastPrompt.createdTimestamp),
|
||||
);
|
||||
if (activity.length < env.CLAIM_PROMPT_MIN_MESSAGES) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastPrompt) {
|
||||
await lastPrompt
|
||||
.delete()
|
||||
.catch((error: unknown) => this.logger.warn(error, 'could not delete the previous claim prompt'));
|
||||
}
|
||||
await this.discord.sendMessage(env.DISCORD_GENERAL_CHANNEL_ID, {
|
||||
content: `FUTO Backups customer? Claim your role to unlock${env.DISCORD_CHAT_CHANNEL_ID ? ` <#${env.DISCORD_CHAT_CHANNEL_ID}>` : ' the customer chat'}.`,
|
||||
components: [this.buttonRow(ComponentId.ClaimRole, 'Claim your role')],
|
||||
});
|
||||
}
|
||||
|
||||
private chatMention(): string {
|
||||
return env.DISCORD_CHAT_CHANNEL_ID ? ` Say hi in <#${env.DISCORD_CHAT_CHANNEL_ID}>.` : '';
|
||||
}
|
||||
|
||||
private async startLinkFlow(
|
||||
interaction: ButtonInteraction | ChatInputCommandInteraction,
|
||||
onLinked?: () => Promise<{ content: string; components?: ActionRowBuilder<ButtonBuilder>[] }>,
|
||||
) {
|
||||
const { code, expiresAt } = await this.api.createLinkRequest(interaction.user.id, interaction.user.username);
|
||||
const url = `${env.WEB_URL}/link/discord?code=${code}`;
|
||||
|
||||
@@ -156,15 +237,18 @@ export class SupportService implements OnApplicationBootstrap {
|
||||
});
|
||||
|
||||
while (Date.now() < expiresAt.getTime()) {
|
||||
await sleep(LINK_POLL_INTERVAL_MS);
|
||||
const link = await this.api.getLink(interaction.user.id).catch(() => null);
|
||||
if (link) {
|
||||
await interaction.editReply({
|
||||
content: 'Account linked! Now open your ticket.',
|
||||
components: [this.buttonRow(ComponentId.CreateTicket, 'Open ticket')],
|
||||
});
|
||||
const success = onLinked
|
||||
? await onLinked()
|
||||
: {
|
||||
content: 'Account linked! Now open your ticket.',
|
||||
components: [this.buttonRow(ComponentId.CreateTicket, 'Open ticket')],
|
||||
};
|
||||
await interaction.editReply({ components: [], ...success });
|
||||
return;
|
||||
}
|
||||
await sleep(LINK_POLL_INTERVAL_MS);
|
||||
}
|
||||
|
||||
await interaction.editReply({
|
||||
|
||||
@@ -19,7 +19,10 @@ export const newDiscordRepositoryMock = (): jest.Mocked<RepositoryInterface<Disc
|
||||
enabled: true,
|
||||
start: jest.fn(),
|
||||
registerCommands: jest.fn(),
|
||||
ensurePinnedSupportMessage: jest.fn(),
|
||||
ensurePinnedMessage: jest.fn(),
|
||||
addRoleToMember: jest.fn(),
|
||||
listRecentMessages: jest.fn().mockResolvedValue([]),
|
||||
sendMessage: jest.fn(),
|
||||
createTicketThread: jest.fn(),
|
||||
createStaffThread: jest.fn(),
|
||||
listOpenTicketThreads: jest.fn().mockResolvedValue([]),
|
||||
|
||||
@@ -2,3 +2,6 @@ process.env.FUTO_BACKUPS_BOT_PORT ??= '3050';
|
||||
process.env.DISCORD_GUILD_ID ??= 'guild';
|
||||
process.env.DISCORD_STAFF_ROLE_ID ??= 'staff-role';
|
||||
process.env.DISCORD_SUPPORT_CHANNEL_ID ??= 'support-channel';
|
||||
process.env.DISCORD_GENERAL_CHANNEL_ID ??= 'general-channel';
|
||||
process.env.DISCORD_CHAT_CHANNEL_ID ??= 'chat-channel';
|
||||
process.env.DISCORD_CUSTOMER_ROLE_ID ??= 'customer-role';
|
||||
|
||||
@@ -94,5 +94,8 @@ export TF_VAR_yucca_discord_bot_token="op://yucca_tf_staging/YUCCA_DISCORD_BOT_T
|
||||
export TF_VAR_yucca_discord_guild_id="op://yucca_tf_staging/YUCCA_DISCORD_SUPPORT_IDS/discord/guild_id"
|
||||
export TF_VAR_yucca_discord_staff_role_id="op://yucca_tf_staging/YUCCA_DISCORD_SUPPORT_IDS/discord/staff_role_id"
|
||||
export TF_VAR_yucca_discord_support_channel_id="op://yucca_tf_staging/YUCCA_DISCORD_SUPPORT_IDS/discord/support_channel_id"
|
||||
# export TF_VAR_yucca_discord_general_channel_id="op://yucca_tf_staging/YUCCA_DISCORD_SUPPORT_IDS/discord/general_channel_id"
|
||||
# export TF_VAR_yucca_discord_chat_channel_id="op://yucca_tf_staging/YUCCA_DISCORD_SUPPORT_IDS/discord/chat_channel_id"
|
||||
# export TF_VAR_yucca_discord_customer_role_id="op://yucca_tf_staging/YUCCA_DISCORD_SUPPORT_IDS/discord/customer_role_id"
|
||||
export TF_VAR_sietch_transcripts_access_key="op://yucca_tf_staging/SIETCH_CEPH_S3_SVC_YUCCA_TRANSCRIPTS_ACCESS_KEY/password"
|
||||
export TF_VAR_sietch_transcripts_secret_key="op://yucca_tf_staging/SIETCH_CEPH_S3_SVC_YUCCA_TRANSCRIPTS_SECRET_KEY/password"
|
||||
|
||||
@@ -94,5 +94,8 @@ export TF_VAR_vmauth_remote_write_password="op://shared_tf_prod/O11Y_VICTORIAMET
|
||||
# export TF_VAR_yucca_discord_guild_id="op://yucca_tf_prod/YUCCA_DISCORD_SUPPORT_IDS/discord/guild_id"
|
||||
# export TF_VAR_yucca_discord_staff_role_id="op://yucca_tf_prod/YUCCA_DISCORD_SUPPORT_IDS/discord/staff_role_id"
|
||||
# export TF_VAR_yucca_discord_support_channel_id="op://yucca_tf_prod/YUCCA_DISCORD_SUPPORT_IDS/discord/support_channel_id"
|
||||
# export TF_VAR_yucca_discord_general_channel_id="op://yucca_tf_prod/YUCCA_DISCORD_SUPPORT_IDS/discord/general_channel_id"
|
||||
# export TF_VAR_yucca_discord_chat_channel_id="op://yucca_tf_prod/YUCCA_DISCORD_SUPPORT_IDS/discord/chat_channel_id"
|
||||
# export TF_VAR_yucca_discord_customer_role_id="op://yucca_tf_prod/YUCCA_DISCORD_SUPPORT_IDS/discord/customer_role_id"
|
||||
# export TF_VAR_spice_transcripts_access_key="op://yucca_tf_prod/SPICE_CEPH_S3_SVC_YUCCA_TRANSCRIPTS_ACCESS_KEY/password"
|
||||
# export TF_VAR_spice_transcripts_secret_key="op://yucca_tf_prod/SPICE_CEPH_S3_SVC_YUCCA_TRANSCRIPTS_SECRET_KEY/password"
|
||||
|
||||
@@ -253,6 +253,9 @@ resource "kubernetes_secret_v1" "futo_backups_bot" {
|
||||
DISCORD_GUILD_ID = var.yucca_discord_guild_id
|
||||
DISCORD_STAFF_ROLE_ID = var.yucca_discord_staff_role_id
|
||||
DISCORD_SUPPORT_CHANNEL_ID = var.yucca_discord_support_channel_id
|
||||
DISCORD_GENERAL_CHANNEL_ID = var.yucca_discord_general_channel_id
|
||||
DISCORD_CHAT_CHANNEL_ID = var.yucca_discord_chat_channel_id
|
||||
DISCORD_CUSTOMER_ROLE_ID = var.yucca_discord_customer_role_id
|
||||
TRANSCRIPT_S3_ACCESS_KEY_ID = var.spice_transcripts_access_key
|
||||
TRANSCRIPT_S3_SECRET_ACCESS_KEY = var.spice_transcripts_secret_key
|
||||
}
|
||||
|
||||
@@ -287,3 +287,22 @@ variable "yucca_discord_support_channel_id" {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
variable "yucca_discord_chat_channel_id" {
|
||||
description = "#chat channel id mentioned after a role claim (YUCCA_DISCORD_SUPPORT_IDS)."
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "yucca_discord_customer_role_id" {
|
||||
description = "FUTO Backups customer role id futo-backups-bot assigns on claim (YUCCA_DISCORD_SUPPORT_IDS)."
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "yucca_discord_general_channel_id" {
|
||||
description = "#general channel id for futo-backups-bot's daily claim prompt (YUCCA_DISCORD_SUPPORT_IDS)."
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
@@ -251,6 +251,9 @@ resource "kubernetes_secret_v1" "futo_backups_bot" {
|
||||
DISCORD_GUILD_ID = var.yucca_discord_guild_id
|
||||
DISCORD_STAFF_ROLE_ID = var.yucca_discord_staff_role_id
|
||||
DISCORD_SUPPORT_CHANNEL_ID = var.yucca_discord_support_channel_id
|
||||
DISCORD_GENERAL_CHANNEL_ID = var.yucca_discord_general_channel_id
|
||||
DISCORD_CHAT_CHANNEL_ID = var.yucca_discord_chat_channel_id
|
||||
DISCORD_CUSTOMER_ROLE_ID = var.yucca_discord_customer_role_id
|
||||
TRANSCRIPT_S3_ACCESS_KEY_ID = var.sietch_transcripts_access_key
|
||||
TRANSCRIPT_S3_SECRET_ACCESS_KEY = var.sietch_transcripts_secret_key
|
||||
}
|
||||
|
||||
@@ -260,3 +260,22 @@ variable "yucca_discord_support_channel_id" {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
variable "yucca_discord_chat_channel_id" {
|
||||
description = "#chat channel id mentioned after a role claim (YUCCA_DISCORD_SUPPORT_IDS)."
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "yucca_discord_customer_role_id" {
|
||||
description = "FUTO Backups customer role id futo-backups-bot assigns on claim (YUCCA_DISCORD_SUPPORT_IDS)."
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "yucca_discord_general_channel_id" {
|
||||
description = "#general channel id for futo-backups-bot's daily claim prompt (YUCCA_DISCORD_SUPPORT_IDS)."
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user