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

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

@ -9,20 +9,20 @@ 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
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
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
}

View File

@ -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<Workload>(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<Ticket[]>(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<boolean>
try {
@ -159,9 +167,9 @@ export class CreateTicketSystem {
}
await db
.push<Ticket>(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<Ticket[]>(DBTableEnum.TICKET_OWNERS)
if (tickets) {
const res = tickets.reduce<Ticket[]>((res, ticket) => {
if (
interaction.channel &&
ticket.channel === interaction.channel.id
) {
res.push(ticket)
}
return res
}, [])
await db.set(DBTableEnum.TICKET_OWNERS, res).then(() => {
await db.closeTicket(interaction.channel.id).then(() => {
logger.database(
'Updated active tickets',
`Removed channel ${interaction.channel?.id}`,
)
})
}
logger.action(
'Ticket close attempt successful',

View File

@ -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```',
},
])

View File

@ -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<Ticket[]>(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<Ticket[]>((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')
}
}

View File

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

View File

@ -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 setBanner('ticketUrl', url)
.then(() => logger.database('Banner Ticket URL', url))
.catch(async () => {
await interaction.editReply({
content: `❌ Failed to set banner`,
})
return
})
logger.database(DBTableEnum.BANNER_TICKET, url)
await interaction.editReply({
content: `✔️ Set banner URL to ${url}`,
})

View File

@ -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 setBanner('welcomeUrl', url)
.then(() => logger.database('Welcome Ticket URL', url))
.catch(async () => {
await interaction.editReply({
content: `❌ Failed to set banner`,
})
return
})
logger.database(DBTableEnum.BANNER_WELCOME, url)
await interaction.editReply({
content: `✔️ Set banner URL to ${url}`,
})

View File

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

View File

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

View File

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

View File

@ -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 setSettings('priceChannelId', channel.id)
.then(() => logger.database('Price Channel', channel.id))
.catch(async () => {
await interaction.editReply({
content: `❌ Failed to set price channel`,
})
return
})
logger.database(DBTableEnum.PRICE_CHANNEL, channel.id)
await interaction.editReply({
content: `✔️ Set price channel to ${channel.id}`,
})

View File

@ -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 setSettings('workLoadStatus', status)
.then(() => logger.database('Workload Status', 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)
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}`,
})

View File

@ -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 setRole('welcomeRoleId', role.id)
.then(() => logger.database('Welcome role', role.id))
.catch(async () => {
await interaction.editReply({
content: `❌ Failed to entry role`,
content: `❌ Failed to set entry role`,
})
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}`,

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,
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