- prisma migration
- better ticket handling
- better error handling
This commit is contained in:
Danya H 2024-08-06 00:06:09 +01:00
parent ef1c5be2b7
commit fd558bd975
24 changed files with 425 additions and 202 deletions

Binary file not shown.

View File

@ -1,21 +1,21 @@
-- CreateTable -- CreateTable
CREATE TABLE "Settings" ( CREATE TABLE "Settings" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"welcomeChannelId" INTEGER NOT NULL, "welcomeChannelId" TEXT NOT NULL,
"feedbackChannelId" INTEGER NOT NULL, "feedbackChannelId" TEXT NOT NULL,
"portfolioChannelId" INTEGER NOT NULL, "portfolioChannelId" TEXT NOT NULL,
"makeAnOrderChannelId" INTEGER NOT NULL, "makeAnOrderChannelId" TEXT NOT NULL,
"priceChannelId" INTEGER NOT NULL, "priceChannelId" TEXT NOT NULL,
"workLoadChannelId" INTEGER NOT NULL, "workLoadChannelId" TEXT NOT NULL,
"worlLoadStatus" INTEGER NOT NULL, "worlLoadStatus" TEXT NOT NULL,
"workloadMessageId" INTEGER NOT NULL "workloadMessageId" TEXT NOT NULL
); );
-- CreateTable -- CreateTable
CREATE TABLE "Role" ( CREATE TABLE "Role" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"welcomeRoleId" INTEGER NOT NULL, "welcomeRoleId" TEXT NOT NULL,
"tickerRoleId" INTEGER NOT NULL "tickerRoleId" TEXT NOT NULL
); );
-- CreateTable -- CreateTable
@ -28,13 +28,13 @@ CREATE TABLE "Banner" (
-- CreateTable -- CreateTable
CREATE TABLE "User" ( CREATE TABLE "User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"discordId" INTEGER NOT NULL "discordId" TEXT NOT NULL
); );
-- CreateTable -- CreateTable
CREATE TABLE "Ticket" ( CREATE TABLE "Ticket" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"channelId" INTEGER NOT NULL, "channelId" TEXT NOT NULL,
"closed" BOOLEAN NOT NULL, "closed" BOOLEAN NOT NULL,
"userId" INTEGER, "userId" INTEGER,
CONSTRAINT "Ticket_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE CONSTRAINT "Ticket_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE

View File

@ -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;

View File

@ -8,21 +8,21 @@ datasource db {
} }
model Settings { model Settings {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
welcomeChannelId Int welcomeChannelId String
feedbackChannelId Int feedbackChannelId String
portfolioChannelId Int portfolioChannelId String
makeAnOrderChannelId Int makeAnOrderChannelId String
priceChannelId Int priceChannelId String
workLoadChannelId Int workLoadChannelId String
worlLoadStatus Int workLoadStatus String
workloadMessageId Int workloadMessageId String
} }
model Role { model Role {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
welcomeRoleId Int welcomeRoleId String
tickerRoleId Int ticketRoleId String
} }
model Banner { model Banner {
@ -32,15 +32,13 @@ model Banner {
} }
model User { model User {
id Int @id @default(autoincrement()) discordId String @unique
discordId Int
tickets Ticket[] tickets Ticket[]
} }
model Ticket { model Ticket {
id Int @id channelId String @unique
channelId Int
closed Boolean closed Boolean
User User? @relation(fields: [userId], references: [id]) User User @relation(fields: [userId], references: [discordId])
userId Int? userId String
} }

View File

@ -14,7 +14,7 @@ import {
userMention, userMention,
} from 'discord.js' } from 'discord.js'
import { db, DBTableEnum } from '../../db' import * as db from '../../db'
import { import {
Workload, Workload,
ticketCreateButton, ticketCreateButton,
@ -26,8 +26,6 @@ import {
} from '../../utils' } from '../../utils'
import { logger } from '../../lib' import { logger } from '../../lib'
type Ticket = { user: string; channel: string }
@Discord() @Discord()
export class CreateTicketSystem { export class CreateTicketSystem {
@Slash({ @Slash({
@ -55,11 +53,10 @@ export class CreateTicketSystem {
return return
} }
const pricesChannelId = await db.get(DBTableEnum.PRICE_CHANNEL) const settings = await db.getSettings()
const bannerURL = await db.get(DBTableEnum.BANNER_TICKET) const banners = await db.getBanner()
const workload = await db.get<Workload>(DBTableEnum.WORKLOAD)
if (!pricesChannelId) { if (!settings || !settings.priceChannelId) {
logger.error( logger.error(
'Missing prices channel', 'Missing prices channel',
'Set using /set-prices-channel', 'Set using /set-prices-channel',
@ -69,14 +66,14 @@ export class CreateTicketSystem {
) )
return return
} }
if (!bannerURL) { if (!banners || !banners.ticketUrl) {
logger.error('Missing banner', 'Set using /set-banner-ticket') logger.error('Missing banner', 'Set using /set-banner-ticket')
await interaction.editReply( await interaction.editReply(
'❌ Missing banner\nSet using /set-banner-ticket', '❌ Missing banner\nSet using /set-banner-ticket',
) )
return return
} }
if (!workload) { if (!settings.workLoadStatus) {
logger.error( logger.error(
'Missing workload status', 'Missing workload status',
'Set using /set-workload-status', 'Set using /set-workload-status',
@ -88,21 +85,35 @@ export class CreateTicketSystem {
} }
await db await db
.set(DBTableEnum.TICKET_ROLE, role.id) .setRole('ticketRoleId', role.id)
.then(() => logger.database(DBTableEnum.TICKET_ROLE, 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 // create ticket embed + button
await interaction.channel.send({ await interaction.channel.send({
components: [ticketCreateButton()], components: [ticketCreateButton()],
embeds: [ticketCreateEmbed({ bannerURL, pricesChannelId })], embeds: [
ticketCreateEmbed({
bannerURL: banners.ticketUrl,
pricesChannelId: settings.priceChannelId,
}),
],
}) })
// workload embed // workload embed
const workloadMessage = await interaction.channel.send({ 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.setSettings('workloadMessageId', workloadMessage.id)
await db.set(DBTableEnum.WORKLOAD_CHANNEL, interaction.channel.id) await db.setSettings('workLoadChannelId', interaction.channel.id)
// close interaction // close interaction
await interaction.editReply('✔️ Created ticket system') await interaction.editReply('✔️ Created ticket system')
@ -127,19 +138,16 @@ export class CreateTicketSystem {
return return
} }
const hasOpenedTicket = await db const hasManyTickets =
.get<Ticket[]>(DBTableEnum.TICKET_OWNERS) (await db.getUserOpenedTicketsAmount(interaction.user.id)) > 3
.then(r => if (hasManyTickets) {
Array.isArray(r) await interaction.editReply(`❌ You already have 3 opened tickets`)
? !!r.find(ticket => ticket.user === interaction.user.id)
: false,
)
if (hasOpenedTicket) {
await interaction.editReply(`❌ You already have an active ticket`)
return return
} }
const ticketRole = await db.get(DBTableEnum.TICKET_ROLE) const roles = await db.getRole()
const ticketRole = roles?.ticketRoleId
let threadChannel: ThreadChannel<boolean> let threadChannel: ThreadChannel<boolean>
try { try {
@ -159,9 +167,9 @@ export class CreateTicketSystem {
} }
await db await db
.push<Ticket>(DBTableEnum.TICKET_OWNERS, { .createUser(interaction.user.id, {
user: interaction.user.id, closed: false,
channel: threadChannel.id, channelId: threadChannel.id,
}) })
.then(() => { .then(() => {
logger.database( logger.database(
@ -169,14 +177,10 @@ export class CreateTicketSystem {
`Added User: ${interaction.user.id}\n Channel: ${threadChannel.id}`, `Added User: ${interaction.user.id}\n Channel: ${threadChannel.id}`,
) )
}) })
logger.database(
'Updated active tickets',
`Removed channel ${interaction.channel.id}`,
)
// welcoming message in ticket // welcoming message in ticket
const threadChannelMessage: MessageCreateOptions = { const threadChannelMessage: MessageCreateOptions = {
content: `${userMention(interaction.user.id)} ${roleMention(ticketRole)}`, content: `${userMention(interaction.user.id)} ${roleMention(ticketRole!)}`,
embeds: [ embeds: [
ticketEntityEmbed({ ticketEntityEmbed({
username: interaction.user.username, username: interaction.user.username,
@ -188,7 +192,7 @@ export class CreateTicketSystem {
// add role members and ticket owner // add role members and ticket owner
const user = await interaction.guild.members.fetch(interaction.user.id) 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() const owner = await interaction.guild.fetchOwner()
if (!role) { if (!role) {
@ -259,24 +263,12 @@ export class CreateTicketSystem {
return return
} }
const tickets = await db.get<Ticket[]>(DBTableEnum.TICKET_OWNERS) await db.closeTicket(interaction.channel.id).then(() => {
if (tickets) { logger.database(
const res = tickets.reduce<Ticket[]>((res, ticket) => { 'Updated active tickets',
if ( `Removed channel ${interaction.channel?.id}`,
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}`,
)
})
}
logger.action( logger.action(
'Ticket close attempt successful', 'Ticket close attempt successful',

View File

@ -17,7 +17,7 @@ export class Help {
value: '```/feedback\n' + '/payments```', value: '```/feedback\n' + '/payments```',
}, },
{ {
name: 'Setters', name: 'Setters (preferably in order)',
value: value:
'```/set-banner-ticket\n' + '```/set-banner-ticket\n' +
'/set-banner-welcome\n' + '/set-banner-welcome\n' +
@ -31,11 +31,11 @@ export class Help {
{ {
name: 'Admin', name: 'Admin',
value: value:
'```/ping - потому что могу\n\n' + '```/ping\n\n' +
'/help - без понятия\n\n' + '/help\n\n' +
'/create-ticket-system - Создать систему тикетов отталкиваясь от текущего канала.\n' + '/create-ticket-system - Create ticket system based on channel where the command is executed.\n' +
'Требует /set-banner-ticket /set-prices-channel /set-status\n\n' + 'Requires /set-banner-ticket /set-prices-channel /set-status\n\n' +
'/remove-active-ticket - удалить из базы все активные тикеты пользователя если что-то произошло и они не удалились при архивации тикета```', '/remove-active-ticket - forces closing of all user tickets in case of errors```',
}, },
]) ])

View File

@ -5,19 +5,19 @@ import {
User, User,
} from 'discord.js' } from 'discord.js'
import { db, DBTableEnum } from '../../db' import {
getUser,
closeUserTickets,
createUserSilent,
getUserOpenedTicketsAmount,
} from '../../db'
import { logger } from '../../lib' import { logger } from '../../lib'
type Ticket = {
user: string
channel: string
}
@Discord() @Discord()
export class RemoveActiveTicket { export class RemoveActiveTicket {
@Slash({ @Slash({
name: 'remove-active-ticket', name: 'remove-active-ticket',
description: 'Remove user from database in case of need', description: 'Closes all user tickets manually if needed',
defaultMemberPermissions: 'Administrator', defaultMemberPermissions: 'Administrator',
}) })
async removeActiveTicket( async removeActiveTicket(
@ -32,27 +32,20 @@ export class RemoveActiveTicket {
) { ) {
await interaction.deferReply({ ephemeral: true }) await interaction.deferReply({ ephemeral: true })
const activeTickets = await db.get<Ticket[]>(DBTableEnum.TICKET_OWNERS) const userDB = await getUser(user.id)
if (!activeTickets || !activeTickets.length) { if (!userDB || !(await getUserOpenedTicketsAmount(user.id))) {
await interaction.editReply('❌ No active tickets found for server') await createUserSilent(user.id)
await interaction.editReply('❌ No active tickets found for user')
return return
} }
const res = activeTickets.reduce<Ticket[]>((r, ticket) => { await closeUserTickets(user.id).then(() =>
if (ticket.user !== user.id) { logger.action(
r.push(ticket) 'Changed user',
} `Forced close all tickets for ${user.globalName}(${user.id})`,
return r ),
}, []) )
await db
.set(DBTableEnum.TICKET_OWNERS, res)
.then(() =>
logger.database(
DBTableEnum.TICKET_OWNERS,
`Removed ${user.id}\nCaller: ${interaction.user.id}`,
),
)
await interaction.editReply('✔️ User tickets cleared successfully') await interaction.editReply('✔️ User tickets closed successfully')
} }
} }

View File

@ -1,7 +1,11 @@
import { Discord, Slash, SlashChoice, SlashOption } from 'discordx' 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 { logger } from '../lib'
import { feedbackEmbed } from '../utils' import { feedbackEmbed } from '../utils'
@ -33,8 +37,8 @@ export class Feedback {
if (!interaction.guild) return if (!interaction.guild) return
const reviewChannelID = await db.get(DBTableEnum.FEEDBACK_CHANNEL) const settings = await getSettings()
if (!reviewChannelID) { if (!settings || !settings.feedbackChannelId) {
logger.error( logger.error(
'Missing feedback channel in database', 'Missing feedback channel in database',
'Recreate using /set-feedback-channel', 'Recreate using /set-feedback-channel',
@ -42,14 +46,16 @@ export class Feedback {
await interaction.editReply( await interaction.editReply(
'❌ Feedback channel is not set. Please try again later', '❌ Feedback channel is not set. Please try again later',
) )
return
} }
const reviewChannel = const reviewChannel = await interaction.guild.channels.fetch(
await interaction.guild.channels.fetch(reviewChannelID) settings.feedbackChannelId,
)
if (!reviewChannel) { if (!reviewChannel) {
logger.error( logger.error(
'Missing feedback discord channel', '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( await interaction.editReply(
'❌ Feedback channel is not set. Please try again later', '❌ Feedback channel is not set. Please try again later',
@ -59,7 +65,7 @@ export class Feedback {
if (!reviewChannel || !reviewChannel.isTextBased()) { if (!reviewChannel || !reviewChannel.isTextBased()) {
logger.error( logger.error(
'Missing feedback discord channel', '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( await interaction.editReply(
'❌ Feedback channel is not set. Please try again later', '❌ Feedback channel is not set. Please try again later',
@ -67,6 +73,8 @@ export class Feedback {
return return
} }
const nonce = SnowflakeUtil.generate().toString()
await reviewChannel.send({ await reviewChannel.send({
embeds: [ embeds: [
feedbackEmbed({ feedbackEmbed({
@ -75,6 +83,7 @@ export class Feedback {
rating, rating,
}), }),
], ],
nonce,
}) })
await interaction.editReply('✔️ Review sent successfully!') await interaction.editReply('✔️ Review sent successfully!')

View File

@ -1,7 +1,7 @@
import { Discord, Slash, SlashOption } from 'discordx' import { Discord, Slash, SlashOption } from 'discordx'
import { ApplicationCommandOptionType, CommandInteraction } from 'discord.js' import { ApplicationCommandOptionType, CommandInteraction } from 'discord.js'
import { db, DBTableEnum } from '../../db' import { setBanner } from '../../db'
import { logger } from '../../lib' import { logger } from '../../lib'
@Discord() @Discord()
@ -22,13 +22,16 @@ export class SetBannerTicket {
interaction: CommandInteraction, interaction: CommandInteraction,
) { ) {
await interaction.deferReply({ ephemeral: true }) await interaction.deferReply({ ephemeral: true })
await db.set(DBTableEnum.BANNER_TICKET, url).catch(async () => {
await interaction.editReply({ await setBanner('ticketUrl', url)
content: `❌ Failed to set banner`, .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({ await interaction.editReply({
content: `✔️ Set banner URL to ${url}`, content: `✔️ Set banner URL to ${url}`,
}) })

View File

@ -1,7 +1,7 @@
import { Discord, Slash, SlashOption } from 'discordx' import { Discord, Slash, SlashOption } from 'discordx'
import { ApplicationCommandOptionType, CommandInteraction } from 'discord.js' import { ApplicationCommandOptionType, CommandInteraction } from 'discord.js'
import { db, DBTableEnum } from '../../db' import { setBanner } from '../../db'
import { logger } from '../../lib' import { logger } from '../../lib'
@Discord() @Discord()
@ -22,13 +22,16 @@ export class SetBannerWelcome {
interaction: CommandInteraction, interaction: CommandInteraction,
) { ) {
await interaction.deferReply({ ephemeral: true }) await interaction.deferReply({ ephemeral: true })
await db.set(DBTableEnum.BANNER_WELCOME, url).catch(async () => {
await interaction.editReply({ await setBanner('welcomeUrl', url)
content: `❌ Failed to set banner`, .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({ await interaction.editReply({
content: `✔️ Set banner URL to ${url}`, content: `✔️ Set banner URL to ${url}`,
}) })

View File

@ -5,7 +5,7 @@ import {
TextChannel, TextChannel,
} from 'discord.js' } from 'discord.js'
import { db, DBTableEnum } from '../../db' import { setSettings } from '../../db'
import { logger } from '../../lib' import { logger } from '../../lib'
@Discord() @Discord()
@ -26,15 +26,15 @@ export class SetFeedbackChannel {
interaction: CommandInteraction, interaction: CommandInteraction,
) { ) {
await interaction.deferReply({ ephemeral: true }) 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 () => { .catch(async () => {
await interaction.editReply({ await interaction.editReply({
content: `❌ Failed to set feedback channel`, content: `❌ Failed to set feedback channel`,
}) })
return return
}) })
logger.database(DBTableEnum.FEEDBACK_CHANNEL, channel.id)
await interaction.editReply({ await interaction.editReply({
content: `✔️ Set feedback channel to ${channel.id}`, content: `✔️ Set feedback channel to ${channel.id}`,
}) })

View File

@ -5,7 +5,7 @@ import {
TextChannel, TextChannel,
} from 'discord.js' } from 'discord.js'
import { db, DBTableEnum } from '../../db' import { setSettings } from '../../db'
import { logger } from '../../lib' import { logger } from '../../lib'
@Discord() @Discord()
@ -26,15 +26,16 @@ export class SetOrderChannel {
interaction: CommandInteraction, interaction: CommandInteraction,
) { ) {
await interaction.deferReply({ ephemeral: true }) 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 () => { .catch(async () => {
await interaction.editReply({ await interaction.editReply({
content: `❌ Failed to set make an order channel`, content: `❌ Failed to set make an order channel`,
}) })
return return
}) })
logger.database(DBTableEnum.MAKE_AN_ORDER_CHANNEL, channel.id)
await interaction.editReply({ await interaction.editReply({
content: `✔️ Set make an order channel to ${channel.id}`, content: `✔️ Set make an order channel to ${channel.id}`,
}) })

View File

@ -5,7 +5,7 @@ import {
TextChannel, TextChannel,
} from 'discord.js' } from 'discord.js'
import { db, DBTableEnum } from '../../db' import { setSettings } from '../../db'
import { logger } from '../../lib' import { logger } from '../../lib'
@Discord() @Discord()
@ -26,15 +26,16 @@ export class SetPortfolioChannel {
interaction: CommandInteraction, interaction: CommandInteraction,
) { ) {
await interaction.deferReply({ ephemeral: true }) 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 () => { .catch(async () => {
await interaction.editReply({ await interaction.editReply({
content: `❌ Failed to set portfolio channel`, content: `❌ Failed to set portfolio channel`,
}) })
return return
}) })
logger.database(DBTableEnum.PORTFOLIO_CHANNEL, channel.id)
await interaction.editReply({ await interaction.editReply({
content: `✔️ Set portfolio channel to ${channel.id}`, content: `✔️ Set portfolio channel to ${channel.id}`,
}) })

View File

@ -5,7 +5,7 @@ import {
TextChannel, TextChannel,
} from 'discord.js' } from 'discord.js'
import { db, DBTableEnum } from '../../db' import { setSettings } from '../../db'
import { logger } from '../../lib' import { logger } from '../../lib'
@Discord() @Discord()
@ -26,13 +26,16 @@ export class SetPriceChannel {
interaction: CommandInteraction, interaction: CommandInteraction,
) { ) {
await interaction.deferReply({ ephemeral: true }) await interaction.deferReply({ ephemeral: true })
await db.set(DBTableEnum.PRICE_CHANNEL, channel.id).catch(async () => {
await interaction.editReply({ await setSettings('priceChannelId', channel.id)
content: `❌ Failed to set price channel`, .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({ await interaction.editReply({
content: `✔️ Set price channel to ${channel.id}`, content: `✔️ Set price channel to ${channel.id}`,
}) })

View File

@ -1,7 +1,7 @@
import { Discord, Slash, SlashChoice, SlashOption } from 'discordx' 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 { ticketWorkloadEmbed, Workload } from '../../utils'
import { logger } from '../../lib' import { logger } from '../../lib'
@ -24,25 +24,38 @@ export class SetStatus {
interaction: CommandInteraction, interaction: CommandInteraction,
) { ) {
await interaction.deferReply({ ephemeral: true }) 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) await setSettings('workLoadStatus', status)
const statusChannelId = await db.get(DBTableEnum.WORKLOAD_CHANNEL) .then(() => logger.database('Workload Status', status))
if (statusMsg && statusChannelId) { .catch(async () => {
const statusChannel = await interaction.guild?.channels.fetch(statusChannelId) 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 if (!statusChannel || !statusChannel.isTextBased()) return
const workloadMsg = await statusChannel.messages.fetch(statusMsg)
const workloadMsg = await statusChannel.messages.fetch(
settings.workloadMessageId,
)
await workloadMsg.edit({ await workloadMsg.edit({
embeds: [ticketWorkloadEmbed({ workload: status as Workload })] embeds: [ticketWorkloadEmbed({ workload: status as Workload })],
}) })
} }
logger.database(DBTableEnum.WORKLOAD, status)
await interaction.editReply({ await interaction.editReply({
content: `✔️ Set workload status to ${status}`, content: `✔️ Set workload status to ${status}`,
}) })

View File

@ -1,13 +1,12 @@
import { Client, Discord, Slash, SlashOption } from 'discordx' import { Discord, Slash, SlashOption } from 'discordx'
import { import {
ApplicationCommandOptionType, ApplicationCommandOptionType,
CommandInteraction, CommandInteraction,
Role, Role,
TextChannel, TextChannel,
} from 'discord.js' } from 'discord.js'
import { db, DBTableEnum } from '../../db' import { setSettings, getSettings, setRole } from '../../db'
import { logger } from '../../lib' import { logger } from '../../lib'
import { Workload } from '../../utils'
@Discord() @Discord()
export class SetWelcomeChannel { export class SetWelcomeChannel {
@ -35,8 +34,8 @@ export class SetWelcomeChannel {
) { ) {
await interaction.deferReply({ ephemeral: true }) await interaction.deferReply({ ephemeral: true })
const portfolioChannelId = await db.get(DBTableEnum.PORTFOLIO_CHANNEL) const settings = await getSettings()
if (!portfolioChannelId) { if (!settings || !settings.portfolioChannelId) {
logger.error( logger.error(
'Missing portfolio channel', 'Missing portfolio channel',
'Set using /set-portfolio-channel', 'Set using /set-portfolio-channel',
@ -47,8 +46,7 @@ export class SetWelcomeChannel {
return return
} }
const orderChannelId = await db.get(DBTableEnum.MAKE_AN_ORDER_CHANNEL) if (!settings.makeAnOrderChannelId) {
if (!orderChannelId) {
logger.error( logger.error(
'Missing make an order channel', 'Missing make an order channel',
'Set using /set-order-channel', 'Set using /set-order-channel',
@ -59,23 +57,23 @@ export class SetWelcomeChannel {
return return
} }
await db await setSettings('welcomeChannelId', channel.id)
.set(DBTableEnum.WELCOME_CHANNEL, channel.id) .then(() => logger.database('Welcome channel', channel.id))
.catch(async () => { .catch(async () => {
await interaction.editReply({ await interaction.editReply({
content: `❌ Failed to welcome channel`, content: `❌ Failed to welcome channel`,
}) })
return return
}) })
logger.database(DBTableEnum.WELCOME_CHANNEL, channel.id)
await db.set(DBTableEnum.WELCOME_ROLE, role.id).catch(async () => { await setRole('welcomeRoleId', role.id)
await interaction.editReply({ .then(() => logger.database('Welcome role', role.id))
content: `❌ Failed to entry role`, .catch(async () => {
await interaction.editReply({
content: `❌ Failed to set entry role`,
})
return
}) })
return
})
logger.database(DBTableEnum.WELCOME_ROLE, role.id)
await interaction.editReply({ await interaction.editReply({
content: `✔️ Set welcome channel to ${channel.id}\n✔ Set entry role to ${role.id}`, content: `✔️ Set welcome channel to ${channel.id}\n✔ Set entry role to ${role.id}`,

View File

@ -1,3 +0,0 @@
import { PrismaClient } from '@prisma/client'
export const db = new PrismaClient()

13
src/db/banner.ts Normal file
View File

@ -0,0 +1,13 @@
import { Prisma } from '@prisma/client'
import { db } from './'
export function getBanner() {
return db.banner.findFirst()
}
export function setBanner<T extends keyof Prisma.BannerCreateInput>(key: T, value: Prisma.BannerCreateInput[T]) {
return db.banner.update({
where: { id: 0 },
data: { [key]: value }
})
}

9
src/db/index.ts Normal file
View File

@ -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'

13
src/db/role.ts Normal file
View File

@ -0,0 +1,13 @@
import { Prisma } from '@prisma/client'
import { db } from './'
export function getRole() {
return db.role.findFirst()
}
export function setRole<T extends keyof Prisma.RoleCreateInput>(key: T, value: Prisma.RoleCreateInput[T]) {
return db.role.update({
where: { id: 0 },
data: { [key]: value }
})
}

16
src/db/settings.ts Normal file
View File

@ -0,0 +1,16 @@
import { Prisma } from '@prisma/client'
import { db } from './'
export async function getSettings() {
return db.settings.findFirst()
}
export function setSettings<T extends keyof Prisma.SettingsCreateInput>(
key: T,
value: Prisma.SettingsCreateInput[T],
) {
return db.settings.update({
where: { id: 0 },
data: { [key]: value },
})
}

19
src/db/ticket.ts Normal file
View File

@ -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 }
})
}

82
src/db/user.ts Normal file
View File

@ -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<Prisma.TicketCreateInput, 'User'>,
) {
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,
},
},
},
},
})
}

View File

@ -3,10 +3,9 @@ import {
EmbedBuilder, EmbedBuilder,
channelMention, channelMention,
userMention, userMention,
User,
SnowflakeUtil, SnowflakeUtil,
} from 'discord.js' } from 'discord.js'
import { db, DBTableEnum } from '../db' import { getSettings, getBanner, getRole } from '../db'
import { logger } from '../lib' import { logger } from '../lib'
let lastJoinedUser: { let lastJoinedUser: {
@ -27,29 +26,32 @@ export class GuildMemberAdd {
) )
return return
const portfolioChannelID = await db.get(DBTableEnum.PORTFOLIO_CHANNEL) const settings = await getSettings()
const makeAnOrderChannelID = await db.get( const banner = await getBanner()
DBTableEnum.MAKE_AN_ORDER_CHANNEL, const roles = await getRole()
)
const welcomeChannelID = await db.get(DBTableEnum.WELCOME_CHANNEL)
const imageURL = await db.get(DBTableEnum.BANNER_WELCOME)
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() let embed = new EmbedBuilder()
.setTitle( .setTitle(
`I'm glad to see you on my server. Let me give you a little tour.`, `I'm glad to see you on my server. Let me give you a little tour.`,
) )
.setDescription( .setDescription(
`> 1. In the ${channelMention(portfolioChannelID)} - you can see my previous works. `> 1. In the ${channelMention(settings.portfolioChannelId)} - you can see my previous works.
> 2. Here ${channelMention(makeAnOrderChannelID)} you might open a ticket.`, > 2. Here ${channelMention(settings.makeAnOrderChannelId)} you might open a ticket.`,
) )
.setImage(imageURL) .setImage(banner.welcomeUrl)
const nonce = SnowflakeUtil.generate().toString() const nonce = SnowflakeUtil.generate().toString()
const channel = await member.guild.channels.fetch(welcomeChannelID) const channel = await member.guild.channels.fetch(
const role = await member.guild.roles.fetch(roleID) settings.welcomeChannelId,
)
const role = await member.guild.roles.fetch(roles.welcomeRoleId)
if (channel && channel.isTextBased() && role) { if (channel && channel.isTextBased() && role) {
await channel await channel