11 Commits

29 changed files with 1068 additions and 33 deletions

2
.gitignore vendored
View File

@ -32,3 +32,5 @@ lerna-debug.log*
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
/config.yml

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"cSpell.words": [
"Repos"
]
}

View File

@ -1,11 +0,0 @@
env: dev
http:
port: 7122
db:
postgres:
host: 192.168.31.195
port: 5432
database: fennec
username: fennec
password:

16
config.yml.example Normal file
View File

@ -0,0 +1,16 @@
env: dev
http:
port: 7122
db:
postgres:
host: 192.168.31.194
port: 5432
database: fennec
username: fennec
password:
redis:
mq:
host: localhost
port: 6379
workspaces:
root: '/Users/ivanli/Projects/fennec/workspaces'

10
docs/development-notes.md Normal file
View File

@ -0,0 +1,10 @@
# Fennec CI/CD 工具开发手记
## 前言
这是 Fennec 后端项目开发手记,用于记录开发过程中遇到的知识点、难点、思路和解决方案。
## Git Repository 操作
### 获取 git 远程仓库信息流程
1. `git init` // 初始化一个本地 git 仓库。
2. `git remote add <name> <address>` // 添加远程仓库
3. `git fetch` // 获取远程仓库信息

583
package-lock.json generated
View File

@ -2056,6 +2056,39 @@
"chalk": "^4.0.0"
}
},
"@kwsites/file-exists": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
"integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==",
"requires": {
"debug": "^4.1.1"
},
"dependencies": {
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"@kwsites/promise-deferred": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz",
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="
},
"@nestjs/bull": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@nestjs/bull/-/bull-0.3.1.tgz",
"integrity": "sha512-W7NvohlERijj3LTgNmXPKnoznsy7bQNXQjZcSKkSclmsD24WROGZijdUo3ZeWBawTYzewgmUahrXPTW+vPSQrg=="
},
"@nestjs/cli": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-7.5.4.tgz",
@ -2523,6 +2556,14 @@
"@types/node": "*"
}
},
"@types/bull": {
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/@types/bull/-/bull-3.15.0.tgz",
"integrity": "sha512-54Y1RYkJt6i+4dH45w4gZOP6fyhksTvOImfgBYAxgq/nt5ZrES4xFWwOzt2bxAgSR7FMH9fwvaiJN/pripPzag==",
"requires": {
"@types/ioredis": "*"
}
},
"@types/connect": {
"version": "3.4.34",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz",
@ -2646,6 +2687,14 @@
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.0.tgz",
"integrity": "sha512-2aoSC4UUbHDj2uCsCxcG/vRMXey/m17bC7UwitVm5hn22nI8O8Y9iDpA76Orc+DWkQ4zZrOKEshCqR/jSuXAHA=="
},
"@types/ioredis": {
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.22.0.tgz",
"integrity": "sha512-BhgyAqt+CIFj/ejdYpWSGYUQzoQr7sFOBYLL8yEExa1tSTi2cy2D3a952zF8Tm4Q1cY3srn8xXZfb2riX6hWjw==",
"requires": {
"@types/node": "*"
}
},
"@types/istanbul-lib-coverage": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
@ -2776,6 +2825,14 @@
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz",
"integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ=="
},
"@types/ramda": {
"version": "0.27.38",
"resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.27.38.tgz",
"integrity": "sha512-tZoQ0lv1WKkrpBHemL8yCkI9p8kUk/1PSMwhl0eeyqMQjD+2ePUtVLV8PpNS9Kq3OktObwOx9I3k+HumxTviRg==",
"requires": {
"ts-toolbelt": "^6.15.1"
}
},
"@types/range-parser": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
@ -2849,6 +2906,11 @@
"resolved": "https://registry.npmjs.org/@types/ungap__global-this/-/ungap__global-this-0.3.1.tgz",
"integrity": "sha512-+/DsiV4CxXl6ZWefwHZDXSe1Slitz21tom38qPCaG0DYCS1NnDPIQDTKcmQ/tvK/edJUKkmuIDBJbmKDiB0r/g=="
},
"@types/uuid": {
"version": "3.4.9",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.9.tgz",
"integrity": "sha512-XDwyIlt/47l2kWLTzw/mtrpLdB+GPSskR2n/PIcPn+VYhVO77rGhRncIR5GPU0KRzXuqkDO+J5qqrG0Y8P6jzQ=="
},
"@types/validator": {
"version": "13.1.3",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.1.3.tgz",
@ -4717,6 +4779,23 @@
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="
},
"bull": {
"version": "3.20.1",
"resolved": "https://registry.npmjs.org/bull/-/bull-3.20.1.tgz",
"integrity": "sha512-wDwpVu47WKaGhiguEPa/US5UMrtGLPKNPiGFPo4OgVs3EEGzJEWwv3LRPfjJVIf1COdMAN/yowGhZwYmoOonjQ==",
"requires": {
"cron-parser": "^2.13.0",
"debuglog": "^1.0.0",
"get-port": "^5.1.1",
"ioredis": "^4.22.0",
"lodash": "^4.17.19",
"p-timeout": "^3.2.0",
"promise.prototype.finally": "^3.1.2",
"semver": "^7.3.2",
"util.promisify": "^1.0.1",
"uuid": "^8.3.0"
}
},
"busboy": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
@ -4954,6 +5033,26 @@
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
"optional": true
},
"cli-color": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz",
"integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==",
"requires": {
"ansi-regex": "^2.1.1",
"d": "1",
"es5-ext": "^0.10.46",
"es6-iterator": "^2.0.3",
"memoizee": "^0.4.14",
"timers-ext": "^0.1.5"
},
"dependencies": {
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
}
}
},
"cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
@ -5094,6 +5193,11 @@
"integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=",
"dev": true
},
"cluster-key-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
"integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw=="
},
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@ -5318,6 +5422,15 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true
},
"cron-parser": {
"version": "2.18.0",
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.18.0.tgz",
"integrity": "sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg==",
"requires": {
"is-nan": "^1.3.0",
"moment-timezone": "^0.5.31"
}
},
"cross-fetch": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz",
@ -5371,6 +5484,15 @@
}
}
},
"d": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
"integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
"requires": {
"es5-ext": "^0.10.50",
"type": "^1.0.1"
}
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@ -5404,6 +5526,11 @@
"ms": "2.0.0"
}
},
"debuglog": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz",
"integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI="
},
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
@ -5506,6 +5633,11 @@
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
},
"denque": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz",
"integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ=="
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@ -5743,6 +5875,46 @@
"is-symbol": "^1.0.2"
}
},
"es5-ext": {
"version": "0.10.53",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",
"integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==",
"requires": {
"es6-iterator": "~2.0.3",
"es6-symbol": "~3.1.3",
"next-tick": "~1.0.0"
}
},
"es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
"requires": {
"d": "1",
"es5-ext": "^0.10.35",
"es6-symbol": "^3.1.1"
}
},
"es6-symbol": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
"integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
"requires": {
"d": "^1.0.1",
"ext": "^1.1.2"
}
},
"es6-weak-map": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz",
"integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==",
"requires": {
"d": "1",
"es5-ext": "^0.10.46",
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.1"
}
},
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@ -6041,6 +6213,15 @@
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"event-emitter": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
"integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
"requires": {
"d": "1",
"es5-ext": "~0.10.14"
}
},
"eventemitter3": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
@ -6182,6 +6363,21 @@
}
}
},
"ext": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz",
"integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==",
"requires": {
"type": "^2.0.0"
},
"dependencies": {
"type": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/type/-/type-2.3.0.tgz",
"integrity": "sha512-rgPIqOdfK/4J9FhiVrZ3cveAjRRo5rsQBAIhnylX874y1DX/kEKSVdLsnuHB6l1KTjHyU01VjiMBHgU2adejyg=="
}
}
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@ -6673,6 +6869,11 @@
"integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
"dev": true
},
"get-port": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz",
"integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ=="
},
"get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
@ -7340,6 +7541,43 @@
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
"dev": true
},
"ioredis": {
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.22.0.tgz",
"integrity": "sha512-mtC+jNFMPRxReWx0HodDbcwj34Gj5pK/P4+aE6Nh0pdqgtZKvxUh4z2lVtLjqnRIvMhKaBnIgMYFR8qH/xtttA==",
"requires": {
"cluster-key-slot": "^1.1.0",
"debug": "^4.1.1",
"denque": "^1.1.0",
"lodash.defaults": "^4.2.0",
"lodash.flatten": "^4.4.0",
"p-map": "^2.1.0",
"redis-commands": "1.7.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.0.1"
},
"dependencies": {
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"p-map": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
"integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw=="
}
}
},
"ip": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
@ -7523,6 +7761,15 @@
"integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=",
"optional": true
},
"is-nan": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz",
"integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==",
"requires": {
"call-bind": "^1.0.0",
"define-properties": "^1.1.3"
}
},
"is-negated-glob": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz",
@ -8408,6 +8655,16 @@
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
"optional": true
},
"lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw="
},
"lodash.flatten": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
"integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8="
},
"lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
@ -8483,6 +8740,14 @@
"yallist": "^4.0.0"
}
},
"lru-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
"integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=",
"requires": {
"es5-ext": "~0.10.2"
}
},
"macos-release": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.4.1.tgz",
@ -8581,6 +8846,33 @@
"fs-monkey": "1.0.1"
}
},
"memoizee": {
"version": "0.4.15",
"resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz",
"integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==",
"requires": {
"d": "^1.0.1",
"es5-ext": "^0.10.53",
"es6-weak-map": "^2.0.3",
"event-emitter": "^0.3.5",
"is-promise": "^2.2.2",
"lru-queue": "^0.1.0",
"next-tick": "^1.1.0",
"timers-ext": "^0.1.7"
},
"dependencies": {
"is-promise": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
"integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
},
"next-tick": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
}
}
},
"memory-fs": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz",
@ -8803,6 +9095,19 @@
"minimist": "^1.2.5"
}
},
"moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
},
"moment-timezone": {
"version": "0.5.33",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.33.tgz",
"integrity": "sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==",
"requires": {
"moment": ">= 2.9.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -8913,6 +9218,170 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
"nestjs-redis": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/nestjs-redis/-/nestjs-redis-1.2.8.tgz",
"integrity": "sha512-h3lDq/F4xbA++1OY63Lh2LjJgdJ3RvCPFbM+oRbs5M4Ep/9zgWwtfaVlcx31al/wBXUeaJ4HTNv5IAQZLkdIBA==",
"requires": {
"@nestjs/common": "6.3.1",
"@nestjs/core": "^6.6.7",
"@types/ioredis": "^4.0.4",
"@types/uuid": "^3.4.4",
"ioredis": "^4.2.0",
"reflect-metadata": "^0.1.12",
"rxjs": "^6.2.2",
"uuid": "^3.3.2"
},
"dependencies": {
"@nestjs/common": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-6.3.1.tgz",
"integrity": "sha512-uuI/CCe6MFISMX+fSpkRvvQ6CBlXW89+5wfiveQ22AzAxgqGLAyWvNVHUE8F+zev7QDbxbY9vfPtm8CE5kiJVQ==",
"requires": {
"axios": "0.19.0",
"cli-color": "1.4.0",
"uuid": "3.3.2"
},
"dependencies": {
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
}
}
},
"@nestjs/core": {
"version": "6.11.11",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-6.11.11.tgz",
"integrity": "sha512-ewUy2rjiRWi6SziI5gXZnlat7PfnVklL3tusnU1qqtUm74cPY1Zre+zDCJ27P/+B7sFJHbkFfpi0qQP2pQv9jQ==",
"requires": {
"@nuxtjs/opencollective": "0.2.2",
"fast-safe-stringify": "2.0.7",
"iterare": "1.2.0",
"object-hash": "2.0.3",
"path-to-regexp": "3.2.0",
"tslib": "1.11.1",
"uuid": "7.0.1"
},
"dependencies": {
"uuid": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.1.tgz",
"integrity": "sha512-yqjRXZzSJm9Dbl84H2VDHpM3zMjzSJQ+hn6C4zqd5ilW+7P4ZmLEEqwho9LjP+tGuZlF4xrHQXT0h9QZUS/pWA=="
}
}
},
"@nuxtjs/opencollective": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.2.2.tgz",
"integrity": "sha512-69gFVDs7mJfNjv9Zs5DFVD+pvBW+k1TaHSOqUWqAyTTfLcKI/EMYQgvEvziRd+zAFtUOoye6MfWh0qvinGISPw==",
"requires": {
"chalk": "^2.4.1",
"consola": "^2.3.0",
"node-fetch": "^2.3.0"
}
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"requires": {
"color-convert": "^1.9.0"
}
},
"axios": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
"integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==",
"requires": {
"follow-redirects": "1.5.10",
"is-buffer": "^2.0.2"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"requires": {
"debug": "=3.1.0"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"is-buffer": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
"integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="
},
"iterare": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.0.tgz",
"integrity": "sha512-RxMV9p/UzdK0Iplnd8mVgRvNdXlsTOiuDrqMRnDi3wIhbT+JP4xDquAX9ay13R3CH72NBzQ91KWe0+C168QAyQ=="
},
"object-hash": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.3.tgz",
"integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg=="
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"requires": {
"has-flag": "^3.0.0"
}
},
"tslib": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
"integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA=="
},
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
}
}
},
"next-tick": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
},
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@ -9370,8 +9839,7 @@
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
"dev": true
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
},
"p-limit": {
"version": "2.3.0",
@ -9398,6 +9866,14 @@
"aggregate-error": "^3.0.0"
}
},
"p-timeout": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
"integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
"requires": {
"p-finally": "^1.0.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
@ -9855,6 +10331,36 @@
}
}
},
"promise.prototype.finally": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/promise.prototype.finally/-/promise.prototype.finally-3.1.2.tgz",
"integrity": "sha512-A2HuJWl2opDH0EafgdjwEw7HysI8ff/n4lW4QEVBCUXFk9QeGecBWv0Deph0UmLe3tTNYegz8MOjsVuE6SMoJA==",
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.0-next.0",
"function-bind": "^1.1.1"
},
"dependencies": {
"es-abstract": {
"version": "1.17.7",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz",
"integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==",
"requires": {
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.2.2",
"is-regex": "^1.1.1",
"object-inspect": "^1.8.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.1",
"string.prototype.trimend": "^1.0.1",
"string.prototype.trimstart": "^1.0.1"
}
}
}
},
"prompts": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz",
@ -9929,6 +10435,11 @@
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
},
"ramda": {
"version": "0.27.1",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz",
"integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw=="
},
"randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@ -10044,6 +10555,24 @@
"resolve": "^1.1.6"
}
},
"redis-commands": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
"integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ=="
},
"redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
"integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60="
},
"redis-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
"integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=",
"requires": {
"redis-errors": "^1.0.0"
}
},
"reflect-metadata": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
@ -10611,7 +11140,6 @@
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
@ -10753,6 +11281,31 @@
"resolved": "https://registry.npmjs.org/signedsource/-/signedsource-1.0.0.tgz",
"integrity": "sha1-HdrOSYF5j5O9gzlzgD2A1S6TrWo="
},
"simple-git": {
"version": "2.35.0",
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.35.0.tgz",
"integrity": "sha512-VuXs2/HyZmZm43Z5IjvU+ahTmURh/Hmb/egmgNdFZuu8OEnW2emCalnL/4jRQkXeJvfzCTnev6wo5jtDmWw0Dw==",
"requires": {
"@kwsites/file-exists": "^1.1.1",
"@kwsites/promise-deferred": "^1.1.1",
"debug": "^4.3.1"
},
"dependencies": {
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@ -11113,6 +11666,11 @@
}
}
},
"standard-as-callback": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.0.1.tgz",
"integrity": "sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg=="
},
"static-extend": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
@ -11567,6 +12125,15 @@
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
"timers-ext": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz",
"integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==",
"requires": {
"es5-ext": "~0.10.46",
"next-tick": "1"
}
},
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@ -11753,6 +12320,11 @@
"yn": "3.1.1"
}
},
"ts-toolbelt": {
"version": "6.15.5",
"resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz",
"integrity": "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A=="
},
"tsconfig-paths": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz",
@ -11865,6 +12437,11 @@
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"dev": true
},
"type": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
},
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",

View File

@ -11,7 +11,7 @@
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:debug": "DEBUG=simple-git,simple-git:* nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
@ -21,6 +21,7 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/bull": "^0.3.1",
"@nestjs/common": "^7.5.1",
"@nestjs/config": "^0.6.2",
"@nestjs/core": "^7.5.1",
@ -28,17 +29,23 @@
"@nestjs/platform-express": "^7.5.1",
"@nestjs/typeorm": "^7.1.5",
"@neuralegion/class-sanitizer": "^0.3.2",
"@types/bull": "^3.15.0",
"@types/ramda": "^0.27.38",
"apollo-server-express": "^2.19.2",
"bcrypt": "^5.0.0",
"bull": "^3.20.1",
"class-transformer": "^0.3.2",
"class-validator": "^0.13.1",
"graphql": "^15.5.0",
"graphql-tools": "^7.0.2",
"js-yaml": "^4.0.0",
"nestjs-redis": "^1.2.8",
"pg": "^8.5.1",
"ramda": "^0.27.1",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^6.6.3",
"simple-git": "^2.35.0",
"typeorm": "^0.2.30"
},
"devDependencies": {

View File

@ -6,7 +6,9 @@ import { AppController } from './app.controller';
import { AppResolver } from './app.resolver';
import { AppService } from './app.service';
import { ProjectsModule } from './projects/projects.module';
import { ReposModule } from './repos/repos.module';
import configuration from './commons/config/configuration';
import { BullModule } from '@nestjs/bull';
@Module({
imports: [
@ -15,13 +17,13 @@ import configuration from './commons/config/configuration';
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (cnfigService: ConfigService) => ({
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: cnfigService.get<string>('db.postgres.host'),
port: cnfigService.get<number>('db.postgres.port'),
username: cnfigService.get<string>('db.postgres.username'),
password: cnfigService.get<string>('db.postgres.password'),
database: cnfigService.get<string>('db.postgres.database'),
host: configService.get<string>('db.postgres.host'),
port: configService.get<number>('db.postgres.port'),
username: configService.get<string>('db.postgres.username'),
password: configService.get<string>('db.postgres.password'),
database: configService.get<string>('db.postgres.database'),
synchronize: true,
autoLoadEntities: true,
}),
@ -29,14 +31,25 @@ import configuration from './commons/config/configuration';
}),
GraphQLModule.forRootAsync({
imports: [ConfigModule],
useFactory: (cnfigService: ConfigService) => ({
debug: cnfigService.get<string>('env') !== 'prod',
useFactory: (configService: ConfigService) => ({
debug: configService.get<string>('env') !== 'prod',
playground: true,
autoSchemaFile: true,
}),
inject: [ConfigService],
}),
BullModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
redis: {
host: configService.get<string>('redis.mq.host', 'localhost'),
port: configService.get<number>('redis.mq.port', 6379),
},
}),
inject: [ConfigService],
}),
ProjectsModule,
ReposModule,
],
controllers: [AppController],
providers: [AppService, AppResolver],

View File

@ -1,5 +1,11 @@
import { InputType } from '@nestjs/graphql';
import { IsString, IsUrl, MaxLength, MinLength } from 'class-validator';
import {
IsOptional,
IsString,
IsUrl,
MaxLength,
MinLength,
} from 'class-validator';
@InputType({ isAbstract: true })
export class CreateProjectInput {
@ -13,11 +19,12 @@ export class CreateProjectInput {
@MinLength(2)
comment: string;
@IsUrl({ protocols: ['ssh'], require_protocol: false })
@IsUrl({ protocols: ['ssh'] })
@MaxLength(256)
sshUrl: string;
@IsUrl()
@IsOptional()
@MaxLength(256)
webUrl?: string;

View File

@ -1,6 +1,6 @@
import { ObjectType } from '@nestjs/graphql';
import { AppBaseEntity } from 'src/commons/entities/app-base-entity';
import { Entity, Column, DeleteDateColumn } from 'typeorm';
import { AppBaseEntity } from '../commons/entities/app-base-entity';
@ObjectType()
@Entity()

View File

@ -7,5 +7,6 @@ import { Project } from './project.entity';
@Module({
imports: [TypeOrmModule.forFeature([Project])],
providers: [ProjectsService, ProjectsResolver],
exports: [ProjectsService],
})
export class ProjectsModule {}

View File

@ -1,12 +1,19 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ProjectsResolver } from './projects.resolver';
import { ProjectsService } from './projects.service';
describe('ProjectsResolver', () => {
let resolver: ProjectsResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ProjectsResolver],
providers: [
ProjectsResolver,
{
provide: ProjectsService,
useValue: {},
},
],
}).compile();
resolver = module.get<ProjectsResolver>(ProjectsResolver);

View File

@ -25,13 +25,15 @@ export class ProjectsResolver {
return await this.service.create(dto);
}
@Mutation(() => Boolean)
@Mutation(() => Project)
async modifyProject(
@Args('id', { type: () => String }) id: string,
@Args('project', { type: () => UpdateProjectInput })
dto: UpdateProjectInput,
) {
return await this.service.update(id, dto);
const tmp = await this.service.update(id, dto);
console.log(tmp);
return tmp;
}
@Mutation(() => Number)

View File

@ -1,12 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ProjectsService } from './projects.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Project } from './project.entity';
describe('ProjectsService', () => {
let service: ProjectsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ProjectsService],
providers: [
ProjectsService,
{
provide: getRepositoryToken(Project),
useValue: {},
},
],
}).compile();
service = module.get<ProjectsService>(ProjectsService);

View File

@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { BaseDbService } from 'src/commons/services/base-db.service';
import { BaseDbService } from '../commons/services/base-db.service';
import { Repository } from 'typeorm';
import { CreateProjectInput } from './dtos/create-project.input';
import { Project } from './project.entity';
@ -26,7 +26,8 @@ export class ProjectsService extends BaseDbService<Project> {
async update(id: string, dto: CreateProjectInput) {
await this.isDuplicateEntityForUpdate(id, dto);
await this.repository.update({ id }, dto);
const old = await this.findOne(id);
return await this.repository.save(this.repository.merge(old, dto));
}
async remove(id: string) {

View File

@ -0,0 +1,24 @@
import { ObjectType, Field } from '@nestjs/graphql';
import {
LogResult,
DefaultLogFields,
BranchSummary,
BranchSummaryBranch,
} from 'simple-git';
@ObjectType()
export class Branch implements BranchSummaryBranch {
current: boolean;
name: string;
commit: string;
label: string;
}
@ObjectType()
export class BranchList {
detached: boolean;
current: string;
all: string[];
@Field(() => [Branch])
branches: Branch[];
}

View File

@ -0,0 +1,8 @@
import { InputType } from '@nestjs/graphql';
import { IsUUID } from 'class-validator';
@InputType()
export class ListBranchesArgs {
@IsUUID()
projectId: string;
}

View File

@ -0,0 +1,12 @@
import { InputType, ObjectType } from '@nestjs/graphql';
import { IsOptional, IsString, IsUUID } from 'class-validator';
@InputType()
export class ListLogsArgs {
@IsUUID()
projectId: string;
@IsString()
@IsOptional()
branch?: string;
}

View File

@ -0,0 +1,21 @@
import { ObjectType, Field } from '@nestjs/graphql';
import { LogResult, DefaultLogFields } from 'simple-git';
@ObjectType()
export class LogFields {
hash: string;
date: string;
message: string;
refs: string;
body: string;
author_name: string;
author_email: string;
}
@ObjectType()
export class LogList implements LogResult<DefaultLogFields> {
@Field(() => [LogFields])
all: LogFields[];
total: number;
latest: LogFields;
}

View File

@ -0,0 +1,2 @@
import { ApplicationException } from '../../commons/exceptions/application.exception';
export class GetWorkspaceLockFailedException extends ApplicationException {}

View File

@ -0,0 +1 @@
export const WORKSPACE_ACTION = 'workspace-action';

20
src/repos/repos.module.ts Normal file
View File

@ -0,0 +1,20 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Project } from '../projects/project.entity';
import { ReposResolver } from './repos.resolver';
import { ReposService } from './repos.service';
import { ConfigModule } from '@nestjs/config';
import { BullModule } from '@nestjs/bull';
import { WORKSPACE_ACTION } from './repos.constants';
@Module({
imports: [
TypeOrmModule.forFeature([Project]),
ConfigModule,
BullModule.registerQueue({
name: WORKSPACE_ACTION,
}),
],
providers: [ReposResolver, ReposService],
})
export class ReposModule {}

View File

@ -0,0 +1,25 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ReposResolver } from './repos.resolver';
import { ReposService } from './repos.service';
describe('ReposResolver', () => {
let resolver: ReposResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ReposResolver,
{
provide: ReposService,
useValue: {},
},
],
}).compile();
resolver = module.get<ReposResolver>(ReposResolver);
});
it('should be defined', () => {
expect(resolver).toBeDefined();
});
});

View File

@ -0,0 +1,26 @@
import { Args, Query, Resolver } from '@nestjs/graphql';
import { ListLogsArgs } from './dtos/list-logs.args';
import { ReposService } from './repos.service';
import { LogList } from './dtos/log-list.model';
import { ListBranchesArgs } from './dtos/list-branches.args';
import { BranchList } from './dtos/branch-list.model';
@Resolver()
export class ReposResolver {
constructor(private readonly service: ReposService) {}
@Query(() => LogList)
async listLogs(@Args('listLogsArgs') dto: ListLogsArgs) {
return await this.service.listLogs(dto);
}
@Query(() => BranchList)
async ListBranchesArgs(
@Args('listBranchesArgs') dto: ListBranchesArgs,
): Promise<BranchList> {
return await this.service.listBranches(dto).then((data) => {
return {
...data,
branches: Object.values(data.branches),
};
});
}
}

View File

@ -0,0 +1,131 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Project } from '../projects/project.entity';
import { ReposService } from './repos.service';
import { ConfigModule } from '@nestjs/config';
import { readFile, rm } from 'fs/promises';
import { join } from 'path';
import configuration from '../commons/config/configuration';
import { NotFoundException } from '@nestjs/common';
const getTest1Project = () =>
({
id: '1',
sshUrl: 'ssh://gitea@git.ivanli.cc:7018/Fennec/test-1.git',
name: 'test-1',
} as Project);
describe('ReposService', () => {
let service: ReposService;
const repositoryMockFactory = jest.fn(() => ({
findOneOrFail: jest.fn(
(entity): Project => ({
...getTest1Project(),
...entity,
}),
),
}));
afterEach(async () => {
await rm(service.getWorkspaceRoot(getTest1Project()), {
recursive: true,
}).catch(() => undefined);
});
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
load: [configuration],
}),
],
providers: [
ReposService,
{
provide: getRepositoryToken(Project),
useFactory: repositoryMockFactory,
},
],
}).compile();
service = module.get<ReposService>(ReposService);
await rm(service.getWorkspaceRoot(getTest1Project()), {
recursive: true,
}).catch(() => undefined);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('getWorkspaceRoot', () => {
expect(service.getWorkspaceRoot(getTest1Project())).toBeDefined();
});
describe('listLogs', () => {
it('should be return logs', async () => {
const result = await service.listLogs({ projectId: '1' });
expect(result).toBeDefined();
}, 20_000);
});
describe('listBranch', () => {
it('should be return branches', async () => {
const result = await service.listBranches({ projectId: '1' });
expect(result).toBeDefined();
}, 10_000);
});
describe('checkoutBranch', () => {
it('should be checkout', async () => {
await service.checkoutBranch(getTest1Project(), 'master');
const filePath = join(
service.getWorkspaceRoot(getTest1Project()),
'README.md',
);
const text = await readFile(filePath, { encoding: 'utf-8' });
expect(text).toMatch(/Commit 1/gi);
}, 30_000);
it('multiplexing workspace', async () => {
await service.checkoutBranch(getTest1Project(), 'master');
await service.checkoutBranch(getTest1Project(), 'branch-a');
await service.checkoutBranch(getTest1Project(), 'branch-b');
const filePath = join(
service.getWorkspaceRoot(getTest1Project()),
'branch-b.md',
);
const text = await readFile(filePath, { encoding: 'utf-8' });
expect(text).toMatch(/Commit branch b/gi);
}, 30_000);
it('nonexistent branch', async () => {
return expect(
service.checkoutBranch(getTest1Project(), 'nonexistent'),
).rejects.toBeInstanceOf(NotFoundException);
}, 30_000);
it('checkout the specified version', async () => {
await service.checkoutBranch(getTest1Project(), 'master');
const filePath = join(
service.getWorkspaceRoot(getTest1Project()),
'README.md',
);
const text = await readFile(filePath, { encoding: 'utf-8' });
expect(text).toMatch(/Commit 1/gi);
}, 30_000);
});
describe('checkoutCommit', () => {
it('should be checkout', async () => {
await service.checkoutCommit(getTest1Project(), '498c782685');
const filePath = join(
service.getWorkspaceRoot(getTest1Project()),
'README.md',
);
const text = await readFile(filePath, { encoding: 'utf-8' });
expect(text).toMatch(/Commit 1/gi);
}, 20_000);
it('should be checkout right commit', async () => {
await service.checkoutCommit(getTest1Project(), '7f7123fe5b');
const filePath = join(
service.getWorkspaceRoot(getTest1Project()),
'README.md',
);
const text = await readFile(filePath, { encoding: 'utf-8' });
expect(text).toMatch(/(?!Commit 1)/gi);
}, 20_000);
});
});

101
src/repos/repos.service.ts Normal file
View File

@ -0,0 +1,101 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { F_OK } from 'constants';
import { access, mkdir } from 'fs/promises';
import { join } from 'path';
import { gitP } from 'simple-git';
import { Repository } from 'typeorm';
import { Project } from '../projects/project.entity';
import { ListBranchesArgs } from './dtos/list-branches.args';
import { ListLogsArgs } from './dtos/list-logs.args';
import { ConfigService } from '@nestjs/config';
const DEFAULT_REMOTE_NAME = 'origin';
@Injectable()
export class ReposService {
constructor(
@InjectRepository(Project)
private readonly projectRepository: Repository<Project>,
private readonly configService: ConfigService,
) {}
getWorkspaceRoot(project: Project): string {
return join(
this.configService.get<string>('workspaces.root'),
project.name,
);
}
async lockWorkspace(workspaceRoot: string) {
// TODO: 获取锁,失败抛错。
}
async getGit(project: Project) {
const workspaceRoot = this.getWorkspaceRoot(project);
await this.lockWorkspace(workspaceRoot);
const firstInit = await access(workspaceRoot, F_OK)
.then(() => false)
.catch(async () => {
await mkdir(workspaceRoot);
return true;
});
const git = gitP(workspaceRoot);
if (firstInit) {
await git.init();
await git.addRemote(DEFAULT_REMOTE_NAME, project.sshUrl);
}
return git;
}
async listLogs(dto: ListLogsArgs) {
const project = await this.projectRepository.findOneOrFail({
id: dto.projectId,
});
const git = await this.getGit(project);
await git.fetch();
return await git.log({
'--branches': dto.branch ?? '',
'--remotes': DEFAULT_REMOTE_NAME,
});
}
async listBranches(dto: ListBranchesArgs) {
const project = await this.projectRepository.findOneOrFail({
id: dto.projectId,
});
const git = await this.getGit(project);
return git.branch();
}
async checkoutBranch(project: Project, branch: string) {
const git = await this.getGit(project);
try {
await git.fetch(DEFAULT_REMOTE_NAME, branch);
} catch (err) {
if (err.message.includes("couldn't find remote ref nonexistent")) {
throw new NotFoundException(err.message);
}
throw err;
}
await git.checkout([
'-B',
branch,
'--track',
`${DEFAULT_REMOTE_NAME}/${branch}`,
]);
}
async checkoutCommit(project: Project, commitNumber: string) {
const git = await this.getGit(project);
try {
await git.fetch(DEFAULT_REMOTE_NAME);
} catch (err) {
if (err.message.includes("couldn't find remote ref nonexistent")) {
throw new NotFoundException(err.message);
}
throw err;
}
await git.checkout([commitNumber]);
}
}

View File

@ -0,0 +1,8 @@
import { Process, Processor } from '@nestjs/bull';
import { WORKSPACE_ACTION } from './repos.constants';
@Processor(WORKSPACE_ACTION)
export class WorkspaceActionConsumer {
@Process()
async dispatch() {}
}

View File

@ -0,0 +1,5 @@
import { WorkspaceActions } from './workspace-actions.enum';
export class WorkspaceAction {
action: WorkspaceActions;
}

View File

@ -0,0 +1,6 @@
export enum WorkspaceActions {
checkoutBranch = 'checkoutBranch',
checkoutCommit = 'checkoutCommit',
listLogs = 'listLogs',
listBranches = 'listBranches',
}