diff --git a/.env.prod b/.env.prod index 39a95853..478c2593 100644 --- a/.env.prod +++ b/.env.prod @@ -2,6 +2,7 @@ DISCORD_BOT_TOKEN= DISCORD_BOT_PUBLIC_KEY= DISCORD_BOT_APPLICATION_ID= DISCORD_OWNER_ID= +MONGODB_PREFIX=mongodb+srv MONGODB_USERNAME= MONGODB_PASS= MONGODB_CLUSTER= diff --git a/.env.qa b/.env.qa index 23d4c9ab..eb823261 100644 --- a/.env.qa +++ b/.env.qa @@ -2,6 +2,7 @@ DISCORD_BOT_TOKEN= DISCORD_BOT_PUBLIC_KEY= DISCORD_BOT_APPLICATION_ID= DISCORD_OWNER_ID= +MONGODB_PREFIX=mongodb+srv MONGODB_USERNAME= MONGODB_PASS= MONGODB_CLUSTER= diff --git a/docs/CHANGELOG.md b/CHANGELOG.md similarity index 88% rename from docs/CHANGELOG.md rename to CHANGELOG.md index 053ecab7..e48393e6 100644 --- a/docs/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 1.4.0-RELEASE (2021-08-31) + +1. Fix docker db connection +2. Use mongodb connection pools +3. Expand bounty copies to lvl2+ +4. Add Pradhumna Pancholi#3700 to POAP manager list +5. Allow lvl2+ contributors, admin, and genesis squad to use /poap command + ## 1.3.2-RELEASE (2021-08-27) 1. Wrap all of guildmember in try/catch block diff --git a/Dockerfile b/Dockerfile index 5aba84fa..7dfb038a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,4 +8,6 @@ RUN yarn install COPY . ./ -RUN yarn build \ No newline at end of file +RUN yarn build + +CMD ["yarn", "start"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 01cb8974..38aa5065 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,17 +2,16 @@ version: '3.8' services: bot: + container_name: degen_bot build: . - restart: always - volumes: - - .:/app - - /app/node_modules - command: yarn start - working_dir: /app - ports: - - 80:3000 + environment: + MONGODB_PREFIX: mongodb + MONGODB_USERNAME: dev + MONGODB_PASS: pass + MONGODB_CLUSTER: mongo mongo: + container_name: degen_mongo image: mongo:4.4.6 environment: MONGO_INITDB_ROOT_USERNAME: dev @@ -21,6 +20,7 @@ services: - 27017:27017 volumes: - mongodb:/data/db + - ./src/app/utils/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro volumes: mongodb: \ No newline at end of file diff --git a/package.json b/package.json index a3c557e0..badb7163 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "degen", - "version": "1.3.2", + "version": "1.4.0", "description": "Administrative and Utilitarian bot for the Bankless Discord Server.", "main": "app.js", "private": true, diff --git a/src/app/app.ts b/src/app/app.ts index d034464d..ca455b5a 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -13,9 +13,9 @@ const creator = new SlashCreator({ token: process.env.DISCORD_BOT_TOKEN, }); -creator.on('debug', (message) => console.log(message)); -creator.on('warn', (message) => console.warn(message)); -creator.on('error', (error) => console.error(error)); +creator.on('debug', (message) => console.log(`debug: ${ message }`)); +creator.on('warn', (message) => console.warn(`warn: ${ message }`)); +creator.on('error', (error) => console.error(`error: ${ error }`)); creator.on('synced', () => console.info('Commands synced!')); creator.on('commandRegister', (command) => console.info(`Registered command ${command.commandName}`)); creator.on('commandError', (command, error) => console.error(`Command ${command.commandName}:`, error)); diff --git a/src/app/commands/admin/GuestPass.ts b/src/app/commands/admin/GuestPass.ts index d74ecfe4..eeb6446d 100644 --- a/src/app/commands/admin/GuestPass.ts +++ b/src/app/commands/admin/GuestPass.ts @@ -1,10 +1,16 @@ -import { SlashCommand, CommandOptionType, ApplicationCommandPermissionType, CommandContext } from 'slash-create'; +import { + SlashCommand, + CommandOptionType, + ApplicationCommandPermissionType, + CommandContext, + SlashCreator, +} from 'slash-create'; import client from '../../app'; import roleIds from '../../service/constants/roleIds'; import { addGuestRoleToUser } from '../../service/guest-pass/AddGuestPass'; export default class GuestPass extends SlashCommand { - constructor(creator) { + constructor(creator: SlashCreator) { super(creator, { name: 'guest-pass', description: 'Grant a temporary guest pass to a user', @@ -39,7 +45,7 @@ export default class GuestPass extends SlashCommand { }); } - async run(ctx: CommandContext) { + async run(ctx: CommandContext): Promise { if (ctx.user.bot) return; console.log('/guest-pass start'); @@ -58,5 +64,5 @@ export default class GuestPass extends SlashCommand { return ctx.send(`<@${ctx.user.id}> guest pass added and message sent!`); } -}; +} diff --git a/src/app/commands/bounty/Bounty.ts b/src/app/commands/bounty/Bounty.ts index e7f37d3c..e664d1e2 100644 --- a/src/app/commands/bounty/Bounty.ts +++ b/src/app/commands/bounty/Bounty.ts @@ -189,12 +189,22 @@ export default class Bounty extends SlashCommand { id: roleIds.level4, permission: true, }, + { + type: ApplicationCommandPermissionType.ROLE, + id: roleIds.admin, + permission: true, + }, + { + type: ApplicationCommandPermissionType.ROLE, + id: roleIds.genesisSquad, + permission: true, + }, ], }, }); } - async run(ctx: CommandContext) { + async run(ctx: CommandContext): Promise { if (ctx.user.bot) return; console.log(`start /bounty ${ctx.user.username}#${ctx.user.discriminator}`); @@ -242,7 +252,7 @@ export default class Bounty extends SlashCommand { } } - handleCommandError(ctx: CommandContext, command: Promise) { + handleCommandError(ctx: CommandContext, command: Promise): void { command.then(() => { console.log(`end /bounty ${ctx.user.username}#${ctx.user.discriminator}`); return ctx.send(`${ctx.user.mention} Sent you a DM with information.`); @@ -256,7 +266,7 @@ export default class Bounty extends SlashCommand { }); } - buildBountyCreateNewParams(ctxOptions): BountyCreateNew { + buildBountyCreateNewParams(ctxOptions: { [key: string]: any }): BountyCreateNew { const [reward, symbol] = (ctxOptions.reward != null) ? ctxOptions.reward.split(' ') : [null, null]; const copies = (ctxOptions.copies == null || ctxOptions.copies <= 0) ? 1 : ctxOptions.copies; let scale = reward.split('.')[1]?.length; @@ -272,4 +282,4 @@ export default class Bounty extends SlashCommand { copies: copies, }; } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/app/commands/help/FeatureRequest.ts b/src/app/commands/help/FeatureRequest.ts index b133bee0..c728bfc6 100644 --- a/src/app/commands/help/FeatureRequest.ts +++ b/src/app/commands/help/FeatureRequest.ts @@ -1,7 +1,7 @@ -import { SlashCommand } from 'slash-create'; +import { CommandContext, SlashCommand, SlashCreator } from 'slash-create'; export default class FeatureRequest extends SlashCommand { - constructor(creator) { + constructor(creator: SlashCreator) { super(creator, { name: 'feature-request', description: 'Pull up the form to submit a new feature request', @@ -13,7 +13,7 @@ export default class FeatureRequest extends SlashCommand { }); } - async run(ctx) { + async run(ctx: CommandContext): Promise { // Ignores commands from bots if (ctx.user.bot) return; console.log('/featureRequest start'); @@ -21,4 +21,4 @@ export default class FeatureRequest extends SlashCommand { console.log('/featureRequest end'); return `Here you are ${ctx.user.mention}, the DEGEN feature request form: ${form}`; } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/app/commands/help/Help.ts b/src/app/commands/help/Help.ts index b50f3571..9bef69dd 100644 --- a/src/app/commands/help/Help.ts +++ b/src/app/commands/help/Help.ts @@ -32,7 +32,7 @@ export default class Help extends SlashCommand { }); } - async run(ctx: CommandContext) { + async run(ctx: CommandContext): Promise { if (ctx.user.bot) return; console.log(`/help start ${ctx.user.username}#${ctx.user.discriminator}`); @@ -51,4 +51,4 @@ export default class Help extends SlashCommand { console.log(`/bounty end ${ctx.user.username}#${ctx.user.discriminator}`); return ctx.send(messageOptions); } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/app/commands/notion/NotionFAQs.ts b/src/app/commands/notion/NotionFAQs.ts index 93950eab..b469f5df 100644 --- a/src/app/commands/notion/NotionFAQs.ts +++ b/src/app/commands/notion/NotionFAQs.ts @@ -1,11 +1,11 @@ -import { SlashCommand, CommandOptionType } from 'slash-create'; +import { SlashCommand, CommandOptionType, CommandContext, SlashCreator } from 'slash-create'; import client from '../../app'; import RetrieveFAQs from '../../service/notion/RetrieveFAQs'; const trimPageId = process.env.FAQS_PAGE_ID.replace(/-/g, ''); const FAQ_URL = `https://www.notion.so/FAQs-${trimPageId}`; export default class NotionFAQs extends SlashCommand { - constructor(creator) { + constructor(creator: SlashCreator) { super(creator, { name: 'faqs', description: 'Get frequently asked questions', @@ -24,7 +24,7 @@ export default class NotionFAQs extends SlashCommand { }); } - async run(ctx) { + async run(ctx: CommandContext): Promise { // Ignores commands from bots if (ctx.user.bot) return; console.log('/faqs start'); @@ -102,4 +102,4 @@ export default class NotionFAQs extends SlashCommand { console.error(e); } } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/app/commands/notion/NotionGuildPage.ts b/src/app/commands/notion/NotionGuildPage.ts index 10ca627a..8a2d95e6 100644 --- a/src/app/commands/notion/NotionGuildPage.ts +++ b/src/app/commands/notion/NotionGuildPage.ts @@ -1,8 +1,8 @@ -import { SlashCommand, CommandOptionType } from 'slash-create'; +import { SlashCommand, CommandOptionType, CommandContext, SlashCreator } from 'slash-create'; import notionPageRefs from '../../service/notion/NotionGuildPages'; export default class NotionGuildPage extends SlashCommand { - constructor(creator) { + constructor(creator: SlashCreator) { super(creator, { name: 'notion', description: 'View a Guild\'s notion page', @@ -76,7 +76,7 @@ export default class NotionGuildPage extends SlashCommand { }); } - async run(ctx) { + async run(ctx: CommandContext): Promise { // Ignores commands from bots if (ctx.user.bot) return; console.log('/notion start'); @@ -85,4 +85,4 @@ export default class NotionGuildPage extends SlashCommand { console.log('/notion end'); return `Here you are ${ctx.user.mention}, the ${ctx.options.guild} Guild Notion Page: ${page}`; } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/app/commands/poap/poap.ts b/src/app/commands/poap/poap.ts index 451de36c..5c649778 100644 --- a/src/app/commands/poap/poap.ts +++ b/src/app/commands/poap/poap.ts @@ -13,6 +13,7 @@ import EndPOAP from '../../service/poap/EndPOAP'; import ValidationError from '../../errors/ValidationError'; import poapEvents from '../../service/constants/poapEvents'; import DistributePOAP from '../../service/poap/DistributePOAP'; +import roleIds from '../../service/constants/roleIds'; module.exports = class poap extends SlashCommand { constructor(creator: SlashCreator) { @@ -106,18 +107,44 @@ module.exports = class poap extends SlashCommand { }, defaultPermission: false, permissions: { - [process.env.DISCORD_SERVER_ID]: getAllowedUsers(), + [process.env.DISCORD_SERVER_ID]: [ + { + type: ApplicationCommandPermissionType.ROLE, + id: roleIds.level2, + permission: true, + }, + { + type: ApplicationCommandPermissionType.ROLE, + id: roleIds.level3, + permission: true, + }, + { + type: ApplicationCommandPermissionType.ROLE, + id: roleIds.level4, + permission: true, + }, + { + type: ApplicationCommandPermissionType.ROLE, + id: roleIds.admin, + permission: true, + }, + { + type: ApplicationCommandPermissionType.ROLE, + id: roleIds.genesisSquad, + permission: true, + }, + ], }, }); } - + async run(ctx: CommandContext) { if (ctx.user.bot) return; console.log(`start /poap ${ctx.user.username}#${ctx.user.discriminator}`); - + const { guildMember } = await ServiceUtils.getGuildAndMember(ctx); let command: Promise; - + try { switch (ctx.subcommands[0]) { case 'start': @@ -172,4 +199,21 @@ export const getAllowedUsers = (): ApplicationCommandPermissions[] =>{ permission: true, }); return allowedPermissions; -}; \ No newline at end of file +}; + +// TODO: pass this as a DM conversation... looks like client is not available until after slash commands are set +// export const getAllVoiceChannels = async (): Promise => { +// // const voiceChannels: Collection = client.channels.cache.filter(guildChannel => guildChannel.type === ChannelTypes.GUILD_VOICE.toString()); +// // const choices = []; +// // for (const channel of voiceChannels.values()) { +// // choices.push({ +// // name: channel.type, +// // value: channel.id, +// // }); +// // } +// // return choices; +// return [{ +// name: 'blank', +// value: 'asdfsdf', +// }]; +// }; \ No newline at end of file diff --git a/src/app/events/bounty/messageCreateOnBountyBoard.ts b/src/app/events/bounty/messageCreateOnBountyBoard.ts index 40dfbf8d..91f78931 100644 --- a/src/app/events/bounty/messageCreateOnBountyBoard.ts +++ b/src/app/events/bounty/messageCreateOnBountyBoard.ts @@ -35,7 +35,5 @@ export default async (message: Message): Promise => { return guildMember.send({ content: 'Sorry something is not working, our devs are looking into it.' }); } - await dbInstance.close(); - return guildMember.send({ content: `Bounty published to #🧀-bounty-board and the website! ${envUrls.BOUNTY_BOARD_URL}${bountyId}` }); }; \ No newline at end of file diff --git a/src/app/events/poap/addUserForEvent.ts b/src/app/events/poap/addUserForEvent.ts index 181d741b..11e058d7 100644 --- a/src/app/events/poap/addUserForEvent.ts +++ b/src/app/events/poap/addUserForEvent.ts @@ -29,7 +29,7 @@ export default async (oldState: VoiceState, newState: VoiceState, event: { id: s await updateUserForPOAP(newState.member, db, event.value, false).catch(console.error); } - return dbInstance.close(); + return; }; export const isPOAPTrackingActive = async (db: Db, eventValue: string): Promise => { diff --git a/src/app/events/ready.ts b/src/app/events/ready.ts index c4970cda..38d0c8c7 100644 --- a/src/app/events/ready.ts +++ b/src/app/events/ready.ts @@ -4,6 +4,9 @@ import GuestPassService from '../service/guest-pass/GuestPassService'; import { Client } from 'discord.js'; +import constants from '../service/constants/constants'; +import { connect } from '../utils/db'; + module.exports = { name: 'ready', @@ -12,6 +15,8 @@ module.exports = { async execute(client: Client) { console.log('The Sun will never set on the DAO. Neither will I. DEGEN & Serendipity are ready for service.'); client.user.setActivity('Going Bankless, Doing the DAO'); + await connect(constants.DB_NAME_DEGEN); + await connect(constants.DB_NAME_BOUNTY_BOARD); await GuestPassService(client); }, }; \ No newline at end of file diff --git a/src/app/service/bounty/ClaimBounty.ts b/src/app/service/bounty/ClaimBounty.ts index 7fda0792..1fdad398 100644 --- a/src/app/service/bounty/ClaimBounty.ts +++ b/src/app/service/bounty/ClaimBounty.ts @@ -62,7 +62,7 @@ export const claimBountyForValidId = async (guildMember: GuildMember, console.log(`${bountyId} bounty claimed by ${guildMember.user.tag}`); await claimBountyMessage(guildMember, dbBountyResult.discordMessageId, message); - await dbInstance.close(); + // await dbInstance.close(); return guildMember.send({ content: ` Bounty claimed! If you have any questions, please reach out to <@${createdByUser.id}>. ${envUrls.BOUNTY_BOARD_URL}${bountyId}` }); }; diff --git a/src/app/service/bounty/CompleteBounty.ts b/src/app/service/bounty/CompleteBounty.ts index d56f9cd2..f15e6195 100644 --- a/src/app/service/bounty/CompleteBounty.ts +++ b/src/app/service/bounty/CompleteBounty.ts @@ -60,7 +60,7 @@ export const completeBountyForValidId = async (guildMember: GuildMember, console.log(`${bountyId} bounty reviewed by ${guildMember.user.tag}`); await completeBountyMessage(guildMember, dbBountyResult.discordMessageId, message); await guildMember.send({ content: `Bounty complete! Please remember to tip <@${dbBountyResult.claimedBy.discordId}>` }); - return dbInstance.close(); + return; }; export const completeBountyMessage = async (guildMember: GuildMember, bountyMessageId: string, message?: Message): Promise => { diff --git a/src/app/service/bounty/DeleteBounty.ts b/src/app/service/bounty/DeleteBounty.ts index a8a1a811..da4066ed 100644 --- a/src/app/service/bounty/DeleteBounty.ts +++ b/src/app/service/bounty/DeleteBounty.ts @@ -65,7 +65,6 @@ export const deleteBountyForValidId = async (guildMember: GuildMember, console.log(`${bountyId} bounty deleted by ${guildMember.user.tag}`); await deleteBountyMessage(guildMember, dbBountyResult.discordMessageId, message); - await dbInstance.close(); return guildMember.send({ content: `Bounty \`${bountyId}\` deleted, thanks.` }); }; diff --git a/src/app/service/bounty/ListBounty.ts b/src/app/service/bounty/ListBounty.ts index 9866f947..785d2fea 100644 --- a/src/app/service/bounty/ListBounty.ts +++ b/src/app/service/bounty/ListBounty.ts @@ -36,11 +36,9 @@ export default async (guildMember: GuildMember, listType: string): Promise return guildMember.send({ content: 'Please use a valid list-type' }); } if (!await dbRecords.hasNext()) { - await dbInstance.close(); return guildMember.send({ content: 'We couldn\'t find any bounties!' }); } - await sendMultipleMessages(guildMember, dbRecords); - return dbInstance.close(); + return sendMultipleMessages(guildMember, dbRecords); }; const sendMultipleMessages = async (guildMember: GuildMember, dbRecords: Cursor): Promise => { diff --git a/src/app/service/bounty/ReCreateBounty.ts b/src/app/service/bounty/ReCreateBounty.ts index e6732997..2ae2ee71 100644 --- a/src/app/service/bounty/ReCreateBounty.ts +++ b/src/app/service/bounty/ReCreateBounty.ts @@ -60,7 +60,6 @@ export default async (guildMember: GuildMember, bountyId: string): Promise from Bankless DAO needs some help with bounty ${bountyUrl}. Please reach out to them to check.` }); } console.log(`message sent requesting help for bounty ${bountyId} submitted by ${guildMember.user.tag}`); - await guildMember.send({ content: `SOS sent, look out for a follow up message for bounty ${bountyUrl}` }); - return dbInstance.close(); + return guildMember.send({ content: `SOS sent, look out for a follow up message for bounty ${bountyUrl}` }); }; \ No newline at end of file diff --git a/src/app/service/bounty/SubmitBounty.ts b/src/app/service/bounty/SubmitBounty.ts index a4b43690..18bc4fcf 100644 --- a/src/app/service/bounty/SubmitBounty.ts +++ b/src/app/service/bounty/SubmitBounty.ts @@ -75,8 +75,7 @@ export const submitBountyForValidId = async (guildMember: GuildMember, const createdByUser: GuildMember = await guildMember.guild.members.fetch(dbBountyResult.createdBy.discordId); await createdByUser.send({ content: `Please reach out to <@${guildMember.user.id}>. They are ready for bounty review ${bountyUrl}` }); - await guildMember.send({ content: `Bounty in review! Expect a message from <@${dbBountyResult.createdBy.discordId}>` }); - return dbInstance.close(); + return guildMember.send({ content: `Bounty in review! Expect a message from <@${dbBountyResult.createdBy.discordId}>` }); }; export const submitBountyMessage = async (guildMember: GuildMember, bountyMessageId: string, message?: Message): Promise => { diff --git a/src/app/service/bounty/UpdateEditKeyBounty.ts b/src/app/service/bounty/UpdateEditKeyBounty.ts index 33dfa527..4f2d5cc7 100644 --- a/src/app/service/bounty/UpdateEditKeyBounty.ts +++ b/src/app/service/bounty/UpdateEditKeyBounty.ts @@ -41,6 +41,5 @@ export default async (guildMember: GuildMember, bountyId: string, message?: Mess return guildMember.send({ content: 'Sorry something is not working, can you try again?' }); } - await dbInstance.close(); return guildMember.send({ content: `Bounty can be edited at ${envUrls.BOUNTY_BOARD_URL}${bountyId}/edit?key=${secretEditKey}` }); }; \ No newline at end of file diff --git a/src/app/service/bounty/create/CreateNewBounty.ts b/src/app/service/bounty/create/CreateNewBounty.ts index ddeda070..ce48bd45 100644 --- a/src/app/service/bounty/create/CreateNewBounty.ts +++ b/src/app/service/bounty/create/CreateNewBounty.ts @@ -102,7 +102,6 @@ export default async (guildMember: GuildMember, params: BountyCreateNew): Promis }, }], }; - await dbInstance.close(); await guildMember.send('Thank you! Does this look right?'); const message: Message = await guildMember.send(messageOptions); diff --git a/src/app/service/bounty/create/PublishBounty.ts b/src/app/service/bounty/create/PublishBounty.ts index 019c2c5f..fda78352 100644 --- a/src/app/service/bounty/create/PublishBounty.ts +++ b/src/app/service/bounty/create/PublishBounty.ts @@ -55,8 +55,6 @@ export const finalizeBounty = async (guildMember: GuildMember, bountyId: string) return guildMember.send({ content: 'Sorry something is not working, our devs are looking into it.' }); } - await dbInstance.close(); - return guildMember.send({ content: `Bounty published to #🧀-bounty-board and the website! ${envUrls.BOUNTY_BOARD_URL}${bountyId}` }); }; diff --git a/src/app/service/constants/constants.ts b/src/app/service/constants/constants.ts index 798f88bd..d74ad2bd 100644 --- a/src/app/service/constants/constants.ts +++ b/src/app/service/constants/constants.ts @@ -7,7 +7,7 @@ export default Object.freeze({ DB_COLLECTION_POAP_SETTINGS: 'poapSettings', DB_COLLECTION_POAP_PARTICIPANTS: 'poapParticipants', - MONGODB_URI_PARTIAL: `mongodb+srv://${process.env.MONGODB_USERNAME}:${process.env.MONGODB_PASS}@${process.env.MONGODB_CLUSTER}/`, + MONGODB_URI_PARTIAL: `${process.env.MONGODB_PREFIX}://${process.env.MONGODB_USERNAME}:${process.env.MONGODB_PASS}@${process.env.MONGODB_CLUSTER}/`, MONGODB_OPTIONS: '?retryWrites=true&w=majority', BOUNTY_BOARD_WEBSITE_WEBHOOK_NAME: 'bounty-board-website', diff --git a/src/app/service/guest-pass/AddGuestPass.ts b/src/app/service/guest-pass/AddGuestPass.ts index 03f24ceb..8a7ae417 100644 --- a/src/app/service/guest-pass/AddGuestPass.ts +++ b/src/app/service/guest-pass/AddGuestPass.ts @@ -42,8 +42,6 @@ export const addGuestUserToDb = async (guestUser: GuildMember): Promise => console.error('Failed to insert into DB'); return; } - - await dbInstance.close(); console.log(`/guest-pass end user ${guestUser.user.tag} inserted into guestUsers`); }; @@ -78,7 +76,6 @@ export const removeGuestRoleOnExpiration = (guestUser: GuildMember): void => { console.error('Failed to remove from DB'); return; } - await dbInstance.close(); console.log(`guest pass removed for ${guestUser.user.tag} in db`); // Remove guest pass role diff --git a/src/app/service/guest-pass/GuestPassService.ts b/src/app/service/guest-pass/GuestPassService.ts index 0211b92a..d0c69607 100644 --- a/src/app/service/guest-pass/GuestPassService.ts +++ b/src/app/service/guest-pass/GuestPassService.ts @@ -64,8 +64,6 @@ export default async (client: DiscordClient): Promise => { // discord api rate limit of 50 calls per second await sleep(1000); } - - await dbInstance.close(); // Begin reminder of active guest users for (const activeUser of listOfActiveGuests) { @@ -99,7 +97,6 @@ export default async (client: DiscordClient): Promise => { console.error('Failed to remove user from DB'); return; } - await dbInstance.close(); console.log(`guest pass removed for ${activeUser._id} in db`); await guildMember.send({ content: `Hi <@${activeUser._id}>, your guest pass has expired. Let us know at Bankless DAO if this was a mistake!` }); diff --git a/src/app/service/guest-pass/RemoveGuestPass.ts b/src/app/service/guest-pass/RemoveGuestPass.ts index c353bbe8..3d570345 100644 --- a/src/app/service/guest-pass/RemoveGuestPass.ts +++ b/src/app/service/guest-pass/RemoveGuestPass.ts @@ -23,8 +23,7 @@ export const removeGuestUserFromDb = async (guestUser: GuildMember): Promise { fields: [ { name: '-> /poap start', - value: 'Start keeping track of attendees in the voice channel. The voice channel depends on the event.\n\n ' + - 'Community Call: Community Calls (stage)\n' + - 'Dev Guild: dev workroom', + value: 'Start keeping track of attendees in the voice channel. Once started it must be stopped by the same user.', inline: false, }, { diff --git a/src/app/service/notion/RetrieveFAQs.ts b/src/app/service/notion/RetrieveFAQs.ts index 995a0f2e..ba7cbc9a 100644 --- a/src/app/service/notion/RetrieveFAQs.ts +++ b/src/app/service/notion/RetrieveFAQs.ts @@ -1,5 +1,5 @@ import { Client as NotionClient } from '@notionhq/client'; -import { notionQueue } from "./NotionQueue"; +import { notionQueue } from './NotionQueue'; const notion = new NotionClient({ auth: process.env.NOTION_TOKEN }); export default async (): Promise> => { diff --git a/src/app/service/notion/__mocks__/RetrieveFAQs.ts b/src/app/service/notion/__mocks__/RetrieveFAQs.ts index c4a12f65..46ac109f 100644 --- a/src/app/service/notion/__mocks__/RetrieveFAQs.ts +++ b/src/app/service/notion/__mocks__/RetrieveFAQs.ts @@ -1,101 +1,101 @@ export default (): any => { - return [ - { - question: '1. What is Bankless DAO?', - answer: ' Bankless DAO is a decentralized community focused on driving adoption and awareness of bankless money systems like Ethereum, Bitcoin and DeFi. You can learn more here: https://bankless-dao.gitbook.io/bankless-dao/starting-with-bankless-dao ' - }, - { - question: '2. How do I join?', - answer: ' To join the community and become a member please go to #start-here . You’ll need 35,000 BANK to access the first level and start contributing. Here’s how you can contribute: https://bankless-dao.gitbook.io/bankless-dao/starting-with-bankless-dao ' - }, - { - question: '3. How do I get BANK?', - answer: ' You can claim your BANK by visiting the Bankless DAO website https://www.bankless.community/claim and connecting your wallet. To qualify for a BANK claim you need to have:\n' + + return [ + { + question: '1. What is Bankless DAO?', + answer: ' Bankless DAO is a decentralized community focused on driving adoption and awareness of bankless money systems like Ethereum, Bitcoin and DeFi. You can learn more here: https://bankless-dao.gitbook.io/bankless-dao/starting-with-bankless-dao ', + }, + { + question: '2. How do I join?', + answer: ' To join the community and become a member please go to #start-here . You’ll need 35,000 BANK to access the first level and start contributing. Here’s how you can contribute: https://bankless-dao.gitbook.io/bankless-dao/starting-with-bankless-dao ', + }, + { + question: '3. How do I get BANK?', + answer: ' You can claim your BANK by visiting the Bankless DAO website https://www.bankless.community/claim and connecting your wallet. To qualify for a BANK claim you need to have:\n' + ' - Claimed your Bankless badge before May 4th, 2021\n' + ' - Donated to Bankless through Gitcoin grants\n' + - ' - Owned a BAP0 before May 1st, 2021 ‌ For further details of everyone eligible for BANK please digest https://medium.com/bankless-dao/announcing-bankless-dao-133220f5efd8 ‌' - }, - { - question: '4. I didn’t qualify for BANK distribution. How do I get BANK?', - answer: ' The Bankless DAO is already discussing a further distribution of BANK to premium subscribers and the lowering of the 35,000 BANK threshold to Discord membership. You can also swap ERC-20 tokens or ether for BANK on popular decentralized exchanges:\n' + + ' - Owned a BAP0 before May 1st, 2021 ‌ For further details of everyone eligible for BANK please digest https://medium.com/bankless-dao/announcing-bankless-dao-133220f5efd8 ‌', + }, + { + question: '4. I didn’t qualify for BANK distribution. How do I get BANK?', + answer: ' The Bankless DAO is already discussing a further distribution of BANK to premium subscribers and the lowering of the 35,000 BANK threshold to Discord membership. You can also swap ERC-20 tokens or ether for BANK on popular decentralized exchanges:\n' + ' - Uniswap V2 ETH/BANK Pair\n' + - ' - Uniswap V3 ETH/BANK Pair' - }, - { - question: '5. What’s the purpose of BANK?', - answer: ' The goal of BANK is to create a tokenized vehicle to coordinate actions in line with the DAO’s mission. You can learn more about the Mission, Vision and Core Values of the Bankless DAO here: https://bankless-dao.gitbook.io/bankless-dao/starting-with-bankless-dao ‌' - }, - { - question: '6. How can I contribute?', - answer: ' Share your skills and step up to help the Bankless DAO achieve its mission. When you become a member of the Discord, go to #intros and tell us about yourself and how you can contribute. Here’s the getting started with Bankless DAO guide https://medium.com/bankless-dao/getting-started-with-bankless-dao-94b0b60e052e ‌' - }, - { - question: '7. How do I vote on proposals?', - answer: ' Help guide the future of Bankless DAO by voting on proposals with the BANK in your wallet. Here’s the official Snapshot page https://snapshot.org/#/banklessvault.eth where proposals are open for three days of voting. ‌' - }, - { - question: '8. What is the Genesis Proposal?', - answer: ' The Genesis Proposal has closed for voting but is essential reading for DAO contributors, as it lays the foundation for key infrastructure of the Bankless DAO https://snapshot.org/#/banklessvault.eth/proposal/QmdoixPMMT76vSt6ewkE87JZJywS1piYsGC3nJJpcrPXKS The Genesis Proposal is the first proposal by Bankless LLC to grant a portion of the community treasury to the company in order to act as core participants in the DAO. Since BANK was a fair launch, Bankless LLC did not receive any BANK. It’s up to the community to choose its path. ‌' - }, - { - question: '9. I donated to Bankless through Gitcoin but didn’t get BANK?', - answer: ' If you donated more than $0.95 to Bankless through Gitcoin, but your address shows ‘0 BANK’ when you try and claim https://www.bankless.community/claim, please send a DM to our community manager @Above Average Joe with the details of your donation. ‌' - }, - { - question: '10. I tried joining but didn’t receive a response from the Collab.Land bot?', - answer: ' Check that your Discord settings allow you to receive direct messages and make sure that the wallet you are connecting with has 35,000 BANK. Type !join in #join and try again. ‌' - }, - { - question: '11. I’m still having problems trying to join Discord?', - answer: ' Try using the latest version of MetaMask https://metamask.io/download.html to join Discord. You can import your wallet or send BANK to a new address to join. If you are still struggling please ask for help in the #support channel. ‌' - }, - { - question: '12. Do I need to hold $BANK to maintain my status in the Discord?', - answer: ' Yes, you will need to hold $BANK in your wallet that is connected to collab.land bot in order to maintain your status in Discord and not be removed by the bot. ‌' - }, - { - question: '13. Can collab.land bot “trace” the address associated with my Discord to a yield farm or LP and allow me to maintain my status?', - answer: ' No the bot can’t track your status to a yield farm or LP token, if you move your $BANK tokens from your associated wallet, you will be unable to maintain your status in the Bankless DAO Discord. ‌' - }, - { - question: '14. What do the different level channels represent?\n', - answer: '\n' + + ' - Uniswap V3 ETH/BANK Pair', + }, + { + question: '5. What’s the purpose of BANK?', + answer: ' The goal of BANK is to create a tokenized vehicle to coordinate actions in line with the DAO’s mission. You can learn more about the Mission, Vision and Core Values of the Bankless DAO here: https://bankless-dao.gitbook.io/bankless-dao/starting-with-bankless-dao ‌', + }, + { + question: '6. How can I contribute?', + answer: ' Share your skills and step up to help the Bankless DAO achieve its mission. When you become a member of the Discord, go to #intros and tell us about yourself and how you can contribute. Here’s the getting started with Bankless DAO guide https://medium.com/bankless-dao/getting-started-with-bankless-dao-94b0b60e052e ‌', + }, + { + question: '7. How do I vote on proposals?', + answer: ' Help guide the future of Bankless DAO by voting on proposals with the BANK in your wallet. Here’s the official Snapshot page https://snapshot.org/#/banklessvault.eth where proposals are open for three days of voting. ‌', + }, + { + question: '8. What is the Genesis Proposal?', + answer: ' The Genesis Proposal has closed for voting but is essential reading for DAO contributors, as it lays the foundation for key infrastructure of the Bankless DAO https://snapshot.org/#/banklessvault.eth/proposal/QmdoixPMMT76vSt6ewkE87JZJywS1piYsGC3nJJpcrPXKS The Genesis Proposal is the first proposal by Bankless LLC to grant a portion of the community treasury to the company in order to act as core participants in the DAO. Since BANK was a fair launch, Bankless LLC did not receive any BANK. It’s up to the community to choose its path. ‌', + }, + { + question: '9. I donated to Bankless through Gitcoin but didn’t get BANK?', + answer: ' If you donated more than $0.95 to Bankless through Gitcoin, but your address shows ‘0 BANK’ when you try and claim https://www.bankless.community/claim, please send a DM to our community manager @Above Average Joe with the details of your donation. ‌', + }, + { + question: '10. I tried joining but didn’t receive a response from the Collab.Land bot?', + answer: ' Check that your Discord settings allow you to receive direct messages and make sure that the wallet you are connecting with has 35,000 BANK. Type !join in #join and try again. ‌', + }, + { + question: '11. I’m still having problems trying to join Discord?', + answer: ' Try using the latest version of MetaMask https://metamask.io/download.html to join Discord. You can import your wallet or send BANK to a new address to join. If you are still struggling please ask for help in the #support channel. ‌', + }, + { + question: '12. Do I need to hold $BANK to maintain my status in the Discord?', + answer: ' Yes, you will need to hold $BANK in your wallet that is connected to collab.land bot in order to maintain your status in Discord and not be removed by the bot. ‌', + }, + { + question: '13. Can collab.land bot “trace” the address associated with my Discord to a yield farm or LP and allow me to maintain my status?', + answer: ' No the bot can’t track your status to a yield farm or LP token, if you move your $BANK tokens from your associated wallet, you will be unable to maintain your status in the Bankless DAO Discord. ‌', + }, + { + question: '14. What do the different level channels represent?\n', + answer: '\n' + ' - 🧑‍Member (Level 1) : 35,000 BANK\n' + ' - 🤝 Contributor ( Level 2 ): 35,000 BANK + Nomination/Vote process .\n' + ' - 🐋 Whale (Level 3) : 150,000 BANK\n' + - ' - 🌊 Liquidity Provider (Level 4): 250 Uniswap V2 BANK/ETH LP tokens As you move up levels, you get access to additional channels that will enable you to contribute in certain areas to the DAO, for example business development, social media management, design, etc ‌' - }, - { - question: '15. Who do I contact about a listing proposal for BANK?', - answer: ' BANK is an ERC-20 token and can be listed freely by any exchange. ‌' - }, - { - question: '16. What are Guilds? How do I join?', - answer: ' Untitled ' - }, - { - question: '17. How do I edit Notion?', - answer: ' Ask for Guest access from aboveaveragejoe, frogmonkee, kouros, 0xLucas, or wolfehr on Discord' - }, - { - question: '18: How can I earn Bank?', - answer: '\n' + + ' - 🌊 Liquidity Provider (Level 4): 250 Uniswap V2 BANK/ETH LP tokens As you move up levels, you get access to additional channels that will enable you to contribute in certain areas to the DAO, for example business development, social media management, design, etc ‌', + }, + { + question: '15. Who do I contact about a listing proposal for BANK?', + answer: ' BANK is an ERC-20 token and can be listed freely by any exchange. ‌', + }, + { + question: '16. What are Guilds? How do I join?', + answer: ' Untitled ', + }, + { + question: '17. How do I edit Notion?', + answer: ' Ask for Guest access from aboveaveragejoe, frogmonkee, kouros, 0xLucas, or wolfehr on Discord', + }, + { + question: '18: How can I earn Bank?', + answer: '\n' + ' - Tips from other bankless members for valuable or desirable contributions in the moment. (This can be for work done, well spoken comments, answering other member questions, or whatever else the tipper values)\n' + ' - Bounties posted in the #🧀-bounty-board channel. Bounties are claimed upon delivery of requested action or material.\n' + ' - Grants. Bankless grants are planned for outstanding effort to assist in the growth of the Dao, or for other items as determined by the grants committee. (Coming soon)\n' + - ' - Contributor rewards. Retroactive remuneration for part to full time contribution to the development of the Dao, to be decided by each guild respectively. (Coming soon)' - }, - { - question: "19: I don't have enough Bank to be a full member, and can only speak in the level 0 area. How can I get more involved?", - answer: ' Find an area of the Dao where you feel you may be able to contribute, and reach out to anyone with the @Ops Guild tag or @Contributors (Lvl 2) tag. Let them know where you would like to be involved, or what you have been working on to add value to the dao. You can also speak in #🎢get-involved to find your connection point. Those who contribute are given guest passes into the Dao, and are allowed to retain them until either they go inactive or achieve level 1 status through earnings for contributions.' - }, - { - question: '20. When are community calls?', - answer: ' Community calls are every Friday at 11am EDT ---> https://www.worldtimebuddy.com/ to convert locally' - }, - { - question: '21. I have an idea, how do I make a proposal?', - answer: ' Read under How to Gather Consensus on this page: Untitled ' - } - ] + ' - Contributor rewards. Retroactive remuneration for part to full time contribution to the development of the Dao, to be decided by each guild respectively. (Coming soon)', + }, + { + question: '19: I don\'t have enough Bank to be a full member, and can only speak in the level 0 area. How can I get more involved?', + answer: ' Find an area of the Dao where you feel you may be able to contribute, and reach out to anyone with the @Ops Guild tag or @Contributors (Lvl 2) tag. Let them know where you would like to be involved, or what you have been working on to add value to the dao. You can also speak in #🎢get-involved to find your connection point. Those who contribute are given guest passes into the Dao, and are allowed to retain them until either they go inactive or achieve level 1 status through earnings for contributions.', + }, + { + question: '20. When are community calls?', + answer: ' Community calls are every Friday at 11am EDT ---> https://www.worldtimebuddy.com/ to convert locally', + }, + { + question: '21. I have an idea, how do I make a proposal?', + answer: ' Read under How to Gather Consensus on this page: Untitled ', + }, + ]; }; \ No newline at end of file diff --git a/src/app/service/poap/DistributePOAP.ts b/src/app/service/poap/DistributePOAP.ts index db8f0094..aa266cc3 100644 --- a/src/app/service/poap/DistributePOAP.ts +++ b/src/app/service/poap/DistributePOAP.ts @@ -32,8 +32,7 @@ export default async (guildMember: GuildMember, event: string): Promise => errors: ['time'], }; const poapLinksFile: MessageAttachment = (await dmChannel.awaitMessages(replyOptions)).first().attachments.first(); - await sendOutPOAPLinks(poapGuildManager, listOfParticipants, poapLinksFile); - return dbInstance.close(); + return sendOutPOAPLinks(poapGuildManager, listOfParticipants, poapLinksFile); }; export const getListOfParticipants = async (guildMember: GuildMember, db: Db, event: string) diff --git a/src/app/service/poap/EndPOAP.ts b/src/app/service/poap/EndPOAP.ts index 7d938548..1187b24d 100644 --- a/src/app/service/poap/EndPOAP.ts +++ b/src/app/service/poap/EndPOAP.ts @@ -1,4 +1,4 @@ -import { AwaitMessagesOptions, DMChannel, Guild, GuildMember, MessageAttachment } from 'discord.js'; +import { AwaitMessagesOptions, DMChannel, GuildMember, MessageAttachment } from 'discord.js'; import { Collection, Cursor, Db, UpdateWriteOpResult } from 'mongodb'; import dbInstance from '../../utils/db'; import constants from '../constants/constants'; @@ -61,7 +61,7 @@ export default async (guildMember: GuildMember, event: string): Promise => } else { await poapGuildManager.send({ content: 'You got it!' }); } - return dbInstance.close(); + return; }; export const getListOfParticipants = async (guildMember: GuildMember, db: Db, event: string) diff --git a/src/app/service/poap/StartPOAP.ts b/src/app/service/poap/StartPOAP.ts index 626f2dd6..fcd1803e 100644 --- a/src/app/service/poap/StartPOAP.ts +++ b/src/app/service/poap/StartPOAP.ts @@ -39,8 +39,7 @@ export default async (guildMember: GuildMember, event: string): Promise => }, }); await storePresentMembers(guildMember.guild, event, db); - await guildMember.send({ content: `POAP tracking started for \`${event}\`.` }); - return dbInstance.close(); + return guildMember.send({ content: `POAP tracking started for \`${event}\`.` }); }; export const setupPoapSetting = async (guildMember: GuildMember, poapSettingsDB: Collection, occasion: string): Promise => { diff --git a/src/app/utils/BountyUtils.ts b/src/app/utils/BountyUtils.ts index ef00f0ab..f94e1195 100644 --- a/src/app/utils/BountyUtils.ts +++ b/src/app/utils/BountyUtils.ts @@ -77,16 +77,10 @@ const BountyUtils = { throw new ValidationError('Please try another reward.'); } }, - + validateNumberOfCopies(guildMember: GuildMember, copies: number): void { - const isLevel3 = ServiceUtils.isLevel3(guildMember); - const isLevel4 = ServiceUtils.isLevel4(guildMember); - const isAdmin = ServiceUtils.isAdmin(guildMember); - - if (!(isLevel3 || isLevel4 || isAdmin)) { - throw new ValidationError('Must be `level 3+` to publish multiple copies.'); - } - + ServiceUtils.validateLevel2AboveMembers(guildMember); + if (copies > 100) { throw new ValidationError('Max number of copies is `100`.'); } @@ -119,7 +113,7 @@ const BountyUtils = { throw new ValidationError('Please try another criteria.'); } }, - + validateDate(guildMember: GuildMember, date: string): Date { try { return new Date(date + 'T00:00:00.000Z'); @@ -143,7 +137,7 @@ const BountyUtils = { throw new ValidationError('Please try another url.'); } }, - + async checkBountyExists(guildMember: GuildMember, dbBountyResult: any | null, bountyId: string): Promise { if (dbBountyResult == null) { console.log(`${bountyId} bounty not found in db`); @@ -152,7 +146,7 @@ const BountyUtils = { } console.log(`found bounty ${bountyId} in db`); }, - + async getBountyMessage(guildMember: GuildMember, bountyMessageId: string, message?: Message): Promise { if (message == null) { const bountyChannel: TextChannel = await guildMember.guild.channels.fetch(channelIds.bountyBoard) as TextChannel; @@ -164,15 +158,15 @@ const BountyUtils = { return message; } }, - + getBountyIdFromEmbedMessage(message: Message): string { return message.embeds[0].fields[0].value; }, - + formatBountyAmount(amount: number, scale: number): string { return (amount / 10 ** scale).toString(); }, - + getDateFromISOString(date: string): Date { return new Date(date); }, diff --git a/src/app/utils/ServiceUtils.ts b/src/app/utils/ServiceUtils.ts index 55e99c7c..a3363815 100644 --- a/src/app/utils/ServiceUtils.ts +++ b/src/app/utils/ServiceUtils.ts @@ -5,6 +5,7 @@ import { CommandContext } from 'slash-create'; import { Guild, GuildMember, Role, RoleManager } from 'discord.js'; import client from '../app'; import roleIDs from '../service/constants/roleIds'; +import ValidationError from '../errors/ValidationError'; const ServiceUtils = { async getGuildAndMember(ctx: CommandContext): Promise<{ guild: Guild, guildMember: GuildMember }> { @@ -41,6 +42,10 @@ const ServiceUtils = { return guildMember.roles.cache.some(role => role.id === roleIDs.level4); }, + isGenesisSquad(guildMember: GuildMember): boolean { + return guildMember.roles.cache.some(role => role.id === roleIDs.genesisSquad); + }, + isAnyLevel(guildMember: GuildMember): boolean { console.log(guildMember.roles.cache); return guildMember.roles.cache.some(role => role.id === roleIDs.level1 @@ -56,6 +61,18 @@ const ServiceUtils = { }; return (new Date(dateIso)).toLocaleString('en-US', options); }, + + validateLevel2AboveMembers(guildMember: GuildMember): void { + const isLevel2 = ServiceUtils.isLevel2(guildMember); + const isLevel3 = ServiceUtils.isLevel3(guildMember); + const isLevel4 = ServiceUtils.isLevel4(guildMember); + const isGenesisSquad = ServiceUtils.isGenesisSquad(guildMember); + const isAdmin = ServiceUtils.isAdmin(guildMember); + + if (!(isLevel2 || isLevel3 || isLevel4 || isAdmin || isGenesisSquad)) { + throw new ValidationError('Must be `level 2` or above member.'); + } + }, }; export default ServiceUtils; \ No newline at end of file diff --git a/src/app/utils/db.ts b/src/app/utils/db.ts index 99dcf332..9854aa45 100644 --- a/src/app/utils/db.ts +++ b/src/app/utils/db.ts @@ -1,41 +1,30 @@ -import { Db, MongoClient } from 'mongodb'; +import { Db, MongoClient, MongoClientOptions } from 'mongodb'; import constants from '../service/constants/constants'; -const state: {db: Db, client: MongoClient, mode} = { - db: null, - client: null, - mode: null, +const state: { dbMap: Map, clientMap: Map } = { + dbMap: new Map(), + clientMap: new Map(), }; -const dbInstance = { - connect: (database: string): Promise => { - return MongoClient.connect( - constants.MONGODB_URI_PARTIAL + database + constants.MONGODB_OPTIONS, - { useUnifiedTopology: true }).then((client: MongoClient) => { - state.db = client.db(database); - state.client = client; - return client; - }).catch(e => { - console.log('ERROR', e); - return e; - }); - }, - - db(): Db { - return state.db; - }, - +export default { async dbConnect(database: string): Promise { - await this.connect(database); - return this.db(); - }, - - close(): Promise { - const closePromise = state.client.close(); - state.db = null; - state.client = null; - return closePromise; + const db = state.dbMap.get(database); + if (db == null) { + await connect(database); + } + return db; }, }; -export default dbInstance; +export const connect = async (database: string): Promise => { + console.log(`connecting to ${database} for first time`); + const options: MongoClientOptions = { + writeConcern: { + w: 'majority', + }, + useUnifiedTopology: true, + }; + const mongoClient = await MongoClient.connect(constants.MONGODB_URI_PARTIAL + database, options); + state.clientMap.set(database, mongoClient); + state.dbMap.set(database, mongoClient.db(database)); +}; diff --git a/src/app/utils/mongo-init.js b/src/app/utils/mongo-init.js new file mode 100644 index 00000000..c8e9e1ba --- /dev/null +++ b/src/app/utils/mongo-init.js @@ -0,0 +1,21 @@ +/** + * Script to create local databases + */ + +db = db.getSiblingDB('degen'); +db.createUser( + { + user: 'dev', + pwd: 'pass', + roles: [{ role: 'readWrite', db: 'degen' }], + }, +); + +db = db.getSiblingDB('bountyboard'); +db.createUser( + { + user: 'dev', + pwd: 'pass', + roles: [{ role: 'readWrite', db: 'bountyboard' }], + }, +); \ No newline at end of file diff --git a/src/test/commands/notion/NotionFAQs.test.ts b/src/test/commands/notion/NotionFAQs.test.ts index 48c8117c..0897ee60 100644 --- a/src/test/commands/notion/NotionFAQs.test.ts +++ b/src/test/commands/notion/NotionFAQs.test.ts @@ -1,7 +1,6 @@ - import assert from 'assert'; import RetrieveFAQs from '../../../app/service/notion/RetrieveFAQs'; -jest.mock('../../../app/service/notion/RetrieveFAQs') +jest.mock('../../../app/service/notion/RetrieveFAQs'); describe('NotionFAQs', () => { beforeEach(() => {