feat: inital workings of service
This commit is contained in:
parent
a71c33c085
commit
64ce205296
29 changed files with 975 additions and 22 deletions
|
@ -10,14 +10,14 @@
|
|||
"service": "waterwolf-auth",
|
||||
// The optional 'workspaceFolder' property is the path VS Code should open by default when
|
||||
// connected. This is typically a file mount in .devcontainer/docker-compose.yml
|
||||
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||
"workspaceFolder": "/app/",
|
||||
"features": {
|
||||
"ghcr.io/cirolosapio/devcontainers-features/alpine-git:0": {}
|
||||
},
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {},
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [3000, "mysql:3306"],
|
||||
"forwardPorts": [3000, "mysql:3306", "redis:6379"],
|
||||
|
||||
"portsAttributes": {
|
||||
"3000": {
|
||||
|
|
|
@ -13,7 +13,7 @@ services:
|
|||
|
||||
volumes:
|
||||
# Update this to wherever you want VS Code to mount the folder of your project
|
||||
- ..:/workspaces:cached
|
||||
- ..:/app:cached
|
||||
|
||||
# Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust.
|
||||
# cap_add:
|
||||
|
|
|
@ -1 +1 @@
|
|||
['THIS IS A SECURE THING']
|
||||
["secure_token_1"]
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"keys": [
|
||||
{
|
||||
"p": "xA4KcjSuyAXWzjyG48B_s1Svo-srjm3HbzgNzXz3vyvpkZU3XF-z0TR_Vfoct5LBO8M6Rj4OXyo0vho3v1DrGgkC_V5hMfGvov4C9S31_i5Pl0gjSC_OfScqtTGftJSrA2a9ob21IhAYrE4xtCTf2ETuKaiMY5IZ-HFKuinNI7E",
|
||||
"kty": "RSA",
|
||||
"q": "s0sQRM3OTslJPCAXvgYpWrg1reqaKy7RBuidH90ovHWiQOySwtTcsawek5o3tD6wF-3AUZ6iu_i9Jy4T99Ocz-aOR421GHKTU2SmLmPDpM4d7WvZXvCbddF4k1ihf5OkYYV4HSC10_6SVnSLUxf34YSlfdrRlT3DVFfBg_m9aYc",
|
||||
"d": "ZG1BW0fs-Wu-q9YYM7cdX7v6XixMaFEYDd7Qv-wLK1-sns7Jl0Xd6P8cKEneneRk7oBdEpjCkHiBxsrWydn4JfDZOL4ItD7lTm6q0jq_n6W2SKYu7_0Vxmm3ohuxYuQSoTofJ4G4T_yfAU3-vpF0a-jv9izl8YXZ7H3JZJawKH9TgkpYXQXmjzzro87-JtfsVBlnH_9vhUQrsBS3Eb61voUUH58swTfD0JxnfeogRD8Y0HxuwghhO_xAcE_ZMkOV0W-b4NY6jnM1MFTeP5itHAEEo5SLwXel4eRv5r9GV4XD_2eMYnzvoeA8BVOMWYQgYRanmfVvGbLZYTOmqLxNoQ",
|
||||
"e": "AQAB",
|
||||
"use": "sig",
|
||||
"qi": "Gk6c0VY9i3ka6XNN0PcLEpgM0nIMgfOjYyjl6u0yPSA8-gbLJEKsH1xL4iJj31102VszFpGT5kCCIXUIgcAvetB0A0xxucdAO7CDMEHJoPyNYvd4i5erHuO7Kj2i6I67qmcZGHPzK-TbE4xekVSb8E5lz38J7VTwElleQ5wa08Q",
|
||||
"dp": "biaJXfMVhBIrxsGg89MSrFnXONyHI0WweF9g-ePNeh4c44uXiBHJALBjHpYgjk8ovAAK_K4e-v7GlUw7qAS5om4PvPTK3PmyOXxHgyMog3_XfeKs2ADsHcrkptrTpOymTInr3zSr0RCEHELukAzrqyHHQaaOAd9zMe_NEV0tAXE",
|
||||
"alg": "RS256",
|
||||
"dq": "szd0IqJp950CZGRb9yknizQZDCg2RLX-YN6BuMkToBYhwq33IWMu2zaGNdpwle4XjUOs-qkMV8KSKKjJcu8Gj1YRoHqIq9BTbYdtCW_Vr1YM2jb0yA7QBpwE35w3ilOle4mzf8Ijnq2Xz22dmsiZkcZKuhvRZVGgfx1dJTOs3t8",
|
||||
"n": "iU9N4HxKzLHkNwkbMxozz2fyxC5vl_2m67nFnlI7i_L9ZBPvF2UlEzShAsheriLu4zb4b-5soKVc4L5gII-98dSz_jkO2q6I2UjUDjGKVXht1zAtUqYY5WUTHR6l6Mv2Tt24Mksk2DM5NGzYdFhEZAeP1rWphjJnRXSVXMvvx_IxC5OiQeP6JrH-LZIYzidlwcVUXQaFspkTTDPdovaf29x-NNXxbRrX2fKG7nFehCcZ2S4o6j9annrcxwzbZuOYHtHXTg8euUDs-PNmfeZVB7rGvAPeuslB4QGs45zERa9e9_fKk3YpU-6WVGU5VwaYjuRyuQVUx_L6htwOsX5rVw"
|
||||
}
|
||||
]
|
||||
}
|
10
package.json
10
package.json
|
@ -32,21 +32,26 @@
|
|||
"@nestjs/config": "^3.2.3",
|
||||
"@nestjs/core": "^10.3.10",
|
||||
"@nestjs/platform-express": "^10.3.10",
|
||||
"@nestjs/terminus": "^10.2.3",
|
||||
"@nestjs/typeorm": "^10.0.2",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"bullmq": "^5.9.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"hbs": "^4.2.0",
|
||||
"ioredis": "^5.4.1",
|
||||
"mysql2": "^3.10.2",
|
||||
"nanoid": "^5.0.7",
|
||||
"nestjs-otel": "^6.1.1",
|
||||
"nestjs-postal-client": "^0.0.6",
|
||||
"oidc-provider": "^8.5.1",
|
||||
"pug": "^3.0.3",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"typeorm": "^0.3.20",
|
||||
"typia": "^6.5.1"
|
||||
"typia": "^6.5.1",
|
||||
"uuid": "^10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.4.2",
|
||||
|
@ -90,5 +95,6 @@
|
|||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@9.5.0+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903"
|
||||
}
|
171
pnpm-lock.yaml
171
pnpm-lock.yaml
|
@ -26,9 +26,15 @@ importers:
|
|||
'@nestjs/platform-express':
|
||||
specifier: ^10.3.10
|
||||
version: 10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10)
|
||||
'@nestjs/terminus':
|
||||
specifier: ^10.2.3
|
||||
version: 10.2.3(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/typeorm@10.0.2(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(ioredis@5.4.1)(mysql2@3.10.2)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))))(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(ioredis@5.4.1)(mysql2@3.10.2)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))
|
||||
'@nestjs/typeorm':
|
||||
specifier: ^10.0.2
|
||||
version: 10.0.2(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(ioredis@5.4.1)(mysql2@3.10.2)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))
|
||||
'@opentelemetry/api':
|
||||
specifier: ^1.9.0
|
||||
version: 1.9.0
|
||||
bullmq:
|
||||
specifier: ^5.9.0
|
||||
version: 5.9.0
|
||||
|
@ -41,6 +47,9 @@ importers:
|
|||
dotenv:
|
||||
specifier: ^16.4.5
|
||||
version: 16.4.5
|
||||
hbs:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
ioredis:
|
||||
specifier: ^5.4.1
|
||||
version: 5.4.1
|
||||
|
@ -50,6 +59,9 @@ importers:
|
|||
nanoid:
|
||||
specifier: ^5.0.7
|
||||
version: 5.0.7
|
||||
nestjs-otel:
|
||||
specifier: ^6.1.1
|
||||
version: 6.1.1(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(reflect-metadata@0.2.2)(rxjs@7.8.1))
|
||||
nestjs-postal-client:
|
||||
specifier: ^0.0.6
|
||||
version: 0.0.6(rtrsvjp22aq2opwvwwi7bofxyi)
|
||||
|
@ -71,6 +83,9 @@ importers:
|
|||
typia:
|
||||
specifier: ^6.5.1
|
||||
version: 6.5.1(typescript@5.5.3)
|
||||
uuid:
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0
|
||||
devDependencies:
|
||||
'@nestjs/cli':
|
||||
specifier: ^10.4.2
|
||||
|
@ -659,6 +674,54 @@ packages:
|
|||
class-validator:
|
||||
optional: true
|
||||
|
||||
'@nestjs/terminus@10.2.3':
|
||||
resolution: {integrity: sha512-iX7gXtAooePcyQqFt57aDke5MzgdkBeYgF5YsFNNFwOiAFdIQEhfv3PR0G+HlH9F6D7nBCDZt9U87Pks/qHijg==}
|
||||
peerDependencies:
|
||||
'@grpc/grpc-js': '*'
|
||||
'@grpc/proto-loader': '*'
|
||||
'@mikro-orm/core': '*'
|
||||
'@mikro-orm/nestjs': '*'
|
||||
'@nestjs/axios': ^1.0.0 || ^2.0.0 || ^3.0.0
|
||||
'@nestjs/common': ^9.0.0 || ^10.0.0
|
||||
'@nestjs/core': ^9.0.0 || ^10.0.0
|
||||
'@nestjs/microservices': ^9.0.0 || ^10.0.0
|
||||
'@nestjs/mongoose': ^9.0.0 || ^10.0.0
|
||||
'@nestjs/sequelize': ^9.0.0 || ^10.0.0
|
||||
'@nestjs/typeorm': ^9.0.0 || ^10.0.0
|
||||
'@prisma/client': '*'
|
||||
mongoose: '*'
|
||||
reflect-metadata: 0.1.x || 0.2.x
|
||||
rxjs: 7.x
|
||||
sequelize: '*'
|
||||
typeorm: '*'
|
||||
peerDependenciesMeta:
|
||||
'@grpc/grpc-js':
|
||||
optional: true
|
||||
'@grpc/proto-loader':
|
||||
optional: true
|
||||
'@mikro-orm/core':
|
||||
optional: true
|
||||
'@mikro-orm/nestjs':
|
||||
optional: true
|
||||
'@nestjs/axios':
|
||||
optional: true
|
||||
'@nestjs/microservices':
|
||||
optional: true
|
||||
'@nestjs/mongoose':
|
||||
optional: true
|
||||
'@nestjs/sequelize':
|
||||
optional: true
|
||||
'@nestjs/typeorm':
|
||||
optional: true
|
||||
'@prisma/client':
|
||||
optional: true
|
||||
mongoose:
|
||||
optional: true
|
||||
sequelize:
|
||||
optional: true
|
||||
typeorm:
|
||||
optional: true
|
||||
|
||||
'@nestjs/testing@10.3.10':
|
||||
resolution: {integrity: sha512-i3HAtVQJijxNxJq1k39aelyJlyEIBRONys7IipH/4r8W0J+M1V+y5EKDOyi4j1SdNSb/vmNyWpZ2/ewZjl3kRA==}
|
||||
peerDependencies:
|
||||
|
@ -1056,6 +1119,9 @@ packages:
|
|||
ajv@8.17.1:
|
||||
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
|
||||
|
||||
ansi-align@3.0.1:
|
||||
resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
|
||||
|
||||
ansi-colors@4.1.3:
|
||||
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -1204,6 +1270,10 @@ packages:
|
|||
resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==}
|
||||
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||
|
||||
boxen@5.1.2:
|
||||
resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
brace-expansion@1.1.11:
|
||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||
|
||||
|
@ -1307,6 +1377,10 @@ packages:
|
|||
chardet@0.7.0:
|
||||
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
|
||||
|
||||
check-disk-space@3.4.0:
|
||||
resolution: {integrity: sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
chokidar@3.6.0:
|
||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
|
@ -1328,6 +1402,10 @@ packages:
|
|||
class-validator@0.14.1:
|
||||
resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==}
|
||||
|
||||
cli-boxes@2.2.1:
|
||||
resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
cli-color@2.0.4:
|
||||
resolution: {integrity: sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
@ -1920,6 +1998,9 @@ packages:
|
|||
debug:
|
||||
optional: true
|
||||
|
||||
foreachasync@3.0.0:
|
||||
resolution: {integrity: sha512-J+ler7Ta54FwwNcx6wQRDhTIbNeyDcARMkOcguEqnEdtm0jKvN3Li3PDAb2Du3ubJYEWfYL83XMROXdsXAXycw==}
|
||||
|
||||
foreground-child@3.2.1:
|
||||
resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==}
|
||||
engines: {node: '>=14'}
|
||||
|
@ -2044,6 +2125,11 @@ packages:
|
|||
graphemer@1.4.0:
|
||||
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
|
||||
|
||||
handlebars@4.7.7:
|
||||
resolution: {integrity: sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==}
|
||||
engines: {node: '>=0.4.7'}
|
||||
hasBin: true
|
||||
|
||||
has-flag@3.0.0:
|
||||
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -2075,6 +2161,10 @@ packages:
|
|||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hbs@4.2.0:
|
||||
resolution: {integrity: sha512-dQwHnrfWlTk5PvG9+a45GYpg0VpX47ryKF8dULVd6DtwOE6TEcYQXQ5QM6nyOx/h7v3bvEQbdn19EDAcfUAgZg==}
|
||||
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||
|
||||
hexoid@1.0.0:
|
||||
resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -3788,6 +3878,11 @@ packages:
|
|||
peerDependencies:
|
||||
typescript: '>=4.8.0 <5.6.0'
|
||||
|
||||
uglify-js@3.18.0:
|
||||
resolution: {integrity: sha512-SyVVbcNBCk0dzr9XL/R/ySrmYf0s372K6/hFklzgcp2lBFyXtw4I7BOdDjlLhE1aVqaI/SHWXWmYdlZxuyF38A==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
hasBin: true
|
||||
|
||||
uid@2.0.2:
|
||||
resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -3823,6 +3918,10 @@ packages:
|
|||
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
|
||||
uuid@10.0.0:
|
||||
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
|
||||
hasBin: true
|
||||
|
||||
uuid@9.0.1:
|
||||
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
|
||||
hasBin: true
|
||||
|
@ -3846,6 +3945,9 @@ packages:
|
|||
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
walk@2.3.15:
|
||||
resolution: {integrity: sha512-4eRTBZljBfIISK1Vnt69Gvr2w/wc3U6Vtrw7qiN5iqYJPH7LElcYh/iU4XWhdCy2dZqv1ToMyYlybDylfG/5Vg==}
|
||||
|
||||
walker@1.0.8:
|
||||
resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
|
||||
|
||||
|
@ -3889,6 +3991,10 @@ packages:
|
|||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
widest-line@3.1.0:
|
||||
resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
with@7.0.2:
|
||||
resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
@ -3897,6 +4003,9 @@ packages:
|
|||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
wordwrap@1.0.0:
|
||||
resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -4660,6 +4769,18 @@ snapshots:
|
|||
class-transformer: 0.5.1
|
||||
class-validator: 0.14.1
|
||||
|
||||
'@nestjs/terminus@10.2.3(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/typeorm@10.0.2(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(ioredis@5.4.1)(mysql2@3.10.2)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))))(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(ioredis@5.4.1)(mysql2@3.10.2)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))':
|
||||
dependencies:
|
||||
'@nestjs/common': 10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||
'@nestjs/core': 10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||
boxen: 5.1.2
|
||||
check-disk-space: 3.4.0
|
||||
reflect-metadata: 0.2.2
|
||||
rxjs: 7.8.1
|
||||
optionalDependencies:
|
||||
'@nestjs/typeorm': 10.0.2(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(ioredis@5.4.1)(mysql2@3.10.2)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))
|
||||
typeorm: 0.3.20(ioredis@5.4.1)(mysql2@3.10.2)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))
|
||||
|
||||
'@nestjs/testing@10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10))':
|
||||
dependencies:
|
||||
'@nestjs/common': 10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||
|
@ -5116,6 +5237,10 @@ snapshots:
|
|||
json-schema-traverse: 1.0.0
|
||||
require-from-string: 2.0.2
|
||||
|
||||
ansi-align@3.0.1:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
|
||||
ansi-colors@4.1.3: {}
|
||||
|
||||
ansi-escapes@4.3.2:
|
||||
|
@ -5282,6 +5407,17 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
boxen@5.1.2:
|
||||
dependencies:
|
||||
ansi-align: 3.0.1
|
||||
camelcase: 6.3.0
|
||||
chalk: 4.1.2
|
||||
cli-boxes: 2.2.1
|
||||
string-width: 4.2.3
|
||||
type-fest: 0.20.2
|
||||
widest-line: 3.1.0
|
||||
wrap-ansi: 7.0.0
|
||||
|
||||
brace-expansion@1.1.11:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
@ -5403,6 +5539,8 @@ snapshots:
|
|||
|
||||
chardet@0.7.0: {}
|
||||
|
||||
check-disk-space@3.4.0: {}
|
||||
|
||||
chokidar@3.6.0:
|
||||
dependencies:
|
||||
anymatch: 3.1.3
|
||||
|
@ -5429,6 +5567,8 @@ snapshots:
|
|||
libphonenumber-js: 1.11.4
|
||||
validator: 13.12.0
|
||||
|
||||
cli-boxes@2.2.1: {}
|
||||
|
||||
cli-color@2.0.4:
|
||||
dependencies:
|
||||
d: 1.0.2
|
||||
|
@ -6073,6 +6213,8 @@ snapshots:
|
|||
|
||||
follow-redirects@1.15.6: {}
|
||||
|
||||
foreachasync@3.0.0: {}
|
||||
|
||||
foreground-child@3.2.1:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.3
|
||||
|
@ -6226,6 +6368,15 @@ snapshots:
|
|||
|
||||
graphemer@1.4.0: {}
|
||||
|
||||
handlebars@4.7.7:
|
||||
dependencies:
|
||||
minimist: 1.2.8
|
||||
neo-async: 2.6.2
|
||||
source-map: 0.6.1
|
||||
wordwrap: 1.0.0
|
||||
optionalDependencies:
|
||||
uglify-js: 3.18.0
|
||||
|
||||
has-flag@3.0.0: {}
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
|
@ -6248,6 +6399,11 @@ snapshots:
|
|||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hbs@4.2.0:
|
||||
dependencies:
|
||||
handlebars: 4.7.7
|
||||
walk: 2.3.15
|
||||
|
||||
hexoid@1.0.0: {}
|
||||
|
||||
highlight.js@10.7.3: {}
|
||||
|
@ -8123,6 +8279,9 @@ snapshots:
|
|||
randexp: 0.5.3
|
||||
typescript: 5.5.3
|
||||
|
||||
uglify-js@3.18.0:
|
||||
optional: true
|
||||
|
||||
uid@2.0.2:
|
||||
dependencies:
|
||||
'@lukeed/csprng': 1.1.0
|
||||
|
@ -8151,6 +8310,8 @@ snapshots:
|
|||
|
||||
utils-merge@1.0.1: {}
|
||||
|
||||
uuid@10.0.0: {}
|
||||
|
||||
uuid@9.0.1: {}
|
||||
|
||||
v8-compile-cache-lib@3.0.1: {}
|
||||
|
@ -8167,6 +8328,10 @@ snapshots:
|
|||
|
||||
void-elements@3.1.0: {}
|
||||
|
||||
walk@2.3.15:
|
||||
dependencies:
|
||||
foreachasync: 3.0.0
|
||||
|
||||
walker@1.0.8:
|
||||
dependencies:
|
||||
makeerror: 1.0.12
|
||||
|
@ -8230,6 +8395,10 @@ snapshots:
|
|||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
widest-line@3.1.0:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
|
||||
with@7.0.2:
|
||||
dependencies:
|
||||
'@babel/parser': 7.24.8
|
||||
|
@ -8239,6 +8408,8 @@ snapshots:
|
|||
|
||||
word-wrap@1.2.5: {}
|
||||
|
||||
wordwrap@1.0.0: {}
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
|
|
2
src/app.const.ts
Normal file
2
src/app.const.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
// This is the internal client ID for the application itself.
|
||||
export const internalClientId = 'internal.management';
|
|
@ -1,4 +1,4 @@
|
|||
import { Controller, Get } from '@nestjs/common';
|
||||
import { Controller, Get, Render } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
import { PostalClientService } from 'nestjs-postal-client';
|
||||
|
||||
|
@ -10,8 +10,9 @@ export class AppController {
|
|||
) {}
|
||||
|
||||
@Get()
|
||||
getHello(): string {
|
||||
return this.appService.getHello();
|
||||
@Render('home/index')
|
||||
home() {
|
||||
return { message: 'Hello world!'}
|
||||
}
|
||||
|
||||
@Get('email_test')
|
||||
|
|
|
@ -4,15 +4,37 @@ import { AppService } from './app.service';
|
|||
import { ConfigModule } from '@nestjs/config';
|
||||
import config from './config/config';
|
||||
import { MailModule } from './mail/mail.module';
|
||||
import { RedisModule } from './redis/redis.module';
|
||||
import { OpenTelemetryModule } from 'nestjs-otel';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
OpenTelemetryModule.forRoot({
|
||||
metrics: {
|
||||
apiMetrics: {
|
||||
enable: true,
|
||||
ignoreRoutes: [
|
||||
'/favicon.ico',
|
||||
'/OidcServiceWorker.js',
|
||||
'/swagger',
|
||||
'/swagger-json',
|
||||
'/swagger-yaml',
|
||||
'/swagger/(.*)',
|
||||
'/metrics',
|
||||
'/interaction/(.*}',
|
||||
],
|
||||
ignoreUndefinedRoutes: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
ConfigModule.forRoot({
|
||||
cache: true,
|
||||
isGlobal: true,
|
||||
load: [config],
|
||||
}),
|
||||
MailModule,
|
||||
RedisModule,
|
||||
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
|
|
|
@ -3,4 +3,10 @@ export default async () => ({
|
|||
port: process.env.PORT || 3000,
|
||||
host: process.env.HOST || 'localhost',
|
||||
},
|
||||
redis: {
|
||||
host: process.env['REDIS_HOST'] ?? 'localhost',
|
||||
port: process.env['REDIS_POST'] ?? 6379,
|
||||
password: process.env['REDIS_PASSWORD'] ?? '',
|
||||
db: process.env['REDIS_DB'] ?? 0
|
||||
}
|
||||
});
|
||||
|
|
32
src/database/models/api_keys.model.ts
Normal file
32
src/database/models/api_keys.model.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import {
|
||||
BaseEntity,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
PrimaryColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { User } from './user.model';
|
||||
|
||||
@Entity('api_keys')
|
||||
export class ApiKey extends BaseEntity {
|
||||
@PrimaryColumn()
|
||||
id: string;
|
||||
|
||||
@PrimaryColumn()
|
||||
key: string;
|
||||
|
||||
@Column()
|
||||
title: string;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.apiKeys)
|
||||
user: User;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
}
|
87
src/database/models/oidc_client.model.ts
Normal file
87
src/database/models/oidc_client.model.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
import type {
|
||||
ClientAuthMethod,
|
||||
ResponseType,
|
||||
SigningAlgorithmWithNone,
|
||||
} from 'oidc-provider';
|
||||
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { OidcClientPermission } from './oidc_client_permissions.model';
|
||||
import { MAX_STRING_LENGTH } from '../database.const';
|
||||
|
||||
@Entity()
|
||||
export class OidcClient {
|
||||
// Client ID
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
client_id: string;
|
||||
|
||||
// Owner Org ID
|
||||
@Column({ type: 'varchar', length: MAX_STRING_LENGTH, nullable: false })
|
||||
ownerName: string;
|
||||
|
||||
@Column({ type: 'varchar', length: MAX_STRING_LENGTH, nullable: false })
|
||||
client_secret: string;
|
||||
|
||||
@Column({ type: 'varchar', length: MAX_STRING_LENGTH, nullable: true })
|
||||
client_name: string;
|
||||
|
||||
@Column({ type: 'simple-array', nullable: false })
|
||||
redirect_uris: string[];
|
||||
|
||||
@Column({ type: 'simple-array', nullable: true })
|
||||
client_cors: string[];
|
||||
|
||||
@Column({ type: 'simple-array', nullable: true })
|
||||
allowed_introspection_targets: string[];
|
||||
|
||||
@Column({ type: 'simple-array', nullable: false })
|
||||
include_permissions_from_client: string[];
|
||||
|
||||
@Column({ type: 'simple-array', nullable: false })
|
||||
post_logout_redirect_uris: string[];
|
||||
|
||||
@Column({ type: 'simple-array', nullable: false })
|
||||
response_types: ResponseType[];
|
||||
|
||||
@Column({ type: 'simple-array', nullable: false })
|
||||
grant_types: string[];
|
||||
|
||||
@Column({ type: 'varchar', length: MAX_STRING_LENGTH, nullable: false })
|
||||
token_endpoint_auth_method: ClientAuthMethod;
|
||||
|
||||
@Column({ type: 'varchar', length: MAX_STRING_LENGTH, nullable: false })
|
||||
application_type: 'web' | 'native';
|
||||
|
||||
@Column({ type: 'varchar', length: MAX_STRING_LENGTH, nullable: false })
|
||||
logo_uri: string;
|
||||
|
||||
@Column({ type: 'boolean', nullable: false, default: false })
|
||||
restricted: boolean;
|
||||
|
||||
@OneToMany(() => OidcClientPermission, (permission) => permission.client)
|
||||
permissions: OidcClientPermission[];
|
||||
|
||||
@ManyToMany(
|
||||
() => OidcClientPermission,
|
||||
(permission) => permission.assignedClients,
|
||||
)
|
||||
@JoinTable({
|
||||
name: 'oidc_client_permissions',
|
||||
joinColumn: {
|
||||
name: 'client_id',
|
||||
},
|
||||
inverseJoinColumn: {
|
||||
name: 'permission_id',
|
||||
},
|
||||
})
|
||||
assignedPermissions: OidcClientPermission[];
|
||||
|
||||
[key: string]: unknown;
|
||||
}
|
28
src/database/models/oidc_client_permissions.model.ts
Normal file
28
src/database/models/oidc_client_permissions.model.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import {
|
||||
BeforeInsert,
|
||||
Column,
|
||||
Entity,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
PrimaryColumn,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
} from 'typeorm';
|
||||
|
||||
import { OidcClient } from './oidc_client.model';
|
||||
|
||||
@Unique(['title', 'client'])
|
||||
@Entity('oidc_client_permission')
|
||||
export class OidcClientPermission {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
title: string;
|
||||
|
||||
@ManyToMany(() => OidcClient, (oidcClient) => oidcClient.assignedPermissions)
|
||||
assignedClients: OidcClient[];
|
||||
|
||||
@ManyToOne(() => OidcClient, (oidcClient) => oidcClient.permissions)
|
||||
client: OidcClient;
|
||||
}
|
69
src/database/models/oidc_grant.model.ts
Normal file
69
src/database/models/oidc_grant.model.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
import type { AdapterPayload } from 'oidc-provider';
|
||||
import { Column, Entity, Index, PrimaryColumn } from 'typeorm';
|
||||
|
||||
import {
|
||||
convertFromNumberToTime,
|
||||
convertFromTimeToNumber,
|
||||
} from '../../util/time.util';
|
||||
|
||||
@Entity()
|
||||
export class OidcGrant implements AdapterPayload {
|
||||
@PrimaryColumn()
|
||||
id: string;
|
||||
|
||||
@Index('oidc_account_id')
|
||||
@Column({
|
||||
nullable: true,
|
||||
})
|
||||
accountId: string;
|
||||
|
||||
@Index('oidc_client_id')
|
||||
@Column({
|
||||
nullable: true,
|
||||
})
|
||||
clientId: string;
|
||||
|
||||
@Column({
|
||||
type: 'timestamp',
|
||||
nullable: true,
|
||||
transformer: {
|
||||
from: (value: Date) => convertFromTimeToNumber(value),
|
||||
to: (value: number) => convertFromNumberToTime(value),
|
||||
},
|
||||
})
|
||||
iat?: number;
|
||||
|
||||
@Column({
|
||||
type: 'timestamp',
|
||||
nullable: true,
|
||||
transformer: {
|
||||
from: (value: Date) => convertFromTimeToNumber(value),
|
||||
to: (value: number) => convertFromNumberToTime(value),
|
||||
},
|
||||
})
|
||||
exp?: number;
|
||||
|
||||
@Column({ type: 'json', nullable: true })
|
||||
openid?: {
|
||||
scope?: string;
|
||||
claims?: string[];
|
||||
};
|
||||
|
||||
@Column({ type: 'simple-array', nullable: true })
|
||||
resources?: string[];
|
||||
|
||||
generateResponse(): AdapterPayload {
|
||||
return {
|
||||
iat: this.iat,
|
||||
exp: this.exp,
|
||||
accountId: this.accountId,
|
||||
clientId: this.clientId,
|
||||
kind: 'Grant',
|
||||
jti: this.id,
|
||||
openid: this.openid,
|
||||
resources: this.resources,
|
||||
};
|
||||
}
|
||||
|
||||
[key: string]: unknown;
|
||||
}
|
0
src/database/models/oidc_resource_servers.model.ts
Normal file
0
src/database/models/oidc_resource_servers.model.ts
Normal file
41
src/database/models/organization.model.ts
Normal file
41
src/database/models/organization.model.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { MAX_STRING_LENGTH } from '../database.const';
|
||||
|
||||
@Entity('organization')
|
||||
export class Organization {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ length: MAX_STRING_LENGTH })
|
||||
name: string;
|
||||
|
||||
@Column({ length: MAX_STRING_LENGTH, nullable: true })
|
||||
logo?: string;
|
||||
|
||||
@Column({ length: MAX_STRING_LENGTH, nullable: true })
|
||||
background?: string;
|
||||
|
||||
@Column({ length: MAX_STRING_LENGTH, nullable: true })
|
||||
website?: string;
|
||||
|
||||
@Column({ length: MAX_STRING_LENGTH, nullable: true })
|
||||
description?: string;
|
||||
|
||||
@Column({ name: 'owner_id' })
|
||||
ownerId: number;
|
||||
|
||||
@Column({
|
||||
name: 'created_at',
|
||||
type: 'timestamp',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
createdAt: Date;
|
||||
|
||||
@Column({
|
||||
name: 'updated_at',
|
||||
type: 'timestamp',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
onUpdate: 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
updatedAt: Date;
|
||||
}
|
0
src/database/models/organization_role.model.ts
Normal file
0
src/database/models/organization_role.model.ts
Normal file
|
@ -1,5 +1,6 @@
|
|||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { MAX_STRING_LENGTH, UserRole } from '../database.const';
|
||||
import { ApiKey } from './api_keys.model';
|
||||
|
||||
@Entity('user')
|
||||
export class User {
|
||||
|
@ -9,9 +10,15 @@ export class User {
|
|||
@Column({ length: MAX_STRING_LENGTH })
|
||||
username: string;
|
||||
|
||||
@Column({ length: MAX_STRING_LENGTH, name: 'display_name', nullable: true })
|
||||
displayName: string;
|
||||
|
||||
@Column({ length: MAX_STRING_LENGTH })
|
||||
email: string;
|
||||
|
||||
@Column({ name: 'email_verified', default: false })
|
||||
emailVerified: boolean;
|
||||
|
||||
@Column({ length: MAX_STRING_LENGTH })
|
||||
password: string;
|
||||
|
||||
|
@ -25,5 +32,33 @@ export class User {
|
|||
avatar: string;
|
||||
|
||||
@Column({ length: 15, enum: UserRole, default: UserRole.USER })
|
||||
role: string;
|
||||
role: UserRole;
|
||||
|
||||
@Column({ name: 'disabled', default: false })
|
||||
disabled: boolean;
|
||||
|
||||
// This is for Gravatar Support
|
||||
@Column({ name: 'email_hash', length: MAX_STRING_LENGTH, nullable: true })
|
||||
emailHash: string;
|
||||
|
||||
// Relationship Mapping
|
||||
|
||||
@OneToMany(() => ApiKey, (apiKey) => apiKey.user)
|
||||
apiKeys: ApiKey[];
|
||||
|
||||
// Created At/Updated At
|
||||
@Column({
|
||||
name: 'created_at',
|
||||
type: 'timestamp',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
createdAt: Date;
|
||||
|
||||
@Column({
|
||||
name: 'updated_at',
|
||||
type: 'timestamp',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
onUpdate: 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
|
0
src/database/models/user_audit_log.model.ts
Normal file
0
src/database/models/user_audit_log.model.ts
Normal file
0
src/database/models/user_connection.model.ts
Normal file
0
src/database/models/user_connection.model.ts
Normal file
0
src/encryption/encryption.module.ts
Normal file
0
src/encryption/encryption.module.ts
Normal file
0
src/encryption/encryption.service.ts
Normal file
0
src/encryption/encryption.service.ts
Normal file
11
src/main.ts
11
src/main.ts
|
@ -1,8 +1,17 @@
|
|||
import { NestFactory } from '@nestjs/core';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { join } from 'path';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
const app = await NestFactory.create<NestExpressApplication>(
|
||||
AppModule,
|
||||
);
|
||||
|
||||
app.useStaticAssets(join(__dirname, '..', 'public'));
|
||||
app.setBaseViewsDir(join(__dirname, '..', 'views'));
|
||||
app.setViewEngine('hbs');
|
||||
|
||||
await app.listen(3000);
|
||||
}
|
||||
bootstrap();
|
10
src/redis/redis.module.ts
Normal file
10
src/redis/redis.module.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
import { RedisService } from './redis.service';
|
||||
|
||||
@Module({
|
||||
providers: [RedisService, ConfigService],
|
||||
exports: [RedisService],
|
||||
})
|
||||
export class RedisModule {}
|
286
src/redis/redis.service.ts
Normal file
286
src/redis/redis.service.ts
Normal file
|
@ -0,0 +1,286 @@
|
|||
import { Injectable, Logger, OnApplicationShutdown } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { HealthIndicatorResult, HealthIndicatorStatus } from '@nestjs/terminus';
|
||||
import { ObservableGauge } from '@opentelemetry/api';
|
||||
import Redis, { ChainableCommander } from 'ioredis';
|
||||
import { MetricService } from 'nestjs-otel';
|
||||
|
||||
@Injectable()
|
||||
export class RedisService implements OnApplicationShutdown {
|
||||
private _ioredis: Redis;
|
||||
private readonly logger = new Logger(RedisService.name);
|
||||
private readonly serviceConnectedObservableGauge: ObservableGauge;
|
||||
|
||||
private readonly host: string | undefined;
|
||||
private readonly port: number;
|
||||
private readonly password: string | undefined;
|
||||
private readonly db: number;
|
||||
|
||||
constructor(
|
||||
configService: ConfigService,
|
||||
private readonly metricService: MetricService,
|
||||
) {
|
||||
this.host = configService.getOrThrow('redis.host');
|
||||
this.port = +configService.get('redis.port') ?? 6789;
|
||||
this.password = configService.get('redis.password') ?? '';
|
||||
this.db = +configService.get('redis.db') ?? 0;
|
||||
|
||||
this.serviceConnectedObservableGauge = this.metricService.getObservableGauge(
|
||||
'service.connected',
|
||||
{ description: 'Whether a needed service is connected' },
|
||||
);
|
||||
|
||||
this.logger.debug(`Connecting to Redis at ${this.host}:${this.port} using db ${this.db}`);
|
||||
this._ioredis = new Redis({
|
||||
host: this.host,
|
||||
port: this.port,
|
||||
password: this.password,
|
||||
db: this.db,
|
||||
enableAutoPipelining: true,
|
||||
});
|
||||
|
||||
this._ioredis.on('ready', () => {
|
||||
this.logger.log(`Connected to Redis at ${this.host}:${this.port} using db ${this.db}`);
|
||||
});
|
||||
|
||||
this._ioredis.on('error', (err) => {
|
||||
this.logger.error(err, 'Redis error');
|
||||
});
|
||||
|
||||
this.serviceConnectedObservableGauge.addCallback((result) => {
|
||||
result.observe(this._ioredis.status === 'ready' ? 1 : 0, { target: 'redis' });
|
||||
});
|
||||
}
|
||||
|
||||
async onApplicationShutdown(): Promise<void> {
|
||||
await this._ioredis.quit();
|
||||
}
|
||||
|
||||
public get ioredis(): Redis {
|
||||
return this._ioredis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a value or object in redis
|
||||
* @param key Key for the value to store
|
||||
* @param value Value to store
|
||||
* @param ttl Time to live in seconds
|
||||
*/
|
||||
public async set(key: string, value: string | object, ttl?: number): Promise<void> {
|
||||
this.logger.debug(`Setting key ${key}`);
|
||||
if (typeof value === 'object') {
|
||||
value = JSON.stringify(value);
|
||||
}
|
||||
if (ttl) {
|
||||
await this._ioredis.set(key, value, 'EX', ttl);
|
||||
} else {
|
||||
await this._ioredis.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will return a string or object based on if it was stored as one. Otherwise will return null if not found.
|
||||
* @param key Key for the value to get
|
||||
* @returns
|
||||
*/
|
||||
public async get(key: string): Promise<string | object | null> {
|
||||
this.logger.debug(`Getting key ${key}`);
|
||||
const value = await this._ioredis.get(key);
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (error) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a value from redis
|
||||
* @param key Key for the value to remove
|
||||
* @returns
|
||||
*/
|
||||
public async del(key: string): Promise<void> {
|
||||
await this._ioredis.del(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get then delete a value from redis
|
||||
* @param key Key for the value to get and remove
|
||||
* @returns
|
||||
*/
|
||||
public async getDel(key: string): Promise<string | object | null> {
|
||||
const value = await this.get(key);
|
||||
await this.del(key);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a redis multi command
|
||||
* @returns Redis.Multi
|
||||
*/
|
||||
public multi(): ChainableCommander {
|
||||
return this._ioredis.multi();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a redis multi command
|
||||
* @param multi Redis.Multi
|
||||
* @returns Promise<any>
|
||||
*/
|
||||
public async exec(multi: ChainableCommander): Promise<any> {
|
||||
return await multi.exec();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a ttl on a key
|
||||
* @param key Key for the value to set the ttl on
|
||||
* @param ttl Time to live in seconds
|
||||
* @returns Promise<number>
|
||||
*/
|
||||
public async expire(key: string, ttl: number): Promise<number> {
|
||||
return await this._ioredis.expire(key, ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a key with a ttl
|
||||
* @param key Key for the value to set
|
||||
* @param value Value to set
|
||||
* @param ttl Time to live in seconds
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
public async setex(key: string, value: string | object, ttl: number): Promise<void> {
|
||||
if (typeof value === 'object') {
|
||||
value = JSON.stringify(value);
|
||||
}
|
||||
await this._ioredis.setex(key, ttl, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ttl on a key
|
||||
* @param key Key for the value to get the ttl on
|
||||
* @returns Promise<number>
|
||||
*/
|
||||
public async ttl(key: string): Promise<number> {
|
||||
return await this._ioredis.ttl(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a JSON.GET command and parse the result, return null if not found
|
||||
* @param key Key for the value to get
|
||||
* @returns Promise<object | null>
|
||||
*/
|
||||
public async jsonGet(key: string): Promise<object | null> {
|
||||
const value = (await this._ioredis.call('JSON.GET', key)) as string | null;
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a JSON.SET command and stringify the value if it is an object
|
||||
* @param key Key for the value to set
|
||||
* @param value Value to set
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
public async jsonSet(key: string, value: string | object): Promise<void> {
|
||||
if (typeof value === 'object') {
|
||||
value = JSON.stringify(value);
|
||||
}
|
||||
await this._ioredis.set(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a JSON.SET command with an expiration and stringify the value if it is an object
|
||||
* @param key Key for the value to set
|
||||
* @param value Value to set
|
||||
* @param ttl Time to live in seconds
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
public async jsonSetEx(key: string, value: string | object, ttl: number): Promise<void> {
|
||||
if (typeof value === 'object') {
|
||||
value = JSON.stringify(value);
|
||||
}
|
||||
await this._ioredis.set(key, value, 'EX', ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify a value of a JSON.SET command
|
||||
* @param key Key for the value to modify
|
||||
* @param path Path to the value to modify
|
||||
* @param value Value to set
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
public async jsonSetPath(
|
||||
key: string,
|
||||
path: string,
|
||||
value: string | object | number,
|
||||
): Promise<void> {
|
||||
if (typeof value === 'object') {
|
||||
value = JSON.stringify(value);
|
||||
}
|
||||
await this._ioredis.call('JSON.SET', key, path, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preform a lrange command and parse the results
|
||||
* @param key Key for the value to get
|
||||
* @param start Start index
|
||||
* @param stop Stop index
|
||||
* @param parseJson Parse the results as JSON, normally false
|
||||
* @returns Promise<object[]>
|
||||
*/
|
||||
public async lrange(
|
||||
key: string,
|
||||
start: number,
|
||||
stop: number,
|
||||
parseJson = false,
|
||||
): Promise<object[] | any[]> {
|
||||
const value = await this._ioredis.lrange(key, start, stop);
|
||||
if (!value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (parseJson) {
|
||||
return value.map((v) => JSON.parse(v));
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping the redis server
|
||||
*/
|
||||
public async ping(): Promise<string> {
|
||||
return await this._ioredis.ping();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the health of the redis connection
|
||||
* @returns Promise<string> up | down
|
||||
*/
|
||||
public async checkHealth(): Promise<HealthIndicatorResult> {
|
||||
let status: HealthIndicatorStatus = 'down';
|
||||
|
||||
try {
|
||||
await this.ping();
|
||||
status = 'up';
|
||||
} catch (error) {
|
||||
status = 'down';
|
||||
}
|
||||
|
||||
return {
|
||||
redis: {
|
||||
status,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
7
src/util/time.util.ts
Normal file
7
src/util/time.util.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export const convertFromNumberToTime = (time: number | null): Date | null => {
|
||||
return time ? new Date(time * 1000) : null;
|
||||
};
|
||||
|
||||
export const convertFromTimeToNumber = (time: Date | null): number | null => {
|
||||
return time ? time.getTime() / 1000 : null;
|
||||
};
|
114
views/auth/login.hbs
Normal file
114
views/auth/login.hbs
Normal file
|
@ -0,0 +1,114 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login Page</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||
<style>
|
||||
.video-bg {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: -1;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.video-bg, .overlay {
|
||||
display: none;
|
||||
}
|
||||
.login-prompt {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
position: static;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="relative flex items-center justify-start min-h-screen">
|
||||
<video autoplay muted loop class="video-bg">
|
||||
<source src="file:///C:/Users/kakious/Desktop/login.webm" type="video/webm">
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
<div class="overlay"></div>
|
||||
<div class="relative bg-gray-800 p-8 rounded-lg shadow-lg w-full max-w-md ml-16 login-prompt">
|
||||
<h2 class="text-2xl font-bold mb-2 text-white text-center">Welcome back!</h2>
|
||||
<p class="text-gray-400 mb-6 text-center">We're so excited to see you again!</p>
|
||||
<form action="{{ login_url }}" method="POST" class="space-y-6">
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-400">EMAIL OR USERNAME</label>
|
||||
<input type="text" id="email" name="email" required class="mt-1 block w-full px-3 py-2 bg-gray-700 text-white border border-gray-600 rounded-md shadow-sm placeholder-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium text-gray-400">PASSWORD</label>
|
||||
<input type="password" id="password" name="password" required class="mt-1 block w-full px-3 py-2 bg-gray-700 text-white border border-gray-600 rounded-md shadow-sm placeholder-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-sm">
|
||||
<a href="{{ forgot_password }}" class="font-medium text-indigo-500 hover:text-indigo-400">Forgot your password?</a>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">Log In</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-4 text-sm text-center">
|
||||
<span class="text-gray-400">Need an account? </span><a href="{{ register }}" class="font-medium text-indigo-500 hover:text-indigo-400">Register</a>
|
||||
</div>
|
||||
<div class="mt-6 flex items-center justify-center text-gray-400 text-xs">
|
||||
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 784.69 187.35" width="40%" height="40%">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fff;
|
||||
stroke-width: 0px;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="WatchingWaterwolf">
|
||||
<g>
|
||||
<g>
|
||||
<polygon class="cls-1" points="269.31 78.37 260.14 136.91 248.7 78.37 244.28 78.37 239.92 78.37 235.38 78.37 223.91 137.03 214.7 78.37 198.81 78.37 214.82 165.36 220.98 165.36 225.1 165.36 230.48 165.36 242.08 109.33 253.48 165.36 258.92 165.36 262.92 165.36 269.19 165.36 285.15 78.37 269.31 78.37"/>
|
||||
<path class="cls-1" d="M320.05,78.37h-10.52l-27.42,86.99h16.85l5.18-19.06h25.25l5.18,19.06h16.91l-27.54-86.99h-3.88ZM307.75,132.98l9.01-33.17,9.01,33.17h-18.02Z"/>
|
||||
<polygon class="cls-1" points="365.7 78.37 344.13 78.37 344.13 91.76 365.7 91.76 365.7 165.36 381.59 165.36 381.59 91.76 403.52 91.76 403.52 78.37 381.59 78.37 365.7 78.37"/>
|
||||
<polygon class="cls-1" points="425.51 127.07 453.59 127.07 453.59 114.1 425.51 114.1 425.51 91.76 458.43 91.76 458.43 78.37 425.51 78.37 420.91 78.37 409.5 78.37 409.5 165.36 420.91 165.36 425.51 165.36 458.67 165.36 458.67 152.04 425.51 152.04 425.51 127.07"/>
|
||||
<path class="cls-1" d="M511.89,124.74c2.37-2.23,4.2-5.01,5.5-8.33,1.29-3.33,1.94-7.26,1.94-11.8,0-5.7-1.07-10.5-3.2-14.4-2.13-3.9-5.23-6.85-9.29-8.84-4.06-1.99-8.98-2.99-14.76-2.99h-27.07v86.99h16.01v-32.68h10.33l13.68,32.68h17.09v-.84l-15.69-36c2.03-1.05,3.85-2.3,5.44-3.79ZM481.03,91.76h11.05c2.67,0,4.83.56,6.48,1.67,1.65,1.12,2.87,2.72,3.64,4.81.78,2.09,1.17,4.59,1.17,7.5,0,2.67-.43,5.03-1.28,7.08-.86,2.05-2.15,3.65-3.88,4.81-1.73,1.16-3.89,1.73-6.48,1.73h-10.69v-27.6Z"/>
|
||||
<polygon class="cls-1" points="584.07 136.91 572.63 78.37 568.2 78.37 563.84 78.37 559.3 78.37 547.84 137.03 538.63 78.37 522.74 78.37 538.75 165.36 544.9 165.36 549.03 165.36 554.4 165.36 566.01 109.33 577.4 165.36 582.84 165.36 586.85 165.36 593.12 165.36 609.07 78.37 593.24 78.37 584.07 136.91"/>
|
||||
<path class="cls-1" d="M666.17,86.74c-2.73-3.15-5.97-5.53-9.74-7.14-3.76-1.61-7.96-2.42-12.58-2.42s-8.76.81-12.55,2.42c-3.78,1.61-7.04,3.99-9.77,7.14-2.73,3.15-4.83,7.07-6.3,11.77-1.47,4.7-2.21,10.18-2.21,16.43v14.64c0,6.13.75,11.51,2.24,16.13,1.49,4.62,3.6,8.47,6.33,11.56,2.73,3.09,5.98,5.41,9.77,6.96,3.78,1.55,7.99,2.33,12.61,2.33s8.76-.78,12.55-2.33c3.78-1.55,7.03-3.87,9.74-6.96,2.71-3.09,4.78-6.94,6.21-11.56,1.43-4.62,2.15-10,2.15-16.13v-14.64c0-6.25-.73-11.73-2.18-16.43-1.45-4.7-3.54-8.62-6.27-11.77ZM658.73,129.57c0,4.18-.31,7.77-.93,10.75-.62,2.99-1.55,5.42-2.81,7.29-1.25,1.87-2.81,3.26-4.66,4.15-1.85.9-3.97,1.34-6.36,1.34s-4.57-.45-6.42-1.34c-1.85-.9-3.43-2.28-4.72-4.15-1.29-1.87-2.26-4.3-2.9-7.29-.64-2.99-.96-6.57-.96-10.75v-14.76c0-4.26.32-7.93.96-10.99.64-3.07,1.58-5.57,2.84-7.5,1.25-1.93,2.81-3.36,4.66-4.27,1.85-.92,3.99-1.37,6.42-1.37s4.51.46,6.36,1.37c1.85.92,3.42,2.34,4.69,4.27,1.27,1.93,2.23,4.43,2.87,7.5.64,3.07.96,6.73.96,10.99v14.76Z"/>
|
||||
<polygon class="cls-1" points="699.6 78.37 683.59 78.37 683.59 165.36 695 165.36 699.6 165.36 731.15 165.36 731.15 152.04 699.6 152.04 699.6 78.37"/>
|
||||
<polygon class="cls-1" points="784.69 91.76 784.69 78.37 753.26 78.37 749.38 78.37 737.25 78.37 737.25 165.36 753.26 165.36 753.26 129.16 780.98 129.16 780.98 115.83 753.26 115.83 753.26 91.76 784.69 91.76"/>
|
||||
<path class="cls-1" d="M206.63,54.66h6.38c2.79,0,5.18-.54,7.17-1.62,1.99-1.08,3.53-2.6,4.61-4.56,1.08-1.96,1.62-4.31,1.62-7.04s-.54-4.98-1.62-7.03c-1.08-2.05-2.61-3.66-4.61-4.84-1.99-1.17-4.38-1.76-7.17-1.76h-14.12v42.04h7.74v-15.19ZM213.01,34.28c1.33,0,2.4.34,3.22,1.01.82.67,1.42,1.56,1.79,2.67.38,1.11.56,2.29.56,3.54s-.19,2.44-.56,3.46c-.38,1.02-.97,1.81-1.79,2.38-.82.57-1.89.85-3.22.85h-6.38v-13.92h6.38Z"/>
|
||||
<path class="cls-1" d="M233.41,65.94c1.32,1.49,2.89,2.61,4.72,3.36,1.83.75,3.86,1.13,6.09,1.13s4.23-.38,6.06-1.13c1.83-.75,3.4-1.87,4.71-3.36s2.31-3.35,3-5.59c.69-2.23,1.04-4.83,1.04-7.8v-7.07c0-3.02-.35-5.67-1.05-7.94-.7-2.27-1.71-4.17-3.03-5.69-1.32-1.52-2.89-2.67-4.71-3.45-1.82-.78-3.84-1.17-6.08-1.17s-4.23.39-6.06,1.17c-1.83.78-3.4,1.93-4.72,3.45-1.32,1.52-2.33,3.42-3.05,5.69-.71,2.27-1.07,4.92-1.07,7.94v7.07c0,2.96.36,5.56,1.08,7.8.72,2.23,1.74,4.1,3.06,5.59ZM236.98,45.42c0-2.06.15-3.83.46-5.31.31-1.48.77-2.69,1.37-3.62s1.36-1.62,2.25-2.06c.9-.44,1.93-.66,3.1-.66s2.18.22,3.08.66c.9.44,1.65,1.13,2.27,2.06.62.93,1.08,2.14,1.39,3.62.31,1.48.46,3.25.46,5.31v7.13c0,2.02-.15,3.75-.45,5.2-.3,1.44-.75,2.62-1.36,3.52-.61.91-1.36,1.57-2.25,2.01s-1.92.65-3.07.65-2.21-.22-3.1-.65-1.66-1.1-2.28-2.01c-.63-.9-1.09-2.08-1.4-3.52-.31-1.44-.46-3.18-.46-5.2v-7.13Z"/>
|
||||
<polygon class="cls-1" points="271.89 69.85 273.88 69.85 276.48 69.85 282.09 42.77 287.59 69.85 290.22 69.85 292.16 69.85 295.19 69.85 302.9 27.81 295.25 27.81 290.81 56.1 285.29 27.81 283.15 27.81 281.04 27.81 278.85 27.81 273.31 56.16 268.86 27.81 261.18 27.81 268.91 69.85 271.89 69.85"/>
|
||||
<polygon class="cls-1" points="313.3 69.85 329.32 69.85 329.32 63.41 313.3 63.41 313.3 51.34 326.86 51.34 326.86 45.08 313.3 45.08 313.3 34.28 329.2 34.28 329.2 27.81 313.3 27.81 311.07 27.81 305.56 27.81 305.56 69.85 311.07 69.85 313.3 69.85"/>
|
||||
<path class="cls-1" d="M340.12,54.06h4.99l6.61,15.79h8.26v-.4l-7.58-17.4c.98-.51,1.86-1.11,2.63-1.83,1.14-1.08,2.03-2.42,2.66-4.03.62-1.61.94-3.51.94-5.7,0-2.75-.52-5.07-1.54-6.96-1.03-1.89-2.53-3.31-4.49-4.27s-4.34-1.44-7.13-1.44h-13.08v42.04h7.74v-15.79ZM345.46,34.28c1.29,0,2.33.27,3.13.81.8.54,1.39,1.31,1.76,2.32.38,1.01.56,2.22.56,3.62,0,1.29-.21,2.43-.62,3.42-.41.99-1.04,1.77-1.88,2.32-.84.56-1.88.84-3.13.84h-5.17v-13.34h5.34Z"/>
|
||||
<polygon class="cls-1" points="370.21 69.85 386.23 69.85 386.23 63.41 370.21 63.41 370.21 51.34 383.78 51.34 383.78 45.08 370.21 45.08 370.21 34.28 386.12 34.28 386.12 27.81 370.21 27.81 367.99 27.81 362.47 27.81 362.47 69.85 367.99 69.85 370.21 69.85"/>
|
||||
<path class="cls-1" d="M414.99,59.71c.75-2.19,1.13-4.71,1.13-7.54v-6.67c0-2.83-.38-5.35-1.13-7.55-.75-2.2-1.83-4.06-3.25-5.56-1.42-1.5-3.1-2.64-5.07-3.42-1.96-.78-4.16-1.17-6.58-1.17h-10.83v42.04h10.65c2.48,0,4.72-.39,6.71-1.17,1.99-.78,3.7-1.92,5.11-3.42,1.41-1.5,2.5-3.35,3.25-5.54ZM408.44,52.18c0,2.62-.26,4.75-.79,6.41-.53,1.66-1.41,2.87-2.66,3.65-1.24.78-2.93,1.17-5.07,1.17h-2.92v-29.13h3.09c1.56,0,2.87.21,3.93.64,1.06.42,1.92,1.08,2.57,1.96.65.89,1.13,2.04,1.41,3.45.29,1.42.43,3.12.43,5.12v6.73Z"/>
|
||||
<path class="cls-1" d="M439.51,69.85h5.83c2.73,0,5.05-.47,6.96-1.4,1.91-.93,3.36-2.31,4.36-4.14,1-1.83,1.5-4.09,1.5-6.79,0-1.69-.3-3.29-.9-4.78-.6-1.49-1.55-2.71-2.86-3.65-.64-.46-1.37-.8-2.2-1.03.33-.15.65-.31.94-.5,1.45-.92,2.54-2.11,3.25-3.55.71-1.44,1.07-3.03,1.07-4.76,0-1.96-.29-3.66-.88-5.1-.59-1.43-1.44-2.62-2.56-3.57-1.12-.94-2.47-1.64-4.07-2.09-1.6-.45-3.42-.68-5.46-.68h-12.73v42.04h7.74ZM449.91,60.57c-.37.9-.92,1.59-1.66,2.09s-1.71.75-2.9.75h-5.83v-12.21h6.12c1.17,0,2.11.26,2.81.77.7.51,1.21,1.24,1.53,2.18.32.94.48,2.05.48,3.32,0,1.17-.18,2.21-.55,3.1ZM444.51,34.28c1.23,0,2.23.19,3,.58.77.39,1.33.99,1.69,1.8.36.82.53,1.9.53,3.25,0,1.19-.2,2.2-.59,3.03-.4.83-.98,1.46-1.76,1.89-.78.43-1.75.65-2.9.65h-4.97v-11.2h4.99Z"/>
|
||||
<polygon class="cls-1" points="469.2 69.85 476.99 69.85 476.99 54.49 488.46 27.81 479.97 27.81 473.1 47.24 466.2 27.81 457.74 27.81 469.2 54.49 469.2 69.85"/>
|
||||
</g>
|
||||
<g id="svgg">
|
||||
<path id="path0" class="cls-1" d="M80.35,1.12C23.73,10.38-12.01,65.53,3.72,119.36c2.66,9.11,2.57,9.09,4.65.98,1.01-3.96,2.15-7.97,2.51-8.91.46-1.16.46-3.35,0-6.88C1.7,33.58,80.91-15.25,140.59,24.61c62.01,41.42,41.22,138.57-32.4,151.41-9.96,1.74-9.44,1.91-17.14-5.73-7.48-7.42-7.33-7.49-4.35,2.04l1.5,4.82-4.37-.47c-6.64-.71-16.51-3.4-18.16-4.95-4.5-4.23-8.03-17.49-8.49-31.9-.16-5.13-.52-9.35-.8-9.37-3.57-.26-12.18,3.82-18.23,8.63-5.47,4.35-5.51,4.31-3.58-3.27,6.35-24.86,25.51-50.23,46.13-61.08l4.59-2.41-2.5-.47c-1.37-.26-5.19-.32-8.48-.13l-5.99.34,6.26-3.14c7.29-3.66,12.94-5.63,19.12-6.65,2.54-.42,4.42-1.06,4.42-1.5,0-2.75,17.52-24.58,19.72-24.58.32,0,.39,3,.17,6.71-.5,8.33-.65,8.12,9.92,13.97,9.29,5.14,10.94,6.49,10.67,8.77-.32,2.77-2.15,2.36-4.07-.92-1.99-3.39-3.89-4.57-8.77-5.46-4.07-.74-4.29-.51-3.37,3.57,1.14,5.08,4.17,7.32,12.24,9.03,9.23,1.96,24.42,8.39,23.96,10.15-.17.67.2,2,.83,2.95,1.1,1.67,1.09,1.81-.17,3.51-4.4,5.94-10.39,7.17-17.04,3.5-14.31-7.9-23.73-11.17-33.69-11.72-9.43-.51-17,1.63-9.42,2.67,3.57.49,7.31,2.31,9.46,4.63l1.59,1.7h-3.23c-21.98.09-38.75,19.17-33.92,38.6,1.68,6.75,6.31,15.09,12.02,21.63,2.26,2.59,2.38,2.46.96-.94-3.79-9.06-1.92-25.02,3.66-31.38,4.06-4.62,4.44-4.42,4.07,2.11-1.12,19.7,12.31,32.5,37.18,35.43,4.57.54,4.82.24,1.45-1.68-13.6-7.76-23.89-25.44-21.7-37.28,2.68-14.47,11.58-19.55,31.42-17.92l9.68.79,2.88-1.69c6.92-4.05,15.9-14.15,14.15-15.91-.53-.53-6.4-3.31-13.04-6.18l-12.07-5.22-.33-4.1c-.41-5.11-.38-5.07-6.27-9.04-4.45-3-4.87-3.48-4.87-5.5,0-5.65-3.19-17.27-4.52-16.45-.34.21-.82,3.62-1.08,7.58-.48,7.36-1.03,8.5-2.98,6.14-.62-.75-.88-3.38-.9-9.1-.02-8.59-.79-10.81-3.75-10.81-4.6,0-18.11,10.78-26.81,21.39-1.4,1.71-2.77,2.35-7.85,3.71-20.14,5.36-47.11,21.68-54.6,33.04-2.46,3.73-2.09,3.82,3.62.86,11.63-6.03,24.03-9.95,17.65-5.59-18.72,12.81-30.43,34.04-34.32,62.23-.94,6.84,20.19,26.42,37.06,34.32,69.91,32.76,147.48-28.19,131.88-103.62C175.69,26.85,127.77-6.64,80.35,1.12"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
10
views/home/index.hbs
Normal file
10
views/home/index.hbs
Normal file
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>App</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ message }}
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue