├── .env.sample ├── .gitignore ├── .node-version ├── .nvmrc ├── README.md ├── data ├── comment.json ├── post.json └── users.json ├── package-lock.json ├── package.json ├── src ├── config │ ├── corsSetup.ts │ └── db.ts ├── controllers │ ├── auth.controllers.ts │ ├── comment.controller.ts │ ├── post.controller.ts │ └── user.controller.ts ├── helper │ ├── createJWT.ts │ ├── customError.ts │ ├── customRequest.ts │ ├── filterQuery.ts │ ├── hashPassword.ts │ ├── matchPassword.ts │ ├── pagination.ts │ ├── randomHashCode.ts │ └── responseHandler.ts ├── index.ts ├── middlewares │ ├── authorization.ts │ ├── errorHandler.ts │ ├── protect.ts │ └── validator │ │ ├── file │ │ ├── comment.validator.ts │ │ ├── post.validator.ts │ │ └── user.validator.ts │ │ └── validation.ts ├── prisma │ ├── client │ │ └── client.ts │ ├── migrations │ │ ├── 20231112190230_yes │ │ │ └── migration.sql │ │ └── migration_lock.toml │ └── schema.prisma ├── routes │ ├── auth.route.ts │ ├── comment.route.ts │ ├── post.route.ts │ └── user.route.ts ├── secret.ts ├── server.ts ├── types │ └── types.ts └── utils │ └── email │ ├── accountActivationMail.ts │ └── passwordResetMail.ts └── tsconfig.json /.env.sample: -------------------------------------------------------------------------------- 1 | 2 | NODE_ENV = 3 | 4 | SERVER_PORT = 5 | 6 | DATABASE_URL= 7 | 8 | JWT_VERIFY_SECRET_KEY = 9 | VERIFY_JWT_EXPIRE = -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | # Keep environment variables out of version control 3 | .env 4 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 18.18.0 -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18.18.0 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # API create using TypeScript & Prisma 2 | 3 | ## Use NPM Packages 4 | 5 | - Express 6 | - TypeScript 7 | - Ts-node 8 | - Prisma 9 | - Cors 10 | - Dotenv 11 | - Cookie-parser 12 | - Express-async-handler 13 | - Express-validator 14 | - jsonwebtoken 15 | - Bcryptjs 16 | - morgan -D 17 | - nodemon -D 18 | 19 | ## Routes 20 | 21 | #### Post 22 | 23 | - GET : /posts 24 | - POST : /posts 25 | - GET : /posts/userId=id 26 | - GET : /posts/:id 27 | - PUT : /posts/:id 28 | - DELETE : /posts/:id 29 | - POST : /posts/bulk 30 | - DELETE : /posts/bulk 31 | - GET : /posts/:id/user 32 | - GET : /posts/:id/comments 33 | 34 | #### User 35 | 36 | - GET : /users 37 | - POST : /users 38 | - GET : /users/:id 39 | - PUT : /users/:id 40 | - DELETE : /users/:id 41 | - POST : /users/bulk 42 | - DELETE : /users/bulk 43 | 44 | ##### Comments 45 | 46 | - GET : /comments 47 | - POST : /comments 48 | - GET : /comments/:id 49 | - PUT : /comments/:id 50 | - DELETE : /comments/:id 51 | - POST : /comments/bulk 52 | - DELETE : /comments/bulk 53 | 54 | #### Postman testing URL 55 | 56 | Live test with postman 57 | 58 | #### Live Preview Link 59 | 60 | Live Preview 61 | -------------------------------------------------------------------------------- /data/comment.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "postId": 1, 4 | "name": "id labore ex et quam laborum", 5 | "email": "Eliseo@gardner.biz", 6 | "body": "laudantium enim quasi est quidem magnam voluptate ipsam eos\ntempora quo necessitatibus\ndolor quam autem quasi\nreiciendis et nam sapiente accusantium" 7 | }, 8 | { 9 | "postId": 1, 10 | "name": "quo vero reiciendis velit similique earum", 11 | "email": "Jayne_Kuhic@sydney.com", 12 | "body": "est natus enim nihil est dolore omnis voluptatem numquam\net omnis occaecati quod ullam at\nvoluptatem error expedita pariatur\nnihil sint nostrum voluptatem reiciendis et" 13 | }, 14 | { 15 | "postId": 2, 16 | "name": "provident id voluptas", 17 | "email": "Meghan_Littel@rene.us", 18 | "body": "sapiente assumenda molestiae atque\nadipisci laborum distinctio aperiam et ab ut omnis\net occaecati aspernatur odit sit rem expedita\nquas enim ipsam minus" 19 | }, 20 | { 21 | "postId": 2, 22 | "name": "eaque et deleniti atque tenetur ut quo ut", 23 | "email": "Carmen_Keeling@caroline.name", 24 | "body": "voluptate iusto quis nobis reprehenderit ipsum amet nulla\nquia quas dolores velit et non\naut quia necessitatibus\nnostrum quaerat nulla et accusamus nisi facilis" 25 | }, 26 | { 27 | "postId": 3, 28 | "name": "fugit labore quia mollitia quas deserunt nostrum sunt", 29 | "email": "Veronica_Goodwin@timmothy.net", 30 | "body": "ut dolorum nostrum id quia aut est\nfuga est inventore vel eligendi explicabo quis consectetur\naut occaecati repellat id natus quo est\nut blanditiis quia ut vel ut maiores ea" 31 | }, 32 | { 33 | "postId": 5, 34 | "name": "aliquid rerum mollitia qui a consectetur eum sed", 35 | "email": "Noemie@marques.me", 36 | "body": "deleniti aut sed molestias explicabo\ncommodi odio ratione nesciunt\nvoluptate doloremque est\nnam autem error delectus" 37 | }, 38 | { 39 | "postId": 5, 40 | "name": "porro repellendus aut tempore quis hic", 41 | "email": "Khalil@emile.co.uk", 42 | "body": "qui ipsa animi nostrum praesentium voluptatibus odit\nqui non impedit cum qui nostrum aliquid fuga explicabo\nvoluptatem fugit earum voluptas exercitationem temporibus dignissimos distinctio\nesse inventore reprehenderit quidem ut incidunt nihil necessitatibus rerum" 43 | }, 44 | { 45 | "postId": 5, 46 | "name": "quis tempora quidem nihil iste", 47 | "email": "Sophia@arianna.co.uk", 48 | "body": "voluptates provident repellendus iusto perspiciatis ex fugiat ut\nut dolor nam aliquid et expedita voluptate\nsunt vitae illo rerum in quos\nvel eligendi enim quae fugiat est" 49 | }, 50 | { 51 | "postId": 5, 52 | "name": "in tempore eos beatae est", 53 | "email": "Jeffery@juwan.us", 54 | "body": "repudiandae repellat quia\nsequi est dolore explicabo nihil et\net sit et\net praesentium iste atque asperiores tenetur" 55 | }, 56 | { 57 | "postId": 7, 58 | "name": "voluptatum totam vel voluptate omnis", 59 | "email": "Jaeden.Towne@arlene.tv", 60 | "body": "fugit harum quae vero\nlibero unde tempore\nsoluta eaque culpa sequi quibusdam nulla id\net et necessitatibus" 61 | }, 62 | { 63 | "postId": 7, 64 | "name": "omnis nemo sunt ab autem", 65 | "email": "Ethelyn.Schneider@emelia.co.uk", 66 | "body": "omnis temporibus quasi ab omnis\nfacilis et omnis illum quae quasi aut\nminus iure ex rem ut reprehenderit\nin non fugit" 67 | }, 68 | { 69 | "postId": 9, 70 | "name": "hic molestiae et fuga ea maxime quod", 71 | "email": "Marianna_Wilkinson@rupert.io", 72 | "body": "qui sunt commodi\nsint vel optio vitae quis qui non distinctio\nid quasi modi dicta\neos nihil sit inventore est numquam officiis" 73 | }, 74 | { 75 | "postId": 11, 76 | "name": "esse autem dolorum", 77 | "email": "Abigail.OConnell@june.org", 78 | "body": "et enim voluptatem totam laudantium\nimpedit nam labore repellendus enim earum aut\nconsectetur mollitia fugit qui repellat expedita sunt\naut fugiat vel illo quos aspernatur ducimus" 79 | }, 80 | { 81 | "postId": 11, 82 | "name": "maiores alias necessitatibus aut non", 83 | "email": "Laverne_Price@scotty.info", 84 | "body": "a at tempore\nmolestiae odit qui dolores molestias dolorem et\nlaboriosam repudiandae placeat quisquam\nautem aperiam consectetur maiores laboriosam nostrum" 85 | }, 86 | { 87 | "postId": 11, 88 | "name": "culpa eius tempora sit consequatur neque iure deserunt", 89 | "email": "Kenton_Vandervort@friedrich.com", 90 | "body": "et ipsa rem ullam cum pariatur similique quia\ncum ipsam est sed aut inventore\nprovident sequi commodi enim inventore assumenda aut aut\ntempora possimus soluta quia consequatur modi illo" 91 | }, 92 | { 93 | "postId": 11, 94 | "name": "quas pariatur quia a doloribus", 95 | "email": "Hayden_Olson@marianna.me", 96 | "body": "perferendis eaque labore laudantium ut molestiae soluta et\nvero odio non corrupti error pariatur consectetur et\nenim nam quia voluptatum non\nmollitia culpa facere voluptas suscipit veniam" 97 | }, 98 | { 99 | "postId": 12, 100 | "name": "et dolorem corrupti sed molestias", 101 | "email": "Vince_Crist@heidi.biz", 102 | "body": "cum esse odio nihil reiciendis illum quaerat\nest facere quia\noccaecati sit totam fugiat in beatae\nut occaecati unde vitae nihil quidem consequatur" 103 | }, 104 | { 105 | "postId": 15, 106 | "name": "quia sunt dolor dolor suscipit expedita quis", 107 | "email": "Sabrina.Marks@savanah.name", 108 | "body": "quisquam voluptas ut\npariatur eos amet non\nreprehenderit voluptates numquam\nin est voluptatem dicta ipsa qui esse enim" 109 | }, 110 | { 111 | "postId": 15, 112 | "name": "ut quia ipsa repellat sunt et sequi aut est", 113 | "email": "Desmond_Graham@kailee.biz", 114 | "body": "nam qui possimus deserunt\ninventore dignissimos nihil rerum ut consequatur vel architecto\ntenetur recusandae voluptate\nnumquam dignissimos aliquid ut reprehenderit voluptatibus" 115 | }, 116 | { 117 | "postId": 15, 118 | "name": "ut non illum pariatur dolor", 119 | "email": "Gussie_Kunde@sharon.biz", 120 | "body": "non accusamus eum aut et est\naccusantium animi nesciunt distinctio ea quas quisquam\nsit ut voluptatem modi natus sint\nfacilis est qui molestias recusandae nemo" 121 | }, 122 | { 123 | "postId": 15, 124 | "name": "minus laboriosam consequuntur", 125 | "email": "Richard@chelsie.co.uk", 126 | "body": "natus numquam enim asperiores doloremque ullam et\nest molestias doloribus cupiditate labore vitae aut voluptatem\nitaque quos quo consectetur nihil illum veniam\nnostrum voluptatum repudiandae ut" 127 | }, 128 | { 129 | "postId": 17, 130 | "name": "non voluptas cum est quis aut consectetur nam", 131 | "email": "Alexzander_Davis@eduardo.name", 132 | "body": "quisquam incidunt dolores sint qui doloribus provident\nvel cupiditate deleniti alias voluptatem placeat ad\nut dolorem illum unde iure quis libero neque\nea et distinctio id" 133 | }, 134 | { 135 | "postId": 18, 136 | "name": "suscipit est sunt vel illum sint", 137 | "email": "Jacquelyn@krista.info", 138 | "body": "eum culpa debitis sint\neaque quia magni laudantium qui neque voluptas\nvoluptatem qui molestiae vel earum est ratione excepturi\nsit aut explicabo et repudiandae ut perspiciatis" 139 | }, 140 | { 141 | "postId": 19, 142 | "name": "voluptatem unde quos provident ad qui sit et excepturi", 143 | "email": "Juston.Ruecker@scot.tv", 144 | "body": "at ut tenetur rem\nut fuga quis ea magnam alias\naut tempore fugiat laboriosam porro quia iure qui\narchitecto est enim" 145 | }, 146 | { 147 | "postId": 21, 148 | "name": "ipsum a ut", 149 | "email": "Winona_Price@jevon.me", 150 | "body": "totam eum fugiat repellendus\nquae beatae explicabo excepturi iusto et\nrepellat alias iure voluptates consequatur sequi minus\nsed maxime unde" 151 | }, 152 | { 153 | "postId": 22, 154 | "name": "in ipsam vel id impedit possimus eos voluptate", 155 | "email": "Nicholaus@mikayla.ca", 156 | "body": "eveniet fugit qui\nporro eaque dolores eos adipisci dolores ut\nfugit perferendis pariatur\nnumquam et repellat occaecati atque ipsum neque" 157 | }, 158 | { 159 | "postId": 22, 160 | "name": "ut veritatis corporis placeat suscipit consequatur quaerat", 161 | "email": "Kayla@susanna.org", 162 | "body": "at a vel sequi nostrum\nharum nam nihil\ncumque aut in dolore rerum ipsa hic ratione\nrerum cum ratione provident labore ad quisquam repellendus a" 163 | }, 164 | { 165 | "postId": 24, 166 | "name": "quisquam laborum reiciendis aut", 167 | "email": "Alessandra.Nitzsche@stephania.us", 168 | "body": "repudiandae aliquam maxime cupiditate consequatur id\nquas error repellendus\ntotam officia dolorem beatae natus cum exercitationem\nasperiores dolor ea" 169 | }, 170 | { 171 | "postId": 24, 172 | "name": "minus pariatur odit", 173 | "email": "Matteo@marquis.net", 174 | "body": "et omnis consequatur ut\nin suscipit et voluptatem\nanimi at ut\ndolores quos aut numquam esse praesentium aut placeat nam" 175 | }, 176 | { 177 | "postId": 24, 178 | "name": "harum error sit", 179 | "email": "Joshua.Spinka@toby.io", 180 | "body": "iusto sint recusandae placeat atque perferendis sit corporis molestiae\nrem dolor eius id delectus et qui\nsed dolorem reiciendis error ullam corporis delectus\nexplicabo mollitia odit laborum sed itaque deserunt rem dolorem" 181 | }, 182 | { 183 | "postId": 25, 184 | "name": "deleniti quo corporis ullam magni praesentium molestiae", 185 | "email": "Annabelle@cole.com", 186 | "body": "soluta mollitia impedit cumque nostrum tempore aut placeat repellat\nenim adipisci dolores aut ut ratione laboriosam necessitatibus vel\net blanditiis est iste sapiente qui atque repellendus alias\nearum consequuntur quia quasi quia" 187 | }, 188 | { 189 | "postId": 25, 190 | "name": "nihil tempora et reiciendis modi veniam", 191 | "email": "Kacey@jamal.info", 192 | "body": "doloribus veritatis a et quis corrupti incidunt est\nharum maiores impedit et beatae qui velit et aut\nporro sed dignissimos deserunt deleniti\net eveniet voluptas ipsa pariatur rem ducimus" 193 | }, 194 | { 195 | "postId": 26, 196 | "name": "quod est non quia doloribus quam deleniti libero", 197 | "email": "Trevion_Kuphal@bernice.name", 198 | "body": "dicta sit culpa molestiae quasi at voluptate eos\ndolorem perferendis accusamus rerum expedita ipsum quis qui\nquos est deserunt\nrerum fuga qui aliquam in consequatur aspernatur" 199 | }, 200 | { 201 | "postId": 27, 202 | "name": "voluptas quasi sunt laboriosam", 203 | "email": "Emmet@guy.biz", 204 | "body": "rem magnam at voluptatem\naspernatur et et nostrum rerum\ndignissimos eum quibusdam\noptio quod dolores et" 205 | }, 206 | { 207 | "postId": 27, 208 | "name": "unde tenetur vero eum iusto", 209 | "email": "Megane.Fritsch@claude.name", 210 | "body": "ullam harum consequatur est rerum est\nmagni tenetur aperiam et\nrepudiandae et reprehenderit dolorum enim voluptas impedit\neligendi quis necessitatibus in exercitationem voluptatem qui" 211 | }, 212 | { 213 | "postId": 27, 214 | "name": "est adipisci laudantium amet rem asperiores", 215 | "email": "Amya@durward.ca", 216 | "body": "sunt quis iure molestias qui ipsa commodi dolore a\nodio qui debitis earum\nunde ut omnis\ndoloremque corrupti at repellendus earum eum" 217 | }, 218 | { 219 | "postId": 27, 220 | "name": "reiciendis quo est vitae dignissimos libero ut officiis fugiat", 221 | "email": "Jasen_Rempel@willis.org", 222 | "body": "corrupti perspiciatis eligendi\net omnis tempora nobis dolores hic\ndolorum vitae odit\nreiciendis sunt odit qui" 223 | }, 224 | { 225 | "postId": 29, 226 | "name": "dignissimos perspiciatis voluptate quos rem qui temporibus excepturi", 227 | "email": "Ottis@lourdes.org", 228 | "body": "et ullam id eligendi rem sit\noccaecati et delectus in nemo\naut veritatis deserunt aspernatur dolor enim voluptas quos consequatur\nmolestiae temporibus error" 229 | }, 230 | { 231 | "postId": 29, 232 | "name": "cum dolore sit quisquam provident nostrum vitae", 233 | "email": "Estel@newton.ca", 234 | "body": "cumque voluptas quo eligendi sit\nnemo ut ut dolor et cupiditate aut\net voluptatem quia aut maiores quas accusantium expedita quia\nbeatae aut ad quis soluta id dolorum" 235 | }, 236 | { 237 | "postId": 30, 238 | "name": "id saepe numquam est facilis sint enim voluptas voluptatem", 239 | "email": "Ramiro_Kuhn@harmon.biz", 240 | "body": "est non atque eligendi aspernatur quidem earum mollitia\nminima neque nam exercitationem provident eum\nmaxime quo et ut illum sequi aut fuga repudiandae\nsapiente sed ea distinctio molestias illum consequatur libero quidem" 241 | }, 242 | { 243 | "postId": 31, 244 | "name": "ut quas facilis laborum voluptatum consequatur odio voluptate et", 245 | "email": "Cary@taurean.biz", 246 | "body": "quos eos sint voluptatibus similique iusto perferendis omnis voluptas\nearum nulla cumque\ndolorem consequatur officiis quis consequatur aspernatur nihil ullam et\nenim enim unde nihil labore non ducimus" 247 | }, 248 | { 249 | "postId": 33, 250 | "name": "nesciunt quidem veritatis alias odit nisi voluptatem non est", 251 | "email": "Ashlee_Jast@emie.biz", 252 | "body": "sunt totam blanditiis\neum quos fugit et ab rerum nemo\nut iusto architecto\nut et eligendi iure placeat omnis" 253 | }, 254 | { 255 | "postId": 33, 256 | "name": "animi vitae qui aut corrupti neque culpa modi", 257 | "email": "Antwan@lori.ca", 258 | "body": "nulla impedit porro in sed\nvoluptatem qui voluptas et enim beatae\nnobis et sit ipsam aut\nvoluptatem voluptatibus blanditiis officia quod eos omnis earum dolorem" 259 | }, 260 | { 261 | "postId": 33, 262 | "name": "omnis ducimus ab temporibus nobis porro natus deleniti", 263 | "email": "Estelle@valentina.info", 264 | "body": "molestiae dolorem quae rem neque sapiente voluptatum nesciunt cum\nid rerum at blanditiis est accusantium est\neos illo porro ad\nquod repellendus ad et labore fugit dolorum" 265 | }, 266 | { 267 | "postId": 33, 268 | "name": "eius corrupti ea", 269 | "email": "Haylie@gino.name", 270 | "body": "beatae aut ut autem sit officia rerum nostrum\nprovident ratione sed dicta omnis alias commodi rerum expedita\nnon nobis sapiente consectetur odit unde quia\nvoluptas in nihil consequatur doloremque ullam dolorem cum" 271 | }, 272 | { 273 | "postId": 34, 274 | "name": "cupiditate labore omnis consequatur", 275 | "email": "Gideon.Hyatt@jalen.tv", 276 | "body": "amet id deserunt ipsam\ncupiditate distinctio nulla voluptatem\ncum possimus voluptate\nipsum quidem laudantium quos nihil" 277 | }, 278 | { 279 | "postId": 35, 280 | "name": "voluptatibus qui est et", 281 | "email": "Gerda.Reynolds@ceasar.co.uk", 282 | "body": "sed non beatae placeat qui libero nam eaque qui\nquia ut ad doloremque\nsequi unde quidem adipisci debitis autem velit\narchitecto aperiam eos nihil enim dolores veritatis omnis ex" 283 | }, 284 | { 285 | "postId": 35, 286 | "name": "autem totam velit officiis voluptates et ullam rem", 287 | "email": "Alfonzo.Barton@kelley.co.uk", 288 | "body": "culpa non ea\nperspiciatis exercitationem sed natus sit\nenim voluptatum ratione omnis consequuntur provident commodi omnis\nquae odio sit at tempora" 289 | }, 290 | { 291 | "postId": 36, 292 | "name": "ipsam deleniti incidunt repudiandae voluptatem maxime provident non dolores", 293 | "email": "Esther@ford.me", 294 | "body": "quam culpa fugiat\nrerum impedit ratione vel ipsam rem\ncommodi qui rem eum\nitaque officiis omnis ad" 295 | }, 296 | { 297 | "postId": 37, 298 | "name": "similique ut et non laboriosam in eligendi et", 299 | "email": "Milan.Schoen@cortney.io", 300 | "body": "dolor iure corrupti\net eligendi nesciunt voluptatum autem\nconsequatur in sapiente\ndolor voluptas dolores natus iusto qui et in perferendis" 301 | }, 302 | { 303 | "postId": 38, 304 | "name": "voluptas distinctio qui similique quasi voluptatem non sit", 305 | "email": "Yasmin.Prohaska@hanna.co.uk", 306 | "body": "asperiores eaque error sunt ut natus et omnis\nexpedita error iste vitae\nsit alias voluptas voluptatibus quia iusto quia ea\nenim facere est quam ex" 307 | }, 308 | { 309 | "postId": 38, 310 | "name": "maiores iste dolor itaque nemo voluptas", 311 | "email": "Ursula.Kirlin@eino.org", 312 | "body": "et enim necessitatibus velit autem magni voluptas\nat maxime error sunt nobis sit\ndolor beatae harum rerum\ntenetur facere pariatur et perferendis voluptas maiores nihil eaque" 313 | }, 314 | { 315 | "postId": 38, 316 | "name": "quisquam quod quia nihil animi minima facere odit est", 317 | "email": "Nichole_Bartoletti@mozell.me", 318 | "body": "quam magni adipisci totam\nut reprehenderit ut tempore non asperiores repellendus architecto aperiam\ndignissimos est aut reiciendis consectetur voluptate nihil culpa at\nmolestiae labore qui ea" 319 | }, 320 | { 321 | "postId": 38, 322 | "name": "ut iusto asperiores delectus", 323 | "email": "Lottie_Wyman@jasen.biz", 324 | "body": "nostrum excepturi debitis cum\narchitecto iusto laudantium odit aut dolor voluptatem consectetur nulla\nmollitia beatae autem quasi nemo repellendus ut ea et\naut architecto odio cum quod optio" 325 | }, 326 | { 327 | "postId": 38, 328 | "name": "dignissimos voluptatibus libero", 329 | "email": "Dominique_Hermann@paige.ca", 330 | "body": "laudantium vero similique eum\niure iste culpa praesentium\nmolestias doloremque alias et at doloribus\naperiam natus est illo quo ratione porro excepturi" 331 | }, 332 | { 333 | "postId": 39, 334 | "name": "non ut sunt ut eius autem ipsa eos sapiente", 335 | "email": "Alphonso_Rosenbaum@valentin.co.uk", 336 | "body": "aut distinctio iusto autem sit libero deleniti\nest soluta non perferendis illo\neveniet corrupti est sint quae\nsed sunt voluptatem" 337 | }, 338 | { 339 | "postId": 39, 340 | "name": "tempore vel accusantium qui quidem esse ut aut", 341 | "email": "Frank@rosalind.name", 342 | "body": "culpa voluptas quidem eos quis excepturi\nquasi corporis provident enim\nprovident velit ex occaecati deleniti\nid aspernatur fugiat eligendi" 343 | }, 344 | { 345 | "postId": 40, 346 | "name": "totam vel saepe aut qui velit quis", 347 | "email": "Jenifer_Lowe@reuben.ca", 348 | "body": "eum laborum quidem omnis facere harum ducimus dolores quaerat\ncorporis quidem aliquid\nquod aut aut at dolorum aspernatur reiciendis\nexercitationem quasi consectetur asperiores vero blanditiis dolor" 349 | }, 350 | { 351 | "postId": 40, 352 | "name": "non perspiciatis omnis facere rem", 353 | "email": "Cecelia_Nitzsche@marty.com", 354 | "body": "fugit ut laborum provident\nquos provident voluptatibus quam non\nsed accusantium explicabo dolore quia distinctio voluptatibus et\nconsequatur eos qui iure minus eaque praesentium" 355 | }, 356 | { 357 | "postId": 40, 358 | "name": "quod vel enim sit quia ipsa quo dolores", 359 | "email": "Christop_Friesen@jordan.me", 360 | "body": "est veritatis mollitia atque quas et sint et dolor\net ut beatae aut\nmagni corporis dolores voluptatibus optio molestiae enim minus est\nreiciendis facere voluptate rem officia doloribus ut" 361 | } 362 | ] 363 | -------------------------------------------------------------------------------- /data/post.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "userId": 1, 4 | "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", 5 | "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" 6 | }, 7 | { 8 | "userId": 1, 9 | "title": "qui est esse", 10 | "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla" 11 | }, 12 | { 13 | "userId": 1, 14 | "title": "ea molestias quasi exercitationem repellat qui ipsa sit aut", 15 | "body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut" 16 | }, 17 | { 18 | "userId": 1, 19 | "title": "eum et est occaecati", 20 | "body": "ullam et saepe reiciendis voluptatem adipisci\nsit amet autem assumenda provident rerum culpa\nquis hic commodi nesciunt rem tenetur doloremque ipsam iure\nquis sunt voluptatem rerum illo velit" 21 | }, 22 | { 23 | "userId": 1, 24 | "title": "nesciunt quas odio", 25 | "body": "repudiandae veniam quaerat sunt sed\nalias aut fugiat sit autem sed est\nvoluptatem omnis possimus esse voluptatibus quis\nest aut tenetur dolor neque" 26 | }, 27 | { 28 | "userId": 1, 29 | "title": "dolorem eum magni eos aperiam quia", 30 | "body": "ut aspernatur corporis harum nihil quis provident sequi\nmollitia nobis aliquid molestiae\nperspiciatis et ea nemo ab reprehenderit accusantium quas\nvoluptate dolores velit et doloremque molestiae" 31 | }, 32 | { 33 | "userId": 1, 34 | "title": "magnam facilis autem", 35 | "body": "dolore placeat quibusdam ea quo vitae\nmagni quis enim qui quis quo nemo aut saepe\nquidem repellat excepturi ut quia\nsunt ut sequi eos ea sed quas" 36 | }, 37 | { 38 | "userId": 1, 39 | "title": "dolorem dolore est ipsam", 40 | "body": "dignissimos aperiam dolorem qui eum\nfacilis quibusdam animi sint suscipit qui sint possimus cum\nquaerat magni maiores excepturi\nipsam ut commodi dolor voluptatum modi aut vitae" 41 | }, 42 | { 43 | "userId": 1, 44 | "title": "nesciunt iure omnis dolorem tempora et accusantium", 45 | "body": "consectetur animi nesciunt iure dolore\nenim quia ad\nveniam autem ut quam aut nobis\net est aut quod aut provident voluptas autem voluptas" 46 | }, 47 | { 48 | "userId": 1, 49 | "title": "optio molestias id quia eum", 50 | "body": "quo et expedita modi cum officia vel magni\ndoloribus qui repudiandae\nvero nisi sit\nquos veniam quod sed accusamus veritatis error" 51 | }, 52 | { 53 | "userId": 2, 54 | "title": "et ea vero quia laudantium autem", 55 | "body": "delectus reiciendis molestiae occaecati non minima eveniet qui voluptatibus\naccusamus in eum beatae sit\nvel qui neque voluptates ut commodi qui incidunt\nut animi commodi" 56 | }, 57 | { 58 | "userId": 2, 59 | "title": "in quibusdam tempore odit est dolorem", 60 | "body": "itaque id aut magnam\npraesentium quia et ea odit et ea voluptas et\nsapiente quia nihil amet occaecati quia id voluptatem\nincidunt ea est distinctio odio" 61 | }, 62 | { 63 | "userId": 2, 64 | "title": "dolorum ut in voluptas mollitia et saepe quo animi", 65 | "body": "aut dicta possimus sint mollitia voluptas commodi quo doloremque\niste corrupti reiciendis voluptatem eius rerum\nsit cumque quod eligendi laborum minima\nperferendis recusandae assumenda consectetur porro architecto ipsum ipsam" 66 | }, 67 | { 68 | "userId": 2, 69 | "title": "voluptatem eligendi optio", 70 | "body": "fuga et accusamus dolorum perferendis illo voluptas\nnon doloremque neque facere\nad qui dolorum molestiae beatae\nsed aut voluptas totam sit illum" 71 | }, 72 | { 73 | "userId": 2, 74 | "title": "eveniet quod temporibus", 75 | "body": "reprehenderit quos placeat\nvelit minima officia dolores impedit repudiandae molestiae nam\nvoluptas recusandae quis delectus\nofficiis harum fugiat vitae" 76 | }, 77 | { 78 | "userId": 3, 79 | "title": "delectus ullam et corporis nulla voluptas sequi", 80 | "body": "non et quaerat ex quae ad maiores\nmaiores recusandae totam aut blanditiis mollitia quas illo\nut voluptatibus voluptatem\nsimilique nostrum eum" 81 | }, 82 | { 83 | "userId": 3, 84 | "title": "iusto eius quod necessitatibus culpa ea", 85 | "body": "odit magnam ut saepe sed non qui\ntempora atque nihil\naccusamus illum doloribus illo dolor\neligendi repudiandae odit magni similique sed cum maiores" 86 | }, 87 | { 88 | "userId": 3, 89 | "title": "a quo magni similique perferendis", 90 | "body": "alias dolor cumque\nimpedit blanditiis non eveniet odio maxime\nblanditiis amet eius quis tempora quia autem rem\na provident perspiciatis quia" 91 | }, 92 | { 93 | "userId": 4, 94 | "title": "ullam ut quidem id aut vel consequuntur", 95 | "body": "debitis eius sed quibusdam non quis consectetur vitae\nimpedit ut qui consequatur sed aut in\nquidem sit nostrum et maiores adipisci atque\nquaerat voluptatem adipisci repudiandae" 96 | }, 97 | { 98 | "userId": 4, 99 | "title": "doloremque illum aliquid sunt", 100 | "body": "deserunt eos nobis asperiores et hic\nest debitis repellat molestiae optio\nnihil ratione ut eos beatae quibusdam distinctio maiores\nearum voluptates et aut adipisci ea maiores voluptas maxime" 101 | }, 102 | { 103 | "userId": 4, 104 | "title": "qui explicabo molestiae dolorem", 105 | "body": "rerum ut et numquam laborum odit est sit\nid qui sint in\nquasi tenetur tempore aperiam et quaerat qui in\nrerum officiis sequi cumque quod" 106 | }, 107 | { 108 | "userId": 4, 109 | "title": "magnam ut rerum iure", 110 | "body": "ea velit perferendis earum ut voluptatem voluptate itaque iusto\ntotam pariatur in\nnemo voluptatem voluptatem autem magni tempora minima in\nest distinctio qui assumenda accusamus dignissimos officia nesciunt nobis" 111 | }, 112 | { 113 | "userId": 4, 114 | "title": "id nihil consequatur molestias animi provident", 115 | "body": "nisi error delectus possimus ut eligendi vitae\nplaceat eos harum cupiditate facilis reprehenderit voluptatem beatae\nmodi ducimus quo illum voluptas eligendi\net nobis quia fugit" 116 | }, 117 | { 118 | "userId": 5, 119 | "title": "ut voluptatem illum ea doloribus itaque eos", 120 | "body": "voluptates quo voluptatem facilis iure occaecati\nvel assumenda rerum officia et\nillum perspiciatis ab deleniti\nlaudantium repellat ad ut et autem reprehenderit" 121 | }, 122 | { 123 | "userId": 5, 124 | "title": "laborum non sunt aut ut assumenda perspiciatis voluptas", 125 | "body": "inventore ab sint\nnatus fugit id nulla sequi architecto nihil quaerat\neos tenetur in in eum veritatis non\nquibusdam officiis aspernatur cumque aut commodi aut" 126 | }, 127 | { 128 | "userId": 5, 129 | "title": "repellendus qui recusandae incidunt voluptates tenetur qui omnis exercitationem", 130 | "body": "error suscipit maxime adipisci consequuntur recusandae\nvoluptas eligendi et est et voluptates\nquia distinctio ab amet quaerat molestiae et vitae\nadipisci impedit sequi nesciunt quis consectetur" 131 | }, 132 | { 133 | "userId": 6, 134 | "title": "soluta aliquam aperiam consequatur illo quis voluptas", 135 | "body": "sunt dolores aut doloribus\ndolore doloribus voluptates tempora et\ndoloremque et quo\ncum asperiores sit consectetur dolorem" 136 | }, 137 | { 138 | "userId": 7, 139 | "title": "fugiat quod pariatur odit minima", 140 | "body": "officiis error culpa consequatur modi asperiores et\ndolorum assumenda voluptas et vel qui aut vel rerum\nvoluptatum quisquam perspiciatis quia rerum consequatur totam quas\nsequi commodi repudiandae asperiores et saepe a" 141 | }, 142 | { 143 | "userId": 7, 144 | "title": "voluptatem laborum magni", 145 | "body": "sunt repellendus quae\nest asperiores aut deleniti esse accusamus repellendus quia aut\nquia dolorem unde\neum tempora esse dolore" 146 | }, 147 | { 148 | "userId": 8, 149 | "title": "et iusto veniam et illum aut fuga", 150 | "body": "occaecati a doloribus\niste saepe consectetur placeat eum voluptate dolorem et\nqui quo quia voluptas\nrerum ut id enim velit est perferendis" 151 | }, 152 | { 153 | "userId": 8, 154 | "title": "sint hic doloribus consequatur eos non id", 155 | "body": "quam occaecati qui deleniti consectetur\nconsequatur aut facere quas exercitationem aliquam hic voluptas\nneque id sunt ut aut accusamus\nsunt consectetur expedita inventore velit" 156 | }, 157 | { 158 | "userId": 8, 159 | "title": "consequuntur deleniti eos quia temporibus ab aliquid at", 160 | "body": "voluptatem cumque tenetur consequatur expedita ipsum nemo quia explicabo\naut eum minima consequatur\ntempore cumque quae est et\net in consequuntur voluptatem voluptates aut" 161 | }, 162 | { 163 | "userId": 8, 164 | "title": "pariatur consequatur quia magnam autem omnis non amet", 165 | "body": "libero accusantium et et facere incidunt sit dolorem\nnon excepturi qui quia sed laudantium\nquisquam molestiae ducimus est\nofficiis esse molestiae iste et quos" 166 | }, 167 | { 168 | "userId": 8, 169 | "title": "labore in ex et explicabo corporis aut quas", 170 | "body": "ex quod dolorem ea eum iure qui provident amet\nquia qui facere excepturi et repudiandae\nasperiores molestias provident\nminus incidunt vero fugit rerum sint sunt excepturi provident" 171 | }, 172 | { 173 | "userId": 9, 174 | "title": "tempora rem veritatis voluptas quo dolores vero", 175 | "body": "facere qui nesciunt est voluptatum voluptatem nisi\nsequi eligendi necessitatibus ea at rerum itaque\nharum non ratione velit laboriosam quis consequuntur\nex officiis minima doloremque voluptas ut aut" 176 | }, 177 | { 178 | "userId": 9, 179 | "title": "laudantium voluptate suscipit sunt enim enim", 180 | "body": "ut libero sit aut totam inventore sunt\nporro sint qui sunt molestiae\nconsequatur cupiditate qui iste ducimus adipisci\ndolor enim assumenda soluta laboriosam amet iste delectus hic" 181 | }, 182 | { 183 | "userId": 10, 184 | "title": "qui qui voluptates illo iste minima", 185 | "body": "aspernatur expedita soluta quo ab ut similique\nexpedita dolores amet\nsed temporibus distinctio magnam saepe deleniti\nomnis facilis nam ipsum natus sint similique omnis" 186 | }, 187 | { 188 | "userId": 10, 189 | "title": "id minus libero illum nam ad officiis", 190 | "body": "earum voluptatem facere provident blanditiis velit laboriosam\npariatur accusamus odio saepe\ncumque dolor qui a dicta ab doloribus consequatur omnis\ncorporis cupiditate eaque assumenda ad nesciunt" 191 | }, 192 | { 193 | "userId": 10, 194 | "title": "quaerat velit veniam amet cupiditate aut numquam ut sequi", 195 | "body": "in non odio excepturi sint eum\nlabore voluptates vitae quia qui et\ninventore itaque rerum\nveniam non exercitationem delectus aut" 196 | }, 197 | { 198 | "userId": 10, 199 | "title": "quas fugiat ut perspiciatis vero provident", 200 | "body": "eum non blanditiis soluta porro quibusdam voluptas\nvel voluptatem qui placeat dolores qui velit aut\nvel inventore aut cumque culpa explicabo aliquid at\nperspiciatis est et voluptatem dignissimos dolor itaque sit nam" 201 | } 202 | ] 203 | -------------------------------------------------------------------------------- /data/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Md. Rejoyan Islam", 4 | "username": "joy", 5 | "email": "rejoyanislam0014@gmail.com", 6 | "phone": "01568816822", 7 | "gender": "female", 8 | "password": "12345678", 9 | "role": "admin", 10 | "age": 24 11 | }, 12 | { 13 | "name": "Leanne Graham", 14 | "username": "Bret", 15 | "email": "Sincere@april.biz", 16 | "phone": "1-770-736-8031 x56442", 17 | "gender": "female", 18 | "password": "12345678", 19 | "age": 70 20 | }, 21 | { 22 | "name": "Ervin Howell", 23 | "username": "Antonette", 24 | "email": "Shanna@melissa.tv", 25 | "phone": "010-692-6593 x09125", 26 | "gender": "male", 27 | "password": "12345678", 28 | "age": 7 29 | }, 30 | { 31 | "name": "Clementine Bauch", 32 | "username": "Samantha", 33 | "email": "Nathan@yesenia.net", 34 | "phone": "1-463-123-4447", 35 | "gender": "female", 36 | "password": "12345678", 37 | "age": 68 38 | }, 39 | { 40 | "name": "Patricia Lebsack", 41 | "username": "Karianne", 42 | "email": "Julianne.OConner@kory.org", 43 | "phone": "493-170-9623 x156", 44 | "gender": "female", 45 | "password": "12345678", 46 | "age": 86 47 | }, 48 | { 49 | "name": "Chelsey Dietrich", 50 | "username": "Kamren", 51 | "email": "Lucio_Hettinger@annie.ca", 52 | "phone": "(254)954-1289", 53 | "gender": "female", 54 | "password": "12345678", 55 | "age": 48 56 | }, 57 | { 58 | "name": "Mrs. Dennis Schulist", 59 | "username": "Leopoldo_Corkery", 60 | "email": "Karley_Dach@jasper.info", 61 | "phone": "1-477-935-8478 x6430", 62 | "gender": "male", 63 | "password": "12345678", 64 | "age": 43 65 | }, 66 | { 67 | "name": "Kurtis Weissnat", 68 | "username": "Elwyn.Skiles", 69 | "email": "Telly.Hoeger@billy.biz", 70 | "phone": "210.067.6132", 71 | "gender": "male", 72 | "password": "12345678", 73 | "age": 25 74 | }, 75 | { 76 | "name": "Nicholas Runolfsdottir V", 77 | "username": "Maxime_Nienow", 78 | "email": "Sherwood@rosamond.me", 79 | "phone": "586.493.6943 x140", 80 | "gender": "male", 81 | "password": "12345678", 82 | "age": 88 83 | }, 84 | { 85 | "name": "Glenna Reichert", 86 | "username": "Delphine", 87 | "email": "Chaim_McDermott@dana.io", 88 | "phone": "(775)976-6794 x41206", 89 | "gender": "male", 90 | "password": "12345678", 91 | "age": 39 92 | }, 93 | { 94 | "name": "Clementina DuBuque", 95 | "username": "Moriah.Stanton", 96 | "email": "Rey.Padberg@karina.biz", 97 | "phone": "024-648-3804", 98 | "gender": "male", 99 | "password": "12345678", 100 | "age": 97 101 | } 102 | ] 103 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-with-typescript", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "api-with-typescript", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@prisma/client": "^5.5.2", 13 | "@types/express": "^4.17.20", 14 | "@types/node": "^20.8.9", 15 | "bcryptjs": "^2.4.3", 16 | "cookie-parser": "^1.4.6", 17 | "cors": "^2.8.5", 18 | "dotenv": "^16.3.1", 19 | "express": "^4.18.2", 20 | "express-async-handler": "^1.2.0", 21 | "express-validator": "^7.0.1", 22 | "http-errors": "^2.0.0", 23 | "jsonwebtoken": "^9.0.2", 24 | "nodemailer": "^6.9.7", 25 | "prisma": "^5.5.2", 26 | "randomstring": "^1.3.0", 27 | "ts-node": "^10.9.1", 28 | "typescript": "^5.2.2" 29 | }, 30 | "devDependencies": { 31 | "@types/bcryptjs": "^2.4.5", 32 | "@types/cookie-parser": "^1.4.5", 33 | "@types/cors": "^2.8.15", 34 | "@types/jsonwebtoken": "^9.0.4", 35 | "@types/morgan": "^1.9.7", 36 | "@types/nodemailer": "^6.4.14", 37 | "@types/randomstring": "^1.1.11", 38 | "colors": "^1.4.0", 39 | "morgan": "^1.10.0", 40 | "nodemon": "^3.0.1" 41 | }, 42 | "engines": { 43 | "node": ">=18.18.0 <19.0.0" 44 | } 45 | }, 46 | "node_modules/@cspotcode/source-map-support": { 47 | "version": "0.8.1", 48 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 49 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 50 | "dependencies": { 51 | "@jridgewell/trace-mapping": "0.3.9" 52 | }, 53 | "engines": { 54 | "node": ">=12" 55 | } 56 | }, 57 | "node_modules/@jridgewell/resolve-uri": { 58 | "version": "3.1.1", 59 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", 60 | "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", 61 | "engines": { 62 | "node": ">=6.0.0" 63 | } 64 | }, 65 | "node_modules/@jridgewell/sourcemap-codec": { 66 | "version": "1.4.15", 67 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 68 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" 69 | }, 70 | "node_modules/@jridgewell/trace-mapping": { 71 | "version": "0.3.9", 72 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 73 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 74 | "dependencies": { 75 | "@jridgewell/resolve-uri": "^3.0.3", 76 | "@jridgewell/sourcemap-codec": "^1.4.10" 77 | } 78 | }, 79 | "node_modules/@prisma/client": { 80 | "version": "5.5.2", 81 | "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.5.2.tgz", 82 | "integrity": "sha512-54XkqR8M+fxbzYqe+bIXimYnkkcGqgOh0dn0yWtIk6CQT4IUCAvNFNcQZwk2KqaLU+/1PHTSWrcHtx4XjluR5w==", 83 | "hasInstallScript": true, 84 | "dependencies": { 85 | "@prisma/engines-version": "5.5.1-1.aebc046ce8b88ebbcb45efe31cbe7d06fd6abc0a" 86 | }, 87 | "engines": { 88 | "node": ">=16.13" 89 | }, 90 | "peerDependencies": { 91 | "prisma": "*" 92 | }, 93 | "peerDependenciesMeta": { 94 | "prisma": { 95 | "optional": true 96 | } 97 | } 98 | }, 99 | "node_modules/@prisma/engines": { 100 | "version": "5.5.2", 101 | "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.5.2.tgz", 102 | "integrity": "sha512-Be5hoNF8k+lkB3uEMiCHbhbfF6aj1GnrTBnn5iYFT7GEr3TsOEp1soviEcBR0tYCgHbxjcIxJMhdbvxALJhAqg==", 103 | "hasInstallScript": true 104 | }, 105 | "node_modules/@prisma/engines-version": { 106 | "version": "5.5.1-1.aebc046ce8b88ebbcb45efe31cbe7d06fd6abc0a", 107 | "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.5.1-1.aebc046ce8b88ebbcb45efe31cbe7d06fd6abc0a.tgz", 108 | "integrity": "sha512-O+qHFnZvAyOFk1tUco2/VdiqS0ym42a3+6CYLScllmnpbyiTplgyLt2rK/B9BTjYkSHjrgMhkG47S0oqzdIckA==" 109 | }, 110 | "node_modules/@tsconfig/node10": { 111 | "version": "1.0.9", 112 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 113 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" 114 | }, 115 | "node_modules/@tsconfig/node12": { 116 | "version": "1.0.11", 117 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 118 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" 119 | }, 120 | "node_modules/@tsconfig/node14": { 121 | "version": "1.0.3", 122 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 123 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" 124 | }, 125 | "node_modules/@tsconfig/node16": { 126 | "version": "1.0.4", 127 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", 128 | "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" 129 | }, 130 | "node_modules/@types/bcryptjs": { 131 | "version": "2.4.5", 132 | "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.5.tgz", 133 | "integrity": "sha512-tOF6TivOIvq+TWQm78335CMdyVJhpBG3NUdWQDAp95ax4E2rSKbws/ELHLk5EBoucwx/tHt3/hhLOHwWJgVrSw==", 134 | "dev": true 135 | }, 136 | "node_modules/@types/body-parser": { 137 | "version": "1.19.4", 138 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", 139 | "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==", 140 | "dependencies": { 141 | "@types/connect": "*", 142 | "@types/node": "*" 143 | } 144 | }, 145 | "node_modules/@types/connect": { 146 | "version": "3.4.37", 147 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz", 148 | "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==", 149 | "dependencies": { 150 | "@types/node": "*" 151 | } 152 | }, 153 | "node_modules/@types/cookie-parser": { 154 | "version": "1.4.5", 155 | "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.5.tgz", 156 | "integrity": "sha512-cbpH1NldYslPt7WRHXZFm+G7DTfUg57dQSCf1qrHwT8wtGX41JHLYf3Cieiqg7waPWjorVgcSSllZov+A1PJbg==", 157 | "dev": true, 158 | "dependencies": { 159 | "@types/express": "*" 160 | } 161 | }, 162 | "node_modules/@types/cors": { 163 | "version": "2.8.15", 164 | "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.15.tgz", 165 | "integrity": "sha512-n91JxbNLD8eQIuXDIChAN1tCKNWCEgpceU9b7ZMbFA+P+Q4yIeh80jizFLEvolRPc1ES0VdwFlGv+kJTSirogw==", 166 | "dev": true, 167 | "dependencies": { 168 | "@types/node": "*" 169 | } 170 | }, 171 | "node_modules/@types/express": { 172 | "version": "4.17.20", 173 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", 174 | "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==", 175 | "dependencies": { 176 | "@types/body-parser": "*", 177 | "@types/express-serve-static-core": "^4.17.33", 178 | "@types/qs": "*", 179 | "@types/serve-static": "*" 180 | } 181 | }, 182 | "node_modules/@types/express-serve-static-core": { 183 | "version": "4.17.39", 184 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz", 185 | "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==", 186 | "dependencies": { 187 | "@types/node": "*", 188 | "@types/qs": "*", 189 | "@types/range-parser": "*", 190 | "@types/send": "*" 191 | } 192 | }, 193 | "node_modules/@types/http-errors": { 194 | "version": "2.0.3", 195 | "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", 196 | "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==" 197 | }, 198 | "node_modules/@types/jsonwebtoken": { 199 | "version": "9.0.4", 200 | "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.4.tgz", 201 | "integrity": "sha512-8UYapdmR0QlxgvJmyE8lP7guxD0UGVMfknsdtCFZh4ovShdBl3iOI4zdvqBHrB/IS+xUj3PSx73Qkey1fhWz+g==", 202 | "dev": true, 203 | "dependencies": { 204 | "@types/node": "*" 205 | } 206 | }, 207 | "node_modules/@types/mime": { 208 | "version": "1.3.4", 209 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", 210 | "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==" 211 | }, 212 | "node_modules/@types/morgan": { 213 | "version": "1.9.7", 214 | "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.7.tgz", 215 | "integrity": "sha512-4sJFBUBrIZkP5EvMm1L6VCXp3SQe8dnXqlVpe1jsmTjS1JQVmSjnpMNs8DosQd6omBi/K7BSKJ6z/Mc3ki0K9g==", 216 | "dev": true, 217 | "dependencies": { 218 | "@types/node": "*" 219 | } 220 | }, 221 | "node_modules/@types/node": { 222 | "version": "20.8.9", 223 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz", 224 | "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==", 225 | "dependencies": { 226 | "undici-types": "~5.26.4" 227 | } 228 | }, 229 | "node_modules/@types/nodemailer": { 230 | "version": "6.4.14", 231 | "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.14.tgz", 232 | "integrity": "sha512-fUWthHO9k9DSdPCSPRqcu6TWhYyxTBg382vlNIttSe9M7XfsT06y0f24KHXtbnijPGGRIcVvdKHTNikOI6qiHA==", 233 | "dev": true, 234 | "dependencies": { 235 | "@types/node": "*" 236 | } 237 | }, 238 | "node_modules/@types/qs": { 239 | "version": "6.9.9", 240 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", 241 | "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==" 242 | }, 243 | "node_modules/@types/randomstring": { 244 | "version": "1.1.11", 245 | "resolved": "https://registry.npmjs.org/@types/randomstring/-/randomstring-1.1.11.tgz", 246 | "integrity": "sha512-j3y9mKzGyYN5PHWjRv8Ah/ieZlApRbfSb0rBWVrW9Z+z5N1xjZpbBxgVKbO7WAGfnMpvLG2Gg8gRPxo+f0JWmg==", 247 | "dev": true 248 | }, 249 | "node_modules/@types/range-parser": { 250 | "version": "1.2.6", 251 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", 252 | "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==" 253 | }, 254 | "node_modules/@types/send": { 255 | "version": "0.17.3", 256 | "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", 257 | "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", 258 | "dependencies": { 259 | "@types/mime": "^1", 260 | "@types/node": "*" 261 | } 262 | }, 263 | "node_modules/@types/serve-static": { 264 | "version": "1.15.4", 265 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz", 266 | "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==", 267 | "dependencies": { 268 | "@types/http-errors": "*", 269 | "@types/mime": "*", 270 | "@types/node": "*" 271 | } 272 | }, 273 | "node_modules/abbrev": { 274 | "version": "1.1.1", 275 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 276 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", 277 | "dev": true 278 | }, 279 | "node_modules/accepts": { 280 | "version": "1.3.8", 281 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 282 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 283 | "dependencies": { 284 | "mime-types": "~2.1.34", 285 | "negotiator": "0.6.3" 286 | }, 287 | "engines": { 288 | "node": ">= 0.6" 289 | } 290 | }, 291 | "node_modules/acorn": { 292 | "version": "8.10.0", 293 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", 294 | "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", 295 | "bin": { 296 | "acorn": "bin/acorn" 297 | }, 298 | "engines": { 299 | "node": ">=0.4.0" 300 | } 301 | }, 302 | "node_modules/acorn-walk": { 303 | "version": "8.2.0", 304 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 305 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 306 | "engines": { 307 | "node": ">=0.4.0" 308 | } 309 | }, 310 | "node_modules/anymatch": { 311 | "version": "3.1.3", 312 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 313 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 314 | "dev": true, 315 | "dependencies": { 316 | "normalize-path": "^3.0.0", 317 | "picomatch": "^2.0.4" 318 | }, 319 | "engines": { 320 | "node": ">= 8" 321 | } 322 | }, 323 | "node_modules/arg": { 324 | "version": "4.1.3", 325 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 326 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" 327 | }, 328 | "node_modules/array-flatten": { 329 | "version": "1.1.1", 330 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 331 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 332 | }, 333 | "node_modules/balanced-match": { 334 | "version": "1.0.2", 335 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 336 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 337 | "dev": true 338 | }, 339 | "node_modules/basic-auth": { 340 | "version": "2.0.1", 341 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", 342 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", 343 | "dev": true, 344 | "dependencies": { 345 | "safe-buffer": "5.1.2" 346 | }, 347 | "engines": { 348 | "node": ">= 0.8" 349 | } 350 | }, 351 | "node_modules/basic-auth/node_modules/safe-buffer": { 352 | "version": "5.1.2", 353 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 354 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 355 | "dev": true 356 | }, 357 | "node_modules/bcryptjs": { 358 | "version": "2.4.3", 359 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", 360 | "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" 361 | }, 362 | "node_modules/binary-extensions": { 363 | "version": "2.2.0", 364 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 365 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 366 | "dev": true, 367 | "engines": { 368 | "node": ">=8" 369 | } 370 | }, 371 | "node_modules/body-parser": { 372 | "version": "1.20.1", 373 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", 374 | "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", 375 | "dependencies": { 376 | "bytes": "3.1.2", 377 | "content-type": "~1.0.4", 378 | "debug": "2.6.9", 379 | "depd": "2.0.0", 380 | "destroy": "1.2.0", 381 | "http-errors": "2.0.0", 382 | "iconv-lite": "0.4.24", 383 | "on-finished": "2.4.1", 384 | "qs": "6.11.0", 385 | "raw-body": "2.5.1", 386 | "type-is": "~1.6.18", 387 | "unpipe": "1.0.0" 388 | }, 389 | "engines": { 390 | "node": ">= 0.8", 391 | "npm": "1.2.8000 || >= 1.4.16" 392 | } 393 | }, 394 | "node_modules/brace-expansion": { 395 | "version": "1.1.11", 396 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 397 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 398 | "dev": true, 399 | "dependencies": { 400 | "balanced-match": "^1.0.0", 401 | "concat-map": "0.0.1" 402 | } 403 | }, 404 | "node_modules/braces": { 405 | "version": "3.0.2", 406 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 407 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 408 | "dev": true, 409 | "dependencies": { 410 | "fill-range": "^7.0.1" 411 | }, 412 | "engines": { 413 | "node": ">=8" 414 | } 415 | }, 416 | "node_modules/buffer-equal-constant-time": { 417 | "version": "1.0.1", 418 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 419 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" 420 | }, 421 | "node_modules/bytes": { 422 | "version": "3.1.2", 423 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 424 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 425 | "engines": { 426 | "node": ">= 0.8" 427 | } 428 | }, 429 | "node_modules/call-bind": { 430 | "version": "1.0.5", 431 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", 432 | "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", 433 | "dependencies": { 434 | "function-bind": "^1.1.2", 435 | "get-intrinsic": "^1.2.1", 436 | "set-function-length": "^1.1.1" 437 | }, 438 | "funding": { 439 | "url": "https://github.com/sponsors/ljharb" 440 | } 441 | }, 442 | "node_modules/chokidar": { 443 | "version": "3.5.3", 444 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 445 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 446 | "dev": true, 447 | "funding": [ 448 | { 449 | "type": "individual", 450 | "url": "https://paulmillr.com/funding/" 451 | } 452 | ], 453 | "dependencies": { 454 | "anymatch": "~3.1.2", 455 | "braces": "~3.0.2", 456 | "glob-parent": "~5.1.2", 457 | "is-binary-path": "~2.1.0", 458 | "is-glob": "~4.0.1", 459 | "normalize-path": "~3.0.0", 460 | "readdirp": "~3.6.0" 461 | }, 462 | "engines": { 463 | "node": ">= 8.10.0" 464 | }, 465 | "optionalDependencies": { 466 | "fsevents": "~2.3.2" 467 | } 468 | }, 469 | "node_modules/colors": { 470 | "version": "1.4.0", 471 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 472 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", 473 | "dev": true, 474 | "engines": { 475 | "node": ">=0.1.90" 476 | } 477 | }, 478 | "node_modules/concat-map": { 479 | "version": "0.0.1", 480 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 481 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 482 | "dev": true 483 | }, 484 | "node_modules/content-disposition": { 485 | "version": "0.5.4", 486 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 487 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 488 | "dependencies": { 489 | "safe-buffer": "5.2.1" 490 | }, 491 | "engines": { 492 | "node": ">= 0.6" 493 | } 494 | }, 495 | "node_modules/content-type": { 496 | "version": "1.0.5", 497 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 498 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 499 | "engines": { 500 | "node": ">= 0.6" 501 | } 502 | }, 503 | "node_modules/cookie": { 504 | "version": "0.5.0", 505 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", 506 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", 507 | "engines": { 508 | "node": ">= 0.6" 509 | } 510 | }, 511 | "node_modules/cookie-parser": { 512 | "version": "1.4.6", 513 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", 514 | "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", 515 | "dependencies": { 516 | "cookie": "0.4.1", 517 | "cookie-signature": "1.0.6" 518 | }, 519 | "engines": { 520 | "node": ">= 0.8.0" 521 | } 522 | }, 523 | "node_modules/cookie-parser/node_modules/cookie": { 524 | "version": "0.4.1", 525 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", 526 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", 527 | "engines": { 528 | "node": ">= 0.6" 529 | } 530 | }, 531 | "node_modules/cookie-signature": { 532 | "version": "1.0.6", 533 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 534 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 535 | }, 536 | "node_modules/cors": { 537 | "version": "2.8.5", 538 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 539 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 540 | "dependencies": { 541 | "object-assign": "^4", 542 | "vary": "^1" 543 | }, 544 | "engines": { 545 | "node": ">= 0.10" 546 | } 547 | }, 548 | "node_modules/create-require": { 549 | "version": "1.1.1", 550 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 551 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" 552 | }, 553 | "node_modules/debug": { 554 | "version": "2.6.9", 555 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 556 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 557 | "dependencies": { 558 | "ms": "2.0.0" 559 | } 560 | }, 561 | "node_modules/define-data-property": { 562 | "version": "1.1.1", 563 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", 564 | "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", 565 | "dependencies": { 566 | "get-intrinsic": "^1.2.1", 567 | "gopd": "^1.0.1", 568 | "has-property-descriptors": "^1.0.0" 569 | }, 570 | "engines": { 571 | "node": ">= 0.4" 572 | } 573 | }, 574 | "node_modules/depd": { 575 | "version": "2.0.0", 576 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 577 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 578 | "engines": { 579 | "node": ">= 0.8" 580 | } 581 | }, 582 | "node_modules/destroy": { 583 | "version": "1.2.0", 584 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 585 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 586 | "engines": { 587 | "node": ">= 0.8", 588 | "npm": "1.2.8000 || >= 1.4.16" 589 | } 590 | }, 591 | "node_modules/diff": { 592 | "version": "4.0.2", 593 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 594 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 595 | "engines": { 596 | "node": ">=0.3.1" 597 | } 598 | }, 599 | "node_modules/dotenv": { 600 | "version": "16.3.1", 601 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", 602 | "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", 603 | "engines": { 604 | "node": ">=12" 605 | }, 606 | "funding": { 607 | "url": "https://github.com/motdotla/dotenv?sponsor=1" 608 | } 609 | }, 610 | "node_modules/ecdsa-sig-formatter": { 611 | "version": "1.0.11", 612 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 613 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 614 | "dependencies": { 615 | "safe-buffer": "^5.0.1" 616 | } 617 | }, 618 | "node_modules/ee-first": { 619 | "version": "1.1.1", 620 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 621 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 622 | }, 623 | "node_modules/encodeurl": { 624 | "version": "1.0.2", 625 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 626 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 627 | "engines": { 628 | "node": ">= 0.8" 629 | } 630 | }, 631 | "node_modules/escape-html": { 632 | "version": "1.0.3", 633 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 634 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 635 | }, 636 | "node_modules/etag": { 637 | "version": "1.8.1", 638 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 639 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 640 | "engines": { 641 | "node": ">= 0.6" 642 | } 643 | }, 644 | "node_modules/express": { 645 | "version": "4.18.2", 646 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", 647 | "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", 648 | "dependencies": { 649 | "accepts": "~1.3.8", 650 | "array-flatten": "1.1.1", 651 | "body-parser": "1.20.1", 652 | "content-disposition": "0.5.4", 653 | "content-type": "~1.0.4", 654 | "cookie": "0.5.0", 655 | "cookie-signature": "1.0.6", 656 | "debug": "2.6.9", 657 | "depd": "2.0.0", 658 | "encodeurl": "~1.0.2", 659 | "escape-html": "~1.0.3", 660 | "etag": "~1.8.1", 661 | "finalhandler": "1.2.0", 662 | "fresh": "0.5.2", 663 | "http-errors": "2.0.0", 664 | "merge-descriptors": "1.0.1", 665 | "methods": "~1.1.2", 666 | "on-finished": "2.4.1", 667 | "parseurl": "~1.3.3", 668 | "path-to-regexp": "0.1.7", 669 | "proxy-addr": "~2.0.7", 670 | "qs": "6.11.0", 671 | "range-parser": "~1.2.1", 672 | "safe-buffer": "5.2.1", 673 | "send": "0.18.0", 674 | "serve-static": "1.15.0", 675 | "setprototypeof": "1.2.0", 676 | "statuses": "2.0.1", 677 | "type-is": "~1.6.18", 678 | "utils-merge": "1.0.1", 679 | "vary": "~1.1.2" 680 | }, 681 | "engines": { 682 | "node": ">= 0.10.0" 683 | } 684 | }, 685 | "node_modules/express-async-handler": { 686 | "version": "1.2.0", 687 | "resolved": "https://registry.npmjs.org/express-async-handler/-/express-async-handler-1.2.0.tgz", 688 | "integrity": "sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w==" 689 | }, 690 | "node_modules/express-validator": { 691 | "version": "7.0.1", 692 | "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.0.1.tgz", 693 | "integrity": "sha512-oB+z9QOzQIE8FnlINqyIFA8eIckahC6qc8KtqLdLJcU3/phVyuhXH3bA4qzcrhme+1RYaCSwrq+TlZ/kAKIARA==", 694 | "dependencies": { 695 | "lodash": "^4.17.21", 696 | "validator": "^13.9.0" 697 | }, 698 | "engines": { 699 | "node": ">= 8.0.0" 700 | } 701 | }, 702 | "node_modules/fill-range": { 703 | "version": "7.0.1", 704 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 705 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 706 | "dev": true, 707 | "dependencies": { 708 | "to-regex-range": "^5.0.1" 709 | }, 710 | "engines": { 711 | "node": ">=8" 712 | } 713 | }, 714 | "node_modules/finalhandler": { 715 | "version": "1.2.0", 716 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 717 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 718 | "dependencies": { 719 | "debug": "2.6.9", 720 | "encodeurl": "~1.0.2", 721 | "escape-html": "~1.0.3", 722 | "on-finished": "2.4.1", 723 | "parseurl": "~1.3.3", 724 | "statuses": "2.0.1", 725 | "unpipe": "~1.0.0" 726 | }, 727 | "engines": { 728 | "node": ">= 0.8" 729 | } 730 | }, 731 | "node_modules/forwarded": { 732 | "version": "0.2.0", 733 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 734 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 735 | "engines": { 736 | "node": ">= 0.6" 737 | } 738 | }, 739 | "node_modules/fresh": { 740 | "version": "0.5.2", 741 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 742 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 743 | "engines": { 744 | "node": ">= 0.6" 745 | } 746 | }, 747 | "node_modules/fsevents": { 748 | "version": "2.3.3", 749 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 750 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 751 | "dev": true, 752 | "hasInstallScript": true, 753 | "optional": true, 754 | "os": [ 755 | "darwin" 756 | ], 757 | "engines": { 758 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 759 | } 760 | }, 761 | "node_modules/function-bind": { 762 | "version": "1.1.2", 763 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 764 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 765 | "funding": { 766 | "url": "https://github.com/sponsors/ljharb" 767 | } 768 | }, 769 | "node_modules/get-intrinsic": { 770 | "version": "1.2.2", 771 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", 772 | "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", 773 | "dependencies": { 774 | "function-bind": "^1.1.2", 775 | "has-proto": "^1.0.1", 776 | "has-symbols": "^1.0.3", 777 | "hasown": "^2.0.0" 778 | }, 779 | "funding": { 780 | "url": "https://github.com/sponsors/ljharb" 781 | } 782 | }, 783 | "node_modules/glob-parent": { 784 | "version": "5.1.2", 785 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 786 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 787 | "dev": true, 788 | "dependencies": { 789 | "is-glob": "^4.0.1" 790 | }, 791 | "engines": { 792 | "node": ">= 6" 793 | } 794 | }, 795 | "node_modules/gopd": { 796 | "version": "1.0.1", 797 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 798 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 799 | "dependencies": { 800 | "get-intrinsic": "^1.1.3" 801 | }, 802 | "funding": { 803 | "url": "https://github.com/sponsors/ljharb" 804 | } 805 | }, 806 | "node_modules/has-flag": { 807 | "version": "3.0.0", 808 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 809 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 810 | "dev": true, 811 | "engines": { 812 | "node": ">=4" 813 | } 814 | }, 815 | "node_modules/has-property-descriptors": { 816 | "version": "1.0.1", 817 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", 818 | "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", 819 | "dependencies": { 820 | "get-intrinsic": "^1.2.2" 821 | }, 822 | "funding": { 823 | "url": "https://github.com/sponsors/ljharb" 824 | } 825 | }, 826 | "node_modules/has-proto": { 827 | "version": "1.0.1", 828 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", 829 | "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", 830 | "engines": { 831 | "node": ">= 0.4" 832 | }, 833 | "funding": { 834 | "url": "https://github.com/sponsors/ljharb" 835 | } 836 | }, 837 | "node_modules/has-symbols": { 838 | "version": "1.0.3", 839 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 840 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 841 | "engines": { 842 | "node": ">= 0.4" 843 | }, 844 | "funding": { 845 | "url": "https://github.com/sponsors/ljharb" 846 | } 847 | }, 848 | "node_modules/hasown": { 849 | "version": "2.0.0", 850 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", 851 | "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", 852 | "dependencies": { 853 | "function-bind": "^1.1.2" 854 | }, 855 | "engines": { 856 | "node": ">= 0.4" 857 | } 858 | }, 859 | "node_modules/http-errors": { 860 | "version": "2.0.0", 861 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 862 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 863 | "dependencies": { 864 | "depd": "2.0.0", 865 | "inherits": "2.0.4", 866 | "setprototypeof": "1.2.0", 867 | "statuses": "2.0.1", 868 | "toidentifier": "1.0.1" 869 | }, 870 | "engines": { 871 | "node": ">= 0.8" 872 | } 873 | }, 874 | "node_modules/iconv-lite": { 875 | "version": "0.4.24", 876 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 877 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 878 | "dependencies": { 879 | "safer-buffer": ">= 2.1.2 < 3" 880 | }, 881 | "engines": { 882 | "node": ">=0.10.0" 883 | } 884 | }, 885 | "node_modules/ignore-by-default": { 886 | "version": "1.0.1", 887 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", 888 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", 889 | "dev": true 890 | }, 891 | "node_modules/inherits": { 892 | "version": "2.0.4", 893 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 894 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 895 | }, 896 | "node_modules/ipaddr.js": { 897 | "version": "1.9.1", 898 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 899 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 900 | "engines": { 901 | "node": ">= 0.10" 902 | } 903 | }, 904 | "node_modules/is-binary-path": { 905 | "version": "2.1.0", 906 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 907 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 908 | "dev": true, 909 | "dependencies": { 910 | "binary-extensions": "^2.0.0" 911 | }, 912 | "engines": { 913 | "node": ">=8" 914 | } 915 | }, 916 | "node_modules/is-extglob": { 917 | "version": "2.1.1", 918 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 919 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 920 | "dev": true, 921 | "engines": { 922 | "node": ">=0.10.0" 923 | } 924 | }, 925 | "node_modules/is-glob": { 926 | "version": "4.0.3", 927 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 928 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 929 | "dev": true, 930 | "dependencies": { 931 | "is-extglob": "^2.1.1" 932 | }, 933 | "engines": { 934 | "node": ">=0.10.0" 935 | } 936 | }, 937 | "node_modules/is-number": { 938 | "version": "7.0.0", 939 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 940 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 941 | "dev": true, 942 | "engines": { 943 | "node": ">=0.12.0" 944 | } 945 | }, 946 | "node_modules/jsonwebtoken": { 947 | "version": "9.0.2", 948 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", 949 | "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", 950 | "dependencies": { 951 | "jws": "^3.2.2", 952 | "lodash.includes": "^4.3.0", 953 | "lodash.isboolean": "^3.0.3", 954 | "lodash.isinteger": "^4.0.4", 955 | "lodash.isnumber": "^3.0.3", 956 | "lodash.isplainobject": "^4.0.6", 957 | "lodash.isstring": "^4.0.1", 958 | "lodash.once": "^4.0.0", 959 | "ms": "^2.1.1", 960 | "semver": "^7.5.4" 961 | }, 962 | "engines": { 963 | "node": ">=12", 964 | "npm": ">=6" 965 | } 966 | }, 967 | "node_modules/jsonwebtoken/node_modules/ms": { 968 | "version": "2.1.3", 969 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 970 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 971 | }, 972 | "node_modules/jwa": { 973 | "version": "1.4.1", 974 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 975 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 976 | "dependencies": { 977 | "buffer-equal-constant-time": "1.0.1", 978 | "ecdsa-sig-formatter": "1.0.11", 979 | "safe-buffer": "^5.0.1" 980 | } 981 | }, 982 | "node_modules/jws": { 983 | "version": "3.2.2", 984 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 985 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 986 | "dependencies": { 987 | "jwa": "^1.4.1", 988 | "safe-buffer": "^5.0.1" 989 | } 990 | }, 991 | "node_modules/lodash": { 992 | "version": "4.17.21", 993 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 994 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 995 | }, 996 | "node_modules/lodash.includes": { 997 | "version": "4.3.0", 998 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 999 | "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" 1000 | }, 1001 | "node_modules/lodash.isboolean": { 1002 | "version": "3.0.3", 1003 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 1004 | "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" 1005 | }, 1006 | "node_modules/lodash.isinteger": { 1007 | "version": "4.0.4", 1008 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 1009 | "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" 1010 | }, 1011 | "node_modules/lodash.isnumber": { 1012 | "version": "3.0.3", 1013 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 1014 | "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" 1015 | }, 1016 | "node_modules/lodash.isplainobject": { 1017 | "version": "4.0.6", 1018 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 1019 | "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" 1020 | }, 1021 | "node_modules/lodash.isstring": { 1022 | "version": "4.0.1", 1023 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 1024 | "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" 1025 | }, 1026 | "node_modules/lodash.once": { 1027 | "version": "4.1.1", 1028 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 1029 | "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" 1030 | }, 1031 | "node_modules/lru-cache": { 1032 | "version": "6.0.0", 1033 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 1034 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 1035 | "dependencies": { 1036 | "yallist": "^4.0.0" 1037 | }, 1038 | "engines": { 1039 | "node": ">=10" 1040 | } 1041 | }, 1042 | "node_modules/make-error": { 1043 | "version": "1.3.6", 1044 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 1045 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" 1046 | }, 1047 | "node_modules/media-typer": { 1048 | "version": "0.3.0", 1049 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1050 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 1051 | "engines": { 1052 | "node": ">= 0.6" 1053 | } 1054 | }, 1055 | "node_modules/merge-descriptors": { 1056 | "version": "1.0.1", 1057 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 1058 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 1059 | }, 1060 | "node_modules/methods": { 1061 | "version": "1.1.2", 1062 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1063 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 1064 | "engines": { 1065 | "node": ">= 0.6" 1066 | } 1067 | }, 1068 | "node_modules/mime": { 1069 | "version": "1.6.0", 1070 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1071 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 1072 | "bin": { 1073 | "mime": "cli.js" 1074 | }, 1075 | "engines": { 1076 | "node": ">=4" 1077 | } 1078 | }, 1079 | "node_modules/mime-db": { 1080 | "version": "1.52.0", 1081 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1082 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 1083 | "engines": { 1084 | "node": ">= 0.6" 1085 | } 1086 | }, 1087 | "node_modules/mime-types": { 1088 | "version": "2.1.35", 1089 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1090 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1091 | "dependencies": { 1092 | "mime-db": "1.52.0" 1093 | }, 1094 | "engines": { 1095 | "node": ">= 0.6" 1096 | } 1097 | }, 1098 | "node_modules/minimatch": { 1099 | "version": "3.1.2", 1100 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1101 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1102 | "dev": true, 1103 | "dependencies": { 1104 | "brace-expansion": "^1.1.7" 1105 | }, 1106 | "engines": { 1107 | "node": "*" 1108 | } 1109 | }, 1110 | "node_modules/morgan": { 1111 | "version": "1.10.0", 1112 | "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", 1113 | "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", 1114 | "dev": true, 1115 | "dependencies": { 1116 | "basic-auth": "~2.0.1", 1117 | "debug": "2.6.9", 1118 | "depd": "~2.0.0", 1119 | "on-finished": "~2.3.0", 1120 | "on-headers": "~1.0.2" 1121 | }, 1122 | "engines": { 1123 | "node": ">= 0.8.0" 1124 | } 1125 | }, 1126 | "node_modules/morgan/node_modules/on-finished": { 1127 | "version": "2.3.0", 1128 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1129 | "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", 1130 | "dev": true, 1131 | "dependencies": { 1132 | "ee-first": "1.1.1" 1133 | }, 1134 | "engines": { 1135 | "node": ">= 0.8" 1136 | } 1137 | }, 1138 | "node_modules/ms": { 1139 | "version": "2.0.0", 1140 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1141 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 1142 | }, 1143 | "node_modules/negotiator": { 1144 | "version": "0.6.3", 1145 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 1146 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 1147 | "engines": { 1148 | "node": ">= 0.6" 1149 | } 1150 | }, 1151 | "node_modules/nodemailer": { 1152 | "version": "6.9.7", 1153 | "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.7.tgz", 1154 | "integrity": "sha512-rUtR77ksqex/eZRLmQ21LKVH5nAAsVicAtAYudK7JgwenEDZ0UIQ1adUGqErz7sMkWYxWTTU1aeP2Jga6WQyJw==", 1155 | "engines": { 1156 | "node": ">=6.0.0" 1157 | } 1158 | }, 1159 | "node_modules/nodemon": { 1160 | "version": "3.0.1", 1161 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz", 1162 | "integrity": "sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==", 1163 | "dev": true, 1164 | "dependencies": { 1165 | "chokidar": "^3.5.2", 1166 | "debug": "^3.2.7", 1167 | "ignore-by-default": "^1.0.1", 1168 | "minimatch": "^3.1.2", 1169 | "pstree.remy": "^1.1.8", 1170 | "semver": "^7.5.3", 1171 | "simple-update-notifier": "^2.0.0", 1172 | "supports-color": "^5.5.0", 1173 | "touch": "^3.1.0", 1174 | "undefsafe": "^2.0.5" 1175 | }, 1176 | "bin": { 1177 | "nodemon": "bin/nodemon.js" 1178 | }, 1179 | "engines": { 1180 | "node": ">=10" 1181 | }, 1182 | "funding": { 1183 | "type": "opencollective", 1184 | "url": "https://opencollective.com/nodemon" 1185 | } 1186 | }, 1187 | "node_modules/nodemon/node_modules/debug": { 1188 | "version": "3.2.7", 1189 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 1190 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 1191 | "dev": true, 1192 | "dependencies": { 1193 | "ms": "^2.1.1" 1194 | } 1195 | }, 1196 | "node_modules/nodemon/node_modules/ms": { 1197 | "version": "2.1.3", 1198 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1199 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1200 | "dev": true 1201 | }, 1202 | "node_modules/nopt": { 1203 | "version": "1.0.10", 1204 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", 1205 | "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", 1206 | "dev": true, 1207 | "dependencies": { 1208 | "abbrev": "1" 1209 | }, 1210 | "bin": { 1211 | "nopt": "bin/nopt.js" 1212 | }, 1213 | "engines": { 1214 | "node": "*" 1215 | } 1216 | }, 1217 | "node_modules/normalize-path": { 1218 | "version": "3.0.0", 1219 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1220 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1221 | "dev": true, 1222 | "engines": { 1223 | "node": ">=0.10.0" 1224 | } 1225 | }, 1226 | "node_modules/object-assign": { 1227 | "version": "4.1.1", 1228 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1229 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 1230 | "engines": { 1231 | "node": ">=0.10.0" 1232 | } 1233 | }, 1234 | "node_modules/object-inspect": { 1235 | "version": "1.13.1", 1236 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", 1237 | "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", 1238 | "funding": { 1239 | "url": "https://github.com/sponsors/ljharb" 1240 | } 1241 | }, 1242 | "node_modules/on-finished": { 1243 | "version": "2.4.1", 1244 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1245 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1246 | "dependencies": { 1247 | "ee-first": "1.1.1" 1248 | }, 1249 | "engines": { 1250 | "node": ">= 0.8" 1251 | } 1252 | }, 1253 | "node_modules/on-headers": { 1254 | "version": "1.0.2", 1255 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", 1256 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", 1257 | "dev": true, 1258 | "engines": { 1259 | "node": ">= 0.8" 1260 | } 1261 | }, 1262 | "node_modules/parseurl": { 1263 | "version": "1.3.3", 1264 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1265 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1266 | "engines": { 1267 | "node": ">= 0.8" 1268 | } 1269 | }, 1270 | "node_modules/path-to-regexp": { 1271 | "version": "0.1.7", 1272 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1273 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 1274 | }, 1275 | "node_modules/picomatch": { 1276 | "version": "2.3.1", 1277 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1278 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1279 | "dev": true, 1280 | "engines": { 1281 | "node": ">=8.6" 1282 | }, 1283 | "funding": { 1284 | "url": "https://github.com/sponsors/jonschlinkert" 1285 | } 1286 | }, 1287 | "node_modules/prisma": { 1288 | "version": "5.5.2", 1289 | "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.5.2.tgz", 1290 | "integrity": "sha512-WQtG6fevOL053yoPl6dbHV+IWgKo25IRN4/pwAGqcWmg7CrtoCzvbDbN9fXUc7QS2KK0LimHIqLsaCOX/vHl8w==", 1291 | "hasInstallScript": true, 1292 | "dependencies": { 1293 | "@prisma/engines": "5.5.2" 1294 | }, 1295 | "bin": { 1296 | "prisma": "build/index.js" 1297 | }, 1298 | "engines": { 1299 | "node": ">=16.13" 1300 | } 1301 | }, 1302 | "node_modules/proxy-addr": { 1303 | "version": "2.0.7", 1304 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1305 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1306 | "dependencies": { 1307 | "forwarded": "0.2.0", 1308 | "ipaddr.js": "1.9.1" 1309 | }, 1310 | "engines": { 1311 | "node": ">= 0.10" 1312 | } 1313 | }, 1314 | "node_modules/pstree.remy": { 1315 | "version": "1.1.8", 1316 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", 1317 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", 1318 | "dev": true 1319 | }, 1320 | "node_modules/qs": { 1321 | "version": "6.11.0", 1322 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 1323 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 1324 | "dependencies": { 1325 | "side-channel": "^1.0.4" 1326 | }, 1327 | "engines": { 1328 | "node": ">=0.6" 1329 | }, 1330 | "funding": { 1331 | "url": "https://github.com/sponsors/ljharb" 1332 | } 1333 | }, 1334 | "node_modules/randombytes": { 1335 | "version": "2.0.3", 1336 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.3.tgz", 1337 | "integrity": "sha512-lDVjxQQFoCG1jcrP06LNo2lbWp4QTShEXnhActFBwYuHprllQV6VUpwreApsYqCgD+N1mHoqJ/BI/4eV4R2GYg==" 1338 | }, 1339 | "node_modules/randomstring": { 1340 | "version": "1.3.0", 1341 | "resolved": "https://registry.npmjs.org/randomstring/-/randomstring-1.3.0.tgz", 1342 | "integrity": "sha512-gY7aQ4i1BgwZ8I1Op4YseITAyiDiajeZOPQUbIq9TPGPhUm5FX59izIaOpmKbME1nmnEiABf28d9K2VSii6BBg==", 1343 | "dependencies": { 1344 | "randombytes": "2.0.3" 1345 | }, 1346 | "bin": { 1347 | "randomstring": "bin/randomstring" 1348 | }, 1349 | "engines": { 1350 | "node": "*" 1351 | } 1352 | }, 1353 | "node_modules/range-parser": { 1354 | "version": "1.2.1", 1355 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1356 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1357 | "engines": { 1358 | "node": ">= 0.6" 1359 | } 1360 | }, 1361 | "node_modules/raw-body": { 1362 | "version": "2.5.1", 1363 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 1364 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 1365 | "dependencies": { 1366 | "bytes": "3.1.2", 1367 | "http-errors": "2.0.0", 1368 | "iconv-lite": "0.4.24", 1369 | "unpipe": "1.0.0" 1370 | }, 1371 | "engines": { 1372 | "node": ">= 0.8" 1373 | } 1374 | }, 1375 | "node_modules/readdirp": { 1376 | "version": "3.6.0", 1377 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1378 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1379 | "dev": true, 1380 | "dependencies": { 1381 | "picomatch": "^2.2.1" 1382 | }, 1383 | "engines": { 1384 | "node": ">=8.10.0" 1385 | } 1386 | }, 1387 | "node_modules/safe-buffer": { 1388 | "version": "5.2.1", 1389 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1390 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1391 | "funding": [ 1392 | { 1393 | "type": "github", 1394 | "url": "https://github.com/sponsors/feross" 1395 | }, 1396 | { 1397 | "type": "patreon", 1398 | "url": "https://www.patreon.com/feross" 1399 | }, 1400 | { 1401 | "type": "consulting", 1402 | "url": "https://feross.org/support" 1403 | } 1404 | ] 1405 | }, 1406 | "node_modules/safer-buffer": { 1407 | "version": "2.1.2", 1408 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1409 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1410 | }, 1411 | "node_modules/semver": { 1412 | "version": "7.5.4", 1413 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", 1414 | "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", 1415 | "dependencies": { 1416 | "lru-cache": "^6.0.0" 1417 | }, 1418 | "bin": { 1419 | "semver": "bin/semver.js" 1420 | }, 1421 | "engines": { 1422 | "node": ">=10" 1423 | } 1424 | }, 1425 | "node_modules/send": { 1426 | "version": "0.18.0", 1427 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 1428 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 1429 | "dependencies": { 1430 | "debug": "2.6.9", 1431 | "depd": "2.0.0", 1432 | "destroy": "1.2.0", 1433 | "encodeurl": "~1.0.2", 1434 | "escape-html": "~1.0.3", 1435 | "etag": "~1.8.1", 1436 | "fresh": "0.5.2", 1437 | "http-errors": "2.0.0", 1438 | "mime": "1.6.0", 1439 | "ms": "2.1.3", 1440 | "on-finished": "2.4.1", 1441 | "range-parser": "~1.2.1", 1442 | "statuses": "2.0.1" 1443 | }, 1444 | "engines": { 1445 | "node": ">= 0.8.0" 1446 | } 1447 | }, 1448 | "node_modules/send/node_modules/ms": { 1449 | "version": "2.1.3", 1450 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1451 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1452 | }, 1453 | "node_modules/serve-static": { 1454 | "version": "1.15.0", 1455 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 1456 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 1457 | "dependencies": { 1458 | "encodeurl": "~1.0.2", 1459 | "escape-html": "~1.0.3", 1460 | "parseurl": "~1.3.3", 1461 | "send": "0.18.0" 1462 | }, 1463 | "engines": { 1464 | "node": ">= 0.8.0" 1465 | } 1466 | }, 1467 | "node_modules/set-function-length": { 1468 | "version": "1.1.1", 1469 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", 1470 | "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", 1471 | "dependencies": { 1472 | "define-data-property": "^1.1.1", 1473 | "get-intrinsic": "^1.2.1", 1474 | "gopd": "^1.0.1", 1475 | "has-property-descriptors": "^1.0.0" 1476 | }, 1477 | "engines": { 1478 | "node": ">= 0.4" 1479 | } 1480 | }, 1481 | "node_modules/setprototypeof": { 1482 | "version": "1.2.0", 1483 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1484 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 1485 | }, 1486 | "node_modules/side-channel": { 1487 | "version": "1.0.4", 1488 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 1489 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 1490 | "dependencies": { 1491 | "call-bind": "^1.0.0", 1492 | "get-intrinsic": "^1.0.2", 1493 | "object-inspect": "^1.9.0" 1494 | }, 1495 | "funding": { 1496 | "url": "https://github.com/sponsors/ljharb" 1497 | } 1498 | }, 1499 | "node_modules/simple-update-notifier": { 1500 | "version": "2.0.0", 1501 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", 1502 | "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", 1503 | "dev": true, 1504 | "dependencies": { 1505 | "semver": "^7.5.3" 1506 | }, 1507 | "engines": { 1508 | "node": ">=10" 1509 | } 1510 | }, 1511 | "node_modules/statuses": { 1512 | "version": "2.0.1", 1513 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1514 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1515 | "engines": { 1516 | "node": ">= 0.8" 1517 | } 1518 | }, 1519 | "node_modules/supports-color": { 1520 | "version": "5.5.0", 1521 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1522 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1523 | "dev": true, 1524 | "dependencies": { 1525 | "has-flag": "^3.0.0" 1526 | }, 1527 | "engines": { 1528 | "node": ">=4" 1529 | } 1530 | }, 1531 | "node_modules/to-regex-range": { 1532 | "version": "5.0.1", 1533 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1534 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1535 | "dev": true, 1536 | "dependencies": { 1537 | "is-number": "^7.0.0" 1538 | }, 1539 | "engines": { 1540 | "node": ">=8.0" 1541 | } 1542 | }, 1543 | "node_modules/toidentifier": { 1544 | "version": "1.0.1", 1545 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1546 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1547 | "engines": { 1548 | "node": ">=0.6" 1549 | } 1550 | }, 1551 | "node_modules/touch": { 1552 | "version": "3.1.0", 1553 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", 1554 | "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", 1555 | "dev": true, 1556 | "dependencies": { 1557 | "nopt": "~1.0.10" 1558 | }, 1559 | "bin": { 1560 | "nodetouch": "bin/nodetouch.js" 1561 | } 1562 | }, 1563 | "node_modules/ts-node": { 1564 | "version": "10.9.1", 1565 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", 1566 | "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", 1567 | "dependencies": { 1568 | "@cspotcode/source-map-support": "^0.8.0", 1569 | "@tsconfig/node10": "^1.0.7", 1570 | "@tsconfig/node12": "^1.0.7", 1571 | "@tsconfig/node14": "^1.0.0", 1572 | "@tsconfig/node16": "^1.0.2", 1573 | "acorn": "^8.4.1", 1574 | "acorn-walk": "^8.1.1", 1575 | "arg": "^4.1.0", 1576 | "create-require": "^1.1.0", 1577 | "diff": "^4.0.1", 1578 | "make-error": "^1.1.1", 1579 | "v8-compile-cache-lib": "^3.0.1", 1580 | "yn": "3.1.1" 1581 | }, 1582 | "bin": { 1583 | "ts-node": "dist/bin.js", 1584 | "ts-node-cwd": "dist/bin-cwd.js", 1585 | "ts-node-esm": "dist/bin-esm.js", 1586 | "ts-node-script": "dist/bin-script.js", 1587 | "ts-node-transpile-only": "dist/bin-transpile.js", 1588 | "ts-script": "dist/bin-script-deprecated.js" 1589 | }, 1590 | "peerDependencies": { 1591 | "@swc/core": ">=1.2.50", 1592 | "@swc/wasm": ">=1.2.50", 1593 | "@types/node": "*", 1594 | "typescript": ">=2.7" 1595 | }, 1596 | "peerDependenciesMeta": { 1597 | "@swc/core": { 1598 | "optional": true 1599 | }, 1600 | "@swc/wasm": { 1601 | "optional": true 1602 | } 1603 | } 1604 | }, 1605 | "node_modules/type-is": { 1606 | "version": "1.6.18", 1607 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1608 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1609 | "dependencies": { 1610 | "media-typer": "0.3.0", 1611 | "mime-types": "~2.1.24" 1612 | }, 1613 | "engines": { 1614 | "node": ">= 0.6" 1615 | } 1616 | }, 1617 | "node_modules/typescript": { 1618 | "version": "5.2.2", 1619 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", 1620 | "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", 1621 | "bin": { 1622 | "tsc": "bin/tsc", 1623 | "tsserver": "bin/tsserver" 1624 | }, 1625 | "engines": { 1626 | "node": ">=14.17" 1627 | } 1628 | }, 1629 | "node_modules/undefsafe": { 1630 | "version": "2.0.5", 1631 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", 1632 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", 1633 | "dev": true 1634 | }, 1635 | "node_modules/undici-types": { 1636 | "version": "5.26.5", 1637 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 1638 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" 1639 | }, 1640 | "node_modules/unpipe": { 1641 | "version": "1.0.0", 1642 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1643 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1644 | "engines": { 1645 | "node": ">= 0.8" 1646 | } 1647 | }, 1648 | "node_modules/utils-merge": { 1649 | "version": "1.0.1", 1650 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1651 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 1652 | "engines": { 1653 | "node": ">= 0.4.0" 1654 | } 1655 | }, 1656 | "node_modules/v8-compile-cache-lib": { 1657 | "version": "3.0.1", 1658 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 1659 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" 1660 | }, 1661 | "node_modules/validator": { 1662 | "version": "13.11.0", 1663 | "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", 1664 | "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", 1665 | "engines": { 1666 | "node": ">= 0.10" 1667 | } 1668 | }, 1669 | "node_modules/vary": { 1670 | "version": "1.1.2", 1671 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1672 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1673 | "engines": { 1674 | "node": ">= 0.8" 1675 | } 1676 | }, 1677 | "node_modules/yallist": { 1678 | "version": "4.0.0", 1679 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1680 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 1681 | }, 1682 | "node_modules/yn": { 1683 | "version": "3.1.1", 1684 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 1685 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 1686 | "engines": { 1687 | "node": ">=6" 1688 | } 1689 | } 1690 | } 1691 | } 1692 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@prisma/client": "^5.5.2", 4 | "@types/express": "^4.17.20", 5 | "@types/node": "^20.8.9", 6 | "bcryptjs": "^2.4.3", 7 | "cookie-parser": "^1.4.6", 8 | "cors": "^2.8.5", 9 | "dotenv": "^16.3.1", 10 | "express": "^4.18.2", 11 | "express-async-handler": "^1.2.0", 12 | "express-validator": "^7.0.1", 13 | "http-errors": "^2.0.0", 14 | "jsonwebtoken": "^9.0.2", 15 | "nodemailer": "^6.9.7", 16 | "prisma": "^5.5.2", 17 | "randomstring": "^1.3.0", 18 | "ts-node": "^10.9.1", 19 | "typescript": "^5.2.2" 20 | }, 21 | "name": "api-with-typescript", 22 | "version": "1.0.0", 23 | "main": "index.ts", 24 | "devDependencies": { 25 | "@types/bcryptjs": "^2.4.5", 26 | "@types/cookie-parser": "^1.4.5", 27 | "@types/cors": "^2.8.15", 28 | "@types/jsonwebtoken": "^9.0.4", 29 | "@types/morgan": "^1.9.7", 30 | "@types/nodemailer": "^6.4.14", 31 | "@types/randomstring": "^1.1.11", 32 | "colors": "^1.4.0", 33 | "morgan": "^1.10.0", 34 | "nodemon": "^3.0.1" 35 | }, 36 | "scripts": { 37 | "dev": "nodemon ./src/index.ts", 38 | "build": "tsc -p tsconfig.json", 39 | "start": "node ./dist/index.js" 40 | }, 41 | "prisma": { 42 | "schema": "./src/prisma/schema.prisma" 43 | }, 44 | "keywords": [], 45 | "author": "", 46 | "license": "ISC", 47 | "description": "", 48 | "engines": { 49 | "node": ">=18.18.0 <19.0.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/config/corsSetup.ts: -------------------------------------------------------------------------------- 1 | import createError from "http-errors"; 2 | 3 | // whitelist is an array of url's that are allowed to access the api 4 | const whitelist = [ 5 | "http://localhost:3000", 6 | "http://localhost:3001", 7 | "http://localhost:5173", 8 | "https://kinsust.org", 9 | "https://www.kinsust.org", 10 | "https://kin-sust-nextjs-rejoyanislam.vercel.app", 11 | "http://127.0.0.1:5500", 12 | ]; 13 | 14 | // corsOptions is an object with a function that checks if the origin is in the whitelist 15 | const corsOptions = { 16 | origin: function (origin:any, callback:any) { 17 | if (whitelist.indexOf(origin) !== -1 || !origin) { 18 | callback(null, true); 19 | } else { 20 | callback(createError(401, "Not allowed by CORS")); 21 | } 22 | }, 23 | optionsSuccessStatus: 200, 24 | credentials: true, 25 | }; 26 | 27 | // export the corsOptions object 28 | export default corsOptions; 29 | -------------------------------------------------------------------------------- /src/config/db.ts: -------------------------------------------------------------------------------- 1 | // import { PrismaClient } from "@prisma/client"; 2 | 3 | import client from "../prisma/client/client"; 4 | import CustomError from "../helper/customError"; 5 | 6 | // let db: PrismaClient; 7 | 8 | // declare global { 9 | // var __db: PrismaClient | undefined; 10 | // } 11 | 12 | // if (process.env.NODE_ENV === "production") { 13 | // db = new PrismaClient(); 14 | // db.$connect(); 15 | // } else { 16 | // if (!global.__db) { 17 | // global.__db = new PrismaClient(); 18 | // global.__db.$connect(); 19 | // } 20 | // db = global.__db; 21 | // } 22 | 23 | // export { db }; 24 | 25 | const connectDB = async (): Promise => { 26 | try { 27 | const value = await client.$connect(); 28 | } catch (error) { 29 | console.log(error); 30 | 31 | throw new CustomError("error.message", 400); 32 | } 33 | }; 34 | 35 | export default connectDB; 36 | -------------------------------------------------------------------------------- /src/controllers/auth.controllers.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import asyncHandler from "express-async-handler"; 3 | import client from "../prisma/client/client"; 4 | import CustomError from "../helper/customError"; 5 | import randomHashCode from "../helper/randomHashCode"; 6 | import createJWT from "../helper/createJWT"; 7 | import { 8 | jwtLoginTokenExpire, 9 | jwtLoginTokenSecret, 10 | jwtSecret, 11 | verifyKey, 12 | verifyKeyExpire, 13 | } from "../secret"; 14 | import { errorResponse, successResponse } from "../helper/responseHandler"; 15 | import sendAccountVerifyMail from "../utils/email/accountActivationMail"; 16 | import matchPassword from "../helper/matchPassword"; 17 | import { RequestWithUser, User } from "../types/types"; 18 | import { log } from "console"; 19 | import hashPassword from "../helper/hashPassword"; 20 | import jwt from "jsonwebtoken"; 21 | import bcrypt from "bcryptjs"; 22 | /** 23 | * 24 | * @apiDescription Create a new user account 25 | * @apiMethod POST 26 | * 27 | * @apiRoute /api/v1/auth/register 28 | * @apiAccess public 29 | * 30 | * @apiBody { name, username, email, password, gender } 31 | * 32 | * @apiSuccess { success: true , message: active your account by verify email, data: {} } 33 | * @apiFailed { success: false , error: { status, message } 34 | * 35 | * @apiError ( Bad Request 400 ) Invalid syntax / parameters 36 | * @apiError ( Not Found: 404 ) Couldn't find any data! 37 | * @apiError ( Conflict: 409 ) Already have an account. 38 | * 39 | */ 40 | export const userRegister = asyncHandler( 41 | async (req: Request, res: Response): Promise => { 42 | const { email } = req.body; 43 | 44 | const existEmail = await client.user.findUnique({ 45 | where: { email }, 46 | }); 47 | console.log(existEmail); 48 | 49 | const existUsername = await client.user.findUnique({ 50 | where: { username: req.body.username }, 51 | }); 52 | 53 | if (existEmail) throw new CustomError("Email already exist", 400); 54 | if (existUsername) throw new CustomError("Username already exist", 400); 55 | 56 | // random hash code 57 | const { code, hashCode } = randomHashCode(4); 58 | 59 | // create verify token 60 | const verifyToken = createJWT( 61 | { email, code: hashCode }, 62 | verifyKey, 63 | verifyKeyExpire 64 | ); 65 | 66 | // create user 67 | const user = await client.user.create({ 68 | data: { ...req.body, password: hashPassword(req.body.password) }, 69 | }); 70 | 71 | // prepare email data 72 | const emailData = { 73 | email, 74 | subject: "Account Activation Code.", 75 | code, 76 | verifyToken, 77 | }; 78 | 79 | // send email 80 | await sendAccountVerifyMail(emailData); 81 | 82 | // cookie set 83 | res.cookie("verifyToken", verifyToken, { 84 | httpOnly: false, 85 | maxAge: 1000 * 60 * 5, // 5 min 86 | secure: true, // only https 87 | sameSite: "none", 88 | }); 89 | 90 | // response send 91 | successResponse(res, { 92 | statusCode: 201, 93 | message: `Email has been sent to ${email}. Follow the instruction to activate your account`, 94 | payload: { 95 | data: user, 96 | }, 97 | }); 98 | } 99 | ); 100 | 101 | /** 102 | * 103 | * @apiDescription User login 104 | * @apiMethod POST 105 | * 106 | * @apiRoute /api/v1/auth/login 107 | * @apiAccess public 108 | * 109 | * @apiBody { email, password } 110 | * 111 | * @apiDenied { isBanned: true } 112 | * 113 | * @apiSuccess { success: true , message: Successfully Login, data: {} } 114 | * @apiFailed { success: false , error: { status, message } 115 | * 116 | * @apiError ( Bad Request 400 ) Invalid syntax / parameters 117 | * @apiError ( Not Found: 404 ) Couldn't find any user account!. Please register. 118 | * 119 | */ 120 | export const userLogin = asyncHandler( 121 | async (req: Request, res: Response): Promise => { 122 | const { email, password } = req.body; 123 | 124 | // get user 125 | const user = await client.user.findUnique({ 126 | where: { email }, 127 | }); 128 | 129 | // user check 130 | if (!user) 131 | throw new CustomError( 132 | "Couldn't find any user account!. Please register.", 133 | 400 134 | ); 135 | 136 | // password match 137 | matchPassword(password, user.password); 138 | 139 | // isActivate check 140 | if (user?.isVerified === false) { 141 | throw new CustomError("Please active your account.", 400); 142 | } 143 | 144 | // create access token 145 | const accessToken = createJWT( 146 | { email }, 147 | jwtLoginTokenSecret, 148 | 149 | jwtLoginTokenExpire 150 | ); 151 | 152 | // response send 153 | res.cookie("accessToken", accessToken, { 154 | httpOnly: false, 155 | maxAge: 1000 * 60 * 60 * 24 * 15, // 15 days 156 | secure: true, // only https 157 | sameSite: "none", 158 | }); 159 | 160 | successResponse(res, { 161 | statusCode: 200, 162 | message: "Successfully Login to KIN.", 163 | payload: { 164 | data: { ...user, accessToken }, 165 | }, 166 | }); 167 | } 168 | ); 169 | 170 | /** 171 | * 172 | * @apiDescription User Logout 173 | * @apiMethod POST 174 | * 175 | * @apiRoute /api/v1/auth/logout 176 | * @apiAccess Only Logged in user 177 | * 178 | * @apiCookie accessToken 179 | * 180 | * @apiSuccess { success: true , message: Successfully Logout } 181 | * @apiFailed { success: false , error: { status, message } 182 | * 183 | */ 184 | export const userLogout = (req: Request, res: Response) => { 185 | res?.clearCookie("accessToken", { 186 | httpOnly: false, 187 | secure: true, // only https 188 | sameSite: "none", 189 | }); 190 | 191 | // response send 192 | successResponse(res, { 193 | statusCode: 200, 194 | message: "Successfully Logout.", 195 | }); 196 | }; 197 | 198 | /** 199 | * 200 | * @apiDescription Logged in user data 201 | * @apiMethod GET 202 | * 203 | * @apiRoute /api/v1/auth/me 204 | * @apiAccess Only Logged in user 205 | * 206 | * @apiCookie accessToken 207 | * 208 | * @apiSuccess { success: true , message: Successfully Logout } 209 | * @apiFailed { success: false , error: { status, message } 210 | * 211 | */ 212 | export const me = asyncHandler( 213 | async (req: RequestWithUser, res: Response): Promise => { 214 | if (!req?.me) { 215 | throw new CustomError( 216 | "Couldn't find any user account!. Please register.", 217 | 404 218 | ); 219 | } 220 | successResponse(res, { 221 | statusCode: 200, 222 | message: "Login User Data.", 223 | payload: { 224 | data: req.me, 225 | }, 226 | }); 227 | } 228 | ); 229 | 230 | /** 231 | * 232 | * @apiDescription Active user account by code 233 | * @apiMethod POST 234 | * 235 | * @apiRoute /api/v1/auth/activate 236 | * @apiAccess registered user 237 | * 238 | * @apiBody { code } 239 | * 240 | * @apiSuccess { success: true , message: Successfully activated your account., data: {} } 241 | * @apiFailed { success: false , error: { status, message } 242 | * 243 | */ 244 | export const activeUserAccountByCode = asyncHandler( 245 | async (req: Request, res: Response): Promise => { 246 | // token 247 | const token = req.cookies.verifyToken; 248 | 249 | // check token 250 | if (!token) { 251 | throw new CustomError("Token not found", 400); 252 | } 253 | 254 | // verify token 255 | jwt.verify(token, jwtSecret, async (err: Error | null, decoded: any) => { 256 | if (err) { 257 | return errorResponse(res, { 258 | statusCode: 400, 259 | message: "Time expired! ", 260 | }); 261 | } 262 | 263 | // check if user is already verified 264 | const user = await client.user.findUnique({ 265 | where: { email: decoded?.email }, 266 | }); 267 | 268 | // user exist check 269 | if (!user) { 270 | return errorResponse(res, { 271 | statusCode: 400, 272 | message: "Couldn't find any user account!. Please register.", 273 | }); 274 | } 275 | 276 | if (user.isVerified === true) { 277 | return errorResponse(res, { 278 | statusCode: 400, 279 | message: "Your account is already active. Please login.", 280 | }); 281 | } 282 | 283 | // check code 284 | const code = bcrypt.compareSync(req.body.code, decoded.code); 285 | 286 | if (!code) { 287 | return errorResponse(res, { 288 | statusCode: 400, 289 | message: "wrong code", 290 | }); 291 | } else { 292 | await client.user.update({ 293 | where: { email: decoded?.email }, 294 | data: { isVerified: true }, 295 | }); 296 | 297 | // cookie clear 298 | res?.clearCookie("verifyToken", { 299 | sameSite: "strict", 300 | }); 301 | 302 | // response send 303 | return successResponse(res, { 304 | statusCode: 201, 305 | message: "Successfully activated your account.", 306 | }); 307 | } 308 | }); 309 | } 310 | ); 311 | 312 | /** 313 | * 314 | * @apiDescription Resend verification code to email 315 | * @apiMethod POST 316 | * 317 | * @apiRoute /api/v1/auth/resend-active-code 318 | * @apiAccess registered user 319 | * 320 | * @apiBody { email} 321 | * 322 | * @apiSuccess { success: true , message: Email has been sent to email. Follow the instruction to activate your account, data: {} } 323 | * @apiFailed { success: false , error: { status, message } 324 | * 325 | */ 326 | export const resendActivationCode = asyncHandler(async (req, res) => { 327 | const { email } = req.body; 328 | 329 | const user = await client.user.findUnique({ where: { email } }); 330 | 331 | // check: user is exist or not. 332 | if (!user) { 333 | throw new CustomError( 334 | "Couldn't find any user account!. Please register.", 335 | 400 336 | ); 337 | } 338 | 339 | // check: user is activate or not 340 | if (user.isVerified === true) { 341 | throw new CustomError("Your account is already active. Please login.", 400); 342 | } 343 | 344 | // random hash code 345 | const { code, hashCode } = randomHashCode(4); 346 | 347 | // create verify token 348 | const verifyToken = createJWT( 349 | { email, code: hashCode }, 350 | verifyKey, 351 | verifyKeyExpire 352 | ); 353 | 354 | // prepare email data 355 | const emailData = { 356 | email, 357 | subject: "Account Activation Code", 358 | code, 359 | verifyToken, 360 | }; 361 | 362 | // send email 363 | sendAccountVerifyMail(emailData); 364 | 365 | res.cookie("verifyToken", verifyToken, { 366 | httpOnly: true, 367 | maxAge: 1000 * 60 * 5, // 5 min 368 | secure: true, // only https 369 | sameSite: "none", 370 | }); 371 | 372 | // response send 373 | successResponse(res, { 374 | statusCode: 200, 375 | message: `Email has been sent to ${email}. Follow the instruction to activate your account`, 376 | }); 377 | }); 378 | -------------------------------------------------------------------------------- /src/controllers/comment.controller.ts: -------------------------------------------------------------------------------- 1 | import asyncHandler from "express-async-handler"; 2 | import client from "../prisma/client/client"; 3 | import CustomError from "../helper/customError"; 4 | import { successResponse } from "../helper/responseHandler"; 5 | import { Request, Response } from "express"; 6 | 7 | /** 8 | * @method GET 9 | * @route /api/comments 10 | * @description Get all comments 11 | * @access Public 12 | */ 13 | 14 | export const getAllComments = asyncHandler( 15 | async (req: Request, res: Response) => { 16 | const comments = await client.comment.findMany({ 17 | include: { 18 | post: true, 19 | }, 20 | }); 21 | 22 | if (!comments.length) 23 | throw new CustomError("Couldn't find any comment data", 404); 24 | 25 | // response send 26 | successResponse(res, { 27 | statusCode: 200, 28 | message: "All comments data", 29 | payload: { 30 | data: comments, 31 | }, 32 | }); 33 | } 34 | ); 35 | 36 | /** 37 | * @method GET 38 | * @route /api/comments/:id 39 | * @description Get comment by id 40 | * @access Public 41 | */ 42 | 43 | export const getCommentById = asyncHandler( 44 | async (req: Request, res: Response) => { 45 | const comment = await client.comment.findUnique({ 46 | where: { id: Number(req.params.id) }, 47 | include: { 48 | post: true, 49 | }, 50 | }); 51 | 52 | if (!comment) throw new CustomError("Couldn't find any comment data", 404); 53 | 54 | // response send 55 | successResponse(res, { 56 | statusCode: 200, 57 | message: "Comment data", 58 | payload: comment, 59 | }); 60 | } 61 | ); 62 | 63 | /** 64 | * @method POST 65 | * @route /api/comments 66 | * @description Create new comment 67 | * @access Public 68 | */ 69 | 70 | export const createComment = asyncHandler( 71 | async (req: Request, res: Response) => { 72 | // post find 73 | const post = await client.post.findUnique({ 74 | where: { id: Number(req.body.postId) }, 75 | }); 76 | 77 | if (!post) throw new CustomError("Couldn't find any post data.", 404); 78 | 79 | const newComment = await client.comment.create({ 80 | data: { 81 | ...req.body, 82 | postId: Number(req.body.postId), 83 | }, 84 | }); 85 | 86 | // response send 87 | successResponse(res, { 88 | statusCode: 200, 89 | message: "Comment created", 90 | payload: newComment, 91 | }); 92 | } 93 | ); 94 | 95 | /** 96 | * @method PUT 97 | * @route /api/comments/:id 98 | * @description Update comment by id 99 | * @access Public 100 | */ 101 | 102 | export const updateCommentById = asyncHandler( 103 | async (req: Request, res: Response) => { 104 | const { id } = req.params; 105 | 106 | // comment find 107 | const comment = await client.comment.findUnique({ 108 | where: { id: Number(id) }, 109 | }); 110 | 111 | if (!comment) throw new CustomError("Couldn't find any comment data.", 404); 112 | 113 | const updatedComment = await client.comment.update({ 114 | where: { id: Number(id) }, 115 | data: { 116 | ...req.body, 117 | }, 118 | }); 119 | 120 | // response send 121 | successResponse(res, { 122 | statusCode: 200, 123 | message: "Comment updated", 124 | payload: updatedComment, 125 | }); 126 | } 127 | ); 128 | 129 | /** 130 | * @method DELETE 131 | * @route /api/comments/:id 132 | * @description Delete comment by id 133 | * @access Public 134 | */ 135 | 136 | export const deleteCommentById = asyncHandler( 137 | async (req: Request, res: Response) => { 138 | const { id } = req.params; 139 | 140 | // comment find 141 | const comment = await client.comment.findUnique({ 142 | where: { id: Number(id) }, 143 | }); 144 | 145 | if (!comment) throw new CustomError("Couldn't find any comment data.", 404); 146 | 147 | const deletedComment = await client.comment.delete({ 148 | where: { id: Number(id) }, 149 | }); 150 | 151 | // response send 152 | successResponse(res, { 153 | statusCode: 200, 154 | message: "Comment deleted", 155 | payload: deletedComment, 156 | }); 157 | } 158 | ); 159 | 160 | /** 161 | * @method Bulk DELETE 162 | * @route /api/comments/bulk 163 | * @description Delete all comments 164 | * @access Admin 165 | */ 166 | 167 | export const bulkCommentsCreate = asyncHandler( 168 | async (req: Request, res: Response) => { 169 | // array check 170 | if (!Array.isArray(req.body)) 171 | throw new CustomError("Please provide an array of comments", 400); 172 | 173 | // before all comments delete 174 | await client.comment.deleteMany(); 175 | 176 | // post id check 177 | const posts = await client.post.findMany(); 178 | 179 | req.body.forEach((comment) => { 180 | const post = posts.find((post) => post.id === comment.postId); 181 | if (!post) 182 | throw new CustomError(`Post id ${comment.postId} not found`, 404); 183 | }); 184 | 185 | // bulk create 186 | await client.comment.createMany({ 187 | data: req.body, 188 | }); 189 | 190 | // created comments 191 | const comments = await client.comment.findMany({ 192 | include: { 193 | post: true, 194 | }, 195 | }); 196 | 197 | // response send 198 | successResponse(res, { 199 | statusCode: 200, 200 | message: "Some comments created", 201 | payload: { 202 | data: comments, 203 | }, 204 | }); 205 | } 206 | ); 207 | 208 | /** 209 | * @method Bulk POST 210 | * @route /api/comments/bulk 211 | * @description Delete all comments 212 | * @access Admin 213 | */ 214 | 215 | export const deleteAllComments = asyncHandler( 216 | async (req: Request, res: Response) => { 217 | await client.comment.deleteMany(); 218 | 219 | // response send 220 | successResponse(res, { 221 | statusCode: 200, 222 | message: "All comments deleted", 223 | }); 224 | } 225 | ); 226 | 227 | /** 228 | * @method DELETE 229 | * @route /api/comments/ 230 | * @description Delete =comment by ids 231 | * @access Admin 232 | */ 233 | 234 | export const deleteCommentsByIds = asyncHandler( 235 | async (req: Request, res: Response) => { 236 | const commentsId = await client.comment.findMany({ 237 | select: { 238 | id: true, 239 | }, 240 | }); 241 | 242 | let ids: number[] = []; 243 | commentsId.forEach((item) => { 244 | ids.push(Number(item.id)); 245 | }); 246 | 247 | const idsArray: number[] = req.body.ids; 248 | console.log(ids); 249 | 250 | idsArray.forEach((item) => { 251 | const includeId = ids.includes(item); 252 | if (!includeId) 253 | throw new CustomError(`No Comment found for id:${item}`, 404); 254 | }); 255 | 256 | // delete 257 | await client.comment.deleteMany({ 258 | where: { 259 | id: { 260 | in: req.body.ids, 261 | }, 262 | }, 263 | }); 264 | 265 | // response send 266 | successResponse(res, { 267 | statusCode: 200, 268 | message: "Successfully deleted ids data", 269 | }); 270 | } 271 | ); 272 | -------------------------------------------------------------------------------- /src/controllers/post.controller.ts: -------------------------------------------------------------------------------- 1 | import { log } from "console"; 2 | import asyncHandler from "express-async-handler"; 3 | import client from "../prisma/client/client"; 4 | import CustomError from "../helper/customError"; 5 | import { successResponse } from "../helper/responseHandler"; 6 | import { Request, Response } from "express"; 7 | import filterQuery from "../helper/filterQuery"; 8 | import paginationData from "../helper/pagination"; 9 | import { RequestWithUser } from "../types/types"; 10 | import e from "cors"; 11 | 12 | /** 13 | * @method GET 14 | * @route /api/posts 15 | * @description Get all posts 16 | * @access Public 17 | */ 18 | 19 | export const getAllPosts = asyncHandler(async (req: Request, res: Response) => { 20 | // filter query 21 | const { queries, filters } = filterQuery(req); 22 | 23 | const posts = await client.post.findMany({ 24 | include: { 25 | comments: true, 26 | user: true, 27 | }, 28 | where: { 29 | ...filters, 30 | }, 31 | // select: { 32 | // ...queries.select, // select and include not work together 33 | // }, 34 | skip: queries.skip, 35 | take: queries.take, 36 | orderBy: queries.orderBy, 37 | }); 38 | if (!posts.length) throw new CustomError("Couldn't find any post data", 404); 39 | 40 | //count 41 | const count = await client.post.count({ where: { ...filters } }); 42 | 43 | // pagination 44 | const pagination = paginationData(queries, count); 45 | 46 | // response send 47 | successResponse(res, { 48 | statusCode: 200, 49 | message: "All posts data", 50 | payload: { 51 | pagination, 52 | data: posts, 53 | }, 54 | }); 55 | }); 56 | 57 | /** 58 | * @method GET 59 | * @route /api/posts/:id 60 | * @description Get post by id 61 | * @access Public 62 | */ 63 | 64 | export const getPostById = asyncHandler(async (req: Request, res: Response) => { 65 | const post = await client.post.findUnique({ 66 | where: { id: Number(req.params.id) }, 67 | include: { 68 | comments: true, 69 | user: true, 70 | }, 71 | }); 72 | 73 | if (!post) throw new CustomError("Couldn't find any post data", 404); 74 | 75 | // response send 76 | successResponse(res, { 77 | statusCode: 200, 78 | message: "Post data", 79 | payload: { 80 | data: post, 81 | }, 82 | }); 83 | }); 84 | 85 | /** 86 | * @method POST 87 | * @route /api/posts 88 | * @description Create new post 89 | * @access Public 90 | */ 91 | 92 | export const createPost = asyncHandler( 93 | async (req: RequestWithUser, res: Response) => { 94 | let userId; 95 | 96 | //if admin or super admin 97 | if (req.me?.role === "admin" || req.me?.role === "superAdmin") { 98 | userId = req.body.userId; 99 | } 100 | // if user 101 | else { 102 | userId = req.me?.id; 103 | } 104 | 105 | // check user id 106 | if (!userId) throw new CustomError("User id not found", 404); 107 | 108 | const user = await client.user.findUnique({ 109 | where: { id: Number(userId) }, 110 | }); 111 | 112 | if (!user) throw new CustomError("User not found", 404); 113 | 114 | const post = await client.post.create({ 115 | data: { 116 | ...req.body, 117 | userId: Number(userId), 118 | }, 119 | }); 120 | 121 | // success response send 122 | successResponse(res, { 123 | statusCode: 201, 124 | message: "Post created successfully", 125 | payload: post, 126 | }); 127 | } 128 | ); 129 | 130 | /** 131 | * @method PUT 132 | * @route /api/posts/:id 133 | * @description Update post by id 134 | * @access Public 135 | */ 136 | 137 | export const updatePostById = asyncHandler( 138 | async (req: Request, res: Response) => { 139 | const post = await client.post.findUnique({ 140 | where: { id: Number(req.params.id) }, 141 | include: { 142 | comments: true, 143 | user: true, 144 | }, 145 | }); 146 | 147 | if (!post) throw new CustomError("Couldn't find any post data", 404); 148 | 149 | // if update user id 150 | if (req.body.userId) { 151 | const user = await client.user.findUnique({ 152 | where: { id: req.body.userId }, 153 | }); 154 | 155 | if (!user) throw new CustomError("User not found", 404); 156 | } 157 | 158 | // updated post 159 | const updatedPost = await client.post.update({ 160 | where: { id: Number(req.params.id) }, 161 | include: { 162 | comments: true, 163 | user: true, 164 | }, 165 | data: req.body, 166 | }); 167 | 168 | // response send 169 | successResponse(res, { 170 | statusCode: 200, 171 | message: "Post data updated successfully", 172 | payload: updatedPost, 173 | }); 174 | } 175 | ); 176 | 177 | /** 178 | * @method DELETE 179 | * @route /api/posts/:id 180 | * @description Delete post by id 181 | * @access Public 182 | */ 183 | 184 | export const deletePostById = asyncHandler( 185 | async (req: Request, res: Response) => { 186 | const post = await client.post.findUnique({ 187 | where: { id: Number(req.params.id) }, 188 | include: { 189 | comments: true, 190 | user: true, 191 | }, 192 | }); 193 | 194 | if (!post) throw new CustomError("Couldn't find any post data", 404); 195 | 196 | // deleted post 197 | const deletedPost = await client.post.delete({ 198 | where: { id: Number(req.params.id) }, 199 | }); 200 | 201 | // response send 202 | successResponse(res, { 203 | statusCode: 200, 204 | message: "Post data deleted successfully", 205 | payload: deletedPost, 206 | }); 207 | } 208 | ); 209 | 210 | /** 211 | * @method GET 212 | * @route /api/posts/:id/comments 213 | * @description Get all comments of a post 214 | * @access Public 215 | */ 216 | 217 | export const getAllCommentsOfPost = asyncHandler( 218 | async (req: Request, res: Response) => { 219 | const post = await client.post.findUnique({ 220 | where: { id: Number(req.params.id) }, 221 | include: { 222 | comments: true, 223 | user: true, 224 | }, 225 | }); 226 | 227 | if (!post) throw new CustomError("Couldn't find any post data", 404); 228 | 229 | // response send 230 | successResponse(res, { 231 | statusCode: 200, 232 | message: `All comments of post ${post.id}`, 233 | payload: { 234 | comments: post.comments, 235 | }, 236 | }); 237 | } 238 | ); /** 239 | 240 | * @method GET 241 | * @route /api/posts/:id/user 242 | * @description Get user of a post 243 | * @access Public 244 | */ 245 | 246 | export const getUserOfPost = asyncHandler( 247 | async (req: Request, res: Response) => { 248 | const post = await client.post.findUnique({ 249 | where: { id: Number(req.params.id) }, 250 | include: { 251 | user: true, 252 | }, 253 | }); 254 | 255 | if (!post) throw new CustomError("Couldn't find any post data", 404); 256 | 257 | // response send 258 | successResponse(res, { 259 | statusCode: 200, 260 | message: "User data of post id :" + post.id, 261 | payload: { 262 | user: post.user, 263 | }, 264 | }); 265 | } 266 | ); 267 | 268 | /** 269 | * @method POST 270 | * @route /api/posts/ 271 | * @description Bulk create posts 272 | * @access Admin 273 | */ 274 | 275 | export const bulkCreatePosts = asyncHandler( 276 | async (req: Request, res: Response) => { 277 | // before all data delete 278 | await client.post.deleteMany({}); 279 | 280 | if (!Array.isArray(req.body)) 281 | throw new CustomError("Invalid format.Support array type data", 400); 282 | 283 | // user id check 284 | 285 | // all users 286 | const users = await client.user.findMany(); 287 | 288 | req.body.forEach((item) => { 289 | const user = users.find((user) => user.id === Number(item.userId)); 290 | if (!user) throw new CustomError("User not found", 404); 291 | }); 292 | 293 | const createdPosts = await client.post.createMany({ 294 | data: req.body, 295 | }); 296 | 297 | // created posts data 298 | const posts = await client.post.findMany(); 299 | 300 | // response send 301 | successResponse(res, { 302 | statusCode: 201, 303 | message: "Posts created successfully", 304 | payload: { 305 | data: posts, 306 | }, 307 | }); 308 | } 309 | ); 310 | 311 | /** 312 | * @method DELETE 313 | * @route /api/posts/ 314 | * @description Bulk delete posts 315 | * @access Admin 316 | */ 317 | 318 | export const bulkDeletePosts = asyncHandler( 319 | async (req: Request, res: Response) => {} 320 | ); 321 | 322 | /** 323 | * @method DELETE 324 | * @route /api/posts 325 | * @description Delete posts by ids 326 | * @access Admin 327 | */ 328 | 329 | export const deletePostsByIds = asyncHandler(async (req, res) => {}); 330 | 331 | /** 332 | * @method PUT 333 | * @route /api/posts 334 | * @description Bulk update posts 335 | * @access Admin 336 | */ 337 | 338 | export const bulkUpdatePosts = asyncHandler(async (req, res) => {}); 339 | 340 | /** 341 | * @method PUT 342 | * @route /api/posts/comment/1 343 | * @description add comment in a post 344 | * @access Public 345 | */ 346 | 347 | export const commentOnPost = asyncHandler( 348 | async (req: Request, res: Response) => { 349 | const post = await client.post.findUnique({ 350 | where: { id: Number(req.params.id) }, 351 | }); 352 | 353 | if (!post) throw new CustomError("Couldn't find any post data", 404); 354 | 355 | // comment on post 356 | 357 | const data = await client.post.update({ 358 | where: { id: Number(req.params.id) }, 359 | data: { 360 | comments: { 361 | create: { 362 | ...req.body, 363 | }, 364 | }, 365 | }, 366 | include: { 367 | comments: true, 368 | }, 369 | }); 370 | 371 | // response send 372 | successResponse(res, { 373 | statusCode: 200, 374 | message: "Comment Added", 375 | payload: { 376 | data, 377 | }, 378 | }); 379 | } 380 | ); 381 | -------------------------------------------------------------------------------- /src/controllers/user.controller.ts: -------------------------------------------------------------------------------- 1 | import asyncHandler from "express-async-handler"; 2 | import client from "../prisma/client/client"; 3 | import { Request, Response } from "express"; 4 | import CustomError from "../helper/customError"; 5 | import { successResponse } from "../helper/responseHandler"; 6 | import hashPassword from "../helper/hashPassword"; 7 | import filterQuery from "../helper/filterQuery"; 8 | import paginationData from "../helper/pagination"; 9 | 10 | /** 11 | * @method GET 12 | * @route /api/users 13 | * @description Get all users 14 | * @access Public 15 | */ 16 | 17 | export const getAllUsers = asyncHandler( 18 | async (req: Request, res: Response): Promise => { 19 | // filter query 20 | const { queries, filters } = filterQuery(req); 21 | const users = await client.user.findMany({ 22 | include: { 23 | posts: { 24 | include: { 25 | comments: true, 26 | }, 27 | }, 28 | }, 29 | where: { 30 | ...filters, 31 | }, 32 | // select: { 33 | // ...queries.select, // select and include not work together 34 | // }, 35 | skip: queries.skip, 36 | take: queries.take, 37 | orderBy: queries.orderBy, 38 | }); 39 | 40 | if (!users.length) 41 | throw new CustomError("Couldn't find any user data", 404); 42 | 43 | //count 44 | const count = await client.user.count({ where: { ...filters } }); 45 | 46 | // pagination 47 | const pagination = paginationData(queries, count); 48 | 49 | // response send 50 | successResponse(res, { 51 | statusCode: 200, 52 | message: "All users data", 53 | payload: { 54 | pagination, 55 | data: users, 56 | }, 57 | }); 58 | } 59 | ); 60 | 61 | /** 62 | * @method GET 63 | * @route /api/users/:id 64 | * @description Get user by id 65 | * @access Public 66 | */ 67 | 68 | export const getUserById = asyncHandler( 69 | async (req: Request, res: Response): Promise => { 70 | const user = await client.user.findUnique({ 71 | where: { id: Number(req.params.id) }, 72 | include: { 73 | posts: true, 74 | }, 75 | }); 76 | 77 | if (!user) throw new CustomError("Couldn't find any user data", 404); 78 | 79 | // response send 80 | successResponse(res, { 81 | statusCode: 200, 82 | message: "User data", 83 | payload: { 84 | data: user, 85 | }, 86 | }); 87 | } 88 | ); 89 | 90 | /** 91 | * @method POST 92 | * @route /api/users 93 | * @description Create new user 94 | * @access Public 95 | */ 96 | 97 | export const createUser = asyncHandler( 98 | async (req: Request, res: Response): Promise => { 99 | const { email } = req.body; 100 | 101 | const existEmail = await client.user.findUnique({ 102 | where: { email }, 103 | }); 104 | const existUsername = await client.user.findUnique({ 105 | where: { username: req.body.username }, 106 | }); 107 | if (existEmail) throw new CustomError("Email already exist", 400); 108 | if (existUsername) throw new CustomError("Username already exist", 400); 109 | 110 | // create user 111 | const user = await client.user.create({ 112 | data: req.body, 113 | }); 114 | 115 | // response send 116 | successResponse(res, { 117 | statusCode: 201, 118 | message: "User created successfully", 119 | payload: { 120 | ...user, 121 | password: hashPassword(req.body.password), 122 | }, 123 | }); 124 | } 125 | ); 126 | 127 | /** 128 | * @method PUT 129 | * @route /api/users/:id 130 | * @description Update user by id 131 | * @access Public 132 | */ 133 | 134 | export const updateUserById = asyncHandler( 135 | async (req: Request, res: Response): Promise => { 136 | const user = await client.user.findUnique({ 137 | where: { id: Number(req.params.id) }, 138 | include: { 139 | posts: true, 140 | }, 141 | }); 142 | 143 | if (!user) throw new CustomError("Couldn't find any user data", 404); 144 | 145 | const updatedData = await client.user.update({ 146 | where: { id: Number(req.params.id) }, 147 | include: { 148 | posts: true, 149 | }, 150 | data: { ...req.body, password: hashPassword(req.body.password) }, 151 | }); 152 | 153 | // response send 154 | successResponse(res, { 155 | statusCode: 200, 156 | message: "User data updated successfully", 157 | payload: updatedData, 158 | }); 159 | } 160 | ); 161 | 162 | /** 163 | * @method DELETE 164 | * @route /api/users/:id 165 | * @description Delete user by id 166 | * @access Public 167 | */ 168 | 169 | export const deleteUserById = asyncHandler( 170 | async (req: Request, res: Response): Promise => { 171 | const user = await client.user.findUnique({ 172 | where: { id: Number(req.params.id) }, 173 | include: { 174 | posts: true, 175 | }, 176 | }); 177 | 178 | if (!user) throw new CustomError("Couldn't find any user data", 404); 179 | 180 | // if user has posts then delete all posts 181 | if (user.posts.length) { 182 | await client.post.deleteMany({ 183 | where: { userId: Number(req.params.id) }, 184 | }); 185 | } 186 | 187 | // deleted data 188 | const deletedData = await client.user.delete({ 189 | where: { id: Number(req.params.id) }, 190 | }); 191 | 192 | // response send 193 | successResponse(res, { 194 | statusCode: 200, 195 | message: "User data deleted successfully", 196 | payload: deletedData, 197 | }); 198 | } 199 | ); 200 | 201 | /** 202 | * @method GET 203 | * @route /api/users/:id/posts 204 | * @description Get all posts of a user 205 | * @access Public 206 | */ 207 | 208 | export const getAllPostsOfUser = asyncHandler( 209 | async (req: Request, res: Response): Promise => { 210 | // user check 211 | const user = await client.user 212 | .findUnique({ 213 | where: { id: Number(req.params.id) }, 214 | }) 215 | .posts(); 216 | 217 | if (!user) throw new CustomError("Couldn't find any user data", 404); 218 | 219 | if (!user) throw new CustomError("Couldn't find any post data", 404); 220 | 221 | // response send 222 | successResponse(res, { 223 | statusCode: 200, 224 | message: "All posts data of user", 225 | payload: { 226 | posts: user, 227 | }, 228 | }); 229 | } 230 | ); 231 | 232 | /** 233 | * @method GET 234 | * @route /api/users/:id/comments 235 | * @description Get all comments of a user 236 | * @access Public 237 | */ 238 | 239 | export const getAllCommentsOfUser = asyncHandler(async (req, res) => {}); 240 | 241 | /** 242 | * @method POST 243 | * @route /api/users/bulk-create 244 | * @description Bulk create users 245 | * @access Admin 246 | */ 247 | 248 | export const bulkCreateUsers = asyncHandler( 249 | async (req: Request, res: Response): Promise => { 250 | // before all data delete 251 | await client.user.deleteMany(); 252 | 253 | // only array of users accept 254 | if (!Array.isArray(req.body)) 255 | throw new CustomError("Only array of users accept", 400); 256 | 257 | // password hash 258 | const newUsersData = req.body.map((user) => { 259 | return { 260 | ...user, 261 | password: hashPassword(user.password), 262 | }; 263 | }); 264 | 265 | // create users 266 | const createdUsers = await client.user.createMany({ 267 | data: newUsersData, 268 | }); 269 | 270 | // created users data 271 | const users = await client.user.findMany(); 272 | 273 | // response send 274 | successResponse(res, { 275 | statusCode: 201, 276 | message: "Users created successfully", 277 | payload: { 278 | data: users, 279 | }, 280 | }); 281 | } 282 | ); 283 | 284 | /** 285 | * @method PUT 286 | * @route /api/users/ 287 | * @description Bulk update users 288 | * @access Admin 289 | */ 290 | 291 | export const bulkUpdateUsers = asyncHandler( 292 | async (req: Request, res: Response) => {} 293 | ); 294 | 295 | /** 296 | * @method DELETE 297 | * @route /api/users/ 298 | * @description Bulk delete users 299 | * @access Admin 300 | */ 301 | 302 | export const bulkDeleteUsers = asyncHandler( 303 | async (req: Request, res: Response): Promise => { 304 | // before all comment delete 305 | await client.comment.deleteMany(); 306 | 307 | // before all post delete 308 | await client.post.deleteMany(); 309 | 310 | await client.user.deleteMany(); 311 | 312 | // response send 313 | successResponse(res, { 314 | statusCode: 200, 315 | message: "All users data deleted successfully", 316 | }); 317 | } 318 | ); 319 | 320 | /** 321 | * @method DELETE 322 | * @route /api/users/ 323 | * @description Delete users by ids 324 | * @access Admin 325 | */ 326 | 327 | export const deleteUsersByIds = asyncHandler(async (req, res) => {}); 328 | 329 | /** 330 | * @description truncate 331 | */ 332 | -------------------------------------------------------------------------------- /src/helper/createJWT.ts: -------------------------------------------------------------------------------- 1 | import createError from "http-errors"; 2 | import jwt from "jsonwebtoken"; 3 | 4 | const createJWT = (payload: any, secretKey: string, expiresIn: string) => { 5 | // payload check 6 | if (typeof payload !== "object" || !payload) { 7 | throw createError(404, "Payload must be a non-empty object."); 8 | } 9 | 10 | // secret key check 11 | if (typeof secretKey !== "string" || !secretKey) { 12 | throw createError(404, "Secret key must be a non-empty string"); 13 | } 14 | 15 | // create token and return 16 | return jwt.sign(payload, secretKey, { 17 | expiresIn, 18 | }); 19 | }; 20 | 21 | // export token 22 | export default createJWT; 23 | -------------------------------------------------------------------------------- /src/helper/customError.ts: -------------------------------------------------------------------------------- 1 | class CustomError extends Error { 2 | status: number; 3 | 4 | constructor(message: string, status: number) { 5 | super(message); 6 | this.status = status; 7 | } 8 | } 9 | 10 | 11 | export default CustomError; -------------------------------------------------------------------------------- /src/helper/customRequest.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Response } from "express"; 2 | import { User } from "../types/types"; 3 | 4 | interface CustomRequest extends Request { 5 | me?: User; 6 | } 7 | 8 | const customRequest = ( 9 | req: CustomRequest, 10 | res: Response, 11 | next: NextFunction, 12 | loginUser: User 13 | ):void => { 14 | req.me = loginUser; 15 | next(); 16 | }; 17 | 18 | export default customRequest; 19 | -------------------------------------------------------------------------------- /src/helper/filterQuery.ts: -------------------------------------------------------------------------------- 1 | import { Request } from "express"; 2 | 3 | function filterQuery(req: Request): { queries: any; filters: any } { 4 | // copy all filter query 5 | let filters: any = { ...req.query }; 6 | 7 | // sort ,page,limit, fields exclude from filters 8 | const excludeFilters = ["sort", "page", "limit", "fields"]; 9 | excludeFilters.forEach((field) => delete filters[field]); 10 | 11 | // { userId: '1' } => { userId: 1 } 12 | if (filters && typeof filters.userId === "string") { 13 | filters.userId = Number(filters.userId); 14 | } 15 | 16 | // { id: '1' } => { id: 1 } 17 | if (filters && typeof filters.id === "string") { 18 | filters.id = Number(filters.id); 19 | } 20 | 21 | // queries 22 | const queries: { 23 | select?: {}; 24 | orderBy?: {}; // sort 25 | limit?: number; // limit 26 | page?: number; // page 27 | skip?: number; // skip 28 | take?: number; 29 | } = {}; 30 | 31 | // Specify the fields to display (?fields=name,age) 32 | if (typeof req.query.fields === "string") { 33 | const fields = req.query.fields.split(","); 34 | const fieldsObj = fields.reduce((acc: any, field) => { 35 | acc[field] = true; 36 | return acc; 37 | }, {}); 38 | 39 | queries.select = fieldsObj; 40 | } 41 | 42 | // sort query ( ?sort=-name[desc] , sort=name[asc]) 43 | if (typeof req.query.sort === "string") { 44 | const sortItems = req.query.sort.split(","); 45 | const sortItemObj = sortItems.reduce((acc: any, item) => { 46 | if (item.startsWith("-")) { 47 | acc[item.slice(1)] = "desc"; 48 | } else { 49 | acc[item] = "asc"; 50 | } 51 | return acc; 52 | }, {}); 53 | 54 | queries.orderBy = sortItemObj; 55 | } 56 | 57 | // default pagination query 58 | if (!req.query.page && !req.query.limit) { 59 | queries.take = 10; 60 | queries.page = 1; 61 | } 62 | 63 | // pagination query (?page=1&limit=10) 64 | if (req.query.page || req.query.limit) { 65 | const { page = 1, limit = 10 } = req.query; 66 | const skip = (Number(page) - 1) * Number(limit); 67 | queries.page = Number(page); 68 | queries.skip = skip; 69 | queries.take = Number(limit); 70 | } 71 | 72 | // return 73 | return { queries, filters }; 74 | } 75 | 76 | // export 77 | export default filterQuery; 78 | -------------------------------------------------------------------------------- /src/helper/hashPassword.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcryptjs"; 2 | 3 | const hashPassword = (password: string) => { 4 | const salt = bcrypt.genSaltSync(10); 5 | const passwordHash = bcrypt.hashSync(password, salt); 6 | return passwordHash; 7 | }; 8 | 9 | export default hashPassword; 10 | -------------------------------------------------------------------------------- /src/helper/matchPassword.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcryptjs"; 2 | import createError from "http-errors"; 3 | 4 | const matchPassword = (password: string, hashPassword: string) => { 5 | const isMatch = bcrypt.compareSync(password, hashPassword); 6 | 7 | if (!isMatch) { 8 | throw createError(400, "Wrong password"); 9 | } 10 | }; 11 | 12 | export default matchPassword; 13 | -------------------------------------------------------------------------------- /src/helper/pagination.ts: -------------------------------------------------------------------------------- 1 | import { PaginationType } from "../types/types"; 2 | 3 | const paginationData = (queries: any, count: any): PaginationType => { 4 | // page & limit 5 | const page = Number(queries.page); 6 | const limit = Number(queries.take); 7 | 8 | // pagination object 9 | const pagination = { 10 | totalDocuments: count, 11 | totalPages: Math.ceil(count / limit), 12 | currentPage: page, 13 | previousPage: page > 1 ? page - 1 : null, 14 | nextPage: page < Math.ceil(count / limit) ? page + 1 : null, 15 | }; 16 | 17 | // return pagination 18 | return pagination; 19 | }; 20 | 21 | // export 22 | export default paginationData; 23 | -------------------------------------------------------------------------------- /src/helper/randomHashCode.ts: -------------------------------------------------------------------------------- 1 | import randomString from "randomstring"; 2 | 3 | import bcrypt from "bcryptjs"; 4 | 5 | const randomHashCode = (length: number) => { 6 | const code = randomString.generate({ 7 | length, 8 | charset: "numeric", 9 | }); 10 | const salt = bcrypt.genSaltSync(10); 11 | const hashCode = bcrypt.hashSync(code, salt); 12 | return { 13 | code, 14 | hashCode, 15 | }; 16 | }; 17 | 18 | export default randomHashCode; 19 | -------------------------------------------------------------------------------- /src/helper/responseHandler.ts: -------------------------------------------------------------------------------- 1 | import { Response } from "express"; 2 | import { ApiResponse } from "../types/types"; 3 | 4 | export const errorResponse = ( 5 | res: Response, 6 | { 7 | statusCode = 500, 8 | message = "Unknown Server Error", 9 | }: { statusCode?: number; message?: string } 10 | ) => { 11 | return res.status(statusCode).json({ 12 | success: false, 13 | error: { 14 | status: statusCode, 15 | message, 16 | }, 17 | }); 18 | }; 19 | 20 | export const successResponse = ( 21 | res: Response, 22 | { statusCode = 200, message = "Success", payload = {} } 23 | ) => { 24 | const response: ApiResponse = { 25 | success: true, 26 | message, 27 | ...payload, 28 | }; 29 | 30 | return res.status(statusCode).json(response); 31 | }; 32 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | import app from "./server"; 3 | import { NODE_ENV, PORT } from "./secret"; 4 | 5 | // environment variables 6 | dotenv.config(); 7 | 8 | // listen 9 | app.listen(PORT, () => { 10 | if (NODE_ENV === "development") { 11 | console.log(``); 12 | console.log(`Server running on http://localhost:${PORT}`); 13 | console.log(``); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /src/middlewares/authorization.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Response } from "express"; 2 | import { RequestWithUser, Role } from "../types/types"; 3 | import { log } from "console"; 4 | 5 | export const authorization = (...role: Role[]) => { 6 | return async (req: RequestWithUser, res: Response, next: NextFunction) => { 7 | if (!role.includes(req?.me?.role as Role)) { 8 | return res.status(403).json({ 9 | Status: "Failed", 10 | message: "You don't have permission to perform this action", 11 | }); 12 | } 13 | 14 | // make sure the user is authorized 15 | 16 | let id = req?.params?.id; 17 | 18 | // if post id 19 | if (req.baseUrl === "/api/posts") { 20 | id = String(req?.me?.id); 21 | } 22 | 23 | if (id) { 24 | // others 25 | if ( 26 | req?.me?.role === "admin" || 27 | req?.me?.role === "superAdmin" || 28 | req.me?.id?.toString().split('"')[0] === id 29 | ) { 30 | return next(); 31 | } 32 | return res.status(403).json({ 33 | Status: "Failed", 34 | message: "You don't have permission to perform this action", 35 | }); 36 | } 37 | 38 | next(); 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /src/middlewares/errorHandler.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | import { errorResponse } from "../helper/responseHandler"; 3 | import { PrismaClientValidationError } from "@prisma/client/runtime/library"; 4 | 5 | const errorHandler = ( 6 | err: Error, 7 | re: Request, 8 | res: Response, 9 | next: NextFunction 10 | ): void => { 11 | let statusCode; 12 | if ("status" in err) { 13 | statusCode = Number(err.status); 14 | } 15 | let message = err?.message ? err?.message : "Unknown Server Error"; 16 | 17 | if (err instanceof PrismaClientValidationError) { 18 | message = "Validation Error or query error"; 19 | } 20 | 21 | errorResponse(res, { 22 | statusCode, 23 | message, 24 | }); 25 | }; 26 | 27 | export default errorHandler; 28 | -------------------------------------------------------------------------------- /src/middlewares/protect.ts: -------------------------------------------------------------------------------- 1 | import { CustomRequest, tokenType } from "./../types/types"; 2 | import jwt from "jsonwebtoken"; 3 | import asyncHandler from "express-async-handler"; 4 | import { jwtLoginTokenSecret } from "../secret"; 5 | import { errorResponse } from "../helper/responseHandler"; 6 | import { User } from "../types/types"; 7 | 8 | import { NextFunction, Response } from "express"; 9 | import CustomError from "../helper/customError"; 10 | import client from "../prisma/client/client"; 11 | import { log } from "console"; 12 | 13 | export const isLoggedIn = asyncHandler( 14 | async ( 15 | req: CustomRequest, 16 | res: Response, 17 | next: NextFunction 18 | ): Promise => { 19 | const authHeader: tokenType = req.headers.authorization; // || req.headers.Authorization; 20 | 21 | const authToken: tokenType = req?.cookies?.accessToken; 22 | 23 | let token: tokenType; 24 | 25 | if (authHeader || authToken) { 26 | token = authHeader?.split(" ")[1] || authToken; 27 | } 28 | 29 | if (!token) { 30 | throw new CustomError( 31 | "Unauthorized, Access token not found. Please login.", 32 | 401 33 | ); 34 | } 35 | 36 | jwt.verify(token, jwtLoginTokenSecret, async (err: any, decode: any) => { 37 | if (err) { 38 | return errorResponse(res, { 39 | statusCode: 400, 40 | message: "Unauthorized, Invalid access token.Please login again", 41 | }); 42 | } 43 | const loginUser = await client.user.findUnique({ 44 | where: { email: decode.email }, 45 | }); 46 | 47 | req.me = { ...loginUser, id: Number(loginUser?.id) } as User; 48 | 49 | next(); 50 | }); 51 | } 52 | ); 53 | 54 | export const isLoggedOut = asyncHandler(async (req, res, next) => { 55 | const authHeader: string | undefined = req.headers.authorization; //|| req.headers.Authorization; 56 | const authToken = req?.cookies?.accessToken; 57 | let token: tokenType; 58 | 59 | if (authHeader || authToken) { 60 | token = authHeader?.split(" ")[1] || authToken; 61 | } 62 | 63 | if (token) { 64 | throw new CustomError("User is already logged in", 400); 65 | } 66 | 67 | next(); 68 | }); 69 | -------------------------------------------------------------------------------- /src/middlewares/validator/file/comment.validator.ts: -------------------------------------------------------------------------------- 1 | import { body } from "express-validator"; 2 | 3 | export const commentValidator = [ 4 | body("body") 5 | .trim() 6 | .notEmpty() 7 | .withMessage("Body is required.Please provide a body.") 8 | .isLength({ min: 5 }) 9 | .withMessage("Body must be at least 5 characters long."), 10 | 11 | body("postId") 12 | .trim() 13 | .notEmpty() 14 | .withMessage("Post Id is required.Please provide a post id."), 15 | 16 | body("email") 17 | .trim() 18 | .notEmpty() 19 | .withMessage("Email is required.Please provide an email.") 20 | .isEmail() 21 | .withMessage("Please provide a valid email address."), 22 | 23 | body("name") 24 | .trim() 25 | .notEmpty() 26 | .withMessage("Name is required.Please provide a name.") 27 | .isLength({ min: 3 }) 28 | .withMessage("Name must be at least 3 characters long."), 29 | ]; 30 | -------------------------------------------------------------------------------- /src/middlewares/validator/file/post.validator.ts: -------------------------------------------------------------------------------- 1 | import { body } from "express-validator"; 2 | 3 | export const postValidator = [ 4 | body("title") 5 | .trim() 6 | .notEmpty() 7 | .withMessage("Title is required.Please provide a title.") 8 | .isLength({ min: 5 }) 9 | .withMessage("Title must be at least 5 characters long."), 10 | 11 | body("body") 12 | .trim() 13 | .notEmpty() 14 | .withMessage("Body is required.Please provide a body.") 15 | .isLength({ min: 10 }) 16 | .withMessage("Body must be at least 10 characters long."), 17 | // body("userId") 18 | // .trim() 19 | // .notEmpty() 20 | // .withMessage("User Id is required.Please provide a user id."), 21 | ]; 22 | 23 | export const postCommentDataValidator = [ 24 | body("name") 25 | .trim() 26 | .notEmpty() 27 | .withMessage("Name is required.Please provide user name.") 28 | .isLength({ min: 3 }) 29 | .withMessage("Body must be at least 3 characters long."), 30 | body("email") 31 | .trim() 32 | .notEmpty() 33 | .withMessage("Email is required.Please provide a email.") 34 | .isEmail() 35 | .withMessage("Please provide a valid email."), 36 | body("body") 37 | .trim() 38 | .notEmpty() 39 | .withMessage("Body is required.Please provide a body.") 40 | .isLength({ min: 10 }) 41 | .withMessage("Body must be at least 10 characters long."), 42 | ]; 43 | -------------------------------------------------------------------------------- /src/middlewares/validator/file/user.validator.ts: -------------------------------------------------------------------------------- 1 | import { log } from "console"; 2 | import { body } from "express-validator"; 3 | import CustomError from "../../../helper/customError"; 4 | 5 | export const userRegisterValidator = [ 6 | body("name") 7 | .trim() 8 | .notEmpty() 9 | .withMessage("Name is required.Please provide a name.") 10 | .isLength({ min: 3 }) 11 | .withMessage("Name must be at least 3 characters long."), 12 | 13 | body("username") 14 | .trim() 15 | .notEmpty() 16 | .withMessage("Username is required.Please provide a username.") 17 | .isLength({ min: 2 }) 18 | .withMessage("Username must be at least 2 characters long."), 19 | 20 | body("email") 21 | .trim() 22 | .notEmpty() 23 | .withMessage("Email is required.Please provide a email.") 24 | .isEmail() 25 | .withMessage("Please provide a valid email."), 26 | 27 | body("password") 28 | .notEmpty() 29 | .withMessage("Password is required.Please provide a password.") 30 | .isLength({ min: 6 }) 31 | .withMessage("Password must be at least 6 characters long."), 32 | 33 | body("gender") 34 | .notEmpty() 35 | .withMessage("Gender is required.Please provide a gender.") 36 | .custom((value) => { 37 | if (!/^(male|female)$/.test(value)) 38 | throw new CustomError("Gender value can be only male or female", 400); 39 | return true; 40 | }), 41 | ]; 42 | 43 | export const userLoginValidator = [ 44 | body("email") 45 | .trim() 46 | .notEmpty() 47 | .withMessage("Email is required.Please provide a email.") 48 | .isEmail() 49 | .withMessage("Please provide a valid email."), 50 | 51 | body("password") 52 | .notEmpty() 53 | .withMessage("Password is required.Please provide a password.") 54 | .isLength({ min: 6 }) 55 | .withMessage("Password must be at least 6 characters long."), 56 | ]; 57 | 58 | const passwordResetValidator = [ 59 | body("password") 60 | .notEmpty() 61 | .withMessage("Password is required.Please provide a password.") 62 | .isLength({ min: 6 }) 63 | .withMessage("Password must be at least 6 characters long."), 64 | 65 | body("code") 66 | .notEmpty() 67 | .withMessage("Code is required.Please provide a code.") 68 | .isLength({ min: 4 }) 69 | .withMessage("Code must be 4 characters long."), 70 | ]; 71 | 72 | export const userVerifyCodeValidator = [ 73 | body("code") 74 | .notEmpty() 75 | .withMessage("Code is required.Please provide a code.") 76 | .isLength({ min: 4 }) 77 | .withMessage("Code must be at least 4 characters long."), 78 | ]; 79 | 80 | export const userResendCodeValidator = [ 81 | body("email") 82 | .trim() 83 | .notEmpty() 84 | .withMessage("Email is required.Please provide a email.") 85 | .isEmail() 86 | .withMessage("Please provide a valid email."), 87 | ]; 88 | 89 | const userPasswordUpdateValidator = [ 90 | body("oldPassword").notEmpty().withMessage("Old password is required"), 91 | 92 | body("newPassword") 93 | .notEmpty() 94 | .withMessage("New password is required") 95 | .isLength({ min: 6 }) 96 | .withMessage("Password must be at least 6 characters long."), 97 | 98 | body("confirmPassword") 99 | .notEmpty() 100 | .withMessage("Confirm password is required") 101 | .isLength({ min: 6 }) 102 | .withMessage("Password must be at least 6 characters long") 103 | .custom((value, { req }) => { 104 | if (value !== req.body.newPassword) { 105 | // throw createHttpError(400, "Password does not match"); 106 | } 107 | return true; 108 | }), 109 | ]; 110 | 111 | const userResetPasswordValidator = [ 112 | body("email") 113 | .trim() 114 | .notEmpty() 115 | .withMessage("Email is required.Please provide a email.") 116 | .isEmail() 117 | .withMessage("Please provide a valid email."), 118 | ]; 119 | 120 | const resetPasswordValidatorByCode = [ 121 | body("password") 122 | .notEmpty() 123 | .withMessage("Password is required.Please provide a password.") 124 | .isLength({ min: 6 }) 125 | .withMessage("Password must be at least 6 characters long."), 126 | 127 | body("confirmPassword") 128 | .notEmpty() 129 | .withMessage("Confirm password is required") 130 | .isLength({ min: 6 }) 131 | .withMessage("Password must be at least 6 characters long") 132 | .custom((value, { req }) => { 133 | if (value !== req.body.password) { 134 | // throw createHttpError(400, "Password does not match"); 135 | } 136 | return true; 137 | }), 138 | 139 | body("code") 140 | .notEmpty() 141 | .withMessage("Code is required.Please provide a code.") 142 | .isLength({ min: 4 }) 143 | .withMessage("Code must be at least 4 characters long."), 144 | ]; 145 | 146 | const resetPasswordValidatorByURL = [ 147 | body("password") 148 | .notEmpty() 149 | .withMessage("Password is required.Please provide a password.") 150 | .isLength({ min: 6 }) 151 | .withMessage("Password must be at least 6 characters long."), 152 | 153 | body("confirmPassword") 154 | .notEmpty() 155 | .withMessage("Confirm password is required") 156 | .isLength({ min: 6 }) 157 | .withMessage("Password must be at least 6 characters long") 158 | .custom((value, { req }) => { 159 | if (value !== req.body.password) { 160 | // throw createHttpError(400, "Password does not match"); 161 | } 162 | return true; 163 | }), 164 | ]; 165 | -------------------------------------------------------------------------------- /src/middlewares/validator/validation.ts: -------------------------------------------------------------------------------- 1 | import { validationResult } from "express-validator"; 2 | import asyncHandler from "express-async-handler"; 3 | import { NextFunction, Request, Response } from "express"; 4 | import { errorResponse } from "../../helper/responseHandler"; 5 | 6 | const runValidation = asyncHandler( 7 | async (req: Request, res: Response, next: NextFunction): Promise => { 8 | const errors = validationResult(req); 9 | 10 | if (!errors.isEmpty()) { 11 | return errorResponse(res, { 12 | statusCode: 422, 13 | message: errors.array()[0].msg, 14 | }); 15 | } 16 | next(); 17 | } 18 | ); 19 | 20 | export default runValidation; 21 | -------------------------------------------------------------------------------- /src/prisma/client/client.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | const client = new PrismaClient(); 4 | 5 | export default client; 6 | -------------------------------------------------------------------------------- /src/prisma/migrations/20231112190230_yes/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "Gender" AS ENUM ('male', 'female'); 3 | 4 | -- CreateEnum 5 | CREATE TYPE "Role" AS ENUM ('admin', 'user', 'superAdmin'); 6 | 7 | -- CreateTable 8 | CREATE TABLE "User" ( 9 | "id" SERIAL NOT NULL, 10 | "name" TEXT NOT NULL, 11 | "username" TEXT NOT NULL, 12 | "email" TEXT NOT NULL, 13 | "password" TEXT NOT NULL, 14 | "gender" "Gender" NOT NULL, 15 | "role" "Role" DEFAULT 'user', 16 | "phone" TEXT, 17 | "age" INTEGER, 18 | "isVerified" BOOLEAN NOT NULL DEFAULT false, 19 | "cretedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 20 | "updatedAt" TIMESTAMP(3) NOT NULL, 21 | 22 | CONSTRAINT "User_pkey" PRIMARY KEY ("id") 23 | ); 24 | 25 | -- CreateTable 26 | CREATE TABLE "Post" ( 27 | "id" SERIAL NOT NULL, 28 | "title" TEXT NOT NULL, 29 | "body" TEXT NOT NULL, 30 | "userId" INTEGER NOT NULL, 31 | "cretedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 32 | "updatedAt" TIMESTAMP(3) NOT NULL, 33 | "published" BOOLEAN NOT NULL DEFAULT false, 34 | 35 | CONSTRAINT "Post_pkey" PRIMARY KEY ("id") 36 | ); 37 | 38 | -- CreateTable 39 | CREATE TABLE "Comment" ( 40 | "id" SERIAL NOT NULL, 41 | "body" TEXT NOT NULL, 42 | "name" TEXT NOT NULL, 43 | "email" TEXT NOT NULL, 44 | "postId" INTEGER NOT NULL, 45 | 46 | CONSTRAINT "Comment_pkey" PRIMARY KEY ("id") 47 | ); 48 | 49 | -- CreateIndex 50 | CREATE UNIQUE INDEX "User_username_key" ON "User"("username"); 51 | 52 | -- CreateIndex 53 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); 54 | 55 | -- AddForeignKey 56 | ALTER TABLE "Post" ADD CONSTRAINT "Post_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 57 | 58 | -- AddForeignKey 59 | ALTER TABLE "Comment" ADD CONSTRAINT "Comment_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 60 | -------------------------------------------------------------------------------- /src/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /src/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "postgresql" 10 | url = env("DATABASE_URL") 11 | } 12 | 13 | model User { 14 | id Int @id @default(autoincrement()) 15 | name String 16 | username String @unique 17 | email String @unique 18 | password String 19 | gender Gender 20 | role Role? @default(user) 21 | phone String? 22 | age Int? 23 | posts Post[] 24 | isVerified Boolean @default(false) 25 | cretedAt DateTime @default(now()) 26 | updatedAt DateTime @updatedAt 27 | } 28 | 29 | model Post { 30 | id Int @id @default(autoincrement()) 31 | title String 32 | body String 33 | userId Int 34 | user User @relation(fields: [userId], references: [id]) 35 | comments Comment[] 36 | cretedAt DateTime @default(now()) 37 | updatedAt DateTime @updatedAt 38 | published Boolean @default(false) 39 | } 40 | 41 | model Comment { 42 | id Int @id @default(autoincrement()) 43 | body String 44 | name String 45 | email String 46 | postId Int 47 | post Post @relation(fields: [postId], references: [id]) 48 | } 49 | 50 | enum Gender { 51 | male 52 | female 53 | } 54 | 55 | 56 | 57 | enum Role { 58 | admin 59 | user 60 | superAdmin 61 | } 62 | -------------------------------------------------------------------------------- /src/routes/auth.route.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | 3 | import runValidation from "../middlewares/validator/validation"; 4 | import { 5 | userLoginValidator, 6 | userRegisterValidator, 7 | userResendCodeValidator, 8 | userVerifyCodeValidator, 9 | } from "../middlewares/validator/file/user.validator"; 10 | import { 11 | activeUserAccountByCode, 12 | me, 13 | resendActivationCode, 14 | userLogin, 15 | userLogout, 16 | userRegister, 17 | } from "../controllers/auth.controllers"; 18 | import { isLoggedIn, isLoggedOut } from "../middlewares/protect"; 19 | 20 | const authRouter = express.Router(); 21 | 22 | authRouter 23 | .route("/register") 24 | .post(userRegisterValidator, runValidation, isLoggedOut, userRegister); 25 | 26 | // active user account by code 27 | authRouter 28 | .route("/activate") 29 | .post( 30 | isLoggedOut, 31 | userVerifyCodeValidator, 32 | runValidation, 33 | activeUserAccountByCode 34 | ); 35 | 36 | // resend verification code to email 37 | authRouter 38 | .route("/resend-active-code") 39 | .post( 40 | isLoggedOut, 41 | userResendCodeValidator, 42 | runValidation, 43 | resendActivationCode 44 | ); 45 | 46 | authRouter 47 | .route("/login") 48 | .post(isLoggedOut, userLoginValidator, runValidation, userLogin); 49 | 50 | authRouter.route("/logout").post(isLoggedIn, userLogout); 51 | 52 | authRouter.route("/me").get(isLoggedIn, me); 53 | 54 | // export 55 | export default authRouter; 56 | -------------------------------------------------------------------------------- /src/routes/comment.route.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { 3 | bulkCommentsCreate, 4 | createComment, 5 | deleteAllComments, 6 | deleteCommentById, 7 | deleteCommentsByIds, 8 | getAllComments, 9 | getCommentById, 10 | updateCommentById, 11 | } from "../controllers/comment.controller"; 12 | import { commentValidator } from "../middlewares/validator/file/comment.validator"; 13 | import runValidation from "../middlewares/validator/validation"; 14 | 15 | const commentRouter = express.Router(); 16 | 17 | commentRouter 18 | .route("/") 19 | .get(getAllComments) 20 | .post(commentValidator, runValidation, createComment); 21 | 22 | // bulk comment create and delete 23 | commentRouter.route("/bulk").delete(deleteAllComments).post(bulkCommentsCreate); 24 | 25 | // delete by ids 26 | commentRouter.delete("/delete-by-ids", deleteCommentsByIds); 27 | 28 | commentRouter 29 | .route("/:id") 30 | .get(getCommentById) 31 | .put(updateCommentById) 32 | .delete(deleteCommentById); 33 | 34 | // export 35 | export default commentRouter; 36 | -------------------------------------------------------------------------------- /src/routes/post.route.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { 3 | bulkCreatePosts, 4 | bulkDeletePosts, 5 | commentOnPost, 6 | createPost, 7 | deletePostById, 8 | getAllCommentsOfPost, 9 | getAllPosts, 10 | getPostById, 11 | getUserOfPost, 12 | updatePostById, 13 | } from "../controllers/post.controller"; 14 | import { 15 | postCommentDataValidator, 16 | postValidator, 17 | } from "../middlewares/validator/file/post.validator"; 18 | import runValidation from "../middlewares/validator/validation"; 19 | import { authorization } from "../middlewares/authorization"; 20 | import { isLoggedIn } from "../middlewares/protect"; 21 | 22 | const postRouter = express.Router(); 23 | 24 | postRouter 25 | .route("/") 26 | .get(getAllPosts) 27 | .post(isLoggedIn, postValidator, runValidation, createPost); 28 | 29 | // bulk post create and delete 30 | postRouter 31 | .route("/bulk") 32 | .post(isLoggedIn, authorization("admin", "superAdmin"), bulkCreatePosts) 33 | .delete(isLoggedIn, authorization("admin", "superAdmin"), bulkDeletePosts); 34 | 35 | postRouter 36 | .route("/:id") 37 | .get(getPostById) 38 | .put(isLoggedIn, authorization("admin", "superAdmin", "user"), updatePostById) 39 | .delete( 40 | isLoggedIn, 41 | authorization("admin", "superAdmin", "user"), 42 | deletePostById 43 | ); 44 | 45 | // comment add in post 46 | postRouter.put( 47 | "/:id/add-comment", 48 | postCommentDataValidator, 49 | runValidation, 50 | commentOnPost 51 | ); 52 | 53 | // get all post comments 54 | postRouter.route("/:id/comments").get(getAllCommentsOfPost); 55 | 56 | // get user of post 57 | postRouter.route("/:id/user").get(getUserOfPost); 58 | 59 | // export 60 | export default postRouter; 61 | -------------------------------------------------------------------------------- /src/routes/user.route.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { 3 | bulkCreateUsers, 4 | bulkDeleteUsers, 5 | createUser, 6 | deleteUserById, 7 | getAllPostsOfUser, 8 | getAllUsers, 9 | getUserById, 10 | updateUserById, 11 | } from "../controllers/user.controller"; 12 | import { userRegisterValidator } from "../middlewares/validator/file/user.validator"; 13 | import runValidation from "../middlewares/validator/validation"; 14 | import { isLoggedIn } from "../middlewares/protect"; 15 | import { authorization } from "../middlewares/authorization"; 16 | 17 | const userRouter = express.Router(); 18 | 19 | userRouter 20 | .route("/") 21 | .get(getAllUsers) 22 | .post( 23 | isLoggedIn, 24 | authorization("admin", "superAdmin"), 25 | userRegisterValidator, 26 | runValidation, 27 | createUser 28 | ); 29 | 30 | // bulk user create and delete 31 | userRouter 32 | .route("/bulk") 33 | .post(bulkCreateUsers) 34 | .delete(isLoggedIn, authorization("admin", "superAdmin"), bulkDeleteUsers); 35 | 36 | userRouter 37 | .route("/:id") 38 | .get(getUserById) 39 | .put(isLoggedIn, authorization("admin", "superAdmin", "user"), updateUserById) 40 | .delete( 41 | isLoggedIn, 42 | authorization("admin", "superAdmin", "user"), 43 | deleteUserById 44 | ); 45 | 46 | // get all post of user 47 | userRouter.route("/:id/posts").get(getAllPostsOfUser); 48 | 49 | // export 50 | export default userRouter; 51 | -------------------------------------------------------------------------------- /src/secret.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | import { 3 | NodeEnvType, 4 | PortType, 5 | jwtKeyExpireType, 6 | jwtSecretKeyType, 7 | } from "./types/types"; 8 | import { JwkKeyExportOptions } from "crypto"; 9 | 10 | // config dotenv 11 | dotenv.config(); 12 | 13 | export const PORT: PortType = process.env.SERVER_PORT || 8000; 14 | export const NODE_ENV: NodeEnvType = process.env.NODE_ENV || "development"; 15 | 16 | export const jwtLoginTokenSecret = 17 | process.env.JWT_LOGIN_TOKEN_SECRET || "secret"; 18 | 19 | // account verification key and expire 20 | export const verifyKey: jwtSecretKeyType = 21 | process.env.JWT_VERIFY_SECRET_KEY || "secret"; 22 | export const verifyKeyExpire: jwtKeyExpireType = 23 | process.env.VERIFY_JWT_EXPIRE || "300s"; 24 | 25 | export const jwtLoginTokenExpire: string = 26 | process.env.JWT_LOGIN_EXPIRE || "365d"; 27 | 28 | export const jwtSecret = process.env.JWT_SECRET_KEY || "secret"; 29 | 30 | export const smtpHost: string = process.env.SMTP_HOST || "smtp.gmail.com"; 31 | export const smtpPort: number = Number(process.env.SMTP_PORT) || 587; 32 | export const emailUser: string = process.env.EMAIL_HOST_USER || ""; 33 | export const emailPass: string = process.env.EMAIL_HOST_PASSWORD || ""; 34 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import cors from "cors"; 2 | import dotenv from "dotenv"; 3 | import express, { Request, Response, NextFunction } from "express"; 4 | import morgan from "morgan"; 5 | import cookies from "cookie-parser"; 6 | import errorHandler from "./middlewares/errorHandler"; 7 | import corsOptions from "./config/corsSetup"; 8 | import CustomError from "./helper/customError"; 9 | import { NODE_ENV } from "./secret"; 10 | import userRouter from "./routes/user.route"; 11 | import postRouter from "./routes/post.route"; 12 | import commentRouter from "./routes/comment.route"; 13 | import authRouter from "./routes/auth.route"; 14 | 15 | const app = express(); 16 | 17 | // environment variables 18 | dotenv.config(); 19 | 20 | // static files 21 | app.use(express.static("public")); 22 | 23 | // cookies 24 | app.use(cookies()); 25 | 26 | //middlewares 27 | app.use(express.json()); 28 | app.use(express.urlencoded({ extended: false })); 29 | 30 | // cors setup 31 | app.use(cors(corsOptions)); 32 | 33 | // morgan 34 | if (NODE_ENV === "development") app.use(morgan("dev")); 35 | 36 | // home route 37 | app.get("/", (req: Request, res: Response): void => { 38 | res.status(200).json({ 39 | success: true, 40 | message: "Welcome to the home route", 41 | }); 42 | }); 43 | 44 | app.use("/api/users", userRouter); 45 | app.use("/api/posts", postRouter); 46 | app.use("/api/comments", commentRouter); 47 | app.use("/api/auth", authRouter); 48 | 49 | // invalid route handler 50 | app.use((req: Request, res: Response, next: NextFunction): void => { 51 | next(new CustomError("Invalid route", 404)); 52 | }); 53 | 54 | // error handler 55 | app.use(errorHandler); 56 | 57 | // export 58 | export default app; 59 | -------------------------------------------------------------------------------- /src/types/types.ts: -------------------------------------------------------------------------------- 1 | import { Request } from "express"; 2 | 3 | export type PortType = number | string; 4 | 5 | export type NodeEnvType = string; 6 | 7 | export interface User { 8 | id: number; 9 | email: string; 10 | password: string; 11 | role?: string; 12 | isVerified?: boolean; 13 | } 14 | 15 | export interface Post { 16 | id: number; 17 | title: string; 18 | body: string; 19 | userId: number; 20 | } 21 | 22 | export interface CustomRequest extends Request { 23 | me?: User | undefined; 24 | } 25 | 26 | export type tokenType = string | undefined; 27 | 28 | export interface PaginationType { 29 | totalDocuments: number; 30 | totalPages: number; 31 | currentPage: number; 32 | previousPage: number | null; 33 | nextPage: number | null; 34 | } 35 | 36 | export interface ApiResponse { 37 | success: boolean; 38 | message: string; 39 | payload?: { 40 | pagination?: PaginationType; 41 | data?: {} | []; 42 | }; 43 | } 44 | 45 | export type Role = "admin" | "superAdmin" | "user"; 46 | 47 | export interface RequestWithUser extends Request { 48 | me?: User; 49 | } 50 | 51 | export type jwtSecretKeyType = string; 52 | export type jwtKeyExpireType = string; 53 | 54 | export interface EmailDataType { 55 | code: string; 56 | email: string; 57 | subject: string; 58 | } 59 | -------------------------------------------------------------------------------- /src/utils/email/accountActivationMail.ts: -------------------------------------------------------------------------------- 1 | import nodemailer from "nodemailer"; 2 | import asyncHandler from "express-async-handler"; 3 | import createError from "http-errors"; 4 | import { emailPass, emailUser, smtpHost, smtpPort } from "../../secret"; 5 | import { EmailDataType } from "../../types/types"; 6 | 7 | const transport = nodemailer.createTransport({ 8 | host: smtpHost, // host name 9 | port: smtpPort, // host port number 10 | auth: { 11 | user: emailUser, // email address 12 | pass: emailPass, // email password 13 | }, 14 | }); 15 | 16 | const sendAccountVerifyMail = async ( 17 | emailData: EmailDataType 18 | ): Promise => { 19 | try { 20 | const { code } = emailData; 21 | const mailInfo = { 22 | from: `Activation Code <${emailUser}>`, // sender address 23 | to: emailData.email, // list of receivers 24 | subject: emailData.subject, // Subject line 25 | html: ` 26 |

KIN Account Activation Code

We received a request to create a account.To verify your email address, please use the following verification code:

${code}

Thanks,
KIN A Voluntary Organization,SUST

27 | 28 | `, 29 | }; 30 | 31 | const info = await transport.sendMail(mailInfo); 32 | console.log("Message sent: %s", info.envelope.to[0]); 33 | } catch (error) { 34 | console.log("Message sent failed!"); 35 | } 36 | }; 37 | 38 | export default sendAccountVerifyMail; 39 | -------------------------------------------------------------------------------- /src/utils/email/passwordResetMail.ts: -------------------------------------------------------------------------------- 1 | const nodemailer = require("nodemailer"); 2 | const { 3 | clientURL, 4 | emailPass, 5 | emailUser, 6 | smtpHost, 7 | smtpPort, 8 | } = require("../../secret.js"); 9 | 10 | const transport = nodemailer.createTransport({ 11 | host: smtpHost, // host name 12 | port: smtpPort, // host port number 13 | auth: { 14 | user: emailUser, // email address 15 | pass: emailPass, // email password 16 | }, 17 | }); 18 | 19 | const sendPasswordResetMail = async (emailData) => { 20 | try { 21 | const { code } = emailData; 22 | const mailInfo = { 23 | from: `Password Reset <${emailUser}>`, // sender address 24 | to: emailData.email, // list of receivers 25 | subject: emailData.subject, // Subject line 26 | html: ` 27 |

KIN Password Reset Code

We received a request to reset your account password.To verify your email address, please use the following verification code:

${code}

Thanks,
KIN A Voluntary Organization,SUST

28 | 29 | `, 30 | }; 31 | 32 | const info = await transport.sendMail(mailInfo); 33 | 34 | console.log("Message sent: %s", info.envelope.to[0]); 35 | } catch (error) { 36 | console.log("Message sent failed!"); 37 | } 38 | }; 39 | 40 | module.exports = sendPasswordResetMail; 41 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | "rootDir": "./src", /* Specify the root folder within your source files. */ 30 | "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | "outDir": "./dist", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 83 | 84 | /* Type Checking */ 85 | "strict": true, /* Enable all strict type-checking options. */ 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 108 | }, 109 | // "include": ["./src"] 110 | } 111 | --------------------------------------------------------------------------------