1
0

276 Commitit 6effefbf12 ... f2c5fa330e

Tekijä SHA1 Viesti Päivämäärä
  yazid138 f2c5fa330e commit 3 kuukautta sitten
  yazid138 3849af50b6 Merge branch 'master' of https://source.prod.sidali.vertibiz.com/yazid/ptb-be 3 kuukautta sitten
  yazid138 c84531be8c commit 3 kuukautta sitten
  yazid138 c46629b101 commit 1 vuosi sitten
  yazid138 254f502883 commit 1 vuosi sitten
  yazid138 a5e93112af perubahan port dan token 1 vuosi sitten
  yazid138 9c900d1f6c commit 1 vuosi sitten
  yazid138 a5f44b0b2f commit 1 vuosi sitten
  yazid138 b9926dc052 commit 1 vuosi sitten
  yazid138 1f448d29ea commit 1 vuosi sitten
  yazid138 effa4d272b commit 1 vuosi sitten
  yazid138 b34cb388d9 commit 1 vuosi sitten
  yazid138 804ecc08cb commit 1 vuosi sitten
  yazid138 8beb7869b3 commit 1 vuosi sitten
  yazid138 9f58c9684c commit 1 vuosi sitten
  yazid138 c9c2db03eb commit 1 vuosi sitten
  yazid138 9ac63a7216 commit 1 vuosi sitten
  yazid138 6257669a56 commit 2 vuotta sitten
  yazid138 7f3643cc63 commit 2 vuotta sitten
  yazid138 19550840c8 commit 2 vuotta sitten
  yazid138 ca2599e512 commit 2 vuotta sitten
  yazid138 1494be6d58 commit 2 vuotta sitten
  yazid138 9f4b61686d commit 2 vuotta sitten
  yazid138 ab1abc7cb0 commit 2 vuotta sitten
  yazid138 44b636417d commit 2 vuotta sitten
  yazid138 da6649c7fe commit 2 vuotta sitten
  yazid138 87bb780254 commit 2 vuotta sitten
  yazid138 ea4f5872a4 commit 2 vuotta sitten
  yazid138 28876e9160 commit 2 vuotta sitten
  yazid138 2cc6b9a5d2 commit 2 vuotta sitten
  yazid138 90183fe0d1 commit 2 vuotta sitten
  yazid138 ba2f8f7a25 commit 2 vuotta sitten
  yazid138 fa2ad48945 commit 2 vuotta sitten
  yazid138 c8fc897f37 commit 2 vuotta sitten
  yazid138 acb233dd66 commit 2 vuotta sitten
  yazid138 13db1b9b34 commit 2 vuotta sitten
  yazid138 6f3a667207 commit 2 vuotta sitten
  yazid138 83d9566ff7 commit 2 vuotta sitten
  yazid138 07dd667215 commit 2 vuotta sitten
  yazid138 537d9d35aa commit 2 vuotta sitten
  yazid138 3d20e2ea0f commit 2 vuotta sitten
  yazid138 d44d4208aa commit 2 vuotta sitten
  yazid138 99f960763c commit 2 vuotta sitten
  yazid138 1ac9fefc30 commit 2 vuotta sitten
  yazid138 c371a63e4b commit 2 vuotta sitten
  yazid138 ee3fdcfbe0 commit 2 vuotta sitten
  yazid138 4b92a5353e commit 2 vuotta sitten
  yazid138 3ea6af1543 commit 2 vuotta sitten
  yazid138 d18a1efcb5 commit 2 vuotta sitten
  yazid138 0feb61a283 commit 2 vuotta sitten
  yazid138 ce8e388fdf commit 2 vuotta sitten
  yazid138 a3bfd5e23a commit 2 vuotta sitten
  yazid138 5517149909 commit 2 vuotta sitten
  yazid138 15a6a723d5 commit 2 vuotta sitten
  yazid138 e948882c8a commit 2 vuotta sitten
  yazid138 ae8b98fc71 commit 2 vuotta sitten
  yazid138 f6ec7c026a commit 2 vuotta sitten
  yazid138 9354a6016c commit 2 vuotta sitten
  yazid138 539ef60615 commit 2 vuotta sitten
  yazid138 f4d0e80766 commit 2 vuotta sitten
  yazid138 ece4761978 commit 2 vuotta sitten
  yazid138 bd6d276e6d commit 2 vuotta sitten
  yazid138 5b803041be commit 2 vuotta sitten
  yazid138 b34a8124fe commit 2 vuotta sitten
  yazid138 33e22c55b7 commit 2 vuotta sitten
  yazid138 bad3499b27 commit 2 vuotta sitten
  yazid138 2eda56849d commit 2 vuotta sitten
  yazid138 5eb3e0b869 commit 2 vuotta sitten
  yazid138 56663ed494 commit 2 vuotta sitten
  yazid138 acc702e309 commit 2 vuotta sitten
  yazid138 a7ee40ace0 commit 2 vuotta sitten
  yazid138 c0067b25fd commit 2 vuotta sitten
  yazid138 f310bce238 commit 2 vuotta sitten
  yazid138 e658965ed6 commit 2 vuotta sitten
  yazid138 bacddcd247 commit 2 vuotta sitten
  yazid138 61e2577e79 commit 2 vuotta sitten
  yazid138 46a31d03cd commit 2 vuotta sitten
  yazid138 0a5b254fca commit 2 vuotta sitten
  yazid138 c8a18d55ae commit 2 vuotta sitten
  yazid138 a1e78d27fa commit 2 vuotta sitten
  yazid138 a00bdb807c commit 2 vuotta sitten
  yazid138 856ee8b11b commit 2 vuotta sitten
  yazid138 cf29040219 commit 2 vuotta sitten
  yazid138 c02ecff348 commit 2 vuotta sitten
  yazid138 df350fd56e commit 2 vuotta sitten
  yazid138 22b2e13588 commit 2 vuotta sitten
  yazid138 08cd87e28e commit 2 vuotta sitten
  yazid138 e101b45fb8 commit 2 vuotta sitten
  yazid138 2e58933ee6 commit 2 vuotta sitten
  yazid138 04ad2121ab commit 2 vuotta sitten
  yazid138 021eac70f2 implement enkripsi 2 vuotta sitten
  yazid138 a44e0076bd implement enkripsi 2 vuotta sitten
  yazid138 f624f5b13c commit 2 vuotta sitten
  yazid138 5c0214288f commit 2 vuotta sitten
  yazid138 66f44b6677 commit 2 vuotta sitten
  yazid138 e19e613321 commit 2 vuotta sitten
  yazid138 bfd4cf1821 commit andi 2 vuotta sitten
  yazid138 dee3204c74 commit 2 vuotta sitten
  yazid138 744db25bd6 commit 2 vuotta sitten
  yazid138 36c0861665 commit 2 vuotta sitten
  yazid138 e5663b58d1 ganti environment ke production 2 vuotta sitten
  yazid138 d6068ea57f commit andi 2 vuotta sitten
  yazid138 69688f74e7 fix error api dari dikti 2 vuotta sitten
  yazid138 d6a3fb876b fix db 2 vuotta sitten
  yazid138 d85e3bb97b fix cekData.js 2 vuotta sitten
  yazid138 7457bcff1b update be ke prod 2 vuotta sitten
  yazid138 053c69fc09 fix 2 vuotta sitten
  yazid138 63d066eb34 commit 2 vuotta sitten
  yazid138 1b615968a4 merging 2 vuotta sitten
  yazid138 f4968da742 commit 2 vuotta sitten
  yazid138 5f00556bbf Merge branch 'master' of http://118.98.227.82:50006/appsptb/ptb-be 2 vuotta sitten
  yazid138 207c5f01b0 fixing pelanggaran 2 vuotta sitten
  andi bafd9cc7f5 commit 2 vuotta sitten
  andi 285ddc9d10 connecting to kemdikbud.go.id 2 vuotta sitten
  yazid138 19f63ac020 commit 2 vuotta sitten
  yazid138 aecaf98415 commit lagi andi 2 vuotta sitten
  yazid138 9e5f147f8d fixing role auth from api dikti 2 vuotta sitten
  yazid138 dfb61ed8e0 update lagi andi 3 vuotta sitten
  yazid138 98e0ecb233 commit 3 vuotta sitten
  yazid138 76f67754e2 yg diminta pak rizky 3 vuotta sitten
  yazid138 30f708b33e kebalik 3 vuotta sitten
  yazid138 b720d635ac commit lagi andi 3 vuotta sitten
  yazid138 3bce3ef984 commit 3 vuotta sitten
  yazid138 ffea25fd03 commit 3 vuotta sitten
  yazid138 5ed8317f29 commit aja 3 vuotta sitten
  yazid138 de62dc5fcf add role for admin 3 vuotta sitten
  yazid138 bd6fff3ee7 add role for admin 3 vuotta sitten
  yazid138 9989331551 commit 3 vuotta sitten
  yazid138 1e56b27c58 commit 3 vuotta sitten
  yazid138 aba1d5c0be commit 3 vuotta sitten
  yazid138 bdffef1280 update 3 vuotta sitten
  yazid138 c253a541f0 ganti operator aja 3 vuotta sitten
  yazid138 703aac1efd commit lagi aja dah 3 vuotta sitten
  yazid138 5b5777e387 commit 3 vuotta sitten
  yazid138 65d79623fd commit 3 vuotta sitten
  yazid138 7238856e46 commit lagi 3 vuotta sitten
  yazid138 8ce6953dba fix 3 vuotta sitten
  yazid138 d2288af737 fix 3 vuotta sitten
  yazid138 bd7a9e18d0 fix perubahan sanksi 3 vuotta sitten
  yazid138 78aba3c991 fix perubahan sanksi 3 vuotta sitten
  yazid138 a4f144572f add: api perubahan sanksi 3 vuotta sitten
  yazid138 36756e5f4b add: api perubahan sanksi 3 vuotta sitten
  yazid138 6838e3f031 commit aja 3 vuotta sitten
  yazid138 5060047d22 commit aja 3 vuotta sitten
  yazid138 a3bcb298cd udah andi 3 vuotta sitten
  yazid138 aa493aa947 udah andi 3 vuotta sitten
  yazid138 58b1727823 commit 3 vuotta sitten
  yazid138 d911fda610 commit 3 vuotta sitten
  yazid138 2e3369e158 commit aja dah 3 vuotta sitten
  yazid138 c66866c08e commit aja deh 3 vuotta sitten
  yazid138 28e50de74f udah aman 3 vuotta sitten
  yazid138 79a22c9321 udah aman 3 vuotta sitten
  yazid138 769b1c6ae0 fixing sanksi and graph 3 vuotta sitten
  yazid138 fc776dcafd fixing sanksi and graph 3 vuotta sitten
  yazid138 d84117ff98 commit aja dah 3 vuotta sitten
  yazid138 b547e359d0 commit aja dah 3 vuotta sitten
  yazid138 1e84eb8109 commit aja dah 3 vuotta sitten
  yazid138 62e9bfb9e1 auth with cookie and add auto update status sanksi 3 vuotta sitten
  yazid138 df08431fac add keterangan to get one sanksi 3 vuotta sitten
  yazid138 057d93c396 add rekomendasi 3 vuotta sitten
  yazid138 ec4012e261 all to get one sanksi 3 vuotta sitten
  yazid138 20d696ce1e all to get one sanksi 3 vuotta sitten
  yazid138 4a6c8e81ad add sanksi to laporan 3 vuotta sitten
  yazid138 24eb1ba1ab add sanksi to laporan 3 vuotta sitten
  yazid138 5bd1a31e5b add sanksi to laporan 3 vuotta sitten
  yazid138 b340a24b82 add sanksi to laporan 3 vuotta sitten
  yazid138 e372e58765 fixing get all sanksi 3 vuotta sitten
  yazid138 0dcfbc21e7 fixing get all sanksi 3 vuotta sitten
  yazid138 64105b8f4e add delegasi 3 vuotta sitten
  yazid138 77a72fa7fc add delegasi 3 vuotta sitten
  yazid138 d7570afcdb add pelanggaran 3 vuotta sitten
  yazid138 211df1bf2d add pelanggaran 3 vuotta sitten
  yazid138 8c393d2cce add pelanggaran 3 vuotta sitten
  yazid138 a0b577a5af add pelanggaran 3 vuotta sitten
  yazid138 1804c69888 tambahin data laporan dan sanksi di jumlah status laporan 3 vuotta sitten
  yazid138 0d395c6034 tambahin data laporan dan sanksi di jumlah status laporan 3 vuotta sitten
  yazid 02e8b1fa2b fix, dikti bisa liat semua laporan yg dibuat oleh lldikti 3 vuotta sitten
  yazid a9a70dce40 fix, dikti bisa liat semua laporan yg dibuat oleh lldikti 3 vuotta sitten
  yazid138 1ba1bb9efd get laporan by pembina 3 vuotta sitten
  yazid138 ff793fef11 get laporan by pembina 3 vuotta sitten
  yazid138 e88844e061 fix again 3 vuotta sitten
  yazid138 6cadafe1ce fix again 3 vuotta sitten
  yazid138 dde3ca5293 commit 3 vuotta sitten
  yazid138 baf33b301c commit 3 vuotta sitten
  yazid138 e379ac2735 pemantauan dari dikti tampil semua laporan 3 vuotta sitten
  yazid138 640aa4d313 pemantauan dari dikti tampil semua laporan 3 vuotta sitten
  yazid138 b5683d8700 fix 3 vuotta sitten
  yazid138 192afbc8a9 fix 3 vuotta sitten
  yazid138 f0f0bc8a9a Merge branch 'master' of https://source.dev-sidali.sixsenz.net/appsptb/ptb-be 3 vuotta sitten
  yazid138 2833e7f426 add jumlah status laporan 3 vuotta sitten
  yazid138 006a10a177 add jumlah status laporan 3 vuotta sitten
  andi 9ddb0c880f db -- sixsenz.net 3 vuotta sitten
  andi f24b7bc2ab db -- kemdikbud.go.id 3 vuotta sitten
  andi c645ceea40 --kemdikbud.go.id 3 vuotta sitten
  andi a5b69fac55 to sixsenz.net 3 vuotta sitten
  andi e54493a183 to dev kemdikbud.go.id 3 vuotta sitten
  andi ce81b388c3 back to dev-sixsenz.net 3 vuotta sitten
  andi 512e8378d9 Merge branch 'master' of https://source.dev-sidali.sixsenz.net/appsptb/ptb-be 3 vuotta sitten
  andi e0445581c9 to dev kemdikbud 3 vuotta sitten
  yazid138 685563e5e0 fix all 3 vuotta sitten
  yazid138 b2c95133d4 fixing list sanksi 3 vuotta sitten
  yazid138 338e44e520 Merge branch 'master' of https://source.dev-sidali.sixsenz.net/appsptb/ptb-be 3 vuotta sitten
  yazid138 c5eba787f9 fix 3 vuotta sitten
  andi a0c49861a2 issue 28-09 3 vuotta sitten
  andi c59a810c54 commit issue 21-09-2022 3 vuotta sitten
  andi d08d303cac back to normal 3 vuotta sitten
  andi 94157ee04c to 58 3 vuotta sitten
  andi 6372cd55e5 commit 3 vuotta sitten
  andi 23bdd2693a Merge branch 'master' of https://source.sidali.sixsenz.net/appsptb/PTB-be 3 vuotta sitten
  andi f71068a4dc push to 53 manual 3 vuotta sitten
  yazid138 8eb5c198b8 fixing bug 3 vuotta sitten
  yazid138 89798851f4 sip 3 vuotta sitten
  yazid138 a541b137d6 commit 3 vuotta sitten
  yazid138 30e86966ca fix 3 vuotta sitten
  yazid138 8d93b7330b add auto reminder sanksi 3 vuotta sitten
  yazid138 ad04d6baa3 add auto reminder sanksi 3 vuotta sitten
  yazid138 2d6ba0d323 add .env to server 3 vuotta sitten
  yazid138 e5587fd8f9 add log to phone 3 vuotta sitten
  yazid138 617f02a928 add log to notif wa 3 vuotta sitten
  yazid 63a8ddc206 update jenkins 3 vuotta sitten
  yazid 1d3e388e8f update .env 3 vuotta sitten
  yazid138 fd5f0a9ee5 Revert "remove jenkins and .env" 3 vuotta sitten
  yazid138 e44828db44 remove jenkins and .env 3 vuotta sitten
  yazid138 1e6d507b25 Merge remote-tracking branch 'repo-develop/master' 3 vuotta sitten
  yazid138 f2eea1c752 Merge branch 'master' into repo-develop/master 3 vuotta sitten
  yazid138 cabeb3aaaf gitignore add .env 3 vuotta sitten
  yazid138 afa5d18503 fix tambah perbaikan 3 vuotta sitten
  andi 7259da8d96 to production 3 vuotta sitten
  yazid138 3eebb90fac jenkins to production 3 vuotta sitten
  yazid138 d0342c5897 to production 3 vuotta sitten
  yazid138 2e3fbe5df2 Merge remote-tracking branch 'repo-develop/master' 3 vuotta sitten
  yazid138 069dac08c9 commit 3 vuotta sitten
  yazid138 b03e7dfe75 fix add perbaikan dokumen 3 vuotta sitten
  yazid 0e6b88f1f2 update ke development 3 vuotta sitten
  andi 0c7220ada1 ubah ke dev registry 3 vuotta sitten
  yazid 1ae0db26b2 update mongo url 3 vuotta sitten
  yazid f55557726d update env base url 3 vuotta sitten
  yazid 04a78a872d update ke env development 3 vuotta sitten
  yazid138 73d57dbcde add jumlah laporan riwayat 3 vuotta sitten
  yazid138 40d072631b fix data pengunjung sama fix sorting 3 vuotta sitten
  yazid138 bec7201d09 fix flow dokumen perbaikan 3 vuotta sitten
  yazid138 00dc630418 fix route, fix perbaikan dan pencabutan sanksi 3 vuotta sitten
  yazid138 69655356a2 fix getPengunjung 3 vuotta sitten
  yazid138 ef15be7e6f tambah pengunjung sama model api 3 vuotta sitten
  yazid138 13aafb64af fix: create laporan public 3 vuotta sitten
  yazid138 06b7a61831 remove data from auto.controller.js 3 vuotta sitten
  yazid138 6686972c1d add auto 3 vuotta sitten
  yazid138 634796c89b fix log 3 vuotta sitten
  yazid138 0cb06960d1 fix pemantauan public 3 vuotta sitten
  yazid138 9ceaa3cb80 fix login lagi 3 vuotta sitten
  yazid138 9ea2aa6ee1 fixing login, and data to log(os, ipv4) 3 vuotta sitten
  yazid138 ecb3efa456 tambah fitur log 3 vuotta sitten
  yazid138 623062bc1c fix auth 3 vuotta sitten
  yazid138 2137bf19d9 fix role 3 vuotta sitten
  yazid138 e7772c1755 fixing role 3 vuotta sitten
  yazid138 5edd4bc16d fix role again 3 vuotta sitten
  yazid138 7d7d49fc32 fix role 3 vuotta sitten
  yazid138 322fcb8869 fix role and token 3 vuotta sitten
  yazid138 d24f007b08 fix data graph 3 vuotta sitten
  yazid138 9852f8b6e9 update package.json 3 vuotta sitten
  yazid138 2a6e1a6049 add excel from be 3 vuotta sitten
  yazid138 8c5ebf06af fix graph data 3 vuotta sitten
  yazid138 150067a1b2 fix data graph 3 vuotta sitten
  yazid138 d767213258 add graph 3 vuotta sitten
  yazid138 6c4ffaf5d1 fix pt 3 vuotta sitten
  yazid138 747f654aa0 pt n/a tidak dimasukkan 3 vuotta sitten
  yazid138 0f9d1d0481 perubahan pada pemantauan dan data yg didelegasi 3 vuotta sitten
  yazid138 787e5d9caf penambahan data pengembalian no_hp_aktif 3 vuotta sitten
  yazid138 eaa0d5ab1d penambahan input alasan di update laporan, menambahkan level pada laporan, dan menambahkan for_public untuk pemantauan publik 3 vuotta sitten
  yazid138 0830c025a0 perubahan size dokumen menjadi 15mb dan perubahan pesan pemantauan 3 vuotta sitten
  yazid138 adf52fb0af fitur OTP 3 vuotta sitten
  yazid138 8abedd7eff Ganti Token ke dev 3 vuotta sitten
  yazid138 c4645ffbfe commit 3 vuotta sitten
  yazid138 4309240806 commit 3 vuotta sitten
  yazid138 2f18e6b493 salah link db 3 vuotta sitten
  yazid138 c859d02915 commit 3 vuotta sitten
100 muutettua tiedostoa jossa 5676 lisäystä ja 5106 poistoa
  1. 16 8
      .env
  2. 0 9
      .env.example
  3. 8 2
      .gitignore
  4. 2 2
      Jenkinsfile
  5. 15 5
      app.js
  6. 2 1
      bin/www
  7. 4 1
      config/db.js
  8. 0 110
      controller/auth.controller.js
  9. 29 1
      controller/dokumen.controller.js
  10. 0 545
      controller/laporan.controller.js
  11. 0 22
      controller/lembaga.controller.js
  12. 0 71
      controller/pt.controller.js
  13. 0 301
      controller/sanksi.controller.js
  14. 0 146
      controller/sanksi/banding.controller.js
  15. 0 126
      controller/sanksi/cabutSanksi.controller.js
  16. 0 155
      controller/sanksi/keberatan.controller.js
  17. 0 91
      controller/sanksi/perbaikan.controller.js
  18. 180 0
      controller/v1/auth.controller.js
  19. 454 0
      controller/v1/auto.controller.js
  20. 13 0
      controller/v1/disk.controller.js
  21. 435 0
      controller/v1/graph.controller.js
  22. 53 0
      controller/v1/kontak.controller.js
  23. 564 0
      controller/v1/laporan.controller.js
  24. 12 8
      controller/v1/laporan/evaluasi.controller.js
  25. 12 11
      controller/v1/laporan/jadwal.controller.js
  26. 12 0
      controller/v1/lembaga.controller.js
  27. 44 0
      controller/v1/log.controller.js
  28. 105 0
      controller/v1/migrasi.controller.js
  29. 16 19
      controller/v1/pelanggaran.controller.js
  30. 9 21
      controller/v1/pemantauan.controller.js
  31. 146 0
      controller/v1/pengunjung.controller.js
  32. 49 0
      controller/v1/pt.controller.js
  33. 60 0
      controller/v1/rekomendasi.controller.js
  34. 563 0
      controller/v1/sanksi.controller.js
  35. 180 0
      controller/v1/sanksi/banding.controller.js
  36. 200 0
      controller/v1/sanksi/cabutSanksi.controller.js
  37. 198 0
      controller/v1/sanksi/keberatan.controller.js
  38. 160 0
      controller/v1/sanksi/perbaikan.controller.js
  39. 102 0
      controller/v1/signature.controller.js
  40. 19 30
      controller/v1/user.controller.js
  41. 211 0
      controller/v2/auth.controller.js
  42. 153 0
      controller/v2/catatan.controller.js
  43. 245 0
      controller/v2/laporan.controller.js
  44. 119 0
      controller/v2/pemeriksaan.controller.js
  45. 67 0
      controller/v2/penjadwalan.controller.js
  46. 346 0
      controller/v2/sanksi.controller.js
  47. 2 2
      dockerfile
  48. 18 0
      middleware/blacklistUser.js
  49. 23 0
      middleware/checkData.js
  50. 7 0
      middleware/checkEnv.js
  51. 30 0
      middleware/csrf.js
  52. 19 0
      middleware/isUnique.js
  53. 35 0
      middleware/uploadFile.js
  54. 14 0
      middleware/validation.js
  55. 35 0
      middleware/verifyOTP.js
  56. 2 3
      middleware/verifyToken.js
  57. 2 1
      middleware/verifyTokenAuto.js
  58. 1 1
      middleware/verifyTokenPublic.js
  59. 17 0
      model/auto.model.js
  60. 17 0
      model/autoSave.model.js
  61. 13 0
      model/backup.model.js
  62. 14 0
      model/batch.model.js
  63. 34 0
      model/catatan.model.js
  64. 15 0
      model/disk.model.js
  65. 31 0
      model/laporan.model.js
  66. 2 0
      model/pemantauan.model.js
  67. 177 44
      model/sanksi.model.js
  68. 140 11
      model/sanksi2.model.js
  69. 29 0
      model/signature.model.js
  70. 2 0
      model/user.model.js
  71. 0 3277
      package-lock.json
  72. 12 9
      package.json
  73. 0 13
      public/index.html
  74. 0 8
      public/stylesheets/style.css
  75. 31 0
      routes/index.js
  76. 6 4
      routes/v1/auth.routes.js
  77. 12 6
      routes/v1/auto.routes.js
  78. 16 0
      routes/v1/catatan.routes.js
  79. 9 0
      routes/v1/disk.routes.js
  80. 4 4
      routes/v1/graph.routes.js
  81. 12 4
      routes/v1/index.js
  82. 7 0
      routes/v1/kontak.routes.js
  83. 1 1
      routes/v1/laporan/evaluasi.routes.js
  84. 6 3
      routes/v1/laporan/index.js
  85. 1 1
      routes/v1/laporan/jadwal.routes.js
  86. 1 1
      routes/v1/lembaga.routes.js
  87. 1 1
      routes/v1/log.routes.js
  88. 10 0
      routes/v1/migration.routes.js
  89. 3 3
      routes/v1/pelanggaran.routes.js
  90. 2 2
      routes/v1/pemantauan.routes.js
  91. 1 1
      routes/v1/pengunjung.routes.js
  92. 2 3
      routes/v1/pt.routes.js
  93. 9 7
      routes/v1/public.routes.js
  94. 1 1
      routes/v1/rekomendasi.routes.js
  95. 2 2
      routes/v1/sanksi/banding.routes.js
  96. 8 2
      routes/v1/sanksi/cabutSanksi.routes.js
  97. 16 2
      routes/v1/sanksi/index.js
  98. 2 2
      routes/v1/sanksi/keberatan.routes.js
  99. 9 2
      routes/v1/sanksi/perbaikan.routes.js
  100. 10 0
      routes/v1/signature.routes.js

+ 16 - 8
.env

@@ -1,10 +1,18 @@
-BASE_URL=http://localhost:5000
-PORT=5000
-SECRET=3d3eb3d842f4e595048f1806ca815f8092e29fb6b98a30dff0b8241ad0e6c273
+W8A1C=424f34ffbbde2007ad3797a8c0c91382d6478698bb18ed15c6576953d6bfc77b57c914cb85cef8059e7463a19cd3c40bda0ab2eb19a2534a6535de3e1ad550e1b058350fb4e2b916639df8226f4c9014c5506af17ef8e3ddd1ea3b3c14dd0afe8a1635757b7ed2828d70d46f98939561e11108fd229f7d4dc50cc6676df419dc0eb9
+P3UQ5=1d66650cb3bbe70d1426db09bdad22a326a1e3202598d9c92a7d43432b60a2b1cd835b13bd0aafb4497af0f494b3d0f78d4882fc5d50e05d10d19431a7ebc41b4d38bd92bda412e7f5a6ee52695a695e96b0b2aa29464437098e4377406a789652b69a92
+SRU51=3d3eb3d842f4e595048f1806ca815f8092e29fb6b98a30dff0b8241ad0e6c273
 
-MONGO_URL=mongodb://localhost:27017/ptb-db
-TOKEN_DEVELOPMENT=77aecfec-10ac-3b4f-ab59-3fbfbeed6324
-TOKEN_PRODUCTION=5b62f743-eef2-3370-8c66-6951b2e9c2c5
-TOKEN_AUTO=69qwerq986qwqjhq
+MYDSY=f11576b4cf1c9ce5b97274b9607339f446dce90589040372ed31895a50afabc9301f7d87a605a2ae6f467d4d96e5775c353fd18b93f56bab20ffe0ea9e88fda73c3a308c795e06c4df384f6332f3d80b22581646074d2ae97511ed7b35279c0b64a0047b5e1d3e1cee7b3e8a4f873b5995924a5b991a53c4fd0c45075d9dfab2e0a49827c82f8ff298689e7440764a015ce0a462bc475c957035987ead
+XNX1Q=3f551be0c7519b3765a033014ddb9fc041fb2118679b46ca59db24e33c42964d6b4e311108ff1db25580feaf8dd6b63098e58b980a78bd25f3a685370a834653cc2e1c26dd0e504a76fa4490cd054f3c3eefb0b2ccc044abbe3898173de93b5084ffd2f03bcb78d23da04587737bdd0e7e0fcafd04f7784c50e59f4fd013f3f671351be3
+TEKQU=72372a5ec47568b3e9743691dd37a8854840e688bd0b653274e9652913c5021404cb31178efc06b48947d35ef9a58f0b73bd79236b732215d0d7cef7fa5a2d14e12bee126627525080197bd889c8c9f1279a9a99c53d9da2b8d8aadbe435a30180cfc311cc843000d20cd69820476fa0e6dce2057fb32c1d1f1ea301c892ed1f3bfb301b
+CBGTB=d69969375fb168a7d6cc35e27a5cc982896c1c0ab35d875fd54dc13b1226bb4e962994e8af5b02dc8c2d91eab09b26d06c37ff7aca82813883f98a568a733f3587f857d417bbbcbc292e88e2b706c1e79253ba7c4d300b8081c7ae5c1a5087f6f1b06020b79a3e91903bbe8946c5086643b683a287
 
-ENV=local
+CXQSB=5c8e9b8cb0a154a9fb1683042e85231b600a18b57288165dea5a426a593ca811d4451a4b4f92714633adb67cd3fa622337208d345c82fdf23fb6673ceede764a237f4d704669533fb95a78df8a06023af172449f28f673a880360d30fd426d3c0a1b49cad1c52890a235
+
+AJFAL=fa6f3dd32440d496aef10dfc53f448ef13622d6dc9d1e8625bc9ed74a7f39e9cd57c6a557f0b2fb9e001ba26f8fa6c9e46523c59a5bd0bd1a23502c22a5e3dc91788ade605f1496f2308b2ee199019c835355d464c3683b21b97b322daaf751c4c0feea47484aa361e1a526e07f789030a95
+IOQUR=1304ccaa3dad35b104850390bd73f5624396a3c7983bc842d7425e2893a7370caddde7aeaa7bd91ce43a2b607e970d04f7c472190242236d2beca993bb6ec51851c3417cd4db096f8350b926efb5d67e190586642b76d18965719f01c4de0963f13c4e632cad36f3391cf875eb30bb0e2d7900a980121a80b28fd4308a03d2b3
+ZXCND=43caa966b96598953066715249e4eee1dc707002b4134a86f153780c91929cf129dae5a4f127a1e3cd2867dd867d73699e86f1360d06826278b6135d1aa837ef0aa423874dfecf7a24c9a8a6ce6d21b9db4b8700b1a3b95f3fc001183ee477691a503ba8a271a9ad54acfec7f37297b99a156e5c89560735bb52edf92b7fdb85
+
+AFA1T=3aab65ea2afcedf5bec31052040c70a082eba5d9709ae2b1b4563a2e5e71aeaee25eb001a2f45dc168fae4d20ee2121184c4b08d10ae53d09e2ffa42965cb56b232696770ec4f5c65f9e27400a407a01d547eaca7f7a9f546e219c4c6e3b208a56486f557500f2445dde225baa97141229f35d238557c48bc8fccbbea5141889
+BEAT2=de02f04ed1a1fd53e35c0e06f64c5dd1d8e1583f211344ac6cb921b1aabafe10ba38a2fd2600e1c61b92d8a0d010b83a2843f0326dde4263397be62eee8ab570e2fcf2b86b60571b77c3a96ce9955437cd1b006c8829056475cdc6aed07b7a0307e023582f3fae8b85bbf47ae6ec1a26b0b2be570b724f34b165c5aca991dba6
+R3AOP=bbc8aac0df8aef4a10f3f8e6f9b0bfe000a141a84fc9af9e8da43e338884210fc8af88eb16299c06e90909ae4e609a0781934081a9cf3e44e9f1e7fd2f78b3ef7efca853f8d4a5ba06b8dfe104d3c7a89090025afa605f3da170430b52c3f46132812e5ad907cef2139b972182cbac8dd51525b297e7082dd8940017f68443ce

+ 0 - 9
.env.example

@@ -1,9 +0,0 @@
-BASE_URL=http://localhost:5000
-PORT=5000
-SECRET=
-
-MONGO_URL=
-TOKEN_DEVELOPMENT=
-TOKEN_PRODUCTION=
-
-ENV=local

+ 8 - 2
.gitignore

@@ -56,11 +56,17 @@ typings/
 
 # dotenv environment variables file
 .env
+tes.js
 
 # next.js build output
-.next
+# .next
 
 # idea
+.idea/
 .vscode/
 
-public/images/
+public/images/
+
+#Jenkinsfile
+
+#dockerfile

+ 2 - 2
Jenkinsfile

@@ -10,10 +10,10 @@ node {
   def registryCredential
   try {
     // environment {
-      registryAddress = "https://registry.sidali.sixsenz.net"
+      registryAddress = "https://registry.sidali.kemdikbud.go.id"
       registryCredential = 'DockerRegistry-ID'
+
     // }
-	
     stage('Checkout') {
       checkout scm
     }

+ 15 - 5
app.js

@@ -3,8 +3,8 @@ const path = require('path')
 const cookieParser = require('cookie-parser')
 const logger = require('morgan')
 const cors = require('cors')
+const csrf = require('./middleware/csrf')
 const response = require('./utils/responseHandler')
-const dokumenController = require('./controller/dokumen.controller')
 const app = express()
 
 require('./config/db')()
@@ -13,15 +13,25 @@ app.use(logger('dev'))
 app.use(express.json())
 app.use(cors({ origin: true, credentials: true }))
 app.use(express.urlencoded({ extended: false }))
-app.use(cookieParser())
+app.use(cookieParser(process.env.SRU51))
+// app.use(csrf( ['GET', 'HEAD', 'OPTIONS'], ['/v1/auth/login', /\/v1\/auto\//i, /v2/i]))
+
 app.use(express.static(path.join(__dirname, 'public')))
+app.use((req, res, next) => {req.data = {}; return next()})
 
-// route version
-app.use('/v1', require('./routes/v1'))
-app.get('/dokumen/:id/:nama_file', dokumenController.getDokumen)
+// routes
+app.use('/', require('./routes'))
 
 app.use((req, res) =>
   response.error(res, { code: 404, message: 'request not found' })
 )
 
+app.use((err, req, res, next) => {
+  if (err.code === 'EBADCSRFTOKEN') {
+    response.error(res, { code: 403, message: 'invalid csrf token' })
+  } else {
+    response.error(res, { code: err.code || 500, message: err.message })
+  }
+})
+
 module.exports = app

+ 2 - 1
bin/www

@@ -7,12 +7,13 @@
 const app = require('../app')
 const debug = require('debug')('blog-post:server')
 const http = require('http')
+const coba = require('../utils/coba')
 require('dotenv').config()
 /**
  * Get port from environment and store in Express.
  */
 
-const port = normalizePort(process.env.PORT || '3000')
+const port = normalizePort(coba.decrypt(process.env.P3UQ5 )|| '3000')
 app.set('port', port)
 
 /**

+ 4 - 1
config/db.js

@@ -1,10 +1,13 @@
 const mongoose = require('mongoose')
 require('dotenv').config()
+const coba = require('../utils/coba')
 
 const main = () => {
-  mongoose.connect(process.env.MONGO_URL, {
+  mongoose.connect(coba.decrypt(process.env.MYDSY), {
     useNewUrlParser: true,
     useUnifiedTopology: true,
+    authMechanism: 'DEFAULT',
+    authSource: 'admin'
   })
 
   const db = mongoose.connection

+ 0 - 110
controller/auth.controller.js

@@ -1,110 +0,0 @@
-const handleError = require('../utils/handleError')
-const response = require('../utils/responseHandler')
-const userModel = require('../model/user.model')
-const jwt = require('jsonwebtoken')
-const { validate } = require('../utils/validation')
-const axios = require('../utils/axios')
-const qs = require('qs')
-const convertRole = require('../utils/convertRole')
-const { roleData, roleDataProduction } = require('../utils/constanta')
-const logModel = require('../model/log.model')
-const ip = require('ip')
-const osValue = require('../utils/osValue')
-
-exports.login = handleError(async (req, res) => {
-  const isValid = validate(res, req.body, {
-    username: 'string',
-    password: 'string',
-  })
-  if (!isValid) return
-
-  const { username, password } = req.body
-  const user = await axios.post(
-    'https://api.kemdikbud.go.id:8243/manakses/2.0/auth',
-    qs.stringify({
-      username,
-      password,
-    }),
-    {
-      'Content-Type': 'application/x-www-form-urlencoded',
-    }
-  )
-
-  if (user.code === 400) {
-    return response.error(res, {
-      code: 400,
-      message: user.message,
-    })
-  }
-
-  let cekUser = await userModel.findOne({
-    user_id: user.id,
-  })
-  let role = null
-  if (process.env.ENV === 'production') {
-    role = user.peran.filter((e) => roleDataProduction.includes(e.peran.id))[0]
-    role.peran.id = convertRole(role.peran.id)
-  } else {
-    role = user.peran.filter((e) => roleDataProduction.includes(e.peran.id))[0]
-  }
-  let dataRole = {
-    id: role.peran.id,
-    nama: role.peran.nama,
-    menu: role.peran.menu,
-  }
-  if (!cekUser) {
-    cekUser = await userModel.create({
-      user_id: user.id,
-      nama: user.nama,
-      lembaga: role.organisasi,
-      email: user.username,
-      no_hp: user.no_hp,
-      alamat: user.alamat,
-      role: dataRole,
-      isPublic: false,
-      isPrivate: false,
-    })
-  } else {
-    if (process.env.ENV === 'production') {
-      if (cekUser.role.id !== role.peran.id) {
-        await userModel.updateOne({ _id: cekUser._id }, { role: dataRole })
-      }
-      if (!cekUser.lembaga) {
-        await userModel.updateOne(
-          { _id: cekUser._id },
-          { lembaga: role.organisasi }
-        )
-      }
-      if (cekUser.role.id !== role.peran.id || !cekUser.lembaga) {
-        cekUser = await userModel.findOne({
-          user_id: user.id,
-        })
-      }
-    }
-  }
-
-  const accessToken = jwt.sign({ _id: cekUser._id }, process.env.SECRET, {
-    expiresIn: '1d',
-  })
-  const data = {
-    token: `Bearer ${accessToken}`,
-    user: cekUser,
-  }
-  const now = new Date()
-  const time = now.getTime()
-  now.setTime(time + 24 * 60 * 60 * 1000)
-  res.cookie('sidali-cookie', accessToken, { httpOnly: true, expires: now })
-
-  response.success(res, {
-    message: 'Berhasil Login',
-    data,
-  })
-})
-
-exports.logout = (req, res) => {
-  res.cookie('sidali-cookie', '', { expires: new Date() })
-
-  response.success(res, {
-    message: 'Berhasil Logout',
-  })
-}

+ 29 - 1
controller/dokumen.controller.js

@@ -1,5 +1,8 @@
 const chunkModel = require('../model/chunk.model')
-const handleError = require('../utils/handleError')
+const handleError = require('../utils/v1/handleError')
+const dokumenModel = require('../model/dokumen.model')
+const coba = require('../utils/coba')
+const response = require('../utils/responseHandler')
 
 exports.getDokumen = handleError(async (req, res) => {
   const { id } = req.params
@@ -7,3 +10,28 @@ exports.getDokumen = handleError(async (req, res) => {
   res.header('Content-Type', data.type)
   return res.end(Buffer.from(data.data))
 })
+
+exports.createDokumen = handleError(async (req, res) => {
+  const dokumen = req.file
+  const chunk = await chunkModel.create({
+    data: dokumen.buffer,
+    type: dokumen.mimetype,
+    size: dokumen.size,
+  })
+  const path =
+    coba.decrypt(process.env.W8A1C) +
+    '/dokumen/' +
+    chunk._id +
+    '/' +
+    dokumen.originalname
+  const data = await dokumenModel.create({
+    chunk: chunk._id,
+    type: dokumen.mimetype,
+    judul: Date.now() + '-' + dokumen.originalname,
+    path,
+  })
+  return response.success(res, {
+    data,
+    message: 'berhasil membuat dokumen',
+  })
+})

+ 0 - 545
controller/laporan.controller.js

@@ -1,545 +0,0 @@
-const axios = require('../utils/axios')
-const handleError = require('../utils/handleError')
-const response = require('../utils/responseHandler')
-const laporanModel = require('../model/laporan.model')
-const pelanggaranModel = require('../model/pelanggaran.model')
-const pemantauanModel = require('../model/pemantauan.model')
-const { validate } = require('../utils/validation')
-const { notifWA } = require('../utils/notifFunction')
-const { addManyDokumen } = require('../utils/dokumenFunction')
-const userModel = require('../model/user.model')
-const {
-  cekSatuDataLaporan,
-  cekBanyakDataLaporan,
-  cekBanyakDataSanksi,
-} = require('../utils/cekData')
-const { TEMPLATE_LAPORAN } = require('../utils/constanta')
-const logModel = require('../model/log.model')
-const kontakModel = require('../model/kontak.model')
-
-exports.create = handleError(async (req, res) => {
-  const user = req.user
-  const files = req.files
-
-  const isValid = validate(res, req.body, {
-    no_laporan: 'string',
-    pt_id: 'string',
-    pelanggaran_id: 'string',
-    keterangan: 'string',
-  })
-  if (!isValid) return
-
-  const { no_laporan, pt_id, keterangan } = req.body
-  let { pelanggaran_id } = req.body
-  const pt = await axios.get(
-    `https://api.kemdikbud.go.id:8243/pddikti/1.2/pt/${pt_id}`
-  )
-  if (pt.length === 0)
-    return response.error(res, {
-      message: 'pt_id tidak ditemukan',
-    })
-
-  let dokumen_id = []
-  if (files.length) {
-    const dokumen = await addManyDokumen(files)
-    dokumen_id = dokumen.map((e) => e._id)
-  }
-
-  pelanggaran_id = pelanggaran_id.split(',')
-  const pelanggaran = await pelanggaranModel.find({
-    _id: {
-      $in: pelanggaran_id,
-    },
-  })
-  if (!pelanggaran.length)
-    return response.error(res, { message: 'pelanggaran_id tidak ada' })
-
-  let data = {
-    no_laporan,
-    user: user._id,
-    dokumen: dokumen_id,
-    pt: pt[0],
-    pelanggaran: pelanggaran_id,
-    keterangan,
-    role_data: user.role.id === 2020 ? 'dikti' : 'lldikti',
-    role_asal: user.role.id === 2020 ? 'dikti' : 'lldikti',
-    level: 2,
-  }
-
-  data = await laporanModel.create(data)
-
-  const notif = await notifWA(TEMPLATE_LAPORAN, [
-    {
-      key: '1',
-      value: 'nama',
-      value_text: user.nama,
-    },
-    { key: '2', value: 'pt', value_text: pt[0].nama },
-    { key: '3', value: 'keterangan', value_text: keterangan },
-    { key: '4', value: 'no_laporan', value_text: no_laporan },
-  ])
-
-  let contacts = await kontakModel.find()
-  contacts = contacts.map((e) => e.nama).join(', ')
-  if (notif[0].status == 'success') {
-    await logModel.create({
-      aktivitas: `Server berhasil mengirim notif wa kepada ${contacts} untuk Pembuatan Laporan`,
-    })
-  }
-
-  await pemantauanModel.create({
-    laporan: data._id,
-    action: 'CREATE LAPORAN',
-    pt_id: pt[0].id,
-    user: user._id,
-    keterangan: 'Membuat Laporan',
-    dokumen: dokumen_id,
-    for_pt: false,
-  })
-
-  return response.success(res, {
-    message: 'Berhasil menambah laporan',
-    data,
-  })
-})
-
-// exports.public = handleError(async (req, res) => {
-//   const isValid = validate(res, req.body, {
-//     nama: 'string',
-//     email: 'email',
-//     alamat: 'string',
-//     no_hp: 'string',
-//     no_laporan: 'string',
-//     pt_id: 'string',
-//     pelanggaran_id: 'string',
-//     keterangan: 'string',
-//     is_private: { type: 'string', enum: ['true', 'false'] },
-//   })
-//   if (!isValid) return
-
-//   const {
-//     no_laporan,
-//     pt_id,
-//     keterangan,
-//     nama,
-//     email,
-//     alamat,
-//     no_hp,
-//     is_private,
-//   } = req.body
-//   let { pelanggaran_id } = req.body
-
-//   const pt = await axios.get(
-//     `https://api.kemdikbud.go.id:8243/pddikti/1.2/pt/${pt_id}`
-//   )
-//   if (!pt) {
-//     return response.error(res, {
-//       message: 'pt_id tidak ditemukan',
-//     })
-//   }
-
-//   const { dokumen, foto } = req.files
-//   if (!foto.length) {
-//     return response.error(res, {
-//       message: 'foto harus ada',
-//     })
-//   }
-//   const foto_id = await addDokumen(foto[0])
-//   const user = await userModel.create({
-//     nama,
-//     email,
-//     no_hp,
-//     alamat,
-//     isPublic: true,
-//     isPrivate: is_private === 'true',
-//     foto: foto_id,
-//   })
-//   let dokumen_id = []
-//   if (dokumen?.length) {
-//     const dataDokumen = await addManyDokumen(dokumen)
-//     dokumen_id = dataDokumen.map((e) => e._id)
-//   }
-
-//   pelanggaran_id = pelanggaran_id.split(',')
-//   const pelanggaran = await pelanggaranModel.find({
-//     _id: {
-//       $in: pelanggaran_id,
-//     },
-//   })
-//   if (!pelanggaran.length)
-//     return response.error(res, { message: 'pelanggaran_id tidak ada' })
-
-//   let data = {
-//     no_laporan,
-//     user: user._id,
-//     dokumen: dokumen_id,
-//     pt: pt[0],
-//     pelanggaran: pelanggaran_id,
-//     keterangan,
-//     role_data: 'dikti',
-//   }
-
-//   data = await laporanModel.create(data)
-//   await pemantauanModel.create({
-//     laporan: data._id,
-//     pt_id: pt[0].id,
-//     user: user._id,
-//     keterangan: 'Mengajukan Laporan',
-//     dokumen: dokumen_id,
-//     for_pt: false,
-//   })
-//   await notifWA('d5609c3c-e9e9-4dbe-9a4e-e8fa772d6770', [
-//     { key: '1', value: 'nama', value_text: nama },
-//     { key: '2', value: 'pt', value_text: pt[0].nama },
-//     { key: '3', value: 'keterangan', value_text: keterangan },
-//     { key: '4', value: 'no_laporan', value_text: no_laporan },
-//   ])
-//   return response.success(res, {
-//     message: 'Berhasil menambah laporan',
-//     data,
-//   })
-// })
-
-exports.public = handleError(async (req, res) => {
-  const user = req.user
-  const no_laporan = req.no_laporan
-  let level = req.level
-  const files = req.files
-
-  const isValid = validate(res, req.body, {
-    pt_id: 'string',
-    pelanggaran_id: 'string',
-    keterangan: 'string',
-    // no_verifikasi: 'string',
-  })
-  if (!isValid) return
-
-  const { pt_id, keterangan, no_verifikasi } = req.body
-  let { pelanggaran_id } = req.body
-  if (no_verifikasi && user.no_verifikasi !== no_verifikasi) {
-    return response.error(res, {
-      message: 'no_verifikasi tidak sesuai',
-      error: { no_verifikasi: 'No. Verifikasi tidak sesuai' },
-    })
-  } else if (no_verifikasi && user.no_verifikasi === no_verifikasi) {
-    level = 3
-  }
-
-  const pt = await axios.get(
-    `https://api.kemdikbud.go.id:8243/pddikti/1.2/pt/${pt_id}`
-  )
-  if (pt.length === 0)
-    return response.error(res, {
-      message: 'pt_id tidak ditemukan',
-    })
-
-  let dokumen_id = []
-  if (files.length) {
-    const dokumen = await addManyDokumen(files)
-    dokumen_id = dokumen.map((e) => e._id)
-  }
-
-  pelanggaran_id = pelanggaran_id.split(',')
-  const pelanggaran = await pelanggaranModel.find({
-    _id: {
-      $in: pelanggaran_id,
-    },
-  })
-  if (!pelanggaran.length)
-    return response.error(res, { message: 'pelanggaran_id tidak ada' })
-
-  let data = {
-    no_laporan,
-    user: user._id,
-    dokumen: dokumen_id,
-    pt: pt[0],
-    pelanggaran: pelanggaran_id,
-    keterangan,
-    role_data: 'dikti',
-    role_asal: 'dikti',
-    level,
-  }
-
-  data = await laporanModel.create(data)
-  await pemantauanModel.create({
-    laporan: data._id,
-    action: 'CREATE LAPORAN',
-    pt_id: pt[0].id,
-    user: user._id,
-    keterangan: 'Membuat Laporan',
-    dokumen: dokumen_id,
-    for_pt: false,
-  })
-
-  if (no_verifikasi)
-    await userModel.findByIdAndUpdate(user._id, { verified: true })
-  const notif = await notifWA(TEMPLATE_LAPORAN, [
-    {
-      key: '1',
-      value: 'nama',
-      value_text: user.isPrivate || !user.nama ? 'rahasia' : user.nama,
-    },
-    { key: '2', value: 'pt', value_text: pt[0].nama },
-    { key: '3', value: 'keterangan', value_text: keterangan },
-    { key: '4', value: 'no_laporan', value_text: no_laporan },
-  ])
-
-  let contacts = await kontakModel.find()
-  contacts = contacts.map((e) => e.nama).join(', ')
-  if (notif[0].status == 'success') {
-    await logModel.create({
-      aktivitas: `Server berhasil mengirim notif wa kepada ${contacts} untuk Pembuatan Laporan`,
-    })
-  }
-
-  return response.success(res, {
-    message: 'Berhasil menambah laporan',
-    data,
-  })
-})
-
-exports.getAll = handleError(async (req, res) => {
-  const user = req.user
-  const where = {}
-  const { no_laporan, pt_id, jadwal, evaluasi, aktif, delegasi, all, sanksi } =
-    req.query
-  if (no_laporan) where.no_laporan = no_laporan
-  if (pt_id) where['pt.id'] = pt_id
-  if (aktif) where.aktif = aktif === 'true'
-  if (all) where.all = true
-  else if (delegasi) where.delegasi = delegasi === 'true'
-
-  if (jadwal === 'true') {
-    where.jadwal = {
-      $exists: true,
-      $ne: null,
-    }
-  } else if (evaluasi === 'true') {
-    where.evaluasi = {
-      $exists: true,
-      $ne: null,
-      $not: {
-        $size: 0,
-      },
-    }
-  } else if (sanksi === 'true') {
-    where.sanksi = {
-      $exists: true,
-      $ne: null,
-    }
-  }
-  let data = await cekBanyakDataLaporan(user, where)
-  return response.success(res, {
-    message: 'Berhasil ambil data laporan',
-    data,
-  })
-})
-
-exports.getOne = handleError(async (req, res) => {
-  const { id } = req.params
-  const user = req.user
-  const { aktif, delegasi, all } = req.query
-  const where = {}
-  if (aktif) where.aktif = aktif === 'true'
-  if (all) where.all = true
-  else if (delegasi) where.delegasi = delegasi === 'true'
-  const data = await cekSatuDataLaporan(res, user, id, where)
-  if (!data) return
-  return response.success(res, {
-    message: 'Berhasil ambil data Laporan',
-    data,
-  })
-})
-
-exports.update = handleError(async (req, res) => {
-  const { id } = req.params
-  const user = req.user
-  const laporan = await cekSatuDataLaporan(res, user, id)
-  if (!laporan) return
-
-  const isValid = validate(res, req.body, {
-    change_role: { type: 'string', optional: true, enum: ['true', 'false'] },
-    aktif: { type: 'string', optional: true, enum: ['true', 'false'] },
-    keterangan: 'string',
-  })
-  if (!isValid) return
-
-  const data = {}
-  let keterangan = ''
-  let alasan = ''
-  const { change_role, aktif } = req.body
-  const keterangan2 = req.body.keterangan
-  if (change_role === 'true') {
-    data.role_data = user.role.id === 2020 ? 'lldikti' : 'dikti'
-    keterangan = `Laporan didelegasi ke ${
-      user.role.id === 2020 ? 'LLDIKTI' : 'DIKTI'
-    }`
-    alasan = keterangan2
-    data.alasan_delegasi = keterangan2
-    // if (laporan.jadwal) {
-    //   await laporanModel.findByIdAndUpdate(laporan._id, {
-    //     $unset: { jadwal: 1 },
-    //   })
-    // }
-  }
-  if (aktif) {
-    data.aktif = aktif === 'true'
-    if (aktif === 'true') {
-      keterangan = 'Laporan dibuka'
-    } else {
-      keterangan = `Laporan ditutup`
-      alasan = keterangan2
-    }
-  }
-
-  const update = await laporanModel.findByIdAndUpdate(laporan._id, data)
-  if (change_role || aktif) {
-    await pemantauanModel.create({
-      action: 'UPDATE LAPORAN',
-      laporan: laporan._id,
-      pt_id: laporan.pt.id,
-      user: user._id,
-      keterangan,
-      alasan,
-      for_pt: false,
-    })
-  }
-
-  return response.success(res, {
-    message: 'Berhasil update laporan',
-    data: update,
-  })
-})
-
-exports.jumlahLaporan = handleError(async (req, res) => {
-  const laporan = await laporanModel.aggregate([
-    {
-      $match: {
-        aktif: true,
-      },
-    },
-    {
-      $group: {
-        _id: '$pt.pembina.nama',
-        jumlah_laporan: {
-          $sum: 1,
-        },
-        propinsi: {
-          $addToSet: '$pt.propinsi.nama',
-        },
-      },
-    },
-    {
-      $sort: {
-        _id: 1,
-      },
-    },
-  ])
-
-  return response.success(res, {
-    message: 'Jumlah Laporan',
-    data: laporan,
-  })
-})
-
-exports.laporanByPembina = handleError(async (req, res) => {
-  const { idPembina } = req.params
-  const {
-    penjadwalan,
-    pemeriksaan,
-    sanksi,
-    keberatan,
-    banding,
-    perbaikan,
-    cabutSanksi,
-  } = req.query
-  const user = req.user
-  const where = {}
-  let isLaporan = true
-  let isSanksi = true
-
-  if (penjadwalan === 'true') {
-    where.jadwal = {
-      $exists: true,
-      $ne: null,
-    }
-    isLaporan = true
-    isSanksi = false
-  }
-  if (pemeriksaan === 'true') {
-    where.evaluasi = {
-      $exists: true,
-      $ne: null,
-      $not: {
-        $size: 0,
-      },
-    }
-    isLaporan = true
-    isSanksi = false
-  }
-  if (sanksi === 'true') {
-    where.sanksi = {
-      $exists: true,
-      $ne: null,
-    }
-    isLaporan = false
-    isSanksi = true
-  }
-  if (keberatan === 'true') {
-    where['pengajuan.keberatan'] = { $exists: true, $ne: null }
-    isLaporan = false
-    isSanksi = true
-  }
-  if (banding === 'true') {
-    where.banding = true
-    where['pengajuan.keberatan'] = { $exists: true, $ne: null }
-    where['jawaban.keberatan'] = { $exists: true, $ne: null }
-    where['pengajuan.banding'] = { $exists: true, $ne: null }
-    isLaporan = false
-    isSanksi = true
-  }
-  if (cabutSanksi === 'true') {
-    where.perbaikan = {
-      $exists: true,
-      $ne: null,
-      $not: {
-        $size: 0,
-      },
-    }
-    isLaporan = false
-    isSanksi = true
-  }
-  if (perbaikan === 'true') {
-    where['jawaban.banding'] = { $exists: true, $ne: null }
-    isLaporan = false
-    isSanksi = true
-  }
-
-  const [laporan, dataSanksi] = await Promise.all([
-    (async () =>
-      isLaporan
-        ? await cekBanyakDataLaporan(user, {
-            'pt.pembina.id': idPembina,
-            all: true,
-            ...where,
-          })
-        : [])(),
-    (async () =>
-      isSanksi
-        ? (
-            await cekBanyakDataSanksi(
-              user,
-              { all: true, ...where },
-              {
-                ['pt.pembina.id']: idPembina,
-              }
-            )
-          ).filter((e) => e.laporan != null)
-        : [])(),
-  ])
-
-  return response.success(res, {
-    message: 'berhasil get laporan by pembina',
-    data: { laporan, sanksi: dataSanksi },
-  })
-})

+ 0 - 22
controller/lembaga.controller.js

@@ -1,22 +0,0 @@
-const axios = require('../utils/axios')
-const handleError = require('../utils/handleError')
-const response = require('../utils/responseHandler')
-
-exports.get = handleError(async (req, res) => {
-  const { search } = req.query
-  let url = 'https://api.kemdikbud.go.id:8243/pddikti/1.2/lembaga-non-sp'
-  if (search) {
-    url += '?'
-    const parseURL = []
-    if (search) parseURL.push(`q=${search}`)
-    url += parseURL.join('&')
-  }
-  let data = await axios.get(url)
-  data = data.map((e) => {
-    return { id: e.id, nama: e.nama }
-  })
-  return response.success(res, {
-    message: 'Berhasil mengambil data lembaga',
-    data,
-  })
-})

+ 0 - 71
controller/pt.controller.js

@@ -1,71 +0,0 @@
-const axios = require('../utils/axios')
-const handleError = require('../utils/handleError')
-const response = require('../utils/responseHandler')
-
-exports.getAll = handleError(async (req, res) => {
-  const user = req.user
-  const pembina = user.role.id === 2021 ? user.lembaga.id : req.query.pembina
-  const { search } = req.query
-  let url =
-    user.role.id === 2022
-      ? `https://api.kemdikbud.go.id:8243/pddikti/1.2/pt/${user.lembaga.id}`
-      : 'https://api.kemdikbud.go.id:8243/pddikti/1.2/pt'
-  if (search || pembina) {
-    url += '?'
-    const parseURL = []
-    if (search) parseURL.push(`q=${search}`)
-    if (pembina) parseURL.push(`pembina=${pembina}`)
-    url += parseURL.join('&')
-  }
-
-  let data = await axios.get(url)
-
-  return response.success(res, {
-    message: 'Berhasil mengambil data Perguruan Tinggi',
-    data:
-      user.role.id === 2022
-        ? data[0]
-        : data.filter((e) => e.id !== '4B4B23C1-8E0C-4825-89FA-765401C5E9C5'),
-  })
-})
-
-exports.getOne = handleError(async (req, res) => {
-  const user = req.user
-  const { id } = req.params
-  let data = await axios.get(
-    `https://api.kemdikbud.go.id:8243/pddikti/1.2/pt/${id}`
-  )
-  data = data[0]
-  if (user.role.id === 2021 && data.pembina.id !== user.lembaga.id) {
-    return response.error(res, {
-      message: 'pt_id tidak ada',
-      code: 404,
-    })
-  }
-  return response.success(res, {
-    message: 'Berhasil mengambil satu data Perguruan Tinggi',
-    data,
-  })
-})
-
-exports.public = handleError(async (req, res) => {
-  const { search } = req.query
-  let url = 'https://api.kemdikbud.go.id:8243/pddikti/1.2/pt'
-
-  if (search) {
-    url += '?'
-    const parseURL = []
-    if (search) parseURL.push(`q=${search}`)
-    url += parseURL.join('&')
-  }
-  let data = await axios.get(url)
-  data = data
-    .map((e) => {
-      return { id: e.id, nama: e.nama }
-    })
-    .filter((e) => e.id !== '4B4B23C1-8E0C-4825-89FA-765401C5E9C5')
-  return response.success(res, {
-    message: 'Berhasil mengambil data Perguruan Tinggi',
-    data,
-  })
-})

+ 0 - 301
controller/sanksi.controller.js

@@ -1,301 +0,0 @@
-const sanksiModel = require('../model/sanksi.model')
-const handleError = require('../utils/handleError')
-const response = require('../utils/responseHandler')
-const { addManyDokumen } = require('../utils/dokumenFunction')
-const { validate } = require('../utils/validation')
-const pemantauanModel = require('../model/pemantauan.model')
-const { hariKerja } = require('../utils/hariKerja')
-const {
-  cekSatuDataSanksi,
-  cekSatuDataLaporan,
-  cekBanyakDataPelanggaran,
-  cekBanyakDataSanksi,
-} = require('../utils/cekData')
-const laporanModel = require('../model/laporan.model')
-
-exports.create = handleError(async (req, res) => {
-  const { no_sanksi, keterangan, from_date, to_date } = req.body
-  let { pelanggaran_id } = req.body
-  const { laporan_id } = req.params
-  const files = req.files
-  const user = req.user
-
-  const isValid = validate(res, req.body, {
-    no_sanksi: 'string',
-    keterangan: 'string',
-    pelanggaran_id: 'string',
-    from_date: 'string',
-    to_date: 'string',
-  })
-  if (!isValid) return
-
-  const laporan = await cekSatuDataLaporan(res, user, laporan_id, {
-    evaluasi: { $exists: true, $ne: [] },
-  })
-  if (!laporan) return
-
-  pelanggaran_id = await cekBanyakDataPelanggaran(res, pelanggaran_id)
-  if (!pelanggaran_id) return
-
-  const sanksi = await sanksiModel.findOne({ laporan: laporan_id })
-  if (sanksi) {
-    return response.error(res, {
-      message: 'Sanksi sudah ada',
-    })
-  }
-
-  if (!files.length) {
-    return response.error(res, {
-      message: 'dokumen harus ada',
-    })
-  }
-
-  const dokumen = await addManyDokumen(files)
-  const dokumen_id = dokumen.map((e) => e._id)
-  const data = await sanksiModel.create({
-    no_sanksi,
-    laporan: laporan._id,
-    user: user._id,
-    pelanggaran: pelanggaran_id,
-    keterangan,
-    dokumen: dokumen_id,
-    masa_berlaku: {
-      from_date,
-      to_date,
-    },
-    batas_waktu: {
-      keberatan: hariKerja(10),
-    },
-  })
-  await laporanModel.findByIdAndUpdate(laporan._id, {
-    sanksi: data._id,
-    // aktif: false,
-  })
-  await pemantauanModel.create({
-    laporan: laporan._id,
-    sanksi: data._id,
-    action: 'CREATE SANKSI',
-    pt_id: laporan.pt.id,
-    user: user._id,
-    keterangan: 'Melakukan penetapan Sanksi',
-    dokumen: dokumen_id,
-  })
-
-  return response.success(res, {
-    message: 'Berhasil membuat Sanksi',
-    data,
-  })
-})
-
-exports.update = handleError(async (req, res) => {
-  const { no_sanksi, keterangan, from_date, to_date } = req.body
-  let sanksiBody = req.body.sanksi
-  const { sanksi_id } = req.params
-  const files = req.files
-  const user = req.user
-
-  const isValid = validate(res, req.body, {
-    no_sanksi: 'string',
-    keterangan: 'string',
-    sanksi: 'string',
-    from_date: 'string',
-    to_date: 'string',
-  })
-  if (!isValid) return
-
-  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id, { all: true })
-  if (!sanksi) return
-
-  // pelanggaran_id = await cekBanyakDataPelanggaran(res, pelanggaran_id)
-  // if (!pelanggaran_id) return
-
-  sanksiBody = JSON.parse(sanksiBody)
-
-  // const sanksi = await sanksiModel.findOne({ laporan: laporan_id })
-  // if (sanksi) {
-  //   return response.error(res, {
-  //     message: 'Sanksi sudah ada',
-  //   })
-  // }
-
-  if (!files.length) {
-    return response.error(res, {
-      message: 'dokumen harus ada',
-    })
-  }
-
-  const dokumen = await addManyDokumen(files)
-  const dokumen_id = dokumen.map((e) => e._id)
-  const data = await sanksiModel.updateOne(
-    { _id: sanksi._id },
-    {
-      no_sanksi,
-      // pelanggaran: pelanggaran_id,
-      sanksi: sanksiBody,
-      keterangan,
-      dokumen: dokumen_id,
-      masa_berlaku: {
-        from_date,
-        to_date,
-      },
-      $push: {
-        riwayat_sanksi: sanksi,
-      },
-    }
-  )
-  // await laporanModel.findByIdAndUpdate(laporan._id, {
-  //   sanksi: data._id,
-  //   // aktif: false,
-  // })
-  await pemantauanModel.create({
-    laporan: sanksi.laporan._id,
-    sanksi: sanksi._id,
-    action: 'UPDATE SANKSI',
-    pt_id: sanksi.laporan.pt.id,
-    user: user._id,
-    keterangan: 'Melakukan Perubahan Sanksi',
-    dokumen: dokumen_id,
-  })
-
-  return response.success(res, {
-    message: 'Berhasil merubah Sanksi',
-    data,
-  })
-})
-
-exports.getAll = handleError(async (req, res) => {
-  const user = req.user
-  const {
-    keberatan,
-    jawaban,
-    banding,
-    cabutSanksi,
-    perbaikan,
-    aktif,
-    delegasi,
-    naikSanksi,
-    turunSanksi,
-  } = req.query
-  const where = {}
-  const q = {}
-  if (aktif && aktif === 'false') {
-    where.aktif = false
-  }
-  if (keberatan === 'true') {
-    where['pengajuan.keberatan'] = { $exists: true, $ne: null }
-    if (jawaban === 'true') {
-      where['jawaban.keberatan'] = { $exists: true, $ne: null }
-    }
-  }
-  if (banding === 'true') {
-    where.banding = true
-    where['pengajuan.keberatan'] = { $exists: true, $ne: null }
-    where['jawaban.keberatan'] = { $exists: true, $ne: null }
-    where['pengajuan.banding'] = { $exists: true, $ne: null }
-    if (jawaban === 'true') {
-      where['jawaban.banding'] = { $exists: true, $ne: null }
-    }
-  }
-  if (cabutSanksi === 'true') {
-    where.perbaikan = { $exists: true, $ne: [] }
-    if (jawaban === 'true') {
-      where['pengajuan.cabut_sanksi'] = { $exists: true, $ne: null }
-      // where['jawaban.cabut_sanksi'] = { $exists: true, $ne: null }
-    }
-  }
-  if (perbaikan === 'true') {
-    where['jawaban.banding'] = { $exists: true, $ne: null }
-  }
-  if (delegasi === 'true') {
-    where.delegasi = true
-  }
-  if (naikSanksi === 'true') {
-    q.level_sanksi = { $in: [1, 2] }
-  }
-  if (turunSanksi === 'true') {
-    q.level_sanksi = { $in: [2, 3] }
-  }
-  const data = await cekBanyakDataSanksi(user, where, q)
-  return response.success(res, {
-    message: 'Berhasil ambil data Sanksi',
-    data,
-  })
-})
-
-exports.getOne = handleError(async (req, res) => {
-  const user = req.user
-  const { sanksi_id } = req.params
-
-  const w = {}
-  const { banding, aktif, delegasi, all } = req.query
-  if (banding === 'true') {
-    w.banding = true
-    w['pengajuan.keberatan'] = { $exists: true, $ne: null }
-    w['jawaban.keberatan'] = { $exists: true, $ne: null }
-    w['pengajuan.banding'] = { $exists: true, $ne: null }
-  }
-  if (delegasi === 'true') {
-    w.delegasi = true
-  }
-  if (all === 'true') {
-    w.all = true
-  }
-  if (aktif && aktif === 'false') {
-    w.aktif = false
-  }
-
-  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id, w)
-  if (!sanksi) return
-
-  return response.success(res, {
-    message: 'Berhasil ambil satu data Sanksi',
-    data: sanksi,
-  })
-})
-
-exports.editTmt = handleError(async (req, res) => {
-  const user = req.user
-  const { id } = req.params
-  const { from_date, to_date } = req.body
-  const files = req.files
-
-  const sanksi = await cekSatuDataSanksi(res, user, id)
-  if (!sanksi) return
-
-  const isValid = validate(res, req.body, {
-    from_date: { type: 'date', convert: true },
-    to_date: { type: 'date', convert: true },
-  })
-  if (!isValid) return
-
-  if (!files.length) {
-    return response.error(res, {
-      message: 'dokumen harus ada',
-    })
-  }
-
-  const dokumen = await addManyDokumen(files)
-  const dokumen_id = dokumen.map((e) => e._id)
-
-  const data = await sanksiModel.findByIdAndUpdate(sanksi._id, {
-    masa_berlaku: {
-      from_date,
-      to_date,
-    },
-    'pengajuan.update_tmt': { dokumen: dokumen_id },
-  })
-
-  await pemantauanModel.create({
-    laporan: sanksi.laporan._id,
-    user: user._id,
-    action: 'EDIT TMT',
-    pt_id: sanksi.laporan.pt.id,
-    keterangan: 'Mengubah masa berlaku sanksi',
-    for_public: true,
-  })
-
-  return response.success(res, {
-    message: 'Berhasil update tmt',
-    data,
-  })
-})

+ 0 - 146
controller/sanksi/banding.controller.js

@@ -1,146 +0,0 @@
-const handleError = require('../../utils/handleError')
-const sanksiModel = require('../../model/sanksi.model')
-const { addManyDokumen } = require('../../utils/dokumenFunction')
-const { validate } = require('../../utils/validation')
-const { cekSatuDataSanksi } = require('../../utils/cekData')
-const response = require('../../utils/responseHandler')
-const { hariKerja } = require('../../utils/hariKerja')
-const pemantauanModel = require('../../model/pemantauan.model')
-const { notifWA } = require('../../utils/notifFunction')
-const { TEMPLATE_BANDING } = require('../../utils/constanta')
-const kontakModel = require('../../model/kontak.model')
-const logModel = require('../../model/log.model')
-
-exports.create = handleError(async (req, res) => {
-  const user = req.user
-  const { sanksi_id } = req.params
-
-  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id)
-  if (!sanksi) return
-
-  const files = req.files
-  if (!files?.length) {
-    return response.error(res, {
-      message: 'dokumen harus ada',
-    })
-  }
-  const dokumen = await addManyDokumen(files)
-  const dokumen_id = dokumen.map((e) => e._id)
-
-  const data = await sanksiModel.findOneAndUpdate(
-    {
-      laporan: sanksi.laporan._id,
-      _id: sanksi._id,
-      ['pengajuan.banding']: { $exists: false, $eq: null },
-      ['jawaban.keberatan']: { $exists: true, $ne: null },
-    },
-    {
-      ['pengajuan.banding']: {
-        dokumen: dokumen_id,
-      },
-      ['batas_waktu.jawaban_banding']: hariKerja(10),
-    }
-  )
-  if (!data) {
-    return response.error(res, {
-      message: 'pengajuan banding sudah ada atau jawaban keberatan belum ada',
-    })
-  }
-  await pemantauanModel.create({
-    laporan: sanksi.laporan._id,
-    sanksi: sanksi._id,
-    action: 'ADD BANDING',
-    pt_id: sanksi.laporan.pt.id,
-    user: user._id,
-    keterangan: 'Mengajukan Banding',
-    dokumen: dokumen_id,
-  })
-  const notif = await notifWA(TEMPLATE_BANDING, [
-    { key: '1', value: 'pt', value_text: sanksi.laporan.pt.nama },
-    {
-      key: '2',
-      value: 'pemberi_sanksi',
-      value_text: `${sanksi.user.nama} - ${sanksi.user.role.nama}`,
-    },
-    { key: '3', value: 'no_laporan', value_text: sanksi.laporan.no_laporan },
-  ])
-
-  let contacts = await kontakModel.find()
-  contacts = contacts.map((e) => e.nama).join(', ')
-  if (notif[0].status == 'success') {
-    await logModel.create({
-      aktivitas: `Server berhasil mengirim notif wa kepada ${contacts} untuk Mengajukan Banding dari PT ${sanksi.laporan.pt.nama}`,
-    })
-  }
-
-  return response.success(res, {
-    data,
-    message: 'Berhasil menambah pengajuan banding',
-  })
-})
-
-exports.createJawaban = handleError(async (req, res) => {
-  const user = req.user
-  const { sanksi_id } = req.params
-
-  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id, {
-    banding: true,
-  })
-  if (!sanksi) return
-
-  const isValid = validate(res, req.body, {
-    status: 'string',
-  })
-  if (!isValid) return
-
-  const files = req.files
-  let dokumen_id = []
-  if (files?.length) {
-    const dokumen = await addManyDokumen(files)
-    dokumen_id = dokumen.map((e) => e._id)
-  }
-
-  const { status } = req.body
-
-  const data = await sanksiModel.findOneAndUpdate(
-    {
-      laporan: sanksi.laporan._id,
-      _id: sanksi._id,
-      ['pengajuan.banding']: { $exists: true, $ne: null },
-    },
-    {
-      ['jawaban.banding']: {
-        status,
-        dokumen: dokumen_id,
-      },
-    }
-  )
-  if (!data) {
-    return response.error(res, {
-      message: 'banding tidak ada',
-    })
-  }
-
-  let message = 'Menjawab Pengajuan Banding'
-  let for_public = true
-  if (sanksi.jawaban?.banding) {
-    message = 'Mengubah jawaban Pengajuan Banding'
-    for_public = false
-  }
-
-  await pemantauanModel.create({
-    laporan: sanksi.laporan._id,
-    sanksi: sanksi._id,
-    action: 'ADD BANDING JAWABAN',
-    pt_id: sanksi.laporan.pt.id,
-    jawaban: status,
-    user: user._id,
-    keterangan: message,
-    dokumen: dokumen_id,
-    for_public,
-  })
-
-  return response.success(res, {
-    data,
-  })
-})

+ 0 - 126
controller/sanksi/cabutSanksi.controller.js

@@ -1,126 +0,0 @@
-const handleError = require('../../utils/handleError')
-const sanksiModel = require('../../model/sanksi.model')
-const { addManyDokumen } = require('../../utils/dokumenFunction')
-const { validate } = require('../../utils/validation')
-const { cekSatuDataSanksi, cekSatuDataLaporan } = require('../../utils/cekData')
-const response = require('../../utils/responseHandler')
-const pemantauanModel = require('../../model/pemantauan.model')
-
-exports.create = handleError(async (req, res) => {
-  const user = req.user
-  const { sanksi_id } = req.params
-
-  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id)
-  if (!sanksi) return
-
-  const files = req.files
-  if (!files?.length) {
-    return response.error(res, {
-      message: 'dokumen harus ada',
-    })
-  }
-  const dokumen = await addManyDokumen(files)
-  const dokumen_id = dokumen.map((e) => e._id)
-
-  const data = await sanksiModel.findOneAndUpdate(
-    {
-      laporan: sanksi.laporan._id,
-      _id: sanksi._id,
-      perbaikan: { $exists: true, $ne: [] },
-      ['pengajuan.cabut_sanksi']: { $exists: false, $eq: null },
-    },
-    {
-      ['pengajuan.cabut_sanksi']: {
-        dokumen: dokumen_id,
-      },
-    }
-  )
-  if (!data) {
-    return response.error(res, {
-      message: 'cabut_sanksi sudah ada',
-    })
-  }
-  await pemantauanModel.create({
-    laporan: sanksi.laporan._id,
-    sanksi: sanksi._id,
-    action: 'ADD CABUTSANKSI',
-    pt_id: sanksi.laporan.pt.id,
-    user: user._id,
-    keterangan: 'Mengajukan Pencabutan Sanksi',
-    dokumen: dokumen_id,
-  })
-  return response.success(res, {
-    data,
-    message: 'Berhasil menambah cabut_sanksi',
-  })
-})
-
-exports.createJawaban = handleError(async (req, res) => {
-  const user = req.user
-  const { sanksi_id } = req.params
-
-  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id)
-  if (!sanksi) return
-
-  const isValid = validate(res, req.body, {
-    status: 'string',
-    keterangan: 'string',
-  })
-  if (!isValid) return
-
-  let dokumen_id = null
-  if (req.body.status === 'Diterima') {
-    const files = req.files
-    if (!files?.length) {
-      return response.error(res, {
-        message: 'dokumen harus ada',
-      })
-    }
-    const dokumen = await addManyDokumen(files)
-    dokumen_id = dokumen.map((e) => e._id)
-  }
-
-  const { status, keterangan } = req.body
-  const data = await sanksiModel.findOneAndUpdate(
-    {
-      laporan: sanksi.laporan._id,
-      _id: sanksi._id,
-      ['pengajuan.cabut_sanksi']: { $exists: true, $ne: null },
-    },
-    {
-      aktif: status === 'Diterima' ? false : true,
-      ['jawaban.cabut_sanksi']: {
-        status,
-        keterangan,
-        dokumen: dokumen_id,
-      },
-    }
-  )
-  if (!data) {
-    return response.error(res, {
-      message: 'cabut_sanksi tidak ada',
-    })
-  }
-
-  let message = 'Menjawab Pengajuan Pencabutan Sanksi'
-  let for_public = true
-  if (sanksi.jawaban?.cabut_sanksi) {
-    message = 'Mengubah jawaban Pengajuan Pencabutan Sanksi'
-    for_public = false
-  }
-
-  await pemantauanModel.create({
-    laporan: sanksi.laporan._id,
-    sanksi: sanksi._id,
-    action: 'ADD CABUTSANKSI JAWABAN',
-    jawaban: status,
-    pt_id: sanksi.laporan.pt.id,
-    user: user._id,
-    keterangan: message,
-    dokumen: dokumen_id,
-    for_public,
-  })
-  return response.success(res, {
-    data,
-  })
-})

+ 0 - 155
controller/sanksi/keberatan.controller.js

@@ -1,155 +0,0 @@
-const handleError = require('../../utils/handleError')
-const sanksiModel = require('../../model/sanksi.model')
-const { addManyDokumen } = require('../../utils/dokumenFunction')
-const { validate } = require('../../utils/validation')
-const { cekSatuDataSanksi } = require('../../utils/cekData')
-const response = require('../../utils/responseHandler')
-const { hariKerja } = require('../../utils/hariKerja')
-const pemantauanModel = require('../../model/pemantauan.model')
-const { notifWA } = require('../../utils/notifFunction')
-const { TEMPLATE_KEBERATAN } = require('../../utils/constanta')
-const kontakModel = require('../../model/kontak.model')
-const logModel = require('../../model/log.model')
-
-exports.create = handleError(async (req, res) => {
-  const user = req.user
-  const { sanksi_id } = req.params
-  if (!sanksi_id) {
-    return response.error(res, {
-      message: 'query sanksi_id harus ada',
-    })
-  }
-
-  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id)
-  if (!sanksi) return
-
-  const files = req.files
-  if (!files?.length) {
-    return response.error(res, {
-      message: 'dokumen harus ada',
-    })
-  }
-  const dokumen = await addManyDokumen(files)
-  const dokumen_id = dokumen.map((e) => e._id)
-
-  const data = await sanksiModel.findOneAndUpdate(
-    {
-      laporan: sanksi.laporan._id,
-      _id: sanksi._id,
-      ['pengajuan.keberatan']: { $exists: false, $eq: null },
-    },
-    {
-      ['pengajuan.keberatan']: {
-        dokumen: dokumen_id,
-      },
-      ['batas_waktu.jawaban_keberatan']: hariKerja(10),
-    }
-  )
-  if (!data) {
-    return response.error(res, {
-      message: 'Pengajuan Keberatan sudah ada',
-    })
-  }
-  await pemantauanModel.create({
-    laporan: sanksi.laporan._id,
-    sanksi: sanksi._id,
-    action: 'ADD KEBERATAN',
-    user: user._id,
-    pt_id: sanksi.laporan.pt.id,
-    keterangan: 'Mengajukan Keberatan',
-    dokumen: dokumen_id,
-  })
-  const notif = await notifWA(TEMPLATE_KEBERATAN, [
-    { key: '1', value: 'pt', value_text: sanksi.laporan.pt.nama },
-    {
-      key: '2',
-      value: 'pemberi_sanksi',
-      value_text: `${sanksi.user.nama} - ${sanksi.user.role.nama}`,
-    },
-    { key: '3', value: 'no_laporan', value_text: sanksi.laporan.no_laporan },
-  ])
-
-  let contacts = await kontakModel.find()
-  contacts = contacts.map((e) => e.nama).join(', ')
-  if (notif[0].status == 'success') {
-    await logModel.create({
-      aktivitas: `Server berhasil mengirim notif wa kepada ${contacts} untuk Mengajukan Keberatan dari PT ${sanksi.laporan.pt.nama}`,
-    })
-  }
-
-  return response.success(res, {
-    data,
-    message: 'Berhasil menambah keberatan',
-  })
-})
-
-exports.createJawaban = handleError(async (req, res) => {
-  const user = req.user
-  const { sanksi_id } = req.params
-  if (!sanksi_id) {
-    return response.error(res, {
-      message: 'query sanksi_id harus ada',
-    })
-  }
-
-  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id)
-  if (!sanksi) return
-
-  const isValid = validate(res, req.body, {
-    status: 'string',
-    keterangan: 'string',
-  })
-  if (!isValid) return
-
-  const files = req.files
-  let dokumen_id = []
-  if (files?.length) {
-    const dokumen = await addManyDokumen(files)
-    dokumen_id = dokumen.map((e) => e._id)
-  }
-
-  const { status, keterangan } = req.body
-  const data = await sanksiModel.findOneAndUpdate(
-    {
-      laporan: sanksi.laporan._id,
-      _id: sanksi._id,
-      ['pengajuan.keberatan']: { $exists: true, $ne: null },
-    },
-    {
-      ['jawaban.keberatan']: {
-        status,
-        keterangan,
-        dokumen: dokumen_id,
-      },
-      ['batas_waktu.banding']: hariKerja(21),
-    }
-  )
-  if (!data) {
-    return response.error(res, {
-      message: 'keberatan tidak ada',
-    })
-  }
-
-  let message = 'Menjawab Pengajuan Keberatan'
-  let for_public = true
-  if (sanksi.jawaban?.keberatan) {
-    message = 'Mengubah jawaban Pengajuan Keberatan'
-    for_public = false
-  }
-
-  await pemantauanModel.create({
-    laporan: sanksi.laporan._id,
-    sanksi: sanksi._id,
-    user: user._id,
-    action: 'ADD KEBERATAN JAWABAN',
-    jawaban: status,
-    pt_id: sanksi.laporan.pt.id,
-    keterangan: message,
-    dokumen: dokumen_id,
-    for_public,
-  })
-
-  return response.success(res, {
-    data,
-  })
-})

+ 0 - 91
controller/sanksi/perbaikan.controller.js

@@ -1,91 +0,0 @@
-const handleError = require('../../utils/handleError')
-const sanksiModel = require('../../model/sanksi.model')
-const { addManyDokumen } = require('../../utils/dokumenFunction')
-const { validate } = require('../../utils/validation')
-const { cekSatuDataSanksi } = require('../../utils/cekData')
-const response = require('../../utils/responseHandler')
-const pemantauanModel = require('../../model/pemantauan.model')
-const { notifWA } = require('../../utils/notifFunction')
-const { TEMPLATE_PERBAIKAN_DOKUMEN } = require('../../utils/constanta')
-const kontakModel = require('../../model/kontak.model')
-const logModel = require('../../model/log.model')
-
-exports.add = handleError(async (req, res) => {
-  const user = req.user
-  const { sanksi_id } = req.params
-
-  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id)
-  if (!sanksi) return
-
-  const isValid = validate(res, req.body, {
-    keterangan: 'string',
-  })
-  if (!isValid) return
-
-  const files = req.files
-  if (!files?.length) {
-    return response.error(res, {
-      message: 'dokumen harus ada',
-    })
-  }
-  const dokumen = await addManyDokumen(files)
-  const dokumen_id = dokumen.map((e) => e._id)
-
-  const { keterangan } = req.body
-  const data = await sanksiModel.findOneAndUpdate(
-    {
-      laporan: sanksi.laporan._id,
-      _id: sanksi._id,
-      aktif: true,
-      ['pengajuan.banding']: { $exists: true, $ne: null },
-      // ['pengajuan.cabut_sanksi']: {
-      //   $exists: false,
-      //   $eq: null,
-      // },
-    },
-    {
-      $push: {
-        perbaikan: {
-          keterangan,
-          dokumen: dokumen_id,
-        },
-      },
-    }
-  )
-  if (!data) {
-    return response.error(res, {
-      message: 'Pengajuan banding atau cabut sanksi tidak ada',
-    })
-  }
-  await pemantauanModel.create({
-    laporan: sanksi.laporan._id,
-    sanksi: sanksi._id,
-    action: 'ADD PERBAIKAN DOKUMEN',
-    user: user._id,
-    pt_id: sanksi.laporan.pt.id,
-    keterangan: 'Melakukan Perbaikan Dokumen',
-    dokumen: dokumen_id,
-  })
-  const notif = await notifWA(TEMPLATE_PERBAIKAN_DOKUMEN, [
-    { key: '1', value: 'pt', value_text: sanksi.laporan.pt.nama },
-    {
-      key: '2',
-      value: 'pemberi_sanksi',
-      value_text: `${sanksi.user.nama} - ${sanksi.user.role.nama}`,
-    },
-    { key: '3', value: 'no_laporan', value_text: sanksi.laporan.no_laporan },
-  ])
-
-  let contacts = await kontakModel.find()
-  contacts = contacts.map((e) => e.nama).join(', ')
-  if (notif[0].status == 'success') {
-    await logModel.create({
-      aktivitas: `Server berhasil mengirim notif wa kepada ${contacts} perihal Dokumen Perbaikan dari PT ${sanksi.laporan.pt.nama}`,
-    })
-  }
-
-  return response.success(res, {
-    data,
-    message: 'Berhasil menambah Perbaikan',
-  })
-})

+ 180 - 0
controller/v1/auth.controller.js

@@ -0,0 +1,180 @@
+const handleError = require('../../utils/v1/handleError')
+const response = require('../../utils/responseHandler')
+const userModel = require('../../model/user.model')
+const jwt = require('jsonwebtoken')
+const { validate } = require('../../utils/v1/validation')
+const convertRole = require('../../utils/convertRole')
+const { roleDataProduction } = require('../../utils/constanta')
+const logModel = require('../../model/log.model')
+const pddiktiService = require('../../services/v2/pddikti.service')
+
+exports.login = handleError(async (req, res) => {
+  const isValid = validate(res, req.body, {
+    username: 'string',
+    password: 'string',
+  })
+  if (!isValid) return
+
+  let cekUser = null;
+  const { username, password } = req.body
+  let user = await pddiktiService.login(req.body)
+
+  cekUser = await userModel.findOne({
+    email: username,
+    text: password
+  })
+
+  let role = null
+  if (!cekUser) {
+    if (user.code === 400) {
+      return response.error(res, {
+        code: 400,
+        message: user.message,
+      })
+    } else {
+      cekUser = await userModel.findOne({
+        user_id: user.id,
+      })
+      role = user.peran.filter((e) => roleDataProduction.includes(e.peran.id))[0]
+      role.peran.id = convertRole(role.peran.id)
+
+      await userModel.updateOne({
+        user_id: user.id,
+      }, {
+        lembaga: role.organisasi,
+        role: {
+          id: username.toLowerCase() === 'rizqevo@outlook.com'? 2020 : username.toLowerCase() === 'sugiyanto@gmail.com'? 2024 : role.peran.id,
+          nama: username.toLowerCase() === 'rizqevo@outlook.com'? 'PTB Dikti' : username.toLowerCase() === 'sugiyanto@gmail.com'? 'ReadOnly' : role.peran.nama,
+          menu: role.peran.menu,
+        }
+      })
+    }
+
+  } else {
+    role = {
+      peran: {
+        id: cekUser.role.id,
+        nama: cekUser.role.nama,
+        menu: cekUser.role.menu,
+      }
+    }
+  }
+
+  let dataRole = {
+    id: role.peran.id,
+    nama: role.peran.nama,
+    menu: role.peran.menu,
+  }
+  if (!cekUser) {
+    cekUser = await userModel.create({
+      user_id: user.id,
+      nama: user.nama,
+      lembaga: role.organisasi,
+      email: user.username,
+      no_hp: user.no_hp,
+      alamat: user.alamat,
+      role: dataRole,
+      isPublic: false,
+      isPrivate: false,
+    })
+  } else {
+    if (!cekUser.lembaga) {
+      await userModel.updateOne(
+        { _id: cekUser._id },
+        { lembaga: role.organisasi }
+      )
+    }
+    if (cekUser.role.id !== role.peran.id || !cekUser.lembaga) {
+      cekUser = await userModel.findOne({
+        user_id: user.id,
+      })
+    }
+  }
+
+  const accessToken = jwt.sign({ _id: cekUser._id }, process.env.SRU51, {
+    expiresIn: '1d',
+  })
+  const data = {
+    token: `Bearer ${accessToken}`,
+    user: cekUser,
+  }
+  const now = new Date()
+  const time = now.getTime()
+  now.setTime(time + 24 * 60 * 60 * 1000)
+  res.cookie('sidali-cookie', accessToken, {
+    httpOnly: true,
+    expires: now,
+  })
+
+  response.success(res, {
+    message: 'Berhasil Login',
+    data,
+  })
+})
+
+exports.logout = (req, res) => {
+  res.cookie('sidali-cookie', '', {
+    expires: new Date(),
+  })
+
+  response.success(res, {
+    message: 'Berhasil Logout',
+  })
+}
+
+exports.loginToPT = handleError(async (req, res) => {
+  const isValid = validate(res, req.body, {
+    lembaga_id: 'string',
+    password: 'string',
+  })
+  if (!isValid) return
+
+  let user = req.user
+  const { lembaga_id, password } = req.body
+  let cekUser = await pddiktiService.login({username: user.email, password})
+  if (cekUser.code && cekUser.code !== 200)
+    return response.error(res, {
+    code: 401,
+    message: cekUser.message,
+  })
+
+  const dataLembaga = await pddiktiService.getPT(lembaga_id)
+
+  await userModel.updateOne({
+    _id: user._id
+  },{
+    lembaga: {
+      id: dataLembaga[0].id,
+      nama: dataLembaga[0].nama,
+    },
+    role: {
+      id: 2022,
+      nama: 'PTB PT',
+    }
+  })
+  user = await userModel.findOne({_id: user._id})
+  await logModel.create({
+    user: user._id,
+    aktivitas: `${user.nama} berhasil masuk ke PT ${dataLembaga[0].nama}`
+  })
+
+  const accessToken = jwt.sign({ _id: user._id }, process.env.SRU51, {
+    expiresIn: '1d',
+  })
+  const data = {
+    token: `Bearer ${accessToken}`,
+    user,
+  }
+  const now = new Date()
+  const time = now.getTime()
+  now.setTime(time + 24 * 60 * 60 * 1000)
+  res.cookie('sidali-cookie', accessToken, {
+    httpOnly: true,
+    expires: now,
+  })
+
+  response.success(res, {
+    message: 'Berhasil Login',
+    data,
+  })
+})

+ 454 - 0
controller/v1/auto.controller.js

@@ -0,0 +1,454 @@
+const handleError = require('../../utils/v1/handleError')
+const response = require('../../utils/responseHandler')
+const { notifWA } = require('../../utils/v1/notifFunction')
+const sanksiModel = require('../../model/sanksi.model')
+const laporanModel = require('../../model/laporan.model')
+const {
+  TEMPLATE_REMINDER,
+  TEMPLATE_REMINDER2, TRUE, UPDATE_SANKSI, CREATE_SANKSI, PTB_ADMIN, PTB_DIKTI, PTB_LLDIKTI
+} = require('../../utils/constanta')
+const moment = require('moment')
+const autoSaveModel = require('../../model/autoSave.model')
+const { cekSatuDataSanksi, cekSatuDataLaporan } = require('../../utils/v1/cekData')
+const logModel = require('../../model/log.model')
+const kontakModel = require('../../model/kontak.model')
+const pemantauanModel = require('../../model/pemantauan.model')
+const batchModel = require('../../model/batch.model')
+const pddiktiService = require('../../services/v2/pddikti.service')
+
+exports.keberatan = handleError(async (req, res) => {
+  const dataSanksi = await sanksiModel
+    .find({
+      'tanggal_akhir_keberatan': {
+        $lte: moment().toISOString()
+      },
+      'is_pengajuan_keberatan': {
+        $exists: false,
+        $eq: null
+      }
+    })
+    .populate('user')
+    .populate('laporan')
+
+  if (!dataSanksi.length) {
+    return response.success(res, {
+      message: 'Tidak ada keberatan yang diubah'
+    })
+  }
+
+  await Promise.all(
+    dataSanksi.map(
+      async (sanksi) =>
+        await sanksiModel.findByIdAndUpdate(sanksi._id, {
+          is_pengajuan_keberatan: false,
+          last_step: 'Dokumen Perbaikan'
+        })
+    )
+  )
+  await logModel.create({
+    aktivitas: `Server berhasil merubah last_step PT ${dataSanksi.map((e) => e.laporan.pt.nama).join(', ')} menjadi Dokumen Perbaikan`
+  })
+  return response.success(res, {
+    message: 'Notifikasi berhasil terkirim'
+  })
+})
+
+exports.banding = handleError(async (req, res) => {
+  const dataSanksi = await sanksiModel
+    .find({
+      'tanggal_akhir_banding': {
+        $lt: moment().toISOString()
+      },
+      'is_pengajuan_banding': {
+        $exists: false,
+        $eq: null
+      },
+      'jawaban.keberatan': {
+        $exists: true,
+        $ne: null
+      }
+    })
+    .populate('user')
+    .populate('laporan')
+
+  if (!dataSanksi.length) {
+    return response.success(res, {
+      message: 'Tidak ada banding yang diubah'
+    })
+  }
+
+  await Promise.all(
+    dataSanksi.map(
+      async (sanksi) => {
+        await sanksiModel.findByIdAndUpdate(sanksi._id, {
+          is_pengajuan_banding: false,
+          last_step: 'Dokumen Perbaikan'
+        })
+      }
+    )
+  )
+  await logModel.create({
+    aktivitas: `Server berhasil merubah last_step PT ${dataSanksi.map((e) => e.laporan.pt.nama).join(', ')} menjadi Dokumen Perbaikan`
+  })
+  return response.success(res, {
+    message: 'Notifikasi berhasil terkirim'
+  })
+})
+
+exports.reminderKeberatan = handleError(async (req, res) => {
+  let dataSanksi = await sanksiModel
+    .find({
+      'tanggal_akhir_keberatan': {
+        $exists: true,
+        $ne: null
+      },
+      'jawaban.keberatan': {
+        $exists: false,
+        $eq: null
+      }
+    })
+    .populate('user')
+    .populate('laporan')
+  const notif = await Promise.all(
+    dataSanksi.map(async (e) => {
+      const dayLeft = moment(e.batas_waktu.jawaban_keberatan).diff(
+        new Date(),
+        'days'
+      )
+      if (dayLeft > 0 && dayLeft <= 7) {
+        try {
+          await notifWA(TEMPLATE_REMINDER, [
+            {
+              key: '1',
+              value: 'no_laporan',
+              value_text: e.laporan.no_laporan
+            },
+            {
+              key: '2',
+              value: 'keterangan',
+              value_text: 'Proses Menjawab Pengajuan Keberatan'
+            },
+            {
+              key: '3',
+              value: 'pt',
+              value_text: e.laporan.pt.nama
+            },
+            {
+              key: '4',
+              value: 'masa',
+              value_text: `menjawab pengajuan keberatan tersisa ${dayLeft} hari lagi.`
+            }
+          ])
+          const contacts = await kontakModel.find({
+            'role.id': {
+              $in: [PTB_ADMIN, PTB_DIKTI, PTB_LLDIKTI]
+            }
+          })
+          await logModel.create({
+            aktivitas: `Server berhasil mengirim notifikasi Whatsapp kepada ${contacts.map((e) => e.nama).join(', ')} dengan Nomor Laporan ${e.laporan.no_laporan} terhadap ${e.laporan.pt.nama} untuk Mengajukan Keberatan`
+          })
+        } catch (error) {
+          return response.error(res, {
+            message: 'Notifikasi gagal terkirim',
+            error: error.message
+          })
+        }
+      }
+    })
+  )
+
+  let message = 'Tidak ada notifikasi yang dikirim'
+  if (notif.length) message = 'Notifikasi berhasil terkirim'
+  return response.success(res, {
+    message
+  })
+})
+
+exports.reminderBanding = handleError(async (req, res) => {
+  let dataSanksi = await sanksiModel
+    .find({
+      'tanggal_akhir_banding': {
+        $exists: true,
+        $ne: null
+      },
+      'jawaban.banding': {
+        $exists: false,
+        $eq: null
+      }
+    })
+    .populate('user')
+    .populate('laporan')
+  const notif = await Promise.all(
+    dataSanksi.map(async (e) => {
+      const dayLeft = moment(e.batas_waktu.jawaban_banding).diff(
+        new Date(),
+        'days'
+      )
+      if (dayLeft > 0 && dayLeft <= 7) {
+        try {
+          await notifWA(TEMPLATE_REMINDER, [
+            {
+              key: '1',
+              value: 'no_laporan',
+              value_text: e.laporan.no_laporan
+            },
+            {
+              key: '2',
+              value: 'keterangan',
+              value_text: 'Proses Menjawab Pengajuan Banding'
+            },
+            {
+              key: '3',
+              value: 'pt',
+              value_text: e.laporan.pt.nama
+            },
+            {
+              key: '4',
+              value: 'masa',
+              value_text: `menjawab pengajuan banding tersisa ${dayLeft} hari lagi.`
+            }
+          ])
+          const contacts = await kontakModel.find({
+            'role.id': {
+              $in: [PTB_ADMIN, PTB_DIKTI, PTB_LLDIKTI]
+            }
+          })
+          await logModel.create({
+            aktivitas: `Server berhasil mengirim notifikasi Whatsapp kepada ${contacts.map((e) => e.nama).join(', ')} dengan Nomor Laporan ${e.laporan.no_laporan} terhadap ${e.laporan.pt.nama} untuk Mengajukan Banding`
+          })
+        } catch (error) {
+          return response.error(res, {
+            message: 'Notifikasi gagal terkirim',
+            error: error.message
+          })
+        }
+      }
+    })
+  )
+
+  let message = 'Tidak ada notifikasi yang dikirim'
+  if (notif.length) message = 'Notifikasi berhasil terkirim'
+  return response.success(res, {
+    message
+  })
+})
+
+exports.updateStatusSanksi = handleError(async (req, res) => {
+  const sanksi = await sanksiModel.find({
+    'masa_berlaku.to_date': {
+      $lte: new Date().toISOString()
+    },
+    aktif: true,
+    'masa_berlaku.berakhir': {
+      $exists: false,
+      $eq: null
+    }
+  }).populate('laporan')
+
+  await Promise.all(
+    sanksi.map(async (e) => {
+      await pemantauanModel.create({
+        laporan: e.laporan._id,
+        sanksi: e._id,
+        action: UPDATE_SANKSI,
+        pt_id: e.laporan.pt.id,
+        keterangan: 'Masa berlaku sanksi sudah melewati TMT',
+        levelSanksi: 0
+      })
+      await sanksiModel.findByIdAndUpdate(e._id, { 'masa_berlaku.berakhir': true })
+      await batchModel.create({
+        sanksi: e._id,
+        type: UPDATE_SANKSI
+      })
+    })
+  )
+
+  return response.success(res, {
+    message: 'update status sanksi berhasil'
+  })
+})
+
+exports.save = handleError(async (req, res) => {
+  const { id } = req.params
+  const { laporan: isLaporan, sanksi: isSanksi } = req.query
+  const user = req.user
+
+  let autoData = null
+  let laporan = null
+  let sanksi = null
+  if (isLaporan === TRUE) {
+    laporan = await cekSatuDataLaporan(res, user, id)
+    if (!laporan) return
+    autoData = await autoSaveModel.findOne({ laporan_id: laporan._id })
+  } else if (isSanksi === TRUE) {
+    sanksi = await cekSatuDataSanksi(res, user, id)
+    if (!sanksi) return
+    autoData = await autoSaveModel.findOne({ sanksi_id: sanksi._id })
+  } else {
+    return response.error(res, {
+      message: 'query harus sanksi atau laporan yg bernilai true'
+    })
+  }
+
+  if (autoData) {
+    if (isLaporan) {
+      const dataSave = await autoSaveModel.findOne({ laporan_id: laporan._id })
+      await autoSaveModel.updateOne({ laporan_id: laporan._id }, {
+        laporan: {
+          ...req.body, PenetapanSanksi: {
+            dataSuratBA: req.body?.PenetapanSanksi?.dataSuratBA || dataSave.laporan.PenetapanSanksi?.dataSuratBA,
+            dataUpload: req.body?.PenetapanSanksi?.dataUpload || dataSave.laporan.PenetapanSanksi?.dataUpload,
+            dataPelanggaran: req.body?.PenetapanSanksi?.dataPelanggaran || dataSave.laporan.PenetapanSanksi?.dataPelanggaran,
+            activeStep: req.body?.PenetapanSanksi?.activeStep || dataSave.laporan.PenetapanSanksi?.activeStep
+          }
+        }
+      })
+    } else {
+      await autoSaveModel.updateOne({ sanksi_id: sanksi._id }, { sanksi: req.body })
+    }
+  } else {
+    if (isLaporan) {
+      await autoSaveModel.create({ laporan_id: laporan._id, laporan: req.body })
+    } else {
+      await autoSaveModel.create({ sanksi_id: sanksi._id, sanksi: req.body })
+    }
+  }
+
+  return response.success(res, {
+    message: 'Berhasil menyimpan data auto save'
+  })
+})
+
+exports.getSave = handleError(async (req, res) => {
+  const { id } = req.params
+  const { laporan: isLaporan, sanksi: isSanksi } = req.query
+
+  let data = null
+  let laporan = null
+  let sanksi = null
+  if (isLaporan === TRUE) {
+    laporan = await laporanModel.findById(id)
+    if (!laporan) return response.error(res, {
+      code: 404,
+      message: 'laporan_id tidak ada'
+    })
+    data = (await autoSaveModel.findOne({ laporan_id: laporan._id })).laporan
+  } else if (isSanksi === 'true') {
+    sanksi = await sanksiModel.findById(id)
+    if (!sanksi) return response.error(res, {
+      code: 404,
+      message: 'sanksi_id tidak ada'
+    })
+    data = (await autoSaveModel.findOne({ sanksi_id: sanksi._id })).sanksi
+  } else {
+    return response.error(res, {
+      message: 'harus terdapat query sanksi atau laporan yg bernilai true'
+    })
+  }
+
+  return response.success(res, {
+    message: 'Berhasil mengambil data auto save',
+    data: data
+  })
+})
+
+exports.berakhirSanksi = handleError(async (req, res) => {
+  const sanksi = await sanksiModel.find({
+    'masa_berlaku.to_date': {
+      $ne: null,
+      $exists: true
+    }
+  }).populate('laporan')
+  let count = 0
+
+  await Promise.all(sanksi.map(async e => {
+    const dayLeft = moment(e.masa_berlaku.to_date).diff(
+      new Date(),
+      'days'
+    )
+    if (dayLeft > 0 && dayLeft <= 7) {
+      try {
+        await notifWA(TEMPLATE_REMINDER2, [
+          {
+            key: '1',
+            value: 'no_sanksi',
+            value_text: `nomor sanksi ${e.no_sanksi}`
+          },
+          {
+            key: '2',
+            value: 'nama_pt',
+            value_text: `${e.laporan.pt.nama}`
+          },
+          {
+            key: '3',
+            value: 'keterangan',
+            value_text: `habis masa berlaku sanksi tersisa ${dayLeft} hari lagi.`
+          }
+        ])
+        count++
+        const contacts = await kontakModel.find({
+          'role.id': {
+            $in: [PTB_ADMIN, PTB_DIKTI, PTB_LLDIKTI]
+          }
+        })
+        await logModel.create({
+          aktivitas: `Server berhasil mengirim notifikasi reminder Whatsapp kepada ${contacts.map((e) => e.nama).join(', ')} dengan Nomor Sanksi ${e.no_sanksi} terhadap ${e.laporan.pt.nama} bahwa Masa Berlaku Sanksi tersisa ${dayLeft} hari lagi.`
+        })
+      } catch (error) {
+        return response.error(res, {
+          message: 'Notifikasi gagal terkirim',
+          error: error.message
+        })
+      }
+    }
+  }))
+  let message = 'Notifikasi berhasil terkirim'
+  if (count === 0) message = 'tidak ada notifikasi yg dikirim'
+  return response.success(res, {
+    message
+  })
+})
+
+exports.batchUpdateSanksi = handleError(async (req, res) => {
+  const data = await batchModel.find({
+    type: UPDATE_SANKSI
+  })
+    .populate({
+      path: 'sanksi',
+      populate: ['laporan']
+    })
+  let error = []
+  await Promise.all(data.map(async e => {
+    const { sanksi } = e
+    if (!sanksi?.laporan) return
+    try {
+      await pddiktiService.updatePDDIKTI({
+        ptKode: sanksi.laporan.pt.kode,
+        noSanksi: sanksi.laporan.no_laporan,
+        fromDate: moment(sanksi.masa_berlaku.from_date).format('YYYY-MM-DD'),
+        levelSanksi: sanksi.levelSanksi,
+        terimaSanksi: moment(sanksi.tanggal_terima_sanksi).format('YYYY-MM-DD')
+      })
+      await logModel.create({
+        aktivitas: `Server berhasil mengirimkan data Ke API PDDIKTI untuk update Status PT ${sanksi.laporan.pt.nama}`
+      })
+      await batchModel.deleteOne(e._id)
+    } catch (e) {
+      error.push(e.message)
+      await Promise.all([
+        logModel.create({
+          aktivitas: `Server gagal mengirimkan data Ke API PDDIKTI untuk update Status PT ${sanksi.laporan.pt.nama}, dengan error ${e.message}`
+        }),
+        pemantauanModel.deleteOne({
+          laporan: sanksi.laporan._id,
+          sanksi: sanksi._id,
+          action: CREATE_SANKSI
+        })
+      ])
+    }
+  }))
+  return response.success(res, {
+    message: error.length ? 'Ada beberapa data yang tidak bisa dikirim ke PDDIKTI' : 'Berhasil mengirim data ke PDDIKTI',
+    data: { error },
+  })
+})

+ 13 - 0
controller/v1/disk.controller.js

@@ -0,0 +1,13 @@
+const diskService = require('../../services/disk.service')
+const response = require('../../utils/responseHandler')
+
+exports.getData = async (req, res, next) => {
+  const { date } = req.query
+  try {
+    return response.success(res, {
+      message: 'berhasil mengambil data disk', data: await diskService.getData(date)
+    })
+  } catch (error) {
+    next(error)
+  }
+}

+ 435 - 0
controller/v1/graph.controller.js

@@ -0,0 +1,435 @@
+const handleError = require('../../utils/v1/handleError')
+const excel = require('../../utils/excel')
+const response = require('../../utils/responseHandler')
+const {
+  cekBanyakDataLaporan,
+  dataLaporanAggregate,
+  cekBanyakDataSanksi,
+} = require('../../utils/v1/cekData')
+const laporanModel = require('../../model/laporan.model')
+const moment = require('moment')
+const { TRUE, DIKTI, LLDIKTI, PTB_DIKTI, PTB_LLDIKTI, DITERIMA } = require('../../utils/constanta')
+const { capitalize } = require('../../utils/function')
+
+exports.laporan = handleError(async (req, res) => {
+  const user = req.user
+  const data = {}
+  const date = new Date()
+
+  const {
+    jumlahLaporan,
+    jadwal,
+    evaluasi,
+    sanksi,
+    newLaporan,
+    laporanBulan,
+    laporanTahun,
+    bulan,
+    tahun,
+    listJadwal,
+  } = req.query
+
+  berdasarkan_tahun = {
+    $and: [
+      {
+        createdAt: {
+          $gte: new Date(`${tahun || date.getFullYear()}`),
+        },
+      },
+      {
+        createdAt: {
+          $lt: new Date(`${parseInt(tahun) + 1 || date.getFullYear() + 1}`),
+        },
+      },
+    ],
+  }
+  // }
+
+  const laporan = await cekBanyakDataLaporan(user, { ...berdasarkan_tahun })
+
+  if (jumlahLaporan === TRUE) {
+    const delegasi = await cekBanyakDataLaporan(user, {
+      delegasi: true,
+      ...berdasarkan_tahun,
+    })
+    const ditutup = await cekBanyakDataLaporan(user, {
+      aktif: false,
+      ...berdasarkan_tahun,
+    })
+
+    data.jumlah_laporan = {
+      dikti: laporan.length,
+      lldikti: delegasi.length,
+      ditutup: ditutup.length,
+      tes: {
+        delegasi: true,
+        ...berdasarkan_tahun,
+      },
+    }
+  }
+
+  if (jadwal === TRUE) {
+    const hasJadwal = laporan.filter((e) => e.jadwal.judul).length
+    const notHasJadwal = laporan.filter((e) => !e.jadwal.judul).length
+
+    data.jadwal = {
+      hasJadwal,
+      notHasJadwal,
+    }
+  }
+
+  if (evaluasi === TRUE) {
+    const hasEvaluasi = laporan.filter(
+      (e) => e.evaluasi.length && e.jadwal.judul
+    ).length
+    const notHasEvaluasi = laporan.filter(
+      (e) => e.evaluasi.length === 0 && e.jadwal.judul
+    ).length
+
+    data.evaluasi = {
+      hasEvaluasi,
+      notHasEvaluasi,
+    }
+  }
+
+  if (sanksi === TRUE) {
+    const hasSanksi = laporan.filter(
+      (e) => e.sanksi && e.evaluasi.length
+    ).length
+    const notHasSanksi = laporan.filter(
+      (e) => !e.sanksi && e.evaluasi.length
+    ).length
+
+    data.sanksi = {
+      hasSanksi,
+      notHasSanksi,
+    }
+  }
+
+  if (newLaporan === TRUE) {
+    data.newLaporan = await cekBanyakDataLaporan(user, {
+      limit: 3,
+      select: 'no_laporan pt.nama -user createdAt',
+    })
+  }
+
+  if (laporanBulan === TRUE) {
+    let date = {}
+    if (bulan || (bulan && tahun)) {
+      const temp = new Date()
+      date = {
+        $expr: {
+          $and: [
+            { $eq: [{ $month: '$createdAt' }, parseInt(bulan)] },
+            {
+              $eq: [
+                { $year: '$createdAt' },
+                parseInt(tahun) || temp.getFullYear(),
+              ],
+            },
+          ],
+        },
+      }
+    }
+
+    data.laporan_perbulan = await dataLaporanAggregate(
+      user,
+      { ...date },
+      {
+        _id: {
+          bulan: {
+            $month: '$createdAt',
+          },
+          tahun: {
+            $year: '$createdAt',
+          },
+        },
+        jumlah_laporan: {
+          $sum: 1,
+        },
+      }
+    )
+  } else if (laporanTahun === TRUE) {
+    const temp = new Date()
+    let date = {
+      $expr: {
+        $eq: [{ $year: '$createdAt' }, parseInt(tahun) || temp.getFullYear()],
+      },
+    }
+
+    data.laporan_perTahun = await dataLaporanAggregate(
+      user,
+      { ...date },
+      {
+        _id: {
+          bulan: {
+            $month: '$createdAt',
+          },
+          tahun: {
+            $year: '$createdAt',
+          },
+        },
+        jumlah_laporan: {
+          $sum: 1,
+        },
+      }
+    )
+  }
+
+  if (listJadwal === TRUE) {
+    const temp = new Date()
+    let date = {
+      $expr: {
+        $and: [
+          {
+            $eq: [
+              { $month: '$jadwal.dari_tanggal' },
+              parseInt(bulan) || temp.getMonth() + 1,
+            ],
+          },
+          {
+            $eq: [
+              { $year: '$jadwal.dari_tanggal' },
+              parseInt(tahun) || temp.getFullYear(),
+            ],
+          },
+        ],
+      },
+    }
+
+    data.list_jadwal = await dataLaporanAggregate(
+      user,
+      {
+        aktif: true,
+        ...date,
+        jadwal: {
+          $ne: null,
+          $exists: true,
+        },
+      },
+      {
+        _id: {
+          bulan: {
+            $month: '$jadwal.dari_tanggal',
+          },
+          tahun: {
+            $year: '$jadwal.dari_tanggal',
+          },
+        },
+        jadwal: {
+          $push: '$jadwal',
+        },
+      }
+    )
+  }
+
+  return response.success(res, {
+    message: 'Berhasil menganalisis data',
+    data,
+  })
+})
+
+exports.excel = handleError(async (req, res) => {
+  const user = req.user
+  const w = {}
+  const date = new Date()
+  switch (user.role.id) {
+    case 2020:
+      w.$or = [
+        {
+          role_asal: DIKTI,
+        },
+        {
+          role_data: DIKTI,
+        },
+      ]
+      break
+    case 2021:
+      w.$or = [
+        {
+          role_asal: LLDIKTI,
+        },
+        {
+          role_data: LLDIKTI,
+        },
+      ]
+      w['pt.pembina.id'] = user.lembaga.id
+      break
+  }
+
+  const { tahun, penjadwalan, pelaporan, pemeriksaan, delegasi, sanksi } =
+    req.query
+
+  berdasarkan_tahun = {
+    $and: [
+      {
+        createdAt: {
+          $gte: new Date(`${tahun || date.getFullYear()}`),
+        },
+      },
+      {
+        createdAt: {
+          $lt: new Date(`${parseInt(tahun) + 1 || date.getFullYear() + 1}`),
+        },
+      },
+    ],
+  }
+
+  const laporan = await laporanModel
+    .find({ ...w, ...berdasarkan_tahun })
+    .populate('user')
+
+  const dataDelegasi = laporan.map((value) => ({
+    Tanggal: moment(value.createdAt).format('DD-MMMM-YYYY'),
+    'No. Laporan': value.no_laporan,
+    'Nama Perguruan Tinggi': value.pt.nama,
+    'Keterangan Laporan': value.keterangan,
+    'Dibuat Oleh': value.user.nama,
+    Status: !value.aktif
+      ? 'Ditutup'
+      : (value.role_asal === DIKTI && value.role_data === DIKTI) ||
+        (value.role_data == DIKTI && user.role.id === PTB_DIKTI) ||
+        (value.role_asal === LLDIKTI && value.role_data === LLDIKTI) ||
+        (value.role_data == LLDIKTI && user.role.id === PTB_LLDIKTI)
+      ? `Ditindaklanjuti ${value.role_data === DIKTI ? DIKTI.toUpperCase() : LLDIKTI.toUpperCase()}`
+      : `Delegasi Ke ${value.role_data === DIKTI ? DIKTI.toUpperCase() : LLDIKTI.toUpperCase()}`,
+  }))
+
+  const dataLaporan = laporan.map((value) => ({
+    Tanggal: moment(value.createdAt).format('DD-MMMM-YYYY'),
+    'No. Laporan': value.no_laporan,
+    'Nama Perguruan Tinggi': value.pt.nama,
+    'Keterangan Laporan': value.keterangan,
+    'Dibuat Oleh': value.user.nama,
+    Status:
+      value.sanksi || value.aktif === false
+        ? 'Pelaporan Selesai'
+        : 'Pelaporan Belum Selesai',
+  }))
+
+  const dataJadwal = laporan
+    .filter((e) => e.aktif === true)
+    .map((value) => ({
+      Tanggal: moment(value.createdAt).format('DD-MMMM-YYYY'),
+      'No. Laporan': value.no_laporan,
+      'Nama Perguruan Tinggi': value.pt.nama,
+      'Keterangan Laporan': value.keterangan,
+      'Dibuat Oleh': value.user.nama,
+      'Dari Tanggal':
+        value.jadwal.judul &&
+        moment(value.jadwal.dari_tanggal).format('DD-MMMM-YYYY'),
+      'Sampai Tanggal':
+        value.jadwal.judul &&
+        moment(value.jadwal.sampai_tanggal).format('DD-MMMM-YYYY'),
+      Status: value.jadwal.judul ? 'Sudah ada jadwal' : 'Belum ada jadwal',
+    }))
+
+  const dataPemeriksaan = laporan
+    .filter((e) => e.aktif === true && e.jadwal.judul)
+    .map((value) => ({
+      Tanggal: moment(value.createdAt).format('DD-MMMM-YYYY'),
+      'No. Laporan': value.no_laporan,
+      'Nama Perguruan Tinggi': value.pt.nama,
+      'Keterangan Laporan': value.keterangan,
+      'Dibuat Oleh': value.user.nama,
+      Status: value.evaluasi.length ? 'Sudah diperiksa' : 'Belum diperiksa',
+    }))
+
+  const dataSanksi = laporan
+    .filter((e) => e.aktif === true && e.evaluasi.length)
+    .map((value) => ({
+      Tanggal: moment(value.createdAt).format('DD-MMMM-YYYY'),
+      'No. Laporan': value.no_laporan,
+      'Nama Perguruan Tinggi': value.pt.nama,
+      'Keterangan Laporan': value.keterangan,
+      'Dibuat Oleh': value.user.nama,
+      Status: value.sanksi ? 'Sudah ditetapkan' : 'Belum ditetapkan',
+    }))
+
+  const data = []
+  if (delegasi === TRUE) {
+    data.push({ SheetNames: 'Delegasi', data: dataDelegasi })
+  }
+  if (pelaporan === TRUE) {
+    data.push({ SheetNames: 'Pelaporan', data: dataLaporan })
+  }
+  if (penjadwalan === TRUE) {
+    data.push({
+      SheetNames: 'Penjadwalan',
+      data: dataJadwal,
+    })
+  }
+  if (pemeriksaan === TRUE) {
+    data.push({ SheetNames: 'Pemeriksaan', data: dataPemeriksaan })
+  }
+  if (sanksi === TRUE) {
+    data.push({ SheetNames: 'Sanksi', data: dataSanksi })
+  }
+  const buffer = excel.to_excel(data)
+
+  res.header(
+    'Content-Type',
+    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+  )
+  return res.end(Buffer.from(buffer))
+})
+
+exports.laporanSelesai = handleError(async (req, res) => {
+  const user = req.user
+
+  laporan = await cekBanyakDataLaporan(user, { aktif: false })
+  sanksi = await cekBanyakDataSanksi(user, { aktif: false })
+  const data = {
+    laporan,
+    sanksi,
+    jumlah_selesai: sanksi.length,
+    jumlah_ditutup: laporan.length,
+  }
+
+  return response.success(res, {
+    message: 'Berhasil menganalisis data',
+    data,
+  })
+})
+
+exports.jumlahStatusLaporan = handleError(async (req, res) => {
+  let dataPembina = await laporanModel.find({
+    aktif: true,
+  })
+
+  dataPembina = [
+    ...new Set(
+      dataPembina.map((e) => `${e.pt.pembina.id};${e.pt.pembina.nama}`)
+    ),
+  ]
+
+  dataPembina = dataPembina.map((e) => ({
+    id: e.split(';')[0],
+    name: e.split(';')[1],
+  })).sort((a, b) => a.name < b.name ? -1 : 1)
+
+  let data = await Promise.all(
+    dataPembina.map(async (e) => {
+      const dataLaporan = await laporanModel.find({'pt.pembina.id': e.id}).populate('sanksi').lean()
+      return {
+        pembina: e,
+        jumlah_laporan: dataLaporan.length,
+        jumlah_jadwal_evaluasi: dataLaporan.filter(e => e.jadwal).length,
+        jumlah_delegasi: dataLaporan.filter(e => e.role_asal === DIKTI && e.role_data === LLDIKTI).length,
+        jumlah_pemeriksaan: dataLaporan.filter(e => e.evaluasi.length).length,
+        jumlah_sanksi: dataLaporan.filter(e => e.sanksi).length,
+        jumlah_keberatan: dataLaporan.filter(e => e.sanksi?.pengajuan?.keberatan).length,
+        jumlah_banding: dataLaporan.filter(e => e.sanksi?.pengajuan?.banding).length,
+        jumlah_pemantauan_perbaikan: dataLaporan.filter(e => e.sanksi?.perbaikan?.length).length,
+        jumlah_pencabutan_sanksi: dataLaporan.filter(e => e.sanksi?.pengajuan?.cabut_sanksi).length,
+        jumlah_diterima: dataLaporan.filter(e => e.sanksi?.jawaban?.cabut_sanksi?.status === capitalize(DITERIMA)).length,
+        jumlah_ditutup: dataLaporan.filter(e => !e.aktif).length,
+      }
+    })
+  )
+
+  return response.success(res, {
+    message: 'Berhasil menganalisis data',
+    data,
+  })
+})

+ 53 - 0
controller/v1/kontak.controller.js

@@ -0,0 +1,53 @@
+const auth = require('../../middleware/verifyToken')
+const kontakModel = require('../../model/kontak.model')
+const response = require('../../utils/responseHandler')
+const role = require('../../middleware/role')
+const checkData = require('../../middleware/checkData')
+const verifyOtp = require('../../middleware/verifyOTP')
+const { validation } = require('../../middleware/validation')
+const { PTB_PT } = require('../../utils/constanta')
+
+exports.getKontak = [
+  auth,
+  role(PTB_PT),
+  checkData((req) => req.user, (user) => kontakModel.find({ 'lembaga.id': user.lembaga.id, 'role.id': PTB_PT }), 'kontak'),
+  async (req, res) =>
+    response.success(res, {
+      status: 'success',
+      message: 'Berhasil mendapatkan data kontak',
+      data: req.data['kontak']
+    })
+]
+
+exports.addKontak = [
+  auth,
+  ...verifyOtp,
+  async (req, res) => {
+  const user = req.user
+  const no_hp = req.no_hp
+    try {
+      if (!await kontakModel.findOne({ 'lembaga.id': user.lembaga.id })) {
+        await kontakModel.create({
+          nama: user.nama,
+          no_hp,
+          role: user.role,
+          lembaga: user.lembaga,
+        })
+        return response.success(res, {
+          status: 'success',
+          message: 'Berhasil menambahkan kontak',
+        })
+      }
+      await kontakModel.updateOne({ 'lembaga.id': user.lembaga.id }, { no_hp, })
+      return response.success(res, {
+        status: 'success',
+        message: 'Berhasil mengubah kontak'
+      })
+    } catch (e) {
+      return response.error(res, {
+        message: e.message,
+        code: e.response?.status || 500
+      })
+    }
+  }
+]

+ 564 - 0
controller/v1/laporan.controller.js

@@ -0,0 +1,564 @@
+const handleError = require('../../utils/v1/handleError')
+const response = require('../../utils/responseHandler')
+const laporanModel = require('../../model/laporan.model')
+const pelanggaranModel = require('../../model/pelanggaran.model')
+const pemantauanModel = require('../../model/pemantauan.model')
+const { validate } = require('../../utils/v1/validation')
+const { notifWA } = require('../../utils/v1/notifFunction')
+const { addManyDokumen } = require('../../utils/dokumenFunction')
+const userModel = require('../../model/user.model')
+const {
+  cekSatuDataLaporan,
+  cekBanyakDataLaporan,
+} = require('../../utils/v1/cekData')
+const { TEMPLATE_LAPORAN, PELAPORAN, CREATE_LAPORAN, DIKTI, LLDIKTI, DITUTUP, DELEGASI, TRUE, FALSE, ADD_JADWAL,
+  PTB_DIKTI,
+  PTB_ADMIN,
+  PTB_READ,
+  SUCCESS, UPDATE_LAPORAN, DITERIMA
+} = require('../../utils/constanta')
+const logModel = require('../../model/log.model')
+const kontakModel = require('../../model/kontak.model')
+const { isValidObjectId } = require('mongoose')
+const roleId = require('../../middleware/role')
+const pddiktiService = require('../../services/v2/pddikti.service')
+
+exports.create = handleError(async (req, res) => {
+  const user = req.user
+  const files = req.files
+
+  const isValid = validate(res, req.body, {
+    no_laporan: 'string',
+    pt_id: 'string',
+    pelanggaran_id: 'string',
+    keterangan: 'string',
+  })
+  if (!isValid) return
+
+  const { no_laporan, pt_id, keterangan } = req.body
+  let { pelanggaran_id } = req.body
+  const pt = await pddiktiService.getPT(pt_id)
+  if (pt.length === 0)
+    return response.error(res, {
+      message: 'pt_id tidak ditemukan',
+    })
+
+  let dokumen_id = []
+  if (files.length) {
+    const dokumen = await addManyDokumen(files)
+    dokumen_id = dokumen.map((e) => e._id)
+  }
+
+  pelanggaran_id = pelanggaran_id.split(',')
+  const pelanggaran = await pelanggaranModel.find({
+    _id: {
+      $in: pelanggaran_id,
+    },
+  })
+  if (!pelanggaran.length)
+    return response.error(res, { message: 'pelanggaran_id tidak ada' })
+
+  let data = {
+    no_laporan,
+    user: user._id,
+    dokumen: dokumen_id,
+    pt: pt[0],
+    pelanggaran: pelanggaran_id,
+    keterangan,
+    role_data: user.role.id === 2021 ? LLDIKTI : DIKTI,
+    role_asal: user.role.id === 2021 ? LLDIKTI : DIKTI,
+    level: 2,
+    step: [PELAPORAN],
+    flag: PELAPORAN,
+  }
+
+  data = await laporanModel.create(data)
+
+  let contacts = await kontakModel.find()
+  contacts = contacts.map((e) => e.nama).join(', ')
+  try {
+    const notif = await notifWA(TEMPLATE_LAPORAN, [
+      {
+        key: '1',
+        value: 'nama',
+        value_text: user.nama,
+      },
+      { key: '2', value: 'pt', value_text: pt[0].nama },
+      { key: '3', value: 'keterangan', value_text: keterangan },
+      { key: '4', value: 'no_laporan', value_text: no_laporan },
+    ])
+
+    if (notif[0].status === SUCCESS) {
+      await logModel.create({
+        aktivitas: `Server berhasil mengirim notif wa kepada ${contacts} untuk Pembuatan Laporan`,
+      })
+    } else {
+      await logModel.create({
+        aktivitas: `Server gagal mengirim notif wa kepada ${contacts} untuk Pembuatan Laporan, Error: ${JSON.stringify(notif)}`,
+      })
+    }
+  } catch (error) {
+    await logModel.create({
+      aktivitas: `Server gagal mengirim notif wa kepada ${contacts} untuk Pembuatan Laporan, Error: ${error.message}`,
+    })
+  }
+
+  await pemantauanModel.create({
+    laporan: data._id,
+    action: CREATE_LAPORAN,
+    pt_id: pt[0].id,
+    user: user._id,
+    keterangan: 'Membuat Laporan',
+    dokumen: dokumen_id,
+    for_pt: false,
+  })
+
+  return response.success(res, {
+    message: 'Berhasil menambah laporan',
+    data,
+  })
+})
+
+exports.public = handleError(async (req, res) => {
+  const user = req.user
+  const no_laporan = req.no_laporan
+  let level = req.level
+  const files = req.files
+
+  const isValid = validate(res, req.body, {
+    pt_id: 'string',
+    pelanggaran_id: 'string',
+    keterangan: 'string',
+  })
+  if (!isValid) return
+
+  const { pt_id, keterangan, no_verifikasi } = req.body
+  let { pelanggaran_id } = req.body
+  if (no_verifikasi && user.no_verifikasi !== no_verifikasi) {
+    return response.error(res, {
+      message: 'Kode Verifikasi Salah',
+    })
+  } else if (no_verifikasi && user.no_verifikasi === no_verifikasi) {
+    level = 3
+  }
+
+  const pt = await pddiktiService.getPT(pt_id)
+  if (pt.length === 0)
+    return response.error(res, {
+      message: 'pt_id tidak ditemukan',
+    })
+
+  let dokumen_id = []
+  if (files.length) {
+    const dokumen = await addManyDokumen(files)
+    dokumen_id = dokumen.map((e) => e._id)
+  }
+
+  pelanggaran_id = pelanggaran_id.split(',')
+  const pelanggaran = await pelanggaranModel.find({
+    _id: {
+      $in: pelanggaran_id,
+    },
+  })
+  if (!pelanggaran.length)
+    return response.error(res, { message: 'pelanggaran_id tidak ada' })
+
+  let data = {
+    no_laporan,
+    user: user._id,
+    dokumen: dokumen_id,
+    pt: pt[0],
+    pelanggaran: pelanggaran_id,
+    keterangan,
+    role_data: DIKTI,
+    role_asal: DIKTI,
+    level,
+  }
+
+  data = await laporanModel.create(data)
+  await pemantauanModel.create({
+    laporan: data._id,
+    action: CREATE_LAPORAN,
+    pt_id: pt[0].id,
+    user: user._id,
+    keterangan: 'Membuat Laporan',
+    dokumen: dokumen_id,
+    for_pt: false,
+  })
+
+  if (no_verifikasi) await userModel.findByIdAndUpdate(user._id, { verified: true })
+
+  let contacts = await kontakModel.find()
+  contacts = contacts.map((e) => e.nama).join(', ')
+  try {
+    const notif = await notifWA(TEMPLATE_LAPORAN, [
+      {
+        key: '1',
+        value: 'nama',
+        value_text: user.isPrivate || !user.nama ? 'rahasia' : user.nama,
+      },
+      { key: '2', value: 'pt', value_text: pt[0].nama },
+      { key: '3', value: 'keterangan', value_text: keterangan },
+      { key: '4', value: 'no_laporan', value_text: no_laporan },
+    ])
+
+    if (notif[0].status === SUCCESS) {
+      await logModel.create({
+        aktivitas: `Server berhasil mengirim notif wa kepada ${contacts} untuk Pembuatan Laporan`,
+      })
+    } else {
+      await logModel.create({
+        aktivitas: `Server gagal mengirim notif wa kepada ${contacts} untuk Pembuatan Laporan, Error: ${JSON.stringify(notif)}`,
+      })
+    }
+  } catch (error) {
+    await logModel.create({
+      aktivitas: `Server gagal mengirim notif wa kepada ${contacts} untuk Pembuatan Laporan, Error: ${error.message}`,
+    })
+  }
+
+  return response.success(res, {
+    message: 'Berhasil menambah laporan',
+    data,
+  })
+})
+
+exports.getLaporanByNoLaporanAndId = handleError(async (req, res) => {
+  const { no_laporan } = req.params
+  let where = { evaluasi: { $exists: true, $ne: [] } }
+  if (isValidObjectId(no_laporan)) where._id = no_laporan
+  else where.no_laporan = no_laporan
+  const data = await laporanModel.findOne(where)
+    .populate({ path: 'user', populate: 'foto' })
+    .populate({ path: 'pelanggaran', select: 'pelanggaran' })
+    .populate({ path: 'sanksi', populate: ['pelanggaran'] })
+    .populate('dokumen')
+    .populate('peserta_penetapan_sanksi.ttd')
+    .populate({ path: 'evaluasi', populate: ['user', 'dokumen'] })
+  if (!data) {
+    return response.error(res, {
+      message: 'no_laporan atau id tidak ada',
+      code: 404,
+    })
+  }
+  return response.success(res, {
+    message: 'Berhasil ambil data laporan',
+    data,
+  })
+})
+
+exports.getAll = handleError(async (req, res) => {
+  const user = req.user
+  const where = {}
+  const { no_laporan, pt_id, jadwal, evaluasi, aktif, delegasi, all, sanksi, tuntas } =
+    req.query
+  if (no_laporan) where.no_laporan = no_laporan
+  if (pt_id) where['pt.id'] = pt_id
+  if (aktif) where.aktif = aktif === TRUE
+  if (all) where.all = true
+  else if (delegasi) where.delegasi = delegasi === TRUE
+
+  if (jadwal === TRUE) {
+    where.jadwal = {
+      $exists: true,
+      $ne: null,
+    }
+  } else if (evaluasi === TRUE) {
+    where.evaluasi = {
+      $exists: true,
+      $ne: null,
+      $not: {
+        $size: 0,
+      },
+    }
+  } else if (sanksi === TRUE) {
+    where.sanksi = {
+      $exists: true,
+      $ne: null,
+    }
+  } else if (tuntas === TRUE) {
+    let dataLaporan = (await cekBanyakDataLaporan(user, { aktif: 'empty', all: true, }, { lean: true }))
+      .filter(e => e.aktif === false || e.sanksi?.aktif === false)
+      .map(e => ({ ...e, status: e.aktif === false || e.tuntas?.keterangan ? 'Ditutup' : e.sanksi?.jawaban?.cabut_sanksi?.status === 'Diterima' ? 'Diterima' : !e.sanksi?.masa_berlaku ? 'Selesai' : 'Ditutup' }))
+    return response.success(res, {
+      message: 'Berhasil ambil data laporan dan sanksi tuntas dan ditutup',
+      data: dataLaporan
+    })
+  }
+
+  let data = (await cekBanyakDataLaporan(user, where))
+  if (!all) data = data.filter(e => !e.sanksi || e.sanksi?.aktif === true)
+  return response.success(res, {
+    message: 'Berhasil ambil data laporan',
+    data,
+  })
+})
+
+exports.getOne = handleError(async (req, res) => {
+  const { id } = req.params
+  const user = req.user
+  const { aktif, delegasi, all } = req.query
+  const where = {}
+  if (aktif) where.aktif = aktif === TRUE
+  if (all) where.all = true
+  else if (delegasi) where.delegasi = delegasi === TRUE
+  const data = await cekSatuDataLaporan(res, user, id, { normal: true })
+  if (!data) return
+  return response.success(res, {
+    message: 'Berhasil ambil data Laporan',
+    data,
+  })
+})
+
+exports.update = handleError(async (req, res) => {
+  const { id } = req.params
+  const user = req.user
+  const files = req.files
+  const laporan = await cekSatuDataLaporan(res, user, id, { normal: true })
+  if (!laporan) return
+
+  const isValid = validate(res, req.body, {
+    change_role: { type: 'string', optional: true, enum: [TRUE, FALSE] },
+    aktif: { type: 'string', optional: true, enum: [TRUE, FALSE] },
+    keterangan: 'string',
+  })
+  if (!isValid) return
+
+  const data = {}
+  let keterangan = ''
+  let alasan = ''
+  const { change_role, aktif } = req.body
+  const keterangan2 = req.body.keterangan
+  if (change_role === TRUE) {
+    data.flag = DELEGASI
+    data.role_data = user.role.id === 2020 ? 'lldikti' : 'dikti'
+    keterangan = `Laporan didelegasi ke ${user.role.id === 2020 ? 'LLDIKTI' : 'DIKTI'}`
+    alasan = keterangan2
+    data.alasan_delegasi = keterangan2
+  }
+  if (aktif) {
+    let dokumen_id = []
+    data.aktif = aktif === TRUE
+    if (files) {
+      const dokumen = await addManyDokumen(files)
+      dokumen_id = dokumen.map((e) => e._id)
+    }
+    if (aktif === 'true') {
+      keterangan = 'Laporan dibuka'
+    } else {
+      keterangan = `Laporan ditutup`
+      alasan = keterangan2
+      data.flag = DITUTUP
+      data.tuntas = {
+        keterangan: keterangan2,
+        dokumen: dokumen_id,
+      }
+    }
+  }
+
+  const update = await laporanModel.findByIdAndUpdate(laporan._id, data)
+  if (change_role || aktif) {
+    await pemantauanModel.create({
+      action: UPDATE_LAPORAN,
+      laporan: laporan._id,
+      pt_id: laporan.pt.id,
+      user: user._id,
+      keterangan,
+      alasan,
+      for_pt: false,
+    })
+  }
+
+  return response.success(res, {
+    message: 'Berhasil update laporan',
+    data: update,
+  })
+})
+
+exports.jumlahLaporan = handleError(async (req, res) => {
+  const laporan = await laporanModel.aggregate([
+    {
+      $match: {
+        aktif: true,
+      },
+    },
+    {
+      $group: {
+        _id: '$pt.pembina.nama',
+        jumlah_laporan: {
+          $sum: 1,
+        },
+        propinsi: {
+          $addToSet: '$pt.propinsi.nama',
+        },
+      },
+    },
+    {
+      $sort: {
+        _id: 1,
+      },
+    },
+  ])
+
+  return response.success(res, {
+    message: 'Jumlah Laporan',
+    data: laporan,
+  })
+})
+
+exports.laporanByPembina = [
+  roleId([PTB_DIKTI,PTB_ADMIN, PTB_READ]),
+  handleError(async (req, res) => {
+  const { idPembina } = req.params
+  const {
+    penjadwalan,
+    pemeriksaan,
+    sanksi,
+    keberatan,
+    banding,
+    perbaikan,
+    cabutSanksi,
+    delegasi,
+    ditutup,
+    diterima
+  } = req.query
+  let where = {}
+  let where2 = {}
+  let isLaporan = true
+  let isSanksi = false
+
+  if (penjadwalan === TRUE) {
+    where.jadwal = {
+      $exists: true,
+      $ne: null,
+    }
+    isLaporan = true
+    isSanksi = false
+  }
+  if (pemeriksaan === TRUE) {
+    where.evaluasi = {
+      $exists: true,
+      $ne: null,
+      $not: {
+        $size: 0,
+      },
+    }
+    isLaporan = true
+    isSanksi = false
+  }
+  if (sanksi === TRUE) {
+    where.sanksi = {
+      $exists: true,
+      $ne: null,
+    }
+    isLaporan = false
+    isSanksi = true
+  }
+  if (keberatan === TRUE) {
+    where2['pengajuan.keberatan'] = { $exists: true, $ne: null }
+    isLaporan = false
+    isSanksi = true
+  }
+  if (banding === TRUE) {
+    where2['pengajuan.banding'] = { $exists: true, $ne: null }
+    isLaporan = false
+    isSanksi = true
+  }
+  if (cabutSanksi === TRUE) {
+    where2['pengajuan.cabut_sanksi'] = {
+      $exists: true,
+      $ne: null,
+    }
+    isLaporan = false
+    isSanksi = true
+  }
+  if (perbaikan === TRUE) {
+    where2.perbaikan = {
+      $exists: true,
+      $ne: null,
+      $not: {
+        $size: 0,
+      },
+    }
+    isLaporan = false
+    isSanksi = true
+  }
+  if (delegasi === TRUE) {
+    where = {
+      role_asal: DIKTI,
+      role_data: LLDIKTI
+    }
+    isLaporan = true
+    isSanksi = false
+  }
+  if (ditutup === TRUE) {
+    where.aktif = false
+    isLaporan = true
+    isSanksi = false
+  }
+  if (diterima === TRUE) {
+    where2 = {
+      'jawaban.cabut_sanksi.status': DITERIMA,
+      aktif: false
+    }
+    isLaporan = false
+    isSanksi = true
+  }
+
+  let laporan = []
+  if (isSanksi) {
+    laporan = (await laporanModel.find({ ...where, 'pt.pembina.id': idPembina }).lean().populate({ path: 'sanksi', match: where2 }).lean())
+      .filter(e => e.sanksi)
+      .map(e => {
+        let step = 'Pelaporan'
+        if (e.jadwal && !e.evaluasi.length) step = 'Penjadwalan'
+        else if (e.evaluasi.length && !e.sanksi) step = 'Pemeriksaan'
+        else if (e.sanksi?.pengajuan?.cabut_sanksi) step = 'Cabut Sanksi'
+        else if (e.sanksi?.pengajuan?.keberatan && !e.sanksi?.pengajuan?.banding) step = 'Keberatan'
+        else if (e.sanksi?.aktif === false && !e.sanksi?.masa_berlaku?.from_date) step = 'Selesai'
+        else if (e.sanksi?.aktif === false && e.jawaban?.cabut_sanksi?.status === 'Diterima') step = 'Diterima'
+        else if (e.sanksi?.pengajuan?.banding) step = 'Banding'
+        else if (e.sanksi) step = 'Sanksi'
+        return { ...e, step }
+      })
+  } else if (isLaporan) {
+    laporan = (await laporanModel.find({ ...where, 'pt.pembina.id': idPembina }).lean().populate({ path: 'sanksi', match: where2 }).lean())
+      .map(e => {
+        let step = 'Pelaporan'
+        if (e.jadwal && !e.evaluasi.length) step = 'Penjadwalan'
+        else if (e.evaluasi.length && !e.sanksi) step = 'Pemeriksaan'
+        else if (e.sanksi?.pengajuan?.cabut_sanksi) step = 'Cabut Sanksi'
+        else if (e.sanksi?.pengajuan?.keberatan && !e.sanksi?.pengajuan?.banding) step = 'Keberatan'
+        else if (e.sanksi?.aktif === false && !e.sanksi?.masa_berlaku?.from_date) step = 'Selesai'
+        else if (e.sanksi?.aktif === false && e.jawaban?.cabut_sanksi?.status === 'Diterima') step = 'Diterima'
+        else if (e.sanksi?.pengajuan?.banding) step = 'Banding'
+        else if (e.sanksi) step = 'Sanksi'
+        return { ...e, step }
+      })
+  }
+  return response.success(res, {
+    message: 'berhasil get laporan by pembina',
+    data: laporan,
+  })
+})]
+
+exports.getOneLaporanPublic = handleError(async (req, res) => {
+  const { id } = req.params
+  const data = await laporanModel.findById(id)
+    .populate({ path: 'user', populate: 'foto' })
+    .populate({ path: 'pelanggaran', select: 'pelanggaran' })
+    .populate({ path: 'sanksi', populate: ['pelanggaran'] })
+    .populate('dokumen')
+    .populate('peserta_penetapan_sanksi.ttd')
+    .populate({ path: 'evaluasi', populate: ['user', 'dokumen'] })
+  console.log(data)
+  if (!data) return response.error(res, {
+    code: 404,
+    message: 'laporan_id tidak ada'
+  })
+
+  return response.success(res, {
+    message: 'Berhasil ambil satu data Laporan',
+    data,
+  })
+})

+ 12 - 8
controller/laporan/evaluasi.controller.js → controller/v1/laporan/evaluasi.controller.js

@@ -1,14 +1,16 @@
-const laporanModel = require('../../model/laporan.model')
-const handleError = require('../../utils/handleError')
-const response = require('../../utils/responseHandler')
-const { validate } = require('../../utils/validation')
-const { addManyDokumen } = require('../../utils/dokumenFunction')
-const { cekSatuDataLaporan } = require('../../utils/cekData')
-const pemantauanModel = require('../../model/pemantauan.model')
+const laporanModel = require('../../../model/laporan.model')
+const handleError = require('../../../utils/v1/handleError')
+const response = require('../../../utils/responseHandler')
+const { validate } = require('../../../utils/v1/validation')
+const { addManyDokumen } = require('../../../utils/dokumenFunction')
+const { cekSatuDataLaporan } = require('../../../utils/v1/cekData')
+const pemantauanModel = require('../../../model/pemantauan.model')
+const { PEMERIKSAAN, ADD_EVALUASI } = require('../../../utils/constanta')
 
 exports.add = handleError(async (req, res) => {
   const user = req.user
   const { id } = req.params
+
   const isValid = validate(res, req.body, {
     judul: 'string',
     tanggal: { type: 'date', convert: true },
@@ -37,6 +39,7 @@ exports.add = handleError(async (req, res) => {
       },
     },
     {
+      flag: PEMERIKSAAN,
       $push: {
         evaluasi: {
           dari: user._id,
@@ -44,6 +47,7 @@ exports.add = handleError(async (req, res) => {
           tanggal,
           dokumen: dokumen_id,
         },
+        step: PEMERIKSAAN
       },
     },
     {
@@ -59,7 +63,7 @@ exports.add = handleError(async (req, res) => {
   await pemantauanModel.create({
     laporan: laporan._id,
     user: user._id,
-    action: 'ADD EVALUASI',
+    action: ADD_EVALUASI,
     pt_id: laporan.pt.id,
     keterangan: 'Melakukan evaluasi',
     dokumen: dokumen_id,

+ 12 - 11
controller/laporan/jadwal.controller.js → controller/v1/laporan/jadwal.controller.js

@@ -1,12 +1,10 @@
-const laporanModel = require('../../model/laporan.model')
-const { cekSatuDataLaporan } = require('../../utils/cekData')
-const handleError = require('../../utils/handleError')
-const response = require('../../utils/responseHandler')
-const { validate } = require('../../utils/validation')
-const pemantauanModel = require('../../model/pemantauan.model')
-const logModel = require('../../model/log.model')
-const ip = require('ip')
-const osValue = require('../../utils/osValue')
+const laporanModel = require('../../../model/laporan.model')
+const { cekSatuDataLaporan } = require('../../../utils/v1/cekData')
+const handleError = require('../../../utils/v1/handleError')
+const response = require('../../../utils/responseHandler')
+const { validate } = require('../../../utils/v1/validation')
+const pemantauanModel = require('../../../model/pemantauan.model')
+const { PENJADWALAN, ADD_JADWAL } = require('../../../utils/constanta')
 
 exports.update = handleError(async (req, res) => {
   const user = req.user
@@ -26,19 +24,22 @@ exports.update = handleError(async (req, res) => {
 
   let for_public = true
   if (laporan.jadwal) {
-    // message = 'Mengubah Jadwal Pemeriksaan'
     for_public = false
   }
 
   const data = await laporanModel.findByIdAndUpdate(
     laporan._id,
     {
+      flag: PENJADWALAN,
       jadwal: {
         judul,
         dari_tanggal,
         sampai_tanggal,
         warna,
       },
+      $push: {
+        step: PENJADWALAN
+      }
     },
     {
       new: true,
@@ -50,7 +51,7 @@ exports.update = handleError(async (req, res) => {
   await pemantauanModel.create({
     laporan: laporan._id,
     user: user._id,
-    action: 'ADD JADWAL',
+    action: ADD_JADWAL,
     pt_id: laporan.pt.id,
     keterangan: message,
     jadwal: {

+ 12 - 0
controller/v1/lembaga.controller.js

@@ -0,0 +1,12 @@
+const handleError = require('../../utils/v1/handleError')
+const response = require('../../utils/responseHandler')
+const pddiktiService = require('../../services/v2/pddikti.service')
+
+exports.get = handleError(async (req, res) => {
+  const { search } = req.query
+  const data = (await pddiktiService.getPembina(null,{search})).map((e) => ({ id: e.id, nama: e.nama }))
+  return response.success(res, {
+    message: 'Berhasil mengambil data lembaga',
+    data,
+  })
+})

+ 44 - 0
controller/v1/log.controller.js

@@ -0,0 +1,44 @@
+const logModel = require('../../model/log.model')
+const handleError = require('../../utils/v1/handleError')
+const response = require('../../utils/responseHandler')
+const { validate } = require('../../utils/v1/validation')
+const moment = require('moment')
+
+exports.create = handleError(async (req, res) => {
+  const user = req.user
+  const { aktivitas, os, ipv4, menu } = req.body
+
+  const isValid = validate(res, req.body, {
+    aktivitas: 'string',
+    os: 'string',
+    ipv4: 'string'
+  })
+  if (!isValid) return
+
+  await logModel.create({
+    user: user._id,
+    aktivitas,
+    os,
+    ipv4,
+    menu
+  })
+
+  return response.success(res, {
+    message: 'log berhasil dibuat'
+  })
+})
+
+exports.all = handleError(async (req, res) => {
+  const { from_date, to_date } = req.query
+  let query = {
+    createdAt: {
+      $gte: from_date ? moment(from_date).startOf('day').toDate() : moment().startOf('month').toDate(),
+      $lte: to_date ? moment(to_date).endOf('day').toDate() : moment().endOf('month').toDate(),
+    }
+  }
+  const log = await logModel.find(query).populate('user').sort({ createdAt: -1 })
+
+  return response.success(res, {
+    data: log
+  })
+})

+ 105 - 0
controller/v1/migrasi.controller.js

@@ -0,0 +1,105 @@
+const sanksiModel = require('../../model/sanksi.model')
+const dokumenModel = require('../../model/dokumen.model')
+const laporanModel = require('../../model/laporan.model')
+const handleError = require('../../utils/v1/handleError')
+const response = require('../../utils/responseHandler')
+const coba = require('../../utils/coba')
+
+exports.pengajuan = handleError(async (req, res) => {
+  const [keberatan, banding] = await Promise.all([
+    (() =>
+      sanksiModel.find({
+        ['pengajuan.keberatan']: { $ne: null, $exists: true },
+        is_pengajuan_keberatan: { $eq: null, $exists: false }
+      }))(),
+    (() =>
+      sanksiModel.find({
+        ['jawaban.keberatan']: { $ne: null, $exists: true },
+        ['pengajuan.banding']: { $ne: null, $exists: true },
+        is_pengajuan_banding: { $eq: null, $exists: false }
+      }))()
+  ])
+  await Promise.all([
+    ...keberatan.map(async (e) => {
+      await sanksiModel.findOneAndUpdate({ _id: e._id }, { is_pengajuan_keberatan: true })
+    }),
+    ...banding.map(async (e) => {
+      await sanksiModel.findOneAndUpdate({ _id: e._id }, { is_pengajuan_banding: true })
+    })
+  ])
+
+  return response.success(res, {
+    message: 'Berhasil migrasi pengajuan'
+  })
+})
+
+exports.dokumen = handleError(async (req, res) => {
+  const dokumen = await dokumenModel.find({ path: /api.sidali.sixsenz.net/ })
+
+  if (dokumen?.length) await Promise.all(dokumen.map(async e => {
+    const path = e.path.split('/').slice(3).join('/')
+    await dokumenModel.findOneAndUpdate({
+      _id: e._id
+    }, {
+      path: `${coba.decrypt(process.env.W8A1C)}/${path}`
+    })
+  }))
+
+  return response.success(res, {
+    message: 'Berhasil migrasi dokumen'
+  })
+})
+
+exports.pelanggaranSanksi = handleError(async (req, res) => {
+  const sanksi = await sanksiModel.find({
+    sanksi: {
+      $eq: []
+    }
+  }).populate('pelanggaran')
+
+  if (sanksi?.length) await Promise.all(sanksi.map(async e => {
+    await sanksiModel.findOneAndUpdate({
+      _id: e._id
+    }, {
+      sanksi: e.pelanggaran.map(e2 => ({ label: e2.label_sanksi, description: e2.sanksi, level: e2.level_sanksi }))
+    })
+  }))
+
+  return response.success(res, {
+    message: 'Berhasil migrasi pelanggaran sanksi'
+  })
+})
+
+exports.tambahStep = handleError(async (req, res) => {
+  const laporan = await laporanModel.find()
+  await Promise.all(laporan.map(e => {
+    let step = ['pelaporan']
+    if (e.jadwal) step.push('penjadwalan')
+    if (e.evaluasi.length) step.push('pemeriksaan')
+    if (e.sanksi) step.push('sanksi')
+    return laporanModel.updateOne({ _id: e._id }, { step })
+  }))
+  const sanksi = await sanksiModel.find()
+  await Promise.all(sanksi.map(e => {
+    let step = []
+    if (e.pengajuan?.keberatan) step.push('keberatan')
+    if (e.pengajuan?.banding) step.push('banding')
+    if (e.perbaikan.length) step.push('dokumen_perbaikan')
+    if (e.pengajuan?.cabut_sanksi) step.push('cabut_sanksi')
+    return sanksiModel.updateOne({ _id: e._id }, { step })
+  }))
+  return laporan
+})
+
+exports.backToSanksi = handleError(async (req, res) => {
+  const sanksi = await sanksiModel.find({
+    'masa_berlaku.to_date': {
+      $lte: new Date().toISOString()
+    },
+    aktif: false
+  })
+  await Promise.all(sanksi.map(e => sanksiModel.findOneAndUpdate({ _id: e._id }, { aktif: true, 'masa_berlaku.berakhir': true })))
+  return sanksi
+})
+
+

+ 16 - 19
controller/pelanggaran.controller.js → controller/v1/pelanggaran.controller.js

@@ -1,6 +1,7 @@
-const handleError = require('../utils/handleError')
-const response = require('../utils/responseHandler')
-const pelanggaranModel = require('../model/pelanggaran.model')
+const handleError = require('../../utils/v1/handleError')
+const response = require('../../utils/responseHandler')
+const pelanggaranModel = require('../../model/pelanggaran.model')
+const { PTB_LLDIKTI } = require('../../utils/constanta')
 
 exports.getAll = handleError(async (req, res) => {
   const user = req.user
@@ -10,13 +11,11 @@ exports.getAll = handleError(async (req, res) => {
     id = id.split(',')
     w._id = { $in: id }
   }
-  if (user.role.id === 2021) {
-    w.level_sanksi = 1
-  }
+  if (user.role.id === PTB_LLDIKTI) w.level_sanksi = 1
   const data = await pelanggaranModel.find(w)
   return response.success(res, {
     message: 'Berhasil ambil data Pelanggaran',
-    data,
+    data
   })
 })
 
@@ -24,7 +23,7 @@ exports.public = handleError(async (req, res) => {
   const data = await pelanggaranModel.find().select('pelanggaran')
   return response.success(res, {
     message: 'Berhasil ambil data Pelanggaran',
-    data,
+    data
   })
 })
 
@@ -32,22 +31,20 @@ exports.sanksi = handleError(async (req, res) => {
   const { down } = req.query
   const user = req.user
   const w = {}
-  if (user.role.id === 2021) {
-    w.level_sanksi = 1
-  } else {
-    w.level_sanksi = 3
+  if (user.role.id === PTB_LLDIKTI) w.level_sanksi = 1
+  else {
+    w.level_sanksi = { $in: [3, 2] }
     if (down === 'true') {
       w.level_sanksi = { $in: [1, 2] }
     }
   }
-  let data = await pelanggaranModel.find(w)
-  data = [
-    ...new Set(
-      data.map((e) => `${e.sanksi} - Sanksi Administratif ${e.label_sanksi}`)
-    ),
-  ]
+  const data = await pelanggaranModel.find(w)
   return response.success(res, {
     message: 'Berhasil ambil data Pelanggaran',
-    data,
+    data: [
+      ...new Set(
+        data.map((e) => `${e.label_sanksi};${e.sanksi};${e.level_sanksi}`)
+      )
+    ]
   })
 })

+ 9 - 21
controller/pemantauan.controller.js → controller/v1/pemantauan.controller.js

@@ -1,28 +1,15 @@
-const axios = require('../utils/axios')
-const handleError = require('../utils/handleError')
-const response = require('../utils/responseHandler')
-const pemantauanModel = require('../model/pemantauan.model')
-const { cekSatuDataLaporan, cekSatuDataSanksi } = require('../utils/cekData')
-const laporanModel = require('../model/laporan.model')
-const userModel = require('../model/user.model')
+const handleError = require('../../utils/v1/handleError')
+const response = require('../../utils/responseHandler')
+const pemantauanModel = require('../../model/pemantauan.model')
+const { cekSatuDataLaporan, cekSatuDataSanksi } = require('../../utils/v1/cekData')
+const laporanModel = require('../../model/laporan.model')
+const userModel = require('../../model/user.model')
+const { TRUE } = require('../../utils/constanta')
 
 exports.get = handleError(async (req, res) => {
   const user = req.user
   const { laporan_id } = req.params
   const { delegasi, asc, all } = req.query
-  // const pt = await axios.get(
-  //   `https://api.kemdikbud.go.id:8243/pddikti/1.2/pt/${pt_id}`
-  // )
-  // if (!pt) {
-  //   return response.error(res, {
-  //     message: 'pt_id tidak ditemukan',
-  //   })
-  // }
-  // if (user.role.id === 2021 && user.lembaga.id !== pt[0].pembina.id) {
-  //   return response.error(res, {
-  //     message: 'pt_id tidak ditemukan',
-  //   })
-  // }
   const where = {}
   if (delegasi) where.delegasi = true
   if (all) where.all = true
@@ -36,7 +23,8 @@ exports.get = handleError(async (req, res) => {
     .populate({ path: 'laporan', select: 'no_laporan' })
     .populate({ path: 'sanksi', select: 'no_sanksi' })
     .populate('dokumen')
-    .sort({ createdAt: asc == 'true' ? 1 : -1 })
+    .populate('berita_acara')
+    .sort({ createdAt: asc === TRUE ? 1 : -1 })
 
   return response.success(res, {
     message: 'Berhasil ambil data Pemantauan',

+ 146 - 0
controller/v1/pengunjung.controller.js

@@ -0,0 +1,146 @@
+const pengunjungModel = require('../../model/pengunjung.model')
+const handleError = require('../../utils/v1/handleError')
+const response = require('../../utils/responseHandler')
+const { validate } = require('../../utils/v1/validation')
+
+exports.create = handleError(async (req, res) => {
+  const { os, ipv4, location } = req.body
+  const isValid = validate(res, req.body, {
+    os: 'string',
+    ipv4: 'string',
+    location: {
+      $$type: 'object',
+      country: 'string',
+      region: 'string',
+      city: 'string',
+      lat: 'number',
+      lon: 'number',
+      timezone: 'string',
+    },
+  })
+  if (!isValid) return
+
+  await pengunjungModel.create({ os, ipv4, location })
+
+  return response.success(res, {
+    message: 'data pengunjung berhasil dibuat',
+  })
+})
+
+exports.getPengunjung = handleError(async (req, res) => {
+  const { tahun } = req.query
+
+  let date = {}
+  if (tahun) {
+    date = {
+      $expr: {
+        $eq: [
+          { $year: '$createdAt' },
+          parseInt(tahun) || new Date().getFullYear(),
+        ],
+      },
+    }
+  }
+
+  const pengunjung = await pengunjungModel.aggregate([
+    { $match: date },
+    {
+      $group: {
+        _id: {
+          bulan: {
+            $month: '$createdAt',
+          },
+          tahun: {
+            $year: '$createdAt',
+          },
+        },
+        jumlah_pengunjung: {
+          $sum: 1,
+        },
+      },
+    },
+    {
+      $sort: {
+        '_id.bulan': 1,
+      },
+    },
+  ])
+
+  return response.success(res, {
+    message: 'data pengunjung',
+    data: pengunjung,
+  })
+})
+
+exports.getPengunjungPublic = handleError(async (req, res) => {
+  const { tahun, bulan } = req.query
+
+  let date = {}
+  if (tahun || bulan) {
+    date = {
+      $expr: {
+        $and: [
+          {
+            $eq: [
+              { $year: '$createdAt' },
+              parseInt(tahun) || new Date().getFullYear(),
+            ],
+          },
+          {
+            $eq: [
+              { $month: '$createdAt' },
+              parseInt(bulan) || new Date().getMonth() + 1,
+            ],
+          },
+        ],
+      },
+    }
+  }
+
+  const pengunjung = await pengunjungModel.aggregate([
+    { $match: date },
+    {
+      $group: {
+        _id: {
+          tanggal: {
+            $dayOfMonth: '$createdAt',
+          },
+          bulan: {
+            $month: '$createdAt',
+          },
+          tahun: {
+            $year: '$createdAt',
+          },
+        },
+        jumlah_pengunjung: {
+          $sum: 1,
+        },
+        data_ip: {
+          $addToSet: '$ipv4',
+        },
+      },
+    },
+    {
+      $addFields: {
+        jumlah_pengunjung: {
+          $size: '$data_ip',
+        },
+      },
+    },
+    {
+      $project: {
+        data_ip: 0,
+      },
+    },
+    {
+      $sort: {
+        '_id.tanggal': 1,
+      },
+    },
+  ])
+
+  return response.success(res, {
+    message: 'data pengunjung',
+    data: pengunjung,
+  })
+})

+ 49 - 0
controller/v1/pt.controller.js

@@ -0,0 +1,49 @@
+const handleError = require('../../utils/v1/handleError')
+const response = require('../../utils/responseHandler')
+const pddiktiService = require('../../services/v2/pddikti.service')
+const { PTB_PT, PTB_LLDIKTI, PTB_DIKTI, PTB_ADMIN, PTB_READ } = require('../../utils/constanta')
+const roleId = require('../../middleware/role')
+
+exports.getAll = handleError(async (req, res) => {
+  const user = req.user
+  const pembina = user.role.id === 2021 ? user.lembaga.id : req.query.pembina
+  const { search } = req.query
+  let data = await pddiktiService.getPT(user.role.id === PTB_PT ? user.lembaga.id : null, { search, pembina })
+  return response.success(res, {
+    message: 'Berhasil mengambil data Perguruan Tinggi',
+    data:
+      user.role.id === PTB_PT
+        ? data[0]
+        : data.filter((e) => e.id !== '4B4B23C1-8E0C-4825-89FA-765401C5E9C5')
+  })
+})
+
+exports.getOne = [
+  roleId([PTB_DIKTI, PTB_LLDIKTI, PTB_ADMIN, PTB_READ]),
+  handleError(async (req, res) => {
+    const user = req.user
+    const { id } = req.params
+    let data = (await pddiktiService.getPT(id))[0]
+    if (user.role.id === PTB_LLDIKTI && data.pembina.id !== user.lembaga.id) {
+      return response.error(res, {
+        message: 'pt_id tidak ada',
+        code: 404
+      })
+    }
+    return response.success(res, {
+      message: 'Berhasil mengambil satu data Perguruan Tinggi',
+      data
+    })
+  })
+]
+
+exports.public = handleError(async (req, res) => {
+  const { search } = req.query
+  let data = (await pddiktiService.getPT(null, { search }))
+    .map((e) => ({ id: e.id, nama: e.nama }))
+    .filter((e) => e.id !== '4B4B23C1-8E0C-4825-89FA-765401C5E9C5')
+  return response.success(res, {
+    message: 'Berhasil mengambil data Perguruan Tinggi',
+    data
+  })
+})

+ 60 - 0
controller/v1/rekomendasi.controller.js

@@ -0,0 +1,60 @@
+const handleError = require('../../utils/v1/handleError')
+const response = require('../../utils/responseHandler')
+const { addManyDokumen } = require('../../utils/dokumenFunction')
+const { cekSatuDataSanksi } = require('../../utils/v1/cekData')
+const pemantauanModel = require('../../model/pemantauan.model')
+const sanksiModel = require('../../model/sanksi.model')
+
+exports.createRekomendasi = handleError(async (req, res) => {
+  const user = req.user
+  const { id } = req.params
+
+  const sanksi = await cekSatuDataSanksi(res, user, id, { delegasi: true })
+  if (!sanksi) return
+
+  const files = req.files
+  if (!files.length) {
+    return response.error(res, {
+      message: 'dokumen harus ada',
+    })
+  }
+  const dokumen = await addManyDokumen(files)
+  const dokumen_id = dokumen.map((e) => e._id)
+
+  const data = await sanksiModel.findOneAndUpdate(
+    {
+      _id: sanksi._id,
+    },
+    {
+      $push: {
+        rekomendasi: {
+          dokumen: dokumen_id,
+        },
+      },
+    },
+    {
+      new: true,
+    }
+  )
+
+  let for_public = true
+  if (sanksi.rekomendasi.length > 0) {
+    for_public = false
+  }
+
+  await pemantauanModel.create({
+    laporan: sanksi.laporan._id,
+    user: user._id,
+    action: 'ADD REKOMENDASI',
+    pt_id: sanksi.laporan.pt.id,
+    keterangan: 'Melakukan rekomendasi delegasi',
+    dokumen: dokumen_id,
+    for_pt: false,
+    for_public,
+  })
+
+  return response.success(res, {
+    message: 'Berhasil tambah rekomendasi delegasi',
+    data,
+  })
+})

+ 563 - 0
controller/v1/sanksi.controller.js

@@ -0,0 +1,563 @@
+const sanksiModel = require('../../model/sanksi.model')
+const autoSaveModel = require('../../model/autoSave.model')
+const handleError = require('../../utils/v1/handleError')
+const response = require('../../utils/responseHandler')
+const { addManyDokumen, addDokumen } = require('../../utils/dokumenFunction')
+const { validate } = require('../../utils/v1/validation')
+const pemantauanModel = require('../../model/pemantauan.model')
+const pelanggaranModel = require('../../model/pelanggaran.model')
+const batchModel = require('../../model/batch.model')
+const logModel = require('../../model/log.model')
+const { hariKerja } = require('../../utils/hariKerja')
+const {
+  cekSatuDataSanksi,
+  cekSatuDataLaporan,
+  cekBanyakDataSanksi
+} = require('../../utils/v1/cekData')
+const laporanModel = require('../../model/laporan.model')
+const { SANKSI, SELESAI, TRUE, FALSE, KEBERATAN, PERBAIKAN, BANDING, CREATE_SANKSI, UPDATE_SANKSI } = require('../../utils/constanta')
+const pddiktiService = require('../../services/v2/pddikti.service')
+
+exports.create = handleError(async (req, res) => {
+  const { no_sanksi, keterangan, from_date, to_date, tanggal_terima_sanksi, tanggal_akhir_keberatan } = req.body
+  const sanksiBody = JSON.parse(req.body.sanksi)
+  let { pelanggaran_id } = req.body
+  const { laporan_id } = req.params
+  const { dokumen: files, berita_acara, dokumen_terima_sanksi } = req.files
+  const user = req.user
+
+  const isValid = validate(res, req.body, {
+    no_sanksi: 'string',
+    keterangan: 'string',
+    pelanggaran_id: 'string',
+    tanggal_terima_sanksi: 'string',
+    tanggal_akhir_keberatan: 'string',
+    sanksi: 'string',
+  })
+  if (!isValid) return
+
+  const laporan = await cekSatuDataLaporan(res, user, laporan_id, {
+    evaluasi: { $exists: true, $ne: [] }
+  })
+  if (!laporan) return
+
+  const id_pelanggaran = pelanggaran_id.split(',')
+  const pelanggaran = await pelanggaranModel.find({
+    _id: {
+      $in: id_pelanggaran
+    }
+  })
+  if (!pelanggaran.length) {
+    return response.error(res, { message: 'pelanggaran_id tidak ada' })
+  }
+  pelanggaran_id = pelanggaran.map((e) => e._id)
+
+  const sanksi = await sanksiModel.findOne({ laporan: laporan_id })
+  if (sanksi) {
+    return response.error(res, {
+      message: 'Sanksi sudah ada'
+    })
+  }
+
+  if (!files) {
+    return response.error(res, {
+      message: 'dokumen harus ada'
+    })
+  }
+  let dokumenBeritaAcara_id = null
+  if (berita_acara) {
+    const dokumenBeritaAcara = await addManyDokumen(berita_acara)
+    dokumenBeritaAcara_id = dokumenBeritaAcara[0]
+  }
+  let dokumenTerimaSanksi_id = []
+  if (dokumen_terima_sanksi) {
+    const dokumenTerimaSanksi = await addManyDokumen(dokumen_terima_sanksi)
+    dokumenTerimaSanksi_id = dokumenTerimaSanksi.map((e) => e._id)
+  }
+
+  const dokumen = await addManyDokumen(files)
+  const dokumen_id = dokumen.map((e) => e._id)
+  const autoSave = await autoSaveModel.findOne({ laporan_id: laporan._id })
+  if (autoSave?.laporan?.PenetapanSanksi)
+    await laporanModel.findByIdAndUpdate(laporan._id, {
+      berita_acara: autoSave.laporan.PenetapanSanksi
+    })
+  const data = await sanksiModel.create({
+    no_sanksi,
+    laporan: laporan._id,
+    user: user._id,
+    pelanggaran: pelanggaran_id,
+    keterangan,
+    dokumen: dokumen_id,
+    sanksi: sanksiBody,
+    berita_acara: dokumenBeritaAcara_id,
+    tanggal_terima_sanksi,
+    dokumen_terima_sanksi: dokumenTerimaSanksi_id,
+    tanggal_akhir_keberatan,
+    masa_berlaku: from_date && to_date ? { from_date, to_date } : null,
+    batas_waktu: from_date ? { keberatan: tanggal_akhir_keberatan } : null,
+    aktif: from_date ? true : false,
+    levelSanksi: Math.max(...sanksiBody.map(e => e.level))
+  })
+  await laporanModel.findByIdAndUpdate(laporan._id, {
+    sanksi: data._id,
+    $push: { step: from_date ? SANKSI : SELESAI },
+    flag: from_date ? SANKSI : SELESAI
+  })
+  await pemantauanModel.create({
+    laporan: laporan._id,
+    sanksi: data._id,
+    action: CREATE_SANKSI,
+    pt_id: laporan.pt.id,
+    user: user._id,
+    keterangan: 'Melakukan penetapan Sanksi',
+    dokumen: dokumen_id,
+    berita_acara: dokumenBeritaAcara_id
+  })
+
+  return response.success(res, {
+    message: 'Berhasil membuat Sanksi',
+    data
+  })
+})
+
+exports.updatePDDIKTI = handleError(async (req, res) => {
+  const { sanksi_id } = req.params
+  const sanksi = await sanksiModel.findOne({ _id: sanksi_id }).populate('pelanggaran').populate('laporan')
+  if (sanksi.masa_berlaku.from_date) {
+    await batchModel.create({
+      sanksi: sanksi._id,
+      type: UPDATE_SANKSI
+    })
+  }
+  return response.success(res, {
+    message: 'Berhasil memasukkan data ke dalam batch update status PDDIKTI'
+  })
+})
+
+exports.update = handleError(async (req, res) => {
+  const { no_sanksi, keterangan, from_date, to_date } = req.body
+  let sanksiBody = req.body.sanksi
+  const { sanksi_id } = req.params
+  const files = req.files
+  const user = req.user
+
+  const isValid = validate(res, req.body, {
+    no_sanksi: 'string',
+    keterangan: 'string',
+    sanksi: 'string',
+    from_date: {type: 'string', optional: true},
+    to_date: {type: 'string', optional: true}
+  })
+  if (!isValid) return
+
+  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id, { all: true })
+  if (!sanksi) return
+
+  sanksiBody = JSON.parse(sanksiBody)
+
+  if (!files.length) {
+    return response.error(res, {
+      message: 'Dokumen Harus Ada!'
+    })
+  }
+
+  const dokumen = await addManyDokumen(files)
+  const dokumen_id = dokumen.map((e) => e._id)
+  const masa_berlaku = from_date && to_date ? { from_date, to_date } : null
+  const data = await sanksiModel.updateOne(
+    { _id: sanksi._id },
+    {
+      no_sanksi,
+      sanksi: sanksiBody,
+      keterangan,
+      dokumen: dokumen_id,
+      aktif: from_date ? true : false,
+      levelSanksi: Math.max(...sanksiBody.map(e => e.level)),
+      masa_berlaku,
+      $push: {
+        riwayat_sanksi: sanksi
+      }
+    }
+  )
+  await pemantauanModel.create({
+    laporan: sanksi.laporan._id,
+    sanksi: sanksi._id,
+    action: UPDATE_SANKSI,
+    pt_id: sanksi.laporan.pt.id,
+    user: user._id,
+    keterangan: 'Melakukan Perubahan Sanksi',
+    dokumen: dokumen_id,
+    data: {
+      no_sanksi,
+      sanksi: sanksiBody,
+      keterangan,
+      masa_berlaku,
+    }
+  })
+
+  return response.success(res, {
+    message: 'Berhasil merubah Sanksi',
+    data
+  })
+})
+
+exports.updatePt = handleError(async (req, res) => {
+  const { is_pengajuan_keberatan, is_pengajuan_banding, is_dokumen_perbaikan } = req.body
+  const { sanksi_id } = req.params
+  const user = req.user
+
+  const isValid = validate(res, req.body, {
+    is_pengajuan_keberatan: { type: 'string', optional: true },
+    is_pengajuan_banding: { type: 'string', optional: true },
+    is_dokumen_perbaikan: { type: 'string', optional: true }
+  })
+  if (!isValid) return
+
+  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id)
+  if (!sanksi) return
+
+  let data = {}
+  let data2 = {}
+  let keterangan = ''
+  let last_step = ''
+  let flag
+  if (is_pengajuan_keberatan) {
+    if (is_pengajuan_keberatan === TRUE) {
+      last_step = 'Permohonan Keberatan'
+      keterangan = 'Menerima Pengajuan Keberatan'
+
+    } else if (is_pengajuan_keberatan === FALSE) {
+      keterangan = 'Membatalkan Pengajuan Keberatan'
+      last_step = 'Dokumen Perbaikan'
+    }
+    data = { is_pengajuan_keberatan: is_pengajuan_keberatan === TRUE }
+    flag = is_pengajuan_keberatan === TRUE ? KEBERATAN : PERBAIKAN
+  }
+
+  if (is_pengajuan_banding) {
+    if (is_pengajuan_banding === TRUE) {
+      last_step = 'Permohonan Banding'
+      keterangan = 'Menerima Pengajuan Banding'
+    } else if (is_pengajuan_banding === FALSE) {
+      keterangan = 'Membatalkan Pengajuan Banding'
+      last_step = 'Dokumen Perbaikan'
+    }
+    data = { is_pengajuan_banding: is_pengajuan_banding === TRUE }
+    flag = is_pengajuan_banding === TRUE ? BANDING : PERBAIKAN
+  }
+
+  if (is_pengajuan_keberatan || is_pengajuan_banding) {
+    await pemantauanModel.create({
+      laporan: sanksi.laporan._id,
+      sanksi: sanksi._id,
+      action: UPDATE_SANKSI,
+      pt_id: sanksi.laporan.pt.id,
+      user: user._id,
+      keterangan
+    })
+  }
+
+  if (is_dokumen_perbaikan === TRUE) {
+    last_step = 'Dokumen Perbaikan'
+    flag = PERBAIKAN
+  }
+
+  if (!last_step) {
+    return response.error(res, {
+      message: 'Gagal merubah Sanksi'
+    })
+  }
+
+  data.last_step = last_step
+  data2.flag = flag
+  const checkStep = await laporanModel.findOne({ sanksi: sanksi._id, step: flag })
+  if (!checkStep) data2.$push = { step: flag }
+  await laporanModel.findOneAndUpdate({ sanksi: sanksi._id }, data2)
+  await sanksiModel.updateOne(
+    { _id: sanksi._id },
+    data
+  )
+
+  return response.success(res, {
+    message: 'Berhasil merubah Sanksi'
+  })
+})
+
+exports.getAll = handleError(async (req, res) => {
+  const user = req.user
+  const {
+    keberatan,
+    jawaban,
+    banding,
+    cabutSanksi,
+    perbaikan,
+    aktif,
+    delegasi,
+    naikSanksi,
+    turunSanksi,
+    pengajuan_keberatan,
+    bypassCabutSanksi
+  } = req.query
+  const where = {}
+  const q = {}
+  const sort = { createdAt: -1 }
+  if (aktif && aktif === FALSE) {
+    where.aktif = false
+  } else if (aktif && aktif === TRUE) {
+    where.aktif = true
+  }
+  if (pengajuan_keberatan === TRUE) {
+    where.is_pengajuan_keberatan = true
+  }
+  if (keberatan === TRUE) {
+    where['pengajuan.keberatan'] = { $exists: true, $ne: null }
+    where.is_pengajuan_keberatan = true
+    if (jawaban === TRUE) {
+      where['jawaban.keberatan'] = { $exists: true, $ne: null }
+    }
+  }
+  if (banding === TRUE) {
+    where.banding = true
+    where.is_pengajuan_banding = true
+    where['pengajuan.keberatan'] = { $exists: true, $ne: null }
+    where['jawaban.keberatan'] = { $exists: true, $ne: null }
+    where['pengajuan.banding'] = { $exists: true, $ne: null }
+    if (jawaban === 'true') {
+      where['jawaban.banding'] = { $exists: true, $ne: null }
+    }
+  }
+  if (cabutSanksi === TRUE) {
+    where.$or = [
+      { perbaikan: { $exists: true, $ne: [] } },
+      { bypass_cabut_sanksi: { $eq: true } }
+    ]
+    if (jawaban === FALSE) {
+      where.$or = [
+        {
+          'pengajuan.cabut_sanksi': {
+            $exists: true, $ne: null
+          }
+        }, {
+          bypass_cabut_sanksi: true
+        }
+      ]
+      where.aktif = true
+    }
+  }
+  if (perbaikan === TRUE) {
+    where.perbaikan = { $exists: true, $ne: [] }
+    where.aktif = true
+    sort['perbaikan.updatedAt'] = -1
+  }
+  if (delegasi === TRUE) {
+    where.delegasi = true
+  }
+  if (naikSanksi === TRUE) {
+    where['sanksi.level'] = { $in: [1, 2] }
+  }
+  if (turunSanksi === TRUE) {
+    where['sanksi.level'] = { $in: [2, 3] }
+  }
+  if (bypassCabutSanksi === TRUE) {
+    where['masa_berlaku.from_date'] = {
+      $exists: true,
+      $ne: null
+    }
+    where.aktif = true
+    where.bypass_cabut_sanksi = {
+      $exists: false,
+      $eq: null,
+    }
+  }
+  const data = await cekBanyakDataSanksi(user, where, q, sort)
+  return response.success(res, {
+    message: 'Berhasil ambil data Sanksi',
+    data
+  })
+})
+
+exports.getOne = handleError(async (req, res) => {
+  const user = req.user
+  const { sanksi_id } = req.params
+
+  const w = {}
+  const { banding, aktif, delegasi, all } = req.query
+  if (banding === TRUE) {
+    w.banding = true
+    w['pengajuan.keberatan'] = { $exists: true, $ne: null }
+    w['jawaban.keberatan'] = { $exists: true, $ne: null }
+    w['pengajuan.banding'] = { $exists: true, $ne: null }
+  }
+  if (delegasi === TRUE) {
+    w.delegasi = true
+  }
+  if (all === TRUE) {
+    w.all = true
+  }
+  if (aktif === FALSE) {
+    w.aktif = false
+  }
+
+  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id, w)
+  if (!sanksi) return
+
+  return response.success(res, {
+    message: 'Berhasil ambil satu data Sanksi',
+    data: sanksi
+  })
+})
+
+exports.editTmt = handleError(async (req, res) => {
+  const user = req.user
+  const { id } = req.params
+  const { from_date, to_date, no_surat } = req.body
+  const files = req.files
+
+  const sanksi = await cekSatuDataSanksi(res, user, id)
+  if (!sanksi) return
+
+  const isValid = validate(res, req.body, {
+    from_date: { type: 'date', convert: true },
+    to_date: { type: 'date', convert: true },
+    // no_surat: 'string'
+  })
+  if (!isValid) return
+
+  if (!files.length) {
+    return response.error(res, {
+      message: 'dokumen harus ada'
+    })
+  }
+
+  const dokumen = await addManyDokumen(files)
+  const dokumen_id = dokumen.map((e) => e._id)
+
+  const data = await sanksiModel.findByIdAndUpdate(sanksi._id, {
+    masa_berlaku: {
+      from_date,
+      to_date
+    },
+    index_perpanjangan: sanksi.index_perpanjangan + 1,
+    $push: {
+      riwayat_perpanjangan_sanksi: {
+        masa_berlaku: {
+          from_date: sanksi.masa_berlaku.from_date,
+          to_date: sanksi.masa_berlaku.to_date,
+        },
+        index: sanksi.index_perpanjangan,
+        dokumen: dokumen_id
+      }
+    },
+    // 'pengajuan.update_tmt': { no_surat, dokumen: dokumen_id }
+  })
+
+  await pemantauanModel.create({
+    laporan: sanksi.laporan._id,
+    user: user._id,
+    action: 'EDIT TMT',
+    pt_id: sanksi.laporan.pt.id,
+    keterangan: 'Mengubah masa berlaku sanksi',
+    for_public: true
+  })
+
+  return response.success(res, {
+    message: 'Berhasil update tmt',
+    data
+  })
+})
+
+exports.addPesertaPleno = handleError(async (req, res) => {
+  const isValid = validate(res, req.body, {
+    laporan_id: 'string',
+    nama: 'string'
+  })
+  if (!isValid) return
+
+  const { laporan_id, nama } = req.body
+
+  const laporan = await laporanModel.findOne({
+    _id: laporan_id
+  })
+  if (!laporan) return response.error(res, {
+    code: 404,
+    message: 'laporan_id tidak ada'
+  })
+
+  const file = req.file
+  if (!file) {
+    return response.error(res, {
+      message: 'ttd harus ada'
+    })
+  }
+  const dokumen = await addDokumen(file)
+
+  await laporanModel.findOneAndUpdate(
+    { _id: laporan._id },
+    {
+      $push: {
+        peserta_penetapan_sanksi: {
+          nama,
+          ttd: dokumen.id
+        }
+      }
+    }
+  )
+
+  return response.success(res, {
+    message: 'Berhasil tambah peserta pleno'
+  })
+})
+
+exports.removePesertaPleno = handleError(async (req, res) => {
+  const isValid = validate(res, req.body, {
+    peserta_id: 'string',
+    laporan_id: 'string'
+  })
+  if (!isValid) return
+  const user = req.user
+
+  const { laporan_id, peserta_id } = req.body
+
+  const laporan = await cekSatuDataLaporan(res, user, laporan_id, {
+    evaluasi: { $exists: true, $ne: [] }
+  })
+  if (!laporan) return
+
+  await laporanModel.findOneAndUpdate(
+    { _id: laporan._id },
+    {
+      $pull: {
+        peserta_penetapan_sanksi: {
+          _id: peserta_id
+        }
+      }
+    }
+  )
+
+  return response.success(res, {
+    message: 'Berhasil menghapus peserta pleno'
+  })
+})
+
+exports.updateToDokumenPerbaikan = handleError(async (req, res) => {
+  const isValid = validate(res, req.body, {
+    sanksi_id: 'string'
+  })
+  if (!isValid) return
+
+  const { sanksi_id } = req.body
+  const user = req.user
+  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id)
+
+  await sanksiModel.updateOne({
+    _id: sanksi._id
+  }, {
+    is_pengajuan_keberatan: false,
+    'pengajuan.cabut_sanksi': null
+  })
+
+  return response.success(res, {
+    message: 'Berhasil update data Sanksi ke Dokumen Perbaikan'
+  })
+})

+ 180 - 0
controller/v1/sanksi/banding.controller.js

@@ -0,0 +1,180 @@
+const handleError = require('../../../utils/v1/handleError')
+const sanksiModel = require('../../../model/sanksi.model')
+const laporanModel = require('../../../model/laporan.model')
+const { addManyDokumen } = require('../../../utils/dokumenFunction')
+const { validate } = require('../../../utils/v1/validation')
+const { cekSatuDataSanksi } = require('../../../utils/v1/cekData')
+const response = require('../../../utils/responseHandler')
+const { hariKerja } = require('../../../utils/hariKerja')
+const pemantauanModel = require('../../../model/pemantauan.model')
+const { notifWA } = require('../../../utils/v1/notifFunction')
+const { TEMPLATE_BANDING, BANDING, SUCCESS } = require('../../../utils/constanta')
+const kontakModel = require('../../../model/kontak.model')
+const logModel = require('../../../model/log.model')
+
+exports.create = handleError(async (req, res) => {
+  const user = req.user
+  const { sanksi_id } = req.params
+
+  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id)
+  if (!sanksi) return
+
+  const files = req.files
+  if (!files?.length) {
+    return response.error(res, {
+      message: 'dokumen harus ada',
+    })
+  }
+  const dokumen = await addManyDokumen(files)
+  const dokumen_id = dokumen.map((e) => e._id)
+
+  const data = await sanksiModel.findOneAndUpdate(
+    {
+      laporan: sanksi.laporan._id,
+      _id: sanksi._id,
+      'pengajuan.banding': { $exists: false, $eq: null },
+      'jawaban.keberatan': { $exists: true, $ne: null },
+    },
+    {
+      'pengajuan.banding': {
+        dokumen: dokumen_id,
+      },
+      'batas_waktu.jawaban_banding': hariKerja(10),
+    }
+  )
+  await laporanModel.findOneAndUpdate({_id: sanksi.laporan._id},{
+    flag: BANDING,
+    $push: {
+      step: BANDING,
+    }
+  })
+  if (!data) {
+    return response.error(res, {
+      message: 'pengajuan banding sudah ada atau jawaban keberatan belum ada',
+    })
+  }
+  await pemantauanModel.create({
+    laporan: sanksi.laporan._id,
+    sanksi: sanksi._id,
+    action: 'ADD BANDING',
+    pt_id: sanksi.laporan.pt.id,
+    user: user._id,
+    keterangan: 'Mengajukan Banding',
+    dokumen: dokumen_id,
+  })
+
+  let contacts = await kontakModel.find()
+  contacts = contacts.map((e) => e.nama).join(', ')
+  try {
+    const notif = await notifWA(TEMPLATE_BANDING, [
+      { key: '1', value: 'pt', value_text: sanksi.laporan.pt.nama },
+      {
+        key: '2',
+        value: 'pemberi_sanksi',
+        value_text: `${sanksi.user.nama} - ${sanksi.user.role.nama}`,
+      },
+      { key: '3', value: 'no_laporan', value_text: sanksi.laporan.no_laporan },
+    ])
+
+    if (notif[0].status === SUCCESS) {
+      await logModel.create({
+        aktivitas: `Server berhasil mengirim notif wa kepada ${contacts} untuk Mengajukan Banding dari PT ${sanksi.laporan.pt.nama}`,
+      })
+    } else {
+      await logModel.create({
+        aktivitas: `Server gagal mengirim notif wa kepada ${contacts} untuk Mengajukan Banding dari PT ${sanksi.laporan.pt.nama}, Error: ${JSON.stringify(notif)}`,
+      })
+    }
+  } catch (error) {
+    await logModel.create({
+      aktivitas: `Server gagal mengirim notif wa kepada ${contacts} untuk Mengajukan Banding dari PT ${sanksi.laporan.pt.nama}, Error: ${error.message}`,
+    })
+  }
+
+  return response.success(res, {
+    data,
+    message: 'Berhasil menambah pengajuan banding',
+  })
+})
+
+exports.createJawaban = handleError(async (req, res) => {
+  const user = req.user
+  const { sanksi_id } = req.params
+
+  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id, {
+    banding: true,
+    is_pengajuan_banding: true,
+  })
+  if (!sanksi) return
+
+  const isValid = validate(res, req.body, {
+    status: 'string',
+    no_banding: { type: 'string', optional: true },
+    tanggal_terima_banding: { type: 'string', optional: true },
+    tanggal_surat_banding: { type: 'string', optional: true },
+  })
+  if (!isValid) return
+
+  const { dokumen: files, dokumen_terima_banding } = req.files
+
+  let dokumenTerimaBanding_id = []
+  if (dokumen_terima_banding?.length) {
+    const dokumenTerimaBanding = await addManyDokumen(dokumen_terima_banding)
+    dokumenTerimaBanding_id = dokumenTerimaBanding.map((e) => e._id)
+  }
+
+  let dokumen_id = []
+  if (files?.length) {
+    const dokumen = await addManyDokumen(files)
+    dokumen_id = dokumen.map((e) => e._id)
+  }
+
+  const { status, tanggal_terima_banding, tanggal_surat_banding, no_banding } = req.body
+
+  const data = await sanksiModel.findOneAndUpdate(
+    {
+      laporan: sanksi.laporan._id,
+      _id: sanksi._id,
+      'pengajuan.banding': { $exists: true, $ne: null },
+    },
+    {
+      last_step: 'Jawaban Atas Permohonan Banding',
+      'jawaban.banding': {
+        no_banding,
+        tanggal_terima_banding,
+        tanggal_surat_banding,
+        status,
+        dokumen_terima_banding: dokumenTerimaBanding_id,
+        dokumen: dokumen_id,
+      },
+    }
+  )
+  if (!data) {
+    return response.error(res, {
+      message: 'banding tidak ada',
+    })
+  }
+
+  let message = 'Menjawab Pengajuan Banding'
+  let for_public = true
+  if (sanksi.jawaban?.banding) {
+    message = 'Mengubah jawaban Pengajuan Banding'
+    for_public = false
+  }
+
+  await pemantauanModel.create({
+    laporan: sanksi.laporan._id,
+    sanksi: sanksi._id,
+    action: 'ADD BANDING JAWABAN',
+    pt_id: sanksi.laporan.pt.id,
+    jawaban: status,
+    user: user._id,
+    keterangan: message,
+    dokumen: dokumen_id,
+    for_public,
+  })
+
+  return response.success(res, {
+    data,
+  })
+})

+ 200 - 0
controller/v1/sanksi/cabutSanksi.controller.js

@@ -0,0 +1,200 @@
+const handleError = require('../../../utils/v1/handleError')
+const sanksiModel = require('../../../model/sanksi.model')
+const logModel = require('../../../model/log.model')
+const { addManyDokumen } = require('../../../utils/dokumenFunction')
+const { validate } = require('../../../utils/v1/validation')
+const { cekSatuDataSanksi, cekSatuDataLaporan } = require('../../../utils/v1/cekData')
+const response = require('../../../utils/responseHandler')
+const pemantauanModel = require('../../../model/pemantauan.model')
+const laporanModel = require('../../../model/laporan.model')
+const { CABUT_SANKSI } = require('../../../utils/constanta')
+
+exports.create = handleError(async (req, res) => {
+  const user = req.user
+  const { sanksi_id } = req.params
+
+  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id)
+  if (!sanksi) return
+
+  const files = req.files
+  if (!(files?.dokumen && files?.dokumen_rekomendasi)) {
+    return response.error(res, {
+      message: 'dokumen dan dokumen_rekomendasi harus ada'
+    })
+  }
+
+  const [dokumen_id, dokumenRekomendasi_id] = await Promise.all([
+    (async () => (await addManyDokumen(files.dokumen)).map((e) => e._id))(),
+    (async ()=> (await addManyDokumen(files.dokumen_rekomendasi)).map(e => e._id))()
+  ])
+
+  const data = await sanksiModel.findOneAndUpdate(
+    {
+      laporan: sanksi.laporan._id,
+      _id: sanksi._id,
+      $or: [{
+        perbaikan: { $exists: true, $ne: [] },
+        $or: [{
+          'pengajuan.cabut_sanksi': { $exists: false, $eq: null }
+        }, {
+          is_finalisasi: true
+        }],
+      }, {
+        bypass_cabut_sanksi: {
+          $eq: true
+        }
+      }]
+    },
+    {
+      last_step: 'Permohonan Pencabutan Sanksi',
+      'pengajuan.cabut_sanksi': {
+        dokumen: dokumen_id
+      },
+      $push: {
+        riwayat_pengajuan_cabut_sanksi: {
+          index: sanksi.index_perbaikan - 1,
+          dokumen: dokumen_id,
+          dokumen_rekomendasi: dokumenRekomendasi_id
+        }
+      }
+    }
+  )
+  if (!data) {
+    return response.error(res, {
+      message: 'cabut_sanksi sudah ada'
+    })
+  }
+  await laporanModel.findOneAndUpdate({ _id: sanksi.laporan._id }, {
+    flag: CABUT_SANKSI,
+    $push: {
+      step: CABUT_SANKSI
+    }
+  })
+  await pemantauanModel.create({
+    laporan: sanksi.laporan._id,
+    sanksi: sanksi._id,
+    action: 'ADD CABUTSANKSI',
+    pt_id: sanksi.laporan.pt.id,
+    user: user._id,
+    keterangan: 'Mengajukan Pencabutan Sanksi',
+    dokumen: dokumen_id
+  })
+  return response.success(res, {
+    data,
+    message: 'Berhasil menambah Cabut Sanksi'
+  })
+})
+
+exports.createJawaban = handleError(async (req, res) => {
+  const user = req.user
+  const { sanksi_id } = req.params
+
+  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id)
+  if (!sanksi) return
+
+  const isValid = validate(res, req.body, {
+    status: 'string',
+    keterangan: 'string'
+  })
+  if (!isValid) return
+  let dokumen_id = null
+  const files = req.files
+  if (files?.length) {
+    const dokumen = await addManyDokumen(files)
+    dokumen_id = dokumen.map((e) => e._id)
+  }
+
+  const { status, keterangan } = req.body
+  const data = await sanksiModel.findOneAndUpdate(
+    {
+      laporan: sanksi.laporan._id,
+      _id: sanksi._id,
+      $or: [
+        { 'pengajuan.cabut_sanksi': { $exists: true, $ne: null } },
+        { bypass_cabut_sanksi: true }
+      ]
+    },
+    {
+      is_finalisasi: false,
+      aktif: status !== 'Diterima',
+      last_step: 'Jawaban Atas Permohonan Pencabutan Sanksi',
+      'jawaban.cabut_sanksi': {
+        status,
+        keterangan,
+        dokumen: dokumen_id
+      },
+      $push: {
+        riwayat_jawaban_cabut_sanksi: {
+          index: sanksi.index_perbaikan,
+          status,
+          keterangan,
+          dokumen: dokumen_id
+        }
+      }
+    }
+  )
+  if (!data) {
+    return response.error(res, {
+      message: 'cabut_sanksi tidak ada'
+    })
+  }
+  await laporanModel.findOneAndUpdate({ _id: sanksi.laporan._id }, {
+    flag: status === 'Diterima' ? 'diterima' : 'cabut_sanksi',
+    $push: {
+      step: status === 'Diterima' ? 'diterima' : 'cabut_sanksi'
+    }
+  })
+
+  let message = 'Menjawab Pengajuan Pencabutan Sanksi'
+  let for_public = true
+  if (sanksi.jawaban?.cabut_sanksi) {
+    message = 'Mengubah jawaban Pengajuan Pencabutan Sanksi'
+    for_public = false
+  }
+
+  await pemantauanModel.create({
+    laporan: sanksi.laporan._id,
+    sanksi: sanksi._id,
+    action: 'ADD CABUTSANKSI JAWABAN',
+    jawaban: status,
+    pt_id: sanksi.laporan.pt.id,
+    user: user._id,
+    keterangan: message,
+    dokumen: dokumen_id,
+    for_public
+  })
+  return response.success(res, {
+    message: 'Berhasil menjawab Pengajuan Cabut Sanksi',
+    data
+  })
+})
+
+exports.byPass = handleError(async (req, res) => {
+  const user = req.user
+  const { sanksi_id } = req.params
+
+  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id)
+  if (!sanksi) return
+
+  const check = await sanksiModel.findOneAndUpdate({
+    _id: sanksi._id
+  }, {
+    bypass_cabut_sanksi: true,
+    last_step: 'Permohonan Pencabutan Sanksi'
+  })
+
+  if (!check) {
+    return response.error(res, {
+      message: 'gagal bypass ke pencabutan sanksi'
+    })
+  }
+
+  await logModel.create({
+    user: user._id,
+    aktivitas: `Berhasil bypass laporan ke pencabutan sanksi`
+  })
+
+  return response.success(res, {
+    message: 'berhasil bypass ke pencabutan sanksi'
+  })
+})

+ 198 - 0
controller/v1/sanksi/keberatan.controller.js

@@ -0,0 +1,198 @@
+const handleError = require('../../../utils/v1/handleError')
+const sanksiModel = require('../../../model/sanksi.model')
+const { addManyDokumen } = require('../../../utils/dokumenFunction')
+const { validate } = require('../../../utils/v1/validation')
+const { cekSatuDataSanksi } = require('../../../utils/v1/cekData')
+const response = require('../../../utils/responseHandler')
+const { hariKerja } = require('../../../utils/hariKerja')
+const pemantauanModel = require('../../../model/pemantauan.model')
+const { notifWA } = require('../../../utils/v1/notifFunction')
+const { TEMPLATE_KEBERATAN, KEBERATAN, SUCCESS, DITUTUP } = require('../../../utils/constanta')
+const kontakModel = require('../../../model/kontak.model')
+const logModel = require('../../../model/log.model')
+const laporanModel = require('../../../model/laporan.model')
+const { capitalize } = require('../../../utils/function')
+
+exports.create = handleError(async (req, res) => {
+  const user = req.user
+  const { sanksi_id } = req.params
+  if (!sanksi_id) {
+    return response.error(res, {
+      message: 'query sanksi_id harus ada',
+    })
+  }
+
+  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id, {
+    is_pengajuan_keberatan: true,
+  })
+  if (!sanksi) return
+
+  const files = req.files
+  if (!files?.length) {
+    return response.error(res, {
+      message: 'dokumen harus ada',
+    })
+  }
+  const dokumen = await addManyDokumen(files)
+  const dokumen_id = dokumen.map((e) => e._id)
+
+  const data = await sanksiModel.findOneAndUpdate(
+    {
+      laporan: sanksi.laporan._id,
+      _id: sanksi._id,
+      'pengajuan.keberatan': { $exists: false, $eq: null },
+    },
+    {
+      'pengajuan.keberatan': {
+        dokumen: dokumen_id,
+      },
+      'batas_waktu.jawaban_keberatan': hariKerja(10),
+    }
+  )
+  if (!data) {
+    return response.error(res, {
+      message: 'Pengajuan Keberatan sudah ada',
+    })
+  }
+  await laporanModel.findOneAndUpdate({_id: sanksi.laporan._id},{
+    flag: KEBERATAN,
+    $push: {
+      step: KEBERATAN
+    },
+  })
+  await pemantauanModel.create({
+    laporan: sanksi.laporan._id,
+    sanksi: sanksi._id,
+    action: 'ADD KEBERATAN',
+    user: user._id,
+    pt_id: sanksi.laporan.pt.id,
+    keterangan: 'Mengajukan Keberatan',
+    dokumen: dokumen_id,
+  })
+
+  let contacts = await kontakModel.find()
+  contacts = contacts.map((e) => e.nama).join(', ')
+  try {
+    const notif = await notifWA(TEMPLATE_KEBERATAN, [
+      { key: '1', value: 'pt', value_text: sanksi.laporan.pt.nama },
+      {
+        key: '2',
+        value: 'pemberi_sanksi',
+        value_text: `${sanksi.user.nama} - ${sanksi.user.role.nama}`,
+      },
+      { key: '3', value: 'no_laporan', value_text: sanksi.laporan.no_laporan },
+    ])
+
+    if (notif[0].status === SUCCESS) {
+      await logModel.create({
+        aktivitas: `Server berhasil mengirim notif wa kepada ${contacts} untuk Mengajukan Keberatan dari PT ${sanksi.laporan.pt.nama}`,
+      })
+    } else {
+      await logModel.create({
+        aktivitas: `Server gagal mengirim notif wa kepada ${contacts} untuk Mengajukan Keberatan dari PT ${sanksi.laporan.pt.nama}, Error: ${JSON.stringify(notif)}`,
+      })
+    }
+  } catch (error) {
+    await logModel.create({
+      aktivitas: `Server gagal mengirim notif wa kepada ${contacts} untuk Mengajukan Keberatan dari PT ${sanksi.laporan.pt.nama}, Error: ${error.message}`,
+    })
+  }
+
+  return response.success(res, {
+    data,
+    message: 'Berhasil menambah keberatan',
+  })
+})
+
+exports.createJawaban = handleError(async (req, res) => {
+  const user = req.user
+  const { sanksi_id } = req.params
+  if (!sanksi_id) {
+    return response.error(res, {
+      message: 'query sanksi_id harus ada',
+    })
+  }
+
+  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id)
+  if (!sanksi) return
+
+  const isValid = validate(res, req.body, {
+    status: 'string',
+    keterangan: 'string',
+    no_keberatan: { type: 'string', optional: true },
+    tanggal_terima_keberatan: { type: 'string', optional: true },
+    tanggal_surat_keberatan: { type: 'string', optional: true },
+    tanggal_akhir_banding: { type: 'string', optional: true },
+  })
+  if (!isValid) return
+
+  const { dokumen: files, dokumen_terima_keberatan } = req.files
+
+  let dokumenTerimaKeberatan_id = []
+  if (dokumen_terima_keberatan?.length) {
+    const dokumenTerimaKeberatan = await addManyDokumen(dokumen_terima_keberatan)
+    dokumenTerimaKeberatan_id = dokumenTerimaKeberatan.map((e) => e._id)
+  }
+  let dokumen_id = []
+  if (files?.length) {
+    const dokumen = await addManyDokumen(files)
+    dokumen_id = dokumen.map((e) => e._id)
+  }
+
+  const { status, keterangan, tanggal_terima_keberatan, tanggal_surat_keberatan, no_keberatan, tanggal_akhir_banding } = req.body
+  let value = {
+    'jawaban.keberatan': {
+      dokumen_terima_keberatan: dokumenTerimaKeberatan_id,
+      no_keberatan,
+      tanggal_terima_keberatan,
+      tanggal_surat_keberatan,
+      tanggal_akhir_banding,
+      status,
+      keterangan,
+      dokumen: dokumen_id,
+    },
+    'batas_waktu.banding': tanggal_akhir_banding,
+  }
+  if (status !== capitalize(DITUTUP)) {
+    value.last_step = 'Jawaban Atas Permohonan Keberatan'
+  } else {
+    value.last_step = 'Dokumen Perbaikan'
+    value.is_pengajuan_banding = false
+  }
+  const data = await sanksiModel.findOneAndUpdate(
+    {
+      laporan: sanksi.laporan._id,
+      _id: sanksi._id,
+      'pengajuan.keberatan': { $exists: true, $ne: null },
+    },
+    value
+  )
+  if (!data) {
+    return response.error(res, {
+      message: 'keberatan tidak ada',
+    })
+  }
+
+  let message = 'Menjawab Pengajuan Keberatan'
+  let for_public = true
+  if (sanksi.jawaban?.keberatan) {
+    message = 'Mengubah jawaban Pengajuan Keberatan'
+    for_public = false
+  }
+
+  await pemantauanModel.create({
+    laporan: sanksi.laporan._id,
+    sanksi: sanksi._id,
+    user: user._id,
+    action: 'ADD KEBERATAN JAWABAN',
+    jawaban: status,
+    pt_id: sanksi.laporan.pt.id,
+    keterangan: message,
+    dokumen: dokumen_id,
+    for_public,
+  })
+
+  return response.success(res, {
+    data,
+  })
+})

+ 160 - 0
controller/v1/sanksi/perbaikan.controller.js

@@ -0,0 +1,160 @@
+const handleError = require('../../../utils/v1/handleError')
+const sanksiModel = require('../../../model/sanksi.model')
+const { addManyDokumen } = require('../../../utils/dokumenFunction')
+const { validate } = require('../../../utils/v1/validation')
+const { cekSatuDataSanksi } = require('../../../utils/v1/cekData')
+const response = require('../../../utils/responseHandler')
+const pemantauanModel = require('../../../model/pemantauan.model')
+const { notifWA } = require('../../../utils/v1/notifFunction')
+const { TEMPLATE_PERBAIKAN_DOKUMEN, PERBAIKAN, SUCCESS } = require('../../../utils/constanta')
+const kontakModel = require('../../../model/kontak.model')
+const logModel = require('../../../model/log.model')
+const laporanModel = require('../../../model/laporan.model')
+
+exports.add = handleError(async (req, res) => {
+  const user = req.user
+  const { sanksi_id } = req.params
+
+  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id)
+  if (!sanksi) return
+
+  const isValid = validate(res, req.body, {
+    keterangan: 'string',
+  })
+  if (!isValid) return
+
+  const files = req.files
+  if (!files?.length) {
+    return response.error(res, {
+      message: 'dokumen harus ada',
+    })
+  }
+  const dokumen = await addManyDokumen(files)
+  const dokumen_id = dokumen.map((e) => e._id)
+
+  const { keterangan } = req.body
+  const data = await sanksiModel.findOneAndUpdate(
+    {
+      laporan: sanksi.laporan._id,
+      _id: sanksi._id,
+      aktif: true,
+      is_finalisasi: {$in: [false, null]},
+      $or: [
+        {
+          'pengajuan.banding': { $exists: true, $ne: null },
+          'pengajuan.cabut_sanksi': {
+            $exists: false,
+            $eq: null,
+          },
+        },
+        {
+          is_pengajuan_keberatan: false,
+          'pengajuan.cabut_sanksi': {
+            $exists: false,
+            $eq: null,
+          },
+        },
+        {
+          is_pengajuan_banding: false,
+          'pengajuan.cabut_sanksi': {
+            $exists: false,
+            $eq: null,
+          },
+        },
+        {
+          'jawaban.cabut_sanksi': { $exists: true, $ne: null },
+          'jawaban.cabut_sanksi.status': {
+            $exists: true,
+            $eq: 'Rekomendasi Perbaikan'
+          }
+        }
+      ],
+    },
+    {
+      flag: 'dokumen_perbaikan',
+      last_step: 'Dokumen Perbaikan',
+      is_finalisasi: false,
+      index_perbaikan: sanksi.index_perbaikan || 0,
+      $push: {
+        perbaikan: {
+          keterangan,
+          index: sanksi.index_perbaikan || 0,
+          dokumen: dokumen_id,
+        },
+      },
+    }
+  )
+  if (!data) {
+    return response.error(res, {
+      message: 'Pengajuan banding atau cabut sanksi tidak ada',
+    })
+  }
+  await laporanModel.findOneAndUpdate({_id: sanksi.laporan._id},{
+    flag: PERBAIKAN,
+    $push: {
+      step: PERBAIKAN,
+    }
+  })
+  await pemantauanModel.create({
+    laporan: sanksi.laporan._id,
+    sanksi: sanksi._id,
+    action: 'ADD PERBAIKAN DOKUMEN',
+    user: user._id,
+    pt_id: sanksi.laporan.pt.id,
+    keterangan: 'Melakukan Perbaikan Dokumen',
+    dokumen: dokumen_id,
+  })
+
+  let contacts = await kontakModel.find()
+  contacts = contacts.map((e) => e.nama).join(', ')
+  try {
+    const notif = await notifWA(TEMPLATE_PERBAIKAN_DOKUMEN, [
+      { key: '1', value: 'pt', value_text: sanksi.laporan.pt.nama },
+      {
+        key: '2',
+        value: 'pemberi_sanksi',
+        value_text: `${sanksi.user.nama} - ${sanksi.user.role.nama}`,
+      },
+      { key: '3', value: 'no_laporan', value_text: sanksi.laporan.no_laporan },
+    ])
+
+    if (notif[0].status === SUCCESS) {
+      await logModel.create({
+        aktivitas: `Server berhasil mengirim notif wa kepada ${contacts} perihal Dokumen Perbaikan dari PT ${sanksi.laporan.pt.nama}`,
+      })
+    } else {
+      await logModel.create({
+        aktivitas: `Server gagal mengirim notif wa kepada ${contacts} perihal Dokumen Perbaikan dari PT ${sanksi.laporan.pt.nama}, Error: ${JSON.stringify(notif)}`,
+      })
+    }
+  } catch (error) {
+    await logModel.create({
+      aktivitas: `Server gagal mengirim notif wa kepada ${contacts} perihal Dokumen Perbaikan dari PT ${sanksi.laporan.pt.nama}, Error: ${error.message}`,
+    })
+  }
+
+  return response.success(res, {
+    data,
+    message: 'Berhasil menambah Perbaikan',
+  })
+})
+
+exports.finalisasi = handleError(async (req, res) => {
+  const user = req.user
+  const { sanksi_id } = req.params
+
+  const sanksi = await cekSatuDataSanksi(res, user, sanksi_id)
+  if (!sanksi) return
+
+  await sanksiModel.findOneAndUpdate({
+    _id: sanksi._id,
+    is_finalisasi: false,
+  },{
+    index_perbaikan: sanksi.index_perbaikan + 1,
+    is_finalisasi: true,
+  })
+
+  return response.success(res, {
+    message: 'Berhasil memfinalisasi dokumen perbaikan',
+  })
+})

+ 102 - 0
controller/v1/signature.controller.js

@@ -0,0 +1,102 @@
+const handleError = require('../../utils/v1/handleError')
+const response = require('../../utils/responseHandler')
+const laporanModel = require('../../model/laporan.model')
+const { validate } = require('../../utils/v1/validation')
+const signatureModel = require('../../model/signature.model')
+const { addDokumen } = require('../../utils/dokumenFunction')
+const { cekSatuDataLaporan } = require('../../utils/v1/cekData')
+
+exports.getPeserta = handleError(async (req, res) => {
+  const { laporan_id } = req.params
+  const data = await signatureModel.findOne({ laporan_id }).populate('daftar_kehadiran_peserta.ttd')
+  return response.success(res, {
+    message: 'Berhasil mendapatkan daftar kehadiran peserta',
+    data
+  })
+})
+
+exports.addPeserta = handleError(async (req, res) => {
+  const isValid = validate(res, req.body, {
+    laporan_id: 'string',
+    nama: 'string'
+  })
+  if (!isValid) return
+
+  const { laporan_id, nama } = req.body
+
+  const laporan = await laporanModel.findOne({
+    _id: laporan_id
+  })
+  if (!laporan) return response.error(res, {
+    code: 404,
+    message: 'laporan_id tidak ada'
+  })
+
+  const file = req.file
+  if (!file) {
+    return response.error(res, {
+      message: 'ttd harus ada'
+    })
+  }
+  const dokumen = await addDokumen(file)
+
+  const signature = await signatureModel.findOne({ laporan_id: laporan._id })
+  if (!signature) {
+    await signatureModel.create(
+      {
+        laporan_id: laporan._id,
+        daftar_kehadiran_peserta: [{
+          nama,
+          ttd: dokumen.id
+        }]
+      }
+    )
+  } else {
+    await signatureModel.findOneAndUpdate(
+      { laporan_id: laporan._id },
+      {
+        $push: {
+          daftar_kehadiran_peserta: {
+            nama,
+            ttd: dokumen.id
+          }
+        }
+      }
+    )
+  }
+
+  return response.success(res, {
+    message: 'Berhasil tambah peserta pleno'
+  })
+})
+
+exports.removePeserta = handleError(async (req, res) => {
+  const isValid = validate(res, req.body, {
+    peserta_id: 'string',
+    laporan_id: 'string'
+  })
+  if (!isValid) return
+  const user = req.user
+
+  const { laporan_id, peserta_id } = req.body
+
+  const laporan = await cekSatuDataLaporan(res, user, laporan_id, {
+    evaluasi: { $exists: true, $ne: [] }
+  })
+  if (!laporan) return
+
+  await signatureModel.findOneAndUpdate(
+    { laporan_id: laporan._id },
+    {
+      $pull: {
+        daftar_kehadiran_peserta: {
+          _id: peserta_id
+        }
+      }
+    }
+  )
+
+  return response.success(res, {
+    message: 'Berhasil menghapus peserta pleno'
+  })
+})

+ 19 - 30
controller/user.controller.js → controller/v1/user.controller.js

@@ -1,29 +1,22 @@
-const handleError = require('../utils/handleError')
-const response = require('../utils/responseHandler')
-const userModel = require('../model/user.model')
-const { validate } = require('../utils/validation')
-const { notifWA2 } = require('../utils/notifFunction')
-const axios = require('../utils/axios')
-const { addDokumen } = require('../utils/dokumenFunction')
+const handleError = require('../../utils/v1/handleError')
+const response = require('../../utils/responseHandler')
+const userModel = require('../../model/user.model')
+const { validate } = require('../../utils/v1/validation')
+const { addDokumen } = require('../../utils/dokumenFunction')
 const jwt = require('jsonwebtoken')
-const { TEMPLATE_VERIFIKASI } = require('../utils/constanta')
+const { TEMPLATE_VERIFIKASI, TRUE, FALSE,TEMPLATE_OTP } = require('../../utils/constanta')
+const pddiktiService = require('../../services/v2/pddikti.service')
 
 exports.addUserPublic = handleError(async (req, res) => {
-  const { no_laporan, pt_id, nama, email, no_hp, alamat, is_private } = req.body
+  const { no_laporan, pt_id, nama, email, no_hp, alamat, is_private, isVerify } = req.body
   const isValid = validate(res, req.body, {
     no_laporan: 'string',
     pt_id: 'string',
-    // nama: 'string',
-    // email: 'email',
-    // no_hp: 'string',
-    // alamat: 'string',
-    is_private: { type: 'string', enum: ['true', 'false'] },
+    is_private: { type: 'string', enum: [TRUE, FALSE] },
   })
   if (!isValid) return
 
-  const pt = await axios.get(
-    `https://api.kemdikbud.go.id:8243/pddikti/1.2/pt/${pt_id}`
-  )
+  const pt = await pddiktiService.getPT(pt_id)
   if (pt.length === 0)
     return response.error(res, {
       message: 'pt_id tidak ditemukan',
@@ -62,23 +55,20 @@ exports.addUserPublic = handleError(async (req, res) => {
     no_hp,
     alamat,
     isPublic: true,
-    isPrivate: is_private === 'true',
+    isPrivate: is_private === TRUE,
     foto: foto_id,
     no_verifikasi,
     verified: false,
   })
 
   let notif = null
-  if (no_hp) {
-    notif = await notifWA2(
-      TEMPLATE_VERIFIKASI,
-      { nama: nama || 'rahasia', no_hp: no_hp2 },
-      [
-        { key: '1', value: 'pt', value_text: pt[0].nama },
-        { key: '3', value: 'no_verifikasi', value_text: no_verifikasi },
-        { key: '2', value: 'no_laporan', value_text: no_laporan },
-      ]
-    )
+  if (isVerify !== FALSE && no_hp) {
+    pddiktiService.whatsapp(
+      TEMPLATE_OTP,
+      [{ name: nama || 'rahasia', number: no_hp2 }],
+      [{ key: 1, value: 'no_verifikasi', value_text: no_verifikasi }],
+      [{ index: 0, type: 'url', value: no_verifikasi }]
+    ).catch()
   }
 
   const accessToken = jwt.sign(
@@ -87,7 +77,7 @@ exports.addUserPublic = handleError(async (req, res) => {
       no_laporan,
       level,
     },
-    process.env.SECRET,
+    process.env.SRU51,
     {
       expiresIn: '30m',
     }
@@ -95,7 +85,6 @@ exports.addUserPublic = handleError(async (req, res) => {
 
   data = {
     token: `Bearer ${accessToken}`,
-    no_hp_aktif: no_hp && notif[0].status == 'success' ? true : false,
   }
 
   return response.success(res, {

+ 211 - 0
controller/v2/auth.controller.js

@@ -0,0 +1,211 @@
+const { validation } = require('../../middleware/validation')
+const response = require('../../utils/responseHandler')
+const userModel = require('../../model/user.model')
+const { roleDataProduction, PTB_DIKTI, PTB_ADMIN, TEMPLATE_OTP, PTB_READ } = require('../../utils/constanta')
+const convertRole = require('../../utils/convertRole')
+const jwt = require('jsonwebtoken')
+const moment = require('moment')
+const logModel = require('../../model/log.model')
+const auth = require('../../middleware/verifyToken')
+const generateOTP = require('../../utils/otp')
+const role = require('../../middleware/role')
+const pddiktiService = require('../../services/v2/pddikti.service')
+
+exports.login = [
+  validation((req) => req.body, {
+    username: { type: 'string', empty: false },
+    password: { type: 'string', empty: false }
+  }),
+  async (req, res) => {
+    const { username, password } = req.body
+    let userResponse
+
+    try {
+      userResponse = await pddiktiService.login(req.body)
+      if (userResponse.code === 400) {
+        return response.error(res, {
+          code: 401,
+          message: userResponse.message
+        })
+      }
+    } catch (e) {
+      return response.error(res, {
+        code: 500,
+        message: e.message
+      })
+    }
+
+    let role = userResponse.peran.find((e) => roleDataProduction.includes(e.peran.id))
+    if (!role) {
+      return response.error(res, {
+        code: 401,
+        message: 'Anda tidak memiliki akses ke aplikasi ini'
+      })
+    }
+    role.peran.id = convertRole(role?.peran?.id)
+
+    let user = await userModel.findOne({ user_id: userResponse.id })
+    if (!user) {
+      await userModel.create({
+        user_id: userResponse.id,
+        nama: userResponse.nama,
+        lembaga: role.organisasi,
+        email: userResponse.username,
+        no_hp: userResponse.no_hp,
+        alamat: userResponse.alamat,
+        role: role.peran,
+        role_asal: role.peran,
+        isPublic: false,
+        isPrivate: false
+      })
+    } else {
+      await userModel.findOneAndUpdate({ user_id: userResponse.id }, {
+        lembaga: role.organisasi,
+        role: {
+          id: username.toLowerCase() === 'rizqevo@outlook.com' ? PTB_READ : role.peran.id,
+          nama: username.toLowerCase() === 'rizqevo@outlook.com' ? 'Auditor PTB' : role.peran.nama,
+          menu: role.peran.menu
+        },
+        role_asal: {
+          id: role.peran.id,
+          nama: role.peran.nama,
+          menu: role.peran.menu
+        }
+      })
+    }
+    user = await userModel.findOne({ user_id: userResponse.id })
+
+    const accessToken = jwt.sign({ _id: user._id }, process.env.SRU51, {
+      expiresIn: '1d'
+    })
+    res.cookie('sidali-cookie', accessToken, {
+      httpOnly: true,
+      expires: moment().add(1, 'day').toDate()
+    })
+
+    return response.success(res, {
+      message: 'Berhasil Login',
+      data: {
+        token: `Bearer ${accessToken}`,
+        user
+      }
+    })
+  }
+]
+
+exports.loginToPT = [
+  auth,
+  role([PTB_DIKTI, PTB_ADMIN]),
+  validation((req) => req.body, {
+    lembaga_id: 'string',
+    password: 'string'
+  }),
+  async (req, res) => {
+    let user = req.user
+    const { lembaga_id, password } = req.body
+    let dataLembaga
+
+    try {
+      const userResponse = await pddiktiService.login({ username: user.email, password })
+      if (userResponse.code && userResponse.code !== 200) {
+        return response.error(res, {
+          code: 401,
+          message: userResponse.message
+        })
+      }
+      dataLembaga = await pddiktiService.getPT(lembaga_id)
+    } catch (e) {
+      return response.error(res, {
+        code: e.response.status,
+        message: e.message
+      })
+    }
+
+    await userModel.updateOne({
+      _id: user._id
+    }, {
+      lembaga: {
+        id: dataLembaga[0].id,
+        nama: dataLembaga[0].nama
+      },
+      role: {
+        id: 2022,
+        nama: 'PTB PT'
+      }
+    })
+    user = await userModel.findOne({ _id: user._id })
+    await logModel.create({
+      user: user._id,
+      aktivitas: `${user.nama} berhasil masuk ke PT ${dataLembaga[0].nama}`
+    })
+
+    const accessToken = jwt.sign({ _id: user._id }, process.env.SRU51, {
+      expiresIn: '1d'
+    })
+    const data = {
+      token: `Bearer ${accessToken}`,
+      user
+    }
+
+    res.cookie('sidali-cookie', accessToken, {
+      httpOnly: true,
+      expires: moment().add(1, 'day').toDate()
+    })
+
+    response.success(res, {
+      message: 'Berhasil Login',
+      data
+    })
+  }
+]
+
+exports.logout = [
+  auth,
+  (req, res) => {
+    res.clearCookie('sidali-cookie')
+
+    response.success(res, {
+      message: 'Berhasil Logout'
+    })
+  }
+]
+
+exports.sendOTP = [
+  auth,
+  validation((req) => req.body, { no_hp: 'string' }),
+  async (req, res) => {
+    const user = req.user
+    let no_hp = req.body.no_hp
+    no_hp = req.body.no_hp.substring(0, 1) === '0' ? '62' + no_hp.substring(1) : no_hp
+    const generatedOtp = generateOTP(4)
+    res.cookie('sidali-otp', jwt.sign({ no_hp, otp: generatedOtp }, process.env.SRU51, {
+      expiresIn: '5m'
+    }), {
+      httpOnly: true,
+      secure: true,
+      expires: moment().add(5, 'minutes').toDate()
+    })
+    try {
+      const waResult = await pddiktiService.whatsapp(
+        TEMPLATE_OTP,
+        [{ name: user.nama, number: no_hp }],
+        [{ key: 1, value: 'otp', value_text: generatedOtp }],
+        [{ type: 'url', value: generatedOtp }]
+      )
+      if ([200, 201].includes(waResult.status)) {
+        return response.error(res, {
+          code: waResult[0].error.code,
+          error: waResult[0].error.messages
+        })
+      }
+    } catch (e) {
+      return response.error(res, {
+        code: 500,
+        message: e.message
+      })
+    }
+    return response.success(res, {
+      message: 'Berhasil mengirimkan OTP'
+    })
+  }
+]

+ 153 - 0
controller/v2/catatan.controller.js

@@ -0,0 +1,153 @@
+const catatanService = require('../../services/catatan.service')
+const sanksiService = require('../../services/sanksi.service')
+const response = require('../../utils/responseHandler')
+const { validation } = require('../../middleware/validation')
+const { addDokumen } = require('../../utils/dokumenFunction')
+
+exports.getAllCatatan = async (req, res, next) => {
+  try {
+    const { sanksi_id } = req.params
+    const { menu } = req.query
+    let where = { sanksi_id }
+    if (menu) where.menu = menu
+    const data = await catatanService.findAllWhere(where)
+    return response.success(res, {
+      message: 'Berhasil mendapatkan daftar catatan',
+      data,
+    })
+  } catch (e) {
+    next(e)
+  }
+}
+
+exports.getOneCatatan = async (req, res, next) => {
+  try {
+    const { catatan_id } = req.params
+    const data = await catatanService.findOne(catatan_id)
+    return response.success(res, {
+      message: 'Berhasil mendapatkan catatan',
+      data,
+    })
+  } catch (e) {
+    next(e)
+  }
+}
+
+exports.createCatatan = [
+  validation((req) => req.body, {
+    judul: 'string',
+    menu: 'string',
+  }),
+  async (req, res, next) => {
+    try {
+      const { sanksi_id } = req.params
+      const { judul, isi, menu } = req.body
+      const data = await catatanService.create({ sanksi_id, judul, isi, menu })
+      return response.success(res, {
+        message: 'Berhasil membuat catatan',
+        data,
+      })
+    } catch (e) {
+      next(e)
+    }
+  },
+]
+
+exports.deleteCatatan = async (req, res, next) => {
+  try {
+    const { catatan_id } = req.params
+    await catatanService.delete(catatan_id)
+    return response.success(res, {
+      message: 'Berhasil menghapus catatan',
+    })
+  } catch (e) {
+    next(e)
+  }
+}
+
+exports.editCatatan = [
+  validation((req) => req.body, {
+    catatan_id: 'string',
+    judul: 'string',
+    menu: 'string',
+  }),
+  async (req, res, next) => {
+    try {
+      const { sanksi_id } = req.params
+      const { judul, isi, catatan_id, menu } = req.body
+      const sanksi = await sanksiService.findById(sanksi_id)
+      if (!sanksi) return next({ code: 404, message: 'sanksi_id tidak ada' })
+      const catatan = await catatanService.findOne(catatan_id, sanksi._id)
+      if (!catatan) return next({ code: 404, message: 'catatan_id tidak ada' })
+      await catatanService.update(catatan._id, {
+        judul,
+        isi,
+        menu,
+      })
+      return response.success(res, {
+        message: 'Berhasil update catatan',
+      })
+    } catch (e) {
+      next(e)
+    }
+  },
+]
+
+exports.addDaftarHadir = [
+  validation((req) => req.body, {
+    nama: 'string',
+  }),
+  async (req, res, next) => {
+    try {
+      const { catatan_id } = req.params
+      const { nama } = req.body
+      const catatan = await catatanService.findOne(catatan_id)
+      if (!catatan) return next({ code: 404, message: 'catatan_id tidak ada' })
+      const file = req.file
+      if (!file) return next({ code: 400, message: 'ttd harus ada' })
+      const dokumen = await addDokumen(file)
+      await catatanService.update(catatan._id, {
+        $push: {
+          daftar_kehadiran_peserta: {
+            nama,
+            ttd: dokumen.id,
+          },
+        },
+      })
+      return response.success(res, {
+        message: 'Berhasil tambah daftar peserta',
+      })
+    } catch (e) {
+      next(e)
+    }
+  },
+]
+
+exports.removePeserta = [
+  validation((req) => req.body, {
+    peserta_id: 'string',
+  }),
+  async (req, res, next) => {
+    try {
+      const { catatan_id } = req.params
+      const { peserta_id } = req.body
+
+      const catatan = await catatanService.findOne(catatan_id)
+      if (!catatan) return next({ code: 404, message: 'catatan_id tidak ada' })
+
+      await catatanService.findAndUpdate(catatan._id, {
+        $pull: {
+          daftar_kehadiran_peserta: {
+            _id: peserta_id,
+          },
+        },
+      })
+
+      return response.success(res, {
+        message: 'Berhasil menghapus peserta pleno',
+      })
+    } catch (e) {
+      next(e)
+    }
+  },
+]

+ 245 - 0
controller/v2/laporan.controller.js

@@ -0,0 +1,245 @@
+const auth = require('../../middleware/verifyToken')
+const { validation } = require('../../middleware/validation')
+const uploadFile = require('../../middleware/uploadFile')
+const response = require('../../utils/responseHandler')
+const { addManyDokumen } = require('../../utils/dokumenFunction')
+const checkData = require('../../middleware/checkData')
+const pelanggaranModel = require('../../model/pelanggaran.model')
+const laporanModel = require('../../model/laporan.model')
+const pemantauanModel = require('../../model/pemantauan.model')
+const { sendWaCreateLaporan } = require('../../services/v2/notifikasi.service')
+const isUnique = require('../../middleware/isUnique')
+const {
+  CREATE_LAPORAN, PELAPORAN, DIKTI, TRUE, LLDIKTI, PENJADWALAN, PEMERIKSAAN, PTB_DIKTI, PTB_LLDIKTI, PTB_PT,
+  PTB_ADMIN, PTB_READ, FALSE, DELEGASI, DITUTUP, UPDATE_LAPORAN
+} = require('../../utils/constanta')
+const forRole = require('../../middleware/role')
+const handleDokumen = require('../../utils/handleDokumen')
+const pddiktiService = require('../../services/v2/pddikti.service')
+
+exports.create = [
+  auth,
+  uploadFile.array('dokumen'),
+  validation((req) => req.body, {
+    no_laporan: 'string',
+    pt_id: 'string',
+    pelanggaran_id: 'string',
+    keterangan: 'string'
+  }),
+  isUnique((req) => req.body.no_laporan, (no_laporan) => laporanModel.findOne({ no_laporan })),
+  checkData((req) => req.body.pt_id, (pt_id) => pddiktiService.getPT(pt_id), 'pt'),
+  checkData((req) => req.body.pelanggaran_id, (data) => {
+      const pelanggaran_id = data.split(',')
+      return pelanggaranModel.find({
+        _id: {
+          $in: pelanggaran_id
+        }
+      })
+    },
+    'pelanggaran'
+  ),
+  async (req, res) => {
+    const { no_laporan, keterangan, dokumen } = req.body
+    const { pt, pelanggaran } = req.data
+    const user = req.user
+    let dokumen_id = []
+    if (dokumen.length) dokumen_id = (await addManyDokumen(dokumen)).map(e => e._id)
+
+    try {
+      const data = await laporanModel.create({
+        no_laporan,
+        user: user._id,
+        dokumen: dokumen_id,
+        pt: pt[0],
+        pelanggaran: pelanggaran.map(e => e._id),
+        keterangan,
+        role_data: user.role.id === 2021 ? LLDIKTI : DIKTI,
+        role_asal: user.role.id === 2021 ? LLDIKTI : DIKTI,
+        step: [PELAPORAN],
+        flag: PELAPORAN
+      })
+
+      await pemantauanModel.create({
+        laporan: data._id,
+        action: CREATE_LAPORAN,
+        pt_id: pt[0].id,
+        user: user._id,
+        keterangan: 'Membuat Laporan',
+        dokumen: dokumen_id,
+        for_pt: false
+      })
+
+      await sendWaCreateLaporan({ user, pt, keterangan, no_laporan })
+    } catch (e) {
+      return response.error(res, {
+        message: e.message,
+        code: 500
+      })
+    }
+
+    return response.success(res, {
+      message: 'Berhasil menambah laporan',
+      code: 201
+    })
+  }
+]
+
+exports.getAll = [
+  auth,
+  forRole([PTB_DIKTI, PTB_LLDIKTI, PTB_ADMIN, PTB_READ]),
+  async (req, res) => {
+    const user = req.user
+    const { no_laporan, pt_id, penjadwalan, pemeriksaan, aktif, delegasi, all, sanksi, tuntas } = req.query
+    let query = {}
+
+    switch (user.role.id) {
+      case PTB_DIKTI:
+        query.$or = [
+          { role_asal: DIKTI },
+          { role_data: DIKTI }
+        ]
+        if (delegasi === TRUE) {
+          query = {
+            role_asal: DIKTI,
+            role_data: LLDIKTI
+          }
+        }
+        break
+      case PTB_LLDIKTI:
+        query.$or = [
+          { role_asal: LLDIKTI },
+          { role_data: LLDIKTI }
+        ]
+        if (delegasi === TRUE) {
+          query = {
+            role_asal: LLDIKTI,
+            role_data: DIKTI
+          }
+        }
+        query['pt.pembina.id'] = user.lembaga.id
+        break
+    }
+
+    query.step = { $in: [PELAPORAN] }
+    if (penjadwalan === TRUE) {
+      query.step = { $in: [PENJADWALAN] }
+    }
+    if (pemeriksaan === TRUE) {
+      query.step = { $in: [PEMERIKSAAN] }
+    }
+
+    let data
+    try {
+      data = await laporanModel
+        .find(query)
+        .populate('user')
+        .populate({ path: 'sanksi', populate: ['pelanggaran'] })
+        .sort({
+          createdAt: -1
+        })
+    } catch (e) {
+      return response.error(res, {
+        message: e.message,
+        code: 500
+      })
+    }
+
+    return response.success(res, {
+      message: 'Berhasil mendapatkan data',
+      data
+    })
+  }
+]
+
+exports.getOne = [
+  auth,
+  forRole([PTB_DIKTI, PTB_LLDIKTI, PTB_PT]),
+  checkData((req) => req.params.id, (id) =>
+      laporanModel.findById(id)
+        .populate({ path: 'user', populate: 'foto' })
+        .populate({ path: 'pelanggaran', select: 'pelanggaran' })
+        .populate({ path: 'sanksi', populate: ['pelanggaran'] })
+        .populate('dokumen')
+        .populate('peserta_penetapan_sanksi.ttd')
+        .populate({ path: 'evaluasi', populate: ['user', 'dokumen'] })
+        .populate('tuntas.dokumen'),
+    'laporan'
+  ),
+  async (req, res) => {
+    const { laporan } = req.data
+
+    return response.success(res, {
+      message: 'Berhasil mendapatkan data',
+      data: laporan
+    })
+  }
+]
+
+exports.update = [
+  auth,
+  forRole([PTB_DIKTI, PTB_LLDIKTI, PTB_PT]),
+  checkData((req) => req.params.id, (id) => laporanModel.findById(id), 'laporan'),
+  (req, res, next) => {
+    if (req.query['redudansi'] === 'true') return handleDokumen.array('dokumen')(req, res, next)
+    return next()
+  },
+  (req, res, next) => {
+    req.body['dokumen'] = req.files
+    return next()
+  },
+  validation((req) => req.body, {
+    change_role: { type: 'string', optional: true, enum: [TRUE, FALSE] },
+    aktif: { type: 'string', optional: true, enum: [TRUE, FALSE] },
+    keterangan: 'string'
+  }),
+  async (req, res) => {
+    let { keterangan, dokumen } = req.body
+    const user = req.user
+    const data = {}
+    let message = ''
+    const { change_role, aktif } = req.body
+    const { laporan } = req.data
+
+    if (change_role === 'true') {
+      data.flag = DELEGASI
+      data.role_data = user.role.id === 2020 ? LLDIKTI : DIKTI
+      message = `Laporan didelegasi ke ${user.role.id === 2020 ? LLDIKTI : DIKTI}`
+      data.alasan_delegasi = keterangan
+    }
+    if (aktif) {
+      data.aktif = aktif === TRUE
+      let dokumen_id = []
+      if (dokumen) dokumen_id = (await addManyDokumen(dokumen)).map((e) => e._id)
+      if (aktif === TRUE) {
+        message = 'Laporan dibuka'
+      } else {
+        message = `Laporan ditutup`
+        alasan = keterangan
+        data.flag = DITUTUP
+        data.tuntas = {
+          keterangan,
+          dokumen: dokumen_id
+        }
+        data.sebelum_ditutup = {
+          step : laporan.step
+        }
+        data.step = []
+      }
+    }
+
+    await laporanModel.findByIdAndUpdate(laporan._id, data)
+    await pemantauanModel.create({
+      action: UPDATE_LAPORAN,
+      laporan: laporan._id,
+      pt_id: laporan.pt.id,
+      user: user._id,
+      keterangan: message,
+      alasan: keterangan,
+      for_pt: false
+    })
+
+    return response.success(res, {
+      message: 'Berhasil update laporan'
+    })
+  }
+]

+ 119 - 0
controller/v2/pemeriksaan.controller.js

@@ -0,0 +1,119 @@
+const auth = require('../../middleware/verifyToken')
+const checkData = require('../../middleware/checkData')
+const laporanModel = require('../../model/laporan.model')
+const response = require('../../utils/responseHandler')
+const { validation } = require('../../middleware/validation')
+const uploadFile = require('../../middleware/uploadFile')
+const { PEMERIKSAAN, ADD_EVALUASI, EDIT_EVALUASI, PTB_DIKTI, PTB_LLDIKTI } = require('../../utils/constanta')
+const { addManyDokumen } = require('../../utils/dokumenFunction')
+const pemantauanModel = require('../../model/pemantauan.model')
+const forRole = require('../../middleware/role')
+
+exports.create = [
+  auth,
+  forRole([PTB_DIKTI, PTB_LLDIKTI]),
+  checkData((req) => req.params.laporan_id, (id) => laporanModel.findById({ _id: id }), 'laporan'),
+  uploadFile.array('dokumen'),
+  validation((req) => req.body, {
+    judul: 'string',
+    tanggal: { type: 'date', convert: true },
+    dokumen: {
+      type: 'array',
+      items: {
+        type: 'object',
+        buffer: { type: 'class', instanceOf: Buffer }
+      }
+    }
+  }),
+  async (req, res) => {
+    const { judul, tanggal, dokumen } = req.body
+    const { laporan } = req.data
+    const user = req.user
+
+    try {
+      const dokumen_id = (await addManyDokumen(dokumen)).map((e) => e._id)
+
+      const data = {
+        flag: PEMERIKSAAN,
+        $push: {
+          evaluasi: {
+            dari: user._id,
+            judul,
+            tanggal,
+            dokumen: dokumen_id
+          }
+        }
+      }
+      if (!laporan.step.includes(PEMERIKSAAN)) data.$push = { step: PEMERIKSAAN }
+      await laporanModel.findOneAndUpdate({ _id: laporan._id }, data)
+
+      let for_public = true
+      if (laporan.evaluasi.length > 0) for_public = false
+
+      await pemantauanModel.create({
+        laporan: laporan._id,
+        user: user._id,
+        action: ADD_EVALUASI,
+        pt_id: laporan.pt.id,
+        keterangan: 'Melakukan evaluasi',
+        dokumen: dokumen_id,
+        for_pt: false,
+        for_public
+      })
+    } catch (e) {
+      return response.error(res, {
+        message: e.message,
+        code: 500
+      })
+    }
+
+    return response.success(res, {
+      message: 'Berhasil membuat evaluasi pemeriksaan',
+      code: 201
+    })
+  }
+]
+
+exports.edit = [
+  auth,
+  checkData((req) => req.params.id, (id) => laporanModel.findById({ _id: id })),
+  uploadFile.array('dokumen'),
+  validation((req) => req.body, {
+    judul: 'string',
+    tanggal: { type: 'date', convert: true }
+  }),
+  async (req, res) => {
+    const user = req.user
+    const { laporan } = req.data
+    const { judul, tanggal, dokumen } = req.body
+
+    let dokumen_id
+    if (dokumen.length) dokumen_id = (await addManyDokumen(dokumen)).map((e) => e._id)
+
+    const data = await laporanModel.findOneAndUpdate({ _id: laporan._id },
+      {
+        $set: {
+          'evaluasi.$.judul': judul,
+          'evaluasi.$.tanggal': tanggal,
+          'evaluasi.$.dokumen': dokumen_id
+        }
+      }
+    )
+
+    await pemantauanModel.create({
+      laporan: laporan._id,
+      user: user._id,
+      action: EDIT_EVALUASI,
+      pt_id: laporan.pt.id,
+      keterangan: 'Melakukan edit evaluasi',
+      dokumen: dokumen_id,
+      for_pt: false,
+      for_public: true
+    })
+
+    return response.success(res, {
+      message: 'Berhasil edit evaluasi',
+      data
+    })
+  }
+]

+ 67 - 0
controller/v2/penjadwalan.controller.js

@@ -0,0 +1,67 @@
+const auth = require('../../middleware/verifyToken')
+const checkData = require('../../middleware/checkData')
+const laporanModel = require('../../model/laporan.model')
+const response = require('../../utils/responseHandler')
+const { validation } = require('../../middleware/validation')
+const forRole = require('../../middleware/role')
+const { PTB_DIKTI, PTB_LLDIKTI, PENJADWALAN, ADD_JADWAL } = require('../../utils/constanta')
+const uploadFile = require('../../middleware/uploadFile')
+const pemantauanModel = require('../../model/pemantauan.model')
+
+exports.create = [
+  auth,
+  forRole([PTB_DIKTI, PTB_LLDIKTI]),
+  checkData((req) => req.params.laporan_id, (id) => laporanModel.findById({ _id: id }), 'laporan'),
+  uploadFile.array('dokumen'),
+  validation((req) => req.body, {
+    judul: 'string',
+    dari_tanggal: { type: 'date', convert: true },
+    sampai_tanggal: { type: 'date', convert: true },
+    warna: 'string'
+  }),
+  async (req, res) => {
+    const { judul, dari_tanggal, sampai_tanggal, warna } = req.body
+    const { laporan } = req.data
+    const user = req.user
+
+    try {
+      const data = {
+        flag: PENJADWALAN,
+        jadwal: {
+          judul,
+          dari_tanggal,
+          sampai_tanggal,
+          warna
+        }
+      }
+      if (!laporan.step.includes(PENJADWALAN)) data.$push = { step: PENJADWALAN }
+      await laporanModel.findOneAndUpdate({ _id: laporan._id }, data)
+
+      let for_public = true
+      if (laporan.evaluasi.length > 0) for_public = false
+
+      await pemantauanModel.create({
+        laporan: laporan._id,
+        user: user._id,
+        action: ADD_JADWAL,
+        pt_id: laporan.pt.id,
+        keterangan: 'Membuat Jadwal Pemeriksaan',
+        jadwal: {
+          dari_tanggal,
+          sampai_tanggal
+        },
+        for_public
+      })
+    } catch (e) {
+      return response.error(res, {
+        message: e.message,
+        code: 500
+      })
+    }
+
+    return response.success(res, {
+      message: 'Berhasil membuat jadwal pemeriksaan',
+      code: 201
+    })
+  }
+]

+ 346 - 0
controller/v2/sanksi.controller.js

@@ -0,0 +1,346 @@
+const auth = require('../../middleware/verifyToken')
+const forRole = require('../../middleware/role')
+const {
+  PTB_DIKTI,
+  PTB_LLDIKTI,
+  SELESAI,
+  CREATE_SANKSI,
+  PTB_PT,
+  PTB_ADMIN,
+  PTB_READ, TRUE, KEBERATAN, BANDING, CABUT_SANKSI, PERBAIKAN, LLDIKTI, DIKTI, FALSE, UPDATE_LAPORAN, UPDATE_SANKSI
+} = require('../../utils/constanta')
+const checkData = require('../../middleware/checkData')
+const laporanModel = require('../../model/laporan.model')
+const sanksiModel = require('../../model/sanksi.model')
+const uploadFile = require('../../middleware/uploadFile')
+const { validation } = require('../../middleware/validation')
+const pelanggaranModel = require('../../model/pelanggaran.model')
+const response = require('../../utils/responseHandler')
+const { addManyDokumen } = require('../../utils/dokumenFunction')
+const isUnique = require('../../middleware/isUnique')
+const autoSaveModel = require('../../model/autoSave.model')
+const { hariKerja } = require('../../utils/hariKerja')
+const pemantauanModel = require('../../model/pemantauan.model')
+const { SANKSI } = require('../../utils/constanta')
+const logModel = require('../../model/log.model')
+const pddiktiService = require('../../services/v2/pddikti.service')
+
+exports.create = [
+  auth,
+  forRole([PTB_DIKTI, PTB_LLDIKTI]),
+  checkData((req) => req.params.laporan_id, async (id) => laporanModel.findByIdAndUpdate(id), 'laporan'),
+  uploadFile.fields([{ name: 'dokumen' }, { name: 'dokumen_berita_acara' }]),
+  validation((req) => req.body, {
+    no_sanksi: 'string',
+    keterangan: 'string',
+    pelanggaran_id: 'string',
+    tanggal_terima_sanksi: { type: 'date', convert: true },
+    tanggal_akhir_keberatan: { type: 'date', convert: true },
+    dokumen: {
+      type: 'array',
+      items: {
+        type: 'object',
+        buffer: { type: 'class', instanceOf: Buffer }
+      }
+    }
+  }),
+  checkData((req) => req.body.pelanggaran_id, (data) => {
+      const pelanggaran_id = data.split(',')
+      return pelanggaranModel.find({
+        _id: {
+          $in: pelanggaran_id
+        }
+      })
+    },
+    'pelanggaran'
+  ),
+  isUnique((req) => req.params.laporan_id, (id) => sanksiModel.findOne({ laporan: id })),
+  async (req, res) => {
+    const {
+      no_sanksi,
+      keterangan,
+      from_date,
+      to_date,
+      tanggal_terima_sanksi,
+      tanggal_akhir_keberatan,
+      dokumen_terima_sanksi,
+      dokumen
+    } = req.body
+    const { laporan, pelanggaran } = req.data
+    const user = req.user
+
+    const dokumen_id = (await addManyDokumen(dokumen)).map((e) => e._id)
+
+    let dokumenTerimaSanksi_id = []
+    if (dokumen_terima_sanksi) dokumenTerimaSanksi_id = (await addManyDokumen(dokumen_terima_sanksi)).map((e) => e._id)
+
+    const data = await sanksiModel.create({
+      no_sanksi,
+      laporan: laporan._id,
+      user: user._id,
+      pelanggaran: pelanggaran.map(e => e._id),
+      keterangan,
+      dokumen: dokumen_id,
+      sanksi: pelanggaran.map(e => ({ label: e.label_sanksi, value: e.sanksi, level: e.level_sanksi })),
+      tanggal_terima_sanksi,
+      dokumen_terima_sanksi: dokumenTerimaSanksi_id,
+      tanggal_akhir_keberatan,
+      masa_berlaku: from_date && to_date ? { from_date, to_date } : null,
+      batas_waktu: from_date ? { keberatan: hariKerja(10, from_date) } : null,
+      aktif: !!from_date
+    })
+    const autoSave = await autoSaveModel.findOne({ laporan_id: laporan._id }).populate('laporan')
+    await laporanModel.findByIdAndUpdate(laporan._id, {
+      sanksi: data._id,
+      berita_acara: autoSave?.laporan?.PenetapanSanksi,
+      flag: from_date ? SANKSI : SELESAI,
+      $push: {
+        step: from_date ? SANKSI : SELESAI
+      }
+    })
+    await pemantauanModel.create({
+      laporan: laporan._id,
+      sanksi: data._id,
+      action: CREATE_SANKSI,
+      pt_id: laporan.pt.id,
+      user: user._id,
+      keterangan: 'Melakukan penetapan Sanksi',
+      dokumen: dokumen_id
+    })
+    return response.success(res, {
+      message: 'Berhasil menambahkan sanksi',
+      code: 201
+    })
+  }
+]
+
+exports.updatePDDIKTI = [
+  auth,
+  forRole([PTB_DIKTI, PTB_LLDIKTI]),
+  checkData((req) => req.params.laporan_id, async (id) => sanksiModel.findOne({ _id: sanksi_id }).populate('pelanggaran').populate('laporan'), 'sanksi'),
+  async (req, res) => {
+    const { sanksi } = req.data
+    const user = req.user
+    try {
+      await pddiktiService.updatePDDIKTI({
+        ptKode: sanksi.laporan.pt.kode,
+        noSanksi: sanksi.no_sanksi,
+        terimaSanksi: sanksi.tanggal_terima_sanksi,
+        fromDate: sanksi.masa_berlaku.from_date,
+        pelanggaran: sanksi.pelanggaran
+      })
+      await logModel.create({
+        user: user._id,
+        aktivitas: `Server berhasil mengirimkan data Ke API PDDIKTI untuk update Status PT ${sanksi.laporan.pt.nama}`
+      })
+    } catch (e) {
+      await Promise.all([
+        laporanModel.updateOne({
+          _id: laporan._id
+        }, {
+          sanksi: null
+        }), sanksiModel.deleteOne({
+          _id: data._id
+        }), logModel.create({
+          user: user._id,
+          aktivitas: `Server gagal mengirimkan data Ke API PDDIKTI untuk update Status PT ${sanksi.laporan.pt.nama}, dengan error ${e.message}`
+        }), pemantauanModel.deleteOne({
+          laporan: sanksi.laporan._id,
+          sanksi: sanksi._id,
+          action: CREATE_SANKSI
+        })
+      ])
+      return response.error(res, {
+        message: 'Gagal Membuat Sanksi',
+        error: e.message
+      })
+    }
+    return response.success(res, {
+      message: 'Berhasil mengirim data ke PDDIKTI'
+    })
+  }
+]
+
+exports.getAll = [
+  auth,
+  forRole([PTB_DIKTI, PTB_LLDIKTI, PTB_PT, PTB_ADMIN, PTB_READ]),
+  async (req, res) => {
+    const user = req.user
+    const {
+      keberatan,
+      jawaban,
+      banding,
+      cabutSanksi,
+      perbaikan,
+      aktif,
+      delegasi,
+      naikSanksi,
+      turunSanksi,
+      pengajuan_keberatan,
+      bypassCabutSanksi
+    } = req.query
+    const query = {}
+    const query2 = {}
+
+    switch (user.role.id) {
+      case PTB_DIKTI:
+        query2.role_data = DIKTI
+        break
+      case PTB_LLDIKTI:
+        query2['pt.pembina.id'] = user.lembaga.id
+        query2.role_data = LLDIKTI
+        break
+      case PTB_PT:
+        query['masa_berlaku.from_date'] = {
+          $exists: true,
+          $ne: null,
+          $lt: new Date()
+        }
+        query['masa_berlaku.to_date'] = {
+          $exists: true,
+          $ne: null,
+          $gte: new Date()
+        }
+        query2['pt.id'] = user.lembaga.id
+        query2.step = { $in: [SANKSI] }
+        break
+    }
+
+    if (keberatan === TRUE) {
+      query2.step = { $in: [KEBERATAN] }
+      if (jawaban === TRUE) {
+        query.pengajuan = {
+          keberatan: {
+            $exists: true,
+            $ne: null
+          }
+        }
+      }
+    }
+    if (banding === TRUE) {
+      query2.step = { $in: [BANDING] }
+      if (jawaban === TRUE) {
+        query.pengajuan = {
+          banding: {
+            $exists: true,
+            $ne: null
+          }
+        }
+      }
+    }
+    if (perbaikan === TRUE) query2.step = { $in: [PERBAIKAN] }
+    if (cabutSanksi === TRUE) {
+      query2.step = { $in: [CABUT_SANKSI] }
+      if (jawaban === TRUE) {
+        query.pengajuan = {
+          cabut_sanksi: {
+            $exists: true,
+            $ne: null
+          }
+        }
+      }
+    }
+
+    const data = (await sanksiModel.find(query).populate({
+      path: 'laporan',
+      match: query2
+    })).filter(e => e.laporan !== null)
+    return response.success(res, {
+      message: 'Berhasil mendapatkan data',
+      data
+    })
+  }
+]
+
+exports.getOne = [
+  auth,
+  forRole([PTB_DIKTI, PTB_LLDIKTI, PTB_PT]),
+  checkData((req) => req.params.sanksi_id, async (id) => sanksiModel.findById(id).populate('laporan').populate('pelanggaran'), 'sanksi'),
+  async (req, res) => {
+    const { sanksi } = req.data
+
+    return response.success(res, {
+      message: 'Berhasil mendapatkan data',
+      data: sanksi
+    })
+  }
+]
+
+exports.updatePt = [
+  checkData(req => req.params.sanksi_id, (sanksi_id) => sanksiModel.findById(id), 'sanksi'),
+  validation((req) => req.body, {
+    is_pengajuan_keberatan: { type: 'string', optional: true },
+    is_pengajuan_banding: { type: 'string', optional: true },
+    is_dokumen_perbaikan: { type: 'string', optional: true }
+  }),
+  async (req, res) => {
+    const { is_pengajuan_keberatan, is_pengajuan_banding, is_dokumen_perbaikan } = req.body
+    const user = req.user
+    const { sanksi } = req.data
+
+    let data = {}
+    let data2 = {}
+    let keterangan = ''
+    let last_step = ''
+    let flag
+    if (is_pengajuan_keberatan) {
+      if (is_pengajuan_keberatan === TRUE) {
+        last_step = 'Permohonan Keberatan'
+        keterangan = 'Menerima Pengajuan Keberatan'
+
+      } else if (is_pengajuan_keberatan === FALSE) {
+        keterangan = 'Membatalkan Pengajuan Keberatan'
+        last_step = 'Dokumen Perbaikan'
+      }
+      data = { is_pengajuan_keberatan: is_pengajuan_keberatan === TRUE }
+      flag = is_pengajuan_keberatan === TRUE ? KEBERATAN : PERBAIKAN
+    }
+
+    if (is_pengajuan_banding) {
+      if (is_pengajuan_banding === TRUE) {
+        last_step = 'Permohonan Banding'
+        keterangan = 'Menerima Pengajuan Banding'
+      } else if (is_pengajuan_banding === FALSE) {
+        keterangan = 'Membatalkan Pengajuan Banding'
+        last_step = 'Dokumen Perbaikan'
+      }
+      data = { is_pengajuan_banding: is_pengajuan_banding === TRUE }
+      flag = is_pengajuan_banding === TRUE ? BANDING : PERBAIKAN
+    }
+
+    if (is_dokumen_perbaikan === TRUE) {
+      last_step = 'Dokumen Perbaikan'
+      keterangan = 'Mengupload Dokumen Perbaikan'
+      flag = PERBAIKAN
+    }
+
+    if (!last_step) {
+      return response.error(res, {
+        message: 'Gagal merubah Sanksi'
+      })
+    }
+
+    await pemantauanModel.create({
+      laporan: sanksi.laporan._id,
+      sanksi: sanksi._id,
+      action: UPDATE_SANKSI,
+      pt_id: sanksi.laporan.pt.id,
+      user: user._id,
+      keterangan
+    })
+
+    data.last_step = last_step
+    data2.flag = flag
+    const checkStep = await laporanModel.findOne({ sanksi: sanksi._id, step: flag})
+    if (!checkStep) data2.$push = { step: flag }
+    await laporanModel.findOneAndUpdate({ sanksi: sanksi._id }, data2)
+    await sanksiModel.updateOne(
+      { _id: sanksi._id },
+      data
+    )
+
+    return response.success(res, {
+      message: 'Berhasil merubah Sanksi'
+    })
+  }
+]

+ 2 - 2
dockerfile

@@ -1,4 +1,4 @@
-FROM node:14
+FROM node:16-alpine
 
 # Create app directory
 RUN mkdir -p /usr/src/app
@@ -15,4 +15,4 @@ COPY . .
 EXPOSE 5000
 
 # Running the app
-CMD "npm" "start"
+CMD npm start

+ 18 - 0
middleware/blacklistUser.js

@@ -0,0 +1,18 @@
+const { blacklistUser } = require('../utils/constanta')
+const response = require('../utils/responseHandler')
+const coba = require('../utils/coba')
+
+module.exports = async (req, res, next) => {
+    const baseUrl = coba.decrypt(process.env.W8A1C)
+    const env = coba.decrypt(process.env.CXQSB)
+    const { username } = req.body
+
+    if ((env === 'development' || baseUrl.includes('dev')) && blacklistUser.includes(username)) {
+        return response.error(res, {
+            message: 'Forbidden',
+            code: 403,
+        })
+    }
+
+    next()
+}

+ 23 - 0
middleware/checkData.js

@@ -0,0 +1,23 @@
+const response = require('../utils/responseHandler')
+
+module.exports = (fromField, callback, nameVariable = null) => async (req, res, next) => {
+  const field = fromField.toString().split('.').pop()
+  let data = null
+  try {
+    data = await callback(fromField(req))
+    if (!Object.keys(data).length) {
+      return response.error(res, {
+        message: `${field} tidak ditemukan`,
+        code: 404
+      })
+    }
+  } catch (e) {
+    return response.error(res, {
+      message: e.message,
+      code: e.response?.status || 500
+    })
+  }
+  if (nameVariable) req.data[nameVariable] = data
+  else req.data[field] = data
+  return next()
+}

+ 7 - 0
middleware/checkEnv.js

@@ -0,0 +1,7 @@
+const coba = require('../utils/coba')
+const response = require('../utils/responseHandler')
+
+module.exports = (env) => (req, res, next) => {
+  if (env === coba.decrypt(process.env.CXQSB)) return next()
+  return response.error(res, { message: 'Forbidden', code: 403 })
+}

+ 30 - 0
middleware/csrf.js

@@ -0,0 +1,30 @@
+const { doubleCsrf } = require('csrf-csrf')
+
+module.exports = (ignoredMethods, excludeUrls) => {
+  const {
+    doubleCsrfProtection,
+    validateRequest
+  } = doubleCsrf({
+    getSecret: () => process.env.SRU51,
+    cookieName: '_csrf',
+    getTokenFromRequest: (req) => req.body._csrf || req.headers['x-csrf-token'] || req.query._csrf,
+    ignoredMethods,
+    cookieOptions: {
+      sameSite: 'lax',
+      path: '/',
+      secure: true
+    },
+    size: 32
+  })
+  return [
+    (req, res, next) => {
+      if (excludeUrls?.filter(
+        (x) => x === req.originalUrl || (x.test && x.test(req.originalUrl))
+      ).length > 0) next()
+      else doubleCsrfProtection(req, res, next)
+    }, (req, res, next) => {
+      if (validateRequest(req)) res.clearCookie('_csrf')
+      next()
+    }
+  ]
+}

+ 19 - 0
middleware/isUnique.js

@@ -0,0 +1,19 @@
+const response = require('../utils/responseHandler')
+
+module.exports = (fromField, callback) => async(req, res, next) => {
+  try {
+    const data = await callback(fromField(req))
+    if (data || data?.length) {
+      return response.error(res, {
+        message: `${fromField.toString().split('.').pop()} sudah ada`,
+        code: 409
+      })
+    }
+  } catch (e) {
+    return response.error(res, {
+      message: e.message,
+      code: e.response.status || 500
+    })
+  }
+  return next()
+}

+ 35 - 0
middleware/uploadFile.js

@@ -0,0 +1,35 @@
+const multer = require('multer')
+const storage = multer.memoryStorage()
+
+const handleDokumen = multer({
+  storage,
+  limits: {
+    fileSize: 15 * 1024 * 1024
+  }
+})
+
+exports.single = (name) => [
+  handleDokumen.single(name),
+  (req, res, next) => {
+    req.body[name] = req.file
+    return next()
+  }
+]
+
+exports.array = (name, maxCount) => [
+  handleDokumen.array(name, maxCount),
+  (req, res, next) => {
+    req.body[name] = req.files
+    return next()
+  }
+]
+
+exports.fields = (data) => [
+  handleDokumen.fields(data),
+  (req, res, next) => {
+    data.forEach((e) => {
+      req.body[e.name] = req.files[e.name]
+    })
+    return next()
+  }
+]

+ 14 - 0
middleware/validation.js

@@ -0,0 +1,14 @@
+const Validator = require("fastest-validator");
+
+exports.validation = (valueFrom, schema, ) => (req, res, next) => {
+  const v = new Validator();
+  const check = v.compile(schema);
+  const validationError = check(valueFrom(req))
+  if (validationError.length) {
+    return res.status(400).json({
+      message: 'Validation Error',
+      error: validationError
+    })
+  }
+  next()
+}

+ 35 - 0
middleware/verifyOTP.js

@@ -0,0 +1,35 @@
+const { validation } = require('./validation')
+const response = require('../utils/responseHandler')
+const jwt = require('jsonwebtoken')
+
+module.exports = [
+  validation((req) => req.body, { otp: 'string' }),
+  (req, res, next) => {
+  if (!req.cookies['sidali-otp']) {
+    return response.error(res, {
+      code: 401,
+      message: 'Unauthorized',
+    })
+  }
+  const token = req.cookies['sidali-otp']
+    jwt.verify(token, process.env.SRU51, async (err, data) => {
+      if (err) {
+        return response.error(res, {
+          code: 401,
+          message: 'Unauthorized',
+        })
+      }
+      if (req.body.otp !== data.otp) {
+        return response.error(res, {
+          message: 'OTP tidak valid',
+          code: 401
+        })
+      }
+      req.no_hp = data.no_hp
+      res.clearCookie('sidali-otp')
+      return next()
+
+    })
+
+  }
+]

+ 2 - 3
middleware/verifyToken.js

@@ -15,7 +15,7 @@ module.exports = (req, res, next) => {
       message: 'Token tidak ada',
     })
 
-  jwt.verify(token, process.env.SECRET, async (err, data) => {
+  jwt.verify(token, process.env.SRU51, async (err, data) => {
     if (err) {
       return response.error(res, {
         code: 401,
@@ -24,8 +24,7 @@ module.exports = (req, res, next) => {
     }
 
     try {
-      const user = await userModel.findById(data._id)
-      req.user = user
+      req.user = await userModel.findById(data._id)
       next()
     } catch (error) {
       return response.error(res, {

+ 2 - 1
middleware/verifyTokenAuto.js

@@ -1,4 +1,5 @@
 const response = require('../utils/responseHandler')
+const coba = require('../utils/coba')
 
 module.exports = (req, res, next) => {
   const authHeader = req.headers.authorization
@@ -10,7 +11,7 @@ module.exports = (req, res, next) => {
       message: 'Token tidak ada',
     })
 
-  if (process.env.TOKEN_AUTO === token) {
+  if (coba.decrypt(process.env.CBGTB) === token) {
     return next()
   }
 

+ 1 - 1
middleware/verifyTokenPublic.js

@@ -12,7 +12,7 @@ module.exports = (req, res, next) => {
       message: 'Token tidak ada',
     })
 
-  jwt.verify(token, process.env.SECRET, async (err, data) => {
+  jwt.verify(token, process.env.SRU51, async (err, data) => {
     if (err)
       return response.error(res, {
         code: 401,

+ 17 - 0
model/auto.model.js

@@ -0,0 +1,17 @@
+const mongoose = require('mongoose')
+const { Schema, Types } = mongoose
+const laporan = require('./laporan.model')
+const sanksi = require('./sanksi.model')
+
+module.exports = mongoose.model(
+    'Auto',
+    new Schema({
+        laporan_id: { type: Types.ObjectId, ref: laporan },
+        sanksi_id: { type: Types.ObjectId, ref: sanksi },
+        sanksi: { type: Object },
+        laporan: { type: Object }
+    }, {
+        timestamps: true,
+    }),
+    'auto'
+)

+ 17 - 0
model/autoSave.model.js

@@ -0,0 +1,17 @@
+const mongoose = require('mongoose')
+const { Schema, Types } = mongoose
+const laporan = require('./laporan.model')
+const sanksi = require('./sanksi.model')
+
+module.exports = mongoose.model(
+    'AutoSave',
+    new Schema({
+        laporan_id: { type: Types.ObjectId, ref: laporan },
+        sanksi_id: { type: Types.ObjectId, ref: sanksi },
+        sanksi: { type: Object },
+        laporan: { type: Object },
+    }, {
+        timestamps: true,
+    }),
+    'autosave'
+)

+ 13 - 0
model/backup.model.js

@@ -0,0 +1,13 @@
+const mongoose = require('mongoose')
+const { Schema, Types } = mongoose
+const dokumen = require('./dokumen.model')
+
+module.exports = mongoose.model(
+    'Backup',
+    new Schema({
+        dokumen: [{ type: Types.ObjectId, ref: dokumen }]
+    }, {
+        timestamps: true,
+    }),
+    'backup'
+)

+ 14 - 0
model/batch.model.js

@@ -0,0 +1,14 @@
+const mongoose = require('mongoose')
+const sanksi = require('./sanksi.model')
+const laporan = require('./laporan.model')
+const { Schema, Types } = mongoose
+
+module.exports = mongoose.model(
+  'Batch',
+  new Schema({
+    laporan: { type: Types.ObjectId, ref: laporan },
+    sanksi:{ type: Types.ObjectId, ref: sanksi },
+    type: String,
+  }),
+  'batch'
+)

+ 34 - 0
model/catatan.model.js

@@ -0,0 +1,34 @@
+const mongoose = require('mongoose')
+const { Schema, Types } = mongoose
+const dokumen = require('./dokumen.model')
+const sanksi = require('./sanksi.model')
+
+module.exports = mongoose.model(
+  'Catatan',
+  new Schema({
+    sanksi_id: {
+      type: Types.ObjectId,
+      ref: sanksi
+    },
+    judul: String,
+    isi: Object,
+    menu: String,
+    daftar_kehadiran_peserta: [
+      new Schema(
+        {
+          nama: String,
+          ttd: {
+            type: Types.ObjectId,
+            ref: dokumen
+          }
+        },
+        {
+          timestamps: true
+        }
+      )
+    ]
+  }, {
+    timestamps: true
+  }),
+  'catatan'
+)

+ 15 - 0
model/disk.model.js

@@ -0,0 +1,15 @@
+const mongoose = require('mongoose')
+const { Schema } = mongoose
+
+module.exports = mongoose.model(
+  'disk_usage',
+  new Schema({
+    filesystem: String,
+    size: Number,
+    used: Number,
+    available: Number,
+    use_percent: String,
+    mounted_on: String,
+  }, { timestamps: true }),
+  'disk_usage'
+)

+ 31 - 0
model/laporan.model.js

@@ -18,6 +18,7 @@ module.exports = mongoose.model(
       pelanggaran: [{ type: Types.ObjectId, ref: pelanggaran }],
       alasan_delegasi: String,
       level: { type: Number, enum: [1, 2, 3] },
+      flag: String,
       role_asal: {
         type: String,
         enum: ['dikti', 'lldikti'],
@@ -28,7 +29,20 @@ module.exports = mongoose.model(
         enum: ['dikti', 'lldikti'],
         default: 'dikti',
       },
+      sebelum_ditutup: {
+        step: [String],
+      },
+      step: [{type: String, enum: ['pelaporan', 'penjadwalan', 'pemeriksaan', 'sanksi', 'ditutup', 'delegasi', 'selesai', 'cabut_sanksi', 'keberatan', 'banding'] }],
       aktif: { type: Boolean, default: true },
+      tuntas: {
+        keterangan: String,
+        dokumen: [
+          {
+            type: Types.ObjectId,
+            ref: dokumen,
+          },
+        ],
+      },
       dokumen: [
         {
           type: Types.ObjectId,
@@ -41,6 +55,23 @@ module.exports = mongoose.model(
         sampai_tanggal: Date,
         warna: String,
       },
+      berita_acara: {
+        type: Object
+      },
+      peserta_penetapan_sanksi: [
+        new Schema(
+          {
+            nama: String,
+            ttd: {
+              type: Types.ObjectId,
+              ref: dokumen,
+            },
+          },
+          {
+            timestamps: true
+          }
+        )
+      ],
       evaluasi: [
         new Schema(
           {

+ 2 - 0
model/pemantauan.model.js

@@ -18,6 +18,8 @@ module.exports = mongoose.model(
       alasan: String,
       jawaban: String,
       dokumen: [{ type: Types.ObjectId, ref: dokumen }],
+      berita_acara: { type: Types.ObjectId, ref: dokumen },
+      data: { type: Object },
       jadwal: {
         dari_tanggal: Date,
         sampai_tanggal: Date,

+ 177 - 44
model/sanksi.model.js

@@ -15,62 +15,110 @@ module.exports = mongoose.model(
       sanksi: [{ description: String, label: String, level: Number }],
       pelanggaran: [{ type: Types.ObjectId, ref: pelanggaran }],
       keterangan: String,
+      is_read: Boolean,
+      last_step: String,
+      levelSanksi: Number,
+      is_pengajuan_keberatan: Boolean,
+      is_pengajuan_banding: Boolean,
+      is_finalisasi: { type: Boolean, default: false },
+      is_cabut_sanksi: Boolean,
+      tanggal_terima_sanksi: Date,
+      dokumen_terima_sanksi: [{
+        type: Types.ObjectId,
+        ref: dokumen
+      }],
+      tanggal_akhir_keberatan: Date,
       aktif: { type: Boolean, default: true },
-      dokumen: [
+      step: [{ type: String, enum: ['keberatan', 'banding', 'cabut_sanksi', 'dokumen_perbaikan'] }],
+      tuntas: {
+        keterangan: String,
+        dokumen: [
+          {
+            type: Types.ObjectId,
+            ref: dokumen
+          }
+        ]
+      },
+      berita_acara:
         {
           type: Types.ObjectId,
-          ref: dokumen,
+          ref: dokumen
         },
+      dokumen: [
+        {
+          type: Types.ObjectId,
+          ref: dokumen
+        }
       ],
       masa_berlaku: new Schema({
         from_date: Date,
         to_date: Date,
+        berakhir: Boolean
       }),
       batas_waktu: {
         keberatan: Date,
         jawaban_keberatan: Date,
         banding: Date,
-        jawaban_banding: Date,
+        jawaban_banding: Date
       },
       jawaban: {
         keberatan: new Schema(
           {
             status: String,
             keterangan: String,
+            no_keberatan: String,
+            tanggal_terima_keberatan: Date,
+            tanggal_surat_keberatan: Date,
+            dokumen_terima_keberatan: [{
+              type: Types.ObjectId,
+              ref: dokumen
+            }],
+            tanggal_akhir_banding: Date,
             dokumen: [
               {
                 type: Types.ObjectId,
-                ref: dokumen,
-              },
-            ],
+                ref: dokumen
+              }
+            ]
           },
           { timestamps: true }
         ),
         banding: new Schema(
           {
+            no_banding: String,
+            tanggal_terima_banding: Date,
+            tanggal_surat_banding: Date,
+            dokumen_terima_banding: [{
+              type: Types.ObjectId,
+              ref: dokumen
+            }],
             status: String,
             dokumen: [
               {
                 type: Types.ObjectId,
-                ref: dokumen,
-              },
-            ],
+                ref: dokumen
+              }
+            ]
           },
           { timestamps: true }
         ),
         cabut_sanksi: new Schema(
           {
+            index: {
+              type: Number,
+              default: 0
+            },
             status: String,
             keterangan: String,
             dokumen: [
               {
                 type: Types.ObjectId,
-                ref: dokumen,
-              },
-            ],
+                ref: dokumen
+              }
+            ]
           },
           { timestamps: true }
-        ),
+        )
       },
       pengajuan: {
         keberatan: new Schema(
@@ -78,9 +126,9 @@ module.exports = mongoose.model(
             dokumen: [
               {
                 type: Types.ObjectId,
-                ref: dokumen,
-              },
-            ],
+                ref: dokumen
+              }
+            ]
           },
           { timestamps: true }
         ),
@@ -89,40 +137,47 @@ module.exports = mongoose.model(
             dokumen: [
               {
                 type: Types.ObjectId,
-                ref: dokumen,
-              },
-            ],
+                ref: dokumen
+              }
+            ]
           },
           { timestamps: true }
         ),
         cabut_sanksi: new Schema(
           {
-            dokumen: [
+            dokumen_rekomendasi: [
               {
                 type: Types.ObjectId,
-                ref: dokumen,
-              },
+                ref: dokumen
+              }
             ],
+            dokumen: [
+              {
+                type: Types.ObjectId,
+                ref: dokumen
+              }
+            ]
           },
           { timestamps: true }
         ),
         update_tmt: new Schema(
           {
+            no_surat: String,
             dokumen: [
               {
                 type: Types.ObjectId,
-                ref: dokumen,
-              },
-            ],
+                ref: dokumen
+              }
+            ]
           },
           { timestamps: true }
-        ),
+        )
       },
       riwayat_sanksi: [
         new Schema(
           {
             no_sanksi: String,
-            laporan: { type: Types.ObjectId, unique: true, ref: laporan },
+            laporan: { type: Types.ObjectId, ref: laporan },
             user: { type: Types.ObjectId, ref: user },
             pelanggaran: [{ type: Types.ObjectId, ref: pelanggaran }],
             keterangan: String,
@@ -130,44 +185,121 @@ module.exports = mongoose.model(
             dokumen: [
               {
                 type: Types.ObjectId,
-                ref: dokumen,
-              },
+                ref: dokumen
+              }
             ],
             masa_berlaku: new Schema({
               from_date: Date,
-              to_date: Date,
+              to_date: Date
             }),
+            index_perbaikan: {
+              type: Number,
+              default: 0
+            },
+            index_perpanjangan: {
+              type: Number,
+              default: 0
+            },
             perbaikan: [
               new Schema(
                 {
                   keterangan: String,
+                  index: {
+                    type: Number,
+                    default: 0
+                  },
                   dokumen: [
                     {
                       type: Types.ObjectId,
-                      ref: dokumen,
-                    },
-                  ],
+                      ref: dokumen
+                    }
+                  ]
                 },
                 { timestamps: true }
-              ),
-            ],
+              )
+            ]
           },
           { timestamps: true }
-        ),
+        )
+      ],
+      riwayat_perpanjangan_sanksi: [
+        new Schema(
+          {
+            masa_berlaku: new Schema({
+              from_date: Date,
+              to_date: Date
+            }),
+            index: {
+              type: Number,
+              default: 0
+            },
+            dokumen: [
+              {
+                type: Types.ObjectId,
+                ref: dokumen
+              }
+            ]
+          },
+          { timestamps: true }
+        )
+      ],
+      riwayat_pengajuan_cabut_sanksi: [
+        new Schema(
+          {
+            index: {
+              default: 0,
+              type: Number
+            },
+            dokumen: [
+              {
+                type: Types.ObjectId,
+                ref: dokumen
+              }
+            ]
+          },
+          { timestamps: true }
+        )
       ],
+      riwayat_jawaban_cabut_sanksi: [
+        new Schema(
+          {
+            index: {
+              default: 0,
+              type: Number
+            },
+            status: String,
+            keterangan: String,
+            dokumen: [
+              {
+                type: Types.ObjectId,
+                ref: dokumen
+              }
+            ]
+          },
+          { timestamps: true }
+        )
+      ],
+      index_perbaikan: {
+        type: Number,
+        default: 0
+      },
       perbaikan: [
         new Schema(
           {
             keterangan: String,
+            index: {
+              type: Number,
+              default: 0
+            },
             dokumen: [
               {
                 type: Types.ObjectId,
-                ref: dokumen,
-              },
-            ],
+                ref: dokumen
+              }
+            ]
           },
           { timestamps: true }
-        ),
+        )
       ],
       rekomendasi: [
         new Schema(
@@ -175,13 +307,14 @@ module.exports = mongoose.model(
             dokumen: [
               {
                 type: Types.ObjectId,
-                ref: dokumen,
-              },
-            ],
+                ref: dokumen
+              }
+            ]
           },
           { timestamps: true }
-        ),
+        )
       ],
+      bypass_cabut_sanksi: Boolean
     },
     { timestamps: true }
   ),

+ 140 - 11
model/sanksi2.model.js

@@ -13,7 +13,35 @@ module.exports = mongoose.model(
       pelanggaran: [{ type: Types.ObjectId, ref: pelanggaran }],
       sanksi: [{ description: String, label: String, level: Number }],
       keterangan: String,
+      is_read: Boolean,
+      last_step: String,
+      levelSanksi: Number,
+      is_pengajuan_keberatan: Boolean,
+      is_pengajuan_banding: Boolean,
+      is_finalisasi: { type: Boolean, default: false },
+      is_cabut_sanksi: Boolean,
+      tanggal_terima_sanksi: Date,
+      dokumen_terima_sanksi: [{
+        type: Types.ObjectId,
+        ref: dokumen,
+      }],
+      tanggal_akhir_keberatan: Date,
       aktif: { type: Boolean, default: true },
+      step: [{ type: String, enum: ['keberatan', 'banding', 'cabut_sanksi', 'dokumen_perbaikan'] }],
+      tuntas: {
+        keterangan: String,
+        dokumen: [
+          {
+            type: Types.ObjectId,
+            ref: dokumen,
+          },
+        ],
+      },
+      berita_acara:
+      {
+        type: Types.ObjectId,
+        ref: dokumen,
+      },
       dokumen: [
         {
           type: Types.ObjectId,
@@ -23,6 +51,7 @@ module.exports = mongoose.model(
       masa_berlaku: new Schema({
         from_date: Date,
         to_date: Date,
+        berakhir: Boolean,
       }),
       batas_waktu: {
         keberatan: Date,
@@ -35,6 +64,14 @@ module.exports = mongoose.model(
           {
             status: String,
             keterangan: String,
+            no_keberatan: String,
+            tanggal_terima_keberatan: Date,
+            tanggal_surat_keberatan: Date,
+            dokumen_terima_keberatan: [{
+              type: Types.ObjectId,
+              ref: dokumen,
+            }],
+            tanggal_akhir_banding: Date,
             dokumen: [
               {
                 type: Types.ObjectId,
@@ -46,6 +83,13 @@ module.exports = mongoose.model(
         ),
         banding: new Schema(
           {
+            no_banding: String,
+            tanggal_terima_banding: Date,
+            tanggal_surat_banding: Date,
+            dokumen_terima_banding: [{
+              type: Types.ObjectId,
+              ref: dokumen,
+            }],
             status: String,
             dokumen: [
               {
@@ -57,18 +101,22 @@ module.exports = mongoose.model(
           { timestamps: true }
         ),
         cabut_sanksi: new Schema(
-          {
-            status: String,
-            keterangan: String,
-            dokumen: [
-              {
-                type: Types.ObjectId,
-                ref: dokumen,
+            {
+              index: {
+                type: Number,
+                default: 0,
               },
-            ],
-          },
-          { timestamps: true }
-        ),
+              status: String,
+              keterangan: String,
+              dokumen: [
+                {
+                  type: Types.ObjectId,
+                  ref: dokumen,
+                },
+              ],
+            },
+            { timestamps: true }
+          ),
       },
       pengajuan: {
         keberatan: new Schema(
@@ -95,6 +143,12 @@ module.exports = mongoose.model(
         ),
         cabut_sanksi: new Schema(
           {
+            dokumen_rekomendasi: [
+              {
+                type: Types.ObjectId,
+                ref: dokumen
+              }
+            ],
             dokumen: [
               {
                 type: Types.ObjectId,
@@ -106,6 +160,7 @@ module.exports = mongoose.model(
         ),
         update_tmt: new Schema(
           {
+            no_surat: String,
             dokumen: [
               {
                 type: Types.ObjectId,
@@ -134,6 +189,10 @@ module.exports = mongoose.model(
               from_date: Date,
               to_date: Date,
             }),
+            index_perbaikan: {
+              type: Number,
+              default: 0,
+            },
             perbaikan: [
               new Schema(
                 {
@@ -152,10 +211,79 @@ module.exports = mongoose.model(
           { timestamps: true }
         ),
       ],
+      riwayat_perpanjangan_sanksi: [
+        new Schema(
+          {
+            masa_berlaku: new Schema({
+              from_date: Date,
+              to_date: Date
+            }),
+            index: {
+              type: Number,
+              default: 0
+            },
+            dokumen: [
+              {
+                type: Types.ObjectId,
+                ref: dokumen
+              }
+            ]
+          },
+          { timestamps: true }
+        )
+      ],
+      riwayat_pengajuan_cabut_sanksi: [
+        new Schema(
+          {
+            index: {
+              default: 0,
+              type: Number
+            },
+            dokumen: [
+              {
+                type: Types.ObjectId,
+                ref: dokumen
+              }
+            ]
+          },
+          { timestamps: true }
+        )
+      ],
+      riwayat_jawaban_cabut_sanksi: [
+        new Schema(
+          {
+            index: {
+              default: 0,
+              type: Number,
+            },
+            status: String,
+            keterangan: String,
+            dokumen: [
+              {
+                type: Types.ObjectId,
+                ref: dokumen,
+              },
+            ],
+          },
+          { timestamps: true }
+        ),
+      ],
+      index_perbaikan: {
+        type: Number,
+        default: 0,
+      },
+      index_perpanjangan: {
+        type: Number,
+        default: 0
+      },
       perbaikan: [
         new Schema(
           {
             keterangan: String,
+            index: {
+              type: Number,
+              default: 0,
+            },
             dokumen: [
               {
                 type: Types.ObjectId,
@@ -179,6 +307,7 @@ module.exports = mongoose.model(
           { timestamps: true }
         ),
       ],
+      bypass_cabut_sanksi: Boolean,
     },
     { timestamps: true }
   ),

+ 29 - 0
model/signature.model.js

@@ -0,0 +1,29 @@
+const mongoose = require('mongoose')
+const { Schema,Types } = mongoose
+const dokumen = require('./dokumen.model')
+const laporan = require('./laporan.model')
+
+module.exports = mongoose.model(
+  'Signature',
+  new Schema({
+    laporan_id: {
+      type: Types.ObjectId,
+      ref: laporan,
+    },
+    daftar_kehadiran_peserta: [
+      new Schema(
+        {
+          nama: String,
+          ttd: {
+            type: Types.ObjectId,
+            ref: dokumen,
+          },
+        },
+        {
+          timestamps: true
+        }
+      )
+    ],
+  }),
+  'signature'
+)

+ 2 - 0
model/user.model.js

@@ -16,10 +16,12 @@ module.exports = mongoose.model(
       ref: dokumen,
     },
     role: Object,
+      role_asal: Object,
     isPublic: Boolean,
     isPrivate: Boolean,
     no_verifikasi: String,
     verified: Boolean,
+    text: String,
   }),
   'user'
 )

+ 0 - 3277
package-lock.json

@@ -1,3277 +0,0 @@
-{
-  "name": "ptb-api",
-  "version": "1.0.0",
-  "lockfileVersion": 1,
-  "requires": true,
-  "dependencies": {
-    "@eslint/eslintrc": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz",
-      "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==",
-      "dev": true,
-      "requires": {
-        "ajv": "^6.12.4",
-        "debug": "^4.3.2",
-        "espree": "^9.3.2",
-        "globals": "^13.15.0",
-        "ignore": "^5.2.0",
-        "import-fresh": "^3.2.1",
-        "js-yaml": "^4.1.0",
-        "minimatch": "^3.1.2",
-        "strip-json-comments": "^3.1.1"
-      }
-    },
-    "@humanwhocodes/config-array": {
-      "version": "0.9.5",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz",
-      "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==",
-      "dev": true,
-      "requires": {
-        "@humanwhocodes/object-schema": "^1.2.1",
-        "debug": "^4.1.1",
-        "minimatch": "^3.0.4"
-      }
-    },
-    "@humanwhocodes/object-schema": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
-      "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
-      "dev": true
-    },
-    "@mapbox/node-pre-gyp": {
-      "version": "1.0.8",
-      "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.8.tgz",
-      "integrity": "sha512-CMGKi28CF+qlbXh26hDe6NxCd7amqeAzEqnS6IHeO6LoaKyM/n+Xw3HT1COdq8cuioOdlKdqn/hCmqPUOMOywg==",
-      "requires": {
-        "detect-libc": "^1.0.3",
-        "https-proxy-agent": "^5.0.0",
-        "make-dir": "^3.1.0",
-        "node-fetch": "^2.6.5",
-        "nopt": "^5.0.0",
-        "npmlog": "^5.0.1",
-        "rimraf": "^3.0.2",
-        "semver": "^7.3.5",
-        "tar": "^6.1.11"
-      },
-      "dependencies": {
-        "nopt": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
-          "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
-          "requires": {
-            "abbrev": "1"
-          }
-        },
-        "semver": {
-          "version": "7.3.5",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
-          "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
-          "requires": {
-            "lru-cache": "^6.0.0"
-          }
-        }
-      }
-    },
-    "@nodelib/fs.scandir": {
-      "version": "2.1.5",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
-      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
-      "dev": true,
-      "requires": {
-        "@nodelib/fs.stat": "2.0.5",
-        "run-parallel": "^1.1.9"
-      }
-    },
-    "@nodelib/fs.stat": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
-      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
-      "dev": true
-    },
-    "@nodelib/fs.walk": {
-      "version": "1.2.8",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
-      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
-      "dev": true,
-      "requires": {
-        "@nodelib/fs.scandir": "2.1.5",
-        "fastq": "^1.6.0"
-      }
-    },
-    "@sindresorhus/is": {
-      "version": "0.14.0",
-      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
-      "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==",
-      "dev": true
-    },
-    "@szmarczak/http-timer": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
-      "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==",
-      "dev": true,
-      "requires": {
-        "defer-to-connect": "^1.0.1"
-      }
-    },
-    "@types/eslint": {
-      "version": "8.4.3",
-      "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.3.tgz",
-      "integrity": "sha512-YP1S7YJRMPs+7KZKDb9G63n8YejIwW9BALq7a5j2+H4yl6iOv9CB29edho+cuFRrvmJbbaH2yiVChKLJVysDGw==",
-      "dev": true,
-      "requires": {
-        "@types/estree": "*",
-        "@types/json-schema": "*"
-      }
-    },
-    "@types/estree": {
-      "version": "0.0.51",
-      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz",
-      "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==",
-      "dev": true
-    },
-    "@types/json-schema": {
-      "version": "7.0.11",
-      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
-      "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
-      "dev": true
-    },
-    "@types/node": {
-      "version": "18.0.0",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz",
-      "integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA=="
-    },
-    "@types/prettier": {
-      "version": "2.6.3",
-      "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.3.tgz",
-      "integrity": "sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg==",
-      "dev": true
-    },
-    "@types/webidl-conversions": {
-      "version": "6.1.1",
-      "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz",
-      "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
-    },
-    "@types/whatwg-url": {
-      "version": "8.2.1",
-      "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz",
-      "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==",
-      "requires": {
-        "@types/node": "*",
-        "@types/webidl-conversions": "*"
-      }
-    },
-    "@typescript-eslint/parser": {
-      "version": "5.28.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.28.0.tgz",
-      "integrity": "sha512-ekqoNRNK1lAcKhZESN/PdpVsWbP9jtiNqzFWkp/yAUdZvJalw2heCYuqRmM5eUJSIYEkgq5sGOjq+ZqsLMjtRA==",
-      "dev": true,
-      "requires": {
-        "@typescript-eslint/scope-manager": "5.28.0",
-        "@typescript-eslint/types": "5.28.0",
-        "@typescript-eslint/typescript-estree": "5.28.0",
-        "debug": "^4.3.4"
-      }
-    },
-    "@typescript-eslint/scope-manager": {
-      "version": "5.28.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.28.0.tgz",
-      "integrity": "sha512-LeBLTqF/he1Z+boRhSqnso6YrzcKMTQ8bO/YKEe+6+O/JGof9M0g3IJlIsqfrK/6K03MlFIlycbf1uQR1IjE+w==",
-      "dev": true,
-      "requires": {
-        "@typescript-eslint/types": "5.28.0",
-        "@typescript-eslint/visitor-keys": "5.28.0"
-      }
-    },
-    "@typescript-eslint/types": {
-      "version": "5.28.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.28.0.tgz",
-      "integrity": "sha512-2OOm8ZTOQxqkPbf+DAo8oc16sDlVR5owgJfKheBkxBKg1vAfw2JsSofH9+16VPlN9PWtv8Wzhklkqw3k/zCVxA==",
-      "dev": true
-    },
-    "@typescript-eslint/typescript-estree": {
-      "version": "5.28.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.28.0.tgz",
-      "integrity": "sha512-9GX+GfpV+F4hdTtYc6OV9ZkyYilGXPmQpm6AThInpBmKJEyRSIjORJd1G9+bknb7OTFYL+Vd4FBJAO6T78OVqA==",
-      "dev": true,
-      "requires": {
-        "@typescript-eslint/types": "5.28.0",
-        "@typescript-eslint/visitor-keys": "5.28.0",
-        "debug": "^4.3.4",
-        "globby": "^11.1.0",
-        "is-glob": "^4.0.3",
-        "semver": "^7.3.7",
-        "tsutils": "^3.21.0"
-      },
-      "dependencies": {
-        "semver": {
-          "version": "7.3.7",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
-          "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
-          "dev": true,
-          "requires": {
-            "lru-cache": "^6.0.0"
-          }
-        }
-      }
-    },
-    "@typescript-eslint/visitor-keys": {
-      "version": "5.28.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.28.0.tgz",
-      "integrity": "sha512-BtfP1vCor8cWacovzzPFOoeW4kBQxzmhxGoOpt0v1SFvG+nJ0cWaVdJk7cky1ArTcFHHKNIxyo2LLr3oNkSuXA==",
-      "dev": true,
-      "requires": {
-        "@typescript-eslint/types": "5.28.0",
-        "eslint-visitor-keys": "^3.3.0"
-      }
-    },
-    "abbrev": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
-      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
-    },
-    "accepts": {
-      "version": "1.3.8",
-      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
-      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
-      "requires": {
-        "mime-types": "~2.1.34",
-        "negotiator": "0.6.3"
-      }
-    },
-    "acorn": {
-      "version": "8.7.1",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz",
-      "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==",
-      "dev": true
-    },
-    "acorn-jsx": {
-      "version": "5.3.2",
-      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
-      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
-      "dev": true
-    },
-    "adler-32": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
-      "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A=="
-    },
-    "agent-base": {
-      "version": "6.0.2",
-      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
-      "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
-      "requires": {
-        "debug": "4"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "4.3.3",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
-          "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
-          "requires": {
-            "ms": "2.1.2"
-          }
-        },
-        "ms": {
-          "version": "2.1.2",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
-        }
-      }
-    },
-    "ajv": {
-      "version": "6.12.6",
-      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
-      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
-      "dev": true,
-      "requires": {
-        "fast-deep-equal": "^3.1.1",
-        "fast-json-stable-stringify": "^2.0.0",
-        "json-schema-traverse": "^0.4.1",
-        "uri-js": "^4.2.2"
-      }
-    },
-    "ansi-align": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
-      "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
-      "dev": true,
-      "requires": {
-        "string-width": "^4.1.0"
-      }
-    },
-    "ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-    },
-    "ansi-styles": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "dev": true,
-      "requires": {
-        "color-convert": "^2.0.1"
-      }
-    },
-    "anymatch": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
-      "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
-      "dev": true,
-      "requires": {
-        "normalize-path": "^3.0.0",
-        "picomatch": "^2.0.4"
-      }
-    },
-    "append-field": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
-      "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
-    },
-    "aproba": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
-      "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
-    },
-    "are-we-there-yet": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
-      "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
-      "requires": {
-        "delegates": "^1.0.0",
-        "readable-stream": "^3.6.0"
-      },
-      "dependencies": {
-        "readable-stream": {
-          "version": "3.6.0",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
-          "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
-          "requires": {
-            "inherits": "^2.0.3",
-            "string_decoder": "^1.1.1",
-            "util-deprecate": "^1.0.1"
-          }
-        },
-        "safe-buffer": {
-          "version": "5.2.1",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
-          "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
-        },
-        "string_decoder": {
-          "version": "1.3.0",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
-          "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
-          "requires": {
-            "safe-buffer": "~5.2.0"
-          }
-        }
-      }
-    },
-    "argparse": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
-      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
-      "dev": true
-    },
-    "array-flatten": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
-      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
-    },
-    "array-union": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
-      "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
-      "dev": true
-    },
-    "asynckit": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
-    },
-    "axios": {
-      "version": "0.27.2",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
-      "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
-      "requires": {
-        "follow-redirects": "^1.14.9",
-        "form-data": "^4.0.0"
-      }
-    },
-    "balanced-match": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
-      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
-    },
-    "base64-js": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
-      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
-    },
-    "basic-auth": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
-      "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
-      "requires": {
-        "safe-buffer": "5.1.2"
-      }
-    },
-    "bcrypt": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz",
-      "integrity": "sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==",
-      "requires": {
-        "@mapbox/node-pre-gyp": "^1.0.0",
-        "node-addon-api": "^3.1.0"
-      }
-    },
-    "binary-extensions": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
-      "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
-      "dev": true
-    },
-    "body-parser": {
-      "version": "1.20.0",
-      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
-      "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==",
-      "requires": {
-        "bytes": "3.1.2",
-        "content-type": "~1.0.4",
-        "debug": "2.6.9",
-        "depd": "2.0.0",
-        "destroy": "1.2.0",
-        "http-errors": "2.0.0",
-        "iconv-lite": "0.4.24",
-        "on-finished": "2.4.1",
-        "qs": "6.10.3",
-        "raw-body": "2.5.1",
-        "type-is": "~1.6.18",
-        "unpipe": "1.0.0"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "2.6.9",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-          "requires": {
-            "ms": "2.0.0"
-          }
-        },
-        "ms": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
-        }
-      }
-    },
-    "boxen": {
-      "version": "5.1.2",
-      "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz",
-      "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==",
-      "dev": true,
-      "requires": {
-        "ansi-align": "^3.0.0",
-        "camelcase": "^6.2.0",
-        "chalk": "^4.1.0",
-        "cli-boxes": "^2.2.1",
-        "string-width": "^4.2.2",
-        "type-fest": "^0.20.2",
-        "widest-line": "^3.1.0",
-        "wrap-ansi": "^7.0.0"
-      }
-    },
-    "brace-expansion": {
-      "version": "1.1.11",
-      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
-      "requires": {
-        "balanced-match": "^1.0.0",
-        "concat-map": "0.0.1"
-      }
-    },
-    "braces": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
-      "dev": true,
-      "requires": {
-        "fill-range": "^7.0.1"
-      }
-    },
-    "bson": {
-      "version": "4.6.4",
-      "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.4.tgz",
-      "integrity": "sha512-TdQ3FzguAu5HKPPlr0kYQCyrYUYh8tFM+CMTpxjNzVzxeiJY00Rtuj3LXLHSgiGvmaWlZ8PE+4KyM2thqE38pQ==",
-      "requires": {
-        "buffer": "^5.6.0"
-      }
-    },
-    "buffer": {
-      "version": "5.7.1",
-      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
-      "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
-      "requires": {
-        "base64-js": "^1.3.1",
-        "ieee754": "^1.1.13"
-      }
-    },
-    "buffer-equal-constant-time": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
-      "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
-    },
-    "buffer-from": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
-      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
-    },
-    "busboy": {
-      "version": "1.6.0",
-      "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
-      "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
-      "requires": {
-        "streamsearch": "^1.1.0"
-      }
-    },
-    "bytes": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
-      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
-    },
-    "cacheable-request": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz",
-      "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==",
-      "dev": true,
-      "requires": {
-        "clone-response": "^1.0.2",
-        "get-stream": "^5.1.0",
-        "http-cache-semantics": "^4.0.0",
-        "keyv": "^3.0.0",
-        "lowercase-keys": "^2.0.0",
-        "normalize-url": "^4.1.0",
-        "responselike": "^1.0.2"
-      },
-      "dependencies": {
-        "get-stream": {
-          "version": "5.2.0",
-          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
-          "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
-          "dev": true,
-          "requires": {
-            "pump": "^3.0.0"
-          }
-        },
-        "lowercase-keys": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
-          "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
-          "dev": true
-        }
-      }
-    },
-    "call-bind": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
-      "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
-      "requires": {
-        "function-bind": "^1.1.1",
-        "get-intrinsic": "^1.0.2"
-      }
-    },
-    "callsites": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
-      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
-      "dev": true
-    },
-    "camelcase": {
-      "version": "6.3.0",
-      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
-      "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
-      "dev": true
-    },
-    "cfb": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
-      "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
-      "requires": {
-        "adler-32": "~1.3.0",
-        "crc-32": "~1.2.0"
-      }
-    },
-    "chalk": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-      "dev": true,
-      "requires": {
-        "ansi-styles": "^4.1.0",
-        "supports-color": "^7.1.0"
-      }
-    },
-    "chokidar": {
-      "version": "3.5.3",
-      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
-      "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
-      "dev": true,
-      "requires": {
-        "anymatch": "~3.1.2",
-        "braces": "~3.0.2",
-        "fsevents": "~2.3.2",
-        "glob-parent": "~5.1.2",
-        "is-binary-path": "~2.1.0",
-        "is-glob": "~4.0.1",
-        "normalize-path": "~3.0.0",
-        "readdirp": "~3.6.0"
-      },
-      "dependencies": {
-        "glob-parent": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
-          "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
-          "dev": true,
-          "requires": {
-            "is-glob": "^4.0.1"
-          }
-        }
-      }
-    },
-    "chownr": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
-      "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="
-    },
-    "ci-info": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
-      "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
-      "dev": true
-    },
-    "cli-boxes": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz",
-      "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==",
-      "dev": true
-    },
-    "clone-response": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
-      "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==",
-      "dev": true,
-      "requires": {
-        "mimic-response": "^1.0.0"
-      }
-    },
-    "codepage": {
-      "version": "1.15.0",
-      "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
-      "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA=="
-    },
-    "color-convert": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-      "dev": true,
-      "requires": {
-        "color-name": "~1.1.4"
-      }
-    },
-    "color-name": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-      "dev": true
-    },
-    "color-support": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
-      "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="
-    },
-    "combined-stream": {
-      "version": "1.0.8",
-      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
-      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
-      "requires": {
-        "delayed-stream": "~1.0.0"
-      }
-    },
-    "common-tags": {
-      "version": "1.8.2",
-      "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz",
-      "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==",
-      "dev": true
-    },
-    "concat-map": {
-      "version": "0.0.1",
-      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
-    },
-    "concat-stream": {
-      "version": "1.6.2",
-      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
-      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
-      "requires": {
-        "buffer-from": "^1.0.0",
-        "inherits": "^2.0.3",
-        "readable-stream": "^2.2.2",
-        "typedarray": "^0.0.6"
-      }
-    },
-    "configstore": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz",
-      "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==",
-      "dev": true,
-      "requires": {
-        "dot-prop": "^5.2.0",
-        "graceful-fs": "^4.1.2",
-        "make-dir": "^3.0.0",
-        "unique-string": "^2.0.0",
-        "write-file-atomic": "^3.0.0",
-        "xdg-basedir": "^4.0.0"
-      }
-    },
-    "console-control-strings": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
-      "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
-    },
-    "content-disposition": {
-      "version": "0.5.4",
-      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
-      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
-      "requires": {
-        "safe-buffer": "5.2.1"
-      },
-      "dependencies": {
-        "safe-buffer": {
-          "version": "5.2.1",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
-          "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
-        }
-      }
-    },
-    "content-type": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
-      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
-    },
-    "cookie": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
-      "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
-    },
-    "cookie-parser": {
-      "version": "1.4.6",
-      "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
-      "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
-      "requires": {
-        "cookie": "0.4.1",
-        "cookie-signature": "1.0.6"
-      }
-    },
-    "cookie-signature": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
-      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
-    },
-    "core-util-is": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
-      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
-    },
-    "cors": {
-      "version": "2.8.5",
-      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
-      "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
-      "requires": {
-        "object-assign": "^4",
-        "vary": "^1"
-      }
-    },
-    "crc-32": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
-      "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="
-    },
-    "cross-spawn": {
-      "version": "7.0.3",
-      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
-      "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
-      "dev": true,
-      "requires": {
-        "path-key": "^3.1.0",
-        "shebang-command": "^2.0.0",
-        "which": "^2.0.1"
-      }
-    },
-    "crypto": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
-      "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig=="
-    },
-    "crypto-random-string": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
-      "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
-      "dev": true
-    },
-    "debug": {
-      "version": "4.3.4",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
-      "requires": {
-        "ms": "2.1.2"
-      }
-    },
-    "decompress-response": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
-      "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==",
-      "dev": true,
-      "requires": {
-        "mimic-response": "^1.0.0"
-      }
-    },
-    "deep-extend": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
-      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
-      "dev": true
-    },
-    "deep-is": {
-      "version": "0.1.4",
-      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
-      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
-      "dev": true
-    },
-    "defer-to-connect": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz",
-      "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==",
-      "dev": true
-    },
-    "delayed-stream": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
-      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
-    },
-    "delegates": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
-      "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
-    },
-    "denque": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz",
-      "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ=="
-    },
-    "depd": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
-      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
-    },
-    "destroy": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
-      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
-    },
-    "detect-libc": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
-      "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
-    },
-    "dir-glob": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
-      "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
-      "dev": true,
-      "requires": {
-        "path-type": "^4.0.0"
-      }
-    },
-    "dlv": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
-      "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
-      "dev": true
-    },
-    "doctrine": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
-      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
-      "dev": true,
-      "requires": {
-        "esutils": "^2.0.2"
-      }
-    },
-    "dot-prop": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
-      "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
-      "dev": true,
-      "requires": {
-        "is-obj": "^2.0.0"
-      }
-    },
-    "dotenv": {
-      "version": "16.0.1",
-      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
-      "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ=="
-    },
-    "duplexer3": {
-      "version": "0.1.4",
-      "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
-      "integrity": "sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA==",
-      "dev": true
-    },
-    "ecdsa-sig-formatter": {
-      "version": "1.0.11",
-      "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
-      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
-      "requires": {
-        "safe-buffer": "^5.0.1"
-      }
-    },
-    "ee-first": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
-      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
-    },
-    "emoji-regex": {
-      "version": "8.0.0",
-      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
-    },
-    "encodeurl": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
-      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
-    },
-    "end-of-stream": {
-      "version": "1.4.4",
-      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
-      "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
-      "dev": true,
-      "requires": {
-        "once": "^1.4.0"
-      }
-    },
-    "escape-goat": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz",
-      "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==",
-      "dev": true
-    },
-    "escape-html": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
-      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
-    },
-    "escape-string-regexp": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
-      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
-      "dev": true
-    },
-    "eslint": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.17.0.tgz",
-      "integrity": "sha512-gq0m0BTJfci60Fz4nczYxNAlED+sMcihltndR8t9t1evnU/azx53x3t2UHXC/uRjcbvRw/XctpaNygSTcQD+Iw==",
-      "dev": true,
-      "requires": {
-        "@eslint/eslintrc": "^1.3.0",
-        "@humanwhocodes/config-array": "^0.9.2",
-        "ajv": "^6.10.0",
-        "chalk": "^4.0.0",
-        "cross-spawn": "^7.0.2",
-        "debug": "^4.3.2",
-        "doctrine": "^3.0.0",
-        "escape-string-regexp": "^4.0.0",
-        "eslint-scope": "^7.1.1",
-        "eslint-utils": "^3.0.0",
-        "eslint-visitor-keys": "^3.3.0",
-        "espree": "^9.3.2",
-        "esquery": "^1.4.0",
-        "esutils": "^2.0.2",
-        "fast-deep-equal": "^3.1.3",
-        "file-entry-cache": "^6.0.1",
-        "functional-red-black-tree": "^1.0.1",
-        "glob-parent": "^6.0.1",
-        "globals": "^13.15.0",
-        "ignore": "^5.2.0",
-        "import-fresh": "^3.0.0",
-        "imurmurhash": "^0.1.4",
-        "is-glob": "^4.0.0",
-        "js-yaml": "^4.1.0",
-        "json-stable-stringify-without-jsonify": "^1.0.1",
-        "levn": "^0.4.1",
-        "lodash.merge": "^4.6.2",
-        "minimatch": "^3.1.2",
-        "natural-compare": "^1.4.0",
-        "optionator": "^0.9.1",
-        "regexpp": "^3.2.0",
-        "strip-ansi": "^6.0.1",
-        "strip-json-comments": "^3.1.0",
-        "text-table": "^0.2.0",
-        "v8-compile-cache": "^2.0.3"
-      }
-    },
-    "eslint-scope": {
-      "version": "7.1.1",
-      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
-      "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
-      "dev": true,
-      "requires": {
-        "esrecurse": "^4.3.0",
-        "estraverse": "^5.2.0"
-      }
-    },
-    "eslint-utils": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
-      "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
-      "dev": true,
-      "requires": {
-        "eslint-visitor-keys": "^2.0.0"
-      },
-      "dependencies": {
-        "eslint-visitor-keys": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
-          "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
-          "dev": true
-        }
-      }
-    },
-    "eslint-visitor-keys": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
-      "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
-      "dev": true
-    },
-    "espree": {
-      "version": "9.3.2",
-      "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz",
-      "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==",
-      "dev": true,
-      "requires": {
-        "acorn": "^8.7.1",
-        "acorn-jsx": "^5.3.2",
-        "eslint-visitor-keys": "^3.3.0"
-      }
-    },
-    "esquery": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
-      "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
-      "dev": true,
-      "requires": {
-        "estraverse": "^5.1.0"
-      }
-    },
-    "esrecurse": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
-      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
-      "dev": true,
-      "requires": {
-        "estraverse": "^5.2.0"
-      }
-    },
-    "estraverse": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
-      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
-      "dev": true
-    },
-    "esutils": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
-      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
-      "dev": true
-    },
-    "etag": {
-      "version": "1.8.1",
-      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
-      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
-    },
-    "express": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz",
-      "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==",
-      "requires": {
-        "accepts": "~1.3.8",
-        "array-flatten": "1.1.1",
-        "body-parser": "1.20.0",
-        "content-disposition": "0.5.4",
-        "content-type": "~1.0.4",
-        "cookie": "0.5.0",
-        "cookie-signature": "1.0.6",
-        "debug": "2.6.9",
-        "depd": "2.0.0",
-        "encodeurl": "~1.0.2",
-        "escape-html": "~1.0.3",
-        "etag": "~1.8.1",
-        "finalhandler": "1.2.0",
-        "fresh": "0.5.2",
-        "http-errors": "2.0.0",
-        "merge-descriptors": "1.0.1",
-        "methods": "~1.1.2",
-        "on-finished": "2.4.1",
-        "parseurl": "~1.3.3",
-        "path-to-regexp": "0.1.7",
-        "proxy-addr": "~2.0.7",
-        "qs": "6.10.3",
-        "range-parser": "~1.2.1",
-        "safe-buffer": "5.2.1",
-        "send": "0.18.0",
-        "serve-static": "1.15.0",
-        "setprototypeof": "1.2.0",
-        "statuses": "2.0.1",
-        "type-is": "~1.6.18",
-        "utils-merge": "1.0.1",
-        "vary": "~1.1.2"
-      },
-      "dependencies": {
-        "cookie": {
-          "version": "0.5.0",
-          "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
-          "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
-        },
-        "debug": {
-          "version": "2.6.9",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-          "requires": {
-            "ms": "2.0.0"
-          }
-        },
-        "ms": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
-        },
-        "safe-buffer": {
-          "version": "5.2.1",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
-          "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
-        }
-      }
-    },
-    "fast-deep-equal": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
-      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
-      "dev": true
-    },
-    "fast-glob": {
-      "version": "3.2.11",
-      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
-      "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
-      "dev": true,
-      "requires": {
-        "@nodelib/fs.stat": "^2.0.2",
-        "@nodelib/fs.walk": "^1.2.3",
-        "glob-parent": "^5.1.2",
-        "merge2": "^1.3.0",
-        "micromatch": "^4.0.4"
-      },
-      "dependencies": {
-        "glob-parent": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
-          "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
-          "dev": true,
-          "requires": {
-            "is-glob": "^4.0.1"
-          }
-        }
-      }
-    },
-    "fast-json-stable-stringify": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
-      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
-      "dev": true
-    },
-    "fast-levenshtein": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
-      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
-      "dev": true
-    },
-    "fastest-validator": {
-      "version": "1.12.0",
-      "resolved": "https://registry.npmjs.org/fastest-validator/-/fastest-validator-1.12.0.tgz",
-      "integrity": "sha512-Qc7oCVO9hAPz5GUONmToIoa95YWzoe7SLsrjIXTfCFf6HFQXxxWePXe8D+Kp/XCrr5H/pMJwP2xprW07wYv/BQ=="
-    },
-    "fastq": {
-      "version": "1.13.0",
-      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
-      "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
-      "dev": true,
-      "requires": {
-        "reusify": "^1.0.4"
-      }
-    },
-    "file-entry-cache": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
-      "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
-      "dev": true,
-      "requires": {
-        "flat-cache": "^3.0.4"
-      }
-    },
-    "fill-range": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
-      "dev": true,
-      "requires": {
-        "to-regex-range": "^5.0.1"
-      }
-    },
-    "finalhandler": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
-      "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
-      "requires": {
-        "debug": "2.6.9",
-        "encodeurl": "~1.0.2",
-        "escape-html": "~1.0.3",
-        "on-finished": "2.4.1",
-        "parseurl": "~1.3.3",
-        "statuses": "2.0.1",
-        "unpipe": "~1.0.0"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "2.6.9",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-          "requires": {
-            "ms": "2.0.0"
-          }
-        },
-        "ms": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
-        }
-      }
-    },
-    "flat-cache": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
-      "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
-      "dev": true,
-      "requires": {
-        "flatted": "^3.1.0",
-        "rimraf": "^3.0.2"
-      }
-    },
-    "flatted": {
-      "version": "3.2.5",
-      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz",
-      "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
-      "dev": true
-    },
-    "follow-redirects": {
-      "version": "1.15.1",
-      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
-      "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
-    },
-    "form-data": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
-      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
-      "requires": {
-        "asynckit": "^0.4.0",
-        "combined-stream": "^1.0.8",
-        "mime-types": "^2.1.12"
-      }
-    },
-    "forwarded": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
-      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
-    },
-    "frac": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
-      "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA=="
-    },
-    "fresh": {
-      "version": "0.5.2",
-      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
-      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
-    },
-    "fs-minipass": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
-      "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
-      "requires": {
-        "minipass": "^3.0.0"
-      }
-    },
-    "fs.realpath": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
-    },
-    "fsevents": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
-      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
-      "dev": true,
-      "optional": true
-    },
-    "function-bind": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
-      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
-    },
-    "functional-red-black-tree": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
-      "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
-      "dev": true
-    },
-    "gauge": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
-      "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
-      "requires": {
-        "aproba": "^1.0.3 || ^2.0.0",
-        "color-support": "^1.1.2",
-        "console-control-strings": "^1.0.0",
-        "has-unicode": "^2.0.1",
-        "object-assign": "^4.1.1",
-        "signal-exit": "^3.0.0",
-        "string-width": "^4.2.3",
-        "strip-ansi": "^6.0.1",
-        "wide-align": "^1.1.2"
-      }
-    },
-    "get-intrinsic": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
-      "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==",
-      "requires": {
-        "function-bind": "^1.1.1",
-        "has": "^1.0.3",
-        "has-symbols": "^1.0.3"
-      }
-    },
-    "get-stream": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
-      "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
-      "dev": true,
-      "requires": {
-        "pump": "^3.0.0"
-      }
-    },
-    "glob": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
-      "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
-      "requires": {
-        "fs.realpath": "^1.0.0",
-        "inflight": "^1.0.4",
-        "inherits": "2",
-        "minimatch": "^3.0.4",
-        "once": "^1.3.0",
-        "path-is-absolute": "^1.0.0"
-      }
-    },
-    "glob-parent": {
-      "version": "6.0.2",
-      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
-      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
-      "dev": true,
-      "requires": {
-        "is-glob": "^4.0.3"
-      }
-    },
-    "global-dirs": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz",
-      "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==",
-      "dev": true,
-      "requires": {
-        "ini": "2.0.0"
-      }
-    },
-    "globals": {
-      "version": "13.15.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz",
-      "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==",
-      "dev": true,
-      "requires": {
-        "type-fest": "^0.20.2"
-      }
-    },
-    "globby": {
-      "version": "11.1.0",
-      "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
-      "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
-      "dev": true,
-      "requires": {
-        "array-union": "^2.1.0",
-        "dir-glob": "^3.0.1",
-        "fast-glob": "^3.2.9",
-        "ignore": "^5.2.0",
-        "merge2": "^1.4.1",
-        "slash": "^3.0.0"
-      }
-    },
-    "got": {
-      "version": "9.6.0",
-      "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
-      "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==",
-      "dev": true,
-      "requires": {
-        "@sindresorhus/is": "^0.14.0",
-        "@szmarczak/http-timer": "^1.1.2",
-        "cacheable-request": "^6.0.0",
-        "decompress-response": "^3.3.0",
-        "duplexer3": "^0.1.4",
-        "get-stream": "^4.1.0",
-        "lowercase-keys": "^1.0.1",
-        "mimic-response": "^1.0.1",
-        "p-cancelable": "^1.0.0",
-        "to-readable-stream": "^1.0.0",
-        "url-parse-lax": "^3.0.0"
-      }
-    },
-    "graceful-fs": {
-      "version": "4.2.10",
-      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
-      "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
-      "dev": true
-    },
-    "has": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
-      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
-      "requires": {
-        "function-bind": "^1.1.1"
-      }
-    },
-    "has-ansi": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
-      "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==",
-      "dev": true,
-      "requires": {
-        "ansi-regex": "^2.0.0"
-      },
-      "dependencies": {
-        "ansi-regex": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-          "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
-          "dev": true
-        }
-      }
-    },
-    "has-flag": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-      "dev": true
-    },
-    "has-symbols": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
-      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
-    },
-    "has-unicode": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
-      "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
-    },
-    "has-yarn": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz",
-      "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==",
-      "dev": true
-    },
-    "http-cache-semantics": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
-      "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==",
-      "dev": true
-    },
-    "http-errors": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
-      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
-      "requires": {
-        "depd": "2.0.0",
-        "inherits": "2.0.4",
-        "setprototypeof": "1.2.0",
-        "statuses": "2.0.1",
-        "toidentifier": "1.0.1"
-      },
-      "dependencies": {
-        "inherits": {
-          "version": "2.0.4",
-          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
-          "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
-        }
-      }
-    },
-    "https-proxy-agent": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
-      "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
-      "requires": {
-        "agent-base": "6",
-        "debug": "4"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "4.3.3",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
-          "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
-          "requires": {
-            "ms": "2.1.2"
-          }
-        },
-        "ms": {
-          "version": "2.1.2",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
-        }
-      }
-    },
-    "iconv-lite": {
-      "version": "0.4.24",
-      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
-      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
-      "requires": {
-        "safer-buffer": ">= 2.1.2 < 3"
-      }
-    },
-    "ieee754": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
-      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
-    },
-    "ignore": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
-      "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
-      "dev": true
-    },
-    "ignore-by-default": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
-      "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
-      "dev": true
-    },
-    "import-fresh": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
-      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
-      "dev": true,
-      "requires": {
-        "parent-module": "^1.0.0",
-        "resolve-from": "^4.0.0"
-      }
-    },
-    "import-lazy": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
-      "integrity": "sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==",
-      "dev": true
-    },
-    "imurmurhash": {
-      "version": "0.1.4",
-      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
-      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
-      "dev": true
-    },
-    "indent-string": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
-      "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
-      "dev": true
-    },
-    "inflight": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
-      "requires": {
-        "once": "^1.3.0",
-        "wrappy": "1"
-      }
-    },
-    "inherits": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
-    },
-    "ini": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz",
-      "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==",
-      "dev": true
-    },
-    "ip": {
-      "version": "1.1.8",
-      "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
-      "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg=="
-    },
-    "ipaddr.js": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
-      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
-    },
-    "is-binary-path": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
-      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
-      "dev": true,
-      "requires": {
-        "binary-extensions": "^2.0.0"
-      }
-    },
-    "is-ci": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
-      "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
-      "dev": true,
-      "requires": {
-        "ci-info": "^2.0.0"
-      }
-    },
-    "is-extglob": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
-      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
-      "dev": true
-    },
-    "is-fullwidth-code-point": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
-      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
-    },
-    "is-glob": {
-      "version": "4.0.3",
-      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
-      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
-      "dev": true,
-      "requires": {
-        "is-extglob": "^2.1.1"
-      }
-    },
-    "is-installed-globally": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz",
-      "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==",
-      "dev": true,
-      "requires": {
-        "global-dirs": "^3.0.0",
-        "is-path-inside": "^3.0.2"
-      }
-    },
-    "is-npm": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz",
-      "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==",
-      "dev": true
-    },
-    "is-number": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
-      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
-      "dev": true
-    },
-    "is-obj": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
-      "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
-      "dev": true
-    },
-    "is-path-inside": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
-      "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
-      "dev": true
-    },
-    "is-typedarray": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
-      "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
-      "dev": true
-    },
-    "is-yarn-global": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz",
-      "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==",
-      "dev": true
-    },
-    "isarray": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
-    },
-    "isexe": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
-      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
-      "dev": true
-    },
-    "js-yaml": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
-      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
-      "dev": true,
-      "requires": {
-        "argparse": "^2.0.1"
-      }
-    },
-    "json-buffer": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
-      "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==",
-      "dev": true
-    },
-    "json-schema-traverse": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
-      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
-      "dev": true
-    },
-    "json-stable-stringify-without-jsonify": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
-      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
-      "dev": true
-    },
-    "jsonwebtoken": {
-      "version": "8.5.1",
-      "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
-      "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
-      "requires": {
-        "jws": "^3.2.2",
-        "lodash.includes": "^4.3.0",
-        "lodash.isboolean": "^3.0.3",
-        "lodash.isinteger": "^4.0.4",
-        "lodash.isnumber": "^3.0.3",
-        "lodash.isplainobject": "^4.0.6",
-        "lodash.isstring": "^4.0.1",
-        "lodash.once": "^4.0.0",
-        "ms": "^2.1.1",
-        "semver": "^5.6.0"
-      },
-      "dependencies": {
-        "ms": {
-          "version": "2.1.3",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
-          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
-        }
-      }
-    },
-    "jwa": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
-      "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
-      "requires": {
-        "buffer-equal-constant-time": "1.0.1",
-        "ecdsa-sig-formatter": "1.0.11",
-        "safe-buffer": "^5.0.1"
-      }
-    },
-    "jws": {
-      "version": "3.2.2",
-      "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
-      "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
-      "requires": {
-        "jwa": "^1.4.1",
-        "safe-buffer": "^5.0.1"
-      }
-    },
-    "kareem": {
-      "version": "2.3.5",
-      "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.5.tgz",
-      "integrity": "sha512-qxCyQtp3ioawkiRNQr/v8xw9KIviMSSNmy+63Wubj7KmMn3g7noRXIZB4vPCAP+ETi2SR8eH6CvmlKZuGpoHOg=="
-    },
-    "keyv": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
-      "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==",
-      "dev": true,
-      "requires": {
-        "json-buffer": "3.0.0"
-      }
-    },
-    "latest-version": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz",
-      "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==",
-      "dev": true,
-      "requires": {
-        "package-json": "^6.3.0"
-      }
-    },
-    "levn": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
-      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
-      "dev": true,
-      "requires": {
-        "prelude-ls": "^1.2.1",
-        "type-check": "~0.4.0"
-      }
-    },
-    "lodash": {
-      "version": "4.17.21",
-      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
-      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
-      "dev": true
-    },
-    "lodash.includes": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
-      "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
-    },
-    "lodash.isboolean": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
-      "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
-    },
-    "lodash.isinteger": {
-      "version": "4.0.4",
-      "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
-      "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
-    },
-    "lodash.isnumber": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
-      "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
-    },
-    "lodash.isplainobject": {
-      "version": "4.0.6",
-      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
-      "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
-    },
-    "lodash.isstring": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
-      "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
-    },
-    "lodash.merge": {
-      "version": "4.6.2",
-      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
-      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
-      "dev": true
-    },
-    "lodash.once": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
-      "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
-    },
-    "loglevel": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz",
-      "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==",
-      "dev": true
-    },
-    "loglevel-colored-level-prefix": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz",
-      "integrity": "sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA==",
-      "dev": true,
-      "requires": {
-        "chalk": "^1.1.3",
-        "loglevel": "^1.4.1"
-      },
-      "dependencies": {
-        "ansi-regex": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-          "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
-          "dev": true
-        },
-        "ansi-styles": {
-          "version": "2.2.1",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
-          "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
-          "dev": true
-        },
-        "chalk": {
-          "version": "1.1.3",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-          "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
-          "dev": true,
-          "requires": {
-            "ansi-styles": "^2.2.1",
-            "escape-string-regexp": "^1.0.2",
-            "has-ansi": "^2.0.0",
-            "strip-ansi": "^3.0.0",
-            "supports-color": "^2.0.0"
-          }
-        },
-        "escape-string-regexp": {
-          "version": "1.0.5",
-          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-          "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
-          "dev": true
-        },
-        "strip-ansi": {
-          "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-          "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
-          "dev": true,
-          "requires": {
-            "ansi-regex": "^2.0.0"
-          }
-        },
-        "supports-color": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
-          "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
-          "dev": true
-        }
-      }
-    },
-    "lowercase-keys": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
-      "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
-      "dev": true
-    },
-    "lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "requires": {
-        "yallist": "^4.0.0"
-      }
-    },
-    "make-dir": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
-      "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
-      "requires": {
-        "semver": "^6.0.0"
-      },
-      "dependencies": {
-        "semver": {
-          "version": "6.3.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
-        }
-      }
-    },
-    "media-typer": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
-      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
-    },
-    "memory-pager": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
-      "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
-      "optional": true
-    },
-    "merge-descriptors": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
-      "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
-    },
-    "merge2": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
-      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
-      "dev": true
-    },
-    "methods": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
-      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
-    },
-    "micromatch": {
-      "version": "4.0.5",
-      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
-      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
-      "dev": true,
-      "requires": {
-        "braces": "^3.0.2",
-        "picomatch": "^2.3.1"
-      }
-    },
-    "mime": {
-      "version": "1.6.0",
-      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
-      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
-    },
-    "mime-db": {
-      "version": "1.52.0",
-      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
-      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
-    },
-    "mime-types": {
-      "version": "2.1.35",
-      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
-      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
-      "requires": {
-        "mime-db": "1.52.0"
-      }
-    },
-    "mimic-response": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
-      "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
-      "dev": true
-    },
-    "minimatch": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
-      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
-      "requires": {
-        "brace-expansion": "^1.1.7"
-      }
-    },
-    "minimist": {
-      "version": "1.2.6",
-      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
-      "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
-    },
-    "minipass": {
-      "version": "3.1.6",
-      "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz",
-      "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==",
-      "requires": {
-        "yallist": "^4.0.0"
-      }
-    },
-    "minizlib": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
-      "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
-      "requires": {
-        "minipass": "^3.0.0",
-        "yallist": "^4.0.0"
-      }
-    },
-    "mkdirp": {
-      "version": "0.5.6",
-      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
-      "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
-      "requires": {
-        "minimist": "^1.2.6"
-      }
-    },
-    "moment": {
-      "version": "2.29.3",
-      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz",
-      "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw=="
-    },
-    "mongodb": {
-      "version": "4.5.0",
-      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.5.0.tgz",
-      "integrity": "sha512-A2l8MjEpKojnhbCM0MK3+UOGUSGvTNNSv7AkP1fsT7tkambrkkqN/5F2y+PhzsV0Nbv58u04TETpkaSEdI2zKA==",
-      "requires": {
-        "bson": "^4.6.2",
-        "denque": "^2.0.1",
-        "mongodb-connection-string-url": "^2.5.2",
-        "saslprep": "^1.0.3",
-        "socks": "^2.6.2"
-      }
-    },
-    "mongodb-connection-string-url": {
-      "version": "2.5.2",
-      "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.2.tgz",
-      "integrity": "sha512-tWDyIG8cQlI5k3skB6ywaEA5F9f5OntrKKsT/Lteub2zgwSUlhqEN2inGgBTm8bpYJf8QYBdA/5naz65XDpczA==",
-      "requires": {
-        "@types/whatwg-url": "^8.2.1",
-        "whatwg-url": "^11.0.0"
-      },
-      "dependencies": {
-        "tr46": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
-          "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
-          "requires": {
-            "punycode": "^2.1.1"
-          }
-        },
-        "webidl-conversions": {
-          "version": "7.0.0",
-          "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
-          "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
-        },
-        "whatwg-url": {
-          "version": "11.0.0",
-          "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
-          "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
-          "requires": {
-            "tr46": "^3.0.0",
-            "webidl-conversions": "^7.0.0"
-          }
-        }
-      }
-    },
-    "mongoose": {
-      "version": "6.3.8",
-      "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.3.8.tgz",
-      "integrity": "sha512-TPIm61/DR2Go+aDXD5HM6vwMvl4dEOFos1oTT4yPT8qJpcTugxWXf5J2Vp+0vzqDETfDMtN/gBhPCzFdFJx2bg==",
-      "requires": {
-        "bson": "^4.6.2",
-        "kareem": "2.3.5",
-        "mongodb": "4.5.0",
-        "mpath": "0.9.0",
-        "mquery": "4.0.3",
-        "ms": "2.1.3",
-        "sift": "16.0.0"
-      },
-      "dependencies": {
-        "ms": {
-          "version": "2.1.3",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
-          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
-        }
-      }
-    },
-    "morgan": {
-      "version": "1.10.0",
-      "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
-      "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
-      "requires": {
-        "basic-auth": "~2.0.1",
-        "debug": "2.6.9",
-        "depd": "~2.0.0",
-        "on-finished": "~2.3.0",
-        "on-headers": "~1.0.2"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "2.6.9",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-          "requires": {
-            "ms": "2.0.0"
-          }
-        },
-        "ms": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
-        },
-        "on-finished": {
-          "version": "2.3.0",
-          "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
-          "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
-          "requires": {
-            "ee-first": "1.1.1"
-          }
-        }
-      }
-    },
-    "mpath": {
-      "version": "0.9.0",
-      "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
-      "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew=="
-    },
-    "mquery": {
-      "version": "4.0.3",
-      "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz",
-      "integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==",
-      "requires": {
-        "debug": "4.x"
-      }
-    },
-    "ms": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
-    },
-    "multer": {
-      "version": "1.4.5-lts.1",
-      "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
-      "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
-      "requires": {
-        "append-field": "^1.0.0",
-        "busboy": "^1.0.0",
-        "concat-stream": "^1.5.2",
-        "mkdirp": "^0.5.4",
-        "object-assign": "^4.1.1",
-        "type-is": "^1.6.4",
-        "xtend": "^4.0.0"
-      }
-    },
-    "natural-compare": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
-      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
-      "dev": true
-    },
-    "negotiator": {
-      "version": "0.6.3",
-      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
-      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
-    },
-    "node-addon-api": {
-      "version": "3.2.1",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz",
-      "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A=="
-    },
-    "node-fetch": {
-      "version": "2.6.7",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
-      "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
-      "requires": {
-        "whatwg-url": "^5.0.0"
-      }
-    },
-    "nodemon": {
-      "version": "2.0.16",
-      "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.16.tgz",
-      "integrity": "sha512-zsrcaOfTWRuUzBn3P44RDliLlp263Z/76FPoHFr3cFFkOz0lTPAcIw8dCzfdVIx/t3AtDYCZRCDkoCojJqaG3w==",
-      "dev": true,
-      "requires": {
-        "chokidar": "^3.5.2",
-        "debug": "^3.2.7",
-        "ignore-by-default": "^1.0.1",
-        "minimatch": "^3.0.4",
-        "pstree.remy": "^1.1.8",
-        "semver": "^5.7.1",
-        "supports-color": "^5.5.0",
-        "touch": "^3.1.0",
-        "undefsafe": "^2.0.5",
-        "update-notifier": "^5.1.0"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "3.2.7",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
-          "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
-          "dev": true,
-          "requires": {
-            "ms": "^2.1.1"
-          }
-        },
-        "has-flag": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
-          "dev": true
-        },
-        "supports-color": {
-          "version": "5.5.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
-          "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
-          "dev": true,
-          "requires": {
-            "has-flag": "^3.0.0"
-          }
-        }
-      }
-    },
-    "nopt": {
-      "version": "1.0.10",
-      "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
-      "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
-      "dev": true,
-      "requires": {
-        "abbrev": "1"
-      }
-    },
-    "normalize-path": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
-      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
-      "dev": true
-    },
-    "normalize-url": {
-      "version": "4.5.1",
-      "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz",
-      "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==",
-      "dev": true
-    },
-    "npmlog": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
-      "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
-      "requires": {
-        "are-we-there-yet": "^2.0.0",
-        "console-control-strings": "^1.1.0",
-        "gauge": "^3.0.0",
-        "set-blocking": "^2.0.0"
-      }
-    },
-    "object-assign": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
-    },
-    "object-inspect": {
-      "version": "1.12.2",
-      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
-      "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ=="
-    },
-    "on-finished": {
-      "version": "2.4.1",
-      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
-      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
-      "requires": {
-        "ee-first": "1.1.1"
-      }
-    },
-    "on-headers": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
-      "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
-    },
-    "once": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
-      "requires": {
-        "wrappy": "1"
-      }
-    },
-    "optionator": {
-      "version": "0.9.1",
-      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
-      "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
-      "dev": true,
-      "requires": {
-        "deep-is": "^0.1.3",
-        "fast-levenshtein": "^2.0.6",
-        "levn": "^0.4.1",
-        "prelude-ls": "^1.2.1",
-        "type-check": "^0.4.0",
-        "word-wrap": "^1.2.3"
-      }
-    },
-    "p-cancelable": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz",
-      "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==",
-      "dev": true
-    },
-    "package-json": {
-      "version": "6.5.0",
-      "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz",
-      "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==",
-      "dev": true,
-      "requires": {
-        "got": "^9.6.0",
-        "registry-auth-token": "^4.0.0",
-        "registry-url": "^5.0.0",
-        "semver": "^6.2.0"
-      },
-      "dependencies": {
-        "semver": {
-          "version": "6.3.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
-          "dev": true
-        }
-      }
-    },
-    "parent-module": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
-      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
-      "dev": true,
-      "requires": {
-        "callsites": "^3.0.0"
-      }
-    },
-    "parseurl": {
-      "version": "1.3.3",
-      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
-      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
-    },
-    "path-is-absolute": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
-    },
-    "path-key": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
-      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
-      "dev": true
-    },
-    "path-to-regexp": {
-      "version": "0.1.7",
-      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
-      "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
-    },
-    "path-type": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
-      "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
-      "dev": true
-    },
-    "picomatch": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
-      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
-      "dev": true
-    },
-    "prelude-ls": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
-      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
-      "dev": true
-    },
-    "prepend-http": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
-      "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==",
-      "dev": true
-    },
-    "prettier": {
-      "version": "2.7.0",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.0.tgz",
-      "integrity": "sha512-nwoX4GMFgxoPC6diHvSwmK/4yU8FFH3V8XWtLQrbj4IBsK2pkYhG4kf/ljF/haaZ/aii+wNJqISrCDPgxGWDVQ==",
-      "dev": true
-    },
-    "prettier-eslint": {
-      "version": "15.0.1",
-      "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-15.0.1.tgz",
-      "integrity": "sha512-mGOWVHixSvpZWARqSDXbdtTL54mMBxc5oQYQ6RAqy8jecuNJBgN3t9E5a81G66F8x8fsKNiR1HWaBV66MJDOpg==",
-      "dev": true,
-      "requires": {
-        "@types/eslint": "^8.4.2",
-        "@types/prettier": "^2.6.0",
-        "@typescript-eslint/parser": "^5.10.0",
-        "common-tags": "^1.4.0",
-        "dlv": "^1.1.0",
-        "eslint": "^8.7.0",
-        "indent-string": "^4.0.0",
-        "lodash.merge": "^4.6.0",
-        "loglevel-colored-level-prefix": "^1.0.0",
-        "prettier": "^2.5.1",
-        "pretty-format": "^23.0.1",
-        "require-relative": "^0.8.7",
-        "typescript": "^4.5.4",
-        "vue-eslint-parser": "^8.0.1"
-      }
-    },
-    "pretty-format": {
-      "version": "23.6.0",
-      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz",
-      "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==",
-      "dev": true,
-      "requires": {
-        "ansi-regex": "^3.0.0",
-        "ansi-styles": "^3.2.0"
-      },
-      "dependencies": {
-        "ansi-regex": {
-          "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
-          "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
-          "dev": true
-        },
-        "ansi-styles": {
-          "version": "3.2.1",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
-          "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
-          "dev": true,
-          "requires": {
-            "color-convert": "^1.9.0"
-          }
-        },
-        "color-convert": {
-          "version": "1.9.3",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
-          "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
-          "dev": true,
-          "requires": {
-            "color-name": "1.1.3"
-          }
-        },
-        "color-name": {
-          "version": "1.1.3",
-          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
-          "dev": true
-        }
-      }
-    },
-    "process-nextick-args": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
-      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
-    },
-    "proxy-addr": {
-      "version": "2.0.7",
-      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
-      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
-      "requires": {
-        "forwarded": "0.2.0",
-        "ipaddr.js": "1.9.1"
-      }
-    },
-    "pstree.remy": {
-      "version": "1.1.8",
-      "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
-      "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
-      "dev": true
-    },
-    "pump": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
-      "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
-      "dev": true,
-      "requires": {
-        "end-of-stream": "^1.1.0",
-        "once": "^1.3.1"
-      }
-    },
-    "punycode": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
-      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
-    },
-    "pupa": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz",
-      "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==",
-      "dev": true,
-      "requires": {
-        "escape-goat": "^2.0.0"
-      }
-    },
-    "qs": {
-      "version": "6.10.3",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
-      "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
-      "requires": {
-        "side-channel": "^1.0.4"
-      }
-    },
-    "queue-microtask": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
-      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
-      "dev": true
-    },
-    "range-parser": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
-      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
-    },
-    "raw-body": {
-      "version": "2.5.1",
-      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
-      "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
-      "requires": {
-        "bytes": "3.1.2",
-        "http-errors": "2.0.0",
-        "iconv-lite": "0.4.24",
-        "unpipe": "1.0.0"
-      }
-    },
-    "rc": {
-      "version": "1.2.8",
-      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
-      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
-      "dev": true,
-      "requires": {
-        "deep-extend": "^0.6.0",
-        "ini": "~1.3.0",
-        "minimist": "^1.2.0",
-        "strip-json-comments": "~2.0.1"
-      },
-      "dependencies": {
-        "ini": {
-          "version": "1.3.8",
-          "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
-          "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
-          "dev": true
-        },
-        "strip-json-comments": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
-          "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
-          "dev": true
-        }
-      }
-    },
-    "readable-stream": {
-      "version": "2.3.7",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
-      "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
-      "requires": {
-        "core-util-is": "~1.0.0",
-        "inherits": "~2.0.3",
-        "isarray": "~1.0.0",
-        "process-nextick-args": "~2.0.0",
-        "safe-buffer": "~5.1.1",
-        "string_decoder": "~1.1.1",
-        "util-deprecate": "~1.0.1"
-      }
-    },
-    "readdirp": {
-      "version": "3.6.0",
-      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
-      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
-      "dev": true,
-      "requires": {
-        "picomatch": "^2.2.1"
-      }
-    },
-    "regexpp": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
-      "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
-      "dev": true
-    },
-    "registry-auth-token": {
-      "version": "4.2.1",
-      "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz",
-      "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==",
-      "dev": true,
-      "requires": {
-        "rc": "^1.2.8"
-      }
-    },
-    "registry-url": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz",
-      "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==",
-      "dev": true,
-      "requires": {
-        "rc": "^1.2.8"
-      }
-    },
-    "require-relative": {
-      "version": "0.8.7",
-      "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
-      "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==",
-      "dev": true
-    },
-    "resolve-from": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
-      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
-      "dev": true
-    },
-    "responselike": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz",
-      "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==",
-      "dev": true,
-      "requires": {
-        "lowercase-keys": "^1.0.0"
-      }
-    },
-    "reusify": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
-      "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
-      "dev": true
-    },
-    "rimraf": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
-      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
-      "requires": {
-        "glob": "^7.1.3"
-      }
-    },
-    "run-parallel": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
-      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
-      "dev": true,
-      "requires": {
-        "queue-microtask": "^1.2.2"
-      }
-    },
-    "safe-buffer": {
-      "version": "5.1.2",
-      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
-    },
-    "safer-buffer": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
-      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
-    },
-    "saslprep": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
-      "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
-      "optional": true,
-      "requires": {
-        "sparse-bitfield": "^3.0.3"
-      }
-    },
-    "semver": {
-      "version": "5.7.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
-      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
-    },
-    "semver-diff": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz",
-      "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==",
-      "dev": true,
-      "requires": {
-        "semver": "^6.3.0"
-      },
-      "dependencies": {
-        "semver": {
-          "version": "6.3.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
-          "dev": true
-        }
-      }
-    },
-    "send": {
-      "version": "0.18.0",
-      "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
-      "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
-      "requires": {
-        "debug": "2.6.9",
-        "depd": "2.0.0",
-        "destroy": "1.2.0",
-        "encodeurl": "~1.0.2",
-        "escape-html": "~1.0.3",
-        "etag": "~1.8.1",
-        "fresh": "0.5.2",
-        "http-errors": "2.0.0",
-        "mime": "1.6.0",
-        "ms": "2.1.3",
-        "on-finished": "2.4.1",
-        "range-parser": "~1.2.1",
-        "statuses": "2.0.1"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "2.6.9",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-          "requires": {
-            "ms": "2.0.0"
-          },
-          "dependencies": {
-            "ms": {
-              "version": "2.0.0",
-              "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-              "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
-            }
-          }
-        },
-        "ms": {
-          "version": "2.1.3",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
-          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
-        }
-      }
-    },
-    "serve-static": {
-      "version": "1.15.0",
-      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
-      "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
-      "requires": {
-        "encodeurl": "~1.0.2",
-        "escape-html": "~1.0.3",
-        "parseurl": "~1.3.3",
-        "send": "0.18.0"
-      }
-    },
-    "set-blocking": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
-      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
-    },
-    "setprototypeof": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
-      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
-    },
-    "shebang-command": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
-      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
-      "dev": true,
-      "requires": {
-        "shebang-regex": "^3.0.0"
-      }
-    },
-    "shebang-regex": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
-      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
-      "dev": true
-    },
-    "side-channel": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
-      "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
-      "requires": {
-        "call-bind": "^1.0.0",
-        "get-intrinsic": "^1.0.2",
-        "object-inspect": "^1.9.0"
-      }
-    },
-    "sift": {
-      "version": "16.0.0",
-      "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz",
-      "integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ=="
-    },
-    "signal-exit": {
-      "version": "3.0.7",
-      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
-      "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
-    },
-    "slash": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
-      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
-      "dev": true
-    },
-    "smart-buffer": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
-      "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="
-    },
-    "socks": {
-      "version": "2.6.2",
-      "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz",
-      "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==",
-      "requires": {
-        "ip": "^1.1.5",
-        "smart-buffer": "^4.2.0"
-      }
-    },
-    "sparse-bitfield": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
-      "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
-      "optional": true,
-      "requires": {
-        "memory-pager": "^1.0.2"
-      }
-    },
-    "ssf": {
-      "version": "0.11.2",
-      "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
-      "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
-      "requires": {
-        "frac": "~1.1.2"
-      }
-    },
-    "statuses": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
-      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
-    },
-    "streamsearch": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
-      "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="
-    },
-    "string-width": {
-      "version": "4.2.3",
-      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-      "requires": {
-        "emoji-regex": "^8.0.0",
-        "is-fullwidth-code-point": "^3.0.0",
-        "strip-ansi": "^6.0.1"
-      }
-    },
-    "string_decoder": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-      "requires": {
-        "safe-buffer": "~5.1.0"
-      }
-    },
-    "strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "requires": {
-        "ansi-regex": "^5.0.1"
-      }
-    },
-    "strip-json-comments": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
-      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
-      "dev": true
-    },
-    "supports-color": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-      "dev": true,
-      "requires": {
-        "has-flag": "^4.0.0"
-      }
-    },
-    "tar": {
-      "version": "6.1.11",
-      "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz",
-      "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==",
-      "requires": {
-        "chownr": "^2.0.0",
-        "fs-minipass": "^2.0.0",
-        "minipass": "^3.0.0",
-        "minizlib": "^2.1.1",
-        "mkdirp": "^1.0.3",
-        "yallist": "^4.0.0"
-      },
-      "dependencies": {
-        "mkdirp": {
-          "version": "1.0.4",
-          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
-          "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
-        }
-      }
-    },
-    "text-table": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
-      "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
-      "dev": true
-    },
-    "to-readable-stream": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz",
-      "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==",
-      "dev": true
-    },
-    "to-regex-range": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
-      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
-      "dev": true,
-      "requires": {
-        "is-number": "^7.0.0"
-      }
-    },
-    "toidentifier": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
-      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
-    },
-    "touch": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
-      "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
-      "dev": true,
-      "requires": {
-        "nopt": "~1.0.10"
-      }
-    },
-    "tr46": {
-      "version": "0.0.3",
-      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
-      "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
-    },
-    "tslib": {
-      "version": "1.14.1",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
-      "dev": true
-    },
-    "tsutils": {
-      "version": "3.21.0",
-      "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
-      "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
-      "dev": true,
-      "requires": {
-        "tslib": "^1.8.1"
-      }
-    },
-    "type-check": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
-      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
-      "dev": true,
-      "requires": {
-        "prelude-ls": "^1.2.1"
-      }
-    },
-    "type-fest": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
-      "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
-      "dev": true
-    },
-    "type-is": {
-      "version": "1.6.18",
-      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
-      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
-      "requires": {
-        "media-typer": "0.3.0",
-        "mime-types": "~2.1.24"
-      }
-    },
-    "typedarray": {
-      "version": "0.0.6",
-      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
-      "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
-    },
-    "typedarray-to-buffer": {
-      "version": "3.1.5",
-      "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
-      "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
-      "dev": true,
-      "requires": {
-        "is-typedarray": "^1.0.0"
-      }
-    },
-    "typescript": {
-      "version": "4.7.3",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz",
-      "integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==",
-      "dev": true
-    },
-    "undefsafe": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
-      "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
-      "dev": true
-    },
-    "unique-string": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",
-      "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==",
-      "dev": true,
-      "requires": {
-        "crypto-random-string": "^2.0.0"
-      }
-    },
-    "unpipe": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
-      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
-    },
-    "update-notifier": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz",
-      "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==",
-      "dev": true,
-      "requires": {
-        "boxen": "^5.0.0",
-        "chalk": "^4.1.0",
-        "configstore": "^5.0.1",
-        "has-yarn": "^2.1.0",
-        "import-lazy": "^2.1.0",
-        "is-ci": "^2.0.0",
-        "is-installed-globally": "^0.4.0",
-        "is-npm": "^5.0.0",
-        "is-yarn-global": "^0.3.0",
-        "latest-version": "^5.1.0",
-        "pupa": "^2.1.1",
-        "semver": "^7.3.4",
-        "semver-diff": "^3.1.1",
-        "xdg-basedir": "^4.0.0"
-      },
-      "dependencies": {
-        "semver": {
-          "version": "7.3.7",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
-          "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
-          "dev": true,
-          "requires": {
-            "lru-cache": "^6.0.0"
-          }
-        }
-      }
-    },
-    "uri-js": {
-      "version": "4.4.1",
-      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
-      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
-      "dev": true,
-      "requires": {
-        "punycode": "^2.1.0"
-      }
-    },
-    "url-parse-lax": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
-      "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==",
-      "dev": true,
-      "requires": {
-        "prepend-http": "^2.0.0"
-      }
-    },
-    "util-deprecate": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
-      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
-    },
-    "utils-merge": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
-      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
-    },
-    "v8-compile-cache": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
-      "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
-      "dev": true
-    },
-    "vary": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
-      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
-    },
-    "vue-eslint-parser": {
-      "version": "8.3.0",
-      "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz",
-      "integrity": "sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g==",
-      "dev": true,
-      "requires": {
-        "debug": "^4.3.2",
-        "eslint-scope": "^7.0.0",
-        "eslint-visitor-keys": "^3.1.0",
-        "espree": "^9.0.0",
-        "esquery": "^1.4.0",
-        "lodash": "^4.17.21",
-        "semver": "^7.3.5"
-      },
-      "dependencies": {
-        "semver": {
-          "version": "7.3.7",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
-          "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
-          "dev": true,
-          "requires": {
-            "lru-cache": "^6.0.0"
-          }
-        }
-      }
-    },
-    "webidl-conversions": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
-      "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
-    },
-    "whatwg-url": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
-      "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
-      "requires": {
-        "tr46": "~0.0.3",
-        "webidl-conversions": "^3.0.0"
-      }
-    },
-    "which": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
-      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
-      "dev": true,
-      "requires": {
-        "isexe": "^2.0.0"
-      }
-    },
-    "wide-align": {
-      "version": "1.1.5",
-      "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
-      "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
-      "requires": {
-        "string-width": "^1.0.2 || 2 || 3 || 4"
-      }
-    },
-    "widest-line": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz",
-      "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==",
-      "dev": true,
-      "requires": {
-        "string-width": "^4.0.0"
-      }
-    },
-    "wmf": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
-      "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw=="
-    },
-    "word": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
-      "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA=="
-    },
-    "word-wrap": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
-      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
-      "dev": true
-    },
-    "wrap-ansi": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
-      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
-      "dev": true,
-      "requires": {
-        "ansi-styles": "^4.0.0",
-        "string-width": "^4.1.0",
-        "strip-ansi": "^6.0.0"
-      }
-    },
-    "wrappy": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
-    },
-    "write-file-atomic": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
-      "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
-      "dev": true,
-      "requires": {
-        "imurmurhash": "^0.1.4",
-        "is-typedarray": "^1.0.0",
-        "signal-exit": "^3.0.2",
-        "typedarray-to-buffer": "^3.1.5"
-      }
-    },
-    "xdg-basedir": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz",
-      "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==",
-      "dev": true
-    },
-    "xlsx": {
-      "version": "0.18.5",
-      "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
-      "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
-      "requires": {
-        "adler-32": "~1.3.0",
-        "cfb": "~1.2.1",
-        "codepage": "~1.15.0",
-        "crc-32": "~1.2.1",
-        "ssf": "~0.11.2",
-        "wmf": "~1.0.1",
-        "word": "~0.3.0"
-      }
-    },
-    "xtend": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
-      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
-    },
-    "yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
-    }
-  }
-}

+ 12 - 9
package.json

@@ -10,18 +10,21 @@
   "dependencies": {
     "axios": "^0.27.2",
     "bcrypt": "^5.0.1",
-    "cookie-parser": "~1.4.6",
+    "child-process": "^1.0.2",
+    "cookie-parser": "~1.4.4",
     "cors": "^2.8.5",
-    "crypto": "^1.0.1",
-    "debug": "~4.3.4",
-    "dotenv": "^16.0.1",
-    "express": "~4.18.1",
+    "cryptr": "^6.2.0",
+    "csrf-csrf": "^2.3.0",
+    "debug": "~2.6.9",
+    "dotenv": "^16.0.0",
+    "express": "~4.16.1",
     "fastest-validator": "^1.12.0",
+    "form-data": "^4.0.0",
     "jsonwebtoken": "^8.5.1",
-    "moment": "^2.29.3",
-    "mongoose": "^6.3.8",
-    "morgan": "~1.10.0",
-    "multer": "1.4.5-lts.1",
+    "moment": "^2.29.1",
+    "mongoose": "^6.2.7",
+    "morgan": "~1.9.1",
+    "multer": "^1.4.4",
     "xlsx": "^0.18.5"
   },
   "devDependencies": {

+ 0 - 13
public/index.html

@@ -1,13 +0,0 @@
-<html>
-
-<head>
-  <title>Express</title>
-  <link rel="stylesheet" href="/stylesheets/style.css">
-</head>
-
-<body>
-  <h1>Express</h1>
-  <p>Welcome to Express</p>
-</body>
-
-</html>

+ 0 - 8
public/stylesheets/style.css

@@ -1,8 +0,0 @@
-body {
-  padding: 50px;
-  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
-}
-
-a {
-  color: #00B7FF;
-}

+ 31 - 0
routes/index.js

@@ -0,0 +1,31 @@
+const router = require('express').Router()
+const dokumenController = require('../controller/dokumen.controller')
+const auth = require('../middleware/verifyToken')
+const handleDokumen = require('../utils/handleDokumen')
+
+router.get('/', (req, res) => {
+  return res.json({
+    message: 'API SIDALI'
+  })
+})
+
+router.use('/v1', require('../routes/v1'))
+router.use('/v2', require('../routes/v2'))
+
+router.get('/dokumen/:id/:nama_file', dokumenController.getDokumen)
+router.post('/dokumen', auth, handleDokumen.single('dokumen'), dokumenController.createDokumen)
+
+router.get('/csrf',
+  (req, res, next) => {
+    // if (req.headers['x3u2-y4w1r'] !== Date.now().toString().slice(0, 10) + '51d@l!') {
+    if (req.headers['x3u2-y4w1r'] !== '51d@l!') {
+      return res.status(401).json({ message: 'unauthorized' })
+    }
+    next()
+  },
+  (req, res) => {
+    return res.json({ token: null })
+  }
+)
+
+module.exports = router

+ 6 - 4
routes/v1/auth.routes.js

@@ -1,8 +1,10 @@
 const router = require('express').Router()
-const auth = require('../../controller/auth.controller')
-const verify = require('../../middleware/verifyToken')
+const authv2 = require('../../controller/v2/auth.controller')
+const blacklistUser = require('../../middleware/blacklistUser')
 
-router.post('/login', auth.login)
-router.delete('/logout', verify, auth.logout)
+router.post('/login', authv2.login)
+router.post('/otp', ...authv2.sendOTP)
+router.post('/login-to-pt', ...authv2.loginToPT)
+router.delete('/logout', ...authv2.logout)
 
 module.exports = router

+ 12 - 6
routes/v1/auto.routes.js

@@ -1,10 +1,16 @@
 const router = require('express').Router()
-const auto = require('../../controller/auto.controller')
+const auto = require('../../controller/v1/auto.controller')
+const verify = require('../../middleware/verifyTokenAuto')
+const auth = require('../../middleware/verifyToken')
 
-router.get('/keberatan', auto.keberatan)
-router.get('/banding', auto.banding)
-router.get('/reminder-keberatan', auto.reminderKeberatan)
-router.get('/reminder-banding', auto.reminderBanding)
-router.get('/status-sanksi', auto.updateStatusSanksi)
+router.get('/keberatan', verify, auto.keberatan)
+router.get('/banding', verify, auto.banding)
+router.get('/reminder-keberatan', verify, auto.reminderKeberatan)
+router.get('/reminder-banding', verify, auto.reminderBanding)
+router.get('/status-sanksi', verify, auto.updateStatusSanksi)
+router.get('/berakhir-sanksi', verify, auto.berakhirSanksi)
+router.post('/save/:id', auth, auto.save)
+router.get('/save/:id', auto.getSave)
+router.get('/batch/update-status-sanksi', verify, auto.batchUpdateSanksi)
 
 module.exports = router

+ 16 - 0
routes/v1/catatan.routes.js

@@ -0,0 +1,16 @@
+const router = require('express').Router()
+const catatanController = require('../../controller/v2/catatan.controller')
+const auth = require('../../middleware/verifyToken')
+const handleDokumen = require('../../utils/handleDokumen')
+const role = require('../../middleware/role')
+const {PTB_DIKTI, PTB_LLDIKTI, PTB_ADMIN } = require('../../utils/constanta')
+
+router.get('/:sanksi_id', auth, role([PTB_DIKTI, PTB_LLDIKTI, PTB_ADMIN]), catatanController.getAllCatatan)
+router.post('/hadir/:catatan_id', handleDokumen.single('ttd'), catatanController.addDaftarHadir)
+router.delete('/hadir/:catatan_id', catatanController.removePeserta)
+router.get('/detail/:catatan_id', auth, role([PTB_DIKTI, PTB_LLDIKTI, PTB_ADMIN]), catatanController.getOneCatatan)
+router.post('/:sanksi_id', auth, role([PTB_DIKTI, PTB_LLDIKTI, PTB_ADMIN]), catatanController.createCatatan)
+router.delete('/:catatan_id', auth, role([PTB_DIKTI, PTB_LLDIKTI, PTB_ADMIN]), catatanController.deleteCatatan)
+router.put('/:sanksi_id', auth, role([PTB_DIKTI, PTB_LLDIKTI, PTB_ADMIN]), catatanController.editCatatan)
+
+module.exports = router

+ 9 - 0
routes/v1/disk.routes.js

@@ -0,0 +1,9 @@
+const router = require('express').Router()
+const auth = require('../../middleware/verifyToken')
+const role = require('../../middleware/role')
+const diskController = require('../../controller/v1/disk.controller')
+const { PTB_ADMIN, PTB_DIKTI, PTB_LLDIKTI } = require('../../utils/constanta')
+
+router.get('/', auth, role([PTB_ADMIN, PTB_DIKTI, PTB_LLDIKTI]), diskController.getData)
+
+module.exports = router

+ 4 - 4
routes/v1/graph.routes.js

@@ -1,16 +1,16 @@
 const router = require('express').Router()
-const graph = require('../../controller/graph.controller')
+const graph = require('../../controller/v1/graph.controller')
 const auth = require('../../middleware/verifyToken')
 const roleId = require('../../middleware/role')
 
-router.get('/', auth, roleId([2020, 2021, 2023]), graph.laporan)
+router.get('/', auth, roleId([2020, 2021, 2023, 2024]), graph.laporan)
 router.get(
   '/laporanSelesai',
   auth,
-  roleId([2020, 2021, 2023]),
+  roleId([2020, 2021, 2023, 2024]),
   graph.laporanSelesai
 )
 router.get('/jumlahStatusLaporan', auth, graph.jumlahStatusLaporan)
-router.get('/:token/:nama_file', auth, roleId([2020, 2021, 2023]), graph.excel)
+router.get('/:token/:nama_file', auth, roleId([2020, 2021, 2023, 2024]), graph.excel)
 
 module.exports = router

+ 12 - 4
routes/v1/index.js

@@ -1,7 +1,7 @@
 const router = require('express').Router()
 const auth = require('../../middleware/verifyToken')
 const roleId = require('../../middleware/role')
-const verify = require('../../middleware/verifyTokenAuto')
+const { PTB_DIKTI, PTB_LLDIKTI, PTB_ADMIN, PTB_READ } = require('../../utils/constanta')
 
 router.get('/', (req, res) => {
   return res.json({
@@ -9,8 +9,11 @@ router.get('/', (req, res) => {
     version: 1,
   })
 })
+router.get('/csrf',  (req, res) => {
+  return res.json({token: 'null'})
+})
 
-router.use('/laporan', auth, roleId([2020, 2021, 2023]), require('./laporan'))
+router.use('/laporan', auth, roleId([PTB_DIKTI, PTB_LLDIKTI, PTB_ADMIN, PTB_READ]), require('./laporan'))
 router.use('/sanksi', auth, require('./sanksi'))
 router.use('/public', require('./public.routes'))
 router.use('/auth', require('./auth.routes'))
@@ -18,11 +21,16 @@ router.use('/user', auth, require('./user.routes'))
 router.use('/pemantauan', auth, require('./pemantauan.routes'))
 router.use('/pt', auth, require('./pt.routes'))
 router.use('/pelanggaran', auth, require('./pelanggaran.routes'))
-router.use('/lembaga', auth, roleId([2020, 2023]), require('./lembaga.routes'))
+router.use('/lembaga', auth, roleId([2020, 2023, 2024]), require('./lembaga.routes'))
 router.use('/graph', require('./graph.routes'))
 router.use('/log', require('./log.routes'))
-router.use('/auto', verify, require('./auto.routes'))
+router.use('/auto', require('./auto.routes'))
 router.use('/pengunjung', require('./pengunjung.routes'))
 router.use('/rekomendasi', auth, require('./rekomendasi.routes'))
+router.use('/migrasi', auth, roleId([2020, 2023]), require('./migration.routes'))
+router.use('/kontak', require('./kontak.routes'))
+router.use('/signature', require('./signature.routes'))
+router.use('/catatan', require('./catatan.routes'))
+router.use('/disk', require('./disk.routes'))
 
 module.exports = router

+ 7 - 0
routes/v1/kontak.routes.js

@@ -0,0 +1,7 @@
+const router = require('express').Router()
+const kontak = require('../../controller/v1/kontak.controller')
+
+router.post('/', ...kontak.addKontak)
+router.get('/', ...kontak.getKontak)
+
+module.exports = router

+ 1 - 1
routes/v1/laporan/evaluasi.routes.js

@@ -1,5 +1,5 @@
 const router = require('express').Router()
-const evaluasi = require('../../../controller/laporan/evaluasi.controller')
+const evaluasi = require('../../../controller/v1/laporan/evaluasi.controller')
 const handleDokumen = require('../../../utils/handleDokumen')
 
 router.post('/add/:id', handleDokumen.array('dokumen'), evaluasi.add)

+ 6 - 3
routes/v1/laporan/index.js

@@ -1,14 +1,17 @@
 const router = require('express').Router()
-const laporan = require('../../../controller/laporan.controller')
+const laporan = require('../../../controller/v1/laporan.controller')
 const handleDokumen = require('../../../utils/handleDokumen')
 const roleId = require('../../../middleware/role')
 
 router.get('/', laporan.getAll)
 router.get('/jumlah', laporan.jumlahLaporan)
-router.get('/byPembina/:idPembina', roleId([2020,2023]),laporan.laporanByPembina)
+router.get('/byPembina/:idPembina', ...laporan.laporanByPembina)
 router.get('/:id', laporan.getOne)
 router.post('/create', handleDokumen.array('dokumen'), laporan.create)
-router.put('/update/:id', laporan.update)
+router.put('/update/:id', ((req, res, next) => {
+  if (req.query.redudansi === 'true') return handleDokumen.array('dokumen')(req, res, next);
+  return next()
+}), laporan.update)
 
 router.use('/jadwal', require('./jadwal.routes'))
 router.use('/evaluasi', require('./evaluasi.routes'))

+ 1 - 1
routes/v1/laporan/jadwal.routes.js

@@ -1,5 +1,5 @@
 const router = require('express').Router()
-const jadwal = require('../../../controller/laporan/jadwal.controller')
+const jadwal = require('../../../controller/v1/laporan/jadwal.controller')
 
 router.put('/update/:id', jadwal.update)
 

+ 1 - 1
routes/v1/lembaga.routes.js

@@ -1,5 +1,5 @@
 const router = require('express').Router()
-const lembaga = require('../../controller/lembaga.controller')
+const lembaga = require('../../controller/v1/lembaga.controller')
 
 router.get('/', lembaga.get)
 

+ 1 - 1
routes/v1/log.routes.js

@@ -1,5 +1,5 @@
 const router = require('express').Router()
-const log = require('../../controller/log.controller')
+const log = require('../../controller/v1/log.controller')
 const auth = require('../../middleware/verifyToken')
 const roleId = require('../../middleware/role')
 

+ 10 - 0
routes/v1/migration.routes.js

@@ -0,0 +1,10 @@
+const router = require('express').Router()
+const migrasi = require('../../controller/v1/migrasi.controller')
+
+router.put('/pengajuan', migrasi.pengajuan)
+router.put('/dokumen', migrasi.dokumen)
+router.put('/pelanggaran-sanksi', migrasi.pelanggaranSanksi)
+router.put('/tambah-step', migrasi.tambahStep)
+router.put('/back-to-sanksi', migrasi.backToSanksi)
+
+module.exports = router

+ 3 - 3
routes/v1/pelanggaran.routes.js

@@ -1,9 +1,9 @@
 const router = require('express').Router()
-const pelanggaran = require('../../controller/pelanggaran.controller')
+const pelanggaran = require('../../controller/v1/pelanggaran.controller')
 const roleId = require('../../middleware/role')
 const auth = require('../../middleware/verifyToken')
 
-router.get('/', roleId([2020, 2021, 2023]), pelanggaran.getAll)
-router.get('/sanksi', auth,roleId([2020, 2021, 2023]), pelanggaran.sanksi)
+router.get('/', roleId([2020, 2021, 2023, 2024]), pelanggaran.getAll)
+router.get('/sanksi', auth,roleId([2020, 2021, 2023, 2024]), pelanggaran.sanksi)
 
 module.exports = router

+ 2 - 2
routes/v1/pemantauan.routes.js

@@ -1,8 +1,8 @@
 const router = require('express').Router()
-const pemantauan = require('../../controller/pemantauan.controller')
+const pemantauan = require('../../controller/v1/pemantauan.controller')
 const roleId = require('../../middleware/role')
 
 router.get('/pt', roleId(2022), pemantauan.getPT)
-router.get('/:laporan_id', roleId([2020, 2021, 2023]), pemantauan.get)
+router.get('/:laporan_id', roleId([2020, 2021, 2023, 2024]), pemantauan.get)
 
 module.exports = router

+ 1 - 1
routes/v1/pengunjung.routes.js

@@ -1,5 +1,5 @@
 const router = require('express').Router()
-const pengunjung = require('../../controller/pengunjung.controller')
+const pengunjung = require('../../controller/v1/pengunjung.controller')
 const auth = require('../../middleware/verifyToken')
 
 router.post('/create', pengunjung.create)

+ 2 - 3
routes/v1/pt.routes.js

@@ -1,8 +1,7 @@
 const router = require('express').Router()
-const pt = require('../../controller/pt.controller')
-const roleId = require('../../middleware/role')
+const pt = require('../../controller/v1/pt.controller')
 
 router.get('/', pt.getAll)
-router.get('/:id', roleId([2020, 2021, 2023]), pt.getOne)
+router.get('/:id', ...pt.getOne)
 
 module.exports = router

+ 9 - 7
routes/v1/public.routes.js

@@ -1,17 +1,18 @@
 const router = require('express').Router()
-const user = require('../../controller/user.controller')
-const pt = require('../../controller/pt.controller')
-const pelanggaran = require('../../controller/pelanggaran.controller')
-const laporan = require('../../controller/laporan.controller')
-const pemantauan = require('../../controller/pemantauan.controller')
+const user = require('../../controller/v1/user.controller')
+const pt = require('../../controller/v1/pt.controller')
+const pelanggaran = require('../../controller/v1/pelanggaran.controller')
+const laporan = require('../../controller/v1/laporan.controller')
+const pemantauan = require('../../controller/v1/pemantauan.controller')
 const handleDokumen = require('../../utils/handleDokumen')
 const auth = require('../../middleware/verifyTokenPublic')
-const pengunjung = require('../../controller/pengunjung.controller')
+const pengunjung = require('../../controller/v1/pengunjung.controller')
+const sanksi = require('../../controller/v1/sanksi.controller')
 
 router.get('/pt', pt.public)
 router.get('/pelanggaran', pelanggaran.public)
 router.get('/pemantauan', pemantauan.public)
-
+router.get('/laporan/:no_laporan', laporan.getLaporanByNoLaporanAndId)
 router.post(
   '/laporan/create',
   auth,
@@ -19,6 +20,7 @@ router.post(
   laporan.public
 )
 router.post('/user/create', handleDokumen.single('foto'), user.addUserPublic)
+router.post('/sanksi/add-peserta-pleno', handleDokumen.single('ttd'), sanksi.addPesertaPleno)
 router.get('/pengunjung', pengunjung.getPengunjungPublic)
 
 module.exports = router

+ 1 - 1
routes/v1/rekomendasi.routes.js

@@ -1,5 +1,5 @@
 const router = require('express').Router()
-const rekomendasi = require('../../controller/rekomendasi.controller')
+const rekomendasi = require('../../controller/v1/rekomendasi.controller')
 const handleDokumen = require('../../utils/handleDokumen')
 const roleId = require('../../middleware/role')
 

+ 2 - 2
routes/v1/sanksi/banding.routes.js

@@ -1,5 +1,5 @@
 const router = require('express').Router()
-const banding = require('../../../controller/sanksi/banding.controller')
+const banding = require('../../../controller/v1/sanksi/banding.controller')
 const handleDokumen = require('../../../utils/handleDokumen')
 const roleId = require('../../../middleware/role')
 
@@ -13,7 +13,7 @@ router.post(
 router.post(
   '/jawaban/create/:sanksi_id',
   roleId([2020, 2021, 2023]),
-  handleDokumen.array('dokumen'),
+  handleDokumen.fields([{ name: 'dokumen' }, { name: 'dokumen_terima_banding' }]),
   banding.createJawaban
 )
 

+ 8 - 2
routes/v1/sanksi/cabutSanksi.routes.js

@@ -1,12 +1,12 @@
 const router = require('express').Router()
-const cabutSanksi = require('../../../controller/sanksi/cabutSanksi.controller')
+const cabutSanksi = require('../../../controller/v1/sanksi/cabutSanksi.controller')
 const handleDokumen = require('../../../utils/handleDokumen')
 const roleId = require('../../../middleware/role')
 
 router.post(
   '/create/:sanksi_id',
   roleId(2022),
-  handleDokumen.array('dokumen'),
+  handleDokumen.fields([{ name: 'dokumen' }, {name: 'dokumen_rekomendasi'}]),
   cabutSanksi.create
 )
 
@@ -17,4 +17,10 @@ router.post(
   cabutSanksi.createJawaban
 )
 
+router.put(
+  '/bypass/:sanksi_id',
+  roleId([2020, 2023]),
+  cabutSanksi.byPass
+)
+
 module.exports = router

+ 16 - 2
routes/v1/sanksi/index.js

@@ -1,20 +1,32 @@
 const router = require('express').Router()
-const sanksi = require('../../../controller/sanksi.controller')
+const sanksi = require('../../../controller/v1/sanksi.controller')
 const handleDokumen = require('../../../utils/handleDokumen')
 const roleId = require('../../../middleware/role')
+const checkEnv = require('../../../middleware/checkEnv')
 
 router.post(
   '/create/:laporan_id',
   roleId([2020, 2021, 2023]),
-  handleDokumen.array('dokumen'),
+  handleDokumen.fields([{ name: 'dokumen' }, { name: 'berita_acara', maxCount: 1 }, { name: 'dokumen_terima_sanksi' }]),
   sanksi.create
 )
+router.put(
+  '/pddikti/:sanksi_id',
+  checkEnv('production'),
+  roleId([2020, 2021, 2023]),
+  sanksi.updatePDDIKTI
+)
 router.put(
   '/update/:sanksi_id',
   roleId([2020, 2021, 2023]),
   handleDokumen.array('dokumen'),
   sanksi.update
 )
+router.put(
+  '/update-pt/:sanksi_id',
+  roleId(2022),
+  sanksi.updatePt
+)
 router.put(
   '/tmt/update/:id',
   roleId([2020, 2021, 2023]),
@@ -23,6 +35,8 @@ router.put(
 )
 router.get('/', sanksi.getAll)
 router.get('/:sanksi_id', sanksi.getOne)
+router.delete('/remove-peserta-pleno', sanksi.removePesertaPleno)
+router.put('/update-to-dokumen-perbaikan', sanksi.updateToDokumenPerbaikan)
 
 router.use('/keberatan', require('./keberatan.routes'))
 router.use('/banding', require('./banding.routes'))

+ 2 - 2
routes/v1/sanksi/keberatan.routes.js

@@ -1,5 +1,5 @@
 const router = require('express').Router()
-const keberatan = require('../../../controller/sanksi/keberatan.controller')
+const keberatan = require('../../../controller/v1/sanksi/keberatan.controller')
 const handleDokumen = require('../../../utils/handleDokumen')
 const roleId = require('../../../middleware/role')
 
@@ -13,7 +13,7 @@ router.post(
 router.post(
   '/jawaban/create/:sanksi_id',
   roleId([2020, 2021, 2023]),
-  handleDokumen.array('dokumen'),
+  handleDokumen.fields([{ name: 'dokumen' }, { name: 'dokumen_terima_keberatan' }]),
   keberatan.createJawaban
 )
 

+ 9 - 2
routes/v1/sanksi/perbaikan.routes.js

@@ -1,13 +1,20 @@
 const router = require('express').Router()
-const perbaikan = require('../../../controller/sanksi/perbaikan.controller')
+const perbaikan = require('../../../controller/v1/sanksi/perbaikan.controller')
 const handleDokumen = require('../../../utils/handleDokumen')
 const roleId = require('../../../middleware/role')
+const { PTB_PT } = require('../../../utils/constanta')
 
 router.post(
   '/add/:sanksi_id',
-  roleId(2022),
+  roleId(PTB_PT),
   handleDokumen.array('dokumen'),
   perbaikan.add
 )
 
+router.put(
+  '/finalisasi/:sanksi_id',
+  roleId(PTB_PT),
+  perbaikan.finalisasi
+)
+
 module.exports = router

+ 10 - 0
routes/v1/signature.routes.js

@@ -0,0 +1,10 @@
+const router = require('express').Router()
+const signature = require('../../controller/v1/signature.controller')
+const auth = require('../../middleware/verifyToken')
+const handleDokumen = require('../../utils/handleDokumen')
+
+router.get('/:laporan_id', auth, signature.getPeserta)
+router.post('/', handleDokumen.single('ttd'), signature.addPeserta)
+router.delete('/', auth, signature.removePeserta)
+
+module.exports = router

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä