Skip to content

Commit

Permalink
refactor(configuration-ticket-threads): allow deleting category with …
Browse files Browse the repository at this point in the history
…tickets
  • Loading branch information
CarelessInternet committed Jun 5, 2024
1 parent ba827f3 commit 59f646d
Show file tree
Hide file tree
Showing 21 changed files with 892 additions and 1,122 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/bot/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

ARG NODE_MAJOR_VERSION=22
# Necessary to run "pnpm install" when the Dev Container has started.
ARG PNPM_MAJOR_VERSION=8
ARG PNPM_MAJOR_VERSION=9

FROM node:${NODE_MAJOR_VERSION}-alpine as base
RUN --mount=type=cache,target=/root/.npm \
Expand Down
1 change: 0 additions & 1 deletion .prettierrc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const config = {
trailingComma: 'all',
useTabs: true,
embeddedLanguageFormatting: 'auto',
parser: 'typescript',
plugins: ['prettier-plugin-tailwindcss'],
};

Expand Down
2 changes: 1 addition & 1 deletion apps/bot/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

ARG NODE_MAJOR_VERSION=22
ARG PNPM_MAJOR_VERSION=9
ARG TURBO_MAJOR_VERSION=1
ARG TURBO_MAJOR_VERSION=2

FROM node:${NODE_MAJOR_VERSION}-alpine as base
RUN --mount=type=cache,target=/root/.npm \
Expand Down
4 changes: 2 additions & 2 deletions apps/bot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
"@ticketer/djs-framework": "workspace:*",
"@ticketer/env": "workspace:*",
"chalk": "^5.3.0",
"discord.js": "^14.15.2",
"discord.js": "^14.15.3",
"tsx": "^3.13.0",
"typesafe-i18n": "^5.26.2"
},
"devDependencies": {
"@ticketer/eslint-config": "workspace:*",
"@types/node": "^20.12.8"
"@types/node": "^20.14.2"
}
}
138 changes: 105 additions & 33 deletions apps/bot/src/commands/staff/configuration-ticket-threads.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
ChannelSelectMenuBuilder,
ChannelType,
ModalBuilder,
Expand All @@ -10,6 +12,7 @@ import {
TextInputBuilder,
TextInputStyle,
channelMention,
inlineCode,
roleMention,
} from 'discord.js';
import {
Expand Down Expand Up @@ -38,9 +41,9 @@ import {
eq,
like,
not,
sql,
ticketThreadsCategories,
ticketThreadsConfigurations,
ticketsThreads,
} from '@ticketer/database';

const MAXIMUM_CATEGORY_AMOUNT = 10;
Expand Down Expand Up @@ -258,15 +261,23 @@ export default class extends Command.Interaction {
.setName('edit')
.setDescription('Edit a category for thread tickets.')
.addStringOption((option) =>
option.setName('title').setDescription("The category's title.").setAutocomplete(true).setRequired(true),
option
.setName('title')
.setDescription('The title of the category of which you want to edit.')
.setAutocomplete(true)
.setRequired(true),
),
)
.addSubcommand((subcommand) =>
subcommand
.setName('delete')
.setDescription('Delete a category from thread tickets.')
.addStringOption((option) =>
option.setName('title').setDescription("The category's title.").setAutocomplete(true).setRequired(true),
option
.setName('title')
.setDescription('The title of the category of which you want to delete.')
.setAutocomplete(true)
.setRequired(true),
),
),
);
Expand Down Expand Up @@ -455,44 +466,65 @@ export default class extends Command.Interaction {
@DeferReply()
@HasGlobalConfiguration
private async categoryDelete({ interaction }: Command.Context<'chat'>) {
try {
const id = parseInteger(interaction.options.getString('title', true));

if (id === undefined) return;

// TODO: When the MariaDB driver gets released, change the query to use the RETURNING clause.
const query = await database
.delete(ticketThreadsCategories)
.where(sql`${ticketThreadsCategories.id} = ${id} RETURNING ${ticketThreadsCategories.categoryTitle}`);
const id = parseInteger(interaction.options.getString('title', true));

const title = (query.at(0) as unknown as Pick<typeof ticketThreadsCategories.$inferSelect, 'categoryTitle'>[]).at(
0,
)?.categoryTitle;
if (id === undefined) return;

const [row] = await database
.select({ amount: count(), title: ticketThreadsCategories.categoryTitle })
.from(ticketsThreads)
.where(
and(
eq(ticketsThreads.guildId, interaction.guildId),
eq(ticketsThreads.categoryId, id),
eq(ticketsThreads.state, 'active'),
),
)
.innerJoin(ticketThreadsCategories, eq(ticketsThreads.categoryId, ticketThreadsCategories.id));
const amount = row?.amount ?? 0;

if (amount > 0) {
const confirmButton = new ButtonBuilder()
.setCustomId(super.customId('ticket_threads_category_delete_confirm', id))
.setEmoji('☑️')
.setLabel('Confirm')
.setStyle(ButtonStyle.Success);
const cancelButton = new ButtonBuilder()
.setCustomId(super.customId('ticket_threads_category_delete_cancel', id))
.setEmoji('✖️')
.setLabel('Cancel')
.setStyle(ButtonStyle.Danger);

const actionRow = new ActionRowBuilder<ButtonBuilder>().setComponents(confirmButton, cancelButton);
const embed = super
.userEmbed(interaction.user)
.setTitle('Deleted the Thread Ticket Category')
.setTitle('Are you sure you want to proceed?')
.setDescription(
`${interaction.user.toString()} deleted the category with the following title: ${title ?? 'No Title Found'}.`,
`The category you want to delete still has active tickets. Are you sure you want to delete the ${row?.title ? inlineCode(row.title) : 'No Title Found'} category?
In addition, you would have to manually delete each thread which has that category.`,
);

return interaction.editReply({ embeds: [embed] });
} catch (error) {
const isObject = (object: unknown): object is object =>
typeof object === 'object' && !Array.isArray(object) && object !== null;

if (isObject(error) && 'errno' in error && error.errno === 1451) {
return interaction.editReply({
embeds: [
super
.userEmbedError(interaction.user)
.setDescription(
'You must delete all of the tickets which has this category before deleting the category itself.',
),
],
});
}
return interaction.editReply({
components: [actionRow],
embeds: [embed],
});
}

// TODO: change to use the RETURNING clause for MariaDB when it gets released.

Check warning on line 513 in apps/bot/src/commands/staff/configuration-ticket-threads.ts

View workflow job for this annotation

GitHub Actions / build (22)

Unexpected 'todo' comment: 'TODO: change to use the RETURNING clause...'

Check warning on line 513 in apps/bot/src/commands/staff/configuration-ticket-threads.ts

View workflow job for this annotation

GitHub Actions / build (22)

Unexpected 'todo' comment: 'TODO: change to use the RETURNING clause...'
const [result] = await database
.select({ title: ticketThreadsCategories.categoryTitle })
.from(ticketThreadsCategories)
.where(eq(ticketThreadsCategories.id, id));
await database.delete(ticketThreadsCategories).where(eq(ticketThreadsCategories.id, id));

const embed = super
.userEmbed(interaction.user)
.setTitle('Deleted the Thread Ticket Category')
.setDescription(
`${interaction.user.toString()} deleted the category with the following title: ${result?.title ? inlineCode(result.title) : 'No Title Found'}.`,
);

return interaction.editReply({ embeds: [embed] });
}
}

Expand Down Expand Up @@ -525,6 +557,8 @@ export class ComponentInteraction extends Component.Interaction {
super.dynamicCustomId('ticket_threads_category_configuration_channel'),
super.dynamicCustomId('ticket_threads_category_configuration_logs_channel'),
super.dynamicCustomId('ticket_threads_category_configuration_managers'),
super.dynamicCustomId('ticket_threads_category_delete_confirm'),
super.dynamicCustomId('ticket_threads_category_delete_cancel'),
super.dynamicCustomId('ticket_threads_category_view_previous'),
super.dynamicCustomId('ticket_threads_category_view_next'),
];
Expand All @@ -544,6 +578,10 @@ export class ComponentInteraction extends Component.Interaction {
case super.dynamicCustomId('ticket_threads_category_configuration_managers'): {
return interaction.isRoleSelectMenu() && this.categoryManagers({ interaction });
}
case super.dynamicCustomId('ticket_threads_category_delete_confirm'):
case super.dynamicCustomId('ticket_threads_category_delete_cancel'): {
return interaction.isButton() && this.confirmDeleteCategory({ interaction });
}
case super.dynamicCustomId('ticket_threads_category_view_previous'):
case super.dynamicCustomId('ticket_threads_category_view_next'): {
interaction.isButton() && this.categoryView({ interaction });
Expand Down Expand Up @@ -691,6 +729,40 @@ export class ComponentInteraction extends Component.Interaction {
return interaction.editReply({ embeds: [embed], components: [] });
}

@DeferUpdate
private async confirmDeleteCategory({ interaction }: Component.Context<'button'>) {
const { customId, dynamicValue } = super.extractCustomId(interaction.customId, true);
const confirmDeletion = customId.includes('confirm');
const id = parseInteger(dynamicValue);

if (id === undefined) return;

// TODO: change to use the RETURNING clause for MariaDB when it gets released.

Check warning on line 740 in apps/bot/src/commands/staff/configuration-ticket-threads.ts

View workflow job for this annotation

GitHub Actions / build (22)

Unexpected 'todo' comment: 'TODO: change to use the RETURNING clause...'

Check warning on line 740 in apps/bot/src/commands/staff/configuration-ticket-threads.ts

View workflow job for this annotation

GitHub Actions / build (22)

Unexpected 'todo' comment: 'TODO: change to use the RETURNING clause...'
const [row] = await database
.select({ title: ticketThreadsCategories.categoryTitle })
.from(ticketThreadsCategories)
.where(eq(ticketThreadsCategories.id, id));

if (confirmDeletion) {
await database.delete(ticketsThreads).where(eq(ticketsThreads.categoryId, id));
await database.delete(ticketThreadsCategories).where(eq(ticketThreadsCategories.id, id));
}

return interaction.editReply({
components: [],
embeds: [
super
.userEmbed(interaction.user)
.setTitle(confirmDeletion ? 'Deleted the Category' : 'Deletion Cancelled')
.setDescription(
confirmDeletion
? `${interaction.user.toString()} deleted the ${row?.title ? inlineCode(row.title) : 'No Title Found'} category.`
: `The deletion of the category ${row?.title ? inlineCode(row.title) : 'No Title Found'} has been cancelled.`,
),
],
});
}

@DeferUpdate
private categoryView({ interaction }: Component.Context<'button'>) {
const { customId, dynamicValue } = super.extractCustomId(interaction.customId, true);
Expand Down
10 changes: 5 additions & 5 deletions apps/bot/src/i18n/en-GB/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { BaseTranslation } from '../i18n-types.js';
import ERROR_TITLE from './errorTitle.js';
import type { BaseTranslation } from '../i18n-types';
import ERROR_TITLE from './errorTitle';
import type { PresenceUpdateStatus } from 'discord.js';
import automaticThreads from './automaticThreads.js';
import threads from './threads.js';
import userForums from './userForums.js';
import automaticThreads from './automaticThreads';
import threads from './threads';
import userForums from './userForums';

const en_GB = {
commands: {
Expand Down
14 changes: 7 additions & 7 deletions apps/bot/src/i18n/sv-SE/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import ERROR_TITLE from './errorTitle.js';

import type { Translation } from '../i18n-types.js';
import automaticThreads from './automaticThreads.js';
import threads from './threads.js';
import userForums from './userForums.js';
import type { DeepPartial } from '@/utils';
import ERROR_TITLE from './errorTitle';
import type { Translation } from '../i18n-types';
import automaticThreads from './automaticThreads';
import threads from './threads';
import userForums from './userForums';

const sv_SE = {
commands: {
Expand Down Expand Up @@ -248,6 +248,6 @@ const sv_SE = {
threads,
userForums,
},
} satisfies Translation;
} satisfies DeepPartial<Translation>;

export default sv_SE;
3 changes: 2 additions & 1 deletion apps/bot/src/i18n/sv-SE/threads.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { DeepPartial } from '@/utils';
import ERROR_TITLE from './errorTitle';
import type { Translation } from '../i18n-types';

Expand Down Expand Up @@ -229,4 +230,4 @@ export default {
lockedAndArchived: 'Låst och Stängt',
},
},
} satisfies Translation['tickets']['threads'];
} satisfies DeepPartial<Translation['tickets']['threads']>;
3 changes: 2 additions & 1 deletion apps/bot/src/i18n/sv-SE/userForums.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { DeepPartial } from '@/utils';
import ERROR_TITLE from './errorTitle';
import type { Translation } from '../i18n-types';

Expand Down Expand Up @@ -108,4 +109,4 @@ export default {
},
},
},
} satisfies Translation['tickets']['userForums'];
} satisfies DeepPartial<Translation['tickets']['userForums']>;
5 changes: 5 additions & 0 deletions apps/bot/src/utils/utility/DeepPartial.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type DeepPartial<T> = T extends object
? {
[P in keyof T]?: DeepPartial<T[P]>;
}
: T;
1 change: 1 addition & 0 deletions apps/bot/src/utils/utility/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './capitalise';
export * from './DeepPartial';
export * from './extractEmoji';
export * from './format';
export * from './invalidTicket';
Expand Down
10 changes: 5 additions & 5 deletions apps/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-tooltip": "^1.0.7",
"@vercel/analytics": "^1.2.2",
"@vercel/analytics": "^1.3.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"lucide-react": "^0.378.0",
"lucide-react": "^0.383.0",
"next": "14.2.3",
"next-themes": "^0.3.0",
"react": "^18.3.1",
Expand All @@ -28,14 +28,14 @@
},
"devDependencies": {
"@ticketer/eslint-config": "workspace:*",
"@types/node": "^20.12.8",
"@types/react": "^18.3.1",
"@types/node": "^20.14.2",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
"eslint-config-next": "14.2.3",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3",
"tailwindcss": "^3.4.4",
"typescript": "^5.4.5"
}
}
2 changes: 1 addition & 1 deletion apps/website/src/components/ui/navigation-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const NavigationMenuContent = React.forwardRef<
<NavigationMenuPrimitive.Content
ref={reference}
className={cn(
'data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 left-0 top-0 w-full md:absolute md:w-auto ',
'data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 left-0 top-0 w-full md:absolute md:w-auto',
className,
)}
{...properties}
Expand Down
2 changes: 1 addition & 1 deletion apps/website/src/components/ui/sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const SheetOverlay = React.forwardRef<
>(({ className, ...properties }, reference) => (
<SheetPrimitive.Overlay
className={cn(
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80',
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80',
className,
)}
{...properties}
Expand Down
Loading

0 comments on commit 59f646d

Please sign in to comment.