├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── assets ├── icons │ ├── eslint.svg │ ├── express.svg │ ├── heroku.svg │ ├── jest.svg │ ├── swagger.svg │ ├── tsnode.svg │ └── typescript.svg └── images │ ├── getCalendarByMonth.png │ ├── getCalendarFullYear.png │ └── getCalendarNow.png ├── jest.config.mjs ├── notation.txt ├── package-lock.json ├── package.json ├── src ├── app │ └── index.ts ├── constants │ ├── defaultDate.ts │ ├── names.ts │ ├── numbers.ts │ └── status.ts ├── controllers │ ├── getCalendarByMonth.ts │ ├── getCalendarFullYear.ts │ ├── getCalendarNow.ts │ └── index.ts ├── documents │ └── swagger.json ├── errors │ └── index.ts ├── index.ts ├── middlewares │ ├── error.ts │ ├── index.ts │ ├── validationMonth.ts │ └── validationYear.ts ├── routers │ ├── calendarRouter.ts │ ├── fullyearRoute.ts │ └── root.ts ├── services │ ├── fullYearCalendar.ts │ ├── generateCalendarBoard.ts │ ├── helper │ │ ├── generateBoard.ts │ │ ├── getLastDay.ts │ │ └── toFirstUpperCase.ts │ ├── index.ts │ ├── isThirtyOne.ts │ ├── monthCalendar.ts │ ├── monthNowCalendar.ts │ └── sum.ts ├── tests │ ├── constants │ │ └── index.ts │ └── integration-test │ │ ├── getCalendarByMonth.test.ts │ │ ├── getCalendarFullYear.test.ts │ │ └── getCalendarNow.test.ts └── types │ └── index.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es2021": true, 4 | "node": true, 5 | "jest":true 6 | }, 7 | "extends": [ 8 | "airbnb-base" 9 | ], 10 | "parser": "@typescript-eslint/parser", 11 | "parserOptions": { 12 | "ecmaVersion": 12, 13 | "sourceType": "module" 14 | }, 15 | "plugins": [ 16 | "@typescript-eslint" 17 | ], 18 | "rules": { 19 | "no-import-assign": "off", 20 | "no-shadow": "off", 21 | "no-console": "off", 22 | "import/extensions": [ 23 | "error", 24 | "ignorePackages", 25 | { 26 | "js": "never", 27 | "ts": "never" 28 | } 29 | ], 30 | "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], 31 | "@typescript-eslint/no-unused-vars":["error", { "argsIgnorePattern": "^_" }] 32 | }, 33 | "settings": { 34 | "import/resolver": { 35 | "node": { 36 | "extensions": [".js", ".ts"] 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .env 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 David Gonzaga 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Calendar JSON API {...} :calendar: 2 | 3 | ## Sumary 4 | 5 | - [Introduction](#introduction) 6 | - [Instructions to start locally](#instructions-to-start-locally) 7 | - [Swagger documentation](#swagger-documentation) 8 | - [Endpoints](#endpoints) 9 | - [Current month](#current-month) 10 | - [Month name](#month-name) 11 | - [Month name and year](#month-name-and-year) 12 | - [Current year](#current-year) 13 | - [Year](#year) 14 | - [Technologies](#technologies) 15 | - [Tests](#tests) 16 | - [Instructions to start tests locally](#instructions-to-start-tests-locally) 17 | - [Deployment](#deployment) 18 | 19 | --- 20 | 21 | # Introduction 22 | 23 | The Calendar JSON API can be used to provide you with a calendar formatted as a 2D array. 24 | 25 | For that, you may choose to query for the selected month, or the selected year, as explained in the endpoints below. 26 | 27 | --- 28 | 29 | # Swagger documentation 30 | 31 | Access the swagger documentation: 32 | 33 | Production: https://calendar-json-app.adaptable.app/docs 34 | 35 | Local: `http://localhost:3002/docs` 36 | 37 | --- 38 | 39 | # Instructions to start locally 40 | 41 | - clone the repository `git clone git@github.com:Gonzagadavid/calendar-json-api.git` 42 | - enter the directory `cd calendar-json-api` 43 | - install the dependencies `npm install` 44 | - run the command `npm run dev` 45 | - the api will be available at `http://localhost:3002` 46 | 47 | --- 48 | 49 | # Endpoints 50 | 51 | ## Current month /month 52 | 53 | Returns the calendar of the current date. 54 | 55 | ```url 56 | GET https://calendar-json-app.adaptable.app/month 57 | ``` 58 | *E.G:* 59 | 60 | Being the current date December 2021, the default response will be: 61 | 62 | ```json 63 | { 64 | "December":[ 65 | [ 0, 0, 0, 1, 2, 3, 4], 66 | [ 5, 6, 7, 8, 9, 10, 11], 67 | [ 12, 13, 14, 15, 16, 17, 18], 68 | [ 19, 20, 21, 22, 23, 24, 25], 69 | [ 26, 27, 28, 29, 30, 31, 0] 70 | ], 71 | "year": 2021 72 | } 73 | ``` 74 | 75 | *I.E:* 76 | 77 | **December 2021** 78 | 79 | | Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | 80 | | :-----:| :-----:| :------:| :--------:| :-------:| :-----:| :-------:| 81 | | | | | 1 | 2 | 3 | 4 | 82 | | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 83 | | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 84 | | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 85 | | 26 | 27 | 28 | 29 | 30 | 31 | | 86 | 87 | --- 88 | 89 | ## Month name /month/\ 90 | 91 | Returns current year`s month calendar. 92 | 93 | ```url 94 | GET https://calendar-json-app.adaptable.app/month/ 95 | ``` 96 | *E.G:* 97 | 98 | Being 2021 the current year, and November the desired month, the request must be sent as follows: 99 | 100 | ```url 101 | GET https://calendar-json-app.adaptable.app/month/november 102 | ``` 103 | And the response will be: 104 | 105 | ```json 106 | { 107 | "November":[ 108 | [ 0, 1, 2, 3, 4, 5, 6], 109 | [ 7, 8, 9, 10, 11, 12, 13], 110 | [ 14, 15, 16, 17, 18, 19, 20], 111 | [ 21, 22, 23, 24, 25, 26, 27], 112 | [ 28, 29, 30, 0, 0, 0, 0] 113 | ], 114 | "year": 2021 115 | } 116 | ``` 117 | 118 | *I.E:* 119 | 120 | **November 2021** 121 | 122 | | Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | 123 | | :-----:| :-----:| :------:| :--------:| :-------:| :-----:| :-------:| 124 | | | 1 | 2 | 3 | 4 | 5 | 6 | 125 | | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 126 | | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 127 | | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 128 | | 28 | 29 | 30 | | | | | 129 | 130 | --- 131 | 132 | ## Month name and year /month/\?year=\ 133 | 134 | Returns the calendar for the desired month and year. 135 | 136 | ```url 137 | GET https://calendar-json-app.adaptable.app/month/?year= 138 | ``` 139 | 140 | *E.G:* 141 | 142 | Desired month: October 143 | Desired year: 2012 144 | 145 | ```url 146 | GET https://calendar-json-app.adaptable.app/month/october?year=2012 147 | ``` 148 | 149 | And the response will be: 150 | 151 | ```json 152 | { 153 | "October":[ 154 | [ 0, 1, 2, 3, 4, 5, 6], 155 | [ 7, 8, 9, 10, 11, 12, 13], 156 | [ 14, 15, 16, 17, 18, 19, 20], 157 | [ 21, 22, 23, 24, 25, 26, 27], 158 | [ 28, 29, 30, 31, 0, 0, 0] 159 | ], 160 | "year": 2012 161 | } 162 | ``` 163 | 164 | *I.E:* 165 | 166 | 167 | **October 2012** 168 | 169 | | Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | 170 | | :-----:| :-----:| :------:| :--------:| :-------:| :-----:| :-------:| 171 | | | 1 | 2 | 3 | 4 | 5 | 6 | 172 | | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 173 | | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 174 | | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 175 | | 28 | 29 | 30 | 31 | | | | 176 | 177 | --- 178 | 179 | ## Current year /fullyear 180 | 181 | Returns the complete calendar for the current year. 182 | 183 | ```url 184 | GET https://calendar-json-app.adaptable.app/fullyear 185 | ``` 186 | 187 | *E.G:* 188 | 189 | Being the current year 2021, the response will be: 190 | 191 | ```json 192 | { 193 | "year":2021, 194 | "January":[ 195 | [ 0, 0, 0, 0, 0, 1, 2], 196 | [ 3, 4, 5, 6, 7, 8, 9], 197 | [ 10, 11, 12, 13, 14, 15, 16], 198 | [ 17, 18, 19, 20, 21, 22, 23], 199 | [ 24, 25, 26, 27, 28, 29, 30], 200 | [ 31, 0, 0, 0, 0, 0, 0] 201 | ], 202 | "February":[ 203 | [ 0, 1, 2, 3, 4, 5, 6], 204 | [ 7, 8, 9, 10, 11, 12, 13], 205 | [ 14, 15, 16, 17, 18, 19, 20], 206 | [ 21, 22, 23, 24, 25, 26, 27], 207 | [ 28, 0, 0, 0, 0, 0, 0] 208 | ], 209 | "March":[ 210 | [ 0, 1, 2, 3, 4, 5, 6], 211 | [ 7, 8, 9, 10, 11, 12, 13], 212 | [ 14, 15, 16, 17, 18, 19, 20], 213 | [ 21, 22, 23, 24, 25, 26, 27], 214 | [ 28, 29, 30, 31, 0, 0, 0] 215 | ], 216 | "April":[ 217 | [ 0, 0, 0, 0, 1, 2, 3], 218 | [ 4, 5, 6, 7, 8, 9, 10], 219 | [11, 12, 13, 14, 15, 16, 17], 220 | [18, 19, 20, 21, 22, 23, 24], 221 | [25, 26, 27, 28, 29, 30, 0] 222 | ], 223 | "May":[ 224 | [ 0, 0, 0, 0, 0, 0, 1], 225 | [ 2, 3, 4, 5, 6, 7, 8], 226 | [ 9, 10, 11, 12, 13, 14, 15], 227 | [ 16, 17, 18, 19, 20, 21, 22], 228 | [ 23, 24, 25, 26, 27, 28, 29], 229 | [ 30, 31, 0, 0, 0, 0, 0] 230 | ], 231 | "June":[ 232 | [ 0, 0, 1, 2, 3, 4, 5], 233 | [ 6, 7, 8, 9, 10, 11, 12], 234 | [ 13, 14, 15, 16, 17, 18, 19], 235 | [ 20, 21, 22, 23, 24, 25, 26], 236 | [ 27, 28, 29, 30, 0, 0, 0] 237 | ], 238 | "July":[ 239 | [ 0, 0, 0, 0, 1, 2, 3], 240 | [ 4, 5, 6, 7, 8, 9, 10], 241 | [ 11, 12, 13, 14, 15, 16, 17], 242 | [ 18, 19, 20, 21, 22, 23, 24], 243 | [ 25, 26, 27, 28, 29, 30, 31] 244 | ], 245 | "August":[ 246 | [ 1, 2, 3, 4, 5, 6, 7], 247 | [ 8, 9, 10, 11, 12, 13, 14], 248 | [ 15, 16, 17, 18, 19, 20, 21], 249 | [ 22, 23, 24, 25, 26, 27, 28], 250 | [ 29, 30, 31, 0, 0, 0, 0] 251 | ], 252 | "September":[ 253 | [ 0, 0, 0, 1, 2, 3, 4], 254 | [ 5, 6, 7, 8, 9, 10 ,11], 255 | [ 12 ,13 ,14 ,15 ,16 ,17 ,18], 256 | [ 19 ,20 ,21 ,22 ,23 ,24 ,25], 257 | [ 26 ,27 ,28 ,29 ,30, 0, 0] 258 | ], 259 | "October":[ 260 | [ 0, 0, 0, 0, 0, 1, 2], 261 | [ 3, 4, 5, 6, 7, 8, 9], 262 | [ 10, 11, 12, 13, 14, 15, 16], 263 | [ 17, 18, 19, 20, 21, 22, 23], 264 | [ 24, 25, 26, 27, 28, 29, 30], 265 | [ 31, 0, 0, 0, 0, 0, 0] 266 | ], 267 | "November":[ 268 | [ 0, 1, 2, 3, 4, 5, 6], 269 | [ 7, 8, 9, 10, 11, 12, 13], 270 | [ 14, 15, 16, 17, 18, 19, 20], 271 | [ 21, 22, 23, 24, 25, 26, 27], 272 | [ 28, 29, 30, 0, 0, 0, 0] 273 | ], 274 | "December":[ 275 | [ 0, 0, 0, 1, 2, 3, 4], 276 | [ 5, 6, 7, 8, 9, 10, 11], 277 | [ 12, 13, 14, 15, 16, 17, 18], 278 | [ 19, 20, 21, 22, 23, 24, 25], 279 | [ 26, 27, 28, 29, 30, 31, 0] 280 | ] 281 | } 282 | ``` 283 | --- 284 | 285 | ## Year /fullyear/\ 286 | 287 | Returns the desired year's complete calendar. 288 | 289 | ```url 290 | GET https://calendar-json-app.adaptable.app/fullyear/ 291 | ``` 292 | 293 | *E.G* 294 | 295 | Entering the year 2031 as a parameter 296 | 297 | ```url 298 | GET https://calendar-json-app.adaptable.app/fullyear/2031 299 | ``` 300 | the response will be: 301 | 302 | ```json 303 | { 304 | "year": 2031, 305 | "January":[ 306 | [ 0, 0, 0, 1, 2, 3, 4], 307 | [ 5, 6, 7, 8, 9, 10, 11], 308 | [ 12, 13, 14, 15, 16, 17, 18], 309 | [ 19, 20, 21, 22, 23, 24, 25], 310 | [ 26, 27, 28, 29, 30, 31, 0] 311 | ], 312 | "February":[ 313 | [ 0, 0, 0, 0, 0, 0, 1], 314 | [ 2, 3, 4, 5, 6, 7, 8], 315 | [ 9, 10, 11, 12, 13, 14, 15], 316 | [ 16, 17, 18, 19, 20, 21, 22], 317 | [ 23, 24, 25, 26, 27, 28, 0], 318 | [ 0, 0, 0, 0, 0, 0, 0] 319 | ], 320 | "March":[ 321 | [ 0, 0, 0, 0, 0, 0, 1], 322 | [ 2, 3, 4, 5, 6, 7, 8], 323 | [ 9, 10, 11, 12, 13, 14, 15], 324 | [ 16, 17, 18, 19, 20, 21, 22], 325 | [ 23, 24, 25, 26, 27, 28, 29], 326 | [ 30, 31, 0, 0, 0, 0, 0] 327 | ], 328 | "April":[ 329 | [ 0, 0, 1, 2, 3, 4, 5], 330 | [ 6, 7, 8, 9, 10, 11, 12], 331 | [ 13, 14, 15, 16, 17, 18, 19], 332 | [ 20, 21, 22, 23, 24, 25, 26], 333 | [ 27, 28, 29, 30, 0, 0, 0] 334 | ], 335 | "May":[ 336 | [ 0, 0, 0, 0, 1, 2, 3], 337 | [ 4, 5, 6, 7, 8, 9, 10], 338 | [ 11, 12, 13, 14, 15, 16, 17], 339 | [ 18, 19, 20, 21, 22, 23, 24], 340 | [ 25, 26, 27, 28, 29, 30, 31] 341 | ], 342 | "June":[ 343 | [ 1, 2, 3, 4, 5, 6, 7], 344 | [ 8, 9, 10, 11, 12, 13, 14], 345 | [ 15, 16, 17, 18, 19, 20, 21], 346 | [ 22, 23, 24, 25, 26, 27, 28], 347 | [ 29, 30, 0, 0, 0, 0, 0] 348 | ], 349 | "July":[ 350 | [ 0, 0, 1, 2, 3, 4, 5], 351 | [ 6, 7, 8, 9, 10, 11, 12], 352 | [ 13, 14, 15, 16, 17, 18, 19], 353 | [ 20, 21, 22, 23, 24, 25, 26], 354 | [ 27, 28, 29, 30 ,31, 0, 0] 355 | ], 356 | "August":[ 357 | [ 0, 0, 0, 0, 0, 1, 2], 358 | [ 3, 4, 5, 6, 7, 8, 9], 359 | [ 10, 11, 12, 13, 14, 15, 16], 360 | [ 17, 18, 19, 20, 21, 22, 23], 361 | [ 24, 25, 26, 27, 28, 29, 30], 362 | [ 31, 0, 0, 0, 0, 0, 0] 363 | ], 364 | "September":[ 365 | [ 0, 1, 2, 3, 4, 5, 6], 366 | [ 7, 8, 9, 10, 11, 12, 13], 367 | [ 14, 15, 16, 17, 18, 19, 20], 368 | [ 21, 22, 23, 24, 25, 26, 27], 369 | [ 28, 29, 30, 0, 0, 0, 0] 370 | ], 371 | "October":[ 372 | [ 0, 0, 0, 1, 2, 3, 4], 373 | [ 5, 6, 7, 8, 9, 10, 11], 374 | [ 12, 13, 14, 15, 16, 17, 18], 375 | [ 19, 20, 21, 22, 23, 24, 25], 376 | [ 26, 27, 28, 29, 30, 31, 0] 377 | ], 378 | "November":[ 379 | [ 0, 0, 0, 0, 0, 0, 1], 380 | [ 2, 3, 4, 5, 6, 7, 8], 381 | [ 9, 10, 11, 12, 13, 14, 15], 382 | [ 16, 17, 18, 19, 20, 21, 22], 383 | [ 23, 24, 25, 26, 27, 28, 29], 384 | [ 30, 0, 0, 0, 0, 0, 0] 385 | ], 386 | "December":[ 387 | [ 0, 1, 2, 3, 4, 5, 6], 388 | [ 7, 8, 9, 10, 11, 12, 13], 389 | [ 14, 15, 16, 17, 18, 19, 20], 390 | [ 21, 22, 23, 24, 25, 26, 27], 391 | [ 28, 29, 30, 31, 0, 0, 0] 392 | ] 393 | } 394 | ``` 395 | --- 396 | 397 | # Technologies 398 | 399 | - Typescript 400 | - Express 401 | - Jest 402 | - Ts node dev 403 | - Super test 404 | - Dotenv 405 | - Http status code 406 | - Eslint 407 | - Swagger 408 | 409 |
410 | typescript icon 411 |     412 | express icon 413 |     414 | jest icon 415 |     416 | tsnode icon 417 |     418 | eslint icon 419 |     420 | eslint icon 421 |     422 |
423 | 424 | --- 425 | 426 | # Tests 427 | 428 | Developed integration tests, using jest and supertest, for all endpoints 429 | 430 | tests results 431 | tests results 432 | tests results 433 | 434 | --- 435 | 436 | # Instructions to start tests locally 437 | 438 | - run the command `npm test` 439 | 440 | --- 441 | 442 | # Deployment 443 | 444 | Deployment on [Adaptable](https://adaptable.io/) 445 | 446 | adaptable icon 447 | -------------------------------------------------------------------------------- /assets/icons/eslint.svg: -------------------------------------------------------------------------------- 1 | ESLint -------------------------------------------------------------------------------- /assets/icons/express.svg: -------------------------------------------------------------------------------- 1 | Express -------------------------------------------------------------------------------- /assets/icons/heroku.svg: -------------------------------------------------------------------------------- 1 | Heroku -------------------------------------------------------------------------------- /assets/icons/jest.svg: -------------------------------------------------------------------------------- 1 | Jest -------------------------------------------------------------------------------- /assets/icons/swagger.svg: -------------------------------------------------------------------------------- 1 | Swagger -------------------------------------------------------------------------------- /assets/icons/tsnode.svg: -------------------------------------------------------------------------------- 1 | ts-node -------------------------------------------------------------------------------- /assets/icons/typescript.svg: -------------------------------------------------------------------------------- 1 | TypeScript -------------------------------------------------------------------------------- /assets/images/getCalendarByMonth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gonzagadavid/calendar-json-api/cdb0acdb1c1381e52a931c3b7e51553051e80f2d/assets/images/getCalendarByMonth.png -------------------------------------------------------------------------------- /assets/images/getCalendarFullYear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gonzagadavid/calendar-json-api/cdb0acdb1c1381e52a931c3b7e51553051e80f2d/assets/images/getCalendarFullYear.png -------------------------------------------------------------------------------- /assets/images/getCalendarNow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gonzagadavid/calendar-json-api/cdb0acdb1c1381e52a931c3b7e51553051e80f2d/assets/images/getCalendarNow.png -------------------------------------------------------------------------------- /jest.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | const module = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; 6 | 7 | export default module; 8 | -------------------------------------------------------------------------------- /notation.txt: -------------------------------------------------------------------------------- 1 | declare global { 2 | export namespace NodeJS { 3 | export interface ProcessEnv { 4 | MONGO_URI: string 5 | PORT: string 6 | JWT_SECRET: string 7 | } 8 | } 9 | } 10 | 11 | ------------------------------------------------------------------- 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "calenderjsonapi", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "engines": { 7 | "node": "16.0.0", 8 | "npm": "8.1.2" 9 | }, 10 | "scripts": { 11 | "test": "jest", 12 | "dev": "ts-node-dev --respawn --ignore-watch src/node_modules --transpile-only src index.ts", 13 | "start": "node dist/index.js", 14 | "postinstall": "tsc", 15 | "watch-node": "nodemon dist/index.js", 16 | "watch-ts": "tsc -w" 17 | }, 18 | "keywords": [ 19 | "calendar", 20 | "API", 21 | "calendario", 22 | "JSON" 23 | ], 24 | "author": "David Gonzaga", 25 | "license": "ISC", 26 | "dependencies": { 27 | "@types/cors": "^2.8.12", 28 | "@types/express": "^4.17.13", 29 | "@types/jest": "^27.0.3", 30 | "@types/supertest": "^2.0.11", 31 | "@types/swagger-ui-express": "^4.1.3", 32 | "cors": "^2.8.5", 33 | "dotenv": "^10.0.0", 34 | "express": "^4.17.1", 35 | "http-status-codes": "^2.1.4", 36 | "jest": "^27.4.5", 37 | "nodemon": "^2.0.15", 38 | "supertest": "^6.1.6", 39 | "swagger-ui-express": "^4.3.0", 40 | "ts-jest": "^27.1.2", 41 | "typescript": "^4.5.4" 42 | }, 43 | "devDependencies": { 44 | "@types/dotenv": "^8.2.0", 45 | "@types/node": "^17.0.0", 46 | "@typescript-eslint/eslint-plugin": "^5.7.0", 47 | "@typescript-eslint/parser": "^5.7.0", 48 | "eslint": "^8.4.1", 49 | "eslint-config-airbnb-base": "^15.0.0", 50 | "eslint-plugin-import": "^2.25.3", 51 | "ts-node-dev": "^1.1.8" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/app/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import cors from 'cors'; 3 | import swaggerUi from 'swagger-ui-express'; 4 | import { error } from '../middlewares'; 5 | import routerRoot from '../routers/root'; 6 | import swaggerDocs from '../documents/swagger.json'; 7 | 8 | const app = express(); 9 | 10 | app.use(cors()); 11 | 12 | app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocs)); 13 | 14 | app.use('/', routerRoot); 15 | 16 | app.use(error); 17 | 18 | export default app; 19 | -------------------------------------------------------------------------------- /src/constants/defaultDate.ts: -------------------------------------------------------------------------------- 1 | const date = new Date(); 2 | const defaultYear = date.getFullYear(); 3 | const defaultMonth = date.getMonth(); 4 | 5 | export { defaultMonth, defaultYear }; 6 | -------------------------------------------------------------------------------- /src/constants/names.ts: -------------------------------------------------------------------------------- 1 | const monthNames = [ 2 | 'january', 3 | 'february', 4 | 'march', 5 | 'april', 6 | 'may', 7 | 'june', 8 | 'july', 9 | 'august', 10 | 'september', 11 | 'october', 12 | 'november', 13 | 'december']; 14 | 15 | export default monthNames; 16 | -------------------------------------------------------------------------------- /src/constants/numbers.ts: -------------------------------------------------------------------------------- 1 | export const ZERO = 0; 2 | 3 | export const ONE = 1; 4 | 5 | export const FOUR = 4; 6 | 7 | export const FIVE = 5; 8 | 9 | export const SIX = 6; 10 | 11 | export const SEVEN = 7; 12 | 13 | export const THIRTHY = 30; 14 | 15 | export const THIRTHY_ONE = 31; 16 | -------------------------------------------------------------------------------- /src/constants/status.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from 'http-status-codes'; 2 | 3 | export const { 4 | OK, 5 | INTERNAL_SERVER_ERROR, 6 | CONFLICT, 7 | } = StatusCodes; 8 | -------------------------------------------------------------------------------- /src/controllers/getCalendarByMonth.ts: -------------------------------------------------------------------------------- 1 | import { monthCalendar } from '../services'; 2 | import { DateCompleted, Handler } from '../types'; 3 | import { OK } from '../constants/status'; 4 | 5 | const getCalendarByMonth: Handler = (req, res, next) => { 6 | try { 7 | const { month, year } = req.date as DateCompleted; 8 | const calendar = monthCalendar(year, month); 9 | 10 | res.status(OK).json(calendar); 11 | } catch (err) { 12 | next(err); 13 | } 14 | }; 15 | 16 | export default getCalendarByMonth; 17 | -------------------------------------------------------------------------------- /src/controllers/getCalendarFullYear.ts: -------------------------------------------------------------------------------- 1 | import { OK } from '../constants/status'; 2 | import { fullYearCalendar } from '../services'; 3 | import { Dateyear, Handler } from '../types'; 4 | 5 | const getCalendarFullYear: Handler = (req, res, next) => { 6 | try { 7 | const { year } = req.date as Dateyear; 8 | const calendar = fullYearCalendar(year); 9 | 10 | res.status(OK).json(calendar); 11 | } catch (err) { 12 | next(err); 13 | } 14 | }; 15 | 16 | export default getCalendarFullYear; 17 | -------------------------------------------------------------------------------- /src/controllers/getCalendarNow.ts: -------------------------------------------------------------------------------- 1 | import { monthNowCalendar } from '../services'; 2 | import { Handler } from '../types'; 3 | import { OK } from '../constants/status'; 4 | 5 | const getCalendarNow: Handler = (_req, res, next) => { 6 | try { 7 | const calendar = monthNowCalendar(); 8 | 9 | res.status(OK).json(calendar); 10 | } catch (err) { 11 | next(err); 12 | } 13 | }; 14 | 15 | export default getCalendarNow; 16 | -------------------------------------------------------------------------------- /src/controllers/index.ts: -------------------------------------------------------------------------------- 1 | import getCalendarByMonth from './getCalendarByMonth'; 2 | import getCalendarFullYear from './getCalendarFullYear'; 3 | import getCalendarNow from './getCalendarNow'; 4 | 5 | export { 6 | getCalendarNow, 7 | getCalendarByMonth, 8 | getCalendarFullYear, 9 | }; 10 | -------------------------------------------------------------------------------- /src/documents/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.0", 3 | "info": { 4 | "title": "Calendar Json API", 5 | "description": "The Calendar JSON API can be used to provide you with a calendar formatted as a 2D array", 6 | "contact": { 7 | "email": "gonzagadaviddev@gmail.com" 8 | }, 9 | "license": "MIT", 10 | "version": "1.0.0" 11 | }, 12 | "servers": [ 13 | { 14 | "url": "https://calendar-json-app.adaptable.app", 15 | "description": "Production API" 16 | }, 17 | { 18 | "url": "http://localhost:3002", 19 | "description": "Local API" 20 | } 21 | ], 22 | "paths": { 23 | "/month": { 24 | "get": { 25 | "summary": "Current month", 26 | "description": "Returns the calendar of the current date", 27 | "tags": [ 28 | "Month" 29 | ], 30 | "responses": { 31 | "200": { 32 | "description": "OK" 33 | } 34 | } 35 | } 36 | }, 37 | "/month/{monthName}": { 38 | "get": { 39 | "summary": "Month name and year", 40 | "description": "Returns the calendar for the desired month and year", 41 | "tags": [ 42 | "Month" 43 | ], 44 | "parameters": [ 45 | { 46 | "name": "monthName", 47 | "in": "path", 48 | "description": "the desired month.", 49 | "requiired": true 50 | }, 51 | { 52 | "name": "year", 53 | "in": "query", 54 | "description": "the desired year.", 55 | "requiired": false 56 | } 57 | ], 58 | "responses": { 59 | "200": { 60 | "description": "OK" 61 | }, 62 | "409": { 63 | "description": "CONFLICT - Month name invalid or the year must have at least four digit" 64 | } 65 | } 66 | } 67 | }, 68 | "/fullyear": { 69 | "get": { 70 | "summary": "Current year", 71 | "description": "Returns the complete calendar for the current year", 72 | "tags": [ 73 | "Year" 74 | ], 75 | "responses": { 76 | "200": { 77 | "description": "OK" 78 | }, 79 | "409": { 80 | "description": "CONFLICT - Month name invalid or the year must have at least four digit" 81 | } 82 | } 83 | } 84 | }, 85 | "/fullyear/{year}": { 86 | "get": { 87 | "summary": "Current year", 88 | "description": "Returns the complete calendar for the current year", 89 | "tags": [ 90 | "Year" 91 | ], 92 | "parameters": [ 93 | { 94 | "name": "year", 95 | "in": "path", 96 | "description": "the desired year.", 97 | "requiired": true 98 | } 99 | ], 100 | "responses": { 101 | "200": { 102 | "description": "OK" 103 | }, 104 | "409": { 105 | "description": "CONFLICT - Month name invalid or the year must have at least four digit" 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /src/errors/index.ts: -------------------------------------------------------------------------------- 1 | import { CONFLICT } from '../constants/status'; 2 | 3 | export const INVALID_MONTH = { 4 | status: CONFLICT, 5 | message: 'month name invalid', 6 | }; 7 | 8 | export const INVALID_YEAR = { 9 | status: CONFLICT, 10 | message: 'the year must have at least four digits', 11 | }; 12 | 13 | export const INTERNAL_ERROR = 'Internal Error'; 14 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { config } from 'dotenv'; 2 | import app from './app'; 3 | 4 | config(); 5 | 6 | const { PORT = 3002 } = process.env; 7 | const started = `started in port ${PORT}`; 8 | 9 | app.listen(PORT, () => console.log(started)); 10 | -------------------------------------------------------------------------------- /src/middlewares/error.ts: -------------------------------------------------------------------------------- 1 | import { ErrorRequestHandler } from 'express'; 2 | import { INTERNAL_SERVER_ERROR } from '../constants/status'; 3 | import { INTERNAL_ERROR } from '../errors'; 4 | 5 | const error: ErrorRequestHandler = (err, req, res, _next) => { 6 | const status = err.status || INTERNAL_SERVER_ERROR; 7 | const message = err.message || INTERNAL_ERROR; 8 | 9 | res.status(status).json({ message }); 10 | }; 11 | export default error; 12 | -------------------------------------------------------------------------------- /src/middlewares/index.ts: -------------------------------------------------------------------------------- 1 | import error from './error'; 2 | import validationMonth from './validationMonth'; 3 | import validationYear from './validationYear'; 4 | 5 | export { 6 | validationYear, 7 | validationMonth, 8 | error, 9 | 10 | }; 11 | -------------------------------------------------------------------------------- /src/middlewares/validationMonth.ts: -------------------------------------------------------------------------------- 1 | import monthNames from '../constants/names'; 2 | import { ZERO } from '../constants/numbers'; 3 | import { INVALID_MONTH } from '../errors'; 4 | import { Handler } from '../types'; 5 | 6 | const validationMonth: Handler = (req, _res, next) => { 7 | const { monthName } = req.params; 8 | const monthPosition = monthNames.indexOf(monthName.toLowerCase()); 9 | 10 | if (monthPosition < ZERO) return next(INVALID_MONTH); 11 | 12 | req.date = { month: monthPosition }; 13 | 14 | return next(); 15 | }; 16 | 17 | export default validationMonth; 18 | -------------------------------------------------------------------------------- /src/middlewares/validationYear.ts: -------------------------------------------------------------------------------- 1 | import { defaultYear } from '../constants/defaultDate'; 2 | import { FOUR } from '../constants/numbers'; 3 | import { INVALID_YEAR } from '../errors'; 4 | import { Handler } from '../types'; 5 | 6 | const validationYear: Handler = (req, _res, next) => { 7 | const { query, params } = req; 8 | const year = query.year || params.year; 9 | const { date } = req; 10 | 11 | if (!year) { 12 | req.date = { ...date, year: defaultYear }; 13 | return next(); 14 | } 15 | 16 | if (Number.isNaN(+year) || year.length !== FOUR) return next(INVALID_YEAR); 17 | req.date = { ...date, year: +year }; 18 | 19 | return next(); 20 | }; 21 | 22 | export default validationYear; 23 | -------------------------------------------------------------------------------- /src/routers/calendarRouter.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { getCalendarByMonth, getCalendarNow } from '../controllers'; 3 | import { validationMonth, validationYear } from '../middlewares'; 4 | 5 | const router = Router(); 6 | 7 | router.get('/', getCalendarNow); 8 | 9 | router.get('/:monthName', validationMonth, validationYear, getCalendarByMonth); 10 | 11 | export default router; 12 | -------------------------------------------------------------------------------- /src/routers/fullyearRoute.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { getCalendarFullYear } from '../controllers'; 3 | import { validationYear } from '../middlewares'; 4 | 5 | const router = Router(); 6 | 7 | router.get('/', validationYear, getCalendarFullYear); 8 | router.get('/:year', validationYear, getCalendarFullYear); 9 | 10 | export default router; 11 | -------------------------------------------------------------------------------- /src/routers/root.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import caledarRoute from './calendarRouter'; 3 | import fullyearRoute from './fullyearRoute'; 4 | 5 | const routerRoot = Router(); 6 | 7 | routerRoot.use('/month', caledarRoute); 8 | routerRoot.use('/fullyear', fullyearRoute); 9 | 10 | export default routerRoot; 11 | -------------------------------------------------------------------------------- /src/services/fullYearCalendar.ts: -------------------------------------------------------------------------------- 1 | import { monthCalendar } from '.'; 2 | import monthNames from '../constants/names'; 3 | import { CalendarYear, CalendarResp } from '../types'; 4 | 5 | const fullYearCalendar: CalendarYear = (year) => { 6 | const calendarYear = monthNames.reduce((calendar, _monthName, position) => { 7 | const calendarMonth = monthCalendar(year, position); 8 | return { ...calendar, ...calendarMonth }; 9 | }, { year }); 10 | 11 | return calendarYear; 12 | }; 13 | 14 | export default fullYearCalendar; 15 | -------------------------------------------------------------------------------- /src/services/generateCalendarBoard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ONE, SEVEN, ZERO, 3 | } from '../constants/numbers'; 4 | import { CalendarBoard } from '../types'; 5 | import generateBoard from './helper/generateBoard'; 6 | import getLastDay from './helper/getLastDay'; 7 | 8 | const generateCalendarBoard: CalendarBoard = (year, month) => { 9 | const lastDay = getLastDay(year, month); 10 | const board = generateBoard(); 11 | const dayInit = new Date(year, month, ONE).getDay(); 12 | 13 | let day = ZERO; 14 | 15 | const calendarBoard = board.map((week, weekCount) => week.map((empty, index) => { 16 | const position = weekCount * SEVEN + index; 17 | if (position < dayInit || day >= lastDay) return empty; 18 | day += ONE; 19 | return day; 20 | })).filter((week) => !week.every((day) => !day)); 21 | 22 | return calendarBoard; 23 | }; 24 | 25 | export default generateCalendarBoard; 26 | -------------------------------------------------------------------------------- /src/services/helper/generateBoard.ts: -------------------------------------------------------------------------------- 1 | import { SEVEN, SIX, ZERO } from '../../constants/numbers'; 2 | import { Board } from '../../types'; 3 | 4 | const generateBoard: Board = () => { 5 | const board = Array(SIX).fill(Array(SEVEN).fill(ZERO)); 6 | return board; 7 | }; 8 | 9 | export default generateBoard; 10 | -------------------------------------------------------------------------------- /src/services/helper/getLastDay.ts: -------------------------------------------------------------------------------- 1 | import { THIRTHY_ONE } from '../../constants/numbers'; 2 | import { LastDay } from '../../types'; 3 | 4 | const getLastDay: LastDay = (year, month) => { 5 | let day = THIRTHY_ONE; 6 | let date = new Date(year, month, day); 7 | 8 | while (date.getMonth() !== month) { 9 | day -= 1; 10 | date = new Date(year, month, day); 11 | } 12 | 13 | return day; 14 | }; 15 | 16 | export default getLastDay; 17 | -------------------------------------------------------------------------------- /src/services/helper/toFirstUpperCase.ts: -------------------------------------------------------------------------------- 1 | import { UpperCase } from '../../types'; 2 | 3 | const toFirstUpperCase:UpperCase = (word) => `${word[0].toUpperCase()}${word.slice(1)}`; 4 | 5 | export default toFirstUpperCase; 6 | -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | import fullYearCalendar from './fullYearCalendar'; 2 | import monthCalendar from './monthCalendar'; 3 | import monthNowCalendar from './monthNowCalendar'; 4 | 5 | export { 6 | fullYearCalendar, 7 | monthNowCalendar, 8 | monthCalendar, 9 | }; 10 | -------------------------------------------------------------------------------- /src/services/isThirtyOne.ts: -------------------------------------------------------------------------------- 1 | import { THIRTHY_ONE } from '../constants/numbers'; 2 | import { CheckDate } from '../types'; 3 | 4 | const isThirtyOne: CheckDate = (year, month) => { 5 | const date = new Date(year, month, THIRTHY_ONE); 6 | return date.getMonth() === month; 7 | }; 8 | 9 | export default isThirtyOne; 10 | -------------------------------------------------------------------------------- /src/services/monthCalendar.ts: -------------------------------------------------------------------------------- 1 | import monthNames from '../constants/names'; 2 | import { Calendar } from '../types'; 3 | import generateCalendarBoard from './generateCalendarBoard'; 4 | import toFirstUpperCase from './helper/toFirstUpperCase'; 5 | 6 | const monthCalendar: Calendar = (year, month) => { 7 | const board = generateCalendarBoard(year, month); 8 | const monthName = toFirstUpperCase(monthNames[month]); 9 | 10 | return { [monthName]: board, year }; 11 | }; 12 | 13 | export default monthCalendar; 14 | -------------------------------------------------------------------------------- /src/services/monthNowCalendar.ts: -------------------------------------------------------------------------------- 1 | import monthNames from '../constants/names'; 2 | import { CalendarNow } from '../types'; 3 | import generateCalendarBoard from './generateCalendarBoard'; 4 | import { defaultMonth, defaultYear } from '../constants/defaultDate'; 5 | import toFirstUpperCase from './helper/toFirstUpperCase'; 6 | 7 | const monthNowCalendar: CalendarNow = () => { 8 | const board = generateCalendarBoard(defaultYear, defaultMonth); 9 | const monthName = toFirstUpperCase(monthNames[defaultMonth]); 10 | 11 | return { [monthName]: board, year: defaultYear }; 12 | }; 13 | 14 | export default monthNowCalendar; 15 | -------------------------------------------------------------------------------- /src/services/sum.ts: -------------------------------------------------------------------------------- 1 | const sum = (a: number, b: number) => a + b; 2 | 3 | export default sum; 4 | -------------------------------------------------------------------------------- /src/tests/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const boardResp = { 2 | January: [ 3 | [0, 0, 0, 0, 0, 0, 1], 4 | [2, 3, 4, 5, 6, 7, 8], 5 | [9, 10, 11, 12, 13, 14, 15], 6 | [16, 17, 18, 19, 20, 21, 22], 7 | [23, 24, 25, 26, 27, 28, 29], 8 | [30, 31, 0, 0, 0, 0, 0], 9 | ], 10 | year: 2022, 11 | }; 12 | 13 | export const fullyear = { 14 | year: 2022, 15 | January: [ 16 | [ 17 | 0, 18 | 0, 19 | 0, 20 | 0, 21 | 0, 22 | 0, 23 | 1, 24 | ], 25 | [ 26 | 2, 27 | 3, 28 | 4, 29 | 5, 30 | 6, 31 | 7, 32 | 8, 33 | ], 34 | [ 35 | 9, 36 | 10, 37 | 11, 38 | 12, 39 | 13, 40 | 14, 41 | 15, 42 | ], 43 | [ 44 | 16, 45 | 17, 46 | 18, 47 | 19, 48 | 20, 49 | 21, 50 | 22, 51 | ], 52 | [ 53 | 23, 54 | 24, 55 | 25, 56 | 26, 57 | 27, 58 | 28, 59 | 29, 60 | ], 61 | [ 62 | 30, 63 | 31, 64 | 0, 65 | 0, 66 | 0, 67 | 0, 68 | 0, 69 | ], 70 | ], 71 | February: [ 72 | [ 73 | 0, 74 | 0, 75 | 1, 76 | 2, 77 | 3, 78 | 4, 79 | 5, 80 | ], 81 | [ 82 | 6, 83 | 7, 84 | 8, 85 | 9, 86 | 10, 87 | 11, 88 | 12, 89 | ], 90 | [ 91 | 13, 92 | 14, 93 | 15, 94 | 16, 95 | 17, 96 | 18, 97 | 19, 98 | ], 99 | [ 100 | 20, 101 | 21, 102 | 22, 103 | 23, 104 | 24, 105 | 25, 106 | 26, 107 | ], 108 | [ 109 | 27, 110 | 28, 111 | 0, 112 | 0, 113 | 0, 114 | 0, 115 | 0, 116 | ], 117 | ], 118 | March: [ 119 | [ 120 | 0, 121 | 0, 122 | 1, 123 | 2, 124 | 3, 125 | 4, 126 | 5, 127 | ], 128 | [ 129 | 6, 130 | 7, 131 | 8, 132 | 9, 133 | 10, 134 | 11, 135 | 12, 136 | ], 137 | [ 138 | 13, 139 | 14, 140 | 15, 141 | 16, 142 | 17, 143 | 18, 144 | 19, 145 | ], 146 | [ 147 | 20, 148 | 21, 149 | 22, 150 | 23, 151 | 24, 152 | 25, 153 | 26, 154 | ], 155 | [ 156 | 27, 157 | 28, 158 | 29, 159 | 30, 160 | 31, 161 | 0, 162 | 0, 163 | ], 164 | ], 165 | April: [ 166 | [ 167 | 0, 168 | 0, 169 | 0, 170 | 0, 171 | 0, 172 | 1, 173 | 2, 174 | ], 175 | [ 176 | 3, 177 | 4, 178 | 5, 179 | 6, 180 | 7, 181 | 8, 182 | 9, 183 | ], 184 | [ 185 | 10, 186 | 11, 187 | 12, 188 | 13, 189 | 14, 190 | 15, 191 | 16, 192 | ], 193 | [ 194 | 17, 195 | 18, 196 | 19, 197 | 20, 198 | 21, 199 | 22, 200 | 23, 201 | ], 202 | [ 203 | 24, 204 | 25, 205 | 26, 206 | 27, 207 | 28, 208 | 29, 209 | 30, 210 | ], 211 | ], 212 | May: [ 213 | [ 214 | 1, 215 | 2, 216 | 3, 217 | 4, 218 | 5, 219 | 6, 220 | 7, 221 | ], 222 | [ 223 | 8, 224 | 9, 225 | 10, 226 | 11, 227 | 12, 228 | 13, 229 | 14, 230 | ], 231 | [ 232 | 15, 233 | 16, 234 | 17, 235 | 18, 236 | 19, 237 | 20, 238 | 21, 239 | ], 240 | [ 241 | 22, 242 | 23, 243 | 24, 244 | 25, 245 | 26, 246 | 27, 247 | 28, 248 | ], 249 | [ 250 | 29, 251 | 30, 252 | 31, 253 | 0, 254 | 0, 255 | 0, 256 | 0, 257 | ], 258 | ], 259 | June: [ 260 | [ 261 | 0, 262 | 0, 263 | 0, 264 | 1, 265 | 2, 266 | 3, 267 | 4, 268 | ], 269 | [ 270 | 5, 271 | 6, 272 | 7, 273 | 8, 274 | 9, 275 | 10, 276 | 11, 277 | ], 278 | [ 279 | 12, 280 | 13, 281 | 14, 282 | 15, 283 | 16, 284 | 17, 285 | 18, 286 | ], 287 | [ 288 | 19, 289 | 20, 290 | 21, 291 | 22, 292 | 23, 293 | 24, 294 | 25, 295 | ], 296 | [ 297 | 26, 298 | 27, 299 | 28, 300 | 29, 301 | 30, 302 | 0, 303 | 0, 304 | ], 305 | ], 306 | July: [ 307 | [ 308 | 0, 309 | 0, 310 | 0, 311 | 0, 312 | 0, 313 | 1, 314 | 2, 315 | ], 316 | [ 317 | 3, 318 | 4, 319 | 5, 320 | 6, 321 | 7, 322 | 8, 323 | 9, 324 | ], 325 | [ 326 | 10, 327 | 11, 328 | 12, 329 | 13, 330 | 14, 331 | 15, 332 | 16, 333 | ], 334 | [ 335 | 17, 336 | 18, 337 | 19, 338 | 20, 339 | 21, 340 | 22, 341 | 23, 342 | ], 343 | [ 344 | 24, 345 | 25, 346 | 26, 347 | 27, 348 | 28, 349 | 29, 350 | 30, 351 | ], 352 | [ 353 | 31, 354 | 0, 355 | 0, 356 | 0, 357 | 0, 358 | 0, 359 | 0, 360 | ], 361 | ], 362 | August: [ 363 | [ 364 | 0, 365 | 1, 366 | 2, 367 | 3, 368 | 4, 369 | 5, 370 | 6, 371 | ], 372 | [ 373 | 7, 374 | 8, 375 | 9, 376 | 10, 377 | 11, 378 | 12, 379 | 13, 380 | ], 381 | [ 382 | 14, 383 | 15, 384 | 16, 385 | 17, 386 | 18, 387 | 19, 388 | 20, 389 | ], 390 | [ 391 | 21, 392 | 22, 393 | 23, 394 | 24, 395 | 25, 396 | 26, 397 | 27, 398 | ], 399 | [ 400 | 28, 401 | 29, 402 | 30, 403 | 31, 404 | 0, 405 | 0, 406 | 0, 407 | ], 408 | ], 409 | September: [ 410 | [ 411 | 0, 412 | 0, 413 | 0, 414 | 0, 415 | 1, 416 | 2, 417 | 3, 418 | ], 419 | [ 420 | 4, 421 | 5, 422 | 6, 423 | 7, 424 | 8, 425 | 9, 426 | 10, 427 | ], 428 | [ 429 | 11, 430 | 12, 431 | 13, 432 | 14, 433 | 15, 434 | 16, 435 | 17, 436 | ], 437 | [ 438 | 18, 439 | 19, 440 | 20, 441 | 21, 442 | 22, 443 | 23, 444 | 24, 445 | ], 446 | [ 447 | 25, 448 | 26, 449 | 27, 450 | 28, 451 | 29, 452 | 30, 453 | 0, 454 | ], 455 | ], 456 | October: [ 457 | [ 458 | 0, 459 | 0, 460 | 0, 461 | 0, 462 | 0, 463 | 0, 464 | 1, 465 | ], 466 | [ 467 | 2, 468 | 3, 469 | 4, 470 | 5, 471 | 6, 472 | 7, 473 | 8, 474 | ], 475 | [ 476 | 9, 477 | 10, 478 | 11, 479 | 12, 480 | 13, 481 | 14, 482 | 15, 483 | ], 484 | [ 485 | 16, 486 | 17, 487 | 18, 488 | 19, 489 | 20, 490 | 21, 491 | 22, 492 | ], 493 | [ 494 | 23, 495 | 24, 496 | 25, 497 | 26, 498 | 27, 499 | 28, 500 | 29, 501 | ], 502 | [ 503 | 30, 504 | 31, 505 | 0, 506 | 0, 507 | 0, 508 | 0, 509 | 0, 510 | ], 511 | ], 512 | November: [ 513 | [ 514 | 0, 515 | 0, 516 | 1, 517 | 2, 518 | 3, 519 | 4, 520 | 5, 521 | ], 522 | [ 523 | 6, 524 | 7, 525 | 8, 526 | 9, 527 | 10, 528 | 11, 529 | 12, 530 | ], 531 | [ 532 | 13, 533 | 14, 534 | 15, 535 | 16, 536 | 17, 537 | 18, 538 | 19, 539 | ], 540 | [ 541 | 20, 542 | 21, 543 | 22, 544 | 23, 545 | 24, 546 | 25, 547 | 26, 548 | ], 549 | [ 550 | 27, 551 | 28, 552 | 29, 553 | 30, 554 | 0, 555 | 0, 556 | 0, 557 | ], 558 | ], 559 | December: [ 560 | [ 561 | 0, 562 | 0, 563 | 0, 564 | 0, 565 | 1, 566 | 2, 567 | 3, 568 | ], 569 | [ 570 | 4, 571 | 5, 572 | 6, 573 | 7, 574 | 8, 575 | 9, 576 | 10, 577 | ], 578 | [ 579 | 11, 580 | 12, 581 | 13, 582 | 14, 583 | 15, 584 | 16, 585 | 17, 586 | ], 587 | [ 588 | 18, 589 | 19, 590 | 20, 591 | 21, 592 | 22, 593 | 23, 594 | 24, 595 | ], 596 | [ 597 | 25, 598 | 26, 599 | 27, 600 | 28, 601 | 29, 602 | 30, 603 | 31, 604 | ], 605 | ], 606 | }; 607 | -------------------------------------------------------------------------------- /src/tests/integration-test/getCalendarByMonth.test.ts: -------------------------------------------------------------------------------- 1 | import request from 'supertest'; 2 | import { boardResp } from '../constants'; 3 | import app from '../../app'; 4 | 5 | const date = new Date(); 6 | const year = date.getFullYear(); 7 | 8 | describe('Get /month/:monthName', () => { 9 | it('checks the returns the 409 status when a non-existing month is entered', async () => { 10 | const req = request(app); 11 | const response = await req.get('/month/fevereiro'); 12 | expect(response.statusCode).toBe(409); 13 | }); 14 | 15 | it('checks the returns an error message when entered as a non-existent month parameter', async () => { 16 | const req = request(app); 17 | const response = await req.get('/month/fevereiro'); 18 | expect(response.body).toHaveProperty('message', 'month name invalid'); 19 | expect(response.body.message).toBe('month name invalid'); 20 | }); 21 | 22 | it('check the 200 status when valid month is entered as parameter', async () => { 23 | const req = request(app); 24 | const response = await req.get('/month/february'); 25 | expect(response.statusCode).toBe(200); 26 | }); 27 | 28 | it('check the month and year correspond to the one entered as parameter', async () => { 29 | const req = request(app); 30 | const response = await req.get('/month/february?year=2022'); 31 | expect(response.body).toHaveProperty('year', 2022); 32 | expect(response.body).toHaveProperty('February'); 33 | }); 34 | 35 | it('checks when not entered year is used in current year', async () => { 36 | const req = request(app); 37 | const response = await req.get('/month/july'); 38 | expect(response.body).toHaveProperty('year', year); 39 | expect(response.body).toHaveProperty('July'); 40 | }); 41 | 42 | it('check the first day matches the correct day of the week', async () => { 43 | const firstDay = new Date(2022, 0, 1).getDay(); 44 | const req = request(app); 45 | const response = await req.get('/month/january?year=2022'); 46 | expect(response.body.January[0][firstDay]).toBe(1); 47 | }); 48 | 49 | it('check the amount of 7 days in each week array', async () => { 50 | const req = request(app); 51 | const response = await req.get('/month/january?year=2022'); 52 | response.body.January.forEach((week: number[]) => { 53 | expect(week).toHaveLength(7); 54 | }); 55 | }); 56 | 57 | it('checks the returned calendar agrees with the one entered by parameter', async () => { 58 | const req = request(app); 59 | const response = await req.get('/month/january?year=2022'); 60 | expect(response.body).toEqual(boardResp); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/tests/integration-test/getCalendarFullYear.test.ts: -------------------------------------------------------------------------------- 1 | import request from 'supertest'; 2 | import app from '../../app'; 3 | import monthNames from '../../constants/names'; 4 | import toFirstUpperCase from '../../services/helper/toFirstUpperCase'; 5 | import { fullyear } from '../constants'; 6 | 7 | const date = new Date(); 8 | const year = date.getFullYear(); 9 | 10 | describe('Get /fullyear/:year', () => { 11 | afterEach(() => { 12 | jest.clearAllMocks(); 13 | }); 14 | it('checks the returns the 409 status when a year entered as a parameter has less than 4 digits', async () => { 15 | const req = request(app); 16 | const response = await req.get('/fullyear/22'); 17 | expect(response.statusCode).toBe(409); 18 | }); 19 | 20 | it('checks whether returns an error message when a non-existent month is entered as a parameter', async () => { 21 | const req = request(app); 22 | const response = await req.get('/fullyear/22'); 23 | expect(response.body).toHaveProperty('message', 'the year must have at least four digits'); 24 | expect(response.body.message).toBe('the year must have at least four digits'); 25 | }); 26 | 27 | it('check status 200', async () => { 28 | const req = request(app); 29 | const response = await req.get('/fullyear/2022'); 30 | expect(response.statusCode).toBe(200); 31 | }); 32 | 33 | it('check that the month and year correspond to the one entered by parameter', async () => { 34 | const req = request(app); 35 | const response = await req.get('/fullyear/2022'); 36 | expect(response.body).toHaveProperty('year', 2022); 37 | monthNames.forEach((month) => { 38 | expect(response.body).toHaveProperty(toFirstUpperCase(month)); 39 | }); 40 | }); 41 | 42 | it('check when not past year is used current year', async () => { 43 | const req = request(app); 44 | const response = await req.get('/fullyear/'); 45 | expect(response.body).toHaveProperty('year', year); 46 | monthNames.forEach((month) => { 47 | expect(response.body).toHaveProperty(toFirstUpperCase(month)); 48 | }); 49 | }); 50 | 51 | it('check the first day matches the correct day of the week', async () => { 52 | const req = request(app); 53 | const response = await req.get('/fullyear/2022'); 54 | monthNames.forEach((month, position) => { 55 | const firstDay = new Date(2022, position, 1).getDay(); 56 | expect(response.body[toFirstUpperCase(month)][0][firstDay]).toBe(1); 57 | }); 58 | }); 59 | it('check the amount of 7 days in each week array', async () => { 60 | const req = request(app); 61 | const response = await req.get('/fullyear/2022'); 62 | monthNames.forEach((month) => { 63 | response.body[toFirstUpperCase(month)].forEach((week: number[]) => { 64 | expect(week).toHaveLength(7); 65 | }); 66 | }); 67 | }); 68 | 69 | it('checkth returns the calendar corresponding to the year entered by parameter', async () => { 70 | const req = request(app); 71 | const response = await req.get('/fullyear/2022'); 72 | expect(response.body).toEqual(fullyear); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /src/tests/integration-test/getCalendarNow.test.ts: -------------------------------------------------------------------------------- 1 | import request from 'supertest'; 2 | import app from '../../app'; 3 | import monthNames from '../../constants/names'; 4 | import toFirstUpperCase from '../../services/helper/toFirstUpperCase'; 5 | 6 | const date = new Date(); 7 | const month = toFirstUpperCase(monthNames[date.getMonth()]); 8 | const year = date.getFullYear(); 9 | 10 | describe('Get /month', () => { 11 | it('check status 200', async () => { 12 | const req = request(app); 13 | const response = await req.get('/fullyear/'); 14 | expect(response.statusCode).toBe(200); 15 | }); 16 | 17 | it('check answer properties', async () => { 18 | const req = request(app); 19 | const response = await req.get('/fullyear/'); 20 | expect(response.body).toHaveProperty('year', year); 21 | expect(response.body).toHaveProperty(month); 22 | }); 23 | 24 | it('check the first day matches the correct day of the week', async () => { 25 | const firstDay = new Date(year, date.getMonth(), 1).getDay(); 26 | const req = request(app); 27 | const response = await req.get('/fullyear/'); 28 | expect(response.body[month][0][firstDay]).toBe(1); 29 | }); 30 | 31 | it('check the amount of 7 days in each week array', async () => { 32 | const req = request(app); 33 | const response = await req.get('/fullyear/'); 34 | response.body[month].forEach((week: number[]) => { 35 | expect(week).toHaveLength(7); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | 3 | export type CalendarResp = { 4 | January?: string, 5 | February?: string, 6 | March?: string, 7 | April?: string, 8 | May?: string, 9 | June?: string, 10 | July?: string, 11 | August?: string, 12 | September?: string, 13 | October?: string, 14 | November?: string, 15 | December?: string, 16 | year: number, 17 | } 18 | 19 | declare module 'express' { 20 | interface Request { 21 | date?: { 22 | year?: number, 23 | month?:number, 24 | }; 25 | } 26 | } 27 | 28 | export type Handler = (_req: Request, _res: Response, _next: NextFunction) => void; 29 | 30 | export type CalendarBoard = (_year: number, _mounth: number) => number[][]; 31 | 32 | export type CalendarNow = () => CalendarResp; 33 | 34 | export type Calendar = (_year: number, _mounth: number) => CalendarResp; 35 | 36 | export type CalendarYear = (_year: number) => CalendarResp; 37 | 38 | export type CheckDate = (_year: number, _mounth: number) => boolean; 39 | 40 | export type Board = () => number[][]; 41 | 42 | export type DateCompleted = { month: number, year: number} 43 | 44 | export type Dateyear = { year: number} 45 | 46 | export type UpperCase = (_word: string) => string 47 | 48 | export type LastDay = (_year: number, _mounth: number) => number; 49 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | /* Projects */ 5 | // "incremental": true, /* Enable incremental compilation */ 6 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 7 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 8 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ 9 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 10 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 11 | /* Language and Environment */ 12 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 13 | "lib": [ 14 | "ES2015" 15 | ], /* 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 TC39 stage 2 draft 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 | /* Modules */ 26 | "module": "commonjs", /* Specify what module code is generated. */ 27 | "rootDir": "./src", /* Specify the root folder within your source files. */ 28 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 29 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 30 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 31 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 32 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 33 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 34 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 35 | "resolveJsonModule": true, /* Enable importing .json files */ 36 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 37 | /* JavaScript Support */ 38 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 39 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 40 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 41 | /* Emit */ 42 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 43 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 44 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 45 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 46 | // "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. */ 47 | "outDir": "./dist", /* Specify an output folder for all emitted files. */ 48 | // "removeComments": true, /* Disable emitting comments. */ 49 | // "noEmit": true, /* Disable emitting files from a compilation. */ 50 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 51 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 52 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 53 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 54 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 55 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 56 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 57 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 58 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 59 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 60 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 61 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 62 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 63 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 64 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 65 | /* Interop Constraints */ 66 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 67 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 68 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 69 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 70 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 71 | /* Type Checking */ 72 | "strict": true, /* Enable all strict type-checking options. */ 73 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ 74 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 75 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 76 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 77 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 78 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 79 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 80 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 81 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 82 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 83 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 84 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 85 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 86 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 87 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 88 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 89 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 90 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 91 | /* Completeness */ 92 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 93 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 94 | } 95 | } --------------------------------------------------------------------------------