stuff
This commit is contained in:
parent
6207554c99
commit
8ba00eb9e2
1
.gitignore
vendored
1
.gitignore
vendored
@ -38,3 +38,4 @@ lerna-debug.log*
|
|||||||
.env*
|
.env*
|
||||||
/private
|
/private
|
||||||
/database
|
/database
|
||||||
|
/usercontent/*
|
||||||
|
147
package-lock.json
generated
147
package-lock.json
generated
@ -13,9 +13,11 @@
|
|||||||
"@nestjs/config": "^2.2.0",
|
"@nestjs/config": "^2.2.0",
|
||||||
"@nestjs/core": "^9.0.0",
|
"@nestjs/core": "^9.0.0",
|
||||||
"@nestjs/platform-express": "^9.0.0",
|
"@nestjs/platform-express": "^9.0.0",
|
||||||
|
"@nestjs/serve-static": "^3.0.0",
|
||||||
"@nestjs/swagger": "^6.1.4",
|
"@nestjs/swagger": "^6.1.4",
|
||||||
"@nestjs/typeorm": "^9.0.1",
|
"@nestjs/typeorm": "^9.0.1",
|
||||||
"bcrypt": "^5.1.0",
|
"bcrypt": "^5.1.0",
|
||||||
|
"canvas": "^2.11.0",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.0",
|
"class-validator": "^0.14.0",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
@ -1658,6 +1660,23 @@
|
|||||||
"typescript": "^4.3.5"
|
"typescript": "^4.3.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/serve-static": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/serve-static/-/serve-static-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-TpXjgs4136dQqWUjEcONqppqXDsrJhRkmKWzuBMOUAnP4HjHpNmlycvkHnDnWSoG2YD4a7Enh4ViYGWqCfHStA==",
|
||||||
|
"dependencies": {
|
||||||
|
"path-to-regexp": "0.2.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^9.0.0",
|
||||||
|
"@nestjs/core": "^9.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@nestjs/serve-static/node_modules/path-to-regexp": {
|
||||||
|
"version": "0.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.2.5.tgz",
|
||||||
|
"integrity": "sha512-l6qtdDPIkmAmzEO6egquYDfqQGPMRNGjYtrU13HAXb3YSRrt7HSb1sJY0pKp6o2bAa86tSB6iwaW2JbthPKr7Q=="
|
||||||
|
},
|
||||||
"node_modules/@nestjs/swagger": {
|
"node_modules/@nestjs/swagger": {
|
||||||
"version": "6.1.4",
|
"version": "6.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-6.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-6.1.4.tgz",
|
||||||
@ -3183,6 +3202,20 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/canvas": {
|
||||||
|
"version": "2.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.0.tgz",
|
||||||
|
"integrity": "sha512-bdTjFexjKJEwtIo0oRx8eD4G2yWoUOXP9lj279jmQ2zMnTQhT8C3512OKz3s+ZOaQlLbE7TuVvRDYDB3Llyy5g==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@mapbox/node-pre-gyp": "^1.0.0",
|
||||||
|
"nan": "^2.17.0",
|
||||||
|
"simple-get": "^3.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
|
||||||
@ -3652,6 +3685,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/decompress-response": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
|
||||||
|
"dependencies": {
|
||||||
|
"mimic-response": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dedent": {
|
"node_modules/dedent": {
|
||||||
"version": "0.7.0",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
|
||||||
@ -6593,6 +6637,17 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mimic-response": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
@ -6775,6 +6830,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
||||||
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="
|
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="
|
||||||
},
|
},
|
||||||
|
"node_modules/nan": {
|
||||||
|
"version": "2.17.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz",
|
||||||
|
"integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ=="
|
||||||
|
},
|
||||||
"node_modules/natural-compare": {
|
"node_modules/natural-compare": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||||
@ -7931,6 +7991,35 @@
|
|||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
|
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/simple-concat": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/simple-get": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
|
||||||
|
"dependencies": {
|
||||||
|
"decompress-response": "^4.2.0",
|
||||||
|
"once": "^1.3.1",
|
||||||
|
"simple-concat": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sisteransi": {
|
"node_modules/sisteransi": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||||
@ -10533,6 +10622,21 @@
|
|||||||
"pluralize": "8.0.0"
|
"pluralize": "8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@nestjs/serve-static": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/serve-static/-/serve-static-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-TpXjgs4136dQqWUjEcONqppqXDsrJhRkmKWzuBMOUAnP4HjHpNmlycvkHnDnWSoG2YD4a7Enh4ViYGWqCfHStA==",
|
||||||
|
"requires": {
|
||||||
|
"path-to-regexp": "0.2.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"path-to-regexp": {
|
||||||
|
"version": "0.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.2.5.tgz",
|
||||||
|
"integrity": "sha512-l6qtdDPIkmAmzEO6egquYDfqQGPMRNGjYtrU13HAXb3YSRrt7HSb1sJY0pKp6o2bAa86tSB6iwaW2JbthPKr7Q=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@nestjs/swagger": {
|
"@nestjs/swagger": {
|
||||||
"version": "6.1.4",
|
"version": "6.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-6.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-6.1.4.tgz",
|
||||||
@ -11717,6 +11821,16 @@
|
|||||||
"integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==",
|
"integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"canvas": {
|
||||||
|
"version": "2.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.0.tgz",
|
||||||
|
"integrity": "sha512-bdTjFexjKJEwtIo0oRx8eD4G2yWoUOXP9lj279jmQ2zMnTQhT8C3512OKz3s+ZOaQlLbE7TuVvRDYDB3Llyy5g==",
|
||||||
|
"requires": {
|
||||||
|
"@mapbox/node-pre-gyp": "^1.0.0",
|
||||||
|
"nan": "^2.17.0",
|
||||||
|
"simple-get": "^3.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
|
||||||
@ -12062,6 +12176,14 @@
|
|||||||
"ms": "2.1.2"
|
"ms": "2.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"decompress-response": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
|
||||||
|
"requires": {
|
||||||
|
"mimic-response": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"dedent": {
|
"dedent": {
|
||||||
"version": "0.7.0",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
|
||||||
@ -14287,6 +14409,11 @@
|
|||||||
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"mimic-response": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA=="
|
||||||
|
},
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
@ -14444,6 +14571,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nan": {
|
||||||
|
"version": "2.17.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz",
|
||||||
|
"integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ=="
|
||||||
|
},
|
||||||
"natural-compare": {
|
"natural-compare": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||||
@ -15298,6 +15430,21 @@
|
|||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
|
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
|
||||||
},
|
},
|
||||||
|
"simple-concat": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="
|
||||||
|
},
|
||||||
|
"simple-get": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
|
||||||
|
"requires": {
|
||||||
|
"decompress-response": "^4.2.0",
|
||||||
|
"once": "^1.3.1",
|
||||||
|
"simple-concat": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sisteransi": {
|
"sisteransi": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||||
|
@ -25,9 +25,11 @@
|
|||||||
"@nestjs/config": "^2.2.0",
|
"@nestjs/config": "^2.2.0",
|
||||||
"@nestjs/core": "^9.0.0",
|
"@nestjs/core": "^9.0.0",
|
||||||
"@nestjs/platform-express": "^9.0.0",
|
"@nestjs/platform-express": "^9.0.0",
|
||||||
|
"@nestjs/serve-static": "^3.0.0",
|
||||||
"@nestjs/swagger": "^6.1.4",
|
"@nestjs/swagger": "^6.1.4",
|
||||||
"@nestjs/typeorm": "^9.0.1",
|
"@nestjs/typeorm": "^9.0.1",
|
||||||
"bcrypt": "^5.1.0",
|
"bcrypt": "^5.1.0",
|
||||||
|
"canvas": "^2.11.0",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.0",
|
"class-validator": "^0.14.0",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
|
@ -5,9 +5,16 @@ import { UserModule } from 'src/objects/user/user.module';
|
|||||||
import { AuthModule } from 'src/shared/auth/auth.module';
|
import { AuthModule } from 'src/shared/auth/auth.module';
|
||||||
import { AppBuildingController } from './app-building.controller';
|
import { AppBuildingController } from './app-building.controller';
|
||||||
import { AppBuildingService } from './app-building.service';
|
import { AppBuildingService } from './app-building.service';
|
||||||
|
import { PlanRendererModule } from './plan-renderer/plan-renderer.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [GroupModule, BuildingModule, UserModule, AuthModule],
|
imports: [
|
||||||
|
GroupModule,
|
||||||
|
BuildingModule,
|
||||||
|
UserModule,
|
||||||
|
AuthModule,
|
||||||
|
PlanRendererModule,
|
||||||
|
],
|
||||||
controllers: [AppBuildingController],
|
controllers: [AppBuildingController],
|
||||||
providers: [AppBuildingService],
|
providers: [AppBuildingService],
|
||||||
})
|
})
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
BuildingsCreateRoomRequestDto,
|
BuildingsCreateRoomRequestDto,
|
||||||
BuildingsUpdateRoomRequestDto,
|
BuildingsUpdateRoomRequestDto,
|
||||||
} from './dto/buildings-create-room-request.dto';
|
} from './dto/buildings-create-room-request.dto';
|
||||||
|
import { PlanRendererService } from './plan-renderer/plan-renderer.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppBuildingService {
|
export class AppBuildingService {
|
||||||
@ -19,6 +20,7 @@ export class AppBuildingService {
|
|||||||
private readonly buildingService: BuildingService,
|
private readonly buildingService: BuildingService,
|
||||||
private readonly userService: UserService,
|
private readonly userService: UserService,
|
||||||
private readonly groupService: GroupService,
|
private readonly groupService: GroupService,
|
||||||
|
private readonly planRenderService: PlanRendererService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getUserBuildings(user: User) {
|
async getUserBuildings(user: User) {
|
||||||
@ -133,6 +135,7 @@ export class AppBuildingService {
|
|||||||
|
|
||||||
Object.assign(floor, body);
|
Object.assign(floor, body);
|
||||||
|
|
||||||
|
this.planRenderService.renderFloor(floor);
|
||||||
await this.buildingService.saveFloor(floor);
|
await this.buildingService.saveFloor(floor);
|
||||||
return omit(floor, ['building']);
|
return omit(floor, ['building']);
|
||||||
}
|
}
|
||||||
|
8
src/app-building/plan-renderer/plan-renderer.module.ts
Normal file
8
src/app-building/plan-renderer/plan-renderer.module.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { PlanRendererService } from './plan-renderer.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [PlanRendererService],
|
||||||
|
exports: [PlanRendererService],
|
||||||
|
})
|
||||||
|
export class PlanRendererModule {}
|
45
src/app-building/plan-renderer/plan-renderer.service.ts
Normal file
45
src/app-building/plan-renderer/plan-renderer.service.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { Floor } from 'src/objects/building/entities/floor.entity';
|
||||||
|
import { PlanRenderer } from './renderer';
|
||||||
|
import { FloorDocument } from './renderer/renderer.interfaces';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { isValidLayer } from './renderer/plan-validators';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PlanRendererService {
|
||||||
|
public validateFloorDocument(floorDocument: FloorDocument) {
|
||||||
|
const valid =
|
||||||
|
floorDocument?.layers &&
|
||||||
|
!isNaN(floorDocument.height) &&
|
||||||
|
!isNaN(floorDocument.width) &&
|
||||||
|
floorDocument.height > 0 &&
|
||||||
|
floorDocument.width > 0 &&
|
||||||
|
Array.isArray(floorDocument.layers) &&
|
||||||
|
floorDocument.layers.every(isValidLayer);
|
||||||
|
|
||||||
|
if (!valid)
|
||||||
|
throw new BadRequestException(
|
||||||
|
'Floor plan JSON is incorrect or malformed',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public renderFloor(floor: Floor) {
|
||||||
|
const plan = JSON.parse(floor.plan || '{}') as FloorDocument;
|
||||||
|
this.validateFloorDocument(plan);
|
||||||
|
|
||||||
|
if (plan.layers.every(({ contents }) => !contents.length)) return;
|
||||||
|
|
||||||
|
const filename = `floorplan-${floor.id}.png`;
|
||||||
|
const output = fs.createWriteStream(
|
||||||
|
join(process.cwd(), 'usercontent', filename),
|
||||||
|
);
|
||||||
|
|
||||||
|
const pngStream = PlanRenderer.renderFloorDocument(plan);
|
||||||
|
pngStream.pipe(output);
|
||||||
|
output.on('finish', () =>
|
||||||
|
Logger.log(`Floor plan for ID:${plan.id} has been rendered`),
|
||||||
|
);
|
||||||
|
floor.planImage = filename;
|
||||||
|
}
|
||||||
|
}
|
163
src/app-building/plan-renderer/renderer/index.ts
Normal file
163
src/app-building/plan-renderer/renderer/index.ts
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import { Canvas, CanvasRenderingContext2D } from 'canvas';
|
||||||
|
import {
|
||||||
|
extractLinePoints,
|
||||||
|
vec2Add,
|
||||||
|
vec2AngleFromOrigin,
|
||||||
|
vec2Distance,
|
||||||
|
vec2DivideScalar,
|
||||||
|
vec2Sub,
|
||||||
|
} from './renderer-utils';
|
||||||
|
import {
|
||||||
|
BezierSegment,
|
||||||
|
FloorDocument,
|
||||||
|
Layer,
|
||||||
|
Line,
|
||||||
|
Vec2,
|
||||||
|
} from './renderer.interfaces';
|
||||||
|
|
||||||
|
export class PlanRenderer {
|
||||||
|
constructor(
|
||||||
|
public width: number,
|
||||||
|
public height: number,
|
||||||
|
public origin: Vec2,
|
||||||
|
public ctx: CanvasRenderingContext2D,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
draw(layers: Layer[]) {
|
||||||
|
for (const layer of layers.reverse()) {
|
||||||
|
if (!layer.visible) continue;
|
||||||
|
this.drawLayer(layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
makeBezier(segment: BezierSegment) {
|
||||||
|
const [ox, oy] = this.origin;
|
||||||
|
const bezier = segment as BezierSegment;
|
||||||
|
const [cp1x, cp1y] = bezier.startControl;
|
||||||
|
const [cp2x, cp2y] = bezier.endControl;
|
||||||
|
const [x, y] = bezier.end;
|
||||||
|
this.ctx.bezierCurveTo(
|
||||||
|
cp1x - ox,
|
||||||
|
cp1y - oy,
|
||||||
|
cp2x - ox,
|
||||||
|
cp2y - oy,
|
||||||
|
x - ox,
|
||||||
|
y - oy,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
makeLinePath(line: Line) {
|
||||||
|
const [ox, oy] = this.origin;
|
||||||
|
const [firstSegment, ...segments] = line.segments;
|
||||||
|
// first segment must have a starting point
|
||||||
|
if (!firstSegment.start) return;
|
||||||
|
this.ctx.moveTo(...vec2Sub(firstSegment.start, this.origin));
|
||||||
|
|
||||||
|
if (line.type === 'curve') {
|
||||||
|
const lineLength = vec2Distance(firstSegment.start, firstSegment.end);
|
||||||
|
const lineAngle = vec2AngleFromOrigin(
|
||||||
|
firstSegment.end,
|
||||||
|
firstSegment.start,
|
||||||
|
);
|
||||||
|
const ninety = lineAngle + Math.PI / 2;
|
||||||
|
this.ctx.moveTo(...vec2Sub(firstSegment.end, this.origin));
|
||||||
|
this.ctx.arc(
|
||||||
|
firstSegment.start[0] - ox,
|
||||||
|
firstSegment.start[1] - oy,
|
||||||
|
lineLength,
|
||||||
|
lineAngle,
|
||||||
|
ninety,
|
||||||
|
);
|
||||||
|
this.ctx.lineTo(...vec2Sub(firstSegment.start, this.origin));
|
||||||
|
} else if ((firstSegment as BezierSegment).startControl) {
|
||||||
|
this.makeBezier(firstSegment as BezierSegment);
|
||||||
|
} else {
|
||||||
|
this.ctx.lineTo(...vec2Sub(firstSegment.end, this.origin));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const segment of segments) {
|
||||||
|
if (segment.start) {
|
||||||
|
this.ctx.moveTo(...vec2Sub(segment.start, this.origin));
|
||||||
|
}
|
||||||
|
if ((segment as BezierSegment).startControl) {
|
||||||
|
this.makeBezier(segment as BezierSegment);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ctx.lineTo(...vec2Sub(segment.end, this.origin));
|
||||||
|
}
|
||||||
|
if (line.closed && line.type !== 'curve') {
|
||||||
|
this.ctx.closePath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupLine(line: Line) {
|
||||||
|
this.ctx.beginPath();
|
||||||
|
if (line.lineDash) {
|
||||||
|
this.ctx.setLineDash(line.lineDash);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ctx.strokeStyle = line.color || '#000000';
|
||||||
|
this.ctx.lineWidth = line.width;
|
||||||
|
|
||||||
|
this.ctx.lineCap = line.lineCap || 'butt';
|
||||||
|
this.ctx.lineJoin = line.lineJoin || 'miter';
|
||||||
|
}
|
||||||
|
|
||||||
|
private drawRoomText(line: Line) {
|
||||||
|
const points = extractLinePoints(line);
|
||||||
|
const centerPoint = vec2Sub(
|
||||||
|
vec2DivideScalar(
|
||||||
|
points.reduce<Vec2 | null>(
|
||||||
|
(prev, current) => (prev ? vec2Add(prev, current) : current),
|
||||||
|
null,
|
||||||
|
) as Vec2,
|
||||||
|
points.length,
|
||||||
|
),
|
||||||
|
this.origin,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.ctx.font = '16px Arial';
|
||||||
|
this.ctx.fillStyle = line.color;
|
||||||
|
const { width } = this.ctx.measureText(line.name);
|
||||||
|
this.ctx.fillText(line.name, centerPoint[0] - width / 2, centerPoint[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private drawLine(line: Line) {
|
||||||
|
this.setupLine(line);
|
||||||
|
this.makeLinePath(line);
|
||||||
|
this.ctx.stroke();
|
||||||
|
if (line.type === 'room') {
|
||||||
|
this.drawRoomText(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private drawLayer(layer: Layer) {
|
||||||
|
for (const item of layer.contents) {
|
||||||
|
if (!item.visible) continue;
|
||||||
|
const line = item as Line;
|
||||||
|
if (line.segments) {
|
||||||
|
this.drawLine(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static renderFloorDocument(document: FloorDocument) {
|
||||||
|
let minBound: Vec2 = [0, 0];
|
||||||
|
let maxBound: Vec2 = [document.width, document.height];
|
||||||
|
if (document.boundingBox) {
|
||||||
|
const offset = 80;
|
||||||
|
minBound = vec2Sub(document.boundingBox[0], [offset, offset]);
|
||||||
|
maxBound = vec2Add(document.boundingBox[1], [offset, offset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = maxBound[0] - minBound[0];
|
||||||
|
const height = maxBound[1] - minBound[1];
|
||||||
|
const canvas = new Canvas(width, height, 'image');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const render = new PlanRenderer(width, height, minBound, ctx);
|
||||||
|
render.draw(document.layers);
|
||||||
|
return canvas.createPNGStream();
|
||||||
|
}
|
||||||
|
}
|
50
src/app-building/plan-renderer/renderer/plan-validators.ts
Normal file
50
src/app-building/plan-renderer/renderer/plan-validators.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { isValidColor } from 'src/shared/utils/validator.utils';
|
||||||
|
import {
|
||||||
|
Vec2,
|
||||||
|
Line,
|
||||||
|
BezierSegment,
|
||||||
|
LayerObject,
|
||||||
|
Layer,
|
||||||
|
} from './renderer.interfaces';
|
||||||
|
|
||||||
|
export const isValidVec2 = (subject: Vec2) =>
|
||||||
|
Array.isArray(subject) &&
|
||||||
|
subject.length === 2 &&
|
||||||
|
subject.every((entry) => !isNaN(entry));
|
||||||
|
|
||||||
|
export const isValidLine = (subject: Line) =>
|
||||||
|
// Segments must be array
|
||||||
|
Array.isArray(subject.segments) &&
|
||||||
|
// Every segment must contain at least a valid end point
|
||||||
|
subject.segments.every((segment) => {
|
||||||
|
// Check segment vector
|
||||||
|
if (segment.start && !isValidVec2(segment.start)) return false;
|
||||||
|
if (!segment.end || !isValidVec2(segment.end)) return false;
|
||||||
|
// Check bezier vector
|
||||||
|
const bezier = segment as BezierSegment;
|
||||||
|
if (bezier.startControl && !isValidVec2(bezier.startControl)) return false;
|
||||||
|
if (bezier.endControl && !isValidVec2(bezier.endControl)) return false;
|
||||||
|
return true;
|
||||||
|
}) &&
|
||||||
|
// Color must be a valid color value
|
||||||
|
isValidColor(subject.color) &&
|
||||||
|
// Closed must be a boolean
|
||||||
|
(subject.closed == null || typeof subject.closed === 'boolean');
|
||||||
|
|
||||||
|
export const isValidLayerObject = (object: LayerObject) =>
|
||||||
|
// Must be of correct type
|
||||||
|
['line', 'room', 'curve', 'object'].includes(object.type) &&
|
||||||
|
// If has segments, must be a valid line
|
||||||
|
(!(object as Line).segments || isValidLine(object as Line)) &&
|
||||||
|
// Name must be present
|
||||||
|
object.name != null;
|
||||||
|
|
||||||
|
export const isValidLayer = (layer: Layer) =>
|
||||||
|
// Must have correct color value
|
||||||
|
isValidColor(layer.color) &&
|
||||||
|
// Must have contents
|
||||||
|
Array.isArray(layer.contents) &&
|
||||||
|
// Must have valid contents
|
||||||
|
layer.contents.every(isValidLayerObject) &&
|
||||||
|
// Must be named
|
||||||
|
layer.name != null;
|
107
src/app-building/plan-renderer/renderer/renderer-utils.ts
Normal file
107
src/app-building/plan-renderer/renderer/renderer-utils.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { Line, Vec2, Vec2Box } from './renderer.interfaces';
|
||||||
|
|
||||||
|
export const vec2Length = ([x, y]: Vec2) => Math.abs(Math.sqrt(x * x + y * y));
|
||||||
|
|
||||||
|
export const vec2Add = ([x1, y1]: Vec2, [x2, y2]: Vec2): Vec2 => [
|
||||||
|
x1 + x2,
|
||||||
|
y1 + y2,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const vec2Multiply = ([x1, y1]: Vec2, [x2, y2]: Vec2): Vec2 => [
|
||||||
|
x1 * x2,
|
||||||
|
y1 * y2,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const vec2Sub = ([x1, y1]: Vec2, [x2, y2]: Vec2): Vec2 => [
|
||||||
|
x1 - x2,
|
||||||
|
y1 - y2,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const vec2MultiplyScalar = ([x, y]: Vec2, scalar: number): Vec2 => [
|
||||||
|
x * scalar,
|
||||||
|
y * scalar,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const vec2DivideScalar = ([x, y]: Vec2, scalar: number): Vec2 => [
|
||||||
|
x / scalar,
|
||||||
|
y / scalar,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const vec2Normalize = (vec: Vec2): Vec2 =>
|
||||||
|
vec2DivideScalar(vec, vec2Length(vec));
|
||||||
|
|
||||||
|
export const vec2Distance = (vec1: Vec2, vec2: Vec2) =>
|
||||||
|
vec2Length(vec2Sub(vec2, vec1));
|
||||||
|
|
||||||
|
export const vec2InCircle = (circle: Vec2, point: Vec2, radius: number) =>
|
||||||
|
vec2Distance(circle, point) <= radius;
|
||||||
|
|
||||||
|
export const vec2Inverse = (vec: Vec2): Vec2 => vec2MultiplyScalar(vec, -1);
|
||||||
|
|
||||||
|
export const vec2Snap = ([x, y]: Vec2, snapScale: number): Vec2 =>
|
||||||
|
vec2MultiplyScalar(
|
||||||
|
[Math.round(x / snapScale), Math.round(y / snapScale)],
|
||||||
|
snapScale,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const vec2Equals = ([x1, y1]: Vec2, [x2, y2]: Vec2) =>
|
||||||
|
x1 === x2 && y1 === y2;
|
||||||
|
|
||||||
|
export const vec2AngleFromOrigin = (
|
||||||
|
[x, y]: Vec2,
|
||||||
|
[ox, oy]: Vec2 = [0, 0],
|
||||||
|
): number => Math.atan2(y - oy, x - ox);
|
||||||
|
|
||||||
|
export const vec2PointFromAngle = (
|
||||||
|
angle: number,
|
||||||
|
[ox, oy]: Vec2 = [0, 0],
|
||||||
|
): Vec2 => [Math.cos(angle) + ox, Math.sin(angle) + oy];
|
||||||
|
|
||||||
|
export const deg2rad = (deg: number) => deg * (Math.PI / 180);
|
||||||
|
export const rad2deg = (rad: number) => rad * (180 / Math.PI);
|
||||||
|
|
||||||
|
export const randomNumber = (min: number, max: number) =>
|
||||||
|
Math.floor(Math.random() * (max - min + 1) + min);
|
||||||
|
|
||||||
|
export const boundingBox = (points: Vec2[], start: Vec2Box): Vec2Box => {
|
||||||
|
let minX = start[0][0];
|
||||||
|
let minY = start[0][1];
|
||||||
|
let maxX = start[1][0];
|
||||||
|
let maxY = start[1][1];
|
||||||
|
|
||||||
|
for (let i = 0; i < points.length; i++) {
|
||||||
|
if (points[i][0] > maxX) {
|
||||||
|
maxX = points[i][0];
|
||||||
|
}
|
||||||
|
if (points[i][0] < minX) {
|
||||||
|
minX = points[i][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (points[i][1] > maxY) {
|
||||||
|
maxY = points[i][1];
|
||||||
|
}
|
||||||
|
if (points[i][1] < minY) {
|
||||||
|
minY = points[i][1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
[minX, minY],
|
||||||
|
[maxX, maxY],
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isValidVec2 = ([x, y]: Vec2) =>
|
||||||
|
x != null && y != null && !isNaN(x) && !isNaN(y);
|
||||||
|
|
||||||
|
export function extractLinePoints(line: Line) {
|
||||||
|
return line.segments
|
||||||
|
.reduce<Vec2[]>((list, segment) => {
|
||||||
|
if (segment.start) return [...list, segment.start, segment.end];
|
||||||
|
return [...list, segment.end];
|
||||||
|
}, [])
|
||||||
|
.filter(
|
||||||
|
(vec, index, arry) =>
|
||||||
|
arry.findIndex((point) => vec2Equals(point, vec)) === index,
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
export type Vec2 = [number, number];
|
||||||
|
export type LayerObjectType = 'line' | 'room' | 'curve' | 'object';
|
||||||
|
export type Vec2Box = [Vec2, Vec2];
|
||||||
|
export interface LineSegment {
|
||||||
|
start?: Vec2;
|
||||||
|
end: Vec2;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BezierSegment extends LineSegment {
|
||||||
|
startControl: Vec2;
|
||||||
|
endControl: Vec2;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LayerObject {
|
||||||
|
id: number;
|
||||||
|
databaseId?: number;
|
||||||
|
name: string;
|
||||||
|
visible: boolean;
|
||||||
|
selected: boolean;
|
||||||
|
type: LayerObjectType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Line extends LayerObject {
|
||||||
|
segments: LineSegment[];
|
||||||
|
width: number;
|
||||||
|
color: string;
|
||||||
|
render?: Path2D;
|
||||||
|
lineCap?: CanvasLineCap;
|
||||||
|
lineJoin?: CanvasLineJoin;
|
||||||
|
closed?: boolean;
|
||||||
|
lineDash?: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Layer {
|
||||||
|
id: number;
|
||||||
|
contents: LayerObject[];
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
visible: boolean;
|
||||||
|
active: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FloorDocument {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
layers: Layer[];
|
||||||
|
/**
|
||||||
|
* Min, Max
|
||||||
|
*/
|
||||||
|
boundingBox?: [Vec2, Vec2];
|
||||||
|
}
|
@ -21,10 +21,12 @@ import {
|
|||||||
ApiSecurity,
|
ApiSecurity,
|
||||||
ApiTags,
|
ApiTags,
|
||||||
} from '@nestjs/swagger';
|
} from '@nestjs/swagger';
|
||||||
|
import { Building } from 'src/objects/building/entities/building.entity';
|
||||||
import { Room } from 'src/objects/building/entities/room.entity';
|
import { Room } from 'src/objects/building/entities/room.entity';
|
||||||
import { StorageSet } from 'src/objects/storage/entities/storage-set.entity';
|
import { StorageSet } from 'src/objects/storage/entities/storage-set.entity';
|
||||||
import { Storage } from 'src/objects/storage/entities/storage.entity';
|
import { Storage } from 'src/objects/storage/entities/storage.entity';
|
||||||
import { User } from 'src/objects/user/user.entity';
|
import { User } from 'src/objects/user/user.entity';
|
||||||
|
import { CurrentBuilding } from 'src/shared/decorators/current-building.decorator';
|
||||||
import { CurrentRoom } from 'src/shared/decorators/current-room.decorator';
|
import { CurrentRoom } from 'src/shared/decorators/current-room.decorator';
|
||||||
import { CurrentStorageSet } from 'src/shared/decorators/current-storage-set.decorator';
|
import { CurrentStorageSet } from 'src/shared/decorators/current-storage-set.decorator';
|
||||||
import { CurrentStorage } from 'src/shared/decorators/current-storage.decorator';
|
import { CurrentStorage } from 'src/shared/decorators/current-storage.decorator';
|
||||||
@ -192,6 +194,23 @@ export class AppStorageController {
|
|||||||
return this.service.createStorage(user, room, body);
|
return this.service.createStorage(user, room, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('expiring')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Get expiring and expired for user in all buildings',
|
||||||
|
})
|
||||||
|
@ApiOkResponse({ type: StorageStoredItemResponseDto, isArray: true })
|
||||||
|
async getExpiringItemsForUser(@LoggedInUser() user: User) {
|
||||||
|
return this.service.getExpiringOrExpiredItems(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('expiring/building/:buildingId')
|
||||||
|
@ApiParam({ name: 'buildingId', description: 'Building ID' })
|
||||||
|
@ApiOperation({ summary: 'Get expiring and expired items in building' })
|
||||||
|
@ApiOkResponse({ type: StorageStoredItemResponseDto, isArray: true })
|
||||||
|
async getExpiringItemsInBuilding(@CurrentBuilding() building: Building) {
|
||||||
|
return this.service.getExpiringOrExpiredItemsInBuilding(building.id);
|
||||||
|
}
|
||||||
|
|
||||||
@Get('item')
|
@Get('item')
|
||||||
@ApiOperation({ summary: 'Search for an item' })
|
@ApiOperation({ summary: 'Search for an item' })
|
||||||
@ApiOkResponse({ type: StorageItemSearchResponseDto, isArray: true })
|
@ApiOkResponse({ type: StorageItemSearchResponseDto, isArray: true })
|
||||||
|
@ -143,6 +143,22 @@ export class AppStorageService {
|
|||||||
return responses;
|
return responses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getExpiringOrExpiredItemsInBuilding(buildingId: number) {
|
||||||
|
const expiringSoon =
|
||||||
|
await this.storageService.getExpiredOrExpiringSoonInBuilding(buildingId);
|
||||||
|
return expiringSoon.map((storedItem) =>
|
||||||
|
this.formatStoredItem(storedItem, true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getExpiringOrExpiredItems(user: User) {
|
||||||
|
const expiringSoon =
|
||||||
|
await this.storageService.getExpiredOrExpiringSoonForSub(user.sub);
|
||||||
|
return expiringSoon.map((storedItem) =>
|
||||||
|
this.formatStoredItem(storedItem, true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async createStoredItem(
|
async createStoredItem(
|
||||||
user: User,
|
user: User,
|
||||||
item: Item,
|
item: Item,
|
||||||
@ -150,11 +166,15 @@ export class AppStorageService {
|
|||||||
transactionInfo: StorageStoredItemTransactionRequestDto,
|
transactionInfo: StorageStoredItemTransactionRequestDto,
|
||||||
additionalInfo?: StorageStoredItemRequestDto,
|
additionalInfo?: StorageStoredItemRequestDto,
|
||||||
) {
|
) {
|
||||||
|
const building = await this.buildingService.getStorageBuilding(storage.id);
|
||||||
|
if (!building)
|
||||||
|
throw new NotFoundException('Building for storage was not found');
|
||||||
// Create stored item
|
// Create stored item
|
||||||
let storedItem = new StoredItem();
|
let storedItem = new StoredItem();
|
||||||
storedItem.addedBy = user;
|
storedItem.addedBy = user;
|
||||||
storedItem.item = item;
|
storedItem.item = item;
|
||||||
storedItem.storage = storage;
|
storedItem.storage = storage;
|
||||||
|
storedItem.building = building;
|
||||||
additionalInfo && Object.assign(storedItem, additionalInfo);
|
additionalInfo && Object.assign(storedItem, additionalInfo);
|
||||||
|
|
||||||
storedItem = await this.storageService.saveStoredItem(storedItem);
|
storedItem = await this.storageService.saveStoredItem(storedItem);
|
||||||
@ -416,9 +436,10 @@ export class AppStorageService {
|
|||||||
|
|
||||||
private formatStoredItem(
|
private formatStoredItem(
|
||||||
storedItem: StoredItem,
|
storedItem: StoredItem,
|
||||||
|
fullSet = false,
|
||||||
): StorageStoredItemResponseDto {
|
): StorageStoredItemResponseDto {
|
||||||
return {
|
return {
|
||||||
...omit(storedItem, ['storage']),
|
...(fullSet ? storedItem : omit(storedItem, ['storage', 'building'])),
|
||||||
transactions: !!storedItem.transactions?.length
|
transactions: !!storedItem.transactions?.length
|
||||||
? storedItem.transactions.map((transaction) =>
|
? storedItem.transactions.map((transaction) =>
|
||||||
this.formatTransaction(transaction),
|
this.formatTransaction(transaction),
|
||||||
|
@ -36,6 +36,7 @@ export class StorageStoredItemResponseDto extends OmitType(StoredItem, [
|
|||||||
'addedBy',
|
'addedBy',
|
||||||
'transactions',
|
'transactions',
|
||||||
'storage',
|
'storage',
|
||||||
|
'building',
|
||||||
'item',
|
'item',
|
||||||
]) {
|
]) {
|
||||||
@ApiProperty({ type: StorageActorResponse })
|
@ApiProperty({ type: StorageActorResponse })
|
||||||
|
1
src/app-storage/dto/storage-stored-item-request.dto.ts
Normal file
1
src/app-storage/dto/storage-stored-item-request.dto.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
import { IsBoolean } from 'class-validator';
|
@ -1,6 +1,8 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { join } from 'path';
|
||||||
import { AppBuildingModule } from './app-building/app-building.module';
|
import { AppBuildingModule } from './app-building/app-building.module';
|
||||||
import { AppGroupModule } from './app-group/app-group.module';
|
import { AppGroupModule } from './app-group/app-group.module';
|
||||||
import { AppStorageModule } from './app-storage/app-storage.module';
|
import { AppStorageModule } from './app-storage/app-storage.module';
|
||||||
@ -16,6 +18,10 @@ import { SecretsModule } from './shared/secrets/secrets.module';
|
|||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
ServeStaticModule.forRoot({
|
||||||
|
rootPath: join(__dirname, '..', 'usercontent'),
|
||||||
|
serveRoot: '/usercontent',
|
||||||
|
}),
|
||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({
|
||||||
envFilePath: ['.env.development', '.env'],
|
envFilePath: ['.env.development', '.env'],
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
|
@ -18,6 +18,20 @@ export class BuildingService {
|
|||||||
private readonly groupService: GroupService,
|
private readonly groupService: GroupService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
async getStorageBuilding(storageId: number) {
|
||||||
|
return this.buildingRepository.findOne({
|
||||||
|
where: {
|
||||||
|
floors: {
|
||||||
|
rooms: {
|
||||||
|
storages: {
|
||||||
|
id: storageId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async getBuildingsByUserSub(sub: string, relations = []) {
|
async getBuildingsByUserSub(sub: string, relations = []) {
|
||||||
return this.buildingRepository.find({
|
return this.buildingRepository.find({
|
||||||
where: {
|
where: {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
@ -29,6 +29,10 @@ export class Floor {
|
|||||||
@Column({ type: 'text' })
|
@Column({ type: 'text' })
|
||||||
plan: string;
|
plan: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
planImage?: string;
|
||||||
|
|
||||||
@ApiProperty({ type: () => Building })
|
@ApiProperty({ type: () => Building })
|
||||||
@ManyToOne(() => Building)
|
@ManyToOne(() => Building)
|
||||||
building: Building;
|
building: Building;
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { Storage } from 'src/objects/storage/entities/storage.entity';
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
Entity,
|
Entity,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
|
OneToMany,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
@ -24,6 +26,9 @@ export class Room {
|
|||||||
@ManyToOne(() => Floor)
|
@ManyToOne(() => Floor)
|
||||||
floor: Floor;
|
floor: Floor;
|
||||||
|
|
||||||
|
@OneToMany(() => Storage, (storage) => storage.room)
|
||||||
|
storages: Storage[];
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column()
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { Building } from 'src/objects/building/entities/building.entity';
|
||||||
import { User } from 'src/objects/user/user.entity';
|
import { User } from 'src/objects/user/user.entity';
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
@ -26,7 +27,7 @@ export class StoredItem {
|
|||||||
})
|
})
|
||||||
item: Item;
|
item: Item;
|
||||||
|
|
||||||
@ApiPropertyOptional({ type: () => Storage })
|
@ApiPropertyOptional({ type: () => Storage, nullable: true })
|
||||||
@ManyToOne(() => Storage, {
|
@ManyToOne(() => Storage, {
|
||||||
nullable: true,
|
nullable: true,
|
||||||
onDelete: 'SET NULL',
|
onDelete: 'SET NULL',
|
||||||
@ -34,11 +35,19 @@ export class StoredItem {
|
|||||||
})
|
})
|
||||||
storage?: Storage;
|
storage?: Storage;
|
||||||
|
|
||||||
|
@ApiProperty({ type: () => Building })
|
||||||
|
@ManyToOne(() => Building, {
|
||||||
|
nullable: true,
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
onUpdate: 'CASCADE',
|
||||||
|
})
|
||||||
|
building: Building;
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
@ApiPropertyOptional()
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
notes?: string;
|
notes?: string;
|
||||||
|
|
||||||
@ApiProperty({ type: () => User })
|
@ApiProperty({ type: () => User, nullable: true })
|
||||||
@ManyToOne(() => User, {
|
@ManyToOne(() => User, {
|
||||||
onDelete: 'SET NULL',
|
onDelete: 'SET NULL',
|
||||||
nullable: true,
|
nullable: true,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { ILike, IsNull, Repository } from 'typeorm';
|
import { ILike, IsNull, LessThanOrEqual, Repository } from 'typeorm';
|
||||||
import { StoredItemTransaction } from './entities/item-transaction.entity';
|
import { StoredItemTransaction } from './entities/item-transaction.entity';
|
||||||
import { Item } from './entities/item.entity';
|
import { Item } from './entities/item.entity';
|
||||||
import { StorageSet } from './entities/storage-set.entity';
|
import { StorageSet } from './entities/storage-set.entity';
|
||||||
@ -279,6 +279,44 @@ export class StorageService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getExpiredOrExpiringSoonInBuilding(buildingId: number) {
|
||||||
|
// 8 days
|
||||||
|
return this.storedItemRepository.find({
|
||||||
|
where: {
|
||||||
|
expiresAt: LessThanOrEqual(new Date(Date.now() + 691200000)),
|
||||||
|
building: {
|
||||||
|
id: buildingId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
expiresAt: 'ASC',
|
||||||
|
},
|
||||||
|
take: 16,
|
||||||
|
relations: ['item', 'storage'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getExpiredOrExpiringSoonForSub(sub: string) {
|
||||||
|
// 8 days
|
||||||
|
return this.storedItemRepository.find({
|
||||||
|
where: {
|
||||||
|
expiresAt: LessThanOrEqual(new Date(Date.now() + 691200000)),
|
||||||
|
building: {
|
||||||
|
groups: {
|
||||||
|
members: {
|
||||||
|
sub,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
expiresAt: 'ASC',
|
||||||
|
},
|
||||||
|
take: 16,
|
||||||
|
relations: ['item', 'building', 'storage'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async getStoredItemByStorageAndId(
|
async getStoredItemByStorageAndId(
|
||||||
storage: Storage,
|
storage: Storage,
|
||||||
storedItemId: number,
|
storedItemId: number,
|
||||||
|
13
src/shared/utils/validator.utils.ts
Normal file
13
src/shared/utils/validator.utils.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export const isValidColor = (color: string) => {
|
||||||
|
if (!color) return false;
|
||||||
|
if (
|
||||||
|
!color.startsWith('#') &&
|
||||||
|
!color.startsWith('rgb(') &&
|
||||||
|
!color.startsWith('hsl(') &&
|
||||||
|
!color.startsWith('hsv(')
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// TODO: check color values
|
||||||
|
return true;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user