diff --git a/.env b/.env index 258d393..3e21c95 100644 --- a/.env +++ b/.env @@ -6,3 +6,16 @@ DATABASE_PORT=5432 DATABASE_USERNAME=malma DATABASE_PASSWORD=kucing DATABASE_NAME=goumrah + +JWT_SECRET=goumrah +JWT_ALGORITHM=HS256 +JWT_ISSUER=http://localhost:3000 + +MIDTRANS_BASE_URL=https://app.sandbox.midtrans.com/snap/v1 +MIDTRANS_MERCHANT_ID=G908685501 +MIDTRANS_SERVER_KEY=SB-Mid-server-caiT6pDw8na31mxUBRhvtVPk + +MAIL_HOST=smtp.gmail.com +MAIL_PORT=465 +MAIL_USERNAME=admin@goumrah.id +MAIL_PASSWORD=oscm zldv npls wyzz diff --git a/.env.example b/.env.example index ba6307a..0681940 100644 --- a/.env.example +++ b/.env.example @@ -6,3 +6,16 @@ DATABASE_PORT= DATABASE_USER= DATABASE_PASSWORD= DATABASE_NAME= + +JWT_SECRET= +JWT_ALGORITHM= +JWT_ISSUER= + +MIDTRANS_BASE_URL= +MIDTRANS_MERCHANT_ID= +MIDTRANS_SERVER_KEY= + +MAIL_HOST= +MAIL_PORT= +MAIL_USERNAME= +MAIL_PASSWORD= diff --git a/bun.lock b/bun.lock index 0b9ea35..b4ab542 100644 --- a/bun.lock +++ b/bun.lock @@ -13,6 +13,8 @@ "express": "5.1.0", "file-type": "21.0.0", "helmet": "8.1.0", + "jsonwebtoken": "9.0.2", + "nodemailer": "7.0.10", "reflect-metadata": "0.2.2", "slugify": "1.6.6", "ulid": "3.0.1", @@ -23,12 +25,76 @@ "@types/compression": "1.8.1", "@types/cors": "2.8.19", "@types/express": "5.0.5", + "@types/jsonwebtoken": "9.0.10", + "@types/nodemailer": "7.0.4", "prettier": "3.6.2", "typescript": "5.9.3", }, }, }, "packages": { + "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], + + "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], + + "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], + + "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@aws-sdk/client-sesv2": ["@aws-sdk/client-sesv2@3.934.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.934.0", "@aws-sdk/credential-provider-node": "3.934.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.934.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/signature-v4-multi-region": "3.934.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.934.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Sq2vikq+RDNgRkMN1tpeciNK59mY9FYDvYqrfNtA62urZ8zxD39pmrNK1gEIjWhYiVH2aEXLCxnx7njfwMinCg=="], + + "@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.934.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.934.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.934.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.934.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-gsgJevqhY0j3x014ejhXtHLCA6o83FYm3rJoZG7tqoy3DnWerLv/FHaAnHI/+Q+csadqjoFkWGQTOedPoOunzA=="], + + "@aws-sdk/core": ["@aws-sdk/core@3.934.0", "", { "dependencies": { "@aws-sdk/types": "3.930.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.2", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-b6k916ZxSrBwQPzeirncTIQXGnhps0HFOUakFt0ZEzjksePYUiEoU/SQ7VeY1j9JeAdJ24ejqddCiyLt99/3lg=="], + + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.934.0", "", { "dependencies": { "@aws-sdk/core": "3.934.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-bnpIGYm7Jy46dxZa1cxMQ1sF0n2iBIT+TpOPHK51sz1N2dYOicUVWUHMDgU2xIFOVcKaqV+GV4VyicMmvDBcBQ=="], + + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.934.0", "", { "dependencies": { "@aws-sdk/core": "3.934.0", "@aws-sdk/types": "3.930.0", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-WJcfFik7MPIgjE8lmuDcCqddHKRMpifzoBzTZWqUJJWYXIy0rDfNzt6pn3/TMLwVgnCGjnXlw6dChTxLzO60RQ=="], + + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.934.0", "", { "dependencies": { "@aws-sdk/core": "3.934.0", "@aws-sdk/credential-provider-env": "3.934.0", "@aws-sdk/credential-provider-http": "3.934.0", "@aws-sdk/credential-provider-process": "3.934.0", "@aws-sdk/credential-provider-sso": "3.934.0", "@aws-sdk/credential-provider-web-identity": "3.934.0", "@aws-sdk/nested-clients": "3.934.0", "@aws-sdk/types": "3.930.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-3vVKGe1F2S09G9kC0ZcpWh09opyrGOgQETllqWbuxlTVd7zBgrZWloItLIvneSDP+dWvdLFUbkD7WDWNCeGiig=="], + + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.934.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.934.0", "@aws-sdk/credential-provider-http": "3.934.0", "@aws-sdk/credential-provider-ini": "3.934.0", "@aws-sdk/credential-provider-process": "3.934.0", "@aws-sdk/credential-provider-sso": "3.934.0", "@aws-sdk/credential-provider-web-identity": "3.934.0", "@aws-sdk/types": "3.930.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-nguy36xi8nbH346dJjCmwWtOgfS4VfL7yHP+EEGmma+yg+J7mxgs8kA1NGQdJ8B46GdjlJPpI1P9pm7Pmz7nOw=="], + + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.934.0", "", { "dependencies": { "@aws-sdk/core": "3.934.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-PhvpAgoJ88IOuqlUws9nvHuPex2jK+WS+0s00BQcRTwqPP0jtLT7eql6UfCRduwv2sIy3m1wnWDUubvbpejp/Q=="], + + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.934.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.934.0", "@aws-sdk/core": "3.934.0", "@aws-sdk/token-providers": "3.934.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-7wO86w95V9MZSYo2dunBKruKHdAUmgg9ccOSJSYGnPip1PPBK/rgSgQ8mDlYtFAW3/82bdeM/668QcgLT4+ofA=="], + + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.934.0", "", { "dependencies": { "@aws-sdk/core": "3.934.0", "@aws-sdk/nested-clients": "3.934.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-hb+lvFxiAPcAvUorB0hrUd1kDjDRXhZgCi5426I8KUpGzZ+ALh8/ep0KXAiYe2yg9ZkyMUbMaMvYYhMFcbXRFA=="], + + "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.930.0", "", { "dependencies": { "@aws-sdk/types": "3.930.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-x30jmm3TLu7b/b+67nMyoV0NlbnCVT5DI57yDrhXAPCtdgM1KtdLWt45UcHpKOm1JsaIkmYRh2WYu7Anx4MG0g=="], + + "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.930.0", "", { "dependencies": { "@aws-sdk/types": "3.930.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-vh4JBWzMCBW8wREvAwoSqB2geKsZwSHTa0nSt0OMOLp2PdTYIZDi0ZiVMmpfnjcx9XbS6aSluLv9sKx4RrG46A=="], + + "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.933.0", "", { "dependencies": { "@aws-sdk/types": "3.930.0", "@aws/lambda-invoke-store": "^0.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-qgrMlkVKzTCAdNw2A05DC2sPBo0KRQ7wk+lbYSRJnWVzcrceJhnmhoZVV5PFv7JtchK7sHVcfm9lcpiyd+XaCA=="], + + "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.934.0", "", { "dependencies": { "@aws-sdk/core": "3.934.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-arn-parser": "3.893.0", "@smithy/core": "^3.18.2", "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-eU2R7pVOhCxnkDzq9mW+xh4WvCA3mdXVUHezIcJNFyKCKKv/c9I4WFcnMnUy+wnCWO2mzN/gwSgQxADkvxfLNQ=="], + + "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.934.0", "", { "dependencies": { "@aws-sdk/core": "3.934.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@smithy/core": "^3.18.2", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-68giGM2Zm9K6Qas14ws3Qo5wafpn0I8/L64fS9E6Rc6Tu0k+So73hupysw+9ZOzHwQS5FEBUqLOMtbUibAcjNA=="], + + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.934.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.934.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.934.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.934.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kRO61EMrDR4UuPlKAkziG6urcYXlhrFW/Ce5PjWFdjkm0ZOge75OFV1vhf/vE4Pmoop9jaAONX4E5BaIYrIQfg=="], + + "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.930.0", "", { "dependencies": { "@aws-sdk/types": "3.930.0", "@smithy/config-resolver": "^4.4.3", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-KL2JZqH6aYeQssu1g1KuWsReupdfOoxD6f1as2VC+rdwYFUu4LfzMsFfXnBvvQWWqQ7rZHWOw1T+o5gJmg7Dzw=="], + + "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.934.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.934.0", "@aws-sdk/types": "3.930.0", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-cLphxVoHapSdouAdLSDEwR2Bktjg5dc11EpSpaLo8jcFpAXhFaDllKBfDfws0EqGY6N2CMqEjqPqxDFzmmQOQA=="], + + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.934.0", "", { "dependencies": { "@aws-sdk/core": "3.934.0", "@aws-sdk/nested-clients": "3.934.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-M0WEmgXDdUxapSfjplqJoVCBMcn0vQ5Jou0X/XiQwyVDbfvIyNSHUHyMXEIBAew9kVx9sfMMEYz3LXewvQxdCA=="], + + "@aws-sdk/types": ["@aws-sdk/types@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-we/vaAgwlEFW7IeftmCLlLMw+6hFs3DzZPJw7lVHbj/5HJ0bz9gndxEsS2lQoeJ1zhiiLqAqvXxmM43s0MBg0A=="], + + "@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.893.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA=="], + + "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.930.0", "", { "dependencies": { "@aws-sdk/types": "3.930.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-endpoints": "^3.2.5", "tslib": "^2.6.2" } }, "sha512-M2oEKBzzNAYr136RRc6uqw3aWlwCxqTP1Lawps9E1d2abRPvl1p1ztQmmXp1Ak4rv8eByIZ+yQyKQ3zPdRG5dw=="], + + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.893.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg=="], + + "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.930.0", "", { "dependencies": { "@aws-sdk/types": "3.930.0", "@smithy/types": "^4.9.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-q6lCRm6UAe+e1LguM5E4EqM9brQlDem4XDcQ87NzEvlTW6GzmNCO0w1jS0XgCFXQHjDxjdlNFX+5sRbHijwklg=="], + + "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.934.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.934.0", "@aws-sdk/types": "3.930.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-vPRR4PaqNmuOQJSzq4EAVwFHUaSpPtgDgCEc7AYbArIy+59fckb6JNddlrjx4w4iWbqO0d+7OC5PtRcIk0AcZA=="], + + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA=="], + + "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.0", "", {}, "sha512-D1jAmAZQYMoPiacfgNf7AWhg3DFN3Wq/vQv3WINt9znwjzHp2x+WzdJFxxj7xZL7V1U79As6G8f7PorMYWBKsQ=="], + "@borewit/text-codec": ["@borewit/text-codec@0.1.1", "", {}, "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA=="], "@jercle/yargonaut": ["@jercle/yargonaut@1.1.5", "", { "dependencies": { "chalk": "^4.1.2", "figlet": "^1.5.2", "parent-require": "^1.0.0" } }, "sha512-zBp2myVvBHp1UaJsNTyS6q4UDKT7eRiqTS4oNTS6VQMd6mpxYOdbeK4pY279cDCdakGy6hG0J3ejoXZVsPwHqw=="], @@ -55,6 +121,86 @@ "@rushstack/ts-command-line": ["@rushstack/ts-command-line@4.23.7", "", { "dependencies": { "@rushstack/terminal": "0.15.2", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" } }, "sha512-Gr9cB7DGe6uz5vq2wdr89WbVDKz0UeuFEn5H2CfWDe7JvjFFaiV15gi6mqDBTbHhHCWS7w8mF1h3BnIfUndqdA=="], + "@smithy/abort-controller": ["@smithy/abort-controller@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA=="], + + "@smithy/config-resolver": ["@smithy/config-resolver@4.4.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw=="], + + "@smithy/core": ["@smithy/core@3.18.4", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.6", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-o5tMqPZILBvvROfC8vC+dSVnWJl9a0u9ax1i1+Bq8515eYjUJqqk5XjjEsDLoeL5dSqGSh6WGdVx1eJ1E/Nwhw=="], + + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ=="], + + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg=="], + + "@smithy/hash-node": ["@smithy/hash-node@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA=="], + + "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A=="], + + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A=="], + + "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.11", "", { "dependencies": { "@smithy/core": "^3.18.4", "@smithy/middleware-serde": "^4.2.6", "@smithy/node-config-provider": "^4.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-eJXq9VJzEer1W7EQh3HY2PDJdEcEUnv6sKuNt4eVjyeNWcQFS4KmnY+CKkYOIR6tSqarn6bjjCqg1UB+8UJiPQ=="], + + "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.11", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/service-error-classification": "^4.2.5", "@smithy/smithy-client": "^4.9.7", "@smithy/types": "^4.9.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-EL5OQHvFOKneJVRgzRW4lU7yidSwp/vRJOe542bHgExN3KNThr1rlg0iE4k4SnA+ohC+qlUxoK+smKeAYPzfAQ=="], + + "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ=="], + + "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ=="], + + "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.5", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg=="], + + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw=="], + + "@smithy/property-provider": ["@smithy/property-provider@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg=="], + + "@smithy/protocol-http": ["@smithy/protocol-http@5.3.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ=="], + + "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg=="], + + "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ=="], + + "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0" } }, "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ=="], + + "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA=="], + + "@smithy/signature-v4": ["@smithy/signature-v4@5.3.5", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w=="], + + "@smithy/smithy-client": ["@smithy/smithy-client@4.9.7", "", { "dependencies": { "@smithy/core": "^3.18.4", "@smithy/middleware-endpoint": "^4.3.11", "@smithy/middleware-stack": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-pskaE4kg0P9xNQWihfqlTMyxyFR3CH6Sr6keHYghgyqqDXzjl2QJg5lAzuVe/LzZiOzcbcVtxKYi1/fZPt/3DA=="], + + "@smithy/types": ["@smithy/types@4.9.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA=="], + + "@smithy/url-parser": ["@smithy/url-parser@4.2.5", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ=="], + + "@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], + + "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], + + "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="], + + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="], + + "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.10", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.7", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-3iA3JVO1VLrP21FsZZpMCeF93aqP3uIOMvymAT3qHIJz2YlgDeRvNUspFwCNqd/j3qqILQJGtsVQnJZICh/9YA=="], + + "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.13", "", { "dependencies": { "@smithy/config-resolver": "^4.4.3", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.7", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-PTc6IpnpSGASuzZAgyUtaVfOFpU0jBD2mcGwrgDuHf7PlFgt5TIPxCYBDbFQs06jxgeV3kd/d/sok1pzV0nJRg=="], + + "@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A=="], + + "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + + "@smithy/util-middleware": ["@smithy/util-middleware@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA=="], + + "@smithy/util-retry": ["@smithy/util-retry@4.2.5", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg=="], + + "@smithy/util-stream": ["@smithy/util-stream@4.5.6", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ=="], + + "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + + "@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], + + "@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], + "@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="], "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], @@ -77,10 +223,16 @@ "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="], + "@types/jsonwebtoken": ["@types/jsonwebtoken@9.0.10", "", { "dependencies": { "@types/ms": "*", "@types/node": "*" } }, "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA=="], + "@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="], + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + "@types/node": ["@types/node@24.9.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA=="], + "@types/nodemailer": ["@types/nodemailer@7.0.4", "", { "dependencies": { "@aws-sdk/client-sesv2": "^3.839.0", "@types/node": "*" } }, "sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow=="], + "@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="], "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], @@ -109,8 +261,12 @@ "body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="], + "bowser": ["bowser@2.12.1", "", {}, "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw=="], + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + "bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="], "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], @@ -161,6 +317,8 @@ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], "emittery": ["emittery@0.13.1", "", {}, "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ=="], @@ -191,6 +349,8 @@ "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + "fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], @@ -273,10 +433,30 @@ "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + "jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "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": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="], + + "jwa": ["jwa@1.4.2", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw=="], + + "jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="], + "knex": ["knex@3.1.0", "", { "dependencies": { "colorette": "2.0.19", "commander": "^10.0.0", "debug": "4.3.4", "escalade": "^3.1.1", "esm": "^3.2.25", "get-package-type": "^0.1.0", "getopts": "2.3.0", "interpret": "^2.2.0", "lodash": "^4.17.21", "pg-connection-string": "2.6.2", "rechoir": "^0.8.0", "resolve-from": "^5.0.0", "tarn": "^3.0.2", "tildify": "2.0.0" }, "bin": { "knex": "bin/cli.js" } }, "sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw=="], "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="], + + "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="], + + "lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="], + + "lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="], + + "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="], + + "lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="], + + "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], + "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], @@ -297,10 +477,12 @@ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - "ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], + "nodemailer": ["nodemailer@7.0.10", "", {}, "sha512-Us/Se1WtT0ylXgNFfyFSx4LElllVLJXQjWi2Xz17xWw7amDKO2MLtFnVp1WACy7GkVGs+oBlRopVNUzlrGSw1w=="], + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], @@ -421,6 +603,8 @@ "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], + "strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="], + "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -439,6 +623,8 @@ "tsconfig-paths": ["tsconfig-paths@4.2.0", "", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], @@ -477,6 +663,10 @@ "zod": ["zod@4.1.12", "", {}, "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="], + "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@rushstack/terminal/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], "@tokenizer/inflate/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], @@ -485,6 +675,8 @@ "body-parser/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "express/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "finalhandler/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], @@ -509,18 +701,14 @@ "send/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "send/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], - "@tokenizer/inflate/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "body-parser/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "express/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "finalhandler/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], "knex/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], - "router/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], } } diff --git a/package.json b/package.json index 981d0f5..d47b3a5 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "scripts": { + "format": "prettier --write .", "dev": "bun run src/server.ts", "dev:watch": "bun run --watch src/server.ts" }, @@ -19,6 +20,8 @@ "express": "5.1.0", "file-type": "21.0.0", "helmet": "8.1.0", + "jsonwebtoken": "9.0.2", + "nodemailer": "7.0.10", "reflect-metadata": "0.2.2", "slugify": "1.6.6", "ulid": "3.0.1", @@ -29,6 +32,8 @@ "@types/compression": "1.8.1", "@types/cors": "2.8.19", "@types/express": "5.0.5", + "@types/jsonwebtoken": "9.0.10", + "@types/nodemailer": "7.0.4", "prettier": "3.6.2", "typescript": "5.9.3" } diff --git a/src/application.ts b/src/application.ts index 239319e..1599e80 100644 --- a/src/application.ts +++ b/src/application.ts @@ -1,6 +1,14 @@ +import type { AbstractEmailService } from "@/common/services/email-service/abstract.email-service"; +import { LibraryEmailService } from "@/common/services/email-service/library.email-service"; import type { AbstractFileStorage } from "@/common/services/file-storage/abstract.file-storage"; import { LocalFileStorage } from "@/common/services/file-storage/local.file-storage"; +import type { AbstractJwtService } from "@/common/services/jwt-service/abstract.jwt-service"; +import { LibraryJwtService } from "@/common/services/jwt-service/library.jwt-service"; +import type { AbstractPaymentService } from "@/common/services/payment-service/abstract.payment-service"; +import { MidtransPaymentService } from "@/common/services/payment-service/midtrans.payment-service"; import { serverConfig } from "@/configs/server.config"; +import { AdminController } from "@/modules/admin/admin.controller"; +import { AdminMapper } from "@/modules/admin/admin.mapper"; import { AirlineController } from "@/modules/airline/airline.controller"; import { AirlineMapper } from "@/modules/airline/airline.mapper"; import { AirportController } from "@/modules/airport/airport.controller"; @@ -15,8 +23,12 @@ import { HotelFacilityController } from "@/modules/hotel-facility/hotel-facility import { HotelFacilityMapper } from "@/modules/hotel-facility/hotel-facility.mapper"; import { HotelController } from "@/modules/hotel/hotel.controller"; import { HotelMapper } from "@/modules/hotel/hotel.mapper"; +import { OrderController } from "@/modules/order/order.controller"; +import { OrderMapper } from "@/modules/order/order.mapper"; import { PackageController } from "@/modules/package/package.controller"; import { PackageMapper } from "@/modules/package/package.mapper"; +import { PartnerController } from "@/modules/partner/partner.controller"; +import { PartnerMapper } from "@/modules/partner/partner.mapper"; import { TransportationController } from "@/modules/transportation/transportation.controller"; import { TransportationMapper } from "@/modules/transportation/transportation.mapper"; import compression from "compression"; @@ -28,14 +40,20 @@ export class Application { private readonly _app: express.Application; // Services + private _emailService!: AbstractEmailService; private _fileStorage!: AbstractFileStorage; + private _jwtService!: AbstractJwtService; + private _paymentService!: AbstractPaymentService; public constructor() { this._app = express(); } public initializeServices() { + this._emailService = new LibraryEmailService(); this._fileStorage = new LocalFileStorage(); + this._jwtService = new LibraryJwtService(); + this._paymentService = new MidtransPaymentService(); } public initializeMiddlewares() { @@ -60,29 +78,66 @@ export class Application { hotelMapper, transportationMapper, ); + const adminMapper = new AdminMapper(); + const partnerMapper = new PartnerMapper(); + const orderMapper = new OrderMapper(packageMapper, partnerMapper); - const countryRouter = new CountryController(countryMapper).buildRouter(); - const cityRouter = new CityController(cityMapper).buildRouter(); + const countryRouter = new CountryController( + countryMapper, + this._jwtService, + ).buildRouter(); + const cityRouter = new CityController( + cityMapper, + this._jwtService, + ).buildRouter(); const airlineRouter = new AirlineController( airlineMapper, this._fileStorage, + this._jwtService, + ).buildRouter(); + const airportRouter = new AirportController( + airportMapper, + this._jwtService, + ).buildRouter(); + const flightRouter = new FlightController( + flightMapper, + this._jwtService, ).buildRouter(); - const airportRouter = new AirportController(airportMapper).buildRouter(); - const flightRouter = new FlightController(flightMapper).buildRouter(); const hotelFacilityRouter = new HotelFacilityController( hotelFacilityMapper, + this._jwtService, ).buildRouter(); const hotelRouter = new HotelController( hotelMapper, this._fileStorage, + this._jwtService, ).buildRouter(); const transportationRouter = new TransportationController( transportationMapper, this._fileStorage, + this._jwtService, ).buildRouter(); const packageRouter = new PackageController( packageMapper, this._fileStorage, + this._jwtService, + ).buildRouter(); + const adminRouter = new AdminController( + adminMapper, + this._fileStorage, + this._emailService, + this._jwtService, + ).buildRouter(); + const partnerRouter = new PartnerController( + partnerMapper, + this._fileStorage, + this._emailService, + this._jwtService, + ).buildRouter(); + const orderRouter = new OrderController( + orderMapper, + this._paymentService, + this._jwtService, ).buildRouter(); this._app.use("/countries", countryRouter); @@ -94,6 +149,9 @@ export class Application { this._app.use("/hotels", hotelRouter); this._app.use("/transportations", transportationRouter); this._app.use("/packages", packageRouter); + this._app.use("/admins", adminRouter); + this._app.use("/partners", partnerRouter); + this._app.use("/orders", orderRouter); } public initializeErrorHandlers() {} diff --git a/src/common/errors/invalid-jwt.error.ts b/src/common/errors/invalid-jwt.error.ts new file mode 100644 index 0000000..618952d --- /dev/null +++ b/src/common/errors/invalid-jwt.error.ts @@ -0,0 +1,5 @@ +export class InvalidJwtError extends Error { + public constructor(message: string) { + super(message); + } +} diff --git a/src/common/errors/payment.error.ts b/src/common/errors/payment.error.ts new file mode 100644 index 0000000..a4091d8 --- /dev/null +++ b/src/common/errors/payment.error.ts @@ -0,0 +1,5 @@ +export class PaymentError extends Error { + public constructor(message: string) { + super(message); + } +} diff --git a/src/common/middlewares/orm.middleware.ts b/src/common/middlewares/create-orm-context.middleware.ts similarity index 85% rename from src/common/middlewares/orm.middleware.ts rename to src/common/middlewares/create-orm-context.middleware.ts index a155a56..ee9f710 100644 --- a/src/common/middlewares/orm.middleware.ts +++ b/src/common/middlewares/create-orm-context.middleware.ts @@ -2,7 +2,7 @@ import { orm } from "@/database/orm"; import { RequestContext } from "@mikro-orm/core"; import type { NextFunction, Request, Response } from "express"; -export function ormMiddleware( +export function createOrmContextMiddleware( _req: Request, _res: Response, next: NextFunction, diff --git a/src/common/middlewares/is-admin.middleware.ts b/src/common/middlewares/is-admin.middleware.ts new file mode 100644 index 0000000..ead2b12 --- /dev/null +++ b/src/common/middlewares/is-admin.middleware.ts @@ -0,0 +1,51 @@ +import { type AbstractJwtService } from "@/common/services/jwt-service/abstract.jwt-service"; +import type { ErrorResponse } from "@/common/types"; +import type { Admin } from "@/database/entities/admin.entity"; +import type { AdminPermission } from "@/database/enums/admin-permission.enum"; +import type { NextFunction, Request, Response } from "express"; + +export type AdminRequestPlugin = { + admin: Admin; +}; + +export function isAdminMiddleware( + jwtService: AbstractJwtService, + permissions: AdminPermission[] = [], +) { + return async (_req: Request, res: Response, next: NextFunction) => { + const req = _req as Request & AdminRequestPlugin; + + const authorization = req.headers["authorization"]; + if (!authorization || !authorization.startsWith("Bearer ")) { + return res.status(401).json({ + data: null, + errors: [ + { + path: "Authorization", + location: "header", + message: "Invalid token.", + }, + ], + } satisfies ErrorResponse); + } + + const token = authorization.slice(7); + + req.admin = await jwtService.verifyAdminToken(token); + + for (const permission of permissions) { + if (!req.admin.permissions.includes(permission)) { + return res.status(403).json({ + data: null, + errors: [ + { + message: `You don't have '${permission}' permission.`, + }, + ], + } satisfies ErrorResponse); + } + } + + next(); + }; +} diff --git a/src/common/middlewares/is-partner.middleware.ts b/src/common/middlewares/is-partner.middleware.ts new file mode 100644 index 0000000..1b406dd --- /dev/null +++ b/src/common/middlewares/is-partner.middleware.ts @@ -0,0 +1,34 @@ +import { type AbstractJwtService } from "@/common/services/jwt-service/abstract.jwt-service"; +import type { ErrorResponse } from "@/common/types"; +import type { Partner } from "@/database/entities/partner.entity"; +import type { NextFunction, Request, Response } from "express"; + +export type PartnerRequestPlugin = { + partner: Partner; +}; + +export function isPartnerMiddleware(jwtService: AbstractJwtService) { + return async (_req: Request, res: Response, next: NextFunction) => { + const req = _req as Request & PartnerRequestPlugin; + + const authorization = req.headers["authorization"]; + if (!authorization || !authorization.startsWith("Bearer ")) { + return res.status(401).json({ + data: null, + errors: [ + { + path: "Authorization", + location: "header", + message: "Invalid token.", + }, + ], + } satisfies ErrorResponse); + } + + const token = authorization.slice(7); + + req.partner = await jwtService.verifyPartnerToken(token); + + next(); + }; +} diff --git a/src/common/schemas.ts b/src/common/schemas.ts index 384c5b5..389cd65 100644 --- a/src/common/schemas.ts +++ b/src/common/schemas.ts @@ -45,3 +45,19 @@ export const dateSchema = z } return parsedDate; }); + +export const emailSchema = z + .email("Must be email string.") + .nonempty("Must not empty.") + .max(254, "Max 254 characters."); + +export const phoneNumberSchema = z + .string("Must be string.") + .nonempty("Must not empty.") + .regex(/^\d+$/, "Must be numeric string.") + .max(20, "Max 20 characters."); + +export const passwordSchema = z + .string("Must be string.") + .nonempty("Must not empty.") + .max(72, "Max 72 characters."); diff --git a/src/common/services/email-service/abstract.email-service.ts b/src/common/services/email-service/abstract.email-service.ts new file mode 100644 index 0000000..6ec39a0 --- /dev/null +++ b/src/common/services/email-service/abstract.email-service.ts @@ -0,0 +1,6 @@ +export abstract class AbstractEmailService { + public abstract sendVerificationEmail( + to: string, + code: string, + ): Promise; +} diff --git a/src/common/services/email-service/library.email-service.ts b/src/common/services/email-service/library.email-service.ts new file mode 100644 index 0000000..bd90001 --- /dev/null +++ b/src/common/services/email-service/library.email-service.ts @@ -0,0 +1,30 @@ +import { AbstractEmailService } from "@/common/services/email-service/abstract.email-service"; +import { mailConfig } from "@/configs/mail.config"; +import * as nodemailer from "nodemailer"; + +export class LibraryEmailService extends AbstractEmailService { + private readonly _transporter: nodemailer.Transporter; + + public constructor() { + super(); + + this._transporter = nodemailer.createTransport({ + host: mailConfig.host, + port: mailConfig.port, + secure: true, + auth: { + user: mailConfig.username, + pass: mailConfig.password, + }, + }); + } + + public async sendVerificationEmail(to: string, code: string): Promise { + await this._transporter.sendMail({ + from: mailConfig.username, + to, + subject: "Email Verification", + text: `Your verification code is: ${code}`, + }); + } +} diff --git a/src/common/services/jwt-service/abstract.jwt-service.ts b/src/common/services/jwt-service/abstract.jwt-service.ts new file mode 100644 index 0000000..838907a --- /dev/null +++ b/src/common/services/jwt-service/abstract.jwt-service.ts @@ -0,0 +1,29 @@ +import type { Admin } from "@/database/entities/admin.entity"; +import type { Partner } from "@/database/entities/partner.entity"; + +export enum JwtType { + access = "access", + refresh = "refresh", +} + +export abstract class AbstractJwtService { + public abstract createAdminToken( + admin: Admin, + type: JwtType, + ): Promise<{ + token: string; + expiresAt: Date; + }>; + + public abstract verifyAdminToken(token: string): Promise; + + public abstract createPartnerToken( + partner: Partner, + type: JwtType, + ): Promise<{ + token: string; + expiresAt: Date; + }>; + + public abstract verifyPartnerToken(token: string): Promise; +} diff --git a/src/common/services/jwt-service/library.jwt-service.ts b/src/common/services/jwt-service/library.jwt-service.ts new file mode 100644 index 0000000..e8e38ee --- /dev/null +++ b/src/common/services/jwt-service/library.jwt-service.ts @@ -0,0 +1,149 @@ +import { InvalidJwtError } from "@/common/errors/invalid-jwt.error"; +import { + AbstractJwtService, + JwtType, +} from "@/common/services/jwt-service/abstract.jwt-service"; +import { jwtConfig } from "@/configs/jwt.config"; +import { Admin } from "@/database/entities/admin.entity"; +import { Partner } from "@/database/entities/partner.entity"; +import { orm } from "@/database/orm"; +import * as dateFns from "date-fns"; +import * as jwt from "jsonwebtoken"; +import { ulid } from "ulid"; +import z from "zod"; + +const adminPayloadSchema = z.object({ + sub: z.ulid(), + role: z.literal("admin"), +}); + +const partnerPayloadSchema = z.object({ + sub: z.ulid(), + role: z.literal("partner"), +}); + +export class LibraryJwtService extends AbstractJwtService { + public async createAdminToken( + admin: Admin, + type: JwtType, + ): Promise<{ + token: string; + expiresAt: Date; + }> { + const now = new Date(); + + let expiresAt: Date; + switch (type) { + case JwtType.access: + expiresAt = dateFns.addDays(now, 1); + break; + case JwtType.refresh: + expiresAt = dateFns.addDays(now, 30); + break; + } + + const token = jwt.sign( + { + role: "admin", + }, + jwtConfig.secret, + { + algorithm: jwtConfig.algorithm, + expiresIn: Math.floor((expiresAt.getTime() - now.getTime()) / 1000), + notBefore: "0 seconds", + subject: admin.id, + issuer: jwtConfig.issuer, + jwtid: ulid(), + }, + ); + + return { + token, + expiresAt, + }; + } + + public async verifyAdminToken(token: string): Promise { + const payload = jwt.verify(token, jwtConfig.secret, { + algorithms: [jwtConfig.algorithm], + issuer: jwtConfig.issuer, + }); + + const parsePayloadResult = adminPayloadSchema.safeParse(payload); + if (!parsePayloadResult.success) { + throw new InvalidJwtError("Invalid payload."); + } + const adminPayload = parsePayloadResult.data; + + const admin = await orm.em.findOne(Admin, { + id: adminPayload.sub, + }); + if (!admin) { + throw new InvalidJwtError("Admin not found."); + } + + return admin; + } + + public async createPartnerToken( + partner: Partner, + type: JwtType, + ): Promise<{ + token: string; + expiresAt: Date; + }> { + const now = new Date(); + + let expiresAt: Date; + switch (type) { + case JwtType.access: + expiresAt = dateFns.addDays(now, 1); + break; + case JwtType.refresh: + expiresAt = dateFns.addDays(now, 30); + break; + } + + const token = jwt.sign( + { + role: "partner", + }, + jwtConfig.secret, + { + algorithm: jwtConfig.algorithm, + expiresIn: Math.floor((expiresAt.getTime() - now.getTime()) / 1000), + notBefore: "0 seconds", + subject: partner.id, + issuer: jwtConfig.issuer, + jwtid: ulid(), + }, + ); + + return { + token, + expiresAt, + }; + } + + public async verifyPartnerToken(token: string): Promise { + const payload = jwt.verify(token, jwtConfig.secret, { + algorithms: [jwtConfig.algorithm], + issuer: jwtConfig.issuer, + }); + + const parsePayloadResult = partnerPayloadSchema.safeParse(payload); + if (!parsePayloadResult.success) { + throw new InvalidJwtError("Invalid payload."); + } + const partnerPayload = parsePayloadResult.data; + + const partner = await orm.em.findOne(Partner, { + id: partnerPayload.sub, + }); + if (!partner) { + throw new InvalidJwtError("Partner not found."); + } + + return partner; + } +} diff --git a/src/common/services/payment-service/abstract.payment-service.ts b/src/common/services/payment-service/abstract.payment-service.ts new file mode 100644 index 0000000..2606c07 --- /dev/null +++ b/src/common/services/payment-service/abstract.payment-service.ts @@ -0,0 +1,5 @@ +import type { Order } from "@/database/entities/order.entity"; + +export abstract class AbstractPaymentService { + public abstract createPaymentUrl(order: Order): Promise; +} diff --git a/src/common/services/payment-service/midtrans.payment-service.ts b/src/common/services/payment-service/midtrans.payment-service.ts new file mode 100644 index 0000000..a40e77f --- /dev/null +++ b/src/common/services/payment-service/midtrans.payment-service.ts @@ -0,0 +1,141 @@ +import { PaymentError } from "@/common/errors/payment.error"; +import { AbstractPaymentService } from "@/common/services/payment-service/abstract.payment-service"; +import { midtransConfig } from "@/configs/midtrans.config"; +import type { OrderDetail } from "@/database/entities/order-detail.entity"; +import type { Order } from "@/database/entities/order.entity"; +import { RoomType } from "@/database/enums/room-type.enum"; + +type CreateTransactionResponseSuccess = { + token: string; + redirect_url: string; +}; +type CreateTransactionResponseFailed = { + error_messages: string[]; +}; + +export class MidtransPaymentService extends AbstractPaymentService { + private readonly _basicAuth: string; + + public constructor() { + super(); + + this._basicAuth = `Basic ${Buffer.from(`${midtransConfig.serverKey}:`).toBase64()}`; + } + + private calculateOrderDetailsPrice(orderDetails: OrderDetail[]): number { + let price = 0; + for (const orderDetail of orderDetails) { + switch (orderDetail.roomType) { + case RoomType.double: + price += orderDetail.order.package.doublePrice; + break; + case RoomType.triple: + price += orderDetail.order.package.triplePrice; + break; + case RoomType.quad: + price += orderDetail.order.package.quadPrice; + break; + case RoomType.infant: + price += orderDetail.order.package.infantPrice ?? 0; + break; + } + } + + return price; + } + + public async createPaymentUrl(order: Order): Promise { + const doubleOrderDetails = order.details.filter( + (orderDetail) => orderDetail.roomType === RoomType.double, + ); + const tripleOrderDetails = order.details.filter( + (orderDetail) => orderDetail.roomType === RoomType.double, + ); + const quadOrderDetails = order.details.filter( + (orderDetail) => orderDetail.roomType === RoomType.double, + ); + const infantOrderDetails = order.details.filter( + (orderDetail) => orderDetail.roomType === RoomType.double, + ); + + const response = await fetch(`${midtransConfig.baseUrl}/transactions`, { + method: "POST", + headers: { + Accept: "application/json", + Authorization: this._basicAuth, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + transaction_details: { + order_id: order.id, + gross_amount: this.calculateOrderDetailsPrice( + order.details.getItems(), + ), + }, + item_details: [ + doubleOrderDetails.length > 0 + ? { + id: doubleOrderDetails[0].id, + price: order.package.doublePrice, + quantity: doubleOrderDetails.length, + name: `${order.package.package.name} / Double`, + brand: "GoUmrah", + category: "Paket", + merchant_name: "GoUmrah", + } + : undefined, + tripleOrderDetails.length > 0 + ? { + id: tripleOrderDetails[0].id, + price: order.package.triplePrice, + quantity: tripleOrderDetails.length, + name: `${order.package.package.name} / Triple`, + brand: "GoUmrah", + category: "Paket", + merchant_name: "GoUmrah", + } + : undefined, + quadOrderDetails.length > 0 + ? { + id: quadOrderDetails[0].id, + price: order.package.quadPrice, + quantity: quadOrderDetails.length, + name: `${order.package.package.name} / Quad`, + brand: "GoUmrah", + category: "Paket", + merchant_name: "GoUmrah", + } + : undefined, + infantOrderDetails.length > 0 + ? { + id: infantOrderDetails[0].id, + price: order.package.infantPrice, + quantity: infantOrderDetails.length, + name: `${order.package.package.name} / Infant`, + brand: "GoUmrah", + category: "Paket", + merchant_name: "GoUmrah", + } + : undefined, + ], + customer_details: { + first_name: order.name, + phone: order.whatsapp, + }, + credit_card: { + secure: true, + }, + }), + }); + + if (response.status === 201) { + const responseBody = + (await response.json()) as CreateTransactionResponseSuccess; + return responseBody.redirect_url; + } else { + const responseBody = + (await response.json()) as CreateTransactionResponseFailed; + throw new PaymentError(responseBody.error_messages[0]); + } + } +} diff --git a/src/common/types.ts b/src/common/types.ts index 0dbbf31..80ed4f6 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -3,7 +3,7 @@ import type z from "zod"; export type PaginationQuery = z.infer; -export type SingleResponse = { +export type SingleResponse = { data: T; errors: null; }; diff --git a/src/common/utils.ts b/src/common/utils.ts new file mode 100644 index 0000000..744237d --- /dev/null +++ b/src/common/utils.ts @@ -0,0 +1,11 @@ +export function generateRandomCode(length: number): string { + const numbers = "0123456789"; + + let result = ""; + for (let i = 0; i < length; i++) { + const randomIndex = Math.floor(Math.random() * numbers.length); + result += numbers[randomIndex]; + } + + return result; +} diff --git a/src/configs/_env.ts b/src/configs/_env.ts index 812117f..aad8519 100644 --- a/src/configs/_env.ts +++ b/src/configs/_env.ts @@ -16,5 +16,41 @@ export const _env = z DATABASE_USERNAME: z.string("Must be string.").nonempty("Must not empty."), DATABASE_PASSWORD: z.string("Must be string.").nonempty("Must not empty."), DATABASE_NAME: z.string("Must be string.").nonempty("Must not empty."), + + JWT_SECRET: z.string("Must be string.").nonempty("Must not empty."), + JWT_ALGORITHM: z.enum( + [ + "HS256", + "HS384", + "HS512", + "RS256", + "RS384", + "RS512", + "ES256", + "ES384", + "ES512", + "PS256", + "PS384", + "PS512", + ], + "Invalid value.", + ), + JWT_ISSUER: z.string("Must be string.").nonempty("Must not empty."), + + MIDTRANS_BASE_URL: z.url("Must be valid URL.").nonempty("Must not empty."), + MIDTRANS_MERCHANT_ID: z + .string("Must be string.") + .nonempty("Must not empty."), + MIDTRANS_SERVER_KEY: z + .string("Must be string.") + .nonempty("Must not empty."), + + MAIL_HOST: z.string("Must be string.").nonempty("Must not empty."), + MAIL_PORT: z.coerce + .number("Must be number.") + .int("Must be integer.") + .min(0, "Min 0."), + MAIL_USERNAME: z.string("Must be string.").nonempty("Must not empty."), + MAIL_PASSWORD: z.string("Must be string.").nonempty("Must not empty."), }) .parse(Bun.env); diff --git a/src/configs/jwt.config.ts b/src/configs/jwt.config.ts new file mode 100644 index 0000000..3d8d9be --- /dev/null +++ b/src/configs/jwt.config.ts @@ -0,0 +1,7 @@ +import { _env } from "@/configs/_env"; + +export const jwtConfig = { + secret: _env.JWT_SECRET, + algorithm: _env.JWT_ALGORITHM, + issuer: _env.JWT_ISSUER, +} as const; diff --git a/src/configs/mail.config.ts b/src/configs/mail.config.ts new file mode 100644 index 0000000..12013b5 --- /dev/null +++ b/src/configs/mail.config.ts @@ -0,0 +1,8 @@ +import { _env } from "@/configs/_env"; + +export const mailConfig = { + host: _env.MAIL_HOST, + port: _env.MAIL_PORT, + username: _env.MAIL_USERNAME, + password: _env.MAIL_PASSWORD, +} as const; diff --git a/src/configs/midtrans.config.ts b/src/configs/midtrans.config.ts new file mode 100644 index 0000000..19beadf --- /dev/null +++ b/src/configs/midtrans.config.ts @@ -0,0 +1,7 @@ +import { _env } from "@/configs/_env"; + +export const midtransConfig = { + baseUrl: _env.MIDTRANS_BASE_URL, + merchantId: _env.MIDTRANS_MERCHANT_ID, + serverKey: _env.MIDTRANS_SERVER_KEY, +} as const; diff --git a/src/database/entities/admin.entity.ts b/src/database/entities/admin.entity.ts new file mode 100644 index 0000000..29f41fa --- /dev/null +++ b/src/database/entities/admin.entity.ts @@ -0,0 +1,51 @@ +import { Verification } from "@/database/entities/verification.entity"; +import { AdminPermission } from "@/database/enums/admin-permission.enum"; +import { + Entity, + Enum, + ManyToOne, + PrimaryKey, + Property, + type Rel, +} from "@mikro-orm/core"; + +@Entity() +export class Admin { + @PrimaryKey({ type: "varchar", length: 30 }) + id!: string; + + @Property({ type: "varchar", length: 100 }) + name!: string; + + @Property({ type: "varchar", length: 254, unique: true }) + email!: string; + + @Property({ type: "text" }) + password!: string; + + @Property({ type: "varchar", length: 100, nullable: true }) + avatar!: string | null; + + @Enum({ + items: () => AdminPermission, + array: true, + nativeEnumName: "admin_permission", + }) + permissions!: AdminPermission[]; + + @ManyToOne(() => Verification, { nullable: true }) + verification!: Rel | null; + + @Property({ + type: "timestamp", + onCreate: () => new Date(), + }) + createdAt!: Date; + + @Property({ + type: "timestamp", + onCreate: () => new Date(), + onUpdate: () => new Date(), + }) + updatedAt!: Date; +} diff --git a/src/database/entities/airline.entity.ts b/src/database/entities/airline.entity.ts index 50633c0..e716d61 100644 --- a/src/database/entities/airline.entity.ts +++ b/src/database/entities/airline.entity.ts @@ -14,13 +14,12 @@ export class Airline { code!: string; @Property({ type: "varchar", length: 100 }) - @Unique() logo!: string; @Property({ type: "int", unsigned: true }) skytraxRating!: number; - @Enum(() => SkytraxType) + @Enum({ items: () => SkytraxType, nativeEnumName: "skytrax_type" }) skytraxType!: SkytraxType; @Property({ diff --git a/src/database/entities/order-detail.entity.ts b/src/database/entities/order-detail.entity.ts new file mode 100644 index 0000000..3e2a2c1 --- /dev/null +++ b/src/database/entities/order-detail.entity.ts @@ -0,0 +1,35 @@ +import { Order } from "@/database/entities/order.entity"; +import { RoomType } from "@/database/enums/room-type.enum"; +import { + Entity, + Enum, + ManyToOne, + PrimaryKey, + Property, + type Rel, +} from "@mikro-orm/core"; + +@Entity() +export class OrderDetail { + @PrimaryKey({ type: "varchar", length: 30 }) + id!: string; + + @ManyToOne(() => Order) + order!: Rel; + + @Enum({ items: () => RoomType, nativeEnumName: "room_type" }) + roomType!: RoomType; + + @Property({ + type: "timestamp", + onCreate: () => new Date(), + }) + createdAt!: Date; + + @Property({ + type: "timestamp", + onCreate: () => new Date(), + onUpdate: () => new Date(), + }) + updatedAt!: Date; +} diff --git a/src/database/entities/order.entity.ts b/src/database/entities/order.entity.ts new file mode 100644 index 0000000..76ab940 --- /dev/null +++ b/src/database/entities/order.entity.ts @@ -0,0 +1,61 @@ +import { OrderDetail } from "@/database/entities/order-detail.entity"; +import { PackageDetail } from "@/database/entities/package-detail.entity"; +import { Partner } from "@/database/entities/partner.entity"; +import { Verification } from "@/database/entities/verification.entity"; +import { + Collection, + Entity, + ManyToOne, + OneToMany, + PrimaryKey, + Property, + type Rel, +} from "@mikro-orm/core"; + +@Entity() +export class Order { + @PrimaryKey({ type: "varchar", length: 30 }) + id!: string; + + @ManyToOne(() => PackageDetail) + package!: Rel; + + @Property({ type: "varchar", length: 100 }) + name!: string; + + @Property({ type: "varchar", length: 20 }) + whatsapp!: string; + + @ManyToOne(() => Verification, { nullable: true }) + verification!: Rel | null; + + @ManyToOne(() => Partner, { nullable: true }) + partner!: Rel; + + @Property({ type: "timestamp", nullable: true }) + expiredAt!: Date | null; + + @Property({ type: "timestamp", nullable: true }) + purchasedAt!: Date | null; + + @Property({ type: "timestamp", nullable: true }) + finishedAt!: Date | null; + + @Property({ + type: "timestamp", + onCreate: () => new Date(), + }) + createdAt!: Date; + + @Property({ + type: "timestamp", + onCreate: () => new Date(), + onUpdate: () => new Date(), + }) + updatedAt!: Date; + + // Collections + + @OneToMany(() => OrderDetail, (orderDetail) => orderDetail.order) + details = new Collection(this); +} diff --git a/src/database/entities/package-itinerary-widget.entity.ts b/src/database/entities/package-itinerary-widget.entity.ts index 4b558d6..e316b28 100644 --- a/src/database/entities/package-itinerary-widget.entity.ts +++ b/src/database/entities/package-itinerary-widget.entity.ts @@ -21,7 +21,10 @@ export abstract class PackageItineraryWidget { @ManyToOne(() => PackageItineraryDay) packageItineraryDay!: Rel; - @Enum(() => PackageItineraryWidgetType) + @Enum({ + type: () => PackageItineraryWidgetType, + nativeEnumName: "package_itinerary_widget_type", + }) type!: PackageItineraryWidgetType; @Property({ diff --git a/src/database/entities/package.entity.ts b/src/database/entities/package.entity.ts index a6c3034..dd8f100 100644 --- a/src/database/entities/package.entity.ts +++ b/src/database/entities/package.entity.ts @@ -9,7 +9,6 @@ import { OneToMany, PrimaryKey, Property, - Unique, } from "@mikro-orm/core"; @Entity() @@ -20,14 +19,13 @@ export class Package { @Property({ type: "varchar", length: 100 }) name!: string; - @Enum(() => PackageType) + @Enum({ items: () => PackageType, nativeEnumName: "package_type" }) type!: PackageType; - @Enum(() => PackageClass) + @Enum({ items: () => PackageClass, nativeEnumName: "package_class" }) class!: PackageClass; @Property({ type: "varchar", length: 100 }) - @Unique() thumbnail!: string; @Property({ type: "boolean" }) diff --git a/src/database/entities/partner.entity.ts b/src/database/entities/partner.entity.ts new file mode 100644 index 0000000..1657b0d --- /dev/null +++ b/src/database/entities/partner.entity.ts @@ -0,0 +1,53 @@ +import { Order } from "@/database/entities/order.entity"; +import { Verification } from "@/database/entities/verification.entity"; +import { + Collection, + Entity, + ManyToOne, + OneToMany, + PrimaryKey, + Property, + type Rel, +} from "@mikro-orm/core"; + +@Entity() +export class Partner { + @PrimaryKey({ type: "varchar", length: 30 }) + id!: string; + + @Property({ type: "varchar", length: 100 }) + name!: string; + + @Property({ type: "varchar", length: 254, unique: true }) + email!: string; + + @Property({ type: "varchar", length: 20, unique: true }) + whatsapp!: string; + + @Property({ type: "text" }) + password!: string; + + @Property({ type: "varchar", length: 100, nullable: true }) + avatar!: string | null; + + @ManyToOne(() => Verification, { nullable: true }) + verification!: Rel | null; + + @Property({ + type: "timestamp", + onCreate: () => new Date(), + }) + createdAt!: Date; + + @Property({ + type: "timestamp", + onCreate: () => new Date(), + onUpdate: () => new Date(), + }) + updatedAt!: Date; + + // Collections + + @OneToMany(() => Order, (order) => order.partner) + orders = new Collection(this); +} diff --git a/src/database/entities/verification.entity.ts b/src/database/entities/verification.entity.ts new file mode 100644 index 0000000..0b5766a --- /dev/null +++ b/src/database/entities/verification.entity.ts @@ -0,0 +1,35 @@ +import { VerificationType } from "@/database/enums/verification-type.enum"; +import { Entity, Enum, PrimaryKey, Property } from "@mikro-orm/core"; + +@Entity() +export class Verification { + @PrimaryKey({ type: "varchar", length: 30 }) + id!: string; + + @Property({ type: "char", length: 6 }) + code!: string; + + @Enum({ + items: () => VerificationType, + nativeEnumName: "verification_type", + }) + type!: VerificationType; + + @Property({ + type: "timestamp", + }) + expiredAt!: Date; + + @Property({ + type: "timestamp", + onCreate: () => new Date(), + }) + createdAt!: Date; + + @Property({ + type: "timestamp", + onCreate: () => new Date(), + onUpdate: () => new Date(), + }) + updatedAt!: Date; +} diff --git a/src/database/enums/admin-permission.enum.ts b/src/database/enums/admin-permission.enum.ts new file mode 100644 index 0000000..9e5a439 --- /dev/null +++ b/src/database/enums/admin-permission.enum.ts @@ -0,0 +1,58 @@ +export enum AdminPermission { + // Country permissions + createCountry = "country:create", + updateCountry = "country:update", + deleteCountry = "country:delete", + // City permissions + createCity = "city:create", + updateCity = "city:update", + deleteCity = "city:delete", + // Airline permissions + createAirline = "airline:create", + updateAirline = "airline:update", + deleteAirline = "airline:delete", + // Airport permissions + createAirport = "airport:create", + updateAirport = "airport:update", + deleteAirport = "airport:delete", + // Flight permissions + createFlight = "flight:create", + updateFlight = "flight:update", + deleteFlight = "flight:delete", + // Flight class permissions + createFlightClass = "flight-class:create", + updateFlightClass = "flight-class:update", + deleteFlightClass = "flight-class:delete", + // Hotel facility permissions + createHotelFacility = "hotel-facility:create", + updateHotelFacility = "hotel-facility:update", + deleteHotelFacility = "hotel-facility:delete", + // Hotel permissions + createHotel = "hotel:create", + updateHotel = "hotel:update", + deleteHotel = "hotel:delete", + // Transportation permissions + createTransportation = "transportation:create", + updateTransportation = "transportation:update", + deleteTransportation = "transportation:delete", + // Transportation class permissions + createTransportationClass = "transportation-class:create", + updateTransportationClass = "transportation-class:update", + deleteTransportationClass = "transportation-class:delete", + // Package permissions + createPackage = "package:create", + updatePackage = "package:update", + deletePackage = "package:delete", + // Package detail permissions + createPackageDetail = "package-detail:create", + updatePackageDetail = "package-detail:update", + deletePackageDetail = "package-detail:delete", + // Admin permissions + createAdmin = "admin:create", + updateAdmin = "admin:update", + deleteAdmin = "admin:delete", + // Partner permissions + createPartner = "partner:create", + updatePartner = "partner:update", + deletePartner = "partner:delete", +} diff --git a/src/database/enums/room-type.enum.ts b/src/database/enums/room-type.enum.ts new file mode 100644 index 0000000..ec72660 --- /dev/null +++ b/src/database/enums/room-type.enum.ts @@ -0,0 +1,6 @@ +export enum RoomType { + double = "double", + triple = "triple", + quad = "quad", + infant = "infant", +} diff --git a/src/database/enums/verification-type.enum.ts b/src/database/enums/verification-type.enum.ts new file mode 100644 index 0000000..9480dc4 --- /dev/null +++ b/src/database/enums/verification-type.enum.ts @@ -0,0 +1,14 @@ +export enum VerificationType { + // Admin verifications + createAdmin = "admin:create", + changeEmailAdmin = "admin:changeEmail", + changePasswordAdmin = "admin:changePassword", + updateAdmin = "admin:update", + // Partner verifications + createPartner = "partner:create", + changeEmailPartner = "partner:changeEmail", + changePasswordPartner = "partner:changePassword", + updatePartner = "partner:update", + // Order verifications + createOrder = "order:create", +} diff --git a/src/database/migrations/.snapshot-goumrah.json b/src/database/migrations/.snapshot-goumrah.json index b800745..873b77e 100644 --- a/src/database/migrations/.snapshot-goumrah.json +++ b/src/database/migrations/.snapshot-goumrah.json @@ -1,2501 +1,5402 @@ { - "namespaces": [ - "public" - ], - "name": "public", - "tables": [ - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "name": { - "name": "name", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "code": { - "name": "code", - "type": "varchar(10)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 10, - "mappedType": "string" - }, - "logo": { - "name": "logo", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "skytrax_rating": { - "name": "skytrax_rating", - "type": "int", - "unsigned": true, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - }, - "skytrax_type": { - "name": "skytrax_type", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "enumItems": [ - "full_service", - "low_cost" - ], - "mappedType": "enum" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "airline", - "schema": "public", - "indexes": [ - { - "columnNames": [ - "code" - ], - "composite": false, - "keyName": "airline_code_unique", - "constraint": true, - "primary": false, - "unique": true - }, - { - "columnNames": [ - "logo" - ], - "composite": false, - "keyName": "airline_logo_unique", - "constraint": true, - "primary": false, - "unique": true - }, - { - "keyName": "airline_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": {}, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "name": { - "name": "name", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "country", - "schema": "public", - "indexes": [ - { - "keyName": "country_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": {}, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "name": { - "name": "name", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "country_id": { - "name": "country_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "city", - "schema": "public", - "indexes": [ - { - "keyName": "city_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "city_country_id_foreign": { - "constraintName": "city_country_id_foreign", - "columnNames": [ - "country_id" - ], - "localTableName": "public.city", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.country", - "updateRule": "cascade" - } - }, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "name": { - "name": "name", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "code": { - "name": "code", - "type": "varchar(10)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 10, - "mappedType": "string" - }, - "city_id": { - "name": "city_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "airport", - "schema": "public", - "indexes": [ - { - "columnNames": [ - "code" - ], - "composite": false, - "keyName": "airport_code_unique", - "constraint": true, - "primary": false, - "unique": true - }, - { - "keyName": "airport_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "airport_city_id_foreign": { - "constraintName": "airport_city_id_foreign", - "columnNames": [ - "city_id" - ], - "localTableName": "public.airport", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.city", - "updateRule": "cascade" - } - }, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "airline_id": { - "name": "airline_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "number": { - "name": "number", - "type": "int", - "unsigned": true, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - }, - "departure_airport_id": { - "name": "departure_airport_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "departure_terminal": { - "name": "departure_terminal", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 100, - "mappedType": "string" - }, - "departure_gate": { - "name": "departure_gate", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 100, - "mappedType": "string" - }, - "departure_time": { - "name": "departure_time", - "type": "time(0)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 0, - "mappedType": "time" - }, - "arrival_airport_id": { - "name": "arrival_airport_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "arrival_terminal": { - "name": "arrival_terminal", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 100, - "mappedType": "string" - }, - "arrival_gate": { - "name": "arrival_gate", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 100, - "mappedType": "string" - }, - "duration": { - "name": "duration", - "type": "int", - "unsigned": true, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - }, - "aircraft": { - "name": "aircraft", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "flight", - "schema": "public", - "indexes": [ - { - "keyName": "flight_airline_id_number_unique", - "columnNames": [ - "airline_id", - "number" - ], - "composite": true, - "constraint": true, - "primary": false, - "unique": true - }, - { - "keyName": "flight_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "flight_airline_id_foreign": { - "constraintName": "flight_airline_id_foreign", - "columnNames": [ - "airline_id" - ], - "localTableName": "public.flight", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.airline", - "updateRule": "cascade" - }, - "flight_departure_airport_id_foreign": { - "constraintName": "flight_departure_airport_id_foreign", - "columnNames": [ - "departure_airport_id" - ], - "localTableName": "public.flight", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.airport", - "updateRule": "cascade" - }, - "flight_arrival_airport_id_foreign": { - "constraintName": "flight_arrival_airport_id_foreign", - "columnNames": [ - "arrival_airport_id" - ], - "localTableName": "public.flight", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.airport", - "updateRule": "cascade" - } - }, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "flight_id": { - "name": "flight_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "class": { - "name": "class", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "seat_layout": { - "name": "seat_layout", - "type": "varchar(10)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 10, - "mappedType": "string" - }, - "baggage": { - "name": "baggage", - "type": "int", - "unsigned": true, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - }, - "cabin_baggage": { - "name": "cabin_baggage", - "type": "int", - "unsigned": true, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "flight_class", - "schema": "public", - "indexes": [ - { - "keyName": "flight_class_flight_id_class_unique", - "columnNames": [ - "flight_id", - "class" - ], - "composite": true, - "constraint": true, - "primary": false, - "unique": true - }, - { - "keyName": "flight_class_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "flight_class_flight_id_foreign": { - "constraintName": "flight_class_flight_id_foreign", - "columnNames": [ - "flight_id" - ], - "localTableName": "public.flight_class", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.flight", - "updateRule": "cascade" - } - }, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "flight_id": { - "name": "flight_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "next_id": { - "name": "next_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 30, - "mappedType": "string" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "flight_schedule", - "schema": "public", - "indexes": [ - { - "keyName": "flight_schedule_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "flight_schedule_flight_id_foreign": { - "constraintName": "flight_schedule_flight_id_foreign", - "columnNames": [ - "flight_id" - ], - "localTableName": "public.flight_schedule", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.flight_class", - "updateRule": "cascade" - }, - "flight_schedule_next_id_foreign": { - "constraintName": "flight_schedule_next_id_foreign", - "columnNames": [ - "next_id" - ], - "localTableName": "public.flight_schedule", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.flight_schedule", - "deleteRule": "cascade" - } - }, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "name": { - "name": "name", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "city_id": { - "name": "city_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "star": { - "name": "star", - "type": "int", - "unsigned": true, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - }, - "google_maps_link": { - "name": "google_maps_link", - "type": "varchar(1000)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 1000, - "mappedType": "string" - }, - "google_maps_embed": { - "name": "google_maps_embed", - "type": "varchar(1000)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 1000, - "mappedType": "string" - }, - "google_reviews_link": { - "name": "google_reviews_link", - "type": "varchar(1000)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 1000, - "mappedType": "string" - }, - "description": { - "name": "description", - "type": "varchar(1000)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 1000, - "mappedType": "string" - }, - "address": { - "name": "address", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "landmark": { - "name": "landmark", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "distance_to_landmark": { - "name": "distance_to_landmark", - "type": "numeric(10,0)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "precision": 10, - "scale": 0, - "mappedType": "decimal" - }, - "food_type": { - "name": "food_type", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "food_amount": { - "name": "food_amount", - "type": "int", - "unsigned": true, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - }, - "food_menu": { - "name": "food_menu", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "hotel", - "schema": "public", - "indexes": [ - { - "keyName": "hotel_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "hotel_city_id_foreign": { - "constraintName": "hotel_city_id_foreign", - "columnNames": [ - "city_id" - ], - "localTableName": "public.hotel", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.city", - "updateRule": "cascade" - } - }, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "name": { - "name": "name", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "icon": { - "name": "icon", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "hotel_facility", - "schema": "public", - "indexes": [ - { - "keyName": "hotel_facility_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": {}, - "nativeEnums": {} - }, - { - "columns": { - "hotel_id": { - "name": "hotel_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "hotel_facility_id": { - "name": "hotel_facility_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - } - }, - "name": "hotel_facilities", - "schema": "public", - "indexes": [ - { - "keyName": "hotel_facilities_pkey", - "columnNames": [ - "hotel_id", - "hotel_facility_id" - ], - "composite": true, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "hotel_facilities_hotel_id_foreign": { - "constraintName": "hotel_facilities_hotel_id_foreign", - "columnNames": [ - "hotel_id" - ], - "localTableName": "public.hotel_facilities", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.hotel", - "deleteRule": "cascade", - "updateRule": "cascade" - }, - "hotel_facilities_hotel_facility_id_foreign": { - "constraintName": "hotel_facilities_hotel_facility_id_foreign", - "columnNames": [ - "hotel_facility_id" - ], - "localTableName": "public.hotel_facilities", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.hotel_facility", - "deleteRule": "cascade", - "updateRule": "cascade" - } - }, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "hotel_id": { - "name": "hotel_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "src": { - "name": "src", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "created_at": { - "name": "created_at", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "hotel_image", - "schema": "public", - "indexes": [ - { - "keyName": "hotel_image_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "hotel_image_hotel_id_foreign": { - "constraintName": "hotel_image_hotel_id_foreign", - "columnNames": [ - "hotel_id" - ], - "localTableName": "public.hotel_image", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.hotel", - "updateRule": "cascade" - } - }, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "hotel_id": { - "name": "hotel_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "check_in": { - "name": "check_in", - "type": "time(0)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 0, - "mappedType": "time" - }, - "check_out": { - "name": "check_out", - "type": "time(0)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 0, - "mappedType": "time" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "hotel_schedule", - "schema": "public", - "indexes": [ - { - "keyName": "hotel_schedule_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "hotel_schedule_hotel_id_foreign": { - "constraintName": "hotel_schedule_hotel_id_foreign", - "columnNames": [ - "hotel_id" - ], - "localTableName": "public.hotel_schedule", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.hotel", - "updateRule": "cascade" - } - }, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "name": { - "name": "name", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "type": { - "name": "type", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "enumItems": [ - "reguler", - "plus" - ], - "mappedType": "enum" - }, - "class": { - "name": "class", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "enumItems": [ - "silver", - "gold", - "platinum" - ], - "mappedType": "enum" - }, - "thumbnail": { - "name": "thumbnail", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "use_fast_train": { - "name": "use_fast_train", - "type": "boolean", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "boolean" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "package", - "schema": "public", - "indexes": [ - { - "columnNames": [ - "thumbnail" - ], - "composite": false, - "keyName": "package_thumbnail_unique", - "constraint": true, - "primary": false, - "unique": true - }, - { - "keyName": "package_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": {}, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "title": { - "name": "title", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "description": { - "name": "description", - "type": "varchar(1000)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 1000, - "mappedType": "string" - }, - "next_id": { - "name": "next_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 30, - "mappedType": "string" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "package_itinerary_day", - "schema": "public", - "indexes": [ - { - "keyName": "package_itinerary_day_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "package_itinerary_day_next_id_foreign": { - "constraintName": "package_itinerary_day_next_id_foreign", - "columnNames": [ - "next_id" - ], - "localTableName": "public.package_itinerary_day", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.package_itinerary_day", - "deleteRule": "cascade" - } - }, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "location": { - "name": "location", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "day_id": { - "name": "day_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 30, - "mappedType": "string" - }, - "next_id": { - "name": "next_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 30, - "mappedType": "string" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "package_itinerary", - "schema": "public", - "indexes": [ - { - "keyName": "package_itinerary_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "package_itinerary_day_id_foreign": { - "constraintName": "package_itinerary_day_id_foreign", - "columnNames": [ - "day_id" - ], - "localTableName": "public.package_itinerary", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.package_itinerary_day", - "deleteRule": "cascade" - }, - "package_itinerary_next_id_foreign": { - "constraintName": "package_itinerary_next_id_foreign", - "columnNames": [ - "next_id" - ], - "localTableName": "public.package_itinerary", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.package_itinerary", - "deleteRule": "cascade" - } - }, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "package_itinerary_id": { - "name": "package_itinerary_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "src": { - "name": "src", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "created_at": { - "name": "created_at", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "package_itinerary_image", - "schema": "public", - "indexes": [ - { - "keyName": "package_itinerary_image_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "package_itinerary_image_package_itinerary_id_foreign": { - "constraintName": "package_itinerary_image_package_itinerary_id_foreign", - "columnNames": [ - "package_itinerary_id" - ], - "localTableName": "public.package_itinerary_image", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.package_itinerary", - "updateRule": "cascade" - } - }, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "package_itinerary_day_id": { - "name": "package_itinerary_day_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "type": { - "name": "type", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "enumItems": [ - "transport", - "hotel", - "information" - ], - "mappedType": "enum" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "hotel_id": { - "name": "hotel_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 30, - "mappedType": "string" - }, - "description": { - "name": "description", - "type": "varchar(1000)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 1000, - "mappedType": "string" - }, - "transportation": { - "name": "transportation", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 100, - "mappedType": "string" - }, - "from": { - "name": "from", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 100, - "mappedType": "string" - }, - "to": { - "name": "to", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 100, - "mappedType": "string" - } - }, - "name": "package_itinerary_widget", - "schema": "public", - "indexes": [ - { - "columnNames": [ - "type" - ], - "composite": false, - "keyName": "package_itinerary_widget_type_index", - "constraint": false, - "primary": false, - "unique": false - }, - { - "keyName": "package_itinerary_widget_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "package_itinerary_widget_package_itinerary_day_id_foreign": { - "constraintName": "package_itinerary_widget_package_itinerary_day_id_foreign", - "columnNames": [ - "package_itinerary_day_id" - ], - "localTableName": "public.package_itinerary_widget", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.package_itinerary_day", - "updateRule": "cascade" - }, - "package_itinerary_widget_hotel_id_foreign": { - "constraintName": "package_itinerary_widget_hotel_id_foreign", - "columnNames": [ - "hotel_id" - ], - "localTableName": "public.package_itinerary_widget", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.hotel", - "deleteRule": "set null", - "updateRule": "cascade" - } - }, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "name": { - "name": "name", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "type": { - "name": "type", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "transportation", - "schema": "public", - "indexes": [ - { - "keyName": "transportation_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": {}, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "transportation_id": { - "name": "transportation_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "class": { - "name": "class", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "total_seats": { - "name": "total_seats", - "type": "int", - "unsigned": true, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "transportation_class", - "schema": "public", - "indexes": [ - { - "keyName": "transportation_class_transportation_id_class_unique", - "columnNames": [ - "transportation_id", - "class" - ], - "composite": true, - "constraint": true, - "primary": false, - "unique": true - }, - { - "keyName": "transportation_class_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "transportation_class_transportation_id_foreign": { - "constraintName": "transportation_class_transportation_id_foreign", - "columnNames": [ - "transportation_id" - ], - "localTableName": "public.transportation_class", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.transportation", - "updateRule": "cascade" - } - }, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "package_id": { - "name": "package_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "departure_date": { - "name": "departure_date", - "type": "date", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 0, - "mappedType": "date" - }, - "tour_flight_id": { - "name": "tour_flight_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 30, - "mappedType": "string" - }, - "outbound_flight_id": { - "name": "outbound_flight_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 30, - "mappedType": "string" - }, - "inbound_flight_id": { - "name": "inbound_flight_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 30, - "mappedType": "string" - }, - "makkah_hotel_id": { - "name": "makkah_hotel_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 30, - "mappedType": "string" - }, - "madinah_hotel_id": { - "name": "madinah_hotel_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 30, - "mappedType": "string" - }, - "transportation_id": { - "name": "transportation_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "quad_price": { - "name": "quad_price", - "type": "numeric(10,0)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "precision": 10, - "scale": 0, - "mappedType": "decimal" - }, - "triple_price": { - "name": "triple_price", - "type": "numeric(10,0)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "precision": 10, - "scale": 0, - "mappedType": "decimal" - }, - "double_price": { - "name": "double_price", - "type": "numeric(10,0)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "precision": 10, - "scale": 0, - "mappedType": "decimal" - }, - "infant_price": { - "name": "infant_price", - "type": "numeric(10,0)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "precision": 10, - "scale": 0, - "mappedType": "decimal" - }, - "itinerary_id": { - "name": "itinerary_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 30, - "mappedType": "string" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "package_detail", - "schema": "public", - "indexes": [ - { - "keyName": "package_detail_package_id_departure_date_unique", - "columnNames": [ - "package_id", - "departure_date" - ], - "composite": true, - "constraint": true, - "primary": false, - "unique": true - }, - { - "keyName": "package_detail_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "package_detail_package_id_foreign": { - "constraintName": "package_detail_package_id_foreign", - "columnNames": [ - "package_id" - ], - "localTableName": "public.package_detail", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.package", - "updateRule": "cascade" - }, - "package_detail_tour_flight_id_foreign": { - "constraintName": "package_detail_tour_flight_id_foreign", - "columnNames": [ - "tour_flight_id" - ], - "localTableName": "public.package_detail", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.flight_schedule", - "deleteRule": "cascade" - }, - "package_detail_outbound_flight_id_foreign": { - "constraintName": "package_detail_outbound_flight_id_foreign", - "columnNames": [ - "outbound_flight_id" - ], - "localTableName": "public.package_detail", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.flight_schedule", - "deleteRule": "cascade" - }, - "package_detail_inbound_flight_id_foreign": { - "constraintName": "package_detail_inbound_flight_id_foreign", - "columnNames": [ - "inbound_flight_id" - ], - "localTableName": "public.package_detail", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.flight_schedule", - "deleteRule": "cascade" - }, - "package_detail_makkah_hotel_id_foreign": { - "constraintName": "package_detail_makkah_hotel_id_foreign", - "columnNames": [ - "makkah_hotel_id" - ], - "localTableName": "public.package_detail", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.hotel_schedule", - "deleteRule": "cascade" - }, - "package_detail_madinah_hotel_id_foreign": { - "constraintName": "package_detail_madinah_hotel_id_foreign", - "columnNames": [ - "madinah_hotel_id" - ], - "localTableName": "public.package_detail", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.hotel_schedule", - "deleteRule": "cascade" - }, - "package_detail_transportation_id_foreign": { - "constraintName": "package_detail_transportation_id_foreign", - "columnNames": [ - "transportation_id" - ], - "localTableName": "public.package_detail", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.transportation_class", - "updateRule": "cascade" - }, - "package_detail_itinerary_id_foreign": { - "constraintName": "package_detail_itinerary_id_foreign", - "columnNames": [ - "itinerary_id" - ], - "localTableName": "public.package_detail", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.package_itinerary", - "deleteRule": "cascade" - } - }, - "nativeEnums": {} - }, - { - "columns": { - "package_detail_id": { - "name": "package_detail_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "hotel_schedule_id": { - "name": "hotel_schedule_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - } - }, - "name": "package_detail_tour_hotels", - "schema": "public", - "indexes": [ - { - "keyName": "package_detail_tour_hotels_pkey", - "columnNames": [ - "package_detail_id", - "hotel_schedule_id" - ], - "composite": true, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "package_detail_tour_hotels_package_detail_id_foreign": { - "constraintName": "package_detail_tour_hotels_package_detail_id_foreign", - "columnNames": [ - "package_detail_id" - ], - "localTableName": "public.package_detail_tour_hotels", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.package_detail", - "deleteRule": "cascade", - "updateRule": "cascade" - }, - "package_detail_tour_hotels_hotel_schedule_id_foreign": { - "constraintName": "package_detail_tour_hotels_hotel_schedule_id_foreign", - "columnNames": [ - "hotel_schedule_id" - ], - "localTableName": "public.package_detail_tour_hotels", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.hotel_schedule", - "deleteRule": "cascade", - "updateRule": "cascade" - } - }, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "transportation_id": { - "name": "transportation_id", - "type": "varchar(30)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 30, - "mappedType": "string" - }, - "src": { - "name": "src", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "created_at": { - "name": "created_at", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "transportation_image", - "schema": "public", - "indexes": [ - { - "keyName": "transportation_image_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "transportation_image_transportation_id_foreign": { - "constraintName": "transportation_image_transportation_id_foreign", - "columnNames": [ - "transportation_id" - ], - "localTableName": "public.transportation_image", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.transportation_class", - "updateRule": "cascade" - } - }, - "nativeEnums": {} - } - ], - "nativeEnums": {} + "namespaces": ["public"], + "name": "public", + "tables": [ + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "code": { + "name": "code", + "type": "varchar(10)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 10, + "mappedType": "string" + }, + "logo": { + "name": "logo", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "skytrax_rating": { + "name": "skytrax_rating", + "type": "int", + "unsigned": true, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + }, + "skytrax_type": { + "name": "skytrax_type", + "type": "skytrax_type", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "nativeEnumName": "skytrax_type", + "enumItems": ["full_service", "low_cost"], + "mappedType": "enum" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "airline", + "schema": "public", + "indexes": [ + { + "columnNames": ["code"], + "composite": false, + "keyName": "airline_code_unique", + "constraint": true, + "primary": false, + "unique": true + }, + { + "keyName": "airline_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {}, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "country", + "schema": "public", + "indexes": [ + { + "keyName": "country_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {}, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "country_id": { + "name": "country_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "city", + "schema": "public", + "indexes": [ + { + "keyName": "city_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "city_country_id_foreign": { + "constraintName": "city_country_id_foreign", + "columnNames": ["country_id"], + "localTableName": "public.city", + "referencedColumnNames": ["id"], + "referencedTableName": "public.country", + "updateRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "code": { + "name": "code", + "type": "varchar(10)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 10, + "mappedType": "string" + }, + "city_id": { + "name": "city_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "airport", + "schema": "public", + "indexes": [ + { + "columnNames": ["code"], + "composite": false, + "keyName": "airport_code_unique", + "constraint": true, + "primary": false, + "unique": true + }, + { + "keyName": "airport_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "airport_city_id_foreign": { + "constraintName": "airport_city_id_foreign", + "columnNames": ["city_id"], + "localTableName": "public.airport", + "referencedColumnNames": ["id"], + "referencedTableName": "public.city", + "updateRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "airline_id": { + "name": "airline_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "number": { + "name": "number", + "type": "int", + "unsigned": true, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + }, + "departure_airport_id": { + "name": "departure_airport_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "departure_terminal": { + "name": "departure_terminal", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 100, + "mappedType": "string" + }, + "departure_gate": { + "name": "departure_gate", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 100, + "mappedType": "string" + }, + "departure_time": { + "name": "departure_time", + "type": "time(0)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 0, + "mappedType": "time" + }, + "arrival_airport_id": { + "name": "arrival_airport_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "arrival_terminal": { + "name": "arrival_terminal", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 100, + "mappedType": "string" + }, + "arrival_gate": { + "name": "arrival_gate", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 100, + "mappedType": "string" + }, + "duration": { + "name": "duration", + "type": "int", + "unsigned": true, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + }, + "aircraft": { + "name": "aircraft", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "flight", + "schema": "public", + "indexes": [ + { + "keyName": "flight_airline_id_number_unique", + "columnNames": ["airline_id", "number"], + "composite": true, + "constraint": true, + "primary": false, + "unique": true + }, + { + "keyName": "flight_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "flight_airline_id_foreign": { + "constraintName": "flight_airline_id_foreign", + "columnNames": ["airline_id"], + "localTableName": "public.flight", + "referencedColumnNames": ["id"], + "referencedTableName": "public.airline", + "updateRule": "cascade" + }, + "flight_departure_airport_id_foreign": { + "constraintName": "flight_departure_airport_id_foreign", + "columnNames": ["departure_airport_id"], + "localTableName": "public.flight", + "referencedColumnNames": ["id"], + "referencedTableName": "public.airport", + "updateRule": "cascade" + }, + "flight_arrival_airport_id_foreign": { + "constraintName": "flight_arrival_airport_id_foreign", + "columnNames": ["arrival_airport_id"], + "localTableName": "public.flight", + "referencedColumnNames": ["id"], + "referencedTableName": "public.airport", + "updateRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "flight_id": { + "name": "flight_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "class": { + "name": "class", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "seat_layout": { + "name": "seat_layout", + "type": "varchar(10)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 10, + "mappedType": "string" + }, + "baggage": { + "name": "baggage", + "type": "int", + "unsigned": true, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + }, + "cabin_baggage": { + "name": "cabin_baggage", + "type": "int", + "unsigned": true, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "flight_class", + "schema": "public", + "indexes": [ + { + "keyName": "flight_class_flight_id_class_unique", + "columnNames": ["flight_id", "class"], + "composite": true, + "constraint": true, + "primary": false, + "unique": true + }, + { + "keyName": "flight_class_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "flight_class_flight_id_foreign": { + "constraintName": "flight_class_flight_id_foreign", + "columnNames": ["flight_id"], + "localTableName": "public.flight_class", + "referencedColumnNames": ["id"], + "referencedTableName": "public.flight", + "updateRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "flight_id": { + "name": "flight_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "next_id": { + "name": "next_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 30, + "mappedType": "string" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "flight_schedule", + "schema": "public", + "indexes": [ + { + "keyName": "flight_schedule_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "flight_schedule_flight_id_foreign": { + "constraintName": "flight_schedule_flight_id_foreign", + "columnNames": ["flight_id"], + "localTableName": "public.flight_schedule", + "referencedColumnNames": ["id"], + "referencedTableName": "public.flight_class", + "updateRule": "cascade" + }, + "flight_schedule_next_id_foreign": { + "constraintName": "flight_schedule_next_id_foreign", + "columnNames": ["next_id"], + "localTableName": "public.flight_schedule", + "referencedColumnNames": ["id"], + "referencedTableName": "public.flight_schedule", + "deleteRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "city_id": { + "name": "city_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "star": { + "name": "star", + "type": "int", + "unsigned": true, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + }, + "google_maps_link": { + "name": "google_maps_link", + "type": "varchar(1000)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 1000, + "mappedType": "string" + }, + "google_maps_embed": { + "name": "google_maps_embed", + "type": "varchar(1000)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 1000, + "mappedType": "string" + }, + "google_reviews_link": { + "name": "google_reviews_link", + "type": "varchar(1000)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 1000, + "mappedType": "string" + }, + "description": { + "name": "description", + "type": "varchar(1000)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 1000, + "mappedType": "string" + }, + "address": { + "name": "address", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "landmark": { + "name": "landmark", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "distance_to_landmark": { + "name": "distance_to_landmark", + "type": "numeric(10,0)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "precision": 10, + "scale": 0, + "mappedType": "decimal" + }, + "food_type": { + "name": "food_type", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "food_amount": { + "name": "food_amount", + "type": "int", + "unsigned": true, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + }, + "food_menu": { + "name": "food_menu", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "hotel", + "schema": "public", + "indexes": [ + { + "keyName": "hotel_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "hotel_city_id_foreign": { + "constraintName": "hotel_city_id_foreign", + "columnNames": ["city_id"], + "localTableName": "public.hotel", + "referencedColumnNames": ["id"], + "referencedTableName": "public.city", + "updateRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "icon": { + "name": "icon", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "hotel_facility", + "schema": "public", + "indexes": [ + { + "keyName": "hotel_facility_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {}, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "hotel_id": { + "name": "hotel_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "hotel_facility_id": { + "name": "hotel_facility_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + } + }, + "name": "hotel_facilities", + "schema": "public", + "indexes": [ + { + "keyName": "hotel_facilities_pkey", + "columnNames": ["hotel_id", "hotel_facility_id"], + "composite": true, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "hotel_facilities_hotel_id_foreign": { + "constraintName": "hotel_facilities_hotel_id_foreign", + "columnNames": ["hotel_id"], + "localTableName": "public.hotel_facilities", + "referencedColumnNames": ["id"], + "referencedTableName": "public.hotel", + "deleteRule": "cascade", + "updateRule": "cascade" + }, + "hotel_facilities_hotel_facility_id_foreign": { + "constraintName": "hotel_facilities_hotel_facility_id_foreign", + "columnNames": ["hotel_facility_id"], + "localTableName": "public.hotel_facilities", + "referencedColumnNames": ["id"], + "referencedTableName": "public.hotel_facility", + "deleteRule": "cascade", + "updateRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "hotel_id": { + "name": "hotel_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "src": { + "name": "src", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "created_at": { + "name": "created_at", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "hotel_image", + "schema": "public", + "indexes": [ + { + "keyName": "hotel_image_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "hotel_image_hotel_id_foreign": { + "constraintName": "hotel_image_hotel_id_foreign", + "columnNames": ["hotel_id"], + "localTableName": "public.hotel_image", + "referencedColumnNames": ["id"], + "referencedTableName": "public.hotel", + "updateRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "hotel_id": { + "name": "hotel_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "check_in": { + "name": "check_in", + "type": "time(0)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 0, + "mappedType": "time" + }, + "check_out": { + "name": "check_out", + "type": "time(0)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 0, + "mappedType": "time" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "hotel_schedule", + "schema": "public", + "indexes": [ + { + "keyName": "hotel_schedule_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "hotel_schedule_hotel_id_foreign": { + "constraintName": "hotel_schedule_hotel_id_foreign", + "columnNames": ["hotel_id"], + "localTableName": "public.hotel_schedule", + "referencedColumnNames": ["id"], + "referencedTableName": "public.hotel", + "updateRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "type": { + "name": "type", + "type": "package_type", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "nativeEnumName": "package_type", + "enumItems": ["reguler", "plus"], + "mappedType": "enum" + }, + "class": { + "name": "class", + "type": "package_class", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "nativeEnumName": "package_class", + "enumItems": ["silver", "gold", "platinum"], + "mappedType": "enum" + }, + "thumbnail": { + "name": "thumbnail", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "use_fast_train": { + "name": "use_fast_train", + "type": "boolean", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "boolean" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "package", + "schema": "public", + "indexes": [ + { + "keyName": "package_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {}, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "title": { + "name": "title", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "description": { + "name": "description", + "type": "varchar(1000)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 1000, + "mappedType": "string" + }, + "next_id": { + "name": "next_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 30, + "mappedType": "string" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "package_itinerary_day", + "schema": "public", + "indexes": [ + { + "keyName": "package_itinerary_day_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "package_itinerary_day_next_id_foreign": { + "constraintName": "package_itinerary_day_next_id_foreign", + "columnNames": ["next_id"], + "localTableName": "public.package_itinerary_day", + "referencedColumnNames": ["id"], + "referencedTableName": "public.package_itinerary_day", + "deleteRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "location": { + "name": "location", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "day_id": { + "name": "day_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 30, + "mappedType": "string" + }, + "next_id": { + "name": "next_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 30, + "mappedType": "string" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "package_itinerary", + "schema": "public", + "indexes": [ + { + "keyName": "package_itinerary_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "package_itinerary_day_id_foreign": { + "constraintName": "package_itinerary_day_id_foreign", + "columnNames": ["day_id"], + "localTableName": "public.package_itinerary", + "referencedColumnNames": ["id"], + "referencedTableName": "public.package_itinerary_day", + "deleteRule": "cascade" + }, + "package_itinerary_next_id_foreign": { + "constraintName": "package_itinerary_next_id_foreign", + "columnNames": ["next_id"], + "localTableName": "public.package_itinerary", + "referencedColumnNames": ["id"], + "referencedTableName": "public.package_itinerary", + "deleteRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "package_itinerary_id": { + "name": "package_itinerary_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "src": { + "name": "src", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "created_at": { + "name": "created_at", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "package_itinerary_image", + "schema": "public", + "indexes": [ + { + "keyName": "package_itinerary_image_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "package_itinerary_image_package_itinerary_id_foreign": { + "constraintName": "package_itinerary_image_package_itinerary_id_foreign", + "columnNames": ["package_itinerary_id"], + "localTableName": "public.package_itinerary_image", + "referencedColumnNames": ["id"], + "referencedTableName": "public.package_itinerary", + "updateRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "package_itinerary_day_id": { + "name": "package_itinerary_day_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "type": { + "name": "type", + "type": "package_itinerary_widget_type", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "nativeEnumName": "package_itinerary_widget_type", + "enumItems": ["hotel", "information", "transport"], + "mappedType": "enum" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "hotel_id": { + "name": "hotel_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 30, + "mappedType": "string" + }, + "description": { + "name": "description", + "type": "varchar(1000)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 1000, + "mappedType": "string" + }, + "transportation": { + "name": "transportation", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 100, + "mappedType": "string" + }, + "from": { + "name": "from", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 100, + "mappedType": "string" + }, + "to": { + "name": "to", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 100, + "mappedType": "string" + } + }, + "name": "package_itinerary_widget", + "schema": "public", + "indexes": [ + { + "columnNames": ["type"], + "composite": false, + "keyName": "package_itinerary_widget_type_index", + "constraint": false, + "primary": false, + "unique": false + }, + { + "keyName": "package_itinerary_widget_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "package_itinerary_widget_package_itinerary_day_id_foreign": { + "constraintName": "package_itinerary_widget_package_itinerary_day_id_foreign", + "columnNames": ["package_itinerary_day_id"], + "localTableName": "public.package_itinerary_widget", + "referencedColumnNames": ["id"], + "referencedTableName": "public.package_itinerary_day", + "updateRule": "cascade" + }, + "package_itinerary_widget_hotel_id_foreign": { + "constraintName": "package_itinerary_widget_hotel_id_foreign", + "columnNames": ["hotel_id"], + "localTableName": "public.package_itinerary_widget", + "referencedColumnNames": ["id"], + "referencedTableName": "public.hotel", + "deleteRule": "set null", + "updateRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "type": { + "name": "type", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "transportation", + "schema": "public", + "indexes": [ + { + "keyName": "transportation_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {}, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "transportation_id": { + "name": "transportation_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "class": { + "name": "class", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "total_seats": { + "name": "total_seats", + "type": "int", + "unsigned": true, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "transportation_class", + "schema": "public", + "indexes": [ + { + "keyName": "transportation_class_transportation_id_class_unique", + "columnNames": ["transportation_id", "class"], + "composite": true, + "constraint": true, + "primary": false, + "unique": true + }, + { + "keyName": "transportation_class_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "transportation_class_transportation_id_foreign": { + "constraintName": "transportation_class_transportation_id_foreign", + "columnNames": ["transportation_id"], + "localTableName": "public.transportation_class", + "referencedColumnNames": ["id"], + "referencedTableName": "public.transportation", + "updateRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "package_id": { + "name": "package_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "departure_date": { + "name": "departure_date", + "type": "date", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 0, + "mappedType": "date" + }, + "tour_flight_id": { + "name": "tour_flight_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 30, + "mappedType": "string" + }, + "outbound_flight_id": { + "name": "outbound_flight_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 30, + "mappedType": "string" + }, + "inbound_flight_id": { + "name": "inbound_flight_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 30, + "mappedType": "string" + }, + "makkah_hotel_id": { + "name": "makkah_hotel_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 30, + "mappedType": "string" + }, + "madinah_hotel_id": { + "name": "madinah_hotel_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 30, + "mappedType": "string" + }, + "transportation_id": { + "name": "transportation_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "quad_price": { + "name": "quad_price", + "type": "numeric(10,0)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "precision": 10, + "scale": 0, + "mappedType": "decimal" + }, + "triple_price": { + "name": "triple_price", + "type": "numeric(10,0)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "precision": 10, + "scale": 0, + "mappedType": "decimal" + }, + "double_price": { + "name": "double_price", + "type": "numeric(10,0)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "precision": 10, + "scale": 0, + "mappedType": "decimal" + }, + "infant_price": { + "name": "infant_price", + "type": "numeric(10,0)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "precision": 10, + "scale": 0, + "mappedType": "decimal" + }, + "itinerary_id": { + "name": "itinerary_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 30, + "mappedType": "string" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "package_detail", + "schema": "public", + "indexes": [ + { + "keyName": "package_detail_package_id_departure_date_unique", + "columnNames": ["package_id", "departure_date"], + "composite": true, + "constraint": true, + "primary": false, + "unique": true + }, + { + "keyName": "package_detail_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "package_detail_package_id_foreign": { + "constraintName": "package_detail_package_id_foreign", + "columnNames": ["package_id"], + "localTableName": "public.package_detail", + "referencedColumnNames": ["id"], + "referencedTableName": "public.package", + "updateRule": "cascade" + }, + "package_detail_tour_flight_id_foreign": { + "constraintName": "package_detail_tour_flight_id_foreign", + "columnNames": ["tour_flight_id"], + "localTableName": "public.package_detail", + "referencedColumnNames": ["id"], + "referencedTableName": "public.flight_schedule", + "deleteRule": "cascade" + }, + "package_detail_outbound_flight_id_foreign": { + "constraintName": "package_detail_outbound_flight_id_foreign", + "columnNames": ["outbound_flight_id"], + "localTableName": "public.package_detail", + "referencedColumnNames": ["id"], + "referencedTableName": "public.flight_schedule", + "deleteRule": "cascade" + }, + "package_detail_inbound_flight_id_foreign": { + "constraintName": "package_detail_inbound_flight_id_foreign", + "columnNames": ["inbound_flight_id"], + "localTableName": "public.package_detail", + "referencedColumnNames": ["id"], + "referencedTableName": "public.flight_schedule", + "deleteRule": "cascade" + }, + "package_detail_makkah_hotel_id_foreign": { + "constraintName": "package_detail_makkah_hotel_id_foreign", + "columnNames": ["makkah_hotel_id"], + "localTableName": "public.package_detail", + "referencedColumnNames": ["id"], + "referencedTableName": "public.hotel_schedule", + "deleteRule": "cascade" + }, + "package_detail_madinah_hotel_id_foreign": { + "constraintName": "package_detail_madinah_hotel_id_foreign", + "columnNames": ["madinah_hotel_id"], + "localTableName": "public.package_detail", + "referencedColumnNames": ["id"], + "referencedTableName": "public.hotel_schedule", + "deleteRule": "cascade" + }, + "package_detail_transportation_id_foreign": { + "constraintName": "package_detail_transportation_id_foreign", + "columnNames": ["transportation_id"], + "localTableName": "public.package_detail", + "referencedColumnNames": ["id"], + "referencedTableName": "public.transportation_class", + "updateRule": "cascade" + }, + "package_detail_itinerary_id_foreign": { + "constraintName": "package_detail_itinerary_id_foreign", + "columnNames": ["itinerary_id"], + "localTableName": "public.package_detail", + "referencedColumnNames": ["id"], + "referencedTableName": "public.package_itinerary", + "deleteRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "package_detail_id": { + "name": "package_detail_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "hotel_schedule_id": { + "name": "hotel_schedule_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + } + }, + "name": "package_detail_tour_hotels", + "schema": "public", + "indexes": [ + { + "keyName": "package_detail_tour_hotels_pkey", + "columnNames": ["package_detail_id", "hotel_schedule_id"], + "composite": true, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "package_detail_tour_hotels_package_detail_id_foreign": { + "constraintName": "package_detail_tour_hotels_package_detail_id_foreign", + "columnNames": ["package_detail_id"], + "localTableName": "public.package_detail_tour_hotels", + "referencedColumnNames": ["id"], + "referencedTableName": "public.package_detail", + "deleteRule": "cascade", + "updateRule": "cascade" + }, + "package_detail_tour_hotels_hotel_schedule_id_foreign": { + "constraintName": "package_detail_tour_hotels_hotel_schedule_id_foreign", + "columnNames": ["hotel_schedule_id"], + "localTableName": "public.package_detail_tour_hotels", + "referencedColumnNames": ["id"], + "referencedTableName": "public.hotel_schedule", + "deleteRule": "cascade", + "updateRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "transportation_id": { + "name": "transportation_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "src": { + "name": "src", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "created_at": { + "name": "created_at", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "transportation_image", + "schema": "public", + "indexes": [ + { + "keyName": "transportation_image_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "transportation_image_transportation_id_foreign": { + "constraintName": "transportation_image_transportation_id_foreign", + "columnNames": ["transportation_id"], + "localTableName": "public.transportation_image", + "referencedColumnNames": ["id"], + "referencedTableName": "public.transportation_class", + "updateRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "code": { + "name": "code", + "type": "char(6)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "character" + }, + "type": { + "name": "type", + "type": "verification_type", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "nativeEnumName": "verification_type", + "enumItems": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ], + "mappedType": "enum" + }, + "expired_at": { + "name": "expired_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "verification", + "schema": "public", + "indexes": [ + { + "keyName": "verification_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {}, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "email": { + "name": "email", + "type": "varchar(254)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 254, + "mappedType": "string" + }, + "whatsapp": { + "name": "whatsapp", + "type": "varchar(20)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 20, + "mappedType": "string" + }, + "password": { + "name": "password", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "avatar": { + "name": "avatar", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 100, + "mappedType": "string" + }, + "verification_id": { + "name": "verification_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 30, + "mappedType": "string" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "partner", + "schema": "public", + "indexes": [ + { + "columnNames": ["email"], + "composite": false, + "keyName": "partner_email_unique", + "constraint": true, + "primary": false, + "unique": true + }, + { + "columnNames": ["whatsapp"], + "composite": false, + "keyName": "partner_whatsapp_unique", + "constraint": true, + "primary": false, + "unique": true + }, + { + "keyName": "partner_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "partner_verification_id_foreign": { + "constraintName": "partner_verification_id_foreign", + "columnNames": ["verification_id"], + "localTableName": "public.partner", + "referencedColumnNames": ["id"], + "referencedTableName": "public.verification", + "deleteRule": "set null", + "updateRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "package_id": { + "name": "package_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "whatsapp": { + "name": "whatsapp", + "type": "varchar(20)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 20, + "mappedType": "string" + }, + "verification_id": { + "name": "verification_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 30, + "mappedType": "string" + }, + "partner_id": { + "name": "partner_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 30, + "mappedType": "string" + }, + "expired_at": { + "name": "expired_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + }, + "purchased_at": { + "name": "purchased_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "order", + "schema": "public", + "indexes": [ + { + "keyName": "order_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "order_package_id_foreign": { + "constraintName": "order_package_id_foreign", + "columnNames": ["package_id"], + "localTableName": "public.order", + "referencedColumnNames": ["id"], + "referencedTableName": "public.package_detail", + "updateRule": "cascade" + }, + "order_verification_id_foreign": { + "constraintName": "order_verification_id_foreign", + "columnNames": ["verification_id"], + "localTableName": "public.order", + "referencedColumnNames": ["id"], + "referencedTableName": "public.verification", + "deleteRule": "set null", + "updateRule": "cascade" + }, + "order_partner_id_foreign": { + "constraintName": "order_partner_id_foreign", + "columnNames": ["partner_id"], + "localTableName": "public.order", + "referencedColumnNames": ["id"], + "referencedTableName": "public.partner", + "deleteRule": "set null", + "updateRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "order_id": { + "name": "order_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "room_type": { + "name": "room_type", + "type": "room_type", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "nativeEnumName": "room_type", + "enumItems": ["double", "triple", "quad", "infant"], + "mappedType": "enum" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "order_detail", + "schema": "public", + "indexes": [ + { + "keyName": "order_detail_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "order_detail_order_id_foreign": { + "constraintName": "order_detail_order_id_foreign", + "columnNames": ["order_id"], + "localTableName": "public.order_detail", + "referencedColumnNames": ["id"], + "referencedTableName": "public.order", + "updateRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 30, + "mappedType": "string" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "email": { + "name": "email", + "type": "varchar(254)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 254, + "mappedType": "string" + }, + "password": { + "name": "password", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "avatar": { + "name": "avatar", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 100, + "mappedType": "string" + }, + "permissions": { + "name": "permissions", + "type": "admin_permission[]", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "nativeEnumName": "admin_permission", + "enumItems": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ], + "mappedType": "array" + }, + "verification_id": { + "name": "verification_id", + "type": "varchar(30)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 30, + "mappedType": "string" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "admin", + "schema": "public", + "indexes": [ + { + "columnNames": ["email"], + "composite": false, + "keyName": "admin_email_unique", + "constraint": true, + "primary": false, + "unique": true + }, + { + "keyName": "admin_pkey", + "columnNames": ["id"], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "admin_verification_id_foreign": { + "constraintName": "admin_verification_id_foreign", + "columnNames": ["verification_id"], + "localTableName": "public.admin", + "referencedColumnNames": ["id"], + "referencedTableName": "public.verification", + "deleteRule": "set null", + "updateRule": "cascade" + } + }, + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } + } + ], + "nativeEnums": { + "skytrax_type": { + "name": "skytrax_type", + "schema": "public", + "items": ["full_service", "low_cost"] + }, + "package_type": { + "name": "package_type", + "schema": "public", + "items": ["reguler", "plus"] + }, + "package_class": { + "name": "package_class", + "schema": "public", + "items": ["silver", "gold", "platinum"] + }, + "package_itinerary_widget_type": { + "name": "package_itinerary_widget_type", + "schema": "public", + "items": ["hotel", "information", "transport"] + }, + "verification_type": { + "name": "verification_type", + "schema": "public", + "items": [ + "admin:create", + "admin:changeEmail", + "admin:changePassword", + "admin:update", + "partner:create", + "partner:changeEmail", + "partner:changePassword", + "partner:update", + "order:create" + ] + }, + "room_type": { + "name": "room_type", + "schema": "public", + "items": ["double", "triple", "quad", "infant"] + }, + "admin_permission": { + "name": "admin_permission", + "schema": "public", + "items": [ + "country:create", + "country:update", + "country:delete", + "city:create", + "city:update", + "city:delete", + "airline:create", + "airline:update", + "airline:delete", + "airport:create", + "airport:update", + "airport:delete", + "flight:create", + "flight:update", + "flight:delete", + "flight-class:create", + "flight-class:update", + "flight-class:delete", + "hotel-facility:create", + "hotel-facility:update", + "hotel-facility:delete", + "hotel:create", + "hotel:update", + "hotel:delete", + "transportation:create", + "transportation:update", + "transportation:delete", + "transportation-class:create", + "transportation-class:update", + "transportation-class:delete", + "package:create", + "package:update", + "package:delete", + "package-detail:create", + "package-detail:update", + "package-detail:delete", + "admin:create", + "admin:update", + "admin:delete", + "partner:create", + "partner:update", + "partner:delete" + ] + } + } } diff --git a/src/database/migrations/Migration20251112105413.ts b/src/database/migrations/Migration20251112105413.ts index 15de56f..e09c5ad 100644 --- a/src/database/migrations/Migration20251112105413.ts +++ b/src/database/migrations/Migration20251112105413.ts @@ -1,21 +1,35 @@ -import { Migration } from '@mikro-orm/migrations'; +import { Migration } from "@mikro-orm/migrations"; export class Migration20251112105413 extends Migration { + override async up(): Promise { + this.addSql( + `alter table "transportation_image" drop constraint "transportation_image_transportation_id_foreign";`, + ); - override async up(): Promise { - this.addSql(`alter table "transportation_image" drop constraint "transportation_image_transportation_id_foreign";`); + this.addSql( + `alter table "transportation_image" alter column "transportation_id" type varchar(30) using ("transportation_id"::varchar(30));`, + ); + this.addSql( + `alter table "transportation_image" alter column "transportation_id" drop not null;`, + ); + this.addSql( + `alter table "transportation_image" add constraint "transportation_image_transportation_id_foreign" foreign key ("transportation_id") references "transportation_class" ("id") on delete cascade;`, + ); + } - this.addSql(`alter table "transportation_image" alter column "transportation_id" type varchar(30) using ("transportation_id"::varchar(30));`); - this.addSql(`alter table "transportation_image" alter column "transportation_id" drop not null;`); - this.addSql(`alter table "transportation_image" add constraint "transportation_image_transportation_id_foreign" foreign key ("transportation_id") references "transportation_class" ("id") on delete cascade;`); - } - - override async down(): Promise { - this.addSql(`alter table "transportation_image" drop constraint "transportation_image_transportation_id_foreign";`); - - this.addSql(`alter table "transportation_image" alter column "transportation_id" type varchar(30) using ("transportation_id"::varchar(30));`); - this.addSql(`alter table "transportation_image" alter column "transportation_id" set not null;`); - this.addSql(`alter table "transportation_image" add constraint "transportation_image_transportation_id_foreign" foreign key ("transportation_id") references "transportation_class" ("id") on update cascade;`); - } + override async down(): Promise { + this.addSql( + `alter table "transportation_image" drop constraint "transportation_image_transportation_id_foreign";`, + ); + this.addSql( + `alter table "transportation_image" alter column "transportation_id" type varchar(30) using ("transportation_id"::varchar(30));`, + ); + this.addSql( + `alter table "transportation_image" alter column "transportation_id" set not null;`, + ); + this.addSql( + `alter table "transportation_image" add constraint "transportation_image_transportation_id_foreign" foreign key ("transportation_id") references "transportation_class" ("id") on update cascade;`, + ); + } } diff --git a/src/database/migrations/Migration20251119103455.ts b/src/database/migrations/Migration20251119103455.ts new file mode 100644 index 0000000..a0934ed --- /dev/null +++ b/src/database/migrations/Migration20251119103455.ts @@ -0,0 +1,184 @@ +import { Migration } from "@mikro-orm/migrations"; + +export class Migration20251119103455 extends Migration { + override async up(): Promise { + this.addSql( + `create type "skytrax_type" as enum ('full_service', 'low_cost');`, + ); + this.addSql(`create type "package_type" as enum ('reguler', 'plus');`); + this.addSql( + `create type "package_class" as enum ('silver', 'gold', 'platinum');`, + ); + this.addSql( + `create type "package_itinerary_widget_type" as enum ('hotel', 'information', 'transport');`, + ); + this.addSql( + `create type "verification_type" as enum ('admin:create', 'admin:changeEmail', 'admin:changePassword', 'admin:update', 'partner:create', 'partner:changeEmail', 'partner:changePassword', 'partner:update', 'order:create');`, + ); + this.addSql( + `create type "room_type" as enum ('double', 'triple', 'quad', 'infant');`, + ); + this.addSql( + `create type "admin_permission" as enum ('country:create', 'country:update', 'country:delete', 'city:create', 'city:update', 'city:delete', 'airline:create', 'airline:update', 'airline:delete', 'airport:create', 'airport:update', 'airport:delete', 'flight:create', 'flight:update', 'flight:delete', 'flight-class:create', 'flight-class:update', 'flight-class:delete', 'hotel-facility:create', 'hotel-facility:update', 'hotel-facility:delete', 'hotel:create', 'hotel:update', 'hotel:delete', 'transportation:create', 'transportation:update', 'transportation:delete', 'transportation-class:create', 'transportation-class:update', 'transportation-class:delete', 'package:create', 'package:update', 'package:delete', 'package-detail:create', 'package-detail:update', 'package-detail:delete', 'admin:create', 'admin:update', 'admin:delete', 'partner:create', 'partner:update', 'partner:delete');`, + ); + this.addSql( + `create table "verification" ("id" varchar(30) not null, "code" char(6) not null, "type" "verification_type" not null, "expired_at" timestamptz not null, "created_at" timestamptz not null, "updated_at" timestamptz not null, constraint "verification_pkey" primary key ("id"));`, + ); + + this.addSql( + `create table "partner" ("id" varchar(30) not null, "name" varchar(100) not null, "email" varchar(254) not null, "whatsapp" varchar(20) not null, "password" text not null, "avatar" varchar(100) null, "verification_id" varchar(30) null, "created_at" timestamptz not null, "updated_at" timestamptz not null, constraint "partner_pkey" primary key ("id"));`, + ); + this.addSql( + `alter table "partner" add constraint "partner_email_unique" unique ("email");`, + ); + this.addSql( + `alter table "partner" add constraint "partner_whatsapp_unique" unique ("whatsapp");`, + ); + + this.addSql( + `create table "order" ("id" varchar(30) not null, "package_id" varchar(30) not null, "name" varchar(100) not null, "whatsapp" varchar(20) not null, "verification_id" varchar(30) null, "partner_id" varchar(30) null, "expired_at" timestamptz null, "purchased_at" timestamptz null, "finished_at" timestamptz null, "created_at" timestamptz not null, "updated_at" timestamptz not null, constraint "order_pkey" primary key ("id"));`, + ); + + this.addSql( + `create table "order_detail" ("id" varchar(30) not null, "order_id" varchar(30) not null, "room_type" "room_type" not null, "created_at" timestamptz not null, "updated_at" timestamptz not null, constraint "order_detail_pkey" primary key ("id"));`, + ); + + this.addSql( + `create table "admin" ("id" varchar(30) not null, "name" varchar(100) not null, "email" varchar(254) not null, "password" text not null, "avatar" varchar(100) null, "permissions" "admin_permission"[] not null, "verification_id" varchar(30) null, "created_at" timestamptz not null, "updated_at" timestamptz not null, constraint "admin_pkey" primary key ("id"));`, + ); + this.addSql( + `alter table "admin" add constraint "admin_email_unique" unique ("email");`, + ); + + this.addSql( + `alter table "partner" add constraint "partner_verification_id_foreign" foreign key ("verification_id") references "verification" ("id") on update cascade on delete set null;`, + ); + + this.addSql( + `alter table "order" add constraint "order_package_id_foreign" foreign key ("package_id") references "package_detail" ("id") on update cascade;`, + ); + this.addSql( + `alter table "order" add constraint "order_verification_id_foreign" foreign key ("verification_id") references "verification" ("id") on update cascade on delete set null;`, + ); + this.addSql( + `alter table "order" add constraint "order_partner_id_foreign" foreign key ("partner_id") references "partner" ("id") on update cascade on delete set null;`, + ); + + this.addSql( + `alter table "order_detail" add constraint "order_detail_order_id_foreign" foreign key ("order_id") references "order" ("id") on update cascade;`, + ); + + this.addSql( + `alter table "admin" add constraint "admin_verification_id_foreign" foreign key ("verification_id") references "verification" ("id") on update cascade on delete set null;`, + ); + + this.addSql( + `alter table "airline" drop constraint if exists "airline_skytrax_type_check";`, + ); + + this.addSql( + `alter table "package" drop constraint if exists "package_type_check";`, + ); + this.addSql( + `alter table "package" drop constraint if exists "package_class_check";`, + ); + + this.addSql( + `alter table "package_itinerary_widget" drop constraint if exists "package_itinerary_widget_type_check";`, + ); + + this.addSql(`alter table "airline" drop constraint "airline_logo_unique";`); + + this.addSql( + `alter table "airline" alter column "skytrax_type" type "skytrax_type" using ("skytrax_type"::"skytrax_type");`, + ); + + this.addSql( + `alter table "package" drop constraint "package_thumbnail_unique";`, + ); + + this.addSql( + `alter table "package" alter column "type" type "package_type" using ("type"::"package_type");`, + ); + this.addSql( + `alter table "package" alter column "class" type "package_class" using ("class"::"package_class");`, + ); + + this.addSql( + `alter table "package_itinerary_widget" alter column "type" type "package_itinerary_widget_type" using ("type"::"package_itinerary_widget_type");`, + ); + } + + override async down(): Promise { + this.addSql( + `alter table "partner" drop constraint "partner_verification_id_foreign";`, + ); + + this.addSql( + `alter table "order" drop constraint "order_verification_id_foreign";`, + ); + + this.addSql( + `alter table "admin" drop constraint "admin_verification_id_foreign";`, + ); + + this.addSql( + `alter table "order" drop constraint "order_partner_id_foreign";`, + ); + + this.addSql( + `alter table "order_detail" drop constraint "order_detail_order_id_foreign";`, + ); + + this.addSql(`drop table if exists "verification" cascade;`); + + this.addSql(`drop table if exists "partner" cascade;`); + + this.addSql(`drop table if exists "order" cascade;`); + + this.addSql(`drop table if exists "order_detail" cascade;`); + + this.addSql(`drop table if exists "admin" cascade;`); + + this.addSql( + `alter table "airline" alter column "skytrax_type" type text using ("skytrax_type"::text);`, + ); + this.addSql( + `alter table "airline" add constraint "airline_skytrax_type_check" check("skytrax_type" in ('full_service', 'low_cost'));`, + ); + this.addSql( + `alter table "airline" add constraint "airline_logo_unique" unique ("logo");`, + ); + + this.addSql( + `alter table "package" alter column "type" type text using ("type"::text);`, + ); + this.addSql( + `alter table "package" alter column "class" type text using ("class"::text);`, + ); + this.addSql( + `alter table "package" add constraint "package_type_check" check("type" in ('reguler', 'plus'));`, + ); + this.addSql( + `alter table "package" add constraint "package_class_check" check("class" in ('silver', 'gold', 'platinum'));`, + ); + this.addSql( + `alter table "package" add constraint "package_thumbnail_unique" unique ("thumbnail");`, + ); + + this.addSql( + `alter table "package_itinerary_widget" alter column "type" type text using ("type"::text);`, + ); + this.addSql( + `alter table "package_itinerary_widget" add constraint "package_itinerary_widget_type_check" check("type" in ('transport', 'hotel', 'information'));`, + ); + + this.addSql(`drop type "skytrax_type";`); + this.addSql(`drop type "package_type";`); + this.addSql(`drop type "package_class";`); + this.addSql(`drop type "package_itinerary_widget_type";`); + this.addSql(`drop type "verification_type";`); + this.addSql(`drop type "room_type";`); + this.addSql(`drop type "admin_permission";`); + } +} diff --git a/src/modules/admin/admin.controller.ts b/src/modules/admin/admin.controller.ts new file mode 100644 index 0000000..b2891fa --- /dev/null +++ b/src/modules/admin/admin.controller.ts @@ -0,0 +1,627 @@ +import { Controller } from "@/common/controller"; +import { createOrmContextMiddleware } from "@/common/middlewares/create-orm-context.middleware"; +import { + isAdminMiddleware, + type AdminRequestPlugin, +} from "@/common/middlewares/is-admin.middleware"; +import { paginationQuerySchema } from "@/common/schemas"; +import type { AbstractEmailService } from "@/common/services/email-service/abstract.email-service"; +import type { + AbstractFileStorage, + FileResult, +} from "@/common/services/file-storage/abstract.file-storage"; +import { + JwtType, + type AbstractJwtService, +} from "@/common/services/jwt-service/abstract.jwt-service"; +import type { + ErrorResponse, + ListResponse, + SingleResponse, +} from "@/common/types"; +import { generateRandomCode } from "@/common/utils"; +import { Admin } from "@/database/entities/admin.entity"; +import { Verification } from "@/database/entities/verification.entity"; +import { AdminPermission } from "@/database/enums/admin-permission.enum"; +import { VerificationType } from "@/database/enums/verification-type.enum"; +import { orm } from "@/database/orm"; +import type { AdminMapper } from "@/modules/admin/admin.mapper"; +import { + adminChangeEmailRequestSchema, + adminChangePasswordRequestSchema, + adminParamsSchema, + adminRequestSchema, + adminUpdateRequestSchema, + adminVerifyRequestSchema, +} from "@/modules/admin/admin.schemas"; +import type { AdminResponse } from "@/modules/admin/admin.types"; +import * as dateFns from "date-fns"; +import { Router, type Request, type Response } from "express"; +import { ulid } from "ulid"; + +export class AdminController extends Controller { + public constructor( + private readonly mapper: AdminMapper, + private readonly fileStorage: AbstractFileStorage, + private readonly emailService: AbstractEmailService, + private readonly jwtService: AbstractJwtService, + ) { + super(); + } + + async create(req: Request, res: Response) { + const parseBodyResult = adminRequestSchema.safeParse(req.body); + if (!parseBodyResult.success) { + return this.handleZodError(parseBodyResult.error, res, "body"); + } + const body = parseBodyResult.data; + + let avatarFile: null | FileResult = null; + if (body.avatar !== null) { + avatarFile = await this.fileStorage.storeFile( + Buffer.from(body.avatar, "base64"), + ); + } + + const verification = orm.em.create(Verification, { + id: ulid(), + code: generateRandomCode(6), + type: VerificationType.createAdmin, + expiredAt: dateFns.addHours(new Date(), 1), + createdAt: new Date(), + updatedAt: new Date(), + }); + + const admin = orm.em.create(Admin, { + id: ulid(), + name: body.name, + email: body.email, + password: await Bun.password.hash(body.password), + avatar: avatarFile?.name, + permissions: body.permissions, + verification, + createdAt: new Date(), + updatedAt: new Date(), + }); + + await orm.em.flush(); + + await this.emailService.sendVerificationEmail( + admin.email, + verification.code, + ); + + return res.status(201).json({ + data: { + message: + "Admin created successfully. Please check your email for verification.", + }, + errors: null, + } satisfies SingleResponse); + } + + async login(req: Request, res: Response) { + const parseBodyResult = adminRequestSchema.safeParse(req.body); + if (!parseBodyResult.success) { + return this.handleZodError(parseBodyResult.error, res, "body"); + } + const body = parseBodyResult.data; + + const admin = await orm.em.findOne(Admin, { email: body.email }); + if (!admin) { + return res.status(401).json({ + data: null, + errors: [ + { + location: "body", + message: "Incorrect email or password.", + }, + ], + } satisfies ErrorResponse); + } + if (!(await Bun.password.verify(body.password, admin.password))) { + return res.status(401).json({ + data: null, + errors: [ + { + location: "body", + message: "Incorrect email or password.", + }, + ], + } satisfies ErrorResponse); + } + if (admin.verification !== null) { + return res.status(400).json({ + data: null, + errors: [ + { + message: "Admin is not verified.", + }, + ], + } satisfies ErrorResponse); + } + + const access = await this.jwtService.createAdminToken( + admin, + JwtType.access, + ); + const refresh = await this.jwtService.createAdminToken( + admin, + JwtType.refresh, + ); + + return res.status(200).json({ + data: { + access_token: access.token, + access_token_expires_at: access.expiresAt, + refresh_token: refresh.token, + refresh_token_expires_at: refresh.expiresAt, + }, + errors: null, + } satisfies SingleResponse); + } + + async list(req: Request, res: Response) { + const parseQueryResult = paginationQuerySchema.safeParse(req.query); + if (!parseQueryResult.success) { + return this.handleZodError(parseQueryResult.error, res, "query"); + } + const query = parseQueryResult.data; + + const count = await orm.em.count(Admin); + + const admins = await orm.em.find( + Admin, + { + verification: null, + }, + { + limit: query.per_page, + offset: (query.page - 1) * query.per_page, + orderBy: { createdAt: "DESC" }, + populate: ["*"], + }, + ); + + return res.status(200).json({ + data: admins.map(this.mapper.mapEntityToResponse.bind(this.mapper)), + errors: null, + meta: { + page: query.page, + per_page: query.per_page, + total_pages: Math.ceil(count / query.per_page), + total_items: count, + }, + } satisfies ListResponse); + } + + async view(req: Request, res: Response) { + const parseParamsResult = adminParamsSchema.safeParse(req.params); + if (!parseParamsResult.success) { + return this.handleZodError(parseParamsResult.error, res, "params"); + } + const params = parseParamsResult.data; + + const admin = await orm.em.findOne( + Admin, + { + id: params.id, + verification: null, + }, + { + populate: ["*"], + }, + ); + if (!admin) { + return res.status(404).json({ + data: null, + errors: [ + { + path: "id", + location: "params", + message: "Admin not found.", + }, + ], + } satisfies ErrorResponse); + } + + return res.status(200).json({ + data: this.mapper.mapEntityToResponse(admin), + errors: null, + } satisfies SingleResponse); + } + + async update(req: Request, res: Response) { + const parseParamsResult = adminParamsSchema.safeParse(req.params); + if (!parseParamsResult.success) { + return this.handleZodError(parseParamsResult.error, res, "params"); + } + const params = parseParamsResult.data; + + const parseBodyResult = adminUpdateRequestSchema.safeParse(req.body); + if (!parseBodyResult.success) { + return this.handleZodError(parseBodyResult.error, res, "body"); + } + const body = parseBodyResult.data; + + const admin = await orm.em.findOne( + Admin, + { + id: params.id, + verification: null, + }, + { + populate: ["*"], + }, + ); + if (!admin) { + return res.status(404).json({ + data: null, + errors: [ + { + path: "id", + location: "params", + message: "Admin not found.", + }, + ], + } satisfies ErrorResponse); + } + + if (body.avatar !== null) { + await this.fileStorage.storeFile( + Buffer.from(body.avatar, "base64"), + admin.avatar ?? undefined, + ); + } else if (admin.avatar !== null) { + await this.fileStorage.removeFile(admin.avatar); + } + + admin.name = body.name; + admin.permissions = body.permissions; + admin.verification = orm.em.create(Verification, { + id: ulid(), + code: generateRandomCode(6), + type: VerificationType.updateAdmin, + expiredAt: dateFns.addHours(new Date(), 1), + createdAt: new Date(), + updatedAt: new Date(), + }); + admin.updatedAt = new Date(); + + await orm.em.flush(); + + await this.emailService.sendVerificationEmail( + admin.email, + admin.verification.code, + ); + + return res.status(200).json({ + data: { + message: + "Admin updated successfully. Please check your email for verification.", + }, + errors: null, + } satisfies SingleResponse); + } + + async changeEmail(req: Request, res: Response) { + const parseParamsResult = adminParamsSchema.safeParse(req.params); + if (!parseParamsResult.success) { + return this.handleZodError(parseParamsResult.error, res, "params"); + } + const params = parseParamsResult.data; + + const parseBodyResult = adminChangeEmailRequestSchema.safeParse(req.body); + if (!parseBodyResult.success) { + return this.handleZodError(parseBodyResult.error, res, "body"); + } + const body = parseBodyResult.data; + + const admin = await orm.em.findOne( + Admin, + { + id: params.id, + verification: null, + }, + { + populate: ["*"], + }, + ); + if (!admin) { + return res.status(404).json({ + data: null, + errors: [ + { + path: "id", + location: "params", + message: "Admin not found.", + }, + ], + } satisfies ErrorResponse); + } + + admin.email = body.new_email; + admin.verification = orm.em.create(Verification, { + id: ulid(), + code: generateRandomCode(6), + type: VerificationType.changeEmailAdmin, + expiredAt: dateFns.addHours(new Date(), 1), + createdAt: new Date(), + updatedAt: new Date(), + }); + admin.updatedAt = new Date(); + + await orm.em.flush(); + + await this.emailService.sendVerificationEmail( + admin.email, + admin.verification.code, + ); + + return res.status(200).json({ + data: { + message: + "Admin's email changed successfully. Please check your email for verification.", + }, + errors: null, + } satisfies SingleResponse); + } + + async changePassword(req: Request, res: Response) { + const parseParamsResult = adminParamsSchema.safeParse(req.params); + if (!parseParamsResult.success) { + return this.handleZodError(parseParamsResult.error, res, "params"); + } + const params = parseParamsResult.data; + + const parseBodyResult = adminChangePasswordRequestSchema.safeParse( + req.body, + ); + if (!parseBodyResult.success) { + return this.handleZodError(parseBodyResult.error, res, "body"); + } + const body = parseBodyResult.data; + + const admin = await orm.em.findOne( + Admin, + { + id: params.id, + verification: null, + }, + { + populate: ["*"], + }, + ); + if (!admin) { + return res.status(404).json({ + data: null, + errors: [ + { + path: "id", + location: "params", + message: "Admin not found.", + }, + ], + } satisfies ErrorResponse); + } + + if (!(await Bun.password.verify(body.old_password, admin.password))) { + return res.status(400).json({ + data: null, + errors: [ + { + path: "old_password", + location: "body", + message: "Incorrect.", + }, + ], + } satisfies ErrorResponse); + } + + admin.password = await Bun.password.hash(body.new_password); + admin.verification = orm.em.create(Verification, { + id: ulid(), + code: generateRandomCode(6), + type: VerificationType.changePasswordAdmin, + expiredAt: dateFns.addHours(new Date(), 1), + createdAt: new Date(), + updatedAt: new Date(), + }); + admin.updatedAt = new Date(); + + await orm.em.flush(); + + await this.emailService.sendVerificationEmail( + admin.email, + admin.verification.code, + ); + + return res.status(200).json({ + data: { + message: + "Admin's password changed successfully. Please check your email for verification.", + }, + errors: null, + } satisfies SingleResponse); + } + + async verify(req: Request, res: Response) { + const parseParamsResult = adminParamsSchema.safeParse(req.params); + if (!parseParamsResult.success) { + return this.handleZodError(parseParamsResult.error, res, "params"); + } + const params = parseParamsResult.data; + + const parseBodyResult = adminVerifyRequestSchema.safeParse(req.body); + if (!parseBodyResult.success) { + return this.handleZodError(parseBodyResult.error, res, "body"); + } + const body = parseBodyResult.data; + + const admin = await orm.em.findOne( + Admin, + { id: params.id }, + { + populate: ["*"], + }, + ); + if (!admin) { + return res.status(404).json({ + data: null, + errors: [ + { + path: "id", + location: "params", + message: "Admin not found.", + }, + ], + } satisfies ErrorResponse); + } + + if (admin.verification === null) { + return res.status(400).json({ + data: null, + errors: [ + { + message: "Admin is already verified.", + }, + ], + } satisfies ErrorResponse); + } + + if (admin.verification.code !== body.code) { + return res.status(400).json({ + data: null, + errors: [ + { + path: "code", + location: "body", + message: "Incorrect.", + }, + ], + } satisfies ErrorResponse); + } + + orm.em.remove(admin.verification); + + admin.verification = null; + admin.updatedAt = new Date(); + + await orm.em.flush(); + + return res.status(200).json({ + data: this.mapper.mapEntityToResponse(admin), + errors: null, + } satisfies SingleResponse); + } + + async refresh(_req: Request, res: Response) { + const req = _req as Request & AdminRequestPlugin; + + const access = await this.jwtService.createAdminToken( + req.admin, + JwtType.access, + ); + const refresh = await this.jwtService.createAdminToken( + req.admin, + JwtType.refresh, + ); + + return res.status(200).json({ + data: { + access_token: access.token, + access_token_expires_at: access.expiresAt, + refresh_token: refresh.token, + refresh_token_expires_at: refresh.expiresAt, + }, + errors: null, + } satisfies SingleResponse); + } + + async delete(req: Request, res: Response) { + const parseParamsResult = adminParamsSchema.safeParse(req.params); + if (!parseParamsResult.success) { + return this.handleZodError(parseParamsResult.error, res, "params"); + } + const params = parseParamsResult.data; + + const admin = await orm.em.findOne( + Admin, + { id: params.id, verification: null }, + { + populate: ["*"], + }, + ); + if (!admin) { + return res.status(404).json({ + data: null, + errors: [ + { + path: "id", + location: "params", + message: "Admin not found.", + }, + ], + } satisfies ErrorResponse); + } + + if (admin.avatar !== null) { + await this.fileStorage.removeFile(admin.avatar); + } + + await orm.em.removeAndFlush(admin); + + return res.status(204).send(); + } + + public buildRouter(): Router { + const router = Router(); + router.post( + "/", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.createAdmin]), + this.create.bind(this), + ); + router.post("/login", createOrmContextMiddleware, this.login.bind(this)); + router.get("/", createOrmContextMiddleware, this.list.bind(this)); + router.get("/:id", createOrmContextMiddleware, this.view.bind(this)); + router.put( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updateAdmin]), + this.update.bind(this), + ); + router.put( + "/:id/email", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updateAdmin]), + this.changeEmail.bind(this), + ); + router.put( + "/:id/password", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updateAdmin]), + this.changePassword.bind(this), + ); + router.put( + "/:id/verify", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updateAdmin]), + this.verify.bind(this), + ); + router.put( + "/refresh", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService), + this.refresh.bind(this), + ); + router.delete( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.deleteAdmin]), + this.delete.bind(this), + ); + + return router; + } +} diff --git a/src/modules/admin/admin.mapper.ts b/src/modules/admin/admin.mapper.ts new file mode 100644 index 0000000..d1fad41 --- /dev/null +++ b/src/modules/admin/admin.mapper.ts @@ -0,0 +1,18 @@ +import type { Admin } from "@/database/entities/admin.entity"; +import type { AdminResponse } from "@/modules/admin/admin.types"; + +export class AdminMapper { + public constructor() {} + + public mapEntityToResponse(admin: Admin): AdminResponse { + return { + id: admin.id, + name: admin.name, + email: admin.email, + avatar: admin.avatar, + permissions: admin.permissions, + created_at: admin.createdAt, + updated_at: admin.updatedAt, + }; + } +} diff --git a/src/modules/admin/admin.schemas.ts b/src/modules/admin/admin.schemas.ts new file mode 100644 index 0000000..4e8dcd5 --- /dev/null +++ b/src/modules/admin/admin.schemas.ts @@ -0,0 +1,56 @@ +import { emailSchema, passwordSchema } from "@/common/schemas"; +import { AdminPermission } from "@/database/enums/admin-permission.enum"; +import z from "zod"; + +export const adminRequestSchema = z.object({ + name: z + .string("Must be string.") + .nonempty("Must not empty.") + .max(100, "Max 100 characters."), + email: emailSchema, + password: passwordSchema, + avatar: z + .base64("Must be base64 string.") + .nonempty("Must not empty.") + .nullable(), + permissions: z + .array( + z.enum(AdminPermission, "Must be valid permission."), + "Must be array.", + ) + .nonempty("Must not empty."), +}); + +export const adminLoginRequestSchema = adminRequestSchema.pick({ + email: true, + password: true, +}); + +export const adminUpdateRequestSchema = adminRequestSchema.pick({ + name: true, + avatar: true, + permissions: true, +}); + +export const adminChangeEmailRequestSchema = z.object({ + new_email: emailSchema, +}); + +export const adminChangePasswordRequestSchema = z.object({ + old_password: passwordSchema, + new_password: passwordSchema, +}); + +export const adminVerifyRequestSchema = z.object({ + code: z + .string("Must be string.") + .nonempty("Must not empty.") + .length(6, "Must be 6 characters."), +}); + +export const adminParamsSchema = z.object({ + id: z + .string("Must be string.") + .nonempty("Must not empty.") + .max(200, "Max 200 characters."), +}); diff --git a/src/modules/admin/admin.types.ts b/src/modules/admin/admin.types.ts new file mode 100644 index 0000000..bcd9be1 --- /dev/null +++ b/src/modules/admin/admin.types.ts @@ -0,0 +1,33 @@ +import type { AdminPermission } from "@/database/enums/admin-permission.enum"; +import type { + adminChangeEmailRequestSchema, + adminChangePasswordRequestSchema, + adminParamsSchema, + adminRequestSchema, + adminUpdateRequestSchema, +} from "@/modules/admin/admin.schemas"; +import z from "zod"; + +export type AdminRequest = z.infer; + +export type AdminUpdateRequest = z.infer; + +export type AdminChangeEmailRequest = z.infer< + typeof adminChangeEmailRequestSchema +>; + +export type AdminChangePasswordRequest = z.infer< + typeof adminChangePasswordRequestSchema +>; + +export type AdminParams = z.infer; + +export type AdminResponse = { + id: string; + name: string; + email: string; + avatar: string | null; + permissions: AdminPermission[]; + created_at: Date; + updated_at: Date; +}; diff --git a/src/modules/airline/airline.controller.ts b/src/modules/airline/airline.controller.ts index 29e24fd..64a2ef4 100644 --- a/src/modules/airline/airline.controller.ts +++ b/src/modules/airline/airline.controller.ts @@ -1,13 +1,16 @@ import { Controller } from "@/common/controller"; -import { ormMiddleware } from "@/common/middlewares/orm.middleware"; +import { createOrmContextMiddleware } from "@/common/middlewares/create-orm-context.middleware"; +import { isAdminMiddleware } from "@/common/middlewares/is-admin.middleware"; import { paginationQuerySchema } from "@/common/schemas"; import type { AbstractFileStorage } from "@/common/services/file-storage/abstract.file-storage"; +import type { AbstractJwtService } from "@/common/services/jwt-service/abstract.jwt-service"; import type { ErrorResponse, ListResponse, SingleResponse, } from "@/common/types"; import { Airline } from "@/database/entities/airline.entity"; +import { AdminPermission } from "@/database/enums/admin-permission.enum"; import { orm } from "@/database/orm"; import type { AirlineMapper } from "@/modules/airline/airline.mapper"; import { @@ -22,6 +25,7 @@ export class AirlineController extends Controller { public constructor( private readonly mapper: AirlineMapper, private readonly fileStorage: AbstractFileStorage, + private readonly jwtService: AbstractJwtService, ) { super(); } @@ -43,7 +47,7 @@ export class AirlineController extends Controller { code: body.code, logo: logoFile.name, skytraxRating: body.skytrax_rating, - skytraxType: this.mapper.mapSkytraxType(body.skytrax_type), + skytraxType: body.skytrax_type, createdAt: new Date(), updatedAt: new Date(), }); @@ -149,7 +153,7 @@ export class AirlineController extends Controller { airline.name = body.name; airline.code = body.code; airline.skytraxRating = body.skytrax_rating; - airline.skytraxType = this.mapper.mapSkytraxType(body.skytrax_type); + airline.skytraxType = body.skytrax_type; airline.updatedAt = new Date(); await orm.em.flush(); @@ -196,11 +200,26 @@ export class AirlineController extends Controller { public buildRouter(): Router { const router = Router(); - router.post("/", ormMiddleware, this.create.bind(this)); - router.get("/", ormMiddleware, this.list.bind(this)); - router.get("/:id", ormMiddleware, this.view.bind(this)); - router.put("/:id", ormMiddleware, this.update.bind(this)); - router.delete("/:id", ormMiddleware, this.delete.bind(this)); + router.post( + "/", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.createAirline]), + this.create.bind(this), + ); + router.get("/", createOrmContextMiddleware, this.list.bind(this)); + router.get("/:id", createOrmContextMiddleware, this.view.bind(this)); + router.put( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updateAirline]), + this.update.bind(this), + ); + router.delete( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.deleteAirline]), + this.delete.bind(this), + ); return router; } diff --git a/src/modules/airline/airline.mapper.ts b/src/modules/airline/airline.mapper.ts index 4810696..13b4070 100644 --- a/src/modules/airline/airline.mapper.ts +++ b/src/modules/airline/airline.mapper.ts @@ -1,24 +1,9 @@ import type { Airline } from "@/database/entities/airline.entity"; -import { SkytraxType } from "@/database/enums/skytrax-type.enum"; -import type { - AirlineRequest, - AirlineResponse, -} from "@/modules/airline/airline.types"; +import type { AirlineResponse } from "@/modules/airline/airline.types"; export class AirlineMapper { public constructor() {} - public mapSkytraxType( - skytraxType: AirlineRequest["skytrax_type"], - ): SkytraxType { - switch (skytraxType) { - case "full_service": - return SkytraxType.fullService; - case "low_cost": - return SkytraxType.lowCost; - } - } - public mapEntityToResponse(airline: Airline): AirlineResponse { return { id: airline.id, diff --git a/src/modules/airline/airline.schemas.ts b/src/modules/airline/airline.schemas.ts index 78924c5..9c47b48 100644 --- a/src/modules/airline/airline.schemas.ts +++ b/src/modules/airline/airline.schemas.ts @@ -1,3 +1,4 @@ +import { SkytraxType } from "@/database/enums/skytrax-type.enum"; import z from "zod"; export const airlineRequestSchema = z.object({ @@ -16,7 +17,7 @@ export const airlineRequestSchema = z.object({ .min(1, "Minimum 1.") .max(5, "Maximum 5."), skytrax_type: z.enum( - ["full_service", "low_cost"], + SkytraxType, "Must be either 'full_service' or 'low_cost'.", ), }); diff --git a/src/modules/airline/airline.types.ts b/src/modules/airline/airline.types.ts index 3178b13..1090d4b 100644 --- a/src/modules/airline/airline.types.ts +++ b/src/modules/airline/airline.types.ts @@ -1,3 +1,4 @@ +import type { SkytraxType } from "@/database/enums/skytrax-type.enum"; import type { airlineParamsSchema, airlineRequestSchema, @@ -14,7 +15,7 @@ export type AirlineResponse = { code: string; logo: string; skytrax_rating: number; - skytrax_type: "full_service" | "low_cost"; + skytrax_type: SkytraxType; created_at: Date; updated_at: Date; }; diff --git a/src/modules/airport/airport.controller.ts b/src/modules/airport/airport.controller.ts index ec9bf7b..87b4308 100644 --- a/src/modules/airport/airport.controller.ts +++ b/src/modules/airport/airport.controller.ts @@ -1,6 +1,8 @@ import { Controller } from "@/common/controller"; -import { ormMiddleware } from "@/common/middlewares/orm.middleware"; +import { createOrmContextMiddleware } from "@/common/middlewares/create-orm-context.middleware"; +import { isAdminMiddleware } from "@/common/middlewares/is-admin.middleware"; import { paginationQuerySchema } from "@/common/schemas"; +import type { AbstractJwtService } from "@/common/services/jwt-service/abstract.jwt-service"; import type { ErrorResponse, ListResponse, @@ -8,6 +10,7 @@ import type { } from "@/common/types"; import { Airport } from "@/database/entities/airport.entity"; import { City } from "@/database/entities/city.entity"; +import { AdminPermission } from "@/database/enums/admin-permission.enum"; import { orm } from "@/database/orm"; import type { AirportMapper } from "@/modules/airport/airport.mapper"; import { @@ -19,7 +22,10 @@ import { Router, type Request, type Response } from "express"; import { ulid } from "ulid"; export class AirportController extends Controller { - public constructor(private readonly mapper: AirportMapper) { + public constructor( + private readonly mapper: AirportMapper, + private readonly jwtService: AbstractJwtService, + ) { super(); } @@ -224,11 +230,26 @@ export class AirportController extends Controller { public buildRouter(): Router { const router = Router(); - router.post("/", ormMiddleware, this.create.bind(this)); - router.get("/", ormMiddleware, this.list.bind(this)); - router.get("/:id", ormMiddleware, this.view.bind(this)); - router.put("/:id", ormMiddleware, this.update.bind(this)); - router.delete("/:id", ormMiddleware, this.delete.bind(this)); + router.post( + "/", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.createAirport]), + this.create.bind(this), + ); + router.get("/", createOrmContextMiddleware, this.list.bind(this)); + router.get("/:id", createOrmContextMiddleware, this.view.bind(this)); + router.put( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updateAirport]), + this.update.bind(this), + ); + router.delete( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.deleteAirport]), + this.delete.bind(this), + ); return router; } diff --git a/src/modules/city/city.controller.ts b/src/modules/city/city.controller.ts index 1972dff..3235766 100644 --- a/src/modules/city/city.controller.ts +++ b/src/modules/city/city.controller.ts @@ -1,6 +1,8 @@ import { Controller } from "@/common/controller"; -import { ormMiddleware } from "@/common/middlewares/orm.middleware"; +import { createOrmContextMiddleware } from "@/common/middlewares/create-orm-context.middleware"; +import { isAdminMiddleware } from "@/common/middlewares/is-admin.middleware"; import { paginationQuerySchema } from "@/common/schemas"; +import type { AbstractJwtService } from "@/common/services/jwt-service/abstract.jwt-service"; import type { ErrorResponse, ListResponse, @@ -8,6 +10,7 @@ import type { } from "@/common/types"; import { City } from "@/database/entities/city.entity"; import { Country } from "@/database/entities/country.entity"; +import { AdminPermission } from "@/database/enums/admin-permission.enum"; import { orm } from "@/database/orm"; import type { CityMapper } from "@/modules/city/city.mapper"; import { @@ -19,7 +22,10 @@ import { Router, type Request, type Response } from "express"; import { ulid } from "ulid"; export class CityController extends Controller { - public constructor(private readonly mapper: CityMapper) { + public constructor( + private readonly mapper: CityMapper, + private readonly jwtService: AbstractJwtService, + ) { super(); } @@ -210,11 +216,26 @@ export class CityController extends Controller { public buildRouter(): Router { const router = Router(); - router.post("/", ormMiddleware, this.create.bind(this)); - router.get("/", ormMiddleware, this.list.bind(this)); - router.get("/:id", ormMiddleware, this.view.bind(this)); - router.put("/:id", ormMiddleware, this.update.bind(this)); - router.delete("/:id", ormMiddleware, this.delete.bind(this)); + router.post( + "/", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.createCity]), + this.create.bind(this), + ); + router.get("/", createOrmContextMiddleware, this.list.bind(this)); + router.get("/:id", createOrmContextMiddleware, this.view.bind(this)); + router.put( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updateCity]), + this.update.bind(this), + ); + router.delete( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.deleteCity]), + this.delete.bind(this), + ); return router; } diff --git a/src/modules/country/country.controller.ts b/src/modules/country/country.controller.ts index 53d3f1c..323a5f8 100644 --- a/src/modules/country/country.controller.ts +++ b/src/modules/country/country.controller.ts @@ -1,12 +1,15 @@ import { Controller } from "@/common/controller"; -import { ormMiddleware } from "@/common/middlewares/orm.middleware"; +import { createOrmContextMiddleware } from "@/common/middlewares/create-orm-context.middleware"; +import { isAdminMiddleware } from "@/common/middlewares/is-admin.middleware"; import { paginationQuerySchema } from "@/common/schemas"; +import type { AbstractJwtService } from "@/common/services/jwt-service/abstract.jwt-service"; import type { ErrorResponse, ListResponse, SingleResponse, } from "@/common/types"; import { Country } from "@/database/entities/country.entity"; +import { AdminPermission } from "@/database/enums/admin-permission.enum"; import { orm } from "@/database/orm"; import type { CountryMapper } from "@/modules/country/country.mapper"; import { @@ -18,7 +21,10 @@ import { Router, type Request, type Response } from "express"; import { ulid } from "ulid"; export class CountryController extends Controller { - public constructor(private readonly mapper: CountryMapper) { + public constructor( + private readonly mapper: CountryMapper, + private readonly jwtService: AbstractJwtService, + ) { super(); } @@ -174,11 +180,26 @@ export class CountryController extends Controller { public buildRouter(): Router { const router = Router(); - router.post("/", ormMiddleware, this.create.bind(this)); - router.get("/", ormMiddleware, this.list.bind(this)); - router.get("/:id", ormMiddleware, this.view.bind(this)); - router.put("/:id", ormMiddleware, this.update.bind(this)); - router.delete("/:id", ormMiddleware, this.delete.bind(this)); + router.post( + "/", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.createCountry]), + this.create.bind(this), + ); + router.get("/", createOrmContextMiddleware, this.list.bind(this)); + router.get("/:id", createOrmContextMiddleware, this.view.bind(this)); + router.put( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updateCountry]), + this.update.bind(this), + ); + router.delete( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.deleteCountry]), + this.delete.bind(this), + ); return router; } diff --git a/src/modules/flight/flight.controller.ts b/src/modules/flight/flight.controller.ts index 5390de9..22afc0f 100644 --- a/src/modules/flight/flight.controller.ts +++ b/src/modules/flight/flight.controller.ts @@ -1,6 +1,8 @@ import { Controller } from "@/common/controller"; -import { ormMiddleware } from "@/common/middlewares/orm.middleware"; +import { createOrmContextMiddleware } from "@/common/middlewares/create-orm-context.middleware"; +import { isAdminMiddleware } from "@/common/middlewares/is-admin.middleware"; import { paginationQuerySchema } from "@/common/schemas"; +import type { AbstractJwtService } from "@/common/services/jwt-service/abstract.jwt-service"; import type { ErrorResponse, ListResponse, @@ -10,6 +12,7 @@ import { Airline } from "@/database/entities/airline.entity"; import { Airport } from "@/database/entities/airport.entity"; import { FlightClass } from "@/database/entities/flight-class.entity"; import { Flight } from "@/database/entities/flight.entity"; +import { AdminPermission } from "@/database/enums/admin-permission.enum"; import { orm } from "@/database/orm"; import type { FlightMapper } from "@/modules/flight/flight.mapper"; import { @@ -26,7 +29,10 @@ import { Router, type Request, type Response } from "express"; import { ulid } from "ulid"; export class FlightController extends Controller { - public constructor(private readonly mapper: FlightMapper) { + public constructor( + private readonly mapper: FlightMapper, + private readonly jwtService: AbstractJwtService, + ) { super(); } @@ -622,26 +628,52 @@ export class FlightController extends Controller { public buildRouter(): Router { const router = Router(); - router.post("/", ormMiddleware, this.create.bind(this)); - router.get("/", ormMiddleware, this.list.bind(this)); - router.get("/:id", ormMiddleware, this.view.bind(this)); - router.put("/:id", ormMiddleware, this.update.bind(this)); - router.delete("/:id", ormMiddleware, this.delete.bind(this)); - router.post("/:id/classes", ormMiddleware, this.createClass.bind(this)); - router.get("/:id/classes", ormMiddleware, this.listClasses.bind(this)); + router.post( + "/", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.createFlight]), + this.create.bind(this), + ); + router.get("/", createOrmContextMiddleware, this.list.bind(this)); + router.get("/:id", createOrmContextMiddleware, this.view.bind(this)); + router.put( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updateFlight]), + this.update.bind(this), + ); + router.delete( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.deleteFlight]), + this.delete.bind(this), + ); + router.post( + "/:id/classes", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.createFlightClass]), + this.createClass.bind(this), + ); + router.get( + "/:id/classes", + createOrmContextMiddleware, + this.listClasses.bind(this), + ); router.get( "/:flight_id/classes/:id", - ormMiddleware, + createOrmContextMiddleware, this.viewClass.bind(this), ); router.put( "/:flight_id/classes/:id", - ormMiddleware, + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updateFlightClass]), this.updateClass.bind(this), ); router.delete( "/:flight_id/classes/:id", - ormMiddleware, + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.deleteFlightClass]), this.deleteClass.bind(this), ); diff --git a/src/modules/hotel-facility/hotel-facility.controller.ts b/src/modules/hotel-facility/hotel-facility.controller.ts index ecd1afc..cb9699f 100644 --- a/src/modules/hotel-facility/hotel-facility.controller.ts +++ b/src/modules/hotel-facility/hotel-facility.controller.ts @@ -1,12 +1,15 @@ import { Controller } from "@/common/controller"; -import { ormMiddleware } from "@/common/middlewares/orm.middleware"; +import { createOrmContextMiddleware } from "@/common/middlewares/create-orm-context.middleware"; +import { isAdminMiddleware } from "@/common/middlewares/is-admin.middleware"; import { paginationQuerySchema } from "@/common/schemas"; +import type { AbstractJwtService } from "@/common/services/jwt-service/abstract.jwt-service"; import type { ErrorResponse, ListResponse, SingleResponse, } from "@/common/types"; import { HotelFacility } from "@/database/entities/hotel-facility.entity"; +import { AdminPermission } from "@/database/enums/admin-permission.enum"; import { orm } from "@/database/orm"; import type { HotelFacilityMapper } from "@/modules/hotel-facility/hotel-facility.mapper"; import { @@ -18,7 +21,10 @@ import { Router, type Request, type Response } from "express"; import { ulid } from "ulid"; export class HotelFacilityController extends Controller { - public constructor(private readonly mapper: HotelFacilityMapper) { + public constructor( + private readonly mapper: HotelFacilityMapper, + private readonly jwtService: AbstractJwtService, + ) { super(); } @@ -184,11 +190,26 @@ export class HotelFacilityController extends Controller { public buildRouter(): Router { const router = Router(); - router.post("/", ormMiddleware, this.create.bind(this)); - router.get("/", ormMiddleware, this.list.bind(this)); - router.get("/:id", ormMiddleware, this.view.bind(this)); - router.put("/:id", ormMiddleware, this.update.bind(this)); - router.delete("/:id", ormMiddleware, this.delete.bind(this)); + router.post( + "/", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.createHotelFacility]), + this.create.bind(this), + ); + router.get("/", createOrmContextMiddleware, this.list.bind(this)); + router.get("/:id", createOrmContextMiddleware, this.view.bind(this)); + router.put( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updateHotelFacility]), + this.update.bind(this), + ); + router.delete( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.deleteHotelFacility]), + this.delete.bind(this), + ); return router; } diff --git a/src/modules/hotel/hotel.controller.ts b/src/modules/hotel/hotel.controller.ts index 713dbb4..d0060b0 100644 --- a/src/modules/hotel/hotel.controller.ts +++ b/src/modules/hotel/hotel.controller.ts @@ -1,7 +1,9 @@ import { Controller } from "@/common/controller"; -import { ormMiddleware } from "@/common/middlewares/orm.middleware"; +import { createOrmContextMiddleware } from "@/common/middlewares/create-orm-context.middleware"; +import { isAdminMiddleware } from "@/common/middlewares/is-admin.middleware"; import { paginationQuerySchema } from "@/common/schemas"; import type { AbstractFileStorage } from "@/common/services/file-storage/abstract.file-storage"; +import type { AbstractJwtService } from "@/common/services/jwt-service/abstract.jwt-service"; import type { ErrorResponse, ListResponse, @@ -11,6 +13,7 @@ import { City } from "@/database/entities/city.entity"; import { HotelFacility } from "@/database/entities/hotel-facility.entity"; import { HotelImage } from "@/database/entities/hotel-image.entity"; import { Hotel } from "@/database/entities/hotel.entity"; +import { AdminPermission } from "@/database/enums/admin-permission.enum"; import { orm } from "@/database/orm"; import type { HotelMapper } from "@/modules/hotel/hotel.mapper"; import { @@ -25,6 +28,7 @@ export class HotelController extends Controller { public constructor( private readonly mapper: HotelMapper, private readonly fileStorage: AbstractFileStorage, + private readonly jwtService: AbstractJwtService, ) { super(); } @@ -332,11 +336,26 @@ export class HotelController extends Controller { public buildRouter(): Router { const router = Router(); - router.post("/", ormMiddleware, this.create.bind(this)); - router.get("/", ormMiddleware, this.list.bind(this)); - router.get("/:id", ormMiddleware, this.view.bind(this)); - router.put("/:id", ormMiddleware, this.update.bind(this)); - router.delete("/:id", ormMiddleware, this.delete.bind(this)); + router.post( + "/", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.createHotel]), + this.create.bind(this), + ); + router.get("/", createOrmContextMiddleware, this.list.bind(this)); + router.get("/:id", createOrmContextMiddleware, this.view.bind(this)); + router.put( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updateHotel]), + this.update.bind(this), + ); + router.delete( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.deleteHotel]), + this.delete.bind(this), + ); return router; } diff --git a/src/modules/order/order.controller.ts b/src/modules/order/order.controller.ts new file mode 100644 index 0000000..1f5d8dd --- /dev/null +++ b/src/modules/order/order.controller.ts @@ -0,0 +1,389 @@ +import { Controller } from "@/common/controller"; +import { createOrmContextMiddleware } from "@/common/middlewares/create-orm-context.middleware"; +import { + isPartnerMiddleware, + type PartnerRequestPlugin, +} from "@/common/middlewares/is-partner.middleware"; +import { paginationQuerySchema } from "@/common/schemas"; +import type { AbstractJwtService } from "@/common/services/jwt-service/abstract.jwt-service"; +import type { AbstractPaymentService } from "@/common/services/payment-service/abstract.payment-service"; +import type { + ErrorResponse, + ListResponse, + SingleResponse, +} from "@/common/types"; +import { generateRandomCode } from "@/common/utils"; +import { OrderDetail } from "@/database/entities/order-detail.entity"; +import { Order } from "@/database/entities/order.entity"; +import { PackageDetail } from "@/database/entities/package-detail.entity"; +import { Partner } from "@/database/entities/partner.entity"; +import { Verification } from "@/database/entities/verification.entity"; +import { VerificationType } from "@/database/enums/verification-type.enum"; +import { orm } from "@/database/orm"; +import type { OrderMapper } from "@/modules/order/order.mapper"; +import { + orderParamsSchema, + orderRequestSchema, + orderVerifyRequestSchema, +} from "@/modules/order/order.schemas"; +import type { OrderResponse } from "@/modules/order/order.types"; +import * as dateFns from "date-fns"; +import { Router, type Request, type Response } from "express"; +import { ulid } from "ulid"; + +export class OrderController extends Controller { + public constructor( + private readonly mapper: OrderMapper, + private readonly paymentService: AbstractPaymentService, + private readonly jwtService: AbstractJwtService, + ) { + super(); + } + + async create(req: Request, res: Response) { + const parseBodyResult = orderRequestSchema.safeParse(req.body); + if (!parseBodyResult.success) { + return this.handleZodError(parseBodyResult.error, res, "body"); + } + const body = parseBodyResult.data; + + const packageDetail = await orm.em.findOne(PackageDetail, { + id: body.package_id, + }); + if (!packageDetail) { + return res.status(404).json({ + data: null, + errors: [ + { + path: "package_id", + location: "body", + message: "Package detail not found.", + }, + ], + } satisfies ErrorResponse); + } + + const verification = orm.em.create(Verification, { + id: ulid(), + code: generateRandomCode(6), + type: VerificationType.createOrder, + expiredAt: dateFns.addHours(new Date(), 1), + createdAt: new Date(), + updatedAt: new Date(), + }); + + const order = orm.em.create(Order, { + id: ulid(), + package: packageDetail, + name: body.name, + whatsapp: body.whatsapp, + verification, + partner: null, + expiredAt: null, + purchasedAt: null, + finishedAt: null, + createdAt: new Date(), + updatedAt: new Date(), + }); + + for (const roomType of body.room_types) { + order.details.add( + orm.em.create(OrderDetail, { + id: ulid(), + order, + roomType, + createdAt: new Date(), + updatedAt: new Date(), + }), + ); + } + + await orm.em.flush(); + + return res.status(201).json({ + data: { + message: + "Order created successfully. Please check your email for verification.", + }, + errors: null, + } satisfies SingleResponse); + } + + async list(_req: Request, res: Response) { + const req = _req as Request & PartnerRequestPlugin; + + const parseQueryResult = paginationQuerySchema.safeParse(req.query); + if (!parseQueryResult.success) { + return this.handleZodError(parseQueryResult.error, res, "query"); + } + const query = parseQueryResult.data; + + const count = await orm.em.count(Order); + + const orders = await orm.em.find( + Order, + { + verification: null, + partner: req.partner, + }, + { + limit: query.per_page, + offset: (query.page - 1) * query.per_page, + orderBy: { createdAt: "DESC" }, + populate: ["*"], + }, + ); + + return res.status(200).json({ + data: orders.map(this.mapper.mapEntityToResponse.bind(this.mapper)), + errors: null, + meta: { + page: query.page, + per_page: query.per_page, + total_pages: Math.ceil(count / query.per_page), + total_items: count, + }, + } satisfies ListResponse); + } + + async view(_req: Request, res: Response) { + const req = _req as Request & PartnerRequestPlugin; + + const parseParamsResult = orderParamsSchema.safeParse(req.params); + if (!parseParamsResult.success) { + return this.handleZodError(parseParamsResult.error, res, "params"); + } + const params = parseParamsResult.data; + + const order = await orm.em.findOne( + Order, + { + id: params.id, + verification: null, + partner: req.partner, + }, + { + populate: ["*"], + }, + ); + if (!order) { + return res.status(404).json({ + data: null, + errors: [ + { + path: "id", + location: "params", + message: "Order not found.", + }, + ], + } satisfies ErrorResponse); + } + + return res.status(200).json({ + data: this.mapper.mapEntityToResponse(order), + errors: null, + } satisfies SingleResponse); + } + + async finish(_req: Request, res: Response) { + const req = _req as Request & PartnerRequestPlugin; + + const parseParamsResult = orderParamsSchema.safeParse(req.params); + if (!parseParamsResult.success) { + return this.handleZodError(parseParamsResult.error, res, "params"); + } + const params = parseParamsResult.data; + + const order = await orm.em.findOne( + Order, + { + id: params.id, + verification: null, + partner: req.partner, + }, + { + populate: ["*"], + }, + ); + if (!order) { + return res.status(404).json({ + data: null, + errors: [ + { + path: "id", + location: "params", + message: "Order not found.", + }, + ], + } satisfies ErrorResponse); + } + + order.finishedAt = new Date(); + order.updatedAt = new Date(); + + await orm.em.flush(); + + return res.status(200).json({ + data: this.mapper.mapEntityToResponse(order), + errors: null, + } satisfies SingleResponse); + } + + async verify(req: Request, res: Response) { + const parseParamsResult = orderParamsSchema.safeParse(req.params); + if (!parseParamsResult.success) { + return this.handleZodError(parseParamsResult.error, res, "params"); + } + const params = parseParamsResult.data; + + const parseBodyResult = orderVerifyRequestSchema.safeParse(req.body); + if (!parseBodyResult.success) { + return this.handleZodError(parseBodyResult.error, res, "body"); + } + const body = parseBodyResult.data; + + const order = await orm.em.findOne( + Order, + { id: params.id }, + { + populate: ["*"], + }, + ); + if (!order) { + return res.status(404).json({ + data: null, + errors: [ + { + path: "id", + location: "params", + message: "Order not found.", + }, + ], + } satisfies ErrorResponse); + } + + if (order.verification === null) { + return res.status(400).json({ + data: null, + errors: [ + { + message: "Order is already verified.", + }, + ], + } satisfies ErrorResponse); + } + + if (order.verification.code !== body.code) { + return res.status(400).json({ + data: null, + errors: [ + { + path: "code", + location: "body", + message: "Incorrect.", + }, + ], + } satisfies ErrorResponse); + } + + orm.em.remove(order.verification); + + const partners = await orm.em.findAll(Partner, { populate: ["*"] }); + const partner = partners.toSorted( + (a, b) => + a.orders.filter((order) => order.finishedAt === null).length - + b.orders.filter((order) => order.finishedAt === null).length, + )[0]; + + order.verification = null; + order.partner = partner; + order.expiredAt = dateFns.addHours(new Date(), 24); + order.updatedAt = new Date(); + + await orm.em.flush(); + + const paymentUrl = await this.paymentService.createPaymentUrl(order); + + return res.status(200).json({ + data: { + ...this.mapper.mapEntityToResponse(order), + payment_url: paymentUrl, + }, + errors: null, + } satisfies SingleResponse< + OrderResponse & { + payment_url: string; + } + >); + } + + async delete(_req: Request, res: Response) { + const req = _req as Request & PartnerRequestPlugin; + + const parseParamsResult = orderParamsSchema.safeParse(req.params); + if (!parseParamsResult.success) { + return this.handleZodError(parseParamsResult.error, res, "params"); + } + const params = parseParamsResult.data; + + const order = await orm.em.findOne( + Order, + { id: params.id, verification: null, partner: req.partner }, + { + populate: ["*"], + }, + ); + if (!order) { + return res.status(404).json({ + data: null, + errors: [ + { + path: "id", + location: "params", + message: "Order not found.", + }, + ], + } satisfies ErrorResponse); + } + + await orm.em.removeAndFlush(order); + + return res.status(204).send(); + } + + public buildRouter(): Router { + const router = Router(); + router.post("/", createOrmContextMiddleware, this.create.bind(this)); + router.get( + "/", + createOrmContextMiddleware, + isPartnerMiddleware(this.jwtService), + this.list.bind(this), + ); + router.get( + "/:id", + createOrmContextMiddleware, + isPartnerMiddleware(this.jwtService), + this.view.bind(this), + ); + router.put( + "/:id/finish", + createOrmContextMiddleware, + isPartnerMiddleware(this.jwtService), + this.finish.bind(this), + ); + router.put( + "/:id/verify", + createOrmContextMiddleware, + this.verify.bind(this), + ); + router.delete( + "/:id", + createOrmContextMiddleware, + isPartnerMiddleware(this.jwtService), + this.delete.bind(this), + ); + + return router; + } +} diff --git a/src/modules/order/order.mapper.ts b/src/modules/order/order.mapper.ts new file mode 100644 index 0000000..a1d194a --- /dev/null +++ b/src/modules/order/order.mapper.ts @@ -0,0 +1,59 @@ +import type { Order } from "@/database/entities/order.entity"; +import { RoomType } from "@/database/enums/room-type.enum"; +import type { OrderResponse } from "@/modules/order/order.types"; +import type { PackageMapper } from "@/modules/package/package.mapper"; +import type { PartnerMapper } from "@/modules/partner/partner.mapper"; + +export class OrderMapper { + public constructor( + private readonly packageMapper: PackageMapper, + private readonly partnerMapper: PartnerMapper, + ) {} + + public mapEntityToResponse(order: Order): OrderResponse { + const details: OrderResponse["details"] = []; + let totalPrice = 0; + for (const detail of order.details) { + let price = 0; + switch (detail.roomType) { + case RoomType.double: + price = order.package.doublePrice; + break; + case RoomType.triple: + price = order.package.triplePrice; + break; + case RoomType.quad: + price = order.package.quadPrice; + break; + case RoomType.infant: + price = order.package.infantPrice ?? 0; + break; + } + + details.push({ + price, + room_type: detail.roomType, + }); + + totalPrice += price; + } + + return { + id: order.id, + package: this.packageMapper.mapDetailEntityToResponse(order.package), + name: order.name, + whatsapp: order.whatsapp, + details, + total_price: totalPrice, + is_verified: order.verification === null, + partner: order.partner + ? this.partnerMapper.mapEntityToResponse(order.partner) + : null, + expired_at: order.expiredAt, + purchased_at: order.purchasedAt, + finished_at: order.finishedAt, + created_at: order.createdAt, + updated_at: order.updatedAt, + }; + } +} diff --git a/src/modules/order/order.schemas.ts b/src/modules/order/order.schemas.ts new file mode 100644 index 0000000..256444c --- /dev/null +++ b/src/modules/order/order.schemas.ts @@ -0,0 +1,38 @@ +import { phoneNumberSchema } from "@/common/schemas"; +import { RoomType } from "@/database/enums/room-type.enum"; +import z from "zod"; + +export const orderRequestSchema = z.object({ + package_id: z + .ulid("Must be ulid string.") + .nonempty("Must not empty.") + .max(30, "Max 30 characters."), + name: z + .string("Must be string.") + .nonempty("Must not empty.") + .max(100, "Max 100 characters."), + whatsapp: phoneNumberSchema, + room_types: z + .array( + z.enum( + RoomType, + "Must be either 'double', 'triple', 'quad', or 'infant'.", + ), + "Must be array.", + ) + .nonempty("Must not empty."), +}); + +export const orderVerifyRequestSchema = z.object({ + code: z + .string("Must be string.") + .nonempty("Must not empty.") + .length(6, "Must be 6 characters."), +}); + +export const orderParamsSchema = z.object({ + id: z + .string("Must be string.") + .nonempty("Must not empty.") + .max(200, "Max 200 characters."), +}); diff --git a/src/modules/order/order.types.ts b/src/modules/order/order.types.ts new file mode 100644 index 0000000..9c437d0 --- /dev/null +++ b/src/modules/order/order.types.ts @@ -0,0 +1,31 @@ +import type { RoomType } from "@/database/enums/room-type.enum"; +import type { + orderParamsSchema, + orderRequestSchema, +} from "@/modules/order/order.schemas"; +import type { PackageDetailResponse } from "@/modules/package/package.types"; +import type { PartnerResponse } from "@/modules/partner/partner.types"; +import z from "zod"; + +export type OrderRequest = z.infer; + +export type OrderParams = z.infer; + +export type OrderResponse = { + id: string; + package: PackageDetailResponse; + name: string; + whatsapp: string; + details: { + room_type: RoomType; + price: number; + }[]; + total_price: number; + is_verified: boolean; + partner: PartnerResponse | null; + expired_at: Date | null; + purchased_at: Date | null; + finished_at: Date | null; + created_at: Date; + updated_at: Date; +}; diff --git a/src/modules/package/package.controller.ts b/src/modules/package/package.controller.ts index 40df0a9..7c72be8 100644 --- a/src/modules/package/package.controller.ts +++ b/src/modules/package/package.controller.ts @@ -1,7 +1,9 @@ import { Controller } from "@/common/controller"; -import { ormMiddleware } from "@/common/middlewares/orm.middleware"; +import { createOrmContextMiddleware } from "@/common/middlewares/create-orm-context.middleware"; +import { isAdminMiddleware } from "@/common/middlewares/is-admin.middleware"; import { paginationQuerySchema } from "@/common/schemas"; import type { AbstractFileStorage } from "@/common/services/file-storage/abstract.file-storage"; +import type { AbstractJwtService } from "@/common/services/jwt-service/abstract.jwt-service"; import type { ErrorResponse, ListResponse, @@ -22,6 +24,7 @@ import { import { PackageItinerary } from "@/database/entities/package-itinerary.entity"; import { Package } from "@/database/entities/package.entity"; import { TransportationClass } from "@/database/entities/transportation-class.entity"; +import { AdminPermission } from "@/database/enums/admin-permission.enum"; import { PackageItineraryWidgetType } from "@/database/enums/package-itinerary-widget-type.enum"; import { orm } from "@/database/orm"; import type { PackageMapper } from "@/modules/package/package.mapper"; @@ -43,6 +46,7 @@ export class PackageController extends Controller { public constructor( private readonly mapper: PackageMapper, private readonly fileStorage: AbstractFileStorage, + private readonly jwtService: AbstractJwtService, ) { super(); } @@ -61,8 +65,8 @@ export class PackageController extends Controller { const package_ = orm.em.create(Package, { id: ulid(), name: body.name, - type: this.mapper.mapPackageType(body.type), - class: this.mapper.mapPackageClass(body.class), + type: body.type, + class: body.class, thumbnail: thumbnailFile.name, useFastTrain: body.use_fast_train, createdAt: new Date(), @@ -177,8 +181,8 @@ export class PackageController extends Controller { ); package_.name = body.name; - package_.type = this.mapper.mapPackageType(body.type); - package_.class = this.mapper.mapPackageClass(body.class); + package_.type = body.type; + package_.class = body.class; package_.useFastTrain = body.use_fast_train; package_.updatedAt = new Date(); @@ -1330,26 +1334,52 @@ export class PackageController extends Controller { public buildRouter(): Router { const router = Router(); - router.post("/", ormMiddleware, this.create.bind(this)); - router.get("/", ormMiddleware, this.list.bind(this)); - router.get("/:id", ormMiddleware, this.view.bind(this)); - router.put("/:id", ormMiddleware, this.update.bind(this)); - router.delete("/:id", ormMiddleware, this.delete.bind(this)); - router.post("/:id/details", ormMiddleware, this.createDetail.bind(this)); - router.get("/:id/details", ormMiddleware, this.listDetails.bind(this)); + router.post( + "/", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.createPackage]), + this.create.bind(this), + ); + router.get("/", createOrmContextMiddleware, this.list.bind(this)); + router.get("/:id", createOrmContextMiddleware, this.view.bind(this)); + router.put( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updatePackage]), + this.update.bind(this), + ); + router.delete( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.deletePackage]), + this.delete.bind(this), + ); + router.post( + "/:id/details", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.createPackageDetail]), + this.createDetail.bind(this), + ); + router.get( + "/:id/details", + createOrmContextMiddleware, + this.listDetails.bind(this), + ); router.get( "/:package_id/details/:id", - ormMiddleware, + createOrmContextMiddleware, this.viewDetail.bind(this), ); router.put( "/:package_id/details/:id", - ormMiddleware, + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updatePackageDetail]), this.updateDetail.bind(this), ); router.delete( "/:package_id/details/:id", - ormMiddleware, + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.deletePackageDetail]), this.deleteDetail.bind(this), ); diff --git a/src/modules/package/package.mapper.ts b/src/modules/package/package.mapper.ts index 60d7415..ff4cf96 100644 --- a/src/modules/package/package.mapper.ts +++ b/src/modules/package/package.mapper.ts @@ -10,9 +10,7 @@ import type { } from "@/database/entities/package-itinerary-widget.entity"; import type { PackageItinerary } from "@/database/entities/package-itinerary.entity"; import type { Package } from "@/database/entities/package.entity"; -import { PackageClass } from "@/database/enums/package-class.enum"; import { PackageItineraryWidgetType } from "@/database/enums/package-itinerary-widget-type.enum"; -import { PackageType } from "@/database/enums/package-type.enum"; import type { FlightMapper } from "@/modules/flight/flight.mapper"; import type { FlightClassResponse } from "@/modules/flight/flight.types"; import type { HotelMapper } from "@/modules/hotel/hotel.mapper"; @@ -22,7 +20,6 @@ import type { PackageItineraryDayResponse, PackageItineraryResponse, PackageItineraryWidgetResponse, - PackageRequest, PackageResponse, } from "@/modules/package/package.types"; import type { TransportationMapper } from "@/modules/transportation/transportation.mapper"; @@ -35,26 +32,6 @@ export class PackageMapper { private readonly transportationMapper: TransportationMapper, ) {} - public mapPackageType(packageType: PackageRequest["type"]): PackageType { - switch (packageType) { - case "reguler": - return PackageType.reguler; - case "plus": - return PackageType.plus; - } - } - - public mapPackageClass(packageClass: PackageRequest["class"]): PackageClass { - switch (packageClass) { - case "silver": - return PackageClass.silver; - case "gold": - return PackageClass.gold; - case "platinum": - return PackageClass.platinum; - } - } - public mapEntityToResponse(package_: Package): PackageResponse { return { id: package_.id, diff --git a/src/modules/package/package.schemas.ts b/src/modules/package/package.schemas.ts index fc41158..d7f0e7f 100644 --- a/src/modules/package/package.schemas.ts +++ b/src/modules/package/package.schemas.ts @@ -1,4 +1,6 @@ import { dateSchema, timeSchema } from "@/common/schemas"; +import { PackageClass } from "@/database/enums/package-class.enum"; +import { PackageType } from "@/database/enums/package-type.enum"; import z from "zod"; export const packageRequestSchema = z.object({ @@ -6,9 +8,9 @@ export const packageRequestSchema = z.object({ .string("Must be string.") .nonempty("Must not empty.") .max(100, "Max 100 characters."), - type: z.enum(["reguler", "plus"], "Must be either 'reguler' or 'plus'."), + type: z.enum(PackageType, "Must be either 'reguler' or 'plus'."), class: z.enum( - ["silver", "gold", "platinum"], + PackageClass, "Must be either 'silver', 'gold', or 'platinum'.", ), thumbnail: z.base64("Must be base64 string.").nonempty("Must not empty."), diff --git a/src/modules/package/package.types.ts b/src/modules/package/package.types.ts index 3f096fc..1462d30 100644 --- a/src/modules/package/package.types.ts +++ b/src/modules/package/package.types.ts @@ -1,3 +1,5 @@ +import type { PackageClass } from "@/database/enums/package-class.enum"; +import type { PackageType } from "@/database/enums/package-type.enum"; import type { FlightClassResponse } from "@/modules/flight/flight.types"; import type { HotelResponse } from "@/modules/hotel/hotel.types"; import type { @@ -20,8 +22,8 @@ export type PackageDetailParams = z.infer; export type PackageResponse = { id: string; name: string; - type: "reguler" | "plus"; - class: "silver" | "gold" | "platinum"; + type: PackageType; + class: PackageClass; thumbnail: string; use_fast_train: boolean; created_at: Date; diff --git a/src/modules/partner/partner.controller.ts b/src/modules/partner/partner.controller.ts new file mode 100644 index 0000000..e612c32 --- /dev/null +++ b/src/modules/partner/partner.controller.ts @@ -0,0 +1,627 @@ +import { Controller } from "@/common/controller"; +import { createOrmContextMiddleware } from "@/common/middlewares/create-orm-context.middleware"; +import { isAdminMiddleware } from "@/common/middlewares/is-admin.middleware"; +import { + isPartnerMiddleware, + type PartnerRequestPlugin, +} from "@/common/middlewares/is-partner.middleware"; +import { paginationQuerySchema } from "@/common/schemas"; +import type { AbstractEmailService } from "@/common/services/email-service/abstract.email-service"; +import type { + AbstractFileStorage, + FileResult, +} from "@/common/services/file-storage/abstract.file-storage"; +import { + JwtType, + type AbstractJwtService, +} from "@/common/services/jwt-service/abstract.jwt-service"; +import type { + ErrorResponse, + ListResponse, + SingleResponse, +} from "@/common/types"; +import { generateRandomCode } from "@/common/utils"; +import { Partner } from "@/database/entities/partner.entity"; +import { Verification } from "@/database/entities/verification.entity"; +import { AdminPermission } from "@/database/enums/admin-permission.enum"; +import { VerificationType } from "@/database/enums/verification-type.enum"; +import { orm } from "@/database/orm"; +import type { PartnerMapper } from "@/modules/partner/partner.mapper"; +import { + partnerChangeEmailRequestSchema, + partnerChangePasswordRequestSchema, + partnerParamsSchema, + partnerRequestSchema, + partnerUpdateRequestSchema, + partnerVerifyRequestSchema, +} from "@/modules/partner/partner.schemas"; +import type { PartnerResponse } from "@/modules/partner/partner.types"; +import * as dateFns from "date-fns"; +import { Router, type Request, type Response } from "express"; +import { ulid } from "ulid"; + +export class PartnerController extends Controller { + public constructor( + private readonly mapper: PartnerMapper, + private readonly fileStorage: AbstractFileStorage, + private readonly emailService: AbstractEmailService, + private readonly jwtService: AbstractJwtService, + ) { + super(); + } + + async create(req: Request, res: Response) { + const parseBodyResult = partnerRequestSchema.safeParse(req.body); + if (!parseBodyResult.success) { + return this.handleZodError(parseBodyResult.error, res, "body"); + } + const body = parseBodyResult.data; + + let avatarFile: null | FileResult = null; + if (body.avatar !== null) { + avatarFile = await this.fileStorage.storeFile( + Buffer.from(body.avatar, "base64"), + ); + } + + const verification = orm.em.create(Verification, { + id: ulid(), + code: generateRandomCode(6), + type: VerificationType.createPartner, + expiredAt: dateFns.addHours(new Date(), 1), + createdAt: new Date(), + updatedAt: new Date(), + }); + + const partner = orm.em.create(Partner, { + id: ulid(), + name: body.name, + email: body.email, + whatsapp: body.whatsapp, + password: await Bun.password.hash(body.password), + avatar: avatarFile?.name, + verification, + createdAt: new Date(), + updatedAt: new Date(), + }); + + await orm.em.flush(); + + await this.emailService.sendVerificationEmail( + partner.email, + verification.code, + ); + + return res.status(201).json({ + data: { + message: + "Partner created successfully. Please check your email for verification.", + }, + errors: null, + } satisfies SingleResponse); + } + + async login(req: Request, res: Response) { + const parseBodyResult = partnerRequestSchema.safeParse(req.body); + if (!parseBodyResult.success) { + return this.handleZodError(parseBodyResult.error, res, "body"); + } + const body = parseBodyResult.data; + + const partner = await orm.em.findOne(Partner, { email: body.email }); + if (!partner) { + return res.status(401).json({ + data: null, + errors: [ + { + location: "body", + message: "Incorrect email or password.", + }, + ], + } satisfies ErrorResponse); + } + if (!(await Bun.password.verify(body.password, partner.password))) { + return res.status(401).json({ + data: null, + errors: [ + { + location: "body", + message: "Incorrect email or password.", + }, + ], + } satisfies ErrorResponse); + } + if (partner.verification !== null) { + return res.status(400).json({ + data: null, + errors: [ + { + message: "Partner is not verified.", + }, + ], + } satisfies ErrorResponse); + } + + const access = await this.jwtService.createPartnerToken( + partner, + JwtType.access, + ); + const refresh = await this.jwtService.createPartnerToken( + partner, + JwtType.refresh, + ); + + return res.status(200).json({ + data: { + access_token: access.token, + access_token_expires_at: access.expiresAt, + refresh_token: refresh.token, + refresh_token_expires_at: refresh.expiresAt, + }, + errors: null, + } satisfies SingleResponse); + } + + async list(req: Request, res: Response) { + const parseQueryResult = paginationQuerySchema.safeParse(req.query); + if (!parseQueryResult.success) { + return this.handleZodError(parseQueryResult.error, res, "query"); + } + const query = parseQueryResult.data; + + const count = await orm.em.count(Partner); + + const partners = await orm.em.find( + Partner, + { + verification: null, + }, + { + limit: query.per_page, + offset: (query.page - 1) * query.per_page, + orderBy: { createdAt: "DESC" }, + populate: ["*"], + }, + ); + + return res.status(200).json({ + data: partners.map(this.mapper.mapEntityToResponse.bind(this.mapper)), + errors: null, + meta: { + page: query.page, + per_page: query.per_page, + total_pages: Math.ceil(count / query.per_page), + total_items: count, + }, + } satisfies ListResponse); + } + + async view(req: Request, res: Response) { + const parseParamsResult = partnerParamsSchema.safeParse(req.params); + if (!parseParamsResult.success) { + return this.handleZodError(parseParamsResult.error, res, "params"); + } + const params = parseParamsResult.data; + + const partner = await orm.em.findOne( + Partner, + { + id: params.id, + verification: null, + }, + { + populate: ["*"], + }, + ); + if (!partner) { + return res.status(404).json({ + data: null, + errors: [ + { + path: "id", + location: "params", + message: "Partner not found.", + }, + ], + } satisfies ErrorResponse); + } + + return res.status(200).json({ + data: this.mapper.mapEntityToResponse(partner), + errors: null, + } satisfies SingleResponse); + } + + async update(req: Request, res: Response) { + const parseParamsResult = partnerParamsSchema.safeParse(req.params); + if (!parseParamsResult.success) { + return this.handleZodError(parseParamsResult.error, res, "params"); + } + const params = parseParamsResult.data; + + const parseBodyResult = partnerUpdateRequestSchema.safeParse(req.body); + if (!parseBodyResult.success) { + return this.handleZodError(parseBodyResult.error, res, "body"); + } + const body = parseBodyResult.data; + + const partner = await orm.em.findOne( + Partner, + { + id: params.id, + verification: null, + }, + { + populate: ["*"], + }, + ); + if (!partner) { + return res.status(404).json({ + data: null, + errors: [ + { + path: "id", + location: "params", + message: "Partner not found.", + }, + ], + } satisfies ErrorResponse); + } + + if (body.avatar !== null) { + await this.fileStorage.storeFile( + Buffer.from(body.avatar, "base64"), + partner.avatar ?? undefined, + ); + } else if (partner.avatar !== null) { + await this.fileStorage.removeFile(partner.avatar); + } + + partner.name = body.name; + partner.verification = orm.em.create(Verification, { + id: ulid(), + code: generateRandomCode(6), + type: VerificationType.updatePartner, + expiredAt: dateFns.addHours(new Date(), 1), + createdAt: new Date(), + updatedAt: new Date(), + }); + partner.updatedAt = new Date(); + + await orm.em.flush(); + + await this.emailService.sendVerificationEmail( + partner.email, + partner.verification.code, + ); + + return res.status(200).json({ + data: { + message: + "Partner updated successfully. Please check your email for verification.", + }, + errors: null, + } satisfies SingleResponse); + } + + async changeEmail(req: Request, res: Response) { + const parseParamsResult = partnerParamsSchema.safeParse(req.params); + if (!parseParamsResult.success) { + return this.handleZodError(parseParamsResult.error, res, "params"); + } + const params = parseParamsResult.data; + + const parseBodyResult = partnerChangeEmailRequestSchema.safeParse(req.body); + if (!parseBodyResult.success) { + return this.handleZodError(parseBodyResult.error, res, "body"); + } + const body = parseBodyResult.data; + + const partner = await orm.em.findOne( + Partner, + { + id: params.id, + verification: null, + }, + { + populate: ["*"], + }, + ); + if (!partner) { + return res.status(404).json({ + data: null, + errors: [ + { + path: "id", + location: "params", + message: "Partner not found.", + }, + ], + } satisfies ErrorResponse); + } + + partner.email = body.new_email; + partner.verification = orm.em.create(Verification, { + id: ulid(), + code: generateRandomCode(6), + type: VerificationType.changeEmailPartner, + expiredAt: dateFns.addHours(new Date(), 1), + createdAt: new Date(), + updatedAt: new Date(), + }); + partner.updatedAt = new Date(); + + await orm.em.flush(); + + await this.emailService.sendVerificationEmail( + partner.email, + partner.verification.code, + ); + + return res.status(200).json({ + data: { + message: + "Partner's email changed successfully. Please check your email for verification.", + }, + errors: null, + } satisfies SingleResponse); + } + + async changePassword(req: Request, res: Response) { + const parseParamsResult = partnerParamsSchema.safeParse(req.params); + if (!parseParamsResult.success) { + return this.handleZodError(parseParamsResult.error, res, "params"); + } + const params = parseParamsResult.data; + + const parseBodyResult = partnerChangePasswordRequestSchema.safeParse( + req.body, + ); + if (!parseBodyResult.success) { + return this.handleZodError(parseBodyResult.error, res, "body"); + } + const body = parseBodyResult.data; + + const partner = await orm.em.findOne( + Partner, + { + id: params.id, + verification: null, + }, + { + populate: ["*"], + }, + ); + if (!partner) { + return res.status(404).json({ + data: null, + errors: [ + { + path: "id", + location: "params", + message: "Partner not found.", + }, + ], + } satisfies ErrorResponse); + } + + if (!(await Bun.password.verify(body.old_password, partner.password))) { + return res.status(400).json({ + data: null, + errors: [ + { + path: "old_password", + location: "body", + message: "Incorrect.", + }, + ], + } satisfies ErrorResponse); + } + + partner.password = await Bun.password.hash(body.new_password); + partner.verification = orm.em.create(Verification, { + id: ulid(), + code: generateRandomCode(6), + type: VerificationType.changePasswordPartner, + expiredAt: dateFns.addHours(new Date(), 1), + createdAt: new Date(), + updatedAt: new Date(), + }); + partner.updatedAt = new Date(); + + await orm.em.flush(); + + await this.emailService.sendVerificationEmail( + partner.email, + partner.verification.code, + ); + + return res.status(200).json({ + data: { + message: + "Partner's password changed successfully. Please check your email for verification.", + }, + errors: null, + } satisfies SingleResponse); + } + + async verify(req: Request, res: Response) { + const parseParamsResult = partnerParamsSchema.safeParse(req.params); + if (!parseParamsResult.success) { + return this.handleZodError(parseParamsResult.error, res, "params"); + } + const params = parseParamsResult.data; + + const parseBodyResult = partnerVerifyRequestSchema.safeParse(req.body); + if (!parseBodyResult.success) { + return this.handleZodError(parseBodyResult.error, res, "body"); + } + const body = parseBodyResult.data; + + const partner = await orm.em.findOne( + Partner, + { id: params.id }, + { + populate: ["*"], + }, + ); + if (!partner) { + return res.status(404).json({ + data: null, + errors: [ + { + path: "id", + location: "params", + message: "Partner not found.", + }, + ], + } satisfies ErrorResponse); + } + + if (partner.verification === null) { + return res.status(400).json({ + data: null, + errors: [ + { + message: "Partner is already verified.", + }, + ], + } satisfies ErrorResponse); + } + + if (partner.verification.code !== body.code) { + return res.status(400).json({ + data: null, + errors: [ + { + path: "code", + location: "body", + message: "Incorrect.", + }, + ], + } satisfies ErrorResponse); + } + + orm.em.remove(partner.verification); + + partner.verification = null; + partner.updatedAt = new Date(); + + await orm.em.flush(); + + return res.status(200).json({ + data: this.mapper.mapEntityToResponse(partner), + errors: null, + } satisfies SingleResponse); + } + + async refresh(_req: Request, res: Response) { + const req = _req as Request & PartnerRequestPlugin; + + const access = await this.jwtService.createPartnerToken( + req.partner, + JwtType.access, + ); + const refresh = await this.jwtService.createPartnerToken( + req.partner, + JwtType.refresh, + ); + + return res.status(200).json({ + data: { + access_token: access.token, + access_token_expires_at: access.expiresAt, + refresh_token: refresh.token, + refresh_token_expires_at: refresh.expiresAt, + }, + errors: null, + } satisfies SingleResponse); + } + + async delete(req: Request, res: Response) { + const parseParamsResult = partnerParamsSchema.safeParse(req.params); + if (!parseParamsResult.success) { + return this.handleZodError(parseParamsResult.error, res, "params"); + } + const params = parseParamsResult.data; + + const partner = await orm.em.findOne( + Partner, + { id: params.id, verification: null }, + { + populate: ["*"], + }, + ); + if (!partner) { + return res.status(404).json({ + data: null, + errors: [ + { + path: "id", + location: "params", + message: "Partner not found.", + }, + ], + } satisfies ErrorResponse); + } + + if (partner.avatar !== null) { + await this.fileStorage.removeFile(partner.avatar); + } + + await orm.em.removeAndFlush(partner); + + return res.status(204).send(); + } + + public buildRouter(): Router { + const router = Router(); + router.post( + "/", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.createPartner]), + this.create.bind(this), + ); + router.post("/login", createOrmContextMiddleware, this.login.bind(this)); + router.get("/", createOrmContextMiddleware, this.list.bind(this)); + router.get("/:id", createOrmContextMiddleware, this.view.bind(this)); + router.put( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updatePartner]), + this.update.bind(this), + ); + router.put( + "/:id/email", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updatePartner]), + this.changeEmail.bind(this), + ); + router.put( + "/:id/password", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updatePartner]), + this.changePassword.bind(this), + ); + router.put( + "/:id/verify", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.updatePartner]), + this.verify.bind(this), + ); + router.put( + "/refresh", + createOrmContextMiddleware, + isPartnerMiddleware(this.jwtService), + this.refresh.bind(this), + ); + router.delete( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [AdminPermission.deletePartner]), + this.delete.bind(this), + ); + + return router; + } +} diff --git a/src/modules/partner/partner.mapper.ts b/src/modules/partner/partner.mapper.ts new file mode 100644 index 0000000..86ed7ca --- /dev/null +++ b/src/modules/partner/partner.mapper.ts @@ -0,0 +1,18 @@ +import type { Partner } from "@/database/entities/partner.entity"; +import type { PartnerResponse } from "@/modules/partner/partner.types"; + +export class PartnerMapper { + public constructor() {} + + public mapEntityToResponse(partner: Partner): PartnerResponse { + return { + id: partner.id, + name: partner.name, + email: partner.email, + whatsapp: partner.whatsapp, + avatar: partner.avatar, + created_at: partner.createdAt, + updated_at: partner.updatedAt, + }; + } +} diff --git a/src/modules/partner/partner.schemas.ts b/src/modules/partner/partner.schemas.ts new file mode 100644 index 0000000..be81afd --- /dev/null +++ b/src/modules/partner/partner.schemas.ts @@ -0,0 +1,54 @@ +import { + emailSchema, + passwordSchema, + phoneNumberSchema, +} from "@/common/schemas"; +import z from "zod"; + +export const partnerRequestSchema = z.object({ + name: z + .string("Must be string.") + .nonempty("Must not empty.") + .max(100, "Max 100 characters."), + email: emailSchema, + whatsapp: phoneNumberSchema, + password: passwordSchema, + avatar: z + .base64("Must be base64 string.") + .nonempty("Must not empty.") + .nullable(), +}); + +export const partnerLoginRequestSchema = partnerRequestSchema.pick({ + email: true, + password: true, +}); + +export const partnerUpdateRequestSchema = partnerRequestSchema.pick({ + name: true, + avatar: true, + permissions: true, +}); + +export const partnerChangeEmailRequestSchema = z.object({ + new_email: emailSchema, +}); + +export const partnerChangePasswordRequestSchema = z.object({ + old_password: passwordSchema, + new_password: passwordSchema, +}); + +export const partnerVerifyRequestSchema = z.object({ + code: z + .string("Must be string.") + .nonempty("Must not empty.") + .length(6, "Must be 6 characters."), +}); + +export const partnerParamsSchema = z.object({ + id: z + .string("Must be string.") + .nonempty("Must not empty.") + .max(200, "Max 200 characters."), +}); diff --git a/src/modules/partner/partner.types.ts b/src/modules/partner/partner.types.ts new file mode 100644 index 0000000..652d92a --- /dev/null +++ b/src/modules/partner/partner.types.ts @@ -0,0 +1,32 @@ +import type { + partnerChangeEmailRequestSchema, + partnerChangePasswordRequestSchema, + partnerParamsSchema, + partnerRequestSchema, + partnerUpdateRequestSchema, +} from "@/modules/partner/partner.schemas"; +import z from "zod"; + +export type PartnerRequest = z.infer; + +export type PartnerUpdateRequest = z.infer; + +export type PartnerChangeEmailRequest = z.infer< + typeof partnerChangeEmailRequestSchema +>; + +export type PartnerChangePasswordRequest = z.infer< + typeof partnerChangePasswordRequestSchema +>; + +export type PartnerParams = z.infer; + +export type PartnerResponse = { + id: string; + name: string; + email: string; + whatsapp: string; + avatar: string | null; + created_at: Date; + updated_at: Date; +}; diff --git a/src/modules/transportation/transportation.controller.ts b/src/modules/transportation/transportation.controller.ts index 9566926..be44b64 100644 --- a/src/modules/transportation/transportation.controller.ts +++ b/src/modules/transportation/transportation.controller.ts @@ -1,7 +1,9 @@ import { Controller } from "@/common/controller"; -import { ormMiddleware } from "@/common/middlewares/orm.middleware"; +import { createOrmContextMiddleware } from "@/common/middlewares/create-orm-context.middleware"; +import { isAdminMiddleware } from "@/common/middlewares/is-admin.middleware"; import { paginationQuerySchema } from "@/common/schemas"; import type { AbstractFileStorage } from "@/common/services/file-storage/abstract.file-storage"; +import type { AbstractJwtService } from "@/common/services/jwt-service/abstract.jwt-service"; import type { ErrorResponse, ListResponse, @@ -10,6 +12,7 @@ import type { import { TransportationClass } from "@/database/entities/transportation-class.entity"; import { TransportationImage } from "@/database/entities/transportation-image.entity"; import { Transportation } from "@/database/entities/transportation.entity"; +import { AdminPermission } from "@/database/enums/admin-permission.enum"; import { orm } from "@/database/orm"; import type { TransportationMapper } from "@/modules/transportation/transportation.mapper"; import { @@ -29,6 +32,7 @@ export class TransportationController extends Controller { public constructor( private readonly mapper: TransportationMapper, private readonly fileStorage: AbstractFileStorage, + private readonly jwtService: AbstractJwtService, ) { super(); } @@ -526,26 +530,64 @@ export class TransportationController extends Controller { public buildRouter(): Router { const router = Router(); - router.post("/", ormMiddleware, this.create.bind(this)); - router.get("/", ormMiddleware, this.list.bind(this)); - router.get("/:id", ormMiddleware, this.view.bind(this)); - router.put("/:id", ormMiddleware, this.update.bind(this)); - router.delete("/:id", ormMiddleware, this.delete.bind(this)); - router.post("/:id/classes", ormMiddleware, this.createClass.bind(this)); - router.get("/:id/classes", ormMiddleware, this.listClasses.bind(this)); + router.post( + "/", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [ + AdminPermission.createTransportation, + ]), + this.create.bind(this), + ); + router.get("/", createOrmContextMiddleware, this.list.bind(this)); + router.get("/:id", createOrmContextMiddleware, this.view.bind(this)); + router.put( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [ + AdminPermission.updateTransportation, + ]), + this.update.bind(this), + ); + router.delete( + "/:id", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [ + AdminPermission.deleteTransportation, + ]), + this.delete.bind(this), + ); + router.post( + "/:id/classes", + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [ + AdminPermission.createTransportationClass, + ]), + this.createClass.bind(this), + ); + router.get( + "/:id/classes", + createOrmContextMiddleware, + this.listClasses.bind(this), + ); router.get( "/:transportation_id/classes/:id", - ormMiddleware, + createOrmContextMiddleware, this.viewClass.bind(this), ); router.put( "/:transportation_id/classes/:id", - ormMiddleware, + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [ + AdminPermission.updateTransportationClass, + ]), this.updateClass.bind(this), ); router.delete( "/:transportation_id/classes/:id", - ormMiddleware, + createOrmContextMiddleware, + isAdminMiddleware(this.jwtService, [ + AdminPermission.deleteTransportationClass, + ]), this.deleteClass.bind(this), );