From 9e300f17f99e9595f097022923474decfef5545a Mon Sep 17 00:00:00 2001 From: Naomi Carrigan Date: Wed, 13 Nov 2024 11:50:47 -0800 Subject: [PATCH] feat: fetch author data from hashnode --- package.json | 1 + pnpm-lock.yaml | 37 +++++++++++++++------ prisma/schema.prisma | 2 +- src/adminApi.d.ts | 25 --------------- src/commands/author.ts | 66 +++++++++++++++++++++++++------------- src/gql.d.ts | 1 + src/interfaces/hashnode.ts | 11 +++++++ 7 files changed, 84 insertions(+), 59 deletions(-) delete mode 100644 src/adminApi.d.ts create mode 100644 src/gql.d.ts create mode 100644 src/interfaces/hashnode.ts diff --git a/package.json b/package.json index 303bf586..20730250 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "discord.js": "14.16.3", "dotenv": "16.4.5", "fastify": "5.1.0", + "graphql-query": "0.1.2", "highlight.js": "11.10.0", "mongodb": "6.10.0", "node-html-to-image": "5.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22a89d16..bbd88a2c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: fastify: specifier: 5.1.0 version: 5.1.0 + graphql-query: + specifier: 0.1.2 + version: 0.1.2 highlight.js: specifier: 11.10.0 version: 11.10.0 @@ -545,10 +548,6 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.11.0': - resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint-community/regexpp@4.12.1': resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -1253,6 +1252,9 @@ packages: b4a@1.6.6: resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==} + babel-runtime@5.8.38: + resolution: {integrity: sha512-KpgoA8VE/pMmNCrnEeeXqFG24TIH11Z3ZaimIhJWsin8EbfZy3WzFKUTIan10ZIDgRVvi9EkLbruJElJC9dRlg==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1413,6 +1415,10 @@ packages: core-js-compat@3.37.1: resolution: {integrity: sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==} + core-js@1.2.7: + resolution: {integrity: sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA==} + deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js. + cosmiconfig@9.0.0: resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} @@ -1976,6 +1982,9 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + graphql-query@0.1.2: + resolution: {integrity: sha512-9wNLrqasuhAAZSKyl7FJM7Zdfv9WIZfZXyMS4zvukUMelFWEqO8Jc9OzJp4JICcndRhLL5jJ2iNvmFd3ggg3JA==} + handlebars@4.7.8: resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} engines: {node: '>=0.4.7'} @@ -3664,8 +3673,6 @@ snapshots: eslint: 9.14.0 eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.11.0': {} - '@eslint-community/regexpp@4.12.1': {} '@eslint/compat@1.1.1': {} @@ -4161,7 +4168,7 @@ snapshots: '@typescript-eslint/eslint-plugin@8.0.0-alpha.62(@typescript-eslint/parser@8.0.0-alpha.62(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3)': dependencies: - '@eslint-community/regexpp': 4.11.0 + '@eslint-community/regexpp': 4.12.1 '@typescript-eslint/parser': 8.0.0-alpha.62(eslint@9.14.0)(typescript@5.6.3) '@typescript-eslint/scope-manager': 8.0.0-alpha.62 '@typescript-eslint/type-utils': 8.0.0-alpha.62(eslint@9.14.0)(typescript@5.6.3) @@ -4183,7 +4190,7 @@ snapshots: '@typescript-eslint/types': 8.0.0-alpha.62 '@typescript-eslint/typescript-estree': 8.0.0-alpha.62(typescript@5.6.3) '@typescript-eslint/visitor-keys': 8.0.0-alpha.62 - debug: 4.3.6 + debug: 4.3.7 eslint: 9.14.0 optionalDependencies: typescript: 5.6.3 @@ -4204,7 +4211,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 8.0.0-alpha.62(typescript@5.6.3) '@typescript-eslint/utils': 8.0.0-alpha.62(eslint@9.14.0)(typescript@5.6.3) - debug: 4.3.6 + debug: 4.3.7 ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: typescript: 5.6.3 @@ -4235,7 +4242,7 @@ snapshots: dependencies: '@typescript-eslint/types': 8.0.0-alpha.62 '@typescript-eslint/visitor-keys': 8.0.0-alpha.62 - debug: 4.3.6 + debug: 4.3.7 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.5 @@ -4491,6 +4498,10 @@ snapshots: b4a@1.6.6: {} + babel-runtime@5.8.38: + dependencies: + core-js: 1.2.7 + balanced-match@1.0.2: {} bare-events@2.2.1: @@ -4661,6 +4672,8 @@ snapshots: dependencies: browserslist: 4.23.1 + core-js@1.2.7: {} + cosmiconfig@9.0.0(typescript@5.6.3): dependencies: env-paths: 2.2.1 @@ -5381,6 +5394,10 @@ snapshots: graphemer@1.4.0: {} + graphql-query@0.1.2: + dependencies: + babel-runtime: 5.8.38 + handlebars@4.7.8: dependencies: minimist: 1.2.8 diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f1fa9e6d..d2213ba1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -61,5 +61,5 @@ model supporters { model authors { id String @id @default(auto()) @map("_id") @db.ObjectId userId String @unique - email String @unique + hashnodeUsername String @unique } diff --git a/src/adminApi.d.ts b/src/adminApi.d.ts deleted file mode 100644 index 55ef26b8..00000000 --- a/src/adminApi.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -declare module "@tryghost/admin-api" { - - /** - * Barebones type-def to work with our codebase. - */ - // eslint-disable-next-line import/no-default-export - export default class GhostAdminApi { - public users: { - // eslint-disable-next-line @typescript-eslint/method-signature-style - browse(options: { filter: string }): - Promise>>; - }; - - /** - * @param key - The ADMIN api key for your Ghost instance. - * @param url - The base URL for your ghost instance. - * @param version - The version of ghost you are running. - */ - public constructor(config: { - key: string; - url: string; - version: string; - }); - } -} diff --git a/src/commands/author.ts b/src/commands/author.ts index 5f0bb930..acb14eb4 100644 --- a/src/commands/author.ts +++ b/src/commands/author.ts @@ -1,9 +1,9 @@ -// eslint-disable-next-line @typescript-eslint/naming-convention -import GhostAdminApi from "@tryghost/admin-api"; import { InteractionContextType, SlashCommandBuilder } from "discord.js"; +import { gql, request } from "graphql-request"; import { authorRoleId } from "../config/roles.js"; import { errorHandler } from "../utils/errorHandler.js"; import type { Command } from "../interfaces/command.js"; +import type { HashnodeUser } from "../interfaces/hashnode.js"; export const author: Command = { data: new SlashCommandBuilder(). @@ -12,14 +12,14 @@ export const author: Command = { setContexts(InteractionContextType.Guild). addStringOption((option) => { return option. - setName("email"). - setDescription("The email tied to your freeCodeCamp NEWS account."). + setName("username"). + setDescription("Your Hashnode username."). setRequired(true); }), run: async(camperChan, interaction) => { try { await interaction.deferReply({ ephemeral: true }); - const email = interaction.options.getString("email", true); + const username = interaction.options.getString("username", true); const { member } = interaction; if (member.roles.cache.has(authorRoleId)) { await interaction.editReply({ @@ -39,39 +39,59 @@ export const author: Command = { }); return; } - const existsByEmail = await camperChan.db.authors.findUnique({ + const existsByUsername = await camperChan.db.authors.findUnique({ where: { - email, + hashnodeUsername: username, }, }); - if (existsByEmail) { + if (existsByUsername) { await interaction.editReply({ content: - `An author record already exists on your email. If you believe this is an error, please contact Naomi.`, + `An author record already exists on your Hashnode account. If you believe this is an error, please contact Naomi.`, }); return; } - const api = new GhostAdminApi({ - key: camperChan.config.ghostKey, - url: "https://freecodecamp.org/news", - version: "v3", - }); - const user = await api.users. - browse({ filter: `email:'${email}'` }). - catch(() => { - return null; - }); - if (!user || user.length !== 1) { + const ourId = "65dc2b7cbb4eb0cd565b4463"; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call + const query = gql` + query getMember { + user(username: "Koded001") { + publications(first: 50) { + edges { + node { + id + } + } + } + } + } + `; + const data + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/consistent-type-assertions + = await request("https://api.hashnode.com", query) as HashnodeUser; + + if (!data.user) { + await interaction.editReply({ + content: `There does not appear to be a Hashnode account associated with ${username}.`, + }); + return; + } + const publications = data.user.publications.edges; + const isFreeCodeCamp = publications.some((pub) => { + return pub.node.id === ourId; + }); + if (!isFreeCodeCamp) { await interaction.editReply({ - content: `There does not appear to be a news account associated with ${email}. This has been flagged internally for Naomi to investigate.`, + content: + `It appears that you are not a member of the freeCodeCamp publication.`, }); return; } await camperChan.db.authors.create({ data: { - email: email, - userId: member.id, + hashnodeUsername: username, + userId: member.id, }, }); await member.roles.add(authorRoleId).catch(async() => { diff --git a/src/gql.d.ts b/src/gql.d.ts new file mode 100644 index 00000000..5d15c2c6 --- /dev/null +++ b/src/gql.d.ts @@ -0,0 +1 @@ +declare module "graphql-request"; diff --git a/src/interfaces/hashnode.ts b/src/interfaces/hashnode.ts new file mode 100644 index 00000000..6ff020fe --- /dev/null +++ b/src/interfaces/hashnode.ts @@ -0,0 +1,11 @@ +export interface HashnodeUser { + user: { + publications: { + edges: Array<{ + node: { + id: string; + }; + }>; + }; + } | null; +}