From b970fc2107956bed09c4d6c2bee5fa224219fd7e Mon Sep 17 00:00:00 2001 From: Luke Dinh Date: Fri, 4 Aug 2023 17:49:53 -0700 Subject: [PATCH 01/17] feat: initial backend typescript setup - prisma schema naming changes - added typescript eslint parser --- packages/server/.eslintrc.json | 16 +- packages/server/__tests__/tsconfig.json | 0 packages/server/controllers/auth.js | 2 +- packages/server/controllers/events.js | 2 +- packages/server/controllers/users.js | 2 +- packages/server/package.json | 8 +- packages/server/prisma/prisma.js | 3 - packages/server/prisma/schema.prisma | 38 +-- packages/server/tsconfig.json | 15 ++ packages/server/utils/postgres.js | 17 -- packages/server/utils/prisma.ts | 3 + packages/server/utils/prismaTS.ts | 26 -- yarn.lock | 345 +++++++++++++++++------- 13 files changed, 315 insertions(+), 162 deletions(-) create mode 100644 packages/server/__tests__/tsconfig.json delete mode 100644 packages/server/prisma/prisma.js create mode 100644 packages/server/tsconfig.json delete mode 100644 packages/server/utils/postgres.js create mode 100644 packages/server/utils/prisma.ts delete mode 100644 packages/server/utils/prismaTS.ts diff --git a/packages/server/.eslintrc.json b/packages/server/.eslintrc.json index 6d63e57f..76ecd1e6 100644 --- a/packages/server/.eslintrc.json +++ b/packages/server/.eslintrc.json @@ -1,7 +1,19 @@ { + "root": true, + "parser": "@typescript-eslint/parser", "parserOptions": { - "sourceType": "module", + "sourceType": "script", "ecmaVersion": 2023 }, - "extends": "prettier" + "plugins": ["@typescript-eslint"], + "env": { + "node": true, + "amd": true + }, + "rules": {}, + "extends": [ + "prettier", + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ] } diff --git a/packages/server/__tests__/tsconfig.json b/packages/server/__tests__/tsconfig.json new file mode 100644 index 00000000..e69de29b diff --git a/packages/server/controllers/auth.js b/packages/server/controllers/auth.js index b20a9182..291f10a8 100644 --- a/packages/server/controllers/auth.js +++ b/packages/server/controllers/auth.js @@ -1,6 +1,6 @@ const { OAuth2Client } = require("google-auth-library"); const { v5: uuidv5 } = require("uuid"); -const prisma = require("../prisma/prisma"); +const prisma = require("../utils/prisma"); const token = require("../utils/token"); const client = new OAuth2Client(process.env.WEB_CLIENT_ID); diff --git a/packages/server/controllers/events.js b/packages/server/controllers/events.js index d05a308a..d11f9ee1 100644 --- a/packages/server/controllers/events.js +++ b/packages/server/controllers/events.js @@ -1,5 +1,5 @@ // const postgres = require("../utils/postgres"); -const prisma = require("../prisma/prisma"); +const prisma = require("../utils/prisma"); async function getAllEvents() { return prisma.event.findMany(); diff --git a/packages/server/controllers/users.js b/packages/server/controllers/users.js index 228ba780..f345ed75 100644 --- a/packages/server/controllers/users.js +++ b/packages/server/controllers/users.js @@ -1,4 +1,4 @@ -const prisma = require("../prisma/prisma"); +const prisma = require("../utils/prisma"); async function getAllUsers() { const query = await prisma.users.findMany(); diff --git a/packages/server/package.json b/packages/server/package.json index a1f86f2f..2cff39e7 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -23,14 +23,18 @@ "jsonwebtoken": "^8.5.1", "passport": "^0.6.0", "passport-google-oauth20": "^2.0.0", - "pg": "^8.8.0", "prisma": "^5.0.0", "uniqid": "^5.4.0", "uuid": "^9.0.0" }, "devDependencies": { + "@typescript-eslint/eslint-plugin": "^6.2.1", + "@typescript-eslint/parser": "^6.2.1", "axios": "^1.3.4", + "eslint": "^8.46.0", + "eslint-config-prettier": "^8.10.0", "jest": "^29.5.0", - "nodemon": "^2.0.19" + "nodemon": "^2.0.19", + "typescript": "^5.1.6" } } diff --git a/packages/server/prisma/prisma.js b/packages/server/prisma/prisma.js deleted file mode 100644 index 5bd4993b..00000000 --- a/packages/server/prisma/prisma.js +++ /dev/null @@ -1,3 +0,0 @@ -const { PrismaClient } = require("@prisma/client"); -const prisma = new PrismaClient(); -module.exports = prisma; diff --git a/packages/server/prisma/schema.prisma b/packages/server/prisma/schema.prisma index 68d4f06b..a997c09d 100644 --- a/packages/server/prisma/schema.prisma +++ b/packages/server/prisma/schema.prisma @@ -7,7 +7,7 @@ datasource db { url = env("DB_URL") } -model users { +model User { userId String @id @map("user_id") @db.Uuid joinedDate DateTime? @map("joined_date") @db.Timestamp(6) lastLogin DateTime? @map("last_login") @db.Timestamp(6) @@ -16,21 +16,23 @@ model users { email String @unique @db.VarChar(255) avatar String? @db.VarChar(255) password String? @db.VarChar(255) - eventAttendees eventAttendees[] - guildMembers guildMembers[] + eventAttendees EventAttendees[] + guildMembers GuildMembers[] + + @@map("users") } -model eventAttendees { - userId String @db.Uuid @map("user_id") - eventId String @db.Uuid @map("event_id") - events events @relation(fields: [eventId], references: [eventId], onDelete: Cascade, onUpdate: NoAction, map: "fk_event") - attendees users @relation(fields: [userId], references: [userId], onDelete: Cascade, onUpdate: NoAction, map: "fk_user") +model EventAttendees { + userId String @db.Uuid @map("user_id") + eventId String @db.Uuid @map("event_id") + event Event? @relation(fields: [eventId], references: [eventId], onDelete: Cascade, onUpdate: NoAction, map: "fk_event") + attendee User? @relation(fields: [userId], references: [userId], onDelete: Cascade, onUpdate: NoAction, map: "fk_user") @@id([userId, eventId]) @@map("event_attendees") } -model events { +model Event { eventId String @id(map: "event_pkey") @db.Uuid @map("event_id") guildId String @db.Uuid @map("guild_id") title String @db.VarChar(255) @@ -39,21 +41,23 @@ model events { endDate DateTime? @db.Timestamp(6) @map("end_date") location String? @db.VarChar(255) thumbnail String? @db.VarChar(255) - eventAttendees eventAttendees[] - guilds guilds @relation(fields: [guildId], references: [guildId], onDelete: Cascade, onUpdate: Cascade, map: "event_guild_id_fkey") + eventAttendees EventAttendees[] + guilds Guild @relation(fields: [guildId], references: [guildId], onDelete: Cascade, onUpdate: Cascade, map: "event_guild_id_fkey") + + @@map("events") } -model guildMembers { +model GuildMembers { userId String @db.Uuid @map("user_id") guildId String @db.Uuid @map("guild_id") - guilds guilds @relation(fields: [guildId], references: [guildId], onDelete: Cascade, onUpdate: NoAction, map: "fk_guild") - members users @relation(fields: [userId], references: [userId], onDelete: Cascade, onUpdate: NoAction, map: "fk_user") + guilds Guild @relation(fields: [guildId], references: [guildId], onDelete: Cascade, onUpdate: NoAction, map: "fk_guild") + members User @relation(fields: [userId], references: [userId], onDelete: Cascade, onUpdate: NoAction, map: "fk_user") @@id([guildId, userId], map: "users_guilds_pkey") @@map("guild_members") } -model guilds { +model Guild { guildId String @id(map: "guild_pkey") @db.Uuid @map("guild_id") name String @db.VarChar(100) handler String @db.VarChar(50) @@ -66,6 +70,6 @@ model guilds { icon String? @db.VarChar(255) media String[] isInviteOnly Boolean @map("invite_only") - events events[] - members guildMembers[] + events Event[] + members GuildMembers[] } diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json new file mode 100644 index 00000000..7ad4681b --- /dev/null +++ b/packages/server/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "outDir": "./build", + "allowJs": true, + "module": "Node16", + "moduleResolution": "Node16", + "target": "ES6" + }, + "include": [ + "./controllers/**/*", + "./routes/**/*", + "./utils/**/*", + "./index.js" + ] +} diff --git a/packages/server/utils/postgres.js b/packages/server/utils/postgres.js deleted file mode 100644 index 4f2bc37f..00000000 --- a/packages/server/utils/postgres.js +++ /dev/null @@ -1,17 +0,0 @@ -const { Client: PostgresClient } = require("pg"); - -const postgres = new PostgresClient({ - host: process.env.DB_HOST, - user: process.env.DB_USER, - database: process.env.DB_NAME, - password: process.env.DB_PASSWORD, - port: process.env.DB_PORT, -}); - -postgres.connect(); - -function closePostgres() { - postgres.end(); -} - -module.exports = { postgres, closePostgres }; diff --git a/packages/server/utils/prisma.ts b/packages/server/utils/prisma.ts new file mode 100644 index 00000000..c957cebc --- /dev/null +++ b/packages/server/utils/prisma.ts @@ -0,0 +1,3 @@ +import { PrismaClient } from "@prisma/client"; +const prisma = new PrismaClient(); +export default prisma; diff --git a/packages/server/utils/prismaTS.ts b/packages/server/utils/prismaTS.ts deleted file mode 100644 index 15c3b933..00000000 --- a/packages/server/utils/prismaTS.ts +++ /dev/null @@ -1,26 +0,0 @@ -// prisma client initialization once we migrate backend to TypeScript -// delete "prisma.js" and rename this file to "prisma.ts" once migrated to -// TypeScript - -import { PrismaClient } from "@prisma/client"; - -declare global { - namespace NodeJS { - interface Global { - prisma: PrismaClient; - } - } -} - -let prisma: PrismaClient; - -if (!global.prisma) { - global.prisma = new PrismaClient({ - log: ["info"], - }); -} -prisma = global.prisma; - -export default prisma; -// Prevents hitting the limit on number of Prisma Clients instantiated while testing the code locally -// Achieves goal by setting a single global instance of Prisma Client to be used when local testing diff --git a/yarn.lock b/yarn.lock index af5a6108..71209dfa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + "@ampproject/remapping@^2.1.0": version "2.2.0" resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz" @@ -1153,7 +1158,7 @@ dependencies: "@types/hammerjs" "^2.0.36" -"@eslint-community/eslint-utils@^4.2.0": +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== @@ -1165,6 +1170,11 @@ resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz" integrity sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ== +"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": + version "4.6.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8" + integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw== + "@eslint/eslintrc@^2.0.2": version "2.0.2" resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz" @@ -1180,11 +1190,31 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@eslint/eslintrc@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.1.tgz#18d635e24ad35f7276e8a49d135c7d3ca6a46f93" + integrity sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + "@eslint/js@8.39.0": version "8.39.0" resolved "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz" integrity sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng== +"@eslint/js@^8.46.0": + version "8.46.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.46.0.tgz#3f7802972e8b6fe3f88ed1aabc74ec596c456db6" + integrity sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA== + "@expo/bunyan@4.0.0", "@expo/bunyan@^4.0.0": version "4.0.0" resolved "https://registry.npmjs.org/@expo/bunyan/-/bunyan-4.0.0.tgz" @@ -1521,6 +1551,15 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@humanwhocodes/config-array@^0.11.10": + version "0.11.10" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" + integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.5" + "@humanwhocodes/config-array@^0.11.8": version "0.11.8" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz" @@ -2478,6 +2517,11 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/json-schema@^7.0.12": + version "7.0.12" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" + integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== + "@types/node@*": version "18.11.18" resolved "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz" @@ -2493,6 +2537,11 @@ resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== +"@types/semver@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" + integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== + "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" @@ -2524,6 +2573,92 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/eslint-plugin@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.2.1.tgz#41b79923fee46a745a3a50cba1c33c622aa3c79a" + integrity sha512-iZVM/ALid9kO0+I81pnp1xmYiFyqibAHzrqX4q5YvvVEyJqY+e6rfTXSCsc2jUxGNqJqTfFSSij/NFkZBiBzLw== + dependencies: + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.2.1" + "@typescript-eslint/type-utils" "6.2.1" + "@typescript-eslint/utils" "6.2.1" + "@typescript-eslint/visitor-keys" "6.2.1" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + natural-compare-lite "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/parser@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.2.1.tgz#e18a31eea1cca8841a565f1701960c8123ed07f9" + integrity sha512-Ld+uL1kYFU8e6btqBFpsHkwQ35rw30IWpdQxgOqOh4NfxSDH6uCkah1ks8R/RgQqI5hHPXMaLy9fbFseIe+dIg== + dependencies: + "@typescript-eslint/scope-manager" "6.2.1" + "@typescript-eslint/types" "6.2.1" + "@typescript-eslint/typescript-estree" "6.2.1" + "@typescript-eslint/visitor-keys" "6.2.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.2.1.tgz#b6f43a867b84e5671fe531f2b762e0b68f7cf0c4" + integrity sha512-UCqBF9WFqv64xNsIEPfBtenbfodPXsJ3nPAr55mGPkQIkiQvgoWNo+astj9ZUfJfVKiYgAZDMnM6dIpsxUMp3Q== + dependencies: + "@typescript-eslint/types" "6.2.1" + "@typescript-eslint/visitor-keys" "6.2.1" + +"@typescript-eslint/type-utils@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.2.1.tgz#8eb8a2cccdf39cd7cf93e02bd2c3782dc90b0525" + integrity sha512-fTfCgomBMIgu2Dh2Or3gMYgoNAnQm3RLtRp+jP7A8fY+LJ2+9PNpi5p6QB5C4RSP+U3cjI0vDlI3mspAkpPVbQ== + dependencies: + "@typescript-eslint/typescript-estree" "6.2.1" + "@typescript-eslint/utils" "6.2.1" + debug "^4.3.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/types@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.2.1.tgz#7fcdeceb503aab601274bf5e210207050d88c8ab" + integrity sha512-528bGcoelrpw+sETlyM91k51Arl2ajbNT9L4JwoXE2dvRe1yd8Q64E4OL7vHYw31mlnVsf+BeeLyAZUEQtqahQ== + +"@typescript-eslint/typescript-estree@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.2.1.tgz#2af6e90c1e91cb725a5fe1682841a3f74549389e" + integrity sha512-G+UJeQx9AKBHRQBpmvr8T/3K5bJa485eu+4tQBxFq0KoT22+jJyzo1B50JDT9QdC1DEmWQfdKsa8ybiNWYsi0Q== + dependencies: + "@typescript-eslint/types" "6.2.1" + "@typescript-eslint/visitor-keys" "6.2.1" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.2.1.tgz#2aa4279ec13053d05615bcbde2398e1e8f08c334" + integrity sha512-eBIXQeupYmxVB6S7x+B9SdBeB6qIdXKjgQBge2J+Ouv8h9Cxm5dHf/gfAZA6dkMaag+03HdbVInuXMmqFB/lKQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.2.1" + "@typescript-eslint/types" "6.2.1" + "@typescript-eslint/typescript-estree" "6.2.1" + semver "^7.5.4" + +"@typescript-eslint/visitor-keys@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.2.1.tgz#442e7c09fe94b715a54ebe30e967987c3c41fbf4" + integrity sha512-iTN6w3k2JEZ7cyVdZJTVJx2Lv7t6zFA8DCrJEHD2mwfc16AEvvBWVhbFh34XyG2NORCd0viIgQY1+u7kPI0WpA== + dependencies: + "@typescript-eslint/types" "6.2.1" + eslint-visitor-keys "^3.4.1" + "@urql/core@2.3.6": version "2.3.6" resolved "https://registry.npmjs.org/@urql/core/-/core-2.3.6.tgz" @@ -2587,6 +2722,11 @@ acorn@^8.8.0: resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== +acorn@^8.9.0: + version "8.10.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" + integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== + agent-base@6: version "6.0.2" resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" @@ -3238,11 +3378,6 @@ buffer-from@^1.0.0: resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer-writer@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz" - integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== - buffer@^5.5.0: version "5.7.1" resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" @@ -4151,6 +4286,11 @@ eslint-config-prettier@8.8.0: resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz" integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA== +eslint-config-prettier@^8.10.0: + version "8.10.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz#3a06a662130807e2502fc3ff8b4143d8a0658e11" + integrity sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg== + eslint-plugin-react-native-globals@^0.1.1: version "0.1.2" resolved "https://registry.npmjs.org/eslint-plugin-react-native-globals/-/eslint-plugin-react-native-globals-0.1.2.tgz" @@ -4193,11 +4333,24 @@ eslint-scope@^7.2.0: esrecurse "^4.3.0" estraverse "^5.2.0" +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.0: version "3.4.0" resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz" integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz#8c2095440eca8c933bedcadf16fefa44dbe9ba5f" + integrity sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw== + eslint@8.39.0: version "8.39.0" resolved "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz" @@ -4244,6 +4397,49 @@ eslint@8.39.0: strip-json-comments "^3.1.0" text-table "^0.2.0" +eslint@^8.46.0: + version "8.46.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.46.0.tgz#a06a0ff6974e53e643acc42d1dcf2e7f797b3552" + integrity sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.1" + "@eslint/js" "^8.46.0" + "@humanwhocodes/config-array" "^0.11.10" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.2" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + espree@^9.5.1: version "9.5.1" resolved "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz" @@ -4253,6 +4449,15 @@ espree@^9.5.1: acorn-jsx "^5.3.2" eslint-visitor-keys "^3.4.0" +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + esprima@^4.0.0, esprima@~4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" @@ -5098,7 +5303,7 @@ globalthis@^1.0.3: dependencies: define-properties "^1.1.3" -globby@^11.0.1: +globby@^11.0.1, globby@^11.1.0: version "11.1.0" resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -5149,6 +5354,11 @@ grapheme-splitter@^1.0.4: resolved "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + graphql-tag@^2.10.1: version "2.12.6" resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz" @@ -5331,7 +5541,7 @@ ignore-by-default@^1.0.1: resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz" integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== -ignore@^5.2.0: +ignore@^5.2.0, ignore@^5.2.4: version "5.2.4" resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== @@ -7572,6 +7782,11 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" @@ -7897,6 +8112,18 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + ora@3.4.0: version "3.4.0" resolved "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz" @@ -7994,11 +8221,6 @@ p-try@^2.0.0: resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -packet-reader@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz" - integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== - parent-module@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" @@ -8131,57 +8353,6 @@ pause@0.0.1: resolved "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz" integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg== -pg-connection-string@^2.5.0: - version "2.5.0" - resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz" - integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== - -pg-int8@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz" - integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== - -pg-pool@^3.5.2: - version "3.5.2" - resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz" - integrity sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w== - -pg-protocol@^1.6.0: - version "1.6.0" - resolved "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz" - integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q== - -pg-types@^2.1.0: - version "2.2.0" - resolved "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz" - integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== - dependencies: - pg-int8 "1.0.1" - postgres-array "~2.0.0" - postgres-bytea "~1.0.0" - postgres-date "~1.0.4" - postgres-interval "^1.1.0" - -pg@^8.8.0: - version "8.9.0" - resolved "https://registry.npmjs.org/pg/-/pg-8.9.0.tgz" - integrity sha512-ZJM+qkEbtOHRuXjmvBtOgNOXOtLSbxiMiUVMgE4rV6Zwocy03RicCVvDXgx8l4Biwo8/qORUnEqn2fdQzV7KCg== - dependencies: - buffer-writer "2.0.0" - packet-reader "1.0.0" - pg-connection-string "^2.5.0" - pg-pool "^3.5.2" - pg-protocol "^1.6.0" - pg-types "^2.1.0" - pgpass "1.x" - -pgpass@1.x: - version "1.0.5" - resolved "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz" - integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== - dependencies: - split2 "^4.1.0" - picocolors@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" @@ -8241,28 +8412,6 @@ posix-character-classes@^0.1.0: resolved "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz" integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== -postgres-array@~2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz" - integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== - -postgres-bytea@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz" - integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== - -postgres-date@~1.0.4: - version "1.0.7" - resolved "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz" - integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== - -postgres-interval@^1.1.0: - version "1.2.0" - resolved "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz" - integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== - dependencies: - xtend "^4.0.0" - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" @@ -8981,6 +9130,13 @@ semver@^7.3.5: dependencies: lru-cache "^6.0.0" +semver@^7.5.4: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + semver@~7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz" @@ -9245,11 +9401,6 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" -split2@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz" - integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ== - split@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/split/-/split-1.0.1.tgz" @@ -9685,6 +9836,11 @@ traverse@~0.6.6: resolved "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz" integrity sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg== +ts-api-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.1.tgz#8144e811d44c749cd65b2da305a032510774452d" + integrity sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A== + ts-interface-checker@^0.1.9: version "0.1.13" resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz" @@ -9754,6 +9910,11 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" +typescript@^5.1.6: + version "5.1.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" + integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== + ua-parser-js@^0.7.30: version "0.7.33" resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz" @@ -10189,7 +10350,7 @@ xmlbuilder@~11.0.0: resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== -xtend@^4.0.0, xtend@~4.0.1: +xtend@~4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== From d0ecca0c1dec8155fdbee8cc7f10dc206776b8f1 Mon Sep 17 00:00:00 2001 From: Luke Dinh Date: Thu, 17 Aug 2023 16:55:57 -0700 Subject: [PATCH 02/17] chore: update yarn.lock --- yarn.lock | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 7d302fd6..a32efbbd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3406,6 +3406,11 @@ buffer-from@^1.0.0: resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer-writer@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" + integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== + buffer@^5.5.0: version "5.7.1" resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" @@ -8289,6 +8294,11 @@ p-try@^2.0.0: resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +packet-reader@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" + integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" @@ -8421,6 +8431,64 @@ pause@0.0.1: resolved "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz" integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg== +pg-cloudflare@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" + integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== + +pg-connection-string@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.2.tgz#713d82053de4e2bd166fab70cd4f26ad36aab475" + integrity sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA== + +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-pool@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.1.tgz#5a902eda79a8d7e3c928b77abf776b3cb7d351f7" + integrity sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og== + +pg-protocol@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833" + integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q== + +pg-types@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +pg@^8.8.0: + version "8.11.3" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.11.3.tgz#d7db6e3fe268fcedd65b8e4599cda0b8b4bf76cb" + integrity sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g== + dependencies: + buffer-writer "2.0.0" + packet-reader "1.0.0" + pg-connection-string "^2.6.2" + pg-pool "^3.6.1" + pg-protocol "^1.6.0" + pg-types "^2.1.0" + pgpass "1.x" + optionalDependencies: + pg-cloudflare "^1.1.1" + +pgpass@1.x: + version "1.0.5" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" + integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== + dependencies: + split2 "^4.1.0" + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" @@ -8480,6 +8548,28 @@ posix-character-classes@^0.1.0: resolved "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz" integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" @@ -9493,6 +9583,11 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" +split2@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + split@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/split/-/split-1.0.1.tgz" @@ -10447,7 +10542,7 @@ xmlbuilder@~11.0.0: resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== -xtend@~4.0.1: +xtend@^4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== From 94802d74f5f57563697da009e19d660db63706a6 Mon Sep 17 00:00:00 2001 From: Luke Dinh Date: Thu, 17 Aug 2023 20:52:24 -0700 Subject: [PATCH 03/17] feat: created Request and Response types - adjusted some typescript and eslint config properties - adjust Prisma data model names --- .gitignore | 3 +- packages/server/{.eslintrc.json => .eslintrc} | 2 +- packages/server/package.json | 5 +- packages/server/prisma/schema.prisma | 12 +-- packages/server/tsconfig.json | 8 +- packages/server/types/index.ts | 35 +++++++++ yarn.lock | 74 ++++++++++++++++++- 7 files changed, 127 insertions(+), 12 deletions(-) rename packages/server/{.eslintrc.json => .eslintrc} (88%) create mode 100644 packages/server/types/index.ts diff --git a/.gitignore b/.gitignore index 629cc905..b9ec7ed6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .vscode node_modules/ yarn-error.log -**.env \ No newline at end of file +**.env +**/build/ \ No newline at end of file diff --git a/packages/server/.eslintrc.json b/packages/server/.eslintrc similarity index 88% rename from packages/server/.eslintrc.json rename to packages/server/.eslintrc index 76ecd1e6..8795c854 100644 --- a/packages/server/.eslintrc.json +++ b/packages/server/.eslintrc @@ -10,7 +10,7 @@ "node": true, "amd": true }, - "rules": {}, + "ignorePatterns": ["/__tests__/**/*"], "extends": [ "prettier", "eslint:recommended", diff --git a/packages/server/package.json b/packages/server/package.json index bea14041..fccf68e9 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -30,6 +30,7 @@ "uuid": "^9.0.0" }, "devDependencies": { + "@types/express": "^4.17.17", "@typescript-eslint/eslint-plugin": "^6.2.1", "@typescript-eslint/parser": "^6.2.1", "axios": "^1.3.4", @@ -37,7 +38,7 @@ "eslint-config-prettier": "^8.10.0", "jest": "^29.5.0", "nodemon": "^2.0.19", - "typescript": "^5.1.6", - "prisma": "^5.1.1" + "prisma": "^5.1.1", + "typescript": "^5.1.6" } } diff --git a/packages/server/prisma/schema.prisma b/packages/server/prisma/schema.prisma index 03323ad4..561f3e0c 100644 --- a/packages/server/prisma/schema.prisma +++ b/packages/server/prisma/schema.prisma @@ -16,13 +16,13 @@ model User { email String @unique @db.VarChar(255) avatar String? @db.VarChar(255) password String? @db.VarChar(255) - eventAttendees EventAttendees[] - guildMembers GuildMembers[] + eventAttendees EventAttendee[] + guildMembers GuildMember[] @@map("users") } -model EventAttendees { +model EventAttendee { userId String @db.Uuid @map("user_id") eventId String @db.Uuid @map("event_id") event Event? @relation(fields: [eventId], references: [eventId], onDelete: Cascade, onUpdate: NoAction, map: "fk_event") @@ -41,13 +41,13 @@ model Event { endDate DateTime? @db.Timestamp(6) @map("end_date") location String? @db.VarChar(255) thumbnail String? @db.VarChar(255) - eventAttendees EventAttendees[] + eventAttendees EventAttendee[] guilds Guild @relation(fields: [guildId], references: [guildId], onDelete: Cascade, onUpdate: Cascade, map: "event_guild_id_fkey") @@map("events") } -model GuildMembers { +model GuildMember { userId String @db.Uuid @map("user_id") guildId String @db.Uuid @map("guild_id") guilds Guild @relation(fields: [guildId], references: [guildId], onDelete: Cascade, onUpdate: NoAction, map: "fk_guild") @@ -71,5 +71,5 @@ model Guild { media String[] isInviteOnly Boolean @map("invite_only") events Event[] - members GuildMembers[] + members GuildMember[] } diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index 7ad4681b..62550cd6 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -1,11 +1,17 @@ { "compilerOptions": { + "lib": ["ES2023"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, "outDir": "./build", "allowJs": true, "module": "Node16", "moduleResolution": "Node16", - "target": "ES6" + "target": "ES2022" }, + "exclude": ["./__tests__/**/*"], "include": [ "./controllers/**/*", "./routes/**/*", diff --git a/packages/server/types/index.ts b/packages/server/types/index.ts new file mode 100644 index 00000000..ac5f7ad4 --- /dev/null +++ b/packages/server/types/index.ts @@ -0,0 +1,35 @@ +import * as Express from "express"; + +/** + * @template T, V + * @param T - (optional) structure of request route parameters + * @param V - (optional) structure of request body + * + * void = optional generic parameters + */ +export type Request = Express.Request; + +/** + * @template T + * @param T - structure of response body + */ +export interface Response> extends Express.Response { + json: ( + body: SuccessResponseBody | FailResponseBody | ErrorResponseBody + ) => this; +} + +type SuccessResponseBody> = { + status: "success"; + data: T; +}; + +type FailResponseBody> = { + status: "fail"; + data: Record; +}; + +type ErrorResponseBody = { + status: "error"; + message: string; +}; diff --git a/yarn.lock b/yarn.lock index a32efbbd..c8401b90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2509,6 +2509,41 @@ dependencies: "@babel/types" "^7.3.0" +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@^4.17.33": + version "4.17.35" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz#c95dd4424f0d32e525d23812aa8ab8e4d3906c4f" + integrity sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^4.17.17": + version "4.17.17" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" + integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/graceful-fs@^4.1.3": version "4.1.6" resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz" @@ -2521,6 +2556,11 @@ resolved "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz" integrity sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA== +"@types/http-errors@*": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.1.tgz#20172f9578b225f6c7da63446f56d4ce108d5a65" + integrity sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ== + "@types/invariant@^2.2.35": version "2.2.35" resolved "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.35.tgz" @@ -2550,6 +2590,16 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== +"@types/mime@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + "@types/node@*": version "18.11.18" resolved "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz" @@ -2560,16 +2610,38 @@ resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz" integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg== -"@types/qs@^6.5.3": +"@types/qs@*", "@types/qs@^6.5.3": version "6.9.7" resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + "@types/semver@^7.5.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== +"@types/send@*": + version "0.17.1" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.1.tgz#ed4932b8a2a805f1fe362a70f4e62d0ac994e301" + integrity sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.2.tgz#3e5419ecd1e40e7405d34093f10befb43f63381a" + integrity sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw== + dependencies: + "@types/http-errors" "*" + "@types/mime" "*" + "@types/node" "*" + "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" From 7ec9043bd3435c40c9e75ebf8fd91f92ffbe4dce Mon Sep 17 00:00:00 2001 From: Luke Dinh Date: Tue, 22 Aug 2023 16:31:24 -0700 Subject: [PATCH 04/17] chore: changed backend eslint rules - added yarn build command for building typescript into javascript --- packages/server/.eslintrc | 3 +++ packages/server/package.json | 1 + 2 files changed, 4 insertions(+) diff --git a/packages/server/.eslintrc b/packages/server/.eslintrc index 8795c854..ae1473f6 100644 --- a/packages/server/.eslintrc +++ b/packages/server/.eslintrc @@ -10,6 +10,9 @@ "node": true, "amd": true }, + "rules": { + "@typescript-eslint/no-explicit-any": "off" + }, "ignorePatterns": ["/__tests__/**/*"], "extends": [ "prettier", diff --git a/packages/server/package.json b/packages/server/package.json index fccf68e9..34e6f357 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -7,6 +7,7 @@ "private": true, "scripts": { "start": "nodemon index.js", + "build": "yarn tsc .", "forward": "ttab node scripts/ngrok.js", "dev": "ttab yarn start", "test": "jest" From 16bde57868423e255da73ace1287896dd2434e97 Mon Sep 17 00:00:00 2001 From: Luke Dinh Date: Sun, 8 Oct 2023 22:26:51 -0700 Subject: [PATCH 05/17] fix: typed express response type --- packages/server/types/index.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/server/types/index.ts b/packages/server/types/index.ts index ac5f7ad4..1d94d105 100644 --- a/packages/server/types/index.ts +++ b/packages/server/types/index.ts @@ -1,4 +1,5 @@ import * as Express from "express"; +import { Send } from "express-serve-static-core"; /** * @template T, V @@ -7,16 +8,17 @@ import * as Express from "express"; * * void = optional generic parameters */ -export type Request = Express.Request; +export type APIRequest = Express.Request; /** * @template T * @param T - structure of response body */ -export interface Response> extends Express.Response { - json: ( - body: SuccessResponseBody | FailResponseBody | ErrorResponseBody - ) => this; +export interface APIResponse> extends Express.Response { + json: Send< + SuccessResponseBody | FailResponseBody | ErrorResponseBody, + this + >; } type SuccessResponseBody> = { From 355e9ba37c9b31a8486c5326ae7de26b782123af Mon Sep 17 00:00:00 2001 From: mikephu Date: Thu, 12 Oct 2023 21:39:14 -0700 Subject: [PATCH 06/17] migrated events/guilds controllers to typescript --- .../controllers/{events.js => events.ts} | 23 ++++++++++--------- packages/server/controllers/guilds.js | 20 ---------------- packages/server/controllers/guilds.ts | 21 +++++++++++++++++ 3 files changed, 33 insertions(+), 31 deletions(-) rename packages/server/controllers/{events.js => events.ts} (62%) delete mode 100644 packages/server/controllers/guilds.js create mode 100644 packages/server/controllers/guilds.ts diff --git a/packages/server/controllers/events.js b/packages/server/controllers/events.ts similarity index 62% rename from packages/server/controllers/events.js rename to packages/server/controllers/events.ts index d11f9ee1..42572702 100644 --- a/packages/server/controllers/events.js +++ b/packages/server/controllers/events.ts @@ -1,16 +1,17 @@ // const postgres = require("../utils/postgres"); -const prisma = require("../utils/prisma"); +import prisma from "../utils/prisma"; +import { Event } from ".prisma/client/index"; -async function getAllEvents() { +async function getAllEvents(): Promise { return prisma.event.findMany(); } -async function getEvents(limit, action, eventId) { - let events; +async function getEvents(limit: number, action: string, eventId: string): Promise { + let events: Event[]; switch (action) { case "prev": - events = await prisma.events.findMany({ + events = await prisma.event.findMany({ take: -1 * limit, skip: 1, // skip cursor (TODO: check if skip 1 is needed for previous page queries) cursor: { @@ -22,7 +23,7 @@ async function getEvents(limit, action, eventId) { }); break; case "next": - events = await prisma.events.findMany({ + events = await prisma.event.findMany({ take: limit, skip: 1, // skip cursor cursor: { @@ -35,7 +36,7 @@ async function getEvents(limit, action, eventId) { break; // first request made for first page, no action in cursor present default: - events = await prisma.events.findMany({ + events = await prisma.event.findMany({ take: limit, orderBy: { eventId: "asc", @@ -47,14 +48,14 @@ async function getEvents(limit, action, eventId) { return events; } -async function getPages(limit) { - const totalEvents = await prisma.events.count(); +async function getPages(limit: number): Promise { + const totalEvents = await prisma.event.count(); const totalPages = Math.ceil(totalEvents / limit); return totalPages; } -async function getEvent(eventId) { - const event = await prisma.events.findUnique({ +async function getEvent(eventId: string): Promise { + const event = await prisma.event.findUnique({ where: { eventId: eventId, }, diff --git a/packages/server/controllers/guilds.js b/packages/server/controllers/guilds.js deleted file mode 100644 index 589981f0..00000000 --- a/packages/server/controllers/guilds.js +++ /dev/null @@ -1,20 +0,0 @@ -const prisma = require("../prisma/prisma"); - -async function getAllGuilds() { - const query = await prisma.guilds.findMany(); - return query; -} - -async function getGuild(guildId) { - const query = await prisma.guilds.findFirst({ - where: { - guildId: guildId, - }, - }); - return query; -} - -module.exports = { - getGuild, - getAllGuilds, -}; diff --git a/packages/server/controllers/guilds.ts b/packages/server/controllers/guilds.ts new file mode 100644 index 00000000..a3559d97 --- /dev/null +++ b/packages/server/controllers/guilds.ts @@ -0,0 +1,21 @@ +import prisma from "../utils/prisma"; +import { Guild } from ".prisma/client/index"; + +async function getAllGuilds() { + const query = await prisma.guild.findMany(); + return query; +} + +async function getGuild(guildId: string): Promise { + const query = await prisma.guild.findFirst({ + where: { + guildId: guildId, + }, + }); + return query; +} + +module.exports = { + getGuild, + getAllGuilds, +}; From ed8da1b155ede28dedb8822860b2069df47e2b47 Mon Sep 17 00:00:00 2001 From: mikephu Date: Sun, 15 Oct 2023 18:00:52 -0700 Subject: [PATCH 07/17] fixed imports and function typing --- packages/server/controllers/events.ts | 2 +- packages/server/controllers/guilds.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/server/controllers/events.ts b/packages/server/controllers/events.ts index 42572702..8fdd88b2 100644 --- a/packages/server/controllers/events.ts +++ b/packages/server/controllers/events.ts @@ -1,6 +1,6 @@ // const postgres = require("../utils/postgres"); import prisma from "../utils/prisma"; -import { Event } from ".prisma/client/index"; +import { Event } from "@prisma/client"; async function getAllEvents(): Promise { return prisma.event.findMany(); diff --git a/packages/server/controllers/guilds.ts b/packages/server/controllers/guilds.ts index a3559d97..6eedb247 100644 --- a/packages/server/controllers/guilds.ts +++ b/packages/server/controllers/guilds.ts @@ -1,7 +1,7 @@ import prisma from "../utils/prisma"; -import { Guild } from ".prisma/client/index"; +import { Guild } from "@prisma/client"; -async function getAllGuilds() { +async function getAllGuilds(): Promise { const query = await prisma.guild.findMany(); return query; } From 81ef3a75e4f7a60301dd0b0a1186cbd93e38e176 Mon Sep 17 00:00:00 2001 From: Luke Dinh Date: Sun, 15 Oct 2023 19:59:02 -0700 Subject: [PATCH 08/17] feat: migrated users controller --- packages/server/controllers/users.js | 30 --------------------------- packages/server/controllers/users.ts | 31 ++++++++++++++++++++++++++++ packages/server/package.json | 2 ++ yarn.lock | 12 +++++++++++ 4 files changed, 45 insertions(+), 30 deletions(-) delete mode 100644 packages/server/controllers/users.js create mode 100644 packages/server/controllers/users.ts diff --git a/packages/server/controllers/users.js b/packages/server/controllers/users.js deleted file mode 100644 index 692bd141..00000000 --- a/packages/server/controllers/users.js +++ /dev/null @@ -1,30 +0,0 @@ -const prisma = require("../utils/prisma"); - -async function getAllUsers() { - const query = await prisma.users.findMany(); - return query; -} - -async function getUser(userId) { - const query = await prisma.users.findUnique({ - where: { - userId: userId, - }, - }); - return query; -} - -async function getUserByEmail(email) { - const query = await prisma.users.findUnique({ - where: { - email: email, - }, - }); - return query; -} - -module.exports = { - getUser, - getAllUsers, - getUserByEmail, -}; diff --git a/packages/server/controllers/users.ts b/packages/server/controllers/users.ts new file mode 100644 index 00000000..7a6477d9 --- /dev/null +++ b/packages/server/controllers/users.ts @@ -0,0 +1,31 @@ +import prisma from "../utils/prisma"; +import { User } from "@prisma/client"; + +async function getAllUsers(): Promise { + const query = await prisma.user.findMany(); + return query; +} + +async function getUser(userId: string): Promise { + const query = await prisma.user.findUnique({ + where: { + userId: userId + } + }); + return query; +} + +async function getUserByEmail(email: string): Promise { + const query = await prisma.user.findUnique({ + where: { + email: email + } + }); + return query; +} + +module.exports = { + getUser, + getAllUsers, + getUserByEmail +}; diff --git a/packages/server/package.json b/packages/server/package.json index 34e6f357..16b9e351 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -31,7 +31,9 @@ "uuid": "^9.0.0" }, "devDependencies": { + "@types/bcrypt": "^5.0.0", "@types/express": "^4.17.17", + "@types/uuid": "^9.0.5", "@typescript-eslint/eslint-plugin": "^6.2.1", "@typescript-eslint/parser": "^6.2.1", "axios": "^1.3.4", diff --git a/yarn.lock b/yarn.lock index c8401b90..b292453b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2509,6 +2509,13 @@ dependencies: "@babel/types" "^7.3.0" +"@types/bcrypt@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/bcrypt/-/bcrypt-5.0.0.tgz#a835afa2882d165aff5690893db314eaa98b9f20" + integrity sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw== + dependencies: + "@types/node" "*" + "@types/body-parser@*": version "1.19.2" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" @@ -2647,6 +2654,11 @@ resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/uuid@^9.0.5": + version "9.0.5" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.5.tgz#25a71eb73eba95ac0e559ff3dd018fc08294acf6" + integrity sha512-xfHdwa1FMJ082prjSJpoEI57GZITiQz10r3vEJCHa2khEFQjKy91aWKz6+zybzssCvXUwE1LQWgWVwZ4nYUvHQ== + "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" From 612fb2e38832ef1dbfbf209e4b3e04c992d9fc03 Mon Sep 17 00:00:00 2001 From: Luke Dinh Date: Mon, 23 Oct 2023 21:27:29 -0700 Subject: [PATCH 09/17] fix: fail response body type --- packages/server/types/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/server/types/index.ts b/packages/server/types/index.ts index 1d94d105..96556e42 100644 --- a/packages/server/types/index.ts +++ b/packages/server/types/index.ts @@ -16,7 +16,7 @@ export type APIRequest = Express.Request; */ export interface APIResponse> extends Express.Response { json: Send< - SuccessResponseBody | FailResponseBody | ErrorResponseBody, + SuccessResponseBody | FailResponseBody | ErrorResponseBody, this >; } @@ -26,9 +26,9 @@ type SuccessResponseBody> = { data: T; }; -type FailResponseBody> = { +type FailResponseBody = { status: "fail"; - data: Record; + data: Record; }; type ErrorResponseBody = { From 15c001fb88361e00b344edbe75956e8ca0bf3b31 Mon Sep 17 00:00:00 2001 From: Luke Dinh Date: Mon, 23 Oct 2023 21:51:03 -0700 Subject: [PATCH 10/17] fix: adjusted export --- packages/server/controllers/users.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/controllers/users.ts b/packages/server/controllers/users.ts index 7a6477d9..3a76d7ae 100644 --- a/packages/server/controllers/users.ts +++ b/packages/server/controllers/users.ts @@ -24,7 +24,7 @@ async function getUserByEmail(email: string): Promise { return query; } -module.exports = { +export default { getUser, getAllUsers, getUserByEmail From 93b5dd2b2bcb2b60033158d52988a32dd5345e44 Mon Sep 17 00:00:00 2001 From: mikephu Date: Tue, 24 Oct 2023 18:25:08 -0700 Subject: [PATCH 11/17] fix: adjust exports in events/guild controllers --- packages/server/controllers/events.ts | 2 +- packages/server/controllers/guilds.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/controllers/events.ts b/packages/server/controllers/events.ts index 8fdd88b2..06e747aa 100644 --- a/packages/server/controllers/events.ts +++ b/packages/server/controllers/events.ts @@ -63,7 +63,7 @@ async function getEvent(eventId: string): Promise { return event; } -module.exports = { +export default { getEvent, getEvents, getPages, diff --git a/packages/server/controllers/guilds.ts b/packages/server/controllers/guilds.ts index 6eedb247..d7acd112 100644 --- a/packages/server/controllers/guilds.ts +++ b/packages/server/controllers/guilds.ts @@ -15,7 +15,7 @@ async function getGuild(guildId: string): Promise { return query; } -module.exports = { +export default { getGuild, getAllGuilds, }; From 93022709bbbf8a25fb1bd1a07800e9e1a4fc8d41 Mon Sep 17 00:00:00 2001 From: mikephu Date: Wed, 25 Oct 2023 01:39:25 -0700 Subject: [PATCH 12/17] feat: migrate events/guilds routes --- .../routes/api/{events.js => events.ts} | 92 ++++++++++++++----- packages/server/routes/api/guilds.js | 56 ----------- packages/server/routes/api/guilds.ts | 86 +++++++++++++++++ 3 files changed, 155 insertions(+), 79 deletions(-) rename packages/server/routes/api/{events.js => events.ts} (53%) delete mode 100644 packages/server/routes/api/guilds.js create mode 100644 packages/server/routes/api/guilds.ts diff --git a/packages/server/routes/api/events.js b/packages/server/routes/api/events.ts similarity index 53% rename from packages/server/routes/api/events.js rename to packages/server/routes/api/events.ts index 983df511..b551a842 100644 --- a/packages/server/routes/api/events.js +++ b/packages/server/routes/api/events.ts @@ -1,9 +1,24 @@ -const express = require("express"); +import express from "express"; const router = express.Router(); +const DEFAULT_EVENT_LIMIT: number = 10; -const EventController = require("../../controllers/events"); -const AuthController = require("../../controllers/auth"); -const DEFAULT_EVENT_LIMIT = 10; +import EventController from "../../controllers/events"; +import AuthController from "../../controllers/auth"; +import { APIRequest, APIResponse } from "../../types/index" +import { Event } from "@prisma/client"; + +type APIRequestCursor = { + cursor: string, +} + +type APIResponseCursor = { + events: Event[], + totalPages?: number, + cursor: { + previousPage: string | null, + nextPage: string | null, + } +} /** * cursor is base-64 encoded and formatted as @@ -14,9 +29,11 @@ const DEFAULT_EVENT_LIMIT = 10; router.get( "/pages/:cursor?", AuthController.authenticate, - async (request, response) => { + async (request: APIRequest, response: APIResponse) => { try { - if (request.query.limit && isNaN(request.query.limit)) { + let queryLimit: string = request.query.limit as string + + if (queryLimit && isNaN(parseInt(queryLimit))) { return response.status(400).json({ status: "fail", data: { @@ -25,17 +42,22 @@ router.get( }); } - const eventLimit = parseInt(request.query.limit) || DEFAULT_EVENT_LIMIT; + const eventLimit: number = parseInt(queryLimit) || DEFAULT_EVENT_LIMIT; // base64 decode cursor parameter if not null - const requestCursor = request.params.cursor + const requestCursor: string = request.params.cursor ? Buffer.from(request.params.cursor, "base64").toString() : ""; - let [currentPage, action, eventId] = requestCursor.split("___"); - currentPage = parseInt(currentPage) || 1; - const events = await EventController.getEvents( + + // let [currentPage, action, eventId] = requestCursor.split("___"); + let cursor: string[] = requestCursor.split("___"); + let currentPage: number = parseInt(cursor[0]) || 1; + let action: string = cursor[1] + let eventId: string = cursor[2] + + const events: Event[] = await EventController.getEvents( eventLimit, action, - eventId + eventId, ); if (events.length === 0) { @@ -45,12 +67,12 @@ router.get( // generate cursors for previous and next pages // const firstEventId = events[0].event_id; // const lastEventId = events[events.length - 1].event_id; - const firstEventId = events[0].eventId; - const lastEventId = events[events.length - 1].eventId; - const prevCursor = Buffer.from( + const firstEventId: string = events[0].eventId; + const lastEventId: string = events[events.length - 1].eventId; + const prevCursor: string = Buffer.from( `${currentPage - 1}___prev___${firstEventId}` ).toString("base64"); - const nextCursor = Buffer.from( + const nextCursor: string = Buffer.from( `${currentPage + 1}___next___${lastEventId}` ).toString("base64"); @@ -69,7 +91,7 @@ router.get( } else { // first request made to this route, determine total number of pages // null previous cursor on first page - const totalPages = await EventController.getPages(eventLimit); + const totalPages: number = await EventController.getPages(eventLimit); response.status(200).json({ status: "success", @@ -84,21 +106,37 @@ router.get( }); } } catch (error) { - response.status(500).json({ + if(error instanceof Error) { + response.status(500).json({ status: "error", message: error.message, - }); + }) + } + else { + response.status(500).json({ + status: "error", + message: "An unknown error has occured", + }); + } } } ); +type APIRequestGetEvent = { + eventId: string, +} + +type APIResponseGetEvent = { + event: Event | null, +} + router.get( "/:eventId", AuthController.authenticate, - async (request, response) => { + async (request: APIRequest, response:APIResponse) => { try { const { eventId } = request.params; - const event = await EventController.getEvent(eventId); + const event: Event | null = await EventController.getEvent(eventId); response.status(200).json({ status: "success", data: { @@ -106,10 +144,18 @@ router.get( }, }); } catch (error) { - response.status(500).json({ + if(error instanceof Error) { + response.status(500).json({ status: "error", message: error.message, - }); + }) + } + else { + response.status(500).json({ + status: "error", + message: "An unknown error has occured", + }); + } } } ); diff --git a/packages/server/routes/api/guilds.js b/packages/server/routes/api/guilds.js deleted file mode 100644 index 08cc2f32..00000000 --- a/packages/server/routes/api/guilds.js +++ /dev/null @@ -1,56 +0,0 @@ -const express = require("express"); -const router = express.Router(); - -const GuildController = require("../../controllers/guilds"); -const AuthController = require("../../controllers/auth"); - -router.get("/", AuthController.authenticate, async (request, response) => { - try { - const guilds = await GuildController.getAllGuilds(); - response.status(200).json({ - status: "success", - data: { - guilds, - }, - }); - } catch (error) { - response.status(500).json({ - status: "error", - message: error.message, - }); - } -}); - -router.get( - "/:guildId", - AuthController.authenticate, - async (request, response) => { - try { - const { guildId } = request.params; - - if (guildId === undefined) { - return response.status(400).json({ - status: "fail", - data: { - guildId: "Guild ID not provided", - }, - }); - } - - const guild = await GuildController.getGuild(guildId); - response.status(200).json({ - status: "success", - data: { - guild, - }, - }); - } catch (error) { - response.status(500).json({ - status: "error", - message: error.message, - }); - } - } -); - -module.exports = router; diff --git a/packages/server/routes/api/guilds.ts b/packages/server/routes/api/guilds.ts new file mode 100644 index 00000000..7d78f798 --- /dev/null +++ b/packages/server/routes/api/guilds.ts @@ -0,0 +1,86 @@ +import express from "express"; +const router = express.Router(); + +import GuildController from "../../controllers/guilds"; +import AuthController from "../../controllers/auth"; +import { APIRequest, APIResponse } from "../../types/index" +import { Guild } from "@prisma/client"; + +type APIResponseAllGuilds = { + guilds: Guild[], +} + +router.get("/", AuthController.authenticate, +async (request: APIRequest, response:APIResponse) => { + try { + const guilds: Guild[] = await GuildController.getAllGuilds(); + response.status(200).json({ + status: "success", + data: { + guilds: guilds, + }, + }); + } catch (error) { + if(error instanceof Error) { + response.status(500).json({ + status: "error", + message: error.message, + }) + } + else { + response.status(500).json({ + status: "error", + message: "An unknown error has occured", + }); + } + } +}); + +type APIResponseGetGuild = { + guild: Guild | null, +} + +type APIRequestGetGuild = { + guildId: string, +} + +router.get( + "/:guildId", + AuthController.authenticate, + async (request: APIRequest, response:APIResponse) => { + try { + const { guildId } = request.params; + + if (guildId === undefined) { + return response.status(400).json({ + status: "fail", + data: { + guild: "Guild ID not provided", + }, + }); + } + + const guild = await GuildController.getGuild(guildId); + response.status(200).json({ + status: "success", + data: { + guild: guild, + }, + }); + } catch (error) { + if(error instanceof Error) { + response.status(500).json({ + status: "error", + message: error.message, + }) + } + else { + response.status(500).json({ + status: "error", + message: "An unknown error has occured", + }); + } + } + }); + +module.exports = router; From c6c4677c061616b98267fa0c19f02fbac1d381be Mon Sep 17 00:00:00 2001 From: Luke Dinh Date: Mon, 6 Nov 2023 21:46:45 -0800 Subject: [PATCH 13/17] feat: migrated auth controller and jwt utility functions - added ts-node to run typescript directly without having to transpile to js first --- packages/server/controllers/auth.ts | 171 ++++++++++++++++++++++++++++ packages/server/package.json | 5 +- packages/server/types/auth.ts | 35 ++++++ packages/server/utils/token.ts | 61 ++++++++++ yarn.lock | 118 +++++++++++++++++++ 5 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 packages/server/controllers/auth.ts create mode 100644 packages/server/types/auth.ts create mode 100644 packages/server/utils/token.ts diff --git a/packages/server/controllers/auth.ts b/packages/server/controllers/auth.ts new file mode 100644 index 00000000..2f0e5f3e --- /dev/null +++ b/packages/server/controllers/auth.ts @@ -0,0 +1,171 @@ +import { User } from "@prisma/client"; +import { NewUser, RequestWithUser } from "../types/auth"; + +import { OAuth2Client } from "google-auth-library"; +import { v4 as uuidv4, v5 as uuidv5 } from "uuid"; +import prisma from "../utils/prisma"; +import token from "../utils/token"; +import bcrypt from "bcrypt"; +import { NextFunction, Request, Response } from "express"; + +const client = new OAuth2Client(process.env.WEB_CLIENT_ID); + +const NAMESPACE = "7af17462-8078-4703-adda-be2143a4d93a"; + +async function authenticateWithGoogle(token: string): Promise { + // Verify the token is valid; to ensure it's a valid google auth. + const loginTicket = await client.verifyIdToken({ + idToken: token, + audience: process.env.WEB_CLIENT_ID + }); + const payload = loginTicket.getPayload(); + + if (!payload) { + throw new Error("Could not verify with Google."); + } + + // Check if this user already exist on our database. + const { sub, email, given_name, family_name, picture } = payload; + + // generate Type 5 UUIDs with hashes of user's google account IDs instead of + // completely random Type 4 UUIDs to map our generated UUIDs to google + // accounts + const googleUUID = uuidv5(sub, NAMESPACE); + const user = await prisma.user.findUnique({ + where: { + userId: googleUUID + } + }); + + // if the query returns a row, there's a user with the existing userId. + if (!user) { + const newUser = await prisma.user.create({ + data: { + // ok type assertions, email and profile scope used by default + userId: googleUUID, + email: email as string, + avatar: picture, + firstName: given_name as string, + lastName: family_name as string + } + }); + + return newUser; + } else { + return user; + } +} + +async function register(newUser: NewUser) { + const saltRounds = 10; + const salt = await bcrypt.genSalt(saltRounds); + const passwordHash = await bcrypt.hash(newUser.password, salt); + const userId = uuidv4(); + + // placeholder names until new user puts in their names in onboarding screen + await prisma.user.create({ + data: { + userId, + firstName: "New", + lastName: "User", + email: newUser.email, + password: passwordHash + } + }); +} + +async function login(request: Request, response: Response, next: NextFunction) { + const provider = request.url.slice(1); + + switch (provider) { + case "google": + if (!request.body.token) { + response.status(400).json({ + status: "fail", + data: { + token: "Token not provided" + } + }); + return; + } + + try { + const user = await authenticateWithGoogle(request.body.token); + (request as RequestWithUser).user = user; + next(); + } catch (err) { + if (err instanceof Error) { + response.status(500).json({ + status: "error", + message: err.message + }); + return; + } + + response.status(500).json({ + status: "error", + message: "Internal Server Error" + }); + } + break; + default: + response.status(500).json({ + status: "error", + message: "Unsupported authentication provider" + }); + return; + } +} + +async function authenticate( + request: Request, + response: Response, + next: NextFunction +) { + const authToken = request.get("Authorization"); + + if (!authToken) { + return response.status(400).json({ + status: "fail", + data: { + Authorization: "Token not provided" + } + }); + } + + try { + (request as RequestWithUser).user = await token.verifyAccessToken( + authToken + ); + next(); + } catch (error) { + if (error instanceof Error) { + switch (error.name) { + case "TokenExpiredError": + return response.status(401).json({ + status: "fail", + data: { + accessToken: "Token expired" + } + }); + default: + return response.status(500).json({ + status: "error", + message: error.message + }); + } + } + + response.status(500).json({ + status: "error", + message: "Internal Server Error" + }); + } +} + +module.exports = { + login, + register, + authenticate, + authenticateWithGoogle +}; diff --git a/packages/server/package.json b/packages/server/package.json index 38f01fd3..4e977d9a 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -6,7 +6,7 @@ "license": "MIT", "private": true, "scripts": { - "start": "nodemon index.js", + "start": "ts-node index.ts", "build": "yarn tsc .", "forward": "ttab node scripts/ngrok.js", "dev": "ttab yarn start", @@ -25,12 +25,15 @@ "jsonwebtoken": "^8.5.1", "pg": "^8.8.0", "redis": "^4.6.7", + "ts-node": "^10.9.1", "uniqid": "^5.4.0", "uuid": "^9.0.0" }, "devDependencies": { "@types/bcrypt": "^5.0.0", "@types/express": "^4.17.17", + "@types/jsonwebtoken": "^9.0.4", + "@types/node": "^20.8.10", "@types/uuid": "^9.0.5", "@typescript-eslint/eslint-plugin": "^6.2.1", "@typescript-eslint/parser": "^6.2.1", diff --git a/packages/server/types/auth.ts b/packages/server/types/auth.ts new file mode 100644 index 00000000..f98cc5a5 --- /dev/null +++ b/packages/server/types/auth.ts @@ -0,0 +1,35 @@ +import { Request } from "express"; + +// extending json web token payloads with necessary user properties +// for our authorization +declare module "jsonwebtoken" { + export interface JwtPayload { + userId: string; + firstName: string; + lastName: string; + avatar: string | null; + email: string; + } +} + +export type NewUser = { + email: string; + password: string; +}; + +export type RefreshTokenPayload = { + userId: string; +}; + +// user info stored in JWT payloads +export type UserPayload = { + userId: string; + firstName: string; + lastName: string; + avatar: string | null; + email: string; +}; + +export interface RequestWithUser extends Request { + user: UserPayload; +} diff --git a/packages/server/utils/token.ts b/packages/server/utils/token.ts new file mode 100644 index 00000000..699510b8 --- /dev/null +++ b/packages/server/utils/token.ts @@ -0,0 +1,61 @@ +import { User } from "@prisma/client"; +import jwt, { JwtPayload } from "jsonwebtoken"; +import { UserPayload } from "../types/auth"; + +function generateRefreshToken(user: User | UserPayload) { + const { userId } = user; + return jwt.sign( + { + userId + }, + process.env.TOKEN_SECRET!, + { + expiresIn: "1d" + } + ); +} + +function generateAccessToken(user: User | UserPayload) { + const { userId, firstName, lastName, avatar, email } = user; + return jwt.sign( + { + userId, + firstName, + lastName, + avatar, + email + }, + process.env.WEB_CLIENT_SECRET!, + { + expiresIn: "1h" + } + ); +} + +async function verifyRefreshToken(refreshToken: string) { + return jwt.verify(refreshToken, process.env.TOKEN_SECRET!); +} + +async function verifyAccessToken(accessToken: string): Promise { + return new Promise((resolve, reject) => { + jwt.verify( + accessToken, + process.env.WEB_CLIENT_SECRET!, + function (err, decoded) { + if (err) { + reject(err); + } + // Token is valid + // resolve promise with the payload of the access token + resolve(decoded as jwt.JwtPayload); + } + ); + }); +} + +export default { + generateRefreshToken, + generateAccessToken, + verifyRefreshToken, + verifyAccessToken +}; diff --git a/yarn.lock b/yarn.lock index df695889..b7be2f04 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1151,6 +1151,13 @@ resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + "@egjs/hammerjs@^2.0.17": version "2.0.17" resolved "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz" @@ -2035,6 +2042,11 @@ resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + "@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": version "1.1.2" resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" @@ -2045,6 +2057,14 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.9": version "0.3.17" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz" @@ -2481,6 +2501,26 @@ dependencies: pretty-format "^29.4.0" +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + "@types/babel__core@^7.1.14": version "7.20.0" resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz" @@ -2602,6 +2642,13 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== +"@types/jsonwebtoken@^9.0.4": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.4.tgz#8b74bbe87bde81a3469d4b32a80609bec62c23ec" + integrity sha512-8UYapdmR0QlxgvJmyE8lP7guxD0UGVMfknsdtCFZh4ovShdBl3iOI4zdvqBHrB/IS+xUj3PSx73Qkey1fhWz+g== + dependencies: + "@types/node" "*" + "@types/mime@*": version "3.0.1" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" @@ -2617,6 +2664,13 @@ resolved "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz" integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== +"@types/node@^20.8.10": + version "20.8.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.10.tgz#a5448b895c753ae929c26ce85cab557c6d4a365e" + integrity sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w== + dependencies: + undici-types "~5.26.4" + "@types/prettier@^2.1.5": version "2.7.2" resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz" @@ -2834,6 +2888,16 @@ acorn-jsx@^5.3.2: resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +acorn-walk@^8.1.1: + version "8.3.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.0.tgz#2097665af50fd0cf7a2dfccd2b9368964e66540f" + integrity sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA== + +acorn@^8.4.1: + version "8.11.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" + integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== + acorn@^8.8.0: version "8.8.2" resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz" @@ -2965,6 +3029,11 @@ arg@4.1.0: resolved "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz" integrity sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg== +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" @@ -3999,6 +4068,11 @@ cosmiconfig@^5.0.5, cosmiconfig@^5.1.0: js-yaml "^3.13.1" parse-json "^4.0.0" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-fetch@^3.1.5: version "3.1.5" resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz" @@ -4245,6 +4319,11 @@ diff-sequences@^29.4.3: resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz" integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" @@ -7498,6 +7577,11 @@ make-dir@^3.0.0, make-dir@^3.1.0: dependencies: semver "^6.0.0" +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + makeerror@1.0.12: version "1.0.12" resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" @@ -10214,6 +10298,25 @@ ts-interface-checker@^0.1.9: resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== +ts-node@^10.9.1: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0: version "2.5.0" resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz" @@ -10318,6 +10421,11 @@ undefsafe@^2.0.5: resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" @@ -10490,6 +10598,11 @@ uuid@^9.0.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + v8-to-istanbul@^9.0.1: version "9.0.1" resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz" @@ -10781,6 +10894,11 @@ yargs@^17.3.1: y18n "^5.0.5" yargs-parser "^21.1.1" +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" From a95783c6dbd40f81cc42e7223142d94e3f4141c1 Mon Sep 17 00:00:00 2001 From: Luke Dinh Date: Fri, 10 Nov 2023 09:01:36 -0800 Subject: [PATCH 14/17] feat: migrated index.js and users api router - fixed ApiRequest type to default to empty object instead of void to fix type error --- packages/server/controllers/auth.js | 200 ------------------ packages/server/controllers/auth.ts | 2 +- packages/server/index.ts | 41 ++++ packages/server/package.json | 2 + packages/server/routes/api/events.ts | 2 +- packages/server/routes/api/guilds.ts | 2 +- .../server/routes/api/{users.js => users.ts} | 46 ++-- packages/server/types/index.ts | 6 +- yarn.lock | 24 +++ 9 files changed, 108 insertions(+), 217 deletions(-) delete mode 100644 packages/server/controllers/auth.js create mode 100644 packages/server/index.ts rename packages/server/routes/api/{users.js => users.ts} (51%) diff --git a/packages/server/controllers/auth.js b/packages/server/controllers/auth.js deleted file mode 100644 index 5d453fb7..00000000 --- a/packages/server/controllers/auth.js +++ /dev/null @@ -1,200 +0,0 @@ -const { OAuth2Client } = require("google-auth-library"); -const { v5: uuidv5, v4: uuidv4 } = require("uuid"); -const prisma = require("../prisma/prisma"); -const token = require("../utils/token"); -const bcrypt = require("bcrypt"); -const client = new OAuth2Client(process.env.WEB_CLIENT_ID); - -const NAMESPACE = "7af17462-8078-4703-adda-be2143a4d93a"; - -async function create(accessToken, refreshToken, profile, callback) { - try { - let { sub, given_name, family_name, picture, email } = profile._json; - picture = picture.replace("=s96-c", ""); - const googleUUID = uuidv5(sub, NAMESPACE); - const user = await prisma.users.findUniqueOrThrow({ - where: { - userId: googleUUID, - }, - }); - if (!user) { - const newUser = await prisma.users.create({ - data: { - userId: googleUUID, - email: email, - avatar: picture, - firstName: given_name, - lastName: family_name, - }, - }); - console.log("user doesn't exist. create one"); - callback(null, newUser); - } else { - console.log("user exists"); - callback(null, user); - } - } catch (error) { - callback(error); - } -} - -async function authenticateWithGoogle(token) { - // Verify the token is valid; to ensure it's a valid google auth. - const { payload } = await client.verifyIdToken({ - idToken: token, - audience: process.env.WEB_CLIENT_ID, - }); - - // Check if this user already exist on our database. - const { sub, email, given_name, family_name, picture } = payload; - - // generate Type 5 UUIDs with hashes of user's google account IDs instead of - // completely random Type 4 UUIDs to keep UUIDs unique regardless of google or - // local authentication - const googleUUID = uuidv5(sub, NAMESPACE); - const user = await prisma.users.findUnique({ - where: { - userId: googleUUID, - }, - }); - - // if the query returns a row, there's a user with the existing userId. - if (!user) { - const newUser = await prisma.users.create({ - data: { - userId: googleUUID, - email: email, - avatar: picture, - firstName: given_name, - lastName: family_name, - }, - }); - - return newUser; - } else { - return user; - } -} - -async function register(newUser) { - const saltRounds = 10; - const salt = await bcrypt.genSalt(saltRounds); - const passwordHash = await bcrypt.hash(newUser.password, salt); - const userId = uuidv4(); - - // placeholder names until new user puts in their names in onboarding screen - await prisma.users.create({ - data: { - userId, - firstName: "New", - lastName: "User", - email: newUser.email, - password: passwordHash, - }, - }); -} - -async function login(request, response, next) { - const provider = request.url.slice(1); - - switch (provider) { - case "google": - if (!request.body.token) { - return response.status(400).json({ - status: "fail", - data: { - token: "Token not provided", - }, - }); - } - - try { - const user = await authenticateWithGoogle(request.body.token); - request.user = user; - next(); - } catch (err) { - return response.status(500).json({ - status: "error", - message: err.message, - }); - } - break; - default: - return response.status(500).json({ - status: "error", - message: "Unsupported authentication provider", - }); - } -} - -async function serialize(payload, callback) { - try { - const { user_id } = payload; - console.log("serializeUser", user_id); - callback(null, user_id); - } catch (error) { - callback(error); - } -} - -async function deserialize(id, callback) { - try { - console.log("deserializeUser"); - console.log("id", id); - const user = await prisma.users.findUniqueOrThrow({ - where: { - userId: id, - }, - }); - if (user) { - console.log(user); - callback(null, user); - } - } catch (error) { - callback(error); - } -} - -async function authenticate(request, response, next) { - const authToken = request.get("Authorization"); - - if (!authToken) { - return response.status(400).json({ - status: "fail", - data: { - Authorization: "Token not provided", - }, - }); - } - - try { - request.user = token.verifyAccessToken(authToken); - next(); - } catch (error) { - // Handle any errors that occur during token verification - switch (error.name) { - case "TokenExpiredError": - return response.status(401).json({ - status: "fail", - data: { - accessToken: "Token expired", - }, - }); - default: - return response.status(500).json({ - status: "error", - message: error.message, - }); - } - } -} - -module.exports = { - create, - login, - register, - serialize, - deserialize, - authenticate, - authenticateWithGoogle, -}; diff --git a/packages/server/controllers/auth.ts b/packages/server/controllers/auth.ts index 2f0e5f3e..faf0c57a 100644 --- a/packages/server/controllers/auth.ts +++ b/packages/server/controllers/auth.ts @@ -163,7 +163,7 @@ async function authenticate( } } -module.exports = { +export default { login, register, authenticate, diff --git a/packages/server/index.ts b/packages/server/index.ts new file mode 100644 index 00000000..5af3af36 --- /dev/null +++ b/packages/server/index.ts @@ -0,0 +1,41 @@ +import dotenv from "dotenv"; +import express from "express"; +import cors from "cors"; +import cookieParser from "cookie-parser"; + +dotenv.config(); + +const app = express(); +const PORT = process.env.PORT || 5050; + +import users from "./routes/api/users"; +import guilds from "./routes/api/guilds"; +import events from "./routes/api/events"; +import auth from "./routes/api/auth"; +import images from "./routes/api/images"; + +app.use( + cors({ + origin: ["icebreak://", "http://localhost:8081"], + credentials: true + }) +); + +app.use(cookieParser()); +app.use(express.json({ limit: "20mb" })); + +app.get("/", async (request, response) => { + response.send("Hello SEA!"); +}); + +app.use("/api/auth", auth); +app.use("/api/users", users); +app.use("/api/guilds", guilds); +app.use("/api/events", events); +app.use("/api/media/images", images); + +const server = app.listen(PORT, () => { + console.log(`Server listening on port ${PORT}`); +}); + +module.exports = server; diff --git a/packages/server/package.json b/packages/server/package.json index 6bad9206..bd03c395 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -33,6 +33,8 @@ }, "devDependencies": { "@types/bcrypt": "^5.0.0", + "@types/cookie-parser": "^1.4.6", + "@types/cors": "^2.8.16", "@types/express": "^4.17.17", "@types/jsonwebtoken": "^9.0.4", "@types/node": "^20.8.10", diff --git a/packages/server/routes/api/events.ts b/packages/server/routes/api/events.ts index b551a842..db1cfde1 100644 --- a/packages/server/routes/api/events.ts +++ b/packages/server/routes/api/events.ts @@ -160,4 +160,4 @@ router.get( } ); -module.exports = router; +export default router; diff --git a/packages/server/routes/api/guilds.ts b/packages/server/routes/api/guilds.ts index 7d78f798..08d1e5e6 100644 --- a/packages/server/routes/api/guilds.ts +++ b/packages/server/routes/api/guilds.ts @@ -83,4 +83,4 @@ router.get( } }); -module.exports = router; +export default router; diff --git a/packages/server/routes/api/users.js b/packages/server/routes/api/users.ts similarity index 51% rename from packages/server/routes/api/users.js rename to packages/server/routes/api/users.ts index 9d0359df..99df4ec1 100644 --- a/packages/server/routes/api/users.js +++ b/packages/server/routes/api/users.ts @@ -1,8 +1,8 @@ -const express = require("express"); +import express from "express"; const router = express.Router(); -const UserController = require("../../controllers/users"); -const AuthController = require("../../controllers/auth"); +import UserController from "../../controllers/users"; +import AuthController from "../../controllers/auth"; router.get("/", async (request, response) => { try { @@ -10,14 +10,24 @@ router.get("/", async (request, response) => { response.status(200).json({ status: "success", data: { - users, - }, + users + } }); + return; } catch (error) { + if (error instanceof Error) { + response.status(500).json({ + status: "error", + message: error.message + }); + return; + } + response.status(500).json({ status: "error", - message: error.message, + message: "Internal Server Error" }); + return; } }); @@ -29,28 +39,38 @@ router.get( const { userId } = request.params; if (userId === undefined) { - return response.status(400).json({ + response.status(400).json({ status: "fail", data: { - userId: "User ID not provided", - }, + userId: "User ID not provided" + } }); + return; } const user = await UserController.getUser(userId); response.status(200).json({ status: "success", data: { - user: user, - }, + user: user + } }); } catch (error) { + if (error instanceof Error) { + response.status(500).json({ + status: "error", + message: error.message + }); + return; + } + response.status(500).json({ status: "error", - message: error.message, + message: "Internal Server Error" }); + return; } } ); -module.exports = router; +export default router; diff --git a/packages/server/types/index.ts b/packages/server/types/index.ts index 96556e42..02eb0da8 100644 --- a/packages/server/types/index.ts +++ b/packages/server/types/index.ts @@ -8,7 +8,11 @@ import { Send } from "express-serve-static-core"; * * void = optional generic parameters */ -export type APIRequest = Express.Request; +export type APIRequest, V = void> = Express.Request< + T, + any, + V +>; /** * @template T diff --git a/yarn.lock b/yarn.lock index 107e45d3..c72dd6ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3385,6 +3385,20 @@ dependencies: "@types/node" "*" +"@types/cookie-parser@^1.4.6": + version "1.4.6" + resolved "https://registry.yarnpkg.com/@types/cookie-parser/-/cookie-parser-1.4.6.tgz#002643c514cccf883a65cbe044dbdc38c0b92ade" + integrity sha512-KoooCrD56qlLskXPLGUiJxOMnv5l/8m7cQD2OxJ73NPMhuSz9PmvwRD6EpjDyKBVrdJDdQ4bQK7JFNHnNmax0w== + dependencies: + "@types/express" "*" + +"@types/cors@^2.8.16": + version "2.8.16" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.16.tgz#a24bf65acd216c078890ca6ceb91e672adb158e7" + integrity sha512-Trx5or1Nyg1Fq138PCuWqoApzvoSLWzZ25ORBiHMbbUT42g578lH1GT4TwYDbiUOLFuDsCkfLneT2105fsFWGg== + dependencies: + "@types/node" "*" + "@types/express-serve-static-core@^4.17.33": version "4.17.35" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz#c95dd4424f0d32e525d23812aa8ab8e4d3906c4f" @@ -3395,6 +3409,16 @@ "@types/range-parser" "*" "@types/send" "*" +"@types/express@*": + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/express@^4.17.17": version "4.17.17" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" From 7a88302d0736fc4a0436c1d028bb4cf2553c6c43 Mon Sep 17 00:00:00 2001 From: mikephu Date: Fri, 10 Nov 2023 16:17:39 -0800 Subject: [PATCH 15/17] fix: fixed ESlint errors --- packages/server/routes/api/events.ts | 12 ++++++------ packages/server/routes/api/guilds.ts | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/server/routes/api/events.ts b/packages/server/routes/api/events.ts index db1cfde1..d14bf67c 100644 --- a/packages/server/routes/api/events.ts +++ b/packages/server/routes/api/events.ts @@ -31,7 +31,7 @@ router.get( AuthController.authenticate, async (request: APIRequest, response: APIResponse) => { try { - let queryLimit: string = request.query.limit as string + const queryLimit: string = request.query.limit as string if (queryLimit && isNaN(parseInt(queryLimit))) { return response.status(400).json({ @@ -49,10 +49,10 @@ router.get( : ""; // let [currentPage, action, eventId] = requestCursor.split("___"); - let cursor: string[] = requestCursor.split("___"); - let currentPage: number = parseInt(cursor[0]) || 1; - let action: string = cursor[1] - let eventId: string = cursor[2] + const cursor: string[] = requestCursor.split("___"); + const currentPage: number = parseInt(cursor[0]) || 1; + const action: string = cursor[1] + const eventId: string = cursor[2] const events: Event[] = await EventController.getEvents( eventLimit, @@ -133,7 +133,7 @@ type APIResponseGetEvent = { router.get( "/:eventId", AuthController.authenticate, - async (request: APIRequest, response:APIResponse) => { + async (request: APIRequest, response: APIResponse) => { try { const { eventId } = request.params; const event: Event | null = await EventController.getEvent(eventId); diff --git a/packages/server/routes/api/guilds.ts b/packages/server/routes/api/guilds.ts index 08d1e5e6..a360fd97 100644 --- a/packages/server/routes/api/guilds.ts +++ b/packages/server/routes/api/guilds.ts @@ -11,7 +11,7 @@ type APIResponseAllGuilds = { } router.get("/", AuthController.authenticate, -async (request: APIRequest, response:APIResponse) => { +async (request: APIRequest, response: APIResponse) => { try { const guilds: Guild[] = await GuildController.getAllGuilds(); response.status(200).json({ @@ -47,7 +47,7 @@ type APIRequestGetGuild = { router.get( "/:guildId", AuthController.authenticate, - async (request: APIRequest, response:APIResponse) => { + async (request: APIRequest, response: APIResponse) => { try { const { guildId } = request.params; From d499196deaabf826bd01e375ed2fb1daaa2712da Mon Sep 17 00:00:00 2001 From: Luke Dinh Date: Wed, 15 Nov 2023 12:22:09 -0800 Subject: [PATCH 16/17] fix: migrated local auth api route - enables API testing since local auth is used in automatic authentication in our Postman - fixed Guild Prisma model not mapping correctly to guilds table in Postgres --- packages/server/controllers/guilds.ts | 8 +- packages/server/prisma/schema.prisma | 86 ++++++------ packages/server/routes/api/auth.ts | 127 ++++++++++++++++++ .../server/routes/api/{auth.js => authjs.js} | 0 .../server/utils/{token.js => tokenjs.js} | 0 5 files changed, 175 insertions(+), 46 deletions(-) create mode 100644 packages/server/routes/api/auth.ts rename packages/server/routes/api/{auth.js => authjs.js} (100%) rename packages/server/utils/{token.js => tokenjs.js} (100%) diff --git a/packages/server/controllers/guilds.ts b/packages/server/controllers/guilds.ts index d7acd112..4368e370 100644 --- a/packages/server/controllers/guilds.ts +++ b/packages/server/controllers/guilds.ts @@ -7,15 +7,15 @@ async function getAllGuilds(): Promise { } async function getGuild(guildId: string): Promise { - const query = await prisma.guild.findFirst({ + const query = await prisma.guild.findUnique({ where: { - guildId: guildId, - }, + guildId: guildId + } }); return query; } export default { getGuild, - getAllGuilds, + getAllGuilds }; diff --git a/packages/server/prisma/schema.prisma b/packages/server/prisma/schema.prisma index 561f3e0c..23d54b40 100644 --- a/packages/server/prisma/schema.prisma +++ b/packages/server/prisma/schema.prisma @@ -8,68 +8,70 @@ datasource db { } model User { - userId String @id @map("user_id") @db.Uuid - joinedDate DateTime? @map("joined_date") @db.Timestamp(6) - firstName String @map("first_name") @db.VarChar(50) - lastName String @map("last_name") @db.VarChar(50) - isNew Boolean @map("is_new") @default(true) - email String @unique @db.VarChar(255) - avatar String? @db.VarChar(255) - password String? @db.VarChar(255) - eventAttendees EventAttendee[] - guildMembers GuildMember[] + userId String @id @map("user_id") @db.Uuid + joinedDate DateTime? @map("joined_date") @db.Timestamp(6) + firstName String @map("first_name") @db.VarChar(50) + lastName String @map("last_name") @db.VarChar(50) + isNew Boolean @default(true) @map("is_new") + email String @unique @db.VarChar(255) + avatar String? @db.VarChar(255) + password String? @db.VarChar(255) + eventAttendees EventAttendee[] + guildMembers GuildMember[] @@map("users") } model EventAttendee { - userId String @db.Uuid @map("user_id") - eventId String @db.Uuid @map("event_id") - event Event? @relation(fields: [eventId], references: [eventId], onDelete: Cascade, onUpdate: NoAction, map: "fk_event") - attendee User? @relation(fields: [userId], references: [userId], onDelete: Cascade, onUpdate: NoAction, map: "fk_user") + userId String @map("user_id") @db.Uuid + eventId String @map("event_id") @db.Uuid + event Event? @relation(fields: [eventId], references: [eventId], onDelete: Cascade, onUpdate: NoAction, map: "fk_event") + attendee User? @relation(fields: [userId], references: [userId], onDelete: Cascade, onUpdate: NoAction, map: "fk_user") @@id([userId, eventId]) @@map("event_attendees") } model Event { - eventId String @id(map: "event_pkey") @db.Uuid @map("event_id") - guildId String @db.Uuid @map("guild_id") - title String @db.VarChar(255) - description String? @db.VarChar(255) - startDate DateTime? @db.Timestamp(6) @map("start_date") - endDate DateTime? @db.Timestamp(6) @map("end_date") - location String? @db.VarChar(255) - thumbnail String? @db.VarChar(255) - eventAttendees EventAttendee[] - guilds Guild @relation(fields: [guildId], references: [guildId], onDelete: Cascade, onUpdate: Cascade, map: "event_guild_id_fkey") + eventId String @id(map: "event_pkey") @map("event_id") @db.Uuid + guildId String @map("guild_id") @db.Uuid + title String @db.VarChar(255) + description String? @db.VarChar(255) + startDate DateTime? @map("start_date") @db.Timestamp(6) + endDate DateTime? @map("end_date") @db.Timestamp(6) + location String? @db.VarChar(255) + thumbnail String? @db.VarChar(255) + eventAttendees EventAttendee[] + guilds Guild @relation(fields: [guildId], references: [guildId], onDelete: Cascade, onUpdate: Cascade, map: "event_guild_id_fkey") @@map("events") } model GuildMember { - userId String @db.Uuid @map("user_id") - guildId String @db.Uuid @map("guild_id") - guilds Guild @relation(fields: [guildId], references: [guildId], onDelete: Cascade, onUpdate: NoAction, map: "fk_guild") - members User @relation(fields: [userId], references: [userId], onDelete: Cascade, onUpdate: NoAction, map: "fk_user") + userId String @map("user_id") @db.Uuid + guildId String @map("guild_id") @db.Uuid + guilds Guild @relation(fields: [guildId], references: [guildId], onDelete: Cascade, onUpdate: NoAction, map: "fk_guild") + members User @relation(fields: [userId], references: [userId], onDelete: Cascade, onUpdate: NoAction, map: "fk_user") @@id([guildId, userId], map: "users_guilds_pkey") @@map("guild_members") } model Guild { - guildId String @id(map: "guild_pkey") @db.Uuid @map("guild_id") - name String @db.VarChar(100) - handler String @db.VarChar(50) - description String - category String @db.VarChar(255) - location String? @db.VarChar(255) - website String? @db.VarChar(255) - tags String[] - banner String? @db.VarChar(255) - icon String? @db.VarChar(255) - media String[] - isInviteOnly Boolean @map("invite_only") - events Event[] - members GuildMember[] + guildId String @id(map: "guild_pkey") @map("guild_id") @db.Uuid + name String @db.VarChar(100) + handler String @db.VarChar(50) + description String + category String @db.VarChar(255) + location String? @db.VarChar(255) + website String? @db.VarChar(255) + tags String[] + banner String? @db.VarChar(255) + icon String? @db.VarChar(255) + media String[] + isInviteOnly Boolean @map("invite_only") + events Event[] + members GuildMember[] + + @@map("guilds") } diff --git a/packages/server/routes/api/auth.ts b/packages/server/routes/api/auth.ts new file mode 100644 index 00000000..9f2bd025 --- /dev/null +++ b/packages/server/routes/api/auth.ts @@ -0,0 +1,127 @@ +import express from "express"; +import bcrypt from "bcrypt"; +const router = express.Router(); + +import token from "../../utils/token"; +import UserController from "../../controllers/users"; +import { APIRequest, APIResponse } from "../../types"; + +type LocalAuthReqBody = { + email: string; + password: string; +}; + +type LocalAuthResponse = { + user: { + userId: string; + firstName: string; + lastName: string; + email: string; + avatar: string | null; + isNew: boolean; + }; + accessToken: string; + refreshToken: string; +}; + +router.post( + "/local", + async ( + request: APIRequest, + response: APIResponse + ) => { + try { + // get user input + const { email, password } = request.body; + + if (email == undefined) { + return response.status(400).json({ + status: "fail", + data: { + email: "Email not provided" + } + }); + } + + if (password == undefined) { + return response.status(400).json({ + status: "fail", + data: { + password: "Password not provided" + } + }); + } + + const emailRegex = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/g; + + if (!emailRegex.test(email)) { + // check if email is valid, doesn't include or no spaces + return response.status(400).json({ + status: "fail", + data: { + email: "Invalid email provided" + } + }); + } + + const requestedUser = await UserController.getUserByEmail(email); + + if (!requestedUser || requestedUser.email !== email) { + // check if email is in the database + return response.status(400).json({ + status: "fail", + data: { + email: "A user with that email does not exist." + } + }); + } + + const isValidPassword = await bcrypt.compare( + password, + requestedUser.password || "" + ); + + if (!isValidPassword) { + return response.status(400).json({ + status: "fail", + data: { + password: "Incorrect password" + } + }); + } + + const refreshToken = token.generateRefreshToken(requestedUser); + const accessToken = token.generateAccessToken(requestedUser); + + response.status(200).json({ + status: "success", + data: { + user: { + userId: requestedUser.userId, + firstName: requestedUser.firstName, + lastName: requestedUser.lastName, + email: requestedUser.email, + avatar: requestedUser.avatar, + isNew: requestedUser.isNew + }, + accessToken, + refreshToken + } + }); + } catch (error) { + if (error instanceof Error) { + response.status(500).json({ + status: "error", + message: error.message + }); + } else { + response.status(500).json({ + status: "error", + message: "Internal Server Error" + }); + } + } + } +); + +export default router; diff --git a/packages/server/routes/api/auth.js b/packages/server/routes/api/authjs.js similarity index 100% rename from packages/server/routes/api/auth.js rename to packages/server/routes/api/authjs.js diff --git a/packages/server/utils/token.js b/packages/server/utils/tokenjs.js similarity index 100% rename from packages/server/utils/token.js rename to packages/server/utils/tokenjs.js From e867ea842b0637cecfc26506e2c2f2144d25ce83 Mon Sep 17 00:00:00 2001 From: Luke Dinh Date: Fri, 17 Nov 2023 08:24:51 -0800 Subject: [PATCH 17/17] feat: migrated auth api routes --- packages/server/prisma/schema.prisma | 2 +- packages/server/routes/api/auth.ts | 316 +++++++++++++++++++++++++-- packages/server/types/auth.ts | 8 +- packages/server/types/index.ts | 9 +- packages/server/utils/token.ts | 18 +- 5 files changed, 325 insertions(+), 28 deletions(-) diff --git a/packages/server/prisma/schema.prisma b/packages/server/prisma/schema.prisma index 23d54b40..03545cd7 100644 --- a/packages/server/prisma/schema.prisma +++ b/packages/server/prisma/schema.prisma @@ -58,7 +58,7 @@ model GuildMember { } model Guild { - guildId String @id(map: "guild_pkey") @map("guild_id") @db.Uuid + guildId String @id(map: "guild_pkey") @default(dbgenerated("gen_random_uuid()")) @map("guild_id") @db.Uuid name String @db.VarChar(100) handler String @db.VarChar(50) description String diff --git a/packages/server/routes/api/auth.ts b/packages/server/routes/api/auth.ts index 9f2bd025..1e909533 100644 --- a/packages/server/routes/api/auth.ts +++ b/packages/server/routes/api/auth.ts @@ -1,34 +1,199 @@ import express from "express"; import bcrypt from "bcrypt"; +import token from "../../utils/token"; + const router = express.Router(); -import token from "../../utils/token"; +import AuthController from "../../controllers/auth"; import UserController from "../../controllers/users"; import { APIRequest, APIResponse } from "../../types"; +import { checkInvalidToken, addToTokenBlacklist } from "../../utils/redis"; +import { RequestWithUser, UserPayload } from "../../types/auth"; + +type AuthResponse = { + user: UserPayload; + accessToken: string; + refreshToken: string; +}; + +router.get( + "/user", + AuthController.authenticate, + (request: RequestWithUser, response: APIResponse) => { + if (!request.user) { + response.status(500).json({ + status: "error", + message: "Could not authenticate user" + }); + return; + } + + const accessToken = token.generateAccessToken(request.user); + const refreshToken = token.generateRefreshToken(request.user); + + // destructuring so we don't send JWT iat and expiration properties in + // response + const { userId, firstName, lastName, avatar, email, isNew } = request.user; + + response.status(200).json({ + status: "success", + data: { + user: { + userId, + firstName, + lastName, + avatar, + email, + isNew + }, + accessToken, + refreshToken + } + }); + } +); + +type GoogleAuthReqBody = { + token: string; +}; + +router.post( + "/google", + AuthController.login, + async ( + request: RequestWithUser, + response: APIResponse + ) => { + if (!request.user) { + response.status(500).json({ + status: "error", + message: "Could not authenticate user" + }); + return; + } + + try { + const { userId, firstName, lastName, email, avatar, isNew } = + request.user; + + const accessToken = token.generateAccessToken(request.user); + const refreshToken = token.generateRefreshToken(request.user); + + response.status(200).json({ + status: "success", + data: { + user: { + userId, + firstName, + lastName, + avatar, + email, + isNew + }, + accessToken, + refreshToken + } + }); + } catch (error) { + if (error instanceof Error) { + response.status(500).json({ + status: "error", + message: error.message + }); + } else { + response.status(500).json({ + status: "error", + message: "Internal Server Error" + }); + } + } + } +); type LocalAuthReqBody = { email: string; password: string; }; -type LocalAuthResponse = { - user: { - userId: string; - firstName: string; - lastName: string; - email: string; - avatar: string | null; - isNew: boolean; - }; - accessToken: string; - refreshToken: string; -}; +router.post( + "/local/register", + async ( + request: APIRequest, + response: APIResponse + ) => { + try { + const { email, password } = request.body; + + if (email == undefined) { + return response.status(400).json({ + status: "fail", + data: { + email: "Email not provided" + } + }); + } + + if (password === undefined) { + return response.status(400).json({ + status: "fail", + data: { + password: "Password not provided" + } + }); + } + + const emailRegex = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/g; + + if (!emailRegex.test(email)) { + // check if email is valid, doesn't include or no spaces + return response.status(400).json({ + status: "fail", + data: { + email: "Invalid email was provided" + } + }); + } + + const requestedUser = await UserController.getUserByEmail(email); + + if (requestedUser?.email === email) { + // check if email is already in the database + return response.status(400).json({ + status: "fail", + data: { + email: "A user with this email already exists." + } + }); + } + + await AuthController.register(request.body); + + // users will have to log in manually after successfully registering + response.status(200).json({ + status: "success", + data: null + }); + } catch (error) { + if (error instanceof Error) { + response.status(500).json({ + status: "error", + message: error.message + }); + } else { + response.status(500).json({ + status: "error", + message: "Internal Server Error" + }); + } + } + } +); router.post( "/local", async ( request: APIRequest, - response: APIResponse + response: APIResponse ) => { try { // get user input @@ -124,4 +289,127 @@ router.post( } ); +type TokenReqBody = { + refreshToken: string; +}; + +type TokenResponse = { + accessToken: string; + refreshToken: string; +}; + +router.post( + "/token", + async ( + request: APIRequest, + response: APIResponse + ) => { + try { + const { refreshToken } = request.body; + + if (!refreshToken) { + return response.status(400).json({ + status: "fail", + data: { + refreshToken: "Refresh token not provided" + } + }); + } + + const isInvalidToken = await checkInvalidToken(refreshToken); + + if (isInvalidToken) { + return response.status(401).json({ + status: "fail", + data: { + refreshToken: "Provided refresh token is revoked" + } + }); + } + + const { userId } = await token.verifyRefreshToken(refreshToken); + const user = await UserController.getUser(userId); + + if (!user) { + response.status(400).json({ + status: "fail", + data: { + refreshToken: "Invalid refresh token provided" + } + }); + return; + } + + const accessToken = token.generateAccessToken(user); + const newRefreshToken = token.generateRefreshToken(user); + + response.status(200).json({ + status: "success", + data: { + accessToken, + refreshToken: newRefreshToken + } + }); + } catch (error) { + if (error instanceof Error) { + response.status(500).json({ + status: "error", + message: error.message + }); + } else { + response.status(500).json({ + status: "error", + message: "Internal Server Error" + }); + } + } + } +); + +type RevokeTokenReqBody = { + refreshToken: string; +}; + +router.post( + "/token/revoke", + async ( + request: APIRequest, + response: APIResponse + ) => { + const { refreshToken } = request.body; + + if (!refreshToken) { + return response.status(400).json({ + status: "fail", + data: { + refreshToken: "Refresh token not provided" + } + }); + } + + try { + token.verifyRefreshToken(refreshToken); + + await addToTokenBlacklist(refreshToken); + + response.status(200).json({ + status: "success", + data: null + }); + } catch (error) { + if (error instanceof Error) { + response.status(500).json({ + status: "error", + message: error.message + }); + } else { + response.status(500).json({ + status: "error", + message: "Internal Server Error" + }); + } + } + } +); + export default router; diff --git a/packages/server/types/auth.ts b/packages/server/types/auth.ts index f98cc5a5..9802e883 100644 --- a/packages/server/types/auth.ts +++ b/packages/server/types/auth.ts @@ -1,4 +1,4 @@ -import { Request } from "express"; +import { APIRequest } from "."; // extending json web token payloads with necessary user properties // for our authorization @@ -28,8 +28,10 @@ export type UserPayload = { lastName: string; avatar: string | null; email: string; + isNew: boolean; }; -export interface RequestWithUser extends Request { - user: UserPayload; +export interface RequestWithUser, V = void> + extends APIRequest { + user?: UserPayload; } diff --git a/packages/server/types/index.ts b/packages/server/types/index.ts index 02eb0da8..3b94c01e 100644 --- a/packages/server/types/index.ts +++ b/packages/server/types/index.ts @@ -1,5 +1,4 @@ -import * as Express from "express"; -import { Send } from "express-serve-static-core"; +import { Send, Request, Response } from "express-serve-static-core"; /** * @template T, V @@ -8,7 +7,7 @@ import { Send } from "express-serve-static-core"; * * void = optional generic parameters */ -export type APIRequest, V = void> = Express.Request< +export type APIRequest, V = void> = Request< T, any, V @@ -18,14 +17,14 @@ export type APIRequest, V = void> = Express.Request< * @template T * @param T - structure of response body */ -export interface APIResponse> extends Express.Response { +export interface APIResponse extends Response { json: Send< SuccessResponseBody | FailResponseBody | ErrorResponseBody, this >; } -type SuccessResponseBody> = { +type SuccessResponseBody = { status: "success"; data: T; }; diff --git a/packages/server/utils/token.ts b/packages/server/utils/token.ts index 699510b8..667a2134 100644 --- a/packages/server/utils/token.ts +++ b/packages/server/utils/token.ts @@ -1,5 +1,5 @@ import { User } from "@prisma/client"; -import jwt, { JwtPayload } from "jsonwebtoken"; +import jwt from "jsonwebtoken"; import { UserPayload } from "../types/auth"; function generateRefreshToken(user: User | UserPayload) { @@ -32,11 +32,19 @@ function generateAccessToken(user: User | UserPayload) { ); } -async function verifyRefreshToken(refreshToken: string) { - return jwt.verify(refreshToken, process.env.TOKEN_SECRET!); +function verifyRefreshToken(refreshToken: string): Promise { + return new Promise((resolve, reject) => { + jwt.verify(refreshToken, process.env.TOKEN_SECRET!, (err, decoded) => { + if (err) { + reject(err); + } + + resolve(decoded as UserPayload); + }); + }); } -async function verifyAccessToken(accessToken: string): Promise { +async function verifyAccessToken(accessToken: string): Promise { return new Promise((resolve, reject) => { jwt.verify( accessToken, @@ -47,7 +55,7 @@ async function verifyAccessToken(accessToken: string): Promise { } // Token is valid // resolve promise with the payload of the access token - resolve(decoded as jwt.JwtPayload); + resolve(decoded as UserPayload); } ); });