From 6513cbae23930f5dfa4346974498cef0ecaee1e2 Mon Sep 17 00:00:00 2001 From: Wayex <30770802+alexrobaina@users.noreply.github.com> Date: Tue, 31 Oct 2023 13:21:36 -0300 Subject: [PATCH] create pet finished (#81) --- .DS_Store | Bin 6148 -> 6148 bytes package.json | 4 +- src/app.ts | 51 ++++--- .../migration.sql | 15 ++ .../migration.sql | 12 ++ .../migration.sql | 11 ++ .../migration.sql | 2 + src/database/prisma/schema.prisma | 22 ++- src/database/prisma/seed.ts | 21 ++- src/database/prisma/utils/createPets.ts | 20 +++ src/database/prisma/utils/createUsers.ts | 144 ++++++++++++++++++ src/middlewares/googleCloudUploader.ts | 110 ++++++------- src/middlewares/utils/handleError.ts | 10 ++ src/middlewares/utils/saveToCloud.ts | 47 ++++++ src/middlewares/utils/saveToLocal.ts | 52 +++++++ src/routes/pet.ts | 22 ++- src/routes/user.ts | 24 +-- src/services/uploadImage.ts | 83 ++++++++++ src/useCases/petCases/createPet.ts | 140 ++++++++++++++++- src/useCases/petCases/deletePet.ts | 20 ++- src/useCases/petCases/getDashboardPets.ts | 2 + src/useCases/petCases/getPets.ts | 15 +- .../petCases/utils/getDashboardPets.ts | 5 +- src/useCases/userCases/updateRole.ts | 25 +-- src/useCases/userCases/updateUser.ts | 8 +- yarn.lock | 141 ++++++++++++++++- 26 files changed, 848 insertions(+), 158 deletions(-) create mode 100644 src/database/prisma/migrations/20231012151220_add_description_in_user_model/migration.sql create mode 100644 src/database/prisma/migrations/20231013120342_add_data_in_user/migration.sql create mode 100644 src/database/prisma/migrations/20231029030138_add_created_by_pets/migration.sql create mode 100644 src/database/prisma/migrations/20231029205130_add_qrcode_in_pet/migration.sql create mode 100644 src/database/prisma/utils/createUsers.ts create mode 100644 src/middlewares/utils/handleError.ts create mode 100644 src/middlewares/utils/saveToCloud.ts create mode 100644 src/middlewares/utils/saveToLocal.ts create mode 100644 src/services/uploadImage.ts diff --git a/.DS_Store b/.DS_Store index bcf1e4112df81d1d9696e449f23b063e79e85fa0..97593a1965278f734b6fe202d43f15c874dd3d1e 100644 GIT binary patch delta 50 zcmZoMXfc@J&nUJrU^g?P*km4--=ZuGDGZqm#SA57!9{sF`FZIK3=E8$Sy*2)ZD!~A G%MSo^5e{(x delta 31 ncmZoMXfc@J&nUVvU^g?P=wu$2-<#!GUocH9kl4)5@s}R}rCtho diff --git a/package.json b/package.json index 5f4f065..7e3936f 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@types/bcrypt": "^5.0.0", "@types/body-parser": "^1.19.2", "@types/google-cloud__storage": "^2.3.1", + "@types/qrcode": "^1.5.4", "@types/sharp": "^0.32.0", "aws-sdk": "^2.814.0", "bcrypt": "^5.0.0", @@ -35,7 +36,8 @@ "nodemailer": "^6.7.2", "passport": "^0.4.1", "passport-jwt": "^4.0.0", - "prisma": "^5.3.0" + "prisma": "^5.3.0", + "qrcode": "^1.5.3" }, "devDependencies": { "@types/cors": "^2.8.8", diff --git a/src/app.ts b/src/app.ts index 8c63f9f..577744e 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,39 +1,40 @@ -import express from 'express'; +import express from 'express' // morgan sirve para ver las peticiones a la api cuando esta corriendo el servidor en la consola -import morgan from 'morgan'; -import dotenv from 'dotenv'; +import morgan from 'morgan' +import dotenv from 'dotenv' // sirve para comunicarnos con otros tipos de servidores de desarrollo -import cors from 'cors'; -import passport from 'passport'; -import passportMiddleware from './middlewares/passport'; -import routes from './routes'; -import path from 'path'; -import { config } from './config/config'; +import cors from 'cors' +import passport from 'passport' +import passportMiddleware from './middlewares/passport' +import routes from './routes' +import path from 'path' +import { config } from './config/config' // initializations -const app = express(); -dotenv.config(); +const app = express() +dotenv.config() -const port = config.PORT; +const port = config.PORT // settings -app.set('port', port); +app.set('port', port) // middleware -app.use(morgan('dev')); -app.use(morgan('dev')); -app.use(cors()); -app.use(express.json()); -app.use(express.urlencoded({ extended: false })); -app.use(express.static(path.join(__dirname, 'uploads'))); -app.use(passport.initialize()); -passport.use(passportMiddleware); +app.use(morgan('dev')) +app.use(morgan('dev')) +app.use(cors()) +app.use(express.json()) +app.use('/uploads', express.static(path.join(__dirname, 'uploads'))) +app.use(express.urlencoded({ extended: false })) +// app.use(express.static(path.join(__dirname, 'uploads'))); +app.use(passport.initialize()) +passport.use(passportMiddleware) // routes app.get('/', (req, res) => { - res.send(`Welcome to ${config.APP_NAME}`); -}); + res.send(`Welcome to ${config.APP_NAME}`) +}) -app.use(routes); +app.use(routes) -export default app; +export default app diff --git a/src/database/prisma/migrations/20231012151220_add_description_in_user_model/migration.sql b/src/database/prisma/migrations/20231012151220_add_description_in_user_model/migration.sql new file mode 100644 index 0000000..318edaa --- /dev/null +++ b/src/database/prisma/migrations/20231012151220_add_description_in_user_model/migration.sql @@ -0,0 +1,15 @@ +/* + Warnings: + + - You are about to drop the column `veterinarian` on the `MedicalRecord` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "MedicalRecord" DROP COLUMN "veterinarian", +ADD COLUMN "vetId" TEXT; + +-- AlterTable +ALTER TABLE "User" ADD COLUMN "description" TEXT; + +-- AddForeignKey +ALTER TABLE "MedicalRecord" ADD CONSTRAINT "MedicalRecord_vetId_fkey" FOREIGN KEY ("vetId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/src/database/prisma/migrations/20231013120342_add_data_in_user/migration.sql b/src/database/prisma/migrations/20231013120342_add_data_in_user/migration.sql new file mode 100644 index 0000000..bb6191c --- /dev/null +++ b/src/database/prisma/migrations/20231013120342_add_data_in_user/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - The `status` column on the `PetVaccine` table would be dropped and recreated. This will lead to data loss if there is data in the column. + +*/ +-- CreateEnum +CREATE TYPE "VaccineStatus" AS ENUM ('DONE', 'PENDING', 'NOT_APPLICABLE'); + +-- AlterTable +ALTER TABLE "PetVaccine" DROP COLUMN "status", +ADD COLUMN "status" "VaccineStatus" NOT NULL DEFAULT 'PENDING'; diff --git a/src/database/prisma/migrations/20231029030138_add_created_by_pets/migration.sql b/src/database/prisma/migrations/20231029030138_add_created_by_pets/migration.sql new file mode 100644 index 0000000..894b5d1 --- /dev/null +++ b/src/database/prisma/migrations/20231029030138_add_created_by_pets/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - Added the required column `title` to the `MedicalRecord` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "MedicalRecord" ADD COLUMN "title" TEXT NOT NULL; + +-- AlterTable +ALTER TABLE "Pet" ADD COLUMN "createdBy" TEXT; diff --git a/src/database/prisma/migrations/20231029205130_add_qrcode_in_pet/migration.sql b/src/database/prisma/migrations/20231029205130_add_qrcode_in_pet/migration.sql new file mode 100644 index 0000000..878e21b --- /dev/null +++ b/src/database/prisma/migrations/20231029205130_add_qrcode_in_pet/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Pet" ADD COLUMN "qrCode" TEXT; diff --git a/src/database/prisma/schema.prisma b/src/database/prisma/schema.prisma index 92d46b0..114b4a7 100644 --- a/src/database/prisma/schema.prisma +++ b/src/database/prisma/schema.prisma @@ -20,6 +20,7 @@ model User { locationId String? location Location? @relation(fields: [locationId], references: [id]) image String? + description String? username String? @unique email String @unique updatedAt DateTime @updatedAt @@ -60,8 +61,10 @@ model Pet { age String // Baby, Young, Adult, Senior createdAt DateTime @default(now()) updatedAt DateTime @updatedAt + qrCode String? adoptedBy String? // Foreign Key for Adopter shelterId String? // Foreign Key for Shelter + createdBy String? // Foreign Key for Volunteer location Location? @relation(fields: [locationId], references: [id]) Adopter User? @relation(name: "AdoptedPets", fields: [adoptedBy], references: [id]) Shelter User? @relation(name: "ShelterPets", fields: [shelterId], references: [id]) @@ -81,12 +84,12 @@ model PetsCaredByVolunteer { } model PetVaccine { - id String @id @default(cuid()) - petId String @map("pet_id") - vaccineId String @map("vaccine_id") - status String // NOT_APPLICABLE, PENDING, DONE - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + petId String @map("pet_id") + vaccineId String @map("vaccine_id") + status VaccineStatus @default(PENDING) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt Pet Pet @relation(fields: [petId], references: [id]) Vaccine Vaccine @relation(fields: [vaccineId], references: [id]) @@ -107,6 +110,7 @@ model MedicalRecord { petId String Pet Pet @relation(fields: [petId], references: [id]) date DateTime @default(now()) + title String // Title of the visit/appointment description String // Brief description or reason for the visit/appointment diagnosis String? // Diagnosis made by the veterinarian treatment String? // Treatment provided or prescribed @@ -152,3 +156,9 @@ enum Role { SHELTER ADMIN } + +enum VaccineStatus { + DONE + PENDING + NOT_APPLICABLE +} diff --git a/src/database/prisma/seed.ts b/src/database/prisma/seed.ts index efe49c2..57a1ab0 100644 --- a/src/database/prisma/seed.ts +++ b/src/database/prisma/seed.ts @@ -1,10 +1,13 @@ -import { PrismaClient } from '@prisma/client' +import { PrismaClient, VaccineStatus } from '@prisma/client' import { createPets } from './utils/createPets' import { createVaccines } from './utils/createVaccunes' +import { createUsers } from './utils/createUsers' const prisma = new PrismaClient() async function main() { + await createUsers() + await createPets() const pets = await prisma.pet.findMany({}) @@ -28,19 +31,23 @@ async function main() { const catVaccineData = catVaccineId.map((vaccineId) => ({ vaccineId: vaccineId, - status: 'pending', + status: VaccineStatus.PENDING, })) const dogsVaccineData = dogVaccineId.map((vaccineId) => ({ vaccineId: vaccineId, - status: 'pending', + status: VaccineStatus.PENDING, })) for (let i = 0; i < pets.length; i++) { if (pets[i].category === 'cat') { for (let i = 0; i < catVaccineData.length; i++) { await prisma.petVaccine.create({ - data: { ...catVaccineData[i], petId: pets[i].id }, + data: { + petId: pets[i].id, + status: catVaccineData[i].status, + vaccineId: catVaccineData[i].vaccineId, + }, }) } } @@ -50,7 +57,11 @@ async function main() { if (pets[i].category === 'dog') { for (let i = 0; i < dogsVaccineData.length; i++) { await prisma.petVaccine.create({ - data: { ...dogsVaccineData[i], petId: pets[i].id }, + data: { + vaccineId: dogsVaccineData[i].vaccineId, + status: dogsVaccineData[i].status, + petId: pets[i].id, + }, }) } } diff --git a/src/database/prisma/utils/createPets.ts b/src/database/prisma/utils/createPets.ts index 3567f19..ba4a144 100644 --- a/src/database/prisma/utils/createPets.ts +++ b/src/database/prisma/utils/createPets.ts @@ -59,6 +59,18 @@ export const createPets = async () => { const petDescription = `He boasts tufted ears, a hallmark of his breed, and a long, bushy tail reminiscent of a luxurious feather duster. With a weight tipping the scale at 18 pounds, he is undeniably large but wears his size with elegance and grace. He possesses a gentle disposition, often seeking the warmth of a human lap or the soft notes of a lullaby sung by his favorite humans. Despite his calm demeanor, Whiskers has a playful side. He's particularly fond of feather toys and laser pointers, often displaying the agility and stealth of a panther when engaged in play.` + const usersShelter = await prisma.user.findMany({ + where: { + role: 'SHELTER', + }, + }) + + const usersAdopter = await prisma.user.findMany({ + where: { + role: 'ADOPTER', + }, + }) + for (let i = 0; i < 14; i++) { const randomTypeIndex = Math.floor(Math.random() * category.length) const randomBreedIndex = Math.floor(Math.random() * breed.length) @@ -66,6 +78,12 @@ export const createPets = async () => { const randomGenderIndex = Math.floor(Math.random() * gender.length) const randomSizeIndex = Math.floor(Math.random() * size.length) const randomPetNamesIndex = Math.floor(Math.random() * petNames.length) + const randomUserAdopterIndex = Math.floor( + Math.random() * usersAdopter.length, + ) + const randomUserShelterIndex = Math.floor( + Math.random() * usersShelter.length, + ) await prisma.pet.create({ data: { @@ -77,6 +95,8 @@ export const createPets = async () => { gender: gender[randomGenderIndex], size: size[randomSizeIndex], description: petDescription, + adoptedBy: usersAdopter[randomUserAdopterIndex].id, + shelterId: usersShelter[randomUserShelterIndex].id, }, }) } diff --git a/src/database/prisma/utils/createUsers.ts b/src/database/prisma/utils/createUsers.ts new file mode 100644 index 0000000..ec6b9d9 --- /dev/null +++ b/src/database/prisma/utils/createUsers.ts @@ -0,0 +1,144 @@ +import { Role } from '@prisma/client' +import { prisma } from '../../prisma' + +export const createUsers = async () => { + const randomFirstNameIndex = Math.floor(Math.random() * firstNames.length) + const randonLastNameIndex = Math.floor(Math.random() * lastNames.length) + const randonImageIndex = Math.floor(Math.random() * userImages.length) + + await prisma.user.createMany({ + data: [ + { + firstName: firstNames[randomFirstNameIndex], + email: `${firstNames[randomFirstNameIndex]}1@example33.com`, + image: userImages[randonImageIndex], + lastName: lastNames[randonLastNameIndex], + role: userRoles[0], + socialMedia: { + whatsapp: 'https://wa.me/1112412412', + facebook: 'https://www.facebook.com/', + instagram: 'https://www.instagram.com/', + twitter: 'https://twitter.com/', + }, + description: + 'Our shelter is a 501(c)3 non-profit organization dedicated to rescuing homeless, abandoned, abused, and neglected dogs and cats. We are a volunteer-driven group with a number of years of experience with animals and rescue. We have adopted out over 1000 animals since 2013. We are committed to saving as many homeless animals as we can and finding them forever homes.', + }, + { + firstName: firstNames[randomFirstNameIndex], + email: `${firstNames[randomFirstNameIndex]}2@example33.com`, + image: userImages[randonImageIndex], + role: userRoles[1], + lastName: lastNames[randonLastNameIndex], + socialMedia: { + whatsapp: 'https://wa.me/1112412412', + facebook: 'https://www.facebook.com/', + instagram: 'https://www.instagram.com/', + twitter: 'https://twitter.com/', + }, + description: + 'Our shelter is a 501(c)3 non-profit organization dedicated to rescuing homeless, abandoned, abused, and neglected dogs and cats. We are a volunteer-driven group with a number of years of experience with animals and rescue. We have adopted out over 1000 animals since 2013. We are committed to saving as many homeless animals as we can and finding them forever homes.', + }, + { + firstName: firstNames[randomFirstNameIndex], + email: `${firstNames[randomFirstNameIndex]}5@example33.com`, + image: userImages[randonImageIndex], + lastName: lastNames[randonLastNameIndex], + role: userRoles[3] as any, + socialMedia: { + whatsapp: 'https://wa.me/1112412412', + facebook: 'https://www.facebook.com/', + instagram: 'https://www.instagram.com/', + twitter: 'https://twitter.com/', + }, + description: + 'Our shelter is a 501(c)3 non-profit organization dedicated to rescuing homeless, abandoned, abused, and neglected dogs and cats. We are a volunteer-driven group with a number of years of experience with animals and rescue. We have adopted out over 1000 animals since 2013. We are committed to saving as many homeless animals as we can and finding them forever homes.', + }, + { + firstName: firstNames[randomFirstNameIndex], + email: `${firstNames[randomFirstNameIndex]}3@example33.com`, + image: userImages[randonImageIndex], + lastName: lastNames[randonLastNameIndex], + role: userRoles[2] as any, + socialMedia: { + whatsapp: 'https://wa.me/1112412412', + facebook: 'https://www.facebook.com/', + instagram: 'https://www.instagram.com/', + twitter: 'https://twitter.com/', + }, + description: + 'Our shelter is a 501(c)3 non-profit organization dedicated to rescuing homeless, abandoned, abused, and neglected dogs and cats. We are a volunteer-driven group with a number of years of experience with animals and rescue. We have adopted out over 1000 animals since 2013. We are committed to saving as many homeless animals as we can and finding them forever homes.', + }, + { + firstName: firstNames[randomFirstNameIndex], + email: `${firstNames[randomFirstNameIndex]}4@example33.com`, + image: userImages[randonImageIndex], + lastName: lastNames[randonLastNameIndex], + role: userRoles[2] as any, + socialMedia: { + whatsapp: 'https://wa.me/1112412412', + facebook: 'https://www.facebook.com/', + instagram: 'https://www.instagram.com/', + twitter: 'https://twitter.com/', + }, + description: + 'Our shelter is a 501(c)3 non-profit organization dedicated to rescuing homeless, abandoned, abused, and neglected dogs and cats. We are a volunteer-driven group with a number of years of experience with animals and rescue. We have adopted out over 1000 animals since 2013. We are committed to saving as many homeless animals as we can and finding them forever homes.', + }, + ], + }) +} + +const userImages = [ + 'https://cdn.midjourney.com/e3f4523e-2ee8-4b74-a1e3-b009d60a479a/0_1.png', + 'https://cdn.midjourney.com/aa780cf2-1b28-44ed-ba99-b44a30a71ea3/0_3_384_N.webp', + 'https://images.unsplash.com/photo-1633332755192-727a05c4013d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2960&q=80', + 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2787&q=80', + 'https://images.unsplash.com/flagged/photo-1573740144655-bbb6e88fb18a?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2835&q=80', + 'https://images.unsplash.com/photo-1546961329-78bef0414d7c?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2787&q=80', + 'https://images.unsplash.com/photo-1573497019940-1c28c88b4f3e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2787&q=80', + 'https://images.unsplash.com/photo-1639149888905-fb39731f2e6c?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2864&q=80', + 'https://images.unsplash.com/photo-1649123245135-4db6ead931b5?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2825&q=80', + 'https://images.unsplash.com/photo-1619895862022-09114b41f16f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2940&q=80', + 'https://images.unsplash.com/photo-1524250502761-1ac6f2e30d43?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2788&q=80', + 'https://images.unsplash.com/photo-1479936343636-73cdc5aae0c3?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2960&q=80', + 'https://images.unsplash.com/photo-1450297350677-623de575f31c?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2787&q=80', + 'https://images.unsplash.com/photo-1581343401100-2c1daf54cb80?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2852&q=80', + 'https://images.unsplash.com/photo-1517841905240-472988babdf9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2459&q=80', +] + +const userRoles = [Role.ADOPTER, Role.SHELTER, Role.VET, Role.VOLUNTEER] +const firstNames = [ + 'John', + 'Jane', + 'Mike', + 'Susan', + 'Chris', + 'Alex', + 'Sam', + 'Taylor', + 'Jordan', + 'Morgan', +] +const lastNames = [ + 'Doe', + 'Smith', + 'Johnson', + 'Lee', + 'Brown', + 'Williams', + 'Jones', + 'Garcia', + 'Martinez', + 'Rodriguez', +] +const usernames = [ + 'johnD', + 'janeS', + 'mikeJ', + 'susanL', + 'chrisB', + 'alexW', + 'samJ', + 'taylorG', + 'jordanM', + 'morganR', +] diff --git a/src/middlewares/googleCloudUploader.ts b/src/middlewares/googleCloudUploader.ts index 2f097ee..de2daf4 100644 --- a/src/middlewares/googleCloudUploader.ts +++ b/src/middlewares/googleCloudUploader.ts @@ -1,71 +1,49 @@ -import { Storage } from '@google-cloud/storage'; -import path from 'path'; -import { v4 as uuidv4 } from 'uuid'; -import Multer from 'multer'; +import { Request, Response, NextFunction } from 'express' +import Multer from 'multer' +import { saveToLocal } from './utils/saveToLocal' +import { saveToCloud } from './utils/saveToCloud' +import { handleError } from './utils/handleError' -const storage = new Storage({ - keyFilename: path.join('pets-love-398920-e3d0357ac41a.json'), -}); +// ... (other imports or code) -const multer = Multer({ +const multerMemoryStorage = Multer({ storage: Multer.memoryStorage(), limits: { fileSize: 5 * 1024 * 1024 }, // Limit to 5MB -}); - -export const googleCloudUploader = (req: any, res: any, next: Function) => { - const bucketName = process.env.BUCKET_NAME as string; - - multer.single('images')(req, res, async err => { - if (err) { - console.error(err); - res.locals.error = { status: 500, message: 'Something is wrong!', error: err }; - return next(); - } - - if (!req.file) { - res.locals.error = { status: 400, message: 'No file provided!' }; - return next(); - } - - try { - const bucket = storage.bucket(bucketName); - const fileName = `${bucketRoute(req.originalUrl)}/pets-love-${uuidv4()}${Date.now()}`; - const file = bucket.file(fileName); - - const blobStream = file.createWriteStream({ - metadata: { contentType: req.file.mimetype }, - }); - - blobStream.on('error', err => { - console.error(err); - res.locals.error = { status: 500, message: 'Something is wrong!', error: err }; - return next(); - }); - - blobStream.on('finish', async () => { - try { - await file.makePublic(); - - res.locals.file = { url: `${fileName}` }; - return next(); - } catch (err) { - console.error(err); - res.locals.error = { status: 500, message: 'Something is wrong!', error: err }; - return next(); +}) + +/** + * Factory function creating the middleware handling the upload based on the given field name. + * @param fieldName - The name of the field containing the files in the request. + * @returns Express middleware tailored for the specified field. + */ +export const createGoogleCloudUploader = (fieldName: string) => { + // Return the middleware function + return (req: Request, res: Response, next: NextFunction) => { + // Use multer to upload the file. 'array' is used here to signify multiple files. + const upload = multerMemoryStorage.array(fieldName) + + upload(req, res, async (err) => { + if (err) { + return handleError(res, err, 'Something is wrong with the file upload!') + } + console.log(req.files) + + // If no files were uploaded, continue with the next middleware + if (!req.files || req.files.length === 0) { + return next() + } + + // Choose where to save the files based on the environment + try { + if (process.env.DEV === 'true') { + await saveToLocal({ req, res, fieldName }) + } else { + await saveToCloud(req, res, process.env.BUCKET_NAME as string) } - }); - - blobStream.end(req.file.buffer); - } catch (error) { - console.error(error); - res.locals.error = { status: 500, message: 'Something is wrong!', error: error }; - return next(); - } - }); -}; - -const bucketRoute = (path: string) => { - if (path === '/api/v1/user/') return 'users/avatar'; - if (path === '/api/v1/pet/') return 'pets'; - return ''; -}; + next() + } catch (error: any) { + handleError(res, error, 'Error saving the file.') + } + }) + } +} diff --git a/src/middlewares/utils/handleError.ts b/src/middlewares/utils/handleError.ts new file mode 100644 index 0000000..57561d0 --- /dev/null +++ b/src/middlewares/utils/handleError.ts @@ -0,0 +1,10 @@ +export const handleError = ( + res: any, + error: Error, + message: string, + status: number = 500, +) => { + console.error(error) + res.locals.error = { status, message, error } + return +} diff --git a/src/middlewares/utils/saveToCloud.ts b/src/middlewares/utils/saveToCloud.ts new file mode 100644 index 0000000..62805be --- /dev/null +++ b/src/middlewares/utils/saveToCloud.ts @@ -0,0 +1,47 @@ +import { v4 as uuidv4 } from 'uuid' +import { Storage } from '@google-cloud/storage' +import path from 'path' + +const GOOGLE_CLOUD_KEY = 'pets-love-398920-e3d0357ac41a.json' +const storage = new Storage({ keyFilename: path.join(GOOGLE_CLOUD_KEY) }) + +export const saveToCloud = async (req: any, res: any, bucketName: string) => { + const fileName = `${bucketRoute( + req.originalUrl, + )}/pets-love-${uuidv4()}${Date.now()}` + const file = storage.bucket(bucketName).file(fileName) + + const blobStream = file.createWriteStream({ + metadata: { contentType: req.file.mimetype }, + }) + + blobStream.on('error', (err) => handleError(res, err, 'Something is wrong!')) + blobStream.on('finish', async () => { + try { + await file.makePublic() + res.locals.file = { url: fileName } + } catch (err: any) { + handleError(res, err, 'Something is wrong!') + } + }) + blobStream.end(req.file.buffer) +} + +const bucketRoute = (path: string) => { + const routes: { [key: string]: string } = { + '/api/v1/user/': 'users/avatar', + '/api/v1/pets/': 'pets', + } + return routes[path] || '' +} + +const handleError = ( + res: any, + error: Error, + message: string, + status: number = 500, +) => { + console.error(error) + res.locals.error = { status, message, error } + return +} diff --git a/src/middlewares/utils/saveToLocal.ts b/src/middlewares/utils/saveToLocal.ts new file mode 100644 index 0000000..0d3094a --- /dev/null +++ b/src/middlewares/utils/saveToLocal.ts @@ -0,0 +1,52 @@ +import path from 'path' +import { promises as fs } from 'fs' +import { handleError } from './handleError' + +declare global { + namespace Express { + interface Response { + locals: { + file?: { + url?: string + urls?: string[] + } + } + } + } +} + +export const saveToLocal = async ({ + req, + res, + fieldName, +}: { + req: any + res: any + fieldName: string +}) => { + const uploadsDir = path.join(__dirname, '../..', 'uploads') + + try { + await fs.access(uploadsDir) + } catch { + await fs.mkdir(uploadsDir, { recursive: true }) + } + + const urls: string[] = [] + for (const file of req.files) { + const uniqueName = `${Date.now()}-${file.originalname}` + const localFilePath = path.join(uploadsDir, uniqueName) + + try { + await fs.writeFile(localFilePath, file.buffer) + urls.push(uniqueName) + } catch (error: any) { + handleError(res, error, 'Failed to save the image locally.') + } + } + + res.locals.file = + urls.length === 1 + ? { [fieldName]: { url: urls[0] } } + : { [fieldName]: { urls } } +} diff --git a/src/routes/pet.ts b/src/routes/pet.ts index 111f781..5560b5e 100644 --- a/src/routes/pet.ts +++ b/src/routes/pet.ts @@ -3,32 +3,30 @@ import { create, getPet, getPets, - update, deletePet, getPetsUser, getDashboardPets, } from '../useCases/petCases/petController' -// import uploadImage from '../middlewares/awsStorageImage'; import { verifyToken } from '../middlewares/auth' +import { createGoogleCloudUploader } from '../middlewares/googleCloudUploader' +// import { googleCloudUploader } from '../middlewares/googleCloudUploader'\ + +const googleCloudUploade = createGoogleCloudUploader('images') +// const qrCodeGoogleCloudUploade = createGoogleCloudUploader('qrCode') const router = express.Router() -// router.post('/pet', [verifyToken, uploadImage], create); // POST PET +router.post('/pets', [verifyToken, googleCloudUploade], create) // POST PET router.get('/pets', getPets) // GET PETS -router.get('/pets/dashboard', [verifyToken], getDashboardPets) // GET PETS - -// router.get('/pets/searchFilterPets', getSearchFilterPets); // GET PETS - -// router.get('/pets/petsDashboard', [verifyToken], getPetsUserDashboard); // GET PETS DASHBOARD +router.get('/dashboard/pets', [verifyToken], getDashboardPets) // GET PETS +// This route need verify and put in user file router.get('/pets/petsUser', getPetsUser) // GET PETS -router.get('/pet', getPet) // GET PET - -router.delete('/pet/delete', [verifyToken], deletePet) // DELETE PETS +router.get('/pets/:petId', getPet) // GET PET -// router.put('/pet', [verifyToken, uploadImage], update); // PUT PET +router.delete('/pets/:petId', [verifyToken], deletePet) // DELETE PETS export default router diff --git a/src/routes/user.ts b/src/routes/user.ts index 043d278..dffc8a0 100644 --- a/src/routes/user.ts +++ b/src/routes/user.ts @@ -1,27 +1,29 @@ -import express from 'express'; +import express from 'express' import { create, getUser, getUsers, updateUser, updateRole, -} from '../useCases/userCases/userController'; -import { googleCloudUploader } from '../middlewares/googleCloudUploader'; -import { verifyToken } from '../middlewares/auth'; +} from '../useCases/userCases/userController' +import { createGoogleCloudUploader } from '../middlewares/googleCloudUploader' +import { verifyToken } from '../middlewares/auth' -const router = express.Router(); +const uploadImages = createGoogleCloudUploader('images') -router.post('/user/', create); // CREATE USER +const router = express.Router() -router.put('/user/role', [verifyToken], updateRole); // CREATE USER +router.post('/user/', create) // CREATE USER + +router.put('/user/role', [verifyToken], updateRole) // CREATE USER // This route need verifyToke -router.put('/user/', [verifyToken, googleCloudUploader], updateUser); // UPDATE USER +router.put('/user/', [verifyToken, uploadImages], updateUser) // UPDATE USER -router.get('/users/', [verifyToken], getUsers); // GET USERS +router.get('/users/', [verifyToken], getUsers) // GET USERS -router.get('/user/', getUser); // GET USER +router.get('/user/', getUser) // GET USER // router.delete('/user', [verifyToken, verifyRole_Admin], Delete); // DELETE USERS -export default router; +export default router diff --git a/src/services/uploadImage.ts b/src/services/uploadImage.ts new file mode 100644 index 0000000..a7c8292 --- /dev/null +++ b/src/services/uploadImage.ts @@ -0,0 +1,83 @@ +import { Storage } from '@google-cloud/storage' +import path from 'path' +import fs from 'fs/promises' +import { v4 as uuidv4 } from 'uuid' + +const GOOGLE_CLOUD_KEY = 'pets-love-398920-e3d0357ac41a.json' +const storage = new Storage({ keyFilename: path.join(GOOGLE_CLOUD_KEY) }) + +export const uploadImage = async ( + bucketName: string, + buffer: Buffer, + mimeType: string, + originalUrl: string, +): Promise => { + if (process.env.DEV === 'true') { + return saveToLocalBuffer(buffer, mimeType) + } else { + return await saveToCloudBuffer(bucketName, buffer, mimeType, originalUrl) + } +} + +const saveToCloudBuffer = async ( + bucketName: string, + buffer: Buffer, + mimeType: string, + originalUrl: string, +): Promise => { + const fileName = `${bucketRoute( + originalUrl, + )}/pets-love-${uuidv4()}${Date.now()}` + const file = storage.bucket(bucketName).file(fileName) + + return new Promise((resolve, reject) => { + const blobStream = file.createWriteStream({ + metadata: { contentType: mimeType }, + }) + + blobStream.on('error', (err) => reject(err)) + blobStream.on('finish', async () => { + try { + await file.makePublic() + resolve(fileName) + } catch (err) { + reject(err) + } + }) + + blobStream.end(buffer) + }) +} + +const bucketRoute = (path: string) => { + const routes: { [key: string]: string } = { + '/api/v1/user/': 'users/avatar', + '/api/v1/pets/': 'pets', + } + return routes[path] || '' +} + +const saveToLocalBuffer = async ( + buffer: Buffer, + mimeType: string, +): Promise => { + const uploadsDir = path.join(__dirname, '../', 'uploads') + + try { + await fs.access(uploadsDir) + } catch { + await fs.mkdir(uploadsDir, { recursive: true }) + } + + const uniqueName = `${Date.now()}.png` // or dynamically get the extension from mimeType + const localFilePath = path.join(uploadsDir, uniqueName) + + try { + await fs.writeFile(localFilePath, buffer) + return uniqueName + } catch (error) { + throw error + } +} + +// ... rest of the functions (handleError, bucketRoute) remains unchanged. diff --git a/src/useCases/petCases/createPet.ts b/src/useCases/petCases/createPet.ts index 7f4564e..0a5c09e 100644 --- a/src/useCases/petCases/createPet.ts +++ b/src/useCases/petCases/createPet.ts @@ -1,5 +1,9 @@ -import { Response, Request } from 'express'; -import { SOMETHING_IS_WRONG, SUCCESS_RESPONSE } from '../../constants/constants'; +import QRCode, { QRCodeErrorCorrectionLevel } from 'qrcode' +import { Response, Request } from 'express' +import { SOMETHING_IS_WRONG, SUCCESS_RESPONSE } from '../../constants/constants' +import { prisma } from '../../database/prisma' +import { ROLES } from '../../database/constants/roles' +import { uploadImage } from '../../services/uploadImage' //===================================== // CREATE USER = POST @@ -7,18 +11,142 @@ import { SOMETHING_IS_WRONG, SUCCESS_RESPONSE } from '../../constants/constants' export const create = async (req: Request, res: Response) => { try { + const cleanedData = cleanData({ + ...req.body, + }) + + if (res.locals.file?.images.url) + cleanedData.images = [res.locals.file.images.url] + if (res.locals.file?.images.urls) + cleanedData.images = res.locals.file.images.urls + + if (cleanedData.adoptedBy) cleanedData.adopted = true + + cleanedData.createdBy = (req.user as { id: string })?.id + + const user = await prisma.user.findUnique({ + where: { id: (req.user as any)?.id }, + }) + + if (user?.role === ROLES.SHELTER) { + cleanedData.shelterId = user.id + } + + if (user?.role === ROLES.ADOPTER) { + cleanedData.adoptedBy = user.id + } + + const data: any = { ...cleanedData } + + const dogVaccines = await prisma.vaccine.findMany({ + where: { + category: 'dog', + }, + }) + const catVaccines = await prisma.vaccine.findMany({ + where: { + category: 'cat', + }, + }) + + const pet = await prisma.pet.create({ + data, + }) + + console.log(catVaccines) + + if (cleanedData.category === 'dog') { + dogVaccines.map(async (vaccine) => { + await prisma.petVaccine.create({ + data: { + petId: pet.id, + vaccineId: vaccine.id, + }, + }) + }) + } + + if (cleanedData.category === 'cat') { + catVaccines.map(async (vaccine) => { + await prisma.petVaccine.create({ + data: { + petId: pet.id, + vaccineId: vaccine.id, + }, + }) + }) + } + + const qrCodeText = `${process.env.HOST}/pet/${pet.id}` + const qrCode = await generateQRCodeDataURL(qrCodeText) + + const base64Image = qrCode + const base64Data = base64Image.split(',')[1] + const imageBuffer = Buffer.from(base64Data, 'base64') + + const imageUrl = await uploadImage( + process.env.BUCKET_NAME || '', + imageBuffer, + 'image/png', + '/api/v1/pets/', + ) + + const petUpdated = await prisma.pet.update({ + where: { id: pet.id }, + data: { qrCode: imageUrl }, + }) + res.status(201).json({ + pet: petUpdated, ok: true, message: SUCCESS_RESPONSE, - }); + }) } catch (error) { if (error) { - console.log(error); + console.log(error) res.status(500).json({ ok: false, error: Error, message: SOMETHING_IS_WRONG, - }); + }) + } + } +} + +const cleanData = (obj: Record): Record => { + const newObj: Record = {} + + for (let [key, value] of Object.entries(obj)) { + if (value == null || value === '') continue + if (key === 'qrCodeImage') continue + + newObj[key] = + typeof value === 'object' && !Array.isArray(value) + ? cleanData(value) + : value + } + + return newObj +} + +const generateQRCodeDataURL = async (text: string) => { + try { + const options = { + errorCorrectionLevel: 'M' as QRCodeErrorCorrectionLevel, + scale: 60, // Large scale to create a high-resolution image + width: 200, + height: 200, + color: { + dark: '#369683', // QR code dots color + light: '#f3faf8', // Transparent background, in this case white + }, } + + const dataURL = await QRCode.toDataURL(text, options) + + return dataURL + } catch (err) { + console.error(err) + return '' } -}; +} diff --git a/src/useCases/petCases/deletePet.ts b/src/useCases/petCases/deletePet.ts index 0337741..8a54f1a 100644 --- a/src/useCases/petCases/deletePet.ts +++ b/src/useCases/petCases/deletePet.ts @@ -3,14 +3,22 @@ import { SOMETHING_IS_WRONG, SUCCESS_RESPONSE } from '../../constants/constants' import { prisma } from '../../database/prisma' export const deletePet = async (req: Request, res: Response) => { - const { petId, userRole } = req?.query as any try { - if (userRole === 'VOLUNTEER') { - await prisma.petsCaredByVolunteer.deleteMany({ - where: { petId }, + const { petId } = req?.params as any + + const pet = await prisma.pet.findUnique({ + where: { id: petId as string }, + }) + + await prisma.petVaccine.deleteMany({ + where: { petId: petId as string }, + }) + + if (pet?.createdBy !== (req.user as any)?.id) + return res.status(401).json({ + ok: false, + message: 'You can not delete this pet', }) - } - console.log('petId', petId) if (petId) await prisma.pet.delete({ diff --git a/src/useCases/petCases/getDashboardPets.ts b/src/useCases/petCases/getDashboardPets.ts index 7102633..0054e74 100644 --- a/src/useCases/petCases/getDashboardPets.ts +++ b/src/useCases/petCases/getDashboardPets.ts @@ -22,6 +22,8 @@ export const getDashboardPets = async (req: Request, res: Response) => { query = filterBasedOnRole(query, user) query = addFiltersToQuery(query, filter) + query.where.createdBy = user?.id + try { if (user?.role === ROLES.VOLUNTEER) { const volunteerQuery = buildVolunteerQuery(user, filter) diff --git a/src/useCases/petCases/getPets.ts b/src/useCases/petCases/getPets.ts index 75df108..cd9f5b4 100644 --- a/src/useCases/petCases/getPets.ts +++ b/src/useCases/petCases/getPets.ts @@ -84,7 +84,7 @@ export const getPets = async (req: Request, res: Response) => { export const getPet = async (req: Request, res: Response) => { const pet = await prisma.pet.findUnique({ where: { - id: req.query.id as string, + id: req.params.petId as string, }, include: { Shelter: { @@ -92,6 +92,19 @@ export const getPet = async (req: Request, res: Response) => { id: true, username: true, image: true, + description: true, + firstName: true, + location: true, + socialMedia: true, + }, + }, + Adopter: { + select: { + id: true, + username: true, + image: true, + firstName: true, + description: true, location: true, }, }, diff --git a/src/useCases/petCases/utils/getDashboardPets.ts b/src/useCases/petCases/utils/getDashboardPets.ts index 0d44f69..5ffb4b7 100644 --- a/src/useCases/petCases/utils/getDashboardPets.ts +++ b/src/useCases/petCases/utils/getDashboardPets.ts @@ -70,12 +70,9 @@ export const filterBasedOnRole = ( } export const addFiltersToQuery = (query: any, filters: IFilters) => { - const { category, shelterId, adoptedBy, gender, adopted, searchByName } = - filters + const { category, gender, adopted, searchByName } = filters if (category) query.where.category = category - if (shelterId) query.where.shelterId = shelterId - if (adoptedBy) query.where.adoptedBy = adoptedBy if (gender) query.where.gender = gender if (adopted) query.where.adopted = adopted === 'true' if (searchByName) query.where.name = { contains: searchByName } diff --git a/src/useCases/userCases/updateRole.ts b/src/useCases/userCases/updateRole.ts index 5fdb170..b14ee1c 100644 --- a/src/useCases/userCases/updateRole.ts +++ b/src/useCases/userCases/updateRole.ts @@ -1,25 +1,26 @@ -import { Request, Response } from 'express'; -import { SUCCESS_RESPONSE, SOMETHING_IS_WRONG } from '../../constants/constants'; -import { prisma } from '../../database/prisma'; +import { Request, Response } from 'express' +import { SUCCESS_RESPONSE, SOMETHING_IS_WRONG } from '../../constants/constants' +import { prisma } from '../../database/prisma' export const updateRole = async (req: Request, res: Response) => { - const { id } = req.body; - const userToken: { id?: string } = req.user || ''; + const { id } = req.body + const userToken: { id?: string } = req.user || '' if (id !== userToken.id) { - return res.status(401).json({ ok: false, message: 'Unauthorized' }); + return res.status(401).json({ ok: false, message: 'Unauthorized' }) } - console.log(req.body); try { const user = await prisma.user.update({ where: { id }, data: req.body, - }); + }) - return res.status(200).json({ user, ok: true, message: SUCCESS_RESPONSE }); + return res.status(200).json({ user, ok: true, message: SUCCESS_RESPONSE }) } catch (error) { - console.error(error); - return res.status(500).json({ ok: false, message: SOMETHING_IS_WRONG, error }); + console.error(error) + return res + .status(500) + .json({ ok: false, message: SOMETHING_IS_WRONG, error }) } -}; +} diff --git a/src/useCases/userCases/updateUser.ts b/src/useCases/userCases/updateUser.ts index 6544229..1a7dc1a 100644 --- a/src/useCases/userCases/updateUser.ts +++ b/src/useCases/userCases/updateUser.ts @@ -20,7 +20,11 @@ export const updateUser = async (req: Request, res: Response) => { if (!existingUser) return res.status(404).json({ ok: false, message: 'User not found!' }) - if (req.body.deleteFiles && req.body.deleteFiles.includes('pets-love')) + if ( + req.body.deleteFiles && + req.body.deleteFiles.includes('pets-love') && + process.env.DEV !== 'true' + ) await googleCloudDeleted(req.body.deleteFiles) delete req.body.deleteFiles @@ -58,7 +62,7 @@ export const updateUser = async (req: Request, res: Response) => { socialMedia: JSON.stringify(updatedSocialMedia), }) - if (res.locals.file) cleanedData.image = res.locals.file.url + if (res.locals.file) cleanedData.image = res.locals.file.images.url const data = { ...cleanedData } if (updatedLocationId) data.locationId = updatedLocationId diff --git a/yarn.lock b/yarn.lock index 87d02dc..399c14d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -294,6 +294,13 @@ dependencies: "@types/express" "*" +"@types/qrcode@^1.5.4": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.5.4.tgz#13c4f25297841e79908c2fa1f8341a757e87e3e7" + integrity sha512-ufYqUO7wUBq49hugJry+oIYKscvxIQerJSmXeny215aJKfrepN04DDZP8FCgxvV82kOqKPULCE4PIW3qUmZrRA== + dependencies: + "@types/node" "*" + "@types/qs@*": version "6.9.8" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.8.tgz#f2a7de3c107b89b441e071d5472e6b726b4adf45" @@ -669,6 +676,11 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + capture-stack-trace@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.2.tgz#1c43f6b059d4249e7f3f8724f15f048b927d3a8a" @@ -689,6 +701,15 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -886,6 +907,11 @@ debug@^3.1.0: dependencies: ms "^2.1.1" +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -931,6 +957,11 @@ dicer@0.2.5: readable-stream "1.1.x" streamsearch "0.1.2" +dijkstrajs@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz#4c8dbdea1f0f6478bff94d9c49c784d623e4fc23" + integrity sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA== + dot-prop@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.1.tgz#45884194a71fc2cda71cbb4bceb3a4dd2f433ba4" @@ -998,6 +1029,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +encode-utf8@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" + integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -1190,6 +1226,14 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" +find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + follow-redirects@1.5.10: version "1.5.10" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" @@ -1336,7 +1380,7 @@ gcs-resumable-upload@^0.10.2: request "^2.85.0" stream-events "^1.0.3" -get-caller-file@^2.0.5: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -1825,6 +1869,13 @@ jws@^4.0.0: jwa "^2.0.0" safe-buffer "^5.0.1" +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + lodash.flattendeep@^4.0.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" @@ -2203,6 +2254,13 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + p-limit@^3.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" @@ -2210,6 +2268,18 @@ p-limit@^3.0.1: dependencies: yocto-queue "^0.1.0" +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -2236,6 +2306,11 @@ passport@^0.4.1: passport-strategy "1.x.x" pause "0.0.1" +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -2278,6 +2353,11 @@ pify@^4.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +pngjs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" + integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== + prebuild-install@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" @@ -2380,6 +2460,16 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== +qrcode@^1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.3.tgz#03afa80912c0dccf12bc93f615a535aad1066170" + integrity sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg== + dependencies: + dijkstrajs "^1.0.1" + encode-utf8 "^1.0.3" + pngjs "^5.0.0" + yargs "^15.3.1" + qs@6.11.0: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" @@ -2522,6 +2612,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + retry-axios@0.3.2, retry-axios@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/retry-axios/-/retry-axios-0.3.2.tgz#5757c80f585b4cc4c4986aa2ffd47a60c6d35e13" @@ -3080,6 +3175,11 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" +which-module@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" + integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== + which-typed-array@^1.1.11, which-typed-array@^1.1.2: version "1.1.11" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" @@ -3105,6 +3205,15 @@ wide-align@^1.1.2: dependencies: string-width "^1.0.2 || 2 || 3 || 4" +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -3151,6 +3260,11 @@ xtend@^4.0.0, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" @@ -3166,11 +3280,36 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs-parser@^20.2.2: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== +yargs@^15.3.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + yargs@^16.1.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"