From fd558bd975cb22b91cf7a278bdfcfeb67f334f89 Mon Sep 17 00:00:00 2001 From: Danya H Date: Tue, 6 Aug 2024 00:06:09 +0100 Subject: [PATCH] changes: - prisma migration - better ticket handling - better error handling --- db/json.sqlite-journal | Bin 8720 -> 12824 bytes .../migration.sql | 24 ++--- .../20240805230451_fixes/migration.sql | 58 +++++++++++ prisma/schema.prisma | 34 +++---- src/commands/admin/create-ticket-system.ts | 96 ++++++++---------- src/commands/admin/help.ts | 12 +-- src/commands/admin/remove-active-ticket.ts | 43 ++++---- src/commands/feedback.ts | 25 +++-- src/commands/setters/set-banner-ticket.ts | 17 ++-- src/commands/setters/set-banner-welcome.ts | 17 ++-- src/commands/setters/set-feedback-channel.ts | 8 +- src/commands/setters/set-order-channel.ts | 9 +- src/commands/setters/set-portfolio-channel.ts | 9 +- src/commands/setters/set-price-channel.ts | 17 ++-- src/commands/setters/set-status.ts | 43 +++++--- src/commands/setters/set-welcome-channel.ts | 30 +++--- src/db.ts | 3 - src/db/banner.ts | 13 +++ src/db/index.ts | 9 ++ src/db/role.ts | 13 +++ src/db/settings.ts | 16 +++ src/db/ticket.ts | 19 ++++ src/db/user.ts | 82 +++++++++++++++ src/events/guildMemberAdd.ts | 30 +++--- 24 files changed, 425 insertions(+), 202 deletions(-) rename prisma/migrations/{20240805201030_init => 20240805205334_}/migration.sql (60%) create mode 100644 prisma/migrations/20240805230451_fixes/migration.sql delete mode 100644 src/db.ts create mode 100644 src/db/banner.ts create mode 100644 src/db/index.ts create mode 100644 src/db/role.ts create mode 100644 src/db/settings.ts create mode 100644 src/db/ticket.ts create mode 100644 src/db/user.ts diff --git a/db/json.sqlite-journal b/db/json.sqlite-journal index 12d8188034163d75452e6cd95e58b90d08b97f07..0f9b43dd22e5319c3e50475975c50d287ef2d7b1 100644 GIT binary patch literal 12824 zcmeI2zi-<{6vs)&mSn}QyBI=-09T;_EI2BTKSgelWjatRQ;tNTbr<4D9&JJtsgM+s z3`Jq5$lMOyGj!_O{u4ogjzPz+>0i*XM@lj*(XEDf33?}xBHz9D?t7niJZccb(Dpwk zu(nMAtbO#3*-6NWHcEgJpaduZN`Mle1SkPYfD)htC;>{~Lnm-iS}PAOwi$_GHa0~? zk)YGy>VhCCbx}e}U6E9=?pVCgaBR-vF~ouh8(5V@Ub1+J7dTmQorbH}jwm%$)l#q| z2oS;sf-V*~$FePpcNI<)R7q{{kbFd_KoSW>i8EdAIgZ&MK7I51Zx@?vDrI9ol_EDF z<{VzEi;8O1MN#GJilVx8QRW)1Y}-iXEXA@^$K_-JBCK$nL{tgTb`@TRmLoedZ`%s5 zLf4gD$HEpwu81sEkp)pypezVv*e5@2UkQRlxM-%v*Wdj7w(=_}ZRtZVFV#;8Py& z9P?Hn7whvI^37hm{%N?VZ1wQt1z7`|ZvzTm0QW?<(c*%R9tC8zn#qPy&m|&pmz}!i(rX_#d#B(F?G!WzMz^h#9LJh&Je&sE=O_rU-zKq!_7w@51XOi}+vlKPZ z(~h*Brnj^{s18V8GOu(>HPF?;q1MsJy!+a0R>U+oIZRQsn*CPuP%GEIEfy=gyX*zZ zsHF+gqqInDmiIk)IbLjsk(1+9u3bDRWOciIkSRDwxkl4-aZ$z8_&U=SD{AvYzSeg+5M?!lK61bSnzeL^)cs)s=~iZuBVEPSy*R{eAW{QyD+? z$&YfC%Q z6k7lNn7J4`7P5yc+{R&)xS{WbEBr=ih?_w-a&WZ5a~yd#Ug7gBjE0>MIji0L)UBVO zWD?)JD$`Ux@j1qEjL!1wqFj6VHCwE39Q(@5YGe|D@d%kC?<_)z7Y1?OvbI>ayu(5T z)2(u;WgFl1jdFPtJjeDho{XxCaX0Okt`~UmId)8xT=!15Dx-a@&DQB z&=1cD%UnxcXUU?__sM|i^0_|h(vWe2q9msa%)PEQcCytTH3uC7*pn#2L1HG}2$SH^ zcs3jwkA06MiW5AJO*@@(RCC4&e;Na)8#}rMHFuOPN Ug)_Ol?GAa>X8!?rUD*BnAENJIY5)KL delta 183 zcmWm8ISRr+7(n5PxI-AVv$wGrndP6Y7jIxKvjqY{8%Zmuopawo#(KyGB&UK z;N9YR{TO!o8LknopVL`|Pt713Z<`zmgnFZ-zXR=w_bWoElea;WcJGV3uS50z0blno6#xJL diff --git a/prisma/migrations/20240805201030_init/migration.sql b/prisma/migrations/20240805205334_/migration.sql similarity index 60% rename from prisma/migrations/20240805201030_init/migration.sql rename to prisma/migrations/20240805205334_/migration.sql index 193c395..b057d27 100644 --- a/prisma/migrations/20240805201030_init/migration.sql +++ b/prisma/migrations/20240805205334_/migration.sql @@ -1,21 +1,21 @@ -- CreateTable CREATE TABLE "Settings" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "welcomeChannelId" INTEGER NOT NULL, - "feedbackChannelId" INTEGER NOT NULL, - "portfolioChannelId" INTEGER NOT NULL, - "makeAnOrderChannelId" INTEGER NOT NULL, - "priceChannelId" INTEGER NOT NULL, - "workLoadChannelId" INTEGER NOT NULL, - "worlLoadStatus" INTEGER NOT NULL, - "workloadMessageId" INTEGER NOT NULL + "welcomeChannelId" TEXT NOT NULL, + "feedbackChannelId" TEXT NOT NULL, + "portfolioChannelId" TEXT NOT NULL, + "makeAnOrderChannelId" TEXT NOT NULL, + "priceChannelId" TEXT NOT NULL, + "workLoadChannelId" TEXT NOT NULL, + "worlLoadStatus" TEXT NOT NULL, + "workloadMessageId" TEXT NOT NULL ); -- CreateTable CREATE TABLE "Role" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "welcomeRoleId" INTEGER NOT NULL, - "tickerRoleId" INTEGER NOT NULL + "welcomeRoleId" TEXT NOT NULL, + "tickerRoleId" TEXT NOT NULL ); -- CreateTable @@ -28,13 +28,13 @@ CREATE TABLE "Banner" ( -- CreateTable CREATE TABLE "User" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "discordId" INTEGER NOT NULL + "discordId" TEXT NOT NULL ); -- CreateTable CREATE TABLE "Ticket" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "channelId" INTEGER NOT NULL, + "channelId" TEXT NOT NULL, "closed" BOOLEAN NOT NULL, "userId" INTEGER, CONSTRAINT "Ticket_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE diff --git a/prisma/migrations/20240805230451_fixes/migration.sql b/prisma/migrations/20240805230451_fixes/migration.sql new file mode 100644 index 0000000..18d0b3d --- /dev/null +++ b/prisma/migrations/20240805230451_fixes/migration.sql @@ -0,0 +1,58 @@ +/* + Warnings: + + - You are about to drop the column `tickerRoleId` on the `Role` table. All the data in the column will be lost. + - You are about to drop the column `worlLoadStatus` on the `Settings` table. All the data in the column will be lost. + - The primary key for the `Ticket` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to drop the column `id` on the `Ticket` table. All the data in the column will be lost. + - The primary key for the `User` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to drop the column `id` on the `User` table. All the data in the column will be lost. + - Added the required column `ticketRoleId` to the `Role` table without a default value. This is not possible if the table is not empty. + - Added the required column `workLoadStatus` to the `Settings` table without a default value. This is not possible if the table is not empty. + - Made the column `userId` on table `Ticket` required. This step will fail if there are existing NULL values in that column. + +*/ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Role" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "welcomeRoleId" TEXT NOT NULL, + "ticketRoleId" TEXT NOT NULL +); +INSERT INTO "new_Role" ("id", "welcomeRoleId") SELECT "id", "welcomeRoleId" FROM "Role"; +DROP TABLE "Role"; +ALTER TABLE "new_Role" RENAME TO "Role"; +CREATE TABLE "new_Settings" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "welcomeChannelId" TEXT NOT NULL, + "feedbackChannelId" TEXT NOT NULL, + "portfolioChannelId" TEXT NOT NULL, + "makeAnOrderChannelId" TEXT NOT NULL, + "priceChannelId" TEXT NOT NULL, + "workLoadChannelId" TEXT NOT NULL, + "workLoadStatus" TEXT NOT NULL, + "workloadMessageId" TEXT NOT NULL +); +INSERT INTO "new_Settings" ("feedbackChannelId", "id", "makeAnOrderChannelId", "portfolioChannelId", "priceChannelId", "welcomeChannelId", "workLoadChannelId", "workloadMessageId") SELECT "feedbackChannelId", "id", "makeAnOrderChannelId", "portfolioChannelId", "priceChannelId", "welcomeChannelId", "workLoadChannelId", "workloadMessageId" FROM "Settings"; +DROP TABLE "Settings"; +ALTER TABLE "new_Settings" RENAME TO "Settings"; +CREATE TABLE "new_Ticket" ( + "channelId" TEXT NOT NULL, + "closed" BOOLEAN NOT NULL, + "userId" TEXT NOT NULL, + CONSTRAINT "Ticket_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("discordId") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_Ticket" ("channelId", "closed", "userId") SELECT "channelId", "closed", "userId" FROM "Ticket"; +DROP TABLE "Ticket"; +ALTER TABLE "new_Ticket" RENAME TO "Ticket"; +CREATE UNIQUE INDEX "Ticket_channelId_key" ON "Ticket"("channelId"); +CREATE TABLE "new_User" ( + "discordId" TEXT NOT NULL +); +INSERT INTO "new_User" ("discordId") SELECT "discordId" FROM "User"; +DROP TABLE "User"; +ALTER TABLE "new_User" RENAME TO "User"; +CREATE UNIQUE INDEX "User_discordId_key" ON "User"("discordId"); +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5d0cba9..505a35c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -8,21 +8,21 @@ datasource db { } model Settings { - id Int @id @default(autoincrement()) - welcomeChannelId Int - feedbackChannelId Int - portfolioChannelId Int - makeAnOrderChannelId Int - priceChannelId Int - workLoadChannelId Int - worlLoadStatus Int - workloadMessageId Int + id Int @id @default(autoincrement()) + welcomeChannelId String + feedbackChannelId String + portfolioChannelId String + makeAnOrderChannelId String + priceChannelId String + workLoadChannelId String + workLoadStatus String + workloadMessageId String } model Role { - id Int @id @default(autoincrement()) - welcomeRoleId Int - tickerRoleId Int + id Int @id @default(autoincrement()) + welcomeRoleId String + ticketRoleId String } model Banner { @@ -32,15 +32,13 @@ model Banner { } model User { - id Int @id @default(autoincrement()) - discordId Int + discordId String @unique tickets Ticket[] } model Ticket { - id Int @id - channelId Int + channelId String @unique closed Boolean - User User? @relation(fields: [userId], references: [id]) - userId Int? + User User @relation(fields: [userId], references: [discordId]) + userId String } diff --git a/src/commands/admin/create-ticket-system.ts b/src/commands/admin/create-ticket-system.ts index fba851f..9ab5e29 100644 --- a/src/commands/admin/create-ticket-system.ts +++ b/src/commands/admin/create-ticket-system.ts @@ -14,7 +14,7 @@ import { userMention, } from 'discord.js' -import { db, DBTableEnum } from '../../db' +import * as db from '../../db' import { Workload, ticketCreateButton, @@ -26,8 +26,6 @@ import { } from '../../utils' import { logger } from '../../lib' -type Ticket = { user: string; channel: string } - @Discord() export class CreateTicketSystem { @Slash({ @@ -55,11 +53,10 @@ export class CreateTicketSystem { return } - const pricesChannelId = await db.get(DBTableEnum.PRICE_CHANNEL) - const bannerURL = await db.get(DBTableEnum.BANNER_TICKET) - const workload = await db.get(DBTableEnum.WORKLOAD) + const settings = await db.getSettings() + const banners = await db.getBanner() - if (!pricesChannelId) { + if (!settings || !settings.priceChannelId) { logger.error( 'Missing prices channel', 'Set using /set-prices-channel', @@ -69,14 +66,14 @@ export class CreateTicketSystem { ) return } - if (!bannerURL) { + if (!banners || !banners.ticketUrl) { logger.error('Missing banner', 'Set using /set-banner-ticket') await interaction.editReply( '❌ Missing banner\nSet using /set-banner-ticket', ) return } - if (!workload) { + if (!settings.workLoadStatus) { logger.error( 'Missing workload status', 'Set using /set-workload-status', @@ -88,21 +85,35 @@ export class CreateTicketSystem { } await db - .set(DBTableEnum.TICKET_ROLE, role.id) - .then(() => logger.database(DBTableEnum.TICKET_ROLE, role.id)) + .setRole('ticketRoleId', role.id) + .then(() => logger.database('Ticket role', role.id)) + .catch(async () => { + logger.error('Ticket role id', 'Failed to set ticket role id') + await interaction.editReply('❌ Failed to set ticket role id') + return + }) // create ticket embed + button await interaction.channel.send({ components: [ticketCreateButton()], - embeds: [ticketCreateEmbed({ bannerURL, pricesChannelId })], + embeds: [ + ticketCreateEmbed({ + bannerURL: banners.ticketUrl, + pricesChannelId: settings.priceChannelId, + }), + ], }) // workload embed const workloadMessage = await interaction.channel.send({ - embeds: [ticketWorkloadEmbed({ workload })], + embeds: [ + ticketWorkloadEmbed({ + workload: settings.workLoadStatus as Workload, + }), + ], }) - await db.set(DBTableEnum.WORKLOAD_MESSAGE, workloadMessage.id) - await db.set(DBTableEnum.WORKLOAD_CHANNEL, interaction.channel.id) + await db.setSettings('workloadMessageId', workloadMessage.id) + await db.setSettings('workLoadChannelId', interaction.channel.id) // close interaction await interaction.editReply('✔️ Created ticket system') @@ -127,19 +138,16 @@ export class CreateTicketSystem { return } - const hasOpenedTicket = await db - .get(DBTableEnum.TICKET_OWNERS) - .then(r => - Array.isArray(r) - ? !!r.find(ticket => ticket.user === interaction.user.id) - : false, - ) - if (hasOpenedTicket) { - await interaction.editReply(`❌ You already have an active ticket`) + const hasManyTickets = + (await db.getUserOpenedTicketsAmount(interaction.user.id)) > 3 + if (hasManyTickets) { + await interaction.editReply(`❌ You already have 3 opened tickets`) return } - const ticketRole = await db.get(DBTableEnum.TICKET_ROLE) + const roles = await db.getRole() + + const ticketRole = roles?.ticketRoleId let threadChannel: ThreadChannel try { @@ -159,9 +167,9 @@ export class CreateTicketSystem { } await db - .push(DBTableEnum.TICKET_OWNERS, { - user: interaction.user.id, - channel: threadChannel.id, + .createUser(interaction.user.id, { + closed: false, + channelId: threadChannel.id, }) .then(() => { logger.database( @@ -169,14 +177,10 @@ export class CreateTicketSystem { `Added User: ${interaction.user.id}\n Channel: ${threadChannel.id}`, ) }) - logger.database( - 'Updated active tickets', - `Removed channel ${interaction.channel.id}`, - ) // welcoming message in ticket const threadChannelMessage: MessageCreateOptions = { - content: `${userMention(interaction.user.id)} ${roleMention(ticketRole)}`, + content: `${userMention(interaction.user.id)} ${roleMention(ticketRole!)}`, embeds: [ ticketEntityEmbed({ username: interaction.user.username, @@ -188,7 +192,7 @@ export class CreateTicketSystem { // add role members and ticket owner const user = await interaction.guild.members.fetch(interaction.user.id) - const role = await interaction.guild.roles.fetch(ticketRole) + const role = await interaction.guild.roles.fetch(ticketRole!) const owner = await interaction.guild.fetchOwner() if (!role) { @@ -259,24 +263,12 @@ export class CreateTicketSystem { return } - const tickets = await db.get(DBTableEnum.TICKET_OWNERS) - if (tickets) { - const res = tickets.reduce((res, ticket) => { - if ( - interaction.channel && - ticket.channel === interaction.channel.id - ) { - res.push(ticket) - } - return res - }, []) - await db.set(DBTableEnum.TICKET_OWNERS, res).then(() => { - logger.database( - 'Updated active tickets', - `Removed channel ${interaction.channel?.id}`, - ) - }) - } + await db.closeTicket(interaction.channel.id).then(() => { + logger.database( + 'Updated active tickets', + `Removed channel ${interaction.channel?.id}`, + ) + }) logger.action( 'Ticket close attempt successful', diff --git a/src/commands/admin/help.ts b/src/commands/admin/help.ts index 1a682c4..19f023a 100644 --- a/src/commands/admin/help.ts +++ b/src/commands/admin/help.ts @@ -17,7 +17,7 @@ export class Help { value: '```/feedback\n' + '/payments```', }, { - name: 'Setters', + name: 'Setters (preferably in order)', value: '```/set-banner-ticket\n' + '/set-banner-welcome\n' + @@ -31,11 +31,11 @@ export class Help { { name: 'Admin', value: - '```/ping - потому что могу\n\n' + - '/help - без понятия\n\n' + - '/create-ticket-system - Создать систему тикетов отталкиваясь от текущего канала.\n' + - 'Требует /set-banner-ticket /set-prices-channel /set-status\n\n' + - '/remove-active-ticket - удалить из базы все активные тикеты пользователя если что-то произошло и они не удалились при архивации тикета```', + '```/ping\n\n' + + '/help\n\n' + + '/create-ticket-system - Create ticket system based on channel where the command is executed.\n' + + 'Requires /set-banner-ticket /set-prices-channel /set-status\n\n' + + '/remove-active-ticket - forces closing of all user tickets in case of errors```', }, ]) diff --git a/src/commands/admin/remove-active-ticket.ts b/src/commands/admin/remove-active-ticket.ts index f3b5be0..2f12e84 100644 --- a/src/commands/admin/remove-active-ticket.ts +++ b/src/commands/admin/remove-active-ticket.ts @@ -5,19 +5,19 @@ import { User, } from 'discord.js' -import { db, DBTableEnum } from '../../db' +import { + getUser, + closeUserTickets, + createUserSilent, + getUserOpenedTicketsAmount, +} from '../../db' import { logger } from '../../lib' -type Ticket = { - user: string - channel: string -} - @Discord() export class RemoveActiveTicket { @Slash({ name: 'remove-active-ticket', - description: 'Remove user from database in case of need', + description: 'Closes all user tickets manually if needed', defaultMemberPermissions: 'Administrator', }) async removeActiveTicket( @@ -32,27 +32,20 @@ export class RemoveActiveTicket { ) { await interaction.deferReply({ ephemeral: true }) - const activeTickets = await db.get(DBTableEnum.TICKET_OWNERS) - if (!activeTickets || !activeTickets.length) { - await interaction.editReply('❌ No active tickets found for server') + const userDB = await getUser(user.id) + if (!userDB || !(await getUserOpenedTicketsAmount(user.id))) { + await createUserSilent(user.id) + await interaction.editReply('❌ No active tickets found for user') return } - const res = activeTickets.reduce((r, ticket) => { - if (ticket.user !== user.id) { - r.push(ticket) - } - return r - }, []) - await db - .set(DBTableEnum.TICKET_OWNERS, res) - .then(() => - logger.database( - DBTableEnum.TICKET_OWNERS, - `Removed ${user.id}\nCaller: ${interaction.user.id}`, - ), - ) + await closeUserTickets(user.id).then(() => + logger.action( + 'Changed user', + `Forced close all tickets for ${user.globalName}(${user.id})`, + ), + ) - await interaction.editReply('✔️ User tickets cleared successfully') + await interaction.editReply('✔️ User tickets closed successfully') } } diff --git a/src/commands/feedback.ts b/src/commands/feedback.ts index 3980592..f4942cc 100644 --- a/src/commands/feedback.ts +++ b/src/commands/feedback.ts @@ -1,7 +1,11 @@ import { Discord, Slash, SlashChoice, SlashOption } from 'discordx' -import { ApplicationCommandOptionType, CommandInteraction } from 'discord.js' +import { + ApplicationCommandOptionType, + CommandInteraction, + SnowflakeUtil, +} from 'discord.js' -import { db, DBTableEnum } from '../db' +import { getSettings } from '../db' import { logger } from '../lib' import { feedbackEmbed } from '../utils' @@ -33,8 +37,8 @@ export class Feedback { if (!interaction.guild) return - const reviewChannelID = await db.get(DBTableEnum.FEEDBACK_CHANNEL) - if (!reviewChannelID) { + const settings = await getSettings() + if (!settings || !settings.feedbackChannelId) { logger.error( 'Missing feedback channel in database', 'Recreate using /set-feedback-channel', @@ -42,14 +46,16 @@ export class Feedback { await interaction.editReply( '❌ Feedback channel is not set. Please try again later', ) + return } - const reviewChannel = - await interaction.guild.channels.fetch(reviewChannelID) + const reviewChannel = await interaction.guild.channels.fetch( + settings.feedbackChannelId, + ) if (!reviewChannel) { logger.error( 'Missing feedback discord channel', - `Feedback channel id exists in database (${reviewChannelID}) but is not found in channels list`, + `Feedback channel id exists in database (${settings.feedbackChannelId}) but is not found in channels list`, ) await interaction.editReply( '❌ Feedback channel is not set. Please try again later', @@ -59,7 +65,7 @@ export class Feedback { if (!reviewChannel || !reviewChannel.isTextBased()) { logger.error( 'Missing feedback discord channel', - `Feedback channel id exists in database (${reviewChannelID}) but is not a text channel or is not found in channels list`, + `Feedback channel id exists in database (${settings.feedbackChannelId}) but is not a text channel or is not found in channels list`, ) await interaction.editReply( '❌ Feedback channel is not set. Please try again later', @@ -67,6 +73,8 @@ export class Feedback { return } + const nonce = SnowflakeUtil.generate().toString() + await reviewChannel.send({ embeds: [ feedbackEmbed({ @@ -75,6 +83,7 @@ export class Feedback { rating, }), ], + nonce, }) await interaction.editReply('✔️ Review sent successfully!') diff --git a/src/commands/setters/set-banner-ticket.ts b/src/commands/setters/set-banner-ticket.ts index 243bf77..b793f2f 100644 --- a/src/commands/setters/set-banner-ticket.ts +++ b/src/commands/setters/set-banner-ticket.ts @@ -1,7 +1,7 @@ import { Discord, Slash, SlashOption } from 'discordx' import { ApplicationCommandOptionType, CommandInteraction } from 'discord.js' -import { db, DBTableEnum } from '../../db' +import { setBanner } from '../../db' import { logger } from '../../lib' @Discord() @@ -22,13 +22,16 @@ export class SetBannerTicket { interaction: CommandInteraction, ) { await interaction.deferReply({ ephemeral: true }) - await db.set(DBTableEnum.BANNER_TICKET, url).catch(async () => { - await interaction.editReply({ - content: `❌ Failed to set banner`, + + await setBanner('ticketUrl', url) + .then(() => logger.database('Banner Ticket URL', url)) + .catch(async () => { + await interaction.editReply({ + content: `❌ Failed to set banner`, + }) + return }) - return - }) - logger.database(DBTableEnum.BANNER_TICKET, url) + await interaction.editReply({ content: `✔️ Set banner URL to ${url}`, }) diff --git a/src/commands/setters/set-banner-welcome.ts b/src/commands/setters/set-banner-welcome.ts index 15c52e1..870c162 100644 --- a/src/commands/setters/set-banner-welcome.ts +++ b/src/commands/setters/set-banner-welcome.ts @@ -1,7 +1,7 @@ import { Discord, Slash, SlashOption } from 'discordx' import { ApplicationCommandOptionType, CommandInteraction } from 'discord.js' -import { db, DBTableEnum } from '../../db' +import { setBanner } from '../../db' import { logger } from '../../lib' @Discord() @@ -22,13 +22,16 @@ export class SetBannerWelcome { interaction: CommandInteraction, ) { await interaction.deferReply({ ephemeral: true }) - await db.set(DBTableEnum.BANNER_WELCOME, url).catch(async () => { - await interaction.editReply({ - content: `❌ Failed to set banner`, + + await setBanner('welcomeUrl', url) + .then(() => logger.database('Welcome Ticket URL', url)) + .catch(async () => { + await interaction.editReply({ + content: `❌ Failed to set banner`, + }) + return }) - return - }) - logger.database(DBTableEnum.BANNER_WELCOME, url) + await interaction.editReply({ content: `✔️ Set banner URL to ${url}`, }) diff --git a/src/commands/setters/set-feedback-channel.ts b/src/commands/setters/set-feedback-channel.ts index 9ffba39..e809ff6 100644 --- a/src/commands/setters/set-feedback-channel.ts +++ b/src/commands/setters/set-feedback-channel.ts @@ -5,7 +5,7 @@ import { TextChannel, } from 'discord.js' -import { db, DBTableEnum } from '../../db' +import { setSettings } from '../../db' import { logger } from '../../lib' @Discord() @@ -26,15 +26,15 @@ export class SetFeedbackChannel { interaction: CommandInteraction, ) { await interaction.deferReply({ ephemeral: true }) - await db - .set(DBTableEnum.FEEDBACK_CHANNEL, channel.id) + + await setSettings('feedbackChannelId', channel.id) + .then(() => logger.database('Feedback Channel', channel.id)) .catch(async () => { await interaction.editReply({ content: `❌ Failed to set feedback channel`, }) return }) - logger.database(DBTableEnum.FEEDBACK_CHANNEL, channel.id) await interaction.editReply({ content: `✔️ Set feedback channel to ${channel.id}`, }) diff --git a/src/commands/setters/set-order-channel.ts b/src/commands/setters/set-order-channel.ts index 69845f3..b7c3500 100644 --- a/src/commands/setters/set-order-channel.ts +++ b/src/commands/setters/set-order-channel.ts @@ -5,7 +5,7 @@ import { TextChannel, } from 'discord.js' -import { db, DBTableEnum } from '../../db' +import { setSettings } from '../../db' import { logger } from '../../lib' @Discord() @@ -26,15 +26,16 @@ export class SetOrderChannel { interaction: CommandInteraction, ) { await interaction.deferReply({ ephemeral: true }) - await db - .set(DBTableEnum.MAKE_AN_ORDER_CHANNEL, channel.id) + + await setSettings('makeAnOrderChannelId', channel.id) + .then(() => logger.database('Order Channel', channel.id)) .catch(async () => { await interaction.editReply({ content: `❌ Failed to set make an order channel`, }) return }) - logger.database(DBTableEnum.MAKE_AN_ORDER_CHANNEL, channel.id) + await interaction.editReply({ content: `✔️ Set make an order channel to ${channel.id}`, }) diff --git a/src/commands/setters/set-portfolio-channel.ts b/src/commands/setters/set-portfolio-channel.ts index d9263ab..9118775 100644 --- a/src/commands/setters/set-portfolio-channel.ts +++ b/src/commands/setters/set-portfolio-channel.ts @@ -5,7 +5,7 @@ import { TextChannel, } from 'discord.js' -import { db, DBTableEnum } from '../../db' +import { setSettings } from '../../db' import { logger } from '../../lib' @Discord() @@ -26,15 +26,16 @@ export class SetPortfolioChannel { interaction: CommandInteraction, ) { await interaction.deferReply({ ephemeral: true }) - await db - .set(DBTableEnum.PORTFOLIO_CHANNEL, channel.id) + + await setSettings('portfolioChannelId', channel.id) + .then(() => logger.database('Portfolio Channel', channel.id)) .catch(async () => { await interaction.editReply({ content: `❌ Failed to set portfolio channel`, }) return }) - logger.database(DBTableEnum.PORTFOLIO_CHANNEL, channel.id) + await interaction.editReply({ content: `✔️ Set portfolio channel to ${channel.id}`, }) diff --git a/src/commands/setters/set-price-channel.ts b/src/commands/setters/set-price-channel.ts index af52a60..0d2be6f 100644 --- a/src/commands/setters/set-price-channel.ts +++ b/src/commands/setters/set-price-channel.ts @@ -5,7 +5,7 @@ import { TextChannel, } from 'discord.js' -import { db, DBTableEnum } from '../../db' +import { setSettings } from '../../db' import { logger } from '../../lib' @Discord() @@ -26,13 +26,16 @@ export class SetPriceChannel { interaction: CommandInteraction, ) { await interaction.deferReply({ ephemeral: true }) - await db.set(DBTableEnum.PRICE_CHANNEL, channel.id).catch(async () => { - await interaction.editReply({ - content: `❌ Failed to set price channel`, + + await setSettings('priceChannelId', channel.id) + .then(() => logger.database('Price Channel', channel.id)) + .catch(async () => { + await interaction.editReply({ + content: `❌ Failed to set price channel`, + }) + return }) - return - }) - logger.database(DBTableEnum.PRICE_CHANNEL, channel.id) + await interaction.editReply({ content: `✔️ Set price channel to ${channel.id}`, }) diff --git a/src/commands/setters/set-status.ts b/src/commands/setters/set-status.ts index a34d903..50e6ece 100644 --- a/src/commands/setters/set-status.ts +++ b/src/commands/setters/set-status.ts @@ -1,7 +1,7 @@ import { Discord, Slash, SlashChoice, SlashOption } from 'discordx' -import { ApplicationCommandOptionType, CommandInteraction, GuildBasedChannel } from 'discord.js' +import { ApplicationCommandOptionType, CommandInteraction } from 'discord.js' -import { db, DBTableEnum } from '../../db' +import { setSettings, getSettings } from '../../db' import { ticketWorkloadEmbed, Workload } from '../../utils' import { logger } from '../../lib' @@ -24,25 +24,38 @@ export class SetStatus { interaction: CommandInteraction, ) { await interaction.deferReply({ ephemeral: true }) - await db.set(DBTableEnum.WORKLOAD, status).catch(async () => { - await interaction.editReply({ - content: `❌ Failed to workload status`, - }) - return - }) - const statusMsg = await db.get(DBTableEnum.WORKLOAD_MESSAGE) - const statusChannelId = await db.get(DBTableEnum.WORKLOAD_CHANNEL) - if (statusMsg && statusChannelId) { - const statusChannel = await interaction.guild?.channels.fetch(statusChannelId) + await setSettings('workLoadStatus', status) + .then(() => logger.database('Workload Status', status)) + .catch(async () => { + await interaction.editReply({ + content: `❌ Failed to workload status`, + }) + return + }) + + const settings = await getSettings() + + if (!settings || !settings.workLoadChannelId) + return await interaction.editReply({ + content: `❌ Failed to find workload channel`, + }) + + if (settings.workLoadStatus && settings.workLoadChannelId) { + const statusChannel = await interaction.guild?.channels.fetch( + settings.workLoadChannelId, + ) + if (!statusChannel || !statusChannel.isTextBased()) return - const workloadMsg = await statusChannel.messages.fetch(statusMsg) + + const workloadMsg = await statusChannel.messages.fetch( + settings.workloadMessageId, + ) await workloadMsg.edit({ - embeds: [ticketWorkloadEmbed({ workload: status as Workload })] + embeds: [ticketWorkloadEmbed({ workload: status as Workload })], }) } - logger.database(DBTableEnum.WORKLOAD, status) await interaction.editReply({ content: `✔️ Set workload status to ${status}`, }) diff --git a/src/commands/setters/set-welcome-channel.ts b/src/commands/setters/set-welcome-channel.ts index bb30427..ae8bfd5 100644 --- a/src/commands/setters/set-welcome-channel.ts +++ b/src/commands/setters/set-welcome-channel.ts @@ -1,13 +1,12 @@ -import { Client, Discord, Slash, SlashOption } from 'discordx' +import { Discord, Slash, SlashOption } from 'discordx' import { ApplicationCommandOptionType, CommandInteraction, Role, TextChannel, } from 'discord.js' -import { db, DBTableEnum } from '../../db' +import { setSettings, getSettings, setRole } from '../../db' import { logger } from '../../lib' -import { Workload } from '../../utils' @Discord() export class SetWelcomeChannel { @@ -35,8 +34,8 @@ export class SetWelcomeChannel { ) { await interaction.deferReply({ ephemeral: true }) - const portfolioChannelId = await db.get(DBTableEnum.PORTFOLIO_CHANNEL) - if (!portfolioChannelId) { + const settings = await getSettings() + if (!settings || !settings.portfolioChannelId) { logger.error( 'Missing portfolio channel', 'Set using /set-portfolio-channel', @@ -47,8 +46,7 @@ export class SetWelcomeChannel { return } - const orderChannelId = await db.get(DBTableEnum.MAKE_AN_ORDER_CHANNEL) - if (!orderChannelId) { + if (!settings.makeAnOrderChannelId) { logger.error( 'Missing make an order channel', 'Set using /set-order-channel', @@ -59,23 +57,23 @@ export class SetWelcomeChannel { return } - await db - .set(DBTableEnum.WELCOME_CHANNEL, channel.id) + await setSettings('welcomeChannelId', channel.id) + .then(() => logger.database('Welcome channel', channel.id)) .catch(async () => { await interaction.editReply({ content: `❌ Failed to welcome channel`, }) return }) - logger.database(DBTableEnum.WELCOME_CHANNEL, channel.id) - await db.set(DBTableEnum.WELCOME_ROLE, role.id).catch(async () => { - await interaction.editReply({ - content: `❌ Failed to entry role`, + await setRole('welcomeRoleId', role.id) + .then(() => logger.database('Welcome role', role.id)) + .catch(async () => { + await interaction.editReply({ + content: `❌ Failed to set entry role`, + }) + return }) - return - }) - logger.database(DBTableEnum.WELCOME_ROLE, role.id) await interaction.editReply({ content: `✔️ Set welcome channel to ${channel.id}\n✔️ Set entry role to ${role.id}`, diff --git a/src/db.ts b/src/db.ts deleted file mode 100644 index 020681b..0000000 --- a/src/db.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { PrismaClient } from '@prisma/client' - -export const db = new PrismaClient() diff --git a/src/db/banner.ts b/src/db/banner.ts new file mode 100644 index 0000000..d436b55 --- /dev/null +++ b/src/db/banner.ts @@ -0,0 +1,13 @@ +import { Prisma } from '@prisma/client' +import { db } from './' + +export function getBanner() { + return db.banner.findFirst() +} + +export function setBanner(key: T, value: Prisma.BannerCreateInput[T]) { + return db.banner.update({ + where: { id: 0 }, + data: { [key]: value } + }) +} diff --git a/src/db/index.ts b/src/db/index.ts new file mode 100644 index 0000000..c898817 --- /dev/null +++ b/src/db/index.ts @@ -0,0 +1,9 @@ +import { PrismaClient } from '@prisma/client' + +export const db = new PrismaClient() + +export * from './banner' +export * from './role' +export * from './settings' +export * from './ticket' +export * from './user' \ No newline at end of file diff --git a/src/db/role.ts b/src/db/role.ts new file mode 100644 index 0000000..c54a502 --- /dev/null +++ b/src/db/role.ts @@ -0,0 +1,13 @@ +import { Prisma } from '@prisma/client' +import { db } from './' + +export function getRole() { + return db.role.findFirst() +} + +export function setRole(key: T, value: Prisma.RoleCreateInput[T]) { + return db.role.update({ + where: { id: 0 }, + data: { [key]: value } + }) +} diff --git a/src/db/settings.ts b/src/db/settings.ts new file mode 100644 index 0000000..d1e3941 --- /dev/null +++ b/src/db/settings.ts @@ -0,0 +1,16 @@ +import { Prisma } from '@prisma/client' +import { db } from './' + +export async function getSettings() { + return db.settings.findFirst() +} + +export function setSettings( + key: T, + value: Prisma.SettingsCreateInput[T], +) { + return db.settings.update({ + where: { id: 0 }, + data: { [key]: value }, + }) +} diff --git a/src/db/ticket.ts b/src/db/ticket.ts new file mode 100644 index 0000000..bc65313 --- /dev/null +++ b/src/db/ticket.ts @@ -0,0 +1,19 @@ +import { db } from './' +import { Prisma } from '@prisma/client' + +export async function createTicket(ticket: Prisma.TicketCreateInput, userId: string) { + return db.ticket.create({ + data: { + channelId: ticket.channelId, + userId, + closed: ticket.closed, + } + }) +} + +export async function closeTicket(channelId: string) { + return db.ticket.update({ + where: { channelId }, + data: { closed: true } + }) +} \ No newline at end of file diff --git a/src/db/user.ts b/src/db/user.ts new file mode 100644 index 0000000..c4de344 --- /dev/null +++ b/src/db/user.ts @@ -0,0 +1,82 @@ +import { db } from './' +import { Prisma } from '@prisma/client' + +export async function getUser(id: string) { + return db.user.findFirst({ + where: { + discordId: id, + }, + include: { + tickets: true, + }, + }) +} + +export async function getUserOpenedTicketsAmount(discordId: string) { + const data = await db.user.findFirst({ + where: { discordId }, + include: { + tickets: { + where: { + closed: false, + }, + }, + }, + }) + + if (!data) return 0 + + return data.tickets.length +} + +export async function createUser( + discordId: string, + ticket: Omit, +) { + const user = await getUser(discordId) + + if (user) { + await db.ticket.create({ + data: { + channelId: ticket.channelId, + closed: ticket.closed, + userId: user.discordId, + }, + }) + return user + } + + return db.user.create({ + data: { + discordId, + tickets: { + create: { + channelId: ticket.channelId, + closed: ticket.closed, + }, + }, + }, + }) +} + +export async function createUserSilent(discordId: string) { + return db.user.create({ data: { discordId } }) +} + +export async function closeUserTickets(discordId: string) { + return db.user.update({ + where: { discordId }, + data: { + tickets: { + updateMany: { + where: { + closed: false, + }, + data: { + closed: true, + }, + }, + }, + }, + }) +} diff --git a/src/events/guildMemberAdd.ts b/src/events/guildMemberAdd.ts index e7a4170..d3fe803 100644 --- a/src/events/guildMemberAdd.ts +++ b/src/events/guildMemberAdd.ts @@ -3,10 +3,9 @@ import { EmbedBuilder, channelMention, userMention, - User, SnowflakeUtil, } from 'discord.js' -import { db, DBTableEnum } from '../db' +import { getSettings, getBanner, getRole } from '../db' import { logger } from '../lib' let lastJoinedUser: { @@ -27,29 +26,32 @@ export class GuildMemberAdd { ) return - const portfolioChannelID = await db.get(DBTableEnum.PORTFOLIO_CHANNEL) - const makeAnOrderChannelID = await db.get( - DBTableEnum.MAKE_AN_ORDER_CHANNEL, - ) - const welcomeChannelID = await db.get(DBTableEnum.WELCOME_CHANNEL) - const imageURL = await db.get(DBTableEnum.BANNER_WELCOME) + const settings = await getSettings() + const banner = await getBanner() + const roles = await getRole() - const roleID = await db.get(DBTableEnum.WELCOME_ROLE) + if (!settings || !banner || !roles) + return logger.error( + 'Missing settings', + 'Please set all required fields', + ) let embed = new EmbedBuilder() .setTitle( `I'm glad to see you on my server. Let me give you a little tour.`, ) .setDescription( - `> 1. In the ${channelMention(portfolioChannelID)} - you can see my previous works. - > 2. Here ${channelMention(makeAnOrderChannelID)} you might open a ticket.`, + `> 1. In the ${channelMention(settings.portfolioChannelId)} - you can see my previous works. + > 2. Here ${channelMention(settings.makeAnOrderChannelId)} you might open a ticket.`, ) - .setImage(imageURL) + .setImage(banner.welcomeUrl) const nonce = SnowflakeUtil.generate().toString() - const channel = await member.guild.channels.fetch(welcomeChannelID) - const role = await member.guild.roles.fetch(roleID) + const channel = await member.guild.channels.fetch( + settings.welcomeChannelId, + ) + const role = await member.guild.roles.fetch(roles.welcomeRoleId) if (channel && channel.isTextBased() && role) { await channel