From 976e8e7fdce19adf05fde2324d25badac1b5c249 Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Sun, 23 Oct 2022 23:07:54 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=9A=20one=20file=20per=20credit=20tran?= =?UTF-8?q?saction=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands/credits/modules/gift/index.ts | 4 +- src/commands/credits/modules/work/index.ts | 4 +- .../modules/credits/modules/give/index.ts | 4 +- .../modules/credits/modules/set/index.ts | 125 +++---------- .../modules/credits/modules/take/index.ts | 150 +++------------ .../modules/credits/modules/transfer/index.ts | 4 +- .../messageCreate/modules/credits/index.ts | 4 +- src/helpers/credits/give.ts | 54 ++++++ src/helpers/credits/index.ts | 173 ------------------ src/helpers/credits/set.ts | 52 ++++++ src/helpers/credits/take.ts | 55 ++++++ src/helpers/credits/transactionRules.ts | 18 ++ src/helpers/credits/transfer.ts | 97 ++++++++++ 13 files changed, 335 insertions(+), 409 deletions(-) create mode 100644 src/helpers/credits/give.ts delete mode 100644 src/helpers/credits/index.ts create mode 100644 src/helpers/credits/set.ts create mode 100644 src/helpers/credits/take.ts create mode 100644 src/helpers/credits/transactionRules.ts create mode 100644 src/helpers/credits/transfer.ts diff --git a/src/commands/credits/modules/gift/index.ts b/src/commands/credits/modules/gift/index.ts index 5401cd1..bd3a46b 100644 --- a/src/commands/credits/modules/gift/index.ts +++ b/src/commands/credits/modules/gift/index.ts @@ -5,7 +5,7 @@ import { import deferReply from "../../../../handlers/deferReply"; import { success as BaseEmbedSuccess } from "../../../../helpers/baseEmbeds"; -import { transfer as CreditsTransfer } from "../../../../helpers/credits"; +import creditsTransfer from "../../../../helpers/credits/transfer"; // 1. Export a builder function. export const builder = (command: SlashCommandSubcommandBuilder) => { @@ -55,7 +55,7 @@ export const execute = async (interaction: ChatInputCommandInteraction) => { const EmbedSuccess = await BaseEmbedSuccess(guild, "[:dollar:] Gift"); // 5. Start an transaction of the credits. - await CreditsTransfer(guild, user, target, credits); + await creditsTransfer(guild, user, target, credits); // 6. Tell the target that they have been gifted credits. await target.send({ diff --git a/src/commands/credits/modules/work/index.ts b/src/commands/credits/modules/work/index.ts index a4b4ba9..71e2c3d 100644 --- a/src/commands/credits/modules/work/index.ts +++ b/src/commands/credits/modules/work/index.ts @@ -6,7 +6,7 @@ import { command as CooldownCommand } from "../../../../handlers/cooldown"; import prisma from "../../../../handlers/database"; import deferReply from "../../../../handlers/deferReply"; import { success as BaseEmbedSuccess } from "../../../../helpers/baseEmbeds"; -import { give as CreditsGive } from "../../../../helpers/credits"; +import creditsGive from "../../../../helpers/credits/give"; import logger from "../../../../middlewares/logger"; // 1. Export a builder function. @@ -52,7 +52,7 @@ export const execute = async (interaction: CommandInteraction) => { max: createGuild.creditsWorkRate, }); - const upsertGuildMember = await CreditsGive(guild, user, creditsEarned); + const upsertGuildMember = await creditsGive(guild, user, creditsEarned); // 8. Send embed. await interaction.editReply({ diff --git a/src/commands/manage/modules/credits/modules/give/index.ts b/src/commands/manage/modules/credits/modules/give/index.ts index 2ecc4ea..2b0e1f9 100644 --- a/src/commands/manage/modules/credits/modules/give/index.ts +++ b/src/commands/manage/modules/credits/modules/give/index.ts @@ -4,7 +4,7 @@ import { ChatInputCommandInteraction, PermissionsBitField } from "discord.js"; import deferReply from "../../../../../../handlers/deferReply"; import { success as baseEmbedSuccess } from "../../../../../../helpers/baseEmbeds"; import checkPermission from "../../../../../../helpers/checkPermission"; -import { give as CreditsGive } from "../../../../../../helpers/credits"; +import creditsGive from "../../../../../../helpers/credits/give"; import pluralize from "../../../../../../helpers/pluralize"; export default { @@ -51,7 +51,7 @@ export default { const embedSuccess = await baseEmbedSuccess(guild, "[:toolbox:] Give"); // 6. Give the credits. - await CreditsGive(guild, discordReceiver, creditsAmount); + await creditsGive(guild, discordReceiver, creditsAmount); // 7. Send embed. return await interaction.editReply({ diff --git a/src/commands/manage/modules/credits/modules/set/index.ts b/src/commands/manage/modules/credits/modules/set/index.ts index 10dd37f..ed7deb4 100644 --- a/src/commands/manage/modules/credits/modules/set/index.ts +++ b/src/commands/manage/modules/credits/modules/set/index.ts @@ -1,21 +1,14 @@ -// Dependencies -// Helpers -// Models -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { ChatInputCommandInteraction, - EmbedBuilder, PermissionsBitField, + SlashCommandSubcommandBuilder, } from "discord.js"; -// Configurations -import getEmbedConfig from "../../../../../../helpers/getEmbedData"; -// Handlers -import prisma from "../../../../../../handlers/database"; -import deferReply from "../../../../../../handlers/deferReply"; -import checkPermission from "../../../../../../helpers/checkPermission"; -import logger from "../../../../../../middlewares/logger"; -// Function +import deferReply from "../../../../../../handlers/deferReply"; +import { success as baseEmbedSuccess } from "../../../../../../helpers/baseEmbeds"; +import checkPermission from "../../../../../../helpers/checkPermission"; +import creditsSet from "../../../../../../helpers/credits/set"; + export default { builder: (command: SlashCommandSubcommandBuilder) => { return command @@ -35,107 +28,35 @@ export default { ); }, execute: async (interaction: ChatInputCommandInteraction) => { + // 1. Defer reply as ephemeral. await deferReply(interaction, true); + // 2. Check if the user has the permission to manage the guild. checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); + // 3. Destructure interaction object. const { options, guild } = interaction; + if (!guild) throw new Error(`We could not find this guild.`); + if (!options) throw new Error(`We could not find the options.`); + // 4. Get the user and amount from the options. const discordUser = options.getUser("user"); const creditAmount = options.getInteger("amount"); + if (typeof creditAmount !== "number") throw new Error("Amount is not set."); + if (!discordUser) throw new Error("User is not specified"); - // If amount is null - if (creditAmount === null) { - logger?.silly(`Amount is null`); + // 5. Set the credits. + await creditsSet(guild, discordUser, creditAmount); - return interaction?.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Set)") - .setDescription(`You must provide an amount.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } + // 6. Create base embeds. + const embedSuccess = await baseEmbedSuccess(guild, "[:toolbox:] Set"); - if (discordUser === null) { - logger?.silly(`User is null`); - - return interaction?.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Set)") - .setDescription(`You must provide a user.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - if (guild === null) { - logger?.silly(`Guild is null`); - - return interaction?.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Set)") - .setDescription(`You must provide a guild.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - const createGuildMember = await prisma.guildMember.upsert({ - where: { - userId_guildId: { - userId: discordUser.id, - guildId: guild.id, - }, - }, - update: { creditsEarned: creditAmount }, - create: { - creditsEarned: creditAmount, - user: { - connectOrCreate: { - create: { - id: discordUser.id, - }, - where: { - id: discordUser.id, - }, - }, - }, - guild: { - connectOrCreate: { - create: { - id: guild.id, - }, - where: { - id: guild.id, - }, - }, - }, - }, - }); - - logger.silly(createGuildMember); - - return interaction?.editReply({ + // 7. Send embed. + return await interaction.editReply({ embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Set)") - .setDescription( - `Set **${discordUser}**'s credits to **${creditAmount}**.` - ) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + embedSuccess.setDescription( + `Set **${discordUser}**'s credits to **${creditAmount}**.` + ), ], }); }, diff --git a/src/commands/manage/modules/credits/modules/take/index.ts b/src/commands/manage/modules/credits/modules/take/index.ts index 9e808af..340eb80 100644 --- a/src/commands/manage/modules/credits/modules/take/index.ts +++ b/src/commands/manage/modules/credits/modules/take/index.ts @@ -1,22 +1,15 @@ -// Dependencies -// Models -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { ChatInputCommandInteraction, - EmbedBuilder, PermissionsBitField, + SlashCommandSubcommandBuilder, } from "discord.js"; -// Configurations -import getEmbedConfig from "../../../../../../helpers/getEmbedData"; -// Helpers../../../../../../../helpers/userData -import pluralize from "../../../../../../helpers/pluralize"; -// Handlers -import prisma from "../../../../../../handlers/database"; -import deferReply from "../../../../../../handlers/deferReply"; -import checkPermission from "../../../../../../helpers/checkPermission"; -import logger from "../../../../../../middlewares/logger"; -// Function +import deferReply from "../../../../../../handlers/deferReply"; +import { success as baseEmbedSuccess } from "../../../../../../helpers/baseEmbeds"; +import checkPermission from "../../../../../../helpers/checkPermission"; +import creditsTake from "../../../../../../helpers/credits/take"; +import pluralize from "../../../../../../helpers/pluralize"; + export default { builder: (command: SlashCommandSubcommandBuilder) => { return command @@ -36,127 +29,36 @@ export default { ); }, execute: async (interaction: ChatInputCommandInteraction) => { + // 1. Defer reply as ephemeral. await deferReply(interaction, true); + // 2. Check if the user has the MANAGE_GUILD permission. checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); // Destructure + // 3. Destructure interaction object. const { guild, options } = interaction; + if (!guild) throw new Error("Invalid guild."); + if (!options) throw new Error("Invalid options."); - // User option - const discordReceiver = options?.getUser("user"); + // 4. Get the user and amount from the options. + const discordReceiver = options.getUser("user"); + const optionAmount = options.getInteger("amount"); + if (typeof optionAmount !== "number") throw new Error("Invalid amount."); + if (!discordReceiver) throw new Error("Invalid user."); - // Amount option - const optionAmount = options?.getInteger("amount"); + // 5. Create base embeds. + const embedSuccess = await baseEmbedSuccess(guild, "[:toolbox:] Take"); - // If amount is null - if (optionAmount === null) { - logger?.silly(`Amount is null`); + // 6. Take the credits. + await creditsTake(guild, discordReceiver, optionAmount); - return interaction?.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Take)") - .setDescription(`You must provide an amount.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - // If amount is zero or below - if (optionAmount <= 0) { - logger?.silly(`Amount is zero or below`); - - return interaction?.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Take)") - .setDescription(`You must provide an amount greater than zero.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - if (discordReceiver === null) { - logger?.silly(`Discord receiver is null`); - - return interaction?.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Take)") - .setDescription(`You must provide a user.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - if (guild === null) { - logger?.silly(`Guild is null`); - - return interaction?.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Take)") - .setDescription(`You must be in a guild.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - const createGuildMember = await prisma.guildMember.upsert({ - where: { - userId_guildId: { - userId: discordReceiver.id, - guildId: guild.id, - }, - }, - update: { creditsEarned: { decrement: optionAmount } }, - create: { - creditsEarned: -optionAmount, - user: { - connectOrCreate: { - create: { - id: discordReceiver.id, - }, - where: { - id: discordReceiver.id, - }, - }, - }, - guild: { - connectOrCreate: { - create: { - id: guild.id, - }, - where: { - id: guild.id, - }, - }, - }, - }, - }); - - logger.silly(createGuildMember); - await interaction?.editReply({ + // 7. Send embed. + return await interaction.editReply({ embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Take)") - .setDescription( - `Took ${pluralize(optionAmount, "credit")} from ${discordReceiver}.` - ) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + embedSuccess.setDescription( + `Took ${pluralize(optionAmount, "credit")} from ${discordReceiver}.` + ), ], }); - return; }, }; diff --git a/src/commands/manage/modules/credits/modules/transfer/index.ts b/src/commands/manage/modules/credits/modules/transfer/index.ts index 723d76d..93f8570 100644 --- a/src/commands/manage/modules/credits/modules/transfer/index.ts +++ b/src/commands/manage/modules/credits/modules/transfer/index.ts @@ -6,7 +6,7 @@ import { EmbedBuilder, PermissionsBitField, } from "discord.js"; -import { transfer as CreditsTransfer } from "../../../../../../helpers/credits"; +import creditsTransfer from "../../../../../../helpers/credits/transfer"; // Configurations import deferReply from "../../../../../../handlers/deferReply"; import checkPermission from "../../../../../../helpers/checkPermission"; @@ -65,7 +65,7 @@ export default { if (!optionToUser) throw new Error("You must provide a user to transfer to."); - await CreditsTransfer(guild, optionFromUser, optionToUser, optionAmount); + await creditsTransfer(guild, optionFromUser, optionToUser, optionAmount); return interaction?.editReply({ embeds: [ diff --git a/src/events/messageCreate/modules/credits/index.ts b/src/events/messageCreate/modules/credits/index.ts index 39534d1..4daa57e 100644 --- a/src/events/messageCreate/modules/credits/index.ts +++ b/src/events/messageCreate/modules/credits/index.ts @@ -1,7 +1,7 @@ import { ChannelType, Message } from "discord.js"; import { message as CooldownMessage } from "../../../../handlers/cooldown"; import prisma from "../../../../handlers/database"; -import { give as CreditsGive } from "../../../../helpers/credits"; +import creditsGive from "../../../../helpers/credits/give"; import logger from "../../../../middlewares/logger"; export default { @@ -59,6 +59,6 @@ export default { ); if (isOnCooldown) return; - await CreditsGive(guild, author, createGuildMember.guild.creditsRate); + await creditsGive(guild, author, createGuildMember.guild.creditsRate); }, }; diff --git a/src/helpers/credits/give.ts b/src/helpers/credits/give.ts new file mode 100644 index 0000000..3882db4 --- /dev/null +++ b/src/helpers/credits/give.ts @@ -0,0 +1,54 @@ +import { Guild, User } from "discord.js"; +import prisma from "../../handlers/database"; +import transactionRules from "./transactionRules"; + +export default async (guild: Guild, user: User, amount: number) => { + return await prisma.$transaction(async (tx) => { + // 1. Check if the transaction is valid. + await transactionRules(guild, user, amount); + + // 2. Make the transaction. + const recipient = await tx.guildMember.upsert({ + update: { + creditsEarned: { + increment: amount, + }, + }, + create: { + user: { + connectOrCreate: { + create: { + id: user.id, + }, + where: { + id: user.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + creditsEarned: amount, + }, + where: { + userId_guildId: { + userId: user.id, + guildId: guild.id, + }, + }, + }); + + // 3. Verify that the recipient actually is created. + if (!recipient) throw new Error("No recipient available"); + + // 4. Return the recipient. + return recipient; + }); +}; diff --git a/src/helpers/credits/index.ts b/src/helpers/credits/index.ts deleted file mode 100644 index bacec5d..0000000 --- a/src/helpers/credits/index.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { Guild, User } from "discord.js"; -import prisma from "../../handlers/database"; - -// Start an transaction between two users in a guild. -export const transfer = async ( - guild: Guild, - from: User, - to: User, - amount: number -) => { - return await prisma.$transaction(async (tx) => { - // 1. Decrement amount from the sender. - const sender = await tx.guildMember.upsert({ - update: { - creditsEarned: { - decrement: amount, - }, - }, - create: { - user: { - connectOrCreate: { - create: { - id: from.id, - }, - where: { - id: from.id, - }, - }, - }, - guild: { - connectOrCreate: { - create: { - id: guild.id, - }, - where: { - id: guild.id, - }, - }, - }, - creditsEarned: -amount, - }, - where: { - userId_guildId: { - userId: from.id, - guildId: guild.id, - }, - }, - }); - - // 2. Verify that the sender actually is created. - if (!sender) throw new Error("No sender available"); - - // 3. Verify that the sender's balance exists. - if (!sender.creditsEarned) throw new Error("No credits available"); - - // 4. Verify that the sender's balance didn't go below zero. - if (sender.creditsEarned < 0) { - throw new Error(`${from} doesn't have enough to send ${amount}`); - } - - // 5. Verify that the sender is not trying to send less that one credits. - if (amount <= 0) { - throw new Error("You can't transfer below one credit."); - } - - // 6. Verify that the sender is not trying to send more than 100.000.000 credits. - if (amount > 100000000) { - throw new Error("You can't transfer more than 100.000.000 credits."); - } - - // 7. Verify that recipient are not an bot. - if (to.bot) throw new Error("You can't transfer to an bot."); - - // 8. Verify that sender and recipient are not the same user. - if (from.id === to.id) throw new Error("You can't transfer to yourself."); - - // 9. Increment the recipient's balance by amount. - const recipient = await tx.guildMember.upsert({ - update: { - creditsEarned: { - increment: amount, - }, - }, - create: { - user: { - connectOrCreate: { - create: { - id: to.id, - }, - where: { - id: to.id, - }, - }, - }, - guild: { - connectOrCreate: { - create: { - id: guild.id, - }, - where: { - id: guild.id, - }, - }, - }, - creditsEarned: amount, - }, - where: { - userId_guildId: { - userId: to.id, - guildId: guild.id, - }, - }, - }); - - return recipient; - }); -}; - -// Give to guildMember -export const give = async (guild: Guild, user: User, amount: number) => { - // 1. Verify that the amount is not above 100.000.000 credits. - if (amount > 100000000) { - throw new Error("You can't give more than 1.000.000 credits."); - } - - // 2. Verify that the amount is not below 1 credits. - if (amount <= 0) { - throw new Error("You can't give below one credit."); - } - - // 3. Verify that the user is not an bot. - if (user.bot) { - throw new Error("You can't give to an bot."); - } - - // 4. Increment the user's balance by amount. - return await prisma.guildMember.upsert({ - update: { - creditsEarned: { - increment: amount, - }, - }, - create: { - user: { - connectOrCreate: { - create: { - id: user.id, - }, - where: { - id: user.id, - }, - }, - }, - guild: { - connectOrCreate: { - create: { - id: guild.id, - }, - where: { - id: guild.id, - }, - }, - }, - creditsEarned: amount, - }, - where: { - userId_guildId: { - userId: user.id, - guildId: guild.id, - }, - }, - }); -}; diff --git a/src/helpers/credits/set.ts b/src/helpers/credits/set.ts new file mode 100644 index 0000000..2db8cf7 --- /dev/null +++ b/src/helpers/credits/set.ts @@ -0,0 +1,52 @@ +import { Guild, User } from "discord.js"; +import prisma from "../../handlers/database"; +import transactionRules from "./transactionRules"; + +export default async (guild: Guild, user: User, amount: number) => { + return await prisma.$transaction(async (tx) => { + // 1. Check if the transaction is valid. + await transactionRules(guild, user, amount); + + // 2. Make the transaction. + const recipient = await tx.guildMember.upsert({ + update: { + creditsEarned: amount, + }, + create: { + user: { + connectOrCreate: { + create: { + id: user.id, + }, + where: { + id: user.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + creditsEarned: amount, + }, + where: { + userId_guildId: { + userId: user.id, + guildId: guild.id, + }, + }, + }); + + // 3. Verify that the recipient actually is created. + if (!recipient) throw new Error("No recipient available"); + + // 4. Return the recipient. + return recipient; + }); +}; diff --git a/src/helpers/credits/take.ts b/src/helpers/credits/take.ts new file mode 100644 index 0000000..ae21ed6 --- /dev/null +++ b/src/helpers/credits/take.ts @@ -0,0 +1,55 @@ +import { Guild, User } from "discord.js"; +import prisma from "../../handlers/database"; +import transactionRules from "./transactionRules"; + +export default async (guild: Guild, user: User, amount: number) => { + return await prisma.$transaction(async (tx) => { + // 1. Check if the transaction is valid. + await transactionRules(guild, user, amount); + + // 2. Make the transaction. + const recipient = await tx.guildMember.upsert({ + update: { + creditsEarned: { + decrement: amount, + }, + }, + create: { + user: { + connectOrCreate: { + create: { + id: user.id, + }, + where: { + id: user.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + creditsEarned: -amount, + }, + where: { + userId_guildId: { + userId: user.id, + guildId: guild.id, + }, + }, + }); + + // 3. Verify that the recipient credits are not below zero. + if (recipient.creditsEarned < -100) + throw new Error("User do not have enough credits"); + + // 4. Return the recipient. + return recipient; + }); +}; diff --git a/src/helpers/credits/transactionRules.ts b/src/helpers/credits/transactionRules.ts new file mode 100644 index 0000000..021448f --- /dev/null +++ b/src/helpers/credits/transactionRules.ts @@ -0,0 +1,18 @@ +import { Guild, User } from "discord.js"; + +export default async (guild: Guild, user: User, amount: number) => { + // 1. Verify that the amount is not above 100.000.000 credits. + if (amount > 100000000) { + throw new Error("You can't give more than 1.000.000 credits."); + } + + // 2. Verify that the amount is not below 1 credits. + if (amount <= 0) { + throw new Error("You can't give below one credit."); + } + + // 3. Verify that the user is not an bot. + if (user.bot) { + throw new Error("You can't give to an bot."); + } +}; diff --git a/src/helpers/credits/transfer.ts b/src/helpers/credits/transfer.ts new file mode 100644 index 0000000..7177328 --- /dev/null +++ b/src/helpers/credits/transfer.ts @@ -0,0 +1,97 @@ +import { Guild, User } from "discord.js"; +import prisma from "../../handlers/database"; +import transactionRules from "./transactionRules"; + +export default async (guild: Guild, from: User, to: User, amount: number) => { + return await prisma.$transaction(async (tx) => { + // 1. Decrement amount from the sender. + const sender = await tx.guildMember.upsert({ + update: { + creditsEarned: { + decrement: amount, + }, + }, + create: { + user: { + connectOrCreate: { + create: { + id: from.id, + }, + where: { + id: from.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + creditsEarned: -amount, + }, + where: { + userId_guildId: { + userId: from.id, + guildId: guild.id, + }, + }, + }); + + // 4. Verify that the sender's balance didn't go below zero. + if (sender.creditsEarned < 0) { + throw new Error(`${from} doesn't have enough to send ${amount}`); + } + + // 5. Check if the transactions is valid. + await transactionRules(guild, from, amount); + await transactionRules(guild, to, amount); + + // 6. Verify that sender and recipient are not the same user. + if (from.id === to.id) throw new Error("You can't transfer to yourself."); + + // 7. Increment the recipient's balance by amount. + const recipient = await tx.guildMember.upsert({ + update: { + creditsEarned: { + increment: amount, + }, + }, + create: { + user: { + connectOrCreate: { + create: { + id: to.id, + }, + where: { + id: to.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + creditsEarned: amount, + }, + where: { + userId_guildId: { + userId: to.id, + guildId: guild.id, + }, + }, + }); + + return recipient; + }); +};