feat: Add conference manager (#71)

This commit is contained in:
Thomas Durieux
2021-09-06 09:34:39 +02:00
committed by GitHub
parent 6dba366296
commit 49da267d5f
23 changed files with 2085 additions and 30 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ const config: Config = {
DEFAULT_QUOTA: 2 * 1024 * 1024 * 1024 * 8, DEFAULT_QUOTA: 2 * 1024 * 1024 * 1024 * 8,
MAX_FILE_SIZE: 10 * 1024 * 1024, // in b MAX_FILE_SIZE: 10 * 1024 * 1024, // in b
MAX_REPO_SIZE: 500 * 8 * 1024, // in kb MAX_REPO_SIZE: 500 * 8 * 1024, // in kb
ENABLE_DOWNLOAD: false, ENABLE_DOWNLOAD: true,
AUTH_CALLBACK: "http://localhost:5000/github/auth", AUTH_CALLBACK: "http://localhost:5000/github/auth",
ANONYMIZATION_MASK: "XXXX", ANONYMIZATION_MASK: "XXXX",
PORT: 5000, PORT: 5000,
+227 -1
View File
@@ -7,7 +7,7 @@
"": { "": {
"name": "anonymous_github", "name": "anonymous_github",
"version": "2.1.0", "version": "2.1.0",
"license": "MIT", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@octokit/oauth-app": "^3.3.2", "@octokit/oauth-app": "^3.3.2",
"@octokit/rest": "^18.5.3", "@octokit/rest": "^18.5.3",
@@ -25,6 +25,7 @@
"istextorbinary": "^6.0.0", "istextorbinary": "^6.0.0",
"mime-types": "^2.1.30", "mime-types": "^2.1.30",
"mongoose": "^5.13.5", "mongoose": "^5.13.5",
"node-schedule": "^2.0.0",
"parse-github-url": "^1.0.2", "parse-github-url": "^1.0.2",
"passport": "^0.4.1", "passport": "^0.4.1",
"passport-github2": "^0.1.12", "passport-github2": "^0.1.12",
@@ -55,6 +56,9 @@
"nodemon": "^2.0.7", "nodemon": "^2.0.7",
"ts-node": "^10.1.0", "ts-node": "^10.1.0",
"typescript": "^4.3.5" "typescript": "^4.3.5"
},
"funding": {
"url": "https://github.com/sponsors/tdurieux"
} }
}, },
"node_modules/@cspotcode/source-map-consumer": { "node_modules/@cspotcode/source-map-consumer": {
@@ -1278,6 +1282,18 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/camelcase": { "node_modules/camelcase": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
@@ -1597,6 +1613,18 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true "dev": true
}, },
"node_modules/cron-parser": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-3.5.0.tgz",
"integrity": "sha512-wyVZtbRs6qDfFd8ap457w3XVntdvqcwBGxBoTvJQH9KGVKL/fB+h2k3C8AqiVxvUQKN1Ps/Ns46CNViOpVDhfQ==",
"dependencies": {
"is-nan": "^1.3.2",
"luxon": "^1.26.0"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/crypto-random-string": { "node_modules/crypto-random-string": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
@@ -1688,6 +1716,17 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
"dependencies": {
"object-keys": "^1.0.12"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/delayed-stream": { "node_modules/delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -2141,6 +2180,11 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/get-caller-file": { "node_modules/get-caller-file": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -2159,6 +2203,19 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/get-intrinsic": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-stream": { "node_modules/get-stream": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
@@ -2273,6 +2330,17 @@
"gunzip-maybe": "bin.js" "gunzip-maybe": "bin.js"
} }
}, },
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/has-flag": { "node_modules/has-flag": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -2282,6 +2350,17 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/has-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-yarn": { "node_modules/has-yarn": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz",
@@ -2488,6 +2567,21 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/is-nan": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz",
"integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==",
"dependencies": {
"call-bind": "^1.0.0",
"define-properties": "^1.1.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-npm": { "node_modules/is-npm": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz",
@@ -2730,6 +2824,11 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/long-timeout": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz",
"integrity": "sha1-lyHXiLR+C8taJMLivuGg2lXatRQ="
},
"node_modules/lowercase-keys": { "node_modules/lowercase-keys": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
@@ -2738,6 +2837,14 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/luxon": {
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz",
"integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==",
"engines": {
"node": "*"
}
},
"node_modules/make-dir": { "node_modules/make-dir": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -3088,6 +3195,19 @@
"node": "4.x || >=6.0.0" "node": "4.x || >=6.0.0"
} }
}, },
"node_modules/node-schedule": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.0.0.tgz",
"integrity": "sha512-cHc9KEcfiuXxYDU+HjsBVo2FkWL1jRAUoczFoMIzRBpOA4p/NRHuuLs85AWOLgKsHtSPjN8csvwIxc2SqMv+CQ==",
"dependencies": {
"cron-parser": "^3.1.0",
"long-timeout": "0.1.1",
"sorted-array-functions": "^1.3.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/nodemon": { "node_modules/nodemon": {
"version": "2.0.12", "version": "2.0.12",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz",
@@ -3177,6 +3297,14 @@
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
"integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE=" "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE="
}, },
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/on-finished": { "node_modules/on-finished": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -4021,6 +4149,11 @@
"resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
"integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
}, },
"node_modules/sorted-array-functions": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz",
"integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA=="
},
"node_modules/sparse-bitfield": { "node_modules/sparse-bitfield": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
@@ -5944,6 +6077,15 @@
"responselike": "^2.0.0" "responselike": "^2.0.0"
} }
}, },
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"requires": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
}
},
"camelcase": { "camelcase": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
@@ -6196,6 +6338,15 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true "dev": true
}, },
"cron-parser": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-3.5.0.tgz",
"integrity": "sha512-wyVZtbRs6qDfFd8ap457w3XVntdvqcwBGxBoTvJQH9KGVKL/fB+h2k3C8AqiVxvUQKN1Ps/Ns46CNViOpVDhfQ==",
"requires": {
"is-nan": "^1.3.2",
"luxon": "^1.26.0"
}
},
"crypto-random-string": { "crypto-random-string": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
@@ -6259,6 +6410,14 @@
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
"integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="
}, },
"define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
"requires": {
"object-keys": "^1.0.12"
}
},
"delayed-stream": { "delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -6607,6 +6766,11 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"get-caller-file": { "get-caller-file": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -6619,6 +6783,16 @@
"integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
"dev": true "dev": true
}, },
"get-intrinsic": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1"
}
},
"get-stream": { "get-stream": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
@@ -6700,12 +6874,25 @@
"through2": "^2.0.3" "through2": "^2.0.3"
} }
}, },
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"requires": {
"function-bind": "^1.1.1"
}
},
"has-flag": { "has-flag": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true "dev": true
}, },
"has-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
},
"has-yarn": { "has-yarn": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz",
@@ -6866,6 +7053,15 @@
"is-path-inside": "^3.0.1" "is-path-inside": "^3.0.1"
} }
}, },
"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-npm": { "is-npm": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz",
@@ -7056,11 +7252,21 @@
"is-unicode-supported": "^0.1.0" "is-unicode-supported": "^0.1.0"
} }
}, },
"long-timeout": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz",
"integrity": "sha1-lyHXiLR+C8taJMLivuGg2lXatRQ="
},
"lowercase-keys": { "lowercase-keys": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="
}, },
"luxon": {
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz",
"integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ=="
},
"make-dir": { "make-dir": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -7303,6 +7509,16 @@
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
}, },
"node-schedule": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.0.0.tgz",
"integrity": "sha512-cHc9KEcfiuXxYDU+HjsBVo2FkWL1jRAUoczFoMIzRBpOA4p/NRHuuLs85AWOLgKsHtSPjN8csvwIxc2SqMv+CQ==",
"requires": {
"cron-parser": "^3.1.0",
"long-timeout": "0.1.1",
"sorted-array-functions": "^1.3.0"
}
},
"nodemon": { "nodemon": {
"version": "2.0.12", "version": "2.0.12",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz",
@@ -7362,6 +7578,11 @@
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
"integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE=" "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE="
}, },
"object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
},
"on-finished": { "on-finished": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -8032,6 +8253,11 @@
"resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
"integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
}, },
"sorted-array-functions": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz",
"integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA=="
},
"sparse-bitfield": { "sparse-bitfield": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+1
View File
@@ -40,6 +40,7 @@
"istextorbinary": "^6.0.0", "istextorbinary": "^6.0.0",
"mime-types": "^2.1.30", "mime-types": "^2.1.30",
"mongoose": "^5.13.5", "mongoose": "^5.13.5",
"node-schedule": "^2.0.0",
"parse-github-url": "^1.0.2", "parse-github-url": "^1.0.2",
"passport": "^0.4.1", "passport": "^0.4.1",
"passport-github2": "^0.1.12", "passport-github2": "^0.1.12",
+2 -1
View File
@@ -27,6 +27,7 @@
"user_not_found": "The current user is not found in the database", "user_not_found": "The current user is not found in the database",
"not_connected": "User is not connected", "not_connected": "User is not connected",
"page_not_supported_on_different_branch": "Anonymized GitHub pages are only supported on the same branch.", "page_not_supported_on_different_branch": "Anonymized GitHub pages are only supported on the same branch.",
"page_not_activated": "Anonymized GitHub page is not enabled." "page_not_activated": "Anonymized GitHub page is not enabled.",
"conf_not_activated": "The conference is not activated."
} }
} }
+28 -10
View File
@@ -129,9 +129,11 @@
id="commit" id="commit"
name="commit" name="commit"
ng-model="source.commit" ng-model="source.commit"
required
ng-class="{'is-invalid': anonymize.commit.$invalid}"
/> />
<small class="form-text text-muted" <small class="form-text text-muted"
>The commit to anonymize</small >The commit to anonymize.</small
> >
</div> </div>
<!-- Repo ID --> <!-- Repo ID -->
@@ -173,9 +175,10 @@
rows="3" rows="3"
ng-model="terms" ng-model="terms"
ng-model-options="{ debounce: 250 }" ng-model-options="{ debounce: 250 }"
ng-class="{'is-invalid': anonymize.terms.$invalid}"
></textarea> ></textarea>
<small id="termsHelp" class="form-text text-muted" <small id="termsHelp" class="form-text text-muted"
>One term per line. Each term will be replaced by XXX</small >One term per line. Each term will be replaced by XXX.</small
> >
<div <div
class="invalid-feedback" class="invalid-feedback"
@@ -194,10 +197,24 @@
id="conference" id="conference"
name="conference" name="conference"
ng-model="conference" ng-model="conference"
ng-class="{'is-invalid': anonymize.conference.$invalid}"
/> />
<small class="form-text text-muted" <small class="form-text text-muted" ng-show="!conference_data"
>In which conference the paper will be submitted.</small >In which conference the paper will be submitted.</small
> >
<small class="form-text text-muted" ng-show="conference_data"
><a ng-href="{{conference_data.url}}" target="_target"
>{{conference_data.name}}</a
>
will expire on {{conference_data.endDate | date}}.</small
>
<div
class="invalid-feedback"
ng-show="anonymize.conference.$error.activated"
>
The conference is not activated.
</div>
</div> </div>
<div class="accordion mb-3" id="options"> <div class="accordion mb-3" id="options">
@@ -341,10 +358,11 @@
<small class="form-text text-muted" <small class="form-text text-muted"
>How the repository will be anonymized. Stream mode will >How the repository will be anonymized. Stream mode will
request the content on the flight. This is the only request the content on the flight. This is the only
option for repositories bigger than {{site_options.MAX_REPO_SIZE * 1024| humanFileSize}}. Download will option for repositories bigger than
download the repository the repository on the {{site_options.MAX_REPO_SIZE * 1024| humanFileSize}}.
anonymous.4open.science server, it is faster and offer Download will download the repository the repository on
more features.</small the anonymous.4open.science server, it is faster and
offer more features.</small
> >
</div> </div>
<div class="form-group"> <div class="form-group">
@@ -402,9 +420,9 @@
ng-model="options.expirationMode" ng-model="options.expirationMode"
> >
<option value="never" selected>Never expire</option> <option value="never" selected>Never expire</option>
<option value="redirect" <option value="redirect">
>Redirect to GitHub when expired</option Redirect to GitHub when expired
> </option>
<option value="remove">Remove when expired</option> <option value="remove">Remove when expired</option>
</select> </select>
<small class="form-text text-muted" <small class="form-text text-muted"
+269
View File
@@ -0,0 +1,269 @@
<div class="container page">
<div>
<h1>
<a ng-href="{{conference.url}}">{{conference.name}}</a>
<span
class="badge"
ng-class="{'badge-warning': conference.status == 'removed' || conference.status == 'expired', 'badge-success': conference.status == 'ready'}"
ng-bind="conference.status | title"
></span>
</h1>
<div class="row mb-3 m-0 py-2 border">
<div class="col-2 font-weight-bold">ID</div>
<div class="col-10">{{conference.conferenceID}}</div>
<div class="col-2 font-weight-bold">Name</div>
<div class="col-10">{{conference.name}}</div>
<div class="col-2 font-weight-bold">URL</div>
<div class="col-10">
<a ng-href="{{conference.url}}" ng-bind="conference.url"></a>
</div>
<div class="col-2 font-weight-bold">From</div>
<div class="col-10">{{conference.startDate | date}}</div>
<div class="col-2 font-weight-bold">End</div>
<div class="col-10">{{conference.endDate | date}}</div>
<div class="col-2 font-weight-bold"># Repositories</div>
<div class="col-10">{{::conference.repositories.length | number}}</div>
<div class="col-2 font-weight-bold">Price</div>
<div class="col-10">{{conference.price || 0 | number}} €</div>
</div>
<h3>Repositories</h3>
<div class="border-bottom color-border-secondary py-3 w-100">
<div class="d-flex flex-items-start w-100">
<form class="w-100" aria-label="Repositories" accept-charset="UTF-8">
<div class="d-flex flex-column flex-lg-row flex-auto">
<div class="mb-1 mb-md-0 mr-md-3">
<input
type="search"
id="search"
class="form-control"
aria-label="Find repositories"
placeholder="Find repositories"
autocomplete="off"
ng-model="search"
/>
</div>
<div class="d-flex flex-wrap">
<div class="dropdown mt-1 mt-lg-0 mr-1">
<button
class="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownSort"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Sort
</button>
<div class="dropdown-menu" aria-labelledby="dropdownSort">
<h6 class="dropdown-header">Select order</h6>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="radio"
name="sort"
id="anonymizeDate"
value="-anonymizeDate"
ng-model="orderBy"
/>
<label class="form-check-label" for="anonymizeDate">
Anonymize Date
</label>
</div>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="radio"
name="sort"
id="sortID"
value="repoId"
ng-model="orderBy"
/>
<label class="form-check-label" for="sortID"> ID </label>
</div>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="radio"
name="sort"
id="sortStatus"
value="-status"
ng-model="orderBy"
/>
<label class="form-check-label" for="sortStatus">
Status
</label>
</div>
</div>
</div>
<div class="dropdown mt-1 mt-lg-0 mr-1">
<button
class="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownStatus"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Status
</button>
<div class="dropdown-menu" aria-labelledby="dropdownStatus">
<h6 class="dropdown-header">Select status</h6>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="checkbox"
name="sort"
id="statusReady"
value="ready"
ng-model="filters.status.ready"
/>
<label class="form-check-label" for="statusReady">
Ready
</label>
</div>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="checkbox"
name="sort"
id="statusExpired"
value="expired"
ng-model="filters.status.expired"
/>
<label class="form-check-label" for="statusExpired">
Expired
</label>
</div>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="checkbox"
name="sort"
id="statusRemoved"
value="removed"
ng-model="filters.status.removed"
/>
<label class="form-check-label" for="statusRemoved">
Removed
</label>
</div>
</div>
</div>
</div>
</div>
</form>
<div
class="d-none d-md-flex flex-md-items-center flex-md-justify-end"
></div>
</div>
</div>
<ul class="p-0 m-0 w-100">
<li
class="col-12 d-flex px-0 py-3 border-bottom color-border-secondary"
ng-class="{'expired': repo.status == 'expired','removed': repo.status == 'removed' }"
ng-repeat="repo in conference.repositories| filter:repoFiler| orderBy:orderBy as filteredRepositories"
>
<div class="w-100">
<div class="">
<h3>
<a ng-href="/r/{{repo.repoId}}" ng-bind="repo.repoId"></a>
<span
class="badge"
ng-class="{'badge-warning': repo.status == 'removed' || repo.status == 'expired', 'badge-success': repo.status == 'ready', 'badge-danger': ''}"
ng-bind="repo.status | title"
></span>
</h3>
<span class="color-text-secondary mb-1">
<span class="repository">
<i class="fab fa-github" aria-hidden="true"></i>
<a
href="https://github.com/{{repo.source.fullName}}/"
class="fullName"
ng-bind="repo.source.fullName"
></a>
</span>
<span class="branch" ng-if="repo.options.update">
<i class="fas fa-code-branch" aria-hidden="true"></i>
<a
href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.branch}}"
class="branch"
ng-bind="repo.source.branch"
></a>
</span>
<span class="commit" ng-if="!repo.options.update">
@<a
href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.commit}}"
class="commit"
ng-bind="repo.source.commit.substring(0, 8)"
></a>
</span>
anonymized {{repo.anonymizeDate | humanTime}}
</span>
</div>
<div class="color-text-secondary mt-2">
<span
class="ml-0 mr-3"
class="terms"
title="Terms: {{::repo.options.terms.join(', ')}}"
data-toggle="tooltip"
data-placement="bottom"
>
<i class="fas fa-shield-alt"></i>
{{::repo.options.terms.length | number}}
</span>
<span
class="ml-0 mr-3"
title="Size: {{::repo.size | humanFileSize}}"
data-toggle="tooltip"
data-placement="bottom"
>
<i class="fas fa-database"></i> {{::repo.size |
humanFileSize}}</span
>
<span
class="ml-0 mr-3"
title="View: {{::repo.pageView | number}}"
data-toggle="tooltip"
data-placement="bottom"
>
<i class="far fa-eye" aria-hidden="true"></i>
{{::repo.pageView | number}}
</span>
<span
class="ml-0 mr-3"
title="Last view: {{::repo.lastView | date}}"
data-toggle="tooltip"
data-placement="bottom"
>
<i class="far fa-calendar-alt" aria-hidden="true"></i>
Last view: {{::repo.lastView | humanTime}}</span
>
<span
class="ml-0 mr-3"
ng-if="repo.options.expirationMode!='never' && repo.status == 'ready'"
>
<i class="far fa-clock" aria-hidden="true"></i>
Expire: {{repo.options.expirationDate | humanTime}}</span
>
</div>
</div>
</li>
<li
class="col-12 d-flex px-0 py-3 border-bottom color-border-secondary"
ng-if="conference.repositories.length == 0"
>
There is no repository for this conference.
</li>
</ul>
</div>
</div>
+225
View File
@@ -0,0 +1,225 @@
<div class="container page">
<div class="row">
<h1>Conferences</h1>
<div class="border-bottom color-border-secondary py-3 w-100">
<div class="d-flex flex-items-start w-100">
<form class="w-100" aria-label="Repositories" accept-charset="UTF-8">
<div class="d-flex flex-column flex-lg-row flex-auto">
<div class="mb-1 mb-md-0 mr-md-3">
<input
type="search"
id="search"
class="form-control"
aria-label="Find a conference…"
placeholder="Find a conference…"
autocomplete="off"
ng-model="search"
/>
</div>
<div class="d-flex flex-wrap">
<div class="dropdown mt-1 mt-lg-0 mr-1">
<button
class="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownSort"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Sort
</button>
<div class="dropdown-menu" aria-labelledby="dropdownSort">
<h6 class="dropdown-header">Select order</h6>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="radio"
name="sort"
id="sortName"
value="name"
ng-model="orderBy"
/>
<label class="form-check-label" for="sortName">
Name
</label>
</div>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="radio"
name="sort"
id="sortID"
value="conferenceID"
ng-model="orderBy"
/>
<label class="form-check-label" for="sortID"> ID </label>
</div>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="radio"
name="sort"
id="sortStatus"
value="-status"
ng-model="orderBy"
/>
<label class="form-check-label" for="sortStatus">
Status
</label>
</div>
</div>
</div>
<div class="dropdown mt-1 mt-lg-0 mr-1">
<button
class="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownStatus"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Status
</button>
<div class="dropdown-menu" aria-labelledby="dropdownStatus">
<h6 class="dropdown-header">Select status</h6>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="checkbox"
name="sort"
id="statusReady"
value="ready"
ng-model="filters.status.ready"
/>
<label class="form-check-label" for="statusReady">
Ready
</label>
</div>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="checkbox"
name="sort"
id="statusExpired"
value="expired"
ng-model="filters.status.expired"
/>
<label class="form-check-label" for="statusExpired">
Expired
</label>
</div>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="checkbox"
name="sort"
id="statusRemoved"
value="removed"
ng-model="filters.status.removed"
/>
<label class="form-check-label" for="statusRemoved">
Removed
</label>
</div>
</div>
</div>
</div>
</div>
</form>
<div class="d-none d-md-flex flex-md-items-center flex-md-justify-end">
<a href="/conference/new" class="text-center btn btn-primary ml-3">
<i class="fa fa-plus-circle" aria-hidden="true"></i> Add
</a>
</div>
</div>
</div>
<ul class="p-0 m-0 w-100">
<li
class="col-12 d-flex px-0 py-3 border-bottom color-border-secondary"
ng-class="{'expired': conference.status == 'expired','removed': conference.status == 'removed' }"
ng-repeat="conference in conferences| filter:conferenceFilter| orderBy:orderBy as filteredConferences"
>
<div class="w-100">
<div class="">
<h3>
<a
ng-href="/conference/{{conference.conferenceID}}"
ng-bind="conference.name"
></a>
<span
class="badge"
ng-class="{'badge-warning': conference.status == 'removed' || conference.status == 'expired', 'badge-success': conference.status == 'ready', 'badge-danger': ''}"
ng-bind="conference.status | title"
></span>
</h3>
<span class="color-text-secondary mb-1 ng-binding">
Conference ID: '{{conference.conferenceID}}'
</span>
</div>
<div class="color-text-secondary mt-2">
<span
class="ml-0 mr-3"
title="# repository: {{conference.nbRepository || 0 | number}}"
data-toggle="tooltip"
data-placement="bottom"
>
<i class="fas fa-table"></i>
{{::conference.nbRepositories || 0 | number}}</span
>
<span class="ml-0 mr-3">
<i class="fas fa-euro-sign"></i>
Total: {{conference.price || 0 | number}} €
</span>
<span class="ml-0 mr-3">
<i class="fas fa-calendar-alt"></i>
From {{conference.startDate | date}} to {{conference.endDate |
date}}</span
>
</div>
</div>
<div class="d-flex">
<div class="dropdown">
<button
class="btn black_border dropdown-toggle btn-sm"
type="button"
id="dropdownMenuButton"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Actions
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a
class="dropdown-item"
href="/conference/{{conference.conferenceID}}/edit"
>
<i class="far fa-edit" aria-hidden="true"></i> Edit
</a>
<a
class="dropdown-item"
href="#"
ng-show="conference.status != 'removed'"
ng-click="removeConference(conference)"
>
<i class="fas fa-trash-alt"></i> Remove
</a>
<a
class="dropdown-item"
href="/conference/{{conference.conferenceID}}/"
>
<i class="fa fa-eye" aria-hidden="true"></i> View conference
</a>
</div>
</div>
</div>
</li>
<li class="col-12 d-flex px-0 py-3 border-bottom color-border-secondary" ng-if="conferences.length == 0">
You have no conference. You a create a
<a href="/conference/new">new one</a>.
</li>
</ul>
</div>
</div>
+12 -8
View File
@@ -215,42 +215,46 @@
</span> </span>
</div> </div>
<div class="color-text-secondary mt-2"> <div class="color-text-secondary mt-2">
<span class="ml-0 mr-3" ng-if="::repo.conference">
<i class="fas fa-chalkboard-teacher"></i>
{{repo.conference}}
</span>
<span <span
class="ml-0 mr-3" class="ml-0 mr-3"
class="terms" class="terms"
title="Terms: {{repo.options.terms.join(', ')}}" title="Terms: {{::repo.options.terms.join(', ')}}"
data-toggle="tooltip" data-toggle="tooltip"
data-placement="bottom" data-placement="bottom"
> >
<i class="fas fa-shield-alt"></i> <i class="fas fa-shield-alt"></i>
{{repo.options.terms.length | number}} {{::repo.options.terms.length | number}}
</span> </span>
<span <span
class="ml-0 mr-3" class="ml-0 mr-3"
title="Size: {{repo.size | humanFileSize}}" title="Size: {{::repo.size | humanFileSize}}"
data-toggle="tooltip" data-toggle="tooltip"
data-placement="bottom" data-placement="bottom"
> >
<i class="fas fa-database"></i> {{repo.size | <i class="fas fa-database"></i> {{::repo.size |
humanFileSize}}</span humanFileSize}}</span
> >
<span <span
class="ml-0 mr-3" class="ml-0 mr-3"
title="View: {{repo.pageView | number}}" title="View: {{::repo.pageView | number}}"
data-toggle="tooltip" data-toggle="tooltip"
data-placement="bottom" data-placement="bottom"
> >
<i class="far fa-eye" aria-hidden="true"></i> <i class="far fa-eye" aria-hidden="true"></i>
{{repo.pageView | number}} {{::repo.pageView | number}}
</span> </span>
<span <span
class="ml-0 mr-3" class="ml-0 mr-3"
title="Last view: {{repo.lastView | date}}" title="Last view: {{::repo.lastView | date}}"
data-toggle="tooltip" data-toggle="tooltip"
data-placement="bottom" data-placement="bottom"
> >
<i class="far fa-calendar-alt" aria-hidden="true"></i> <i class="far fa-calendar-alt" aria-hidden="true"></i>
Last view: {{repo.lastView | humanTime}}</span Last view: {{::repo.lastView | humanTime}}</span
> >
<span <span
class="ml-0 mr-3" class="ml-0 mr-3"
+8
View File
@@ -39,6 +39,14 @@
>Anonymize</a >Anonymize</a
> >
</li> </li>
<li class="nav-item" ng-if="user">
<a
class="nav-link"
ng-class="{'active':path == '/conferences'}"
href="/conferences"
>Conferences</a
>
</li>
</ul> </ul>
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item"> <li class="nav-item">
+443
View File
@@ -0,0 +1,443 @@
<div class="container py-4">
<h2>Create a conference</h2>
<p>
A conference allow the the chairs to access the complete list of anonymized
repositories. It also allows to increase the quota for authors.
</p>
<form class="form needs-validation" name="conference" novalidate>
<!-- name -->
<div class="form-group">
<label for="name">The name of the conference</label>
<input
type="text"
class="form-control"
name="name"
id="name"
required
ng-class="{'is-invalid': conference.name.$invalid}"
ng-model="options.name"
placeholder="The name of the conference"
/>
<div class="invalid-feedback" ng-show="conference.name.$error.invalid">
The name of the conference is invalid.
</div>
<div class="invalid-feedback" ng-show="conference.name.$error.required">
The name of the conference is required.
</div>
</div>
<!-- url -->
<div class="form-group">
<label for="url">The url of the conference</label>
<input
type="url"
class="form-control"
name="url"
id="url"
ng-class="{'is-invalid': conference.url.$invalid}"
ng-model="options.url"
placeholder="The url of the conference"
/>
<div class="invalid-feedback" ng-show="conference.url.$error.invalid">
The url of the conference is invalid.
</div>
</div>
<!-- conferenceID -->
<div class="form-group">
<label for="conferenceID">The conference ID</label>
<input
type="text"
class="form-control"
name="conferenceID"
id="conferenceID"
required
pattern="[a-zA-Z0-9\-_]{3,10}"
ng-class="{'is-invalid': conference.conferenceID.$invalid}"
ng-model="options.conferenceID"
placeholder="The conference ID that the authors will reference"
/>
<div
class="invalid-feedback"
ng-show="conference.conferenceID.$error.used"
>
The conference ID '{{options.conferenceID}}' is already used.
</div>
<div
class="invalid-feedback"
ng-show="conference.conferenceID.$error.required"
>
The conference ID is required.
</div>
<div
class="invalid-feedback"
ng-show="conference.conferenceID.$error.pattern"
>
The format of the conference ID is incorrect ([a-zA-Z0-9-_]{3,10}).
</div>
</div>
<!-- startDate -->
<div class="form-group">
<label for="startDate">Start date of the conference</label>
<input
type="date"
class="form-control"
name="startDate"
id="startDate"
required
ng-class="{'is-invalid': conference.startDate.$invalid}"
ng-model="options.startDate"
/>
<small class="form-text text-muted"
>The beginning date of the reviewing process.</small
>
<div
class="invalid-feedback"
ng-show="conference.startDate.$error.required"
>
The start date of the conference is required.
</div>
<div
class="invalid-feedback"
ng-show="conference.startDate.$error.invalid"
>
The start date of the conference is invalid. The start date should
always be smaller than the end date.
</div>
</div>
<!-- endDate -->
<div class="form-group">
<label for="endDate">End date of the conference</label>
<input
type="date"
class="form-control"
name="endDate"
id="endDate"
required
ng-class="{'is-invalid': conference.endDate.$invalid}"
ng-model="options.endDate"
/>
<small class="form-text text-muted"
>The end date of the reviewing process. All the repositories will expire
and they will not be accessible after this date.</small
>
<div
class="invalid-feedback"
ng-show="conference.endDate.$error.required"
>
The end date of the conference is required.
</div>
<div class="invalid-feedback" ng-show="conference.endDate.$error.invalid">
The end date of the conference is invalid. The end date should always be
smaller than today.
</div>
</div>
<h4>Anonymization options</h4>
<div class="accordion mb-3" id="options">
<div class="card">
<div class="card-header" id="headingOne">
<h2 class="mb-0">
<button
class="btn btn-block text-left"
type="button"
data-toggle="collapse"
data-target="#collapseOne"
aria-expanded="true"
aria-controls="collapseOne"
>
Rendering options
</button>
</h2>
</div>
<div
id="collapseOne"
class="collapse show"
aria-labelledby="headingOne"
data-parent="#options"
>
<div class="card-body">
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="link"
name="link"
ng-model="options.options.link"
/>
<label class="form-check-label" for="link">Keep links</label>
<small id="linkHelp" class="form-text text-muted"
>Keep or remove all links from text files.</small
>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="image"
name="image"
ng-model="options.options.image"
/>
<label class="form-check-label" for="image"
>Display images</label
>
<small id="imageHelp" class="form-text text-muted"
>Display or hide images from the repositories and text
files.</small
>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="pdf"
name="pdf"
ng-model="options.options.pdf"
/>
<label class="form-check-label" for="pdf">Display PDFs</label>
<small id="pdfHelp" class="form-text text-muted"
>Display or hide PDF from the repositories.</small
>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="notebook"
name="notebook"
ng-model="options.options.notebook"
/>
<label class="form-check-label" for="notebook"
>Display Notebooks</label
>
<small id="notebookHelp" class="form-text text-muted"
>Display or hide Notebooks from the repositories.</small
>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="headingTwo">
<h2 class="mb-0">
<button
class="btn btn-block text-left collapsed"
type="button"
data-toggle="collapse"
data-target="#collapseTwo"
aria-expanded="false"
aria-controls="collapseTwo"
>
Features
</button>
</h2>
</div>
<div
id="collapseTwo"
class="collapse"
aria-labelledby="headingTwo"
data-parent="#options"
>
<div class="card-body">
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="update"
name="update"
ng-model="options.options.update"
/>
<label class="form-check-label" for="update">Auto update</label>
<small id="termsHelp" class="form-text text-muted"
>Automatically update the anonymized repository with the
latest commit of the repository. The repository is updated
once per hour maximum.</small
>
</div>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="page"
name="page"
ng-model="options.options.page"
/>
<label class="form-check-label" for="page">Github page</label>
<small id="termsHelp" class="form-text text-muted"
>Enable anonymized Github pages.</small
>
</div>
</div>
</div>
</div>
</div>
</div>
<h4>Plan</h4>
<small class="text-muted">The repositories are bill per hour.</small>
<div class="card-deck mb-3 text-center">
<div class="card mb-4 shadow-sm" ng-repeat="plan in plans">
<div class="card-header">
<h4 class="my-0 font-weight-normal" ng-bind="plan.name"></h4>
</div>
<div class="card-body">
<h1 class="card-title pricing-card-title">
{{plan.pricePerRepo | currency}}
<small class="text-muted"> / repository / month</small>
</h1>
<ul
class="list-unstyled mt-3 mb-4"
ng-bind-html="plan.description"
></ul>
<button
type="button"
class="btn btn-lg btn-block"
ng-class="{'btn-primary': options.plan.planID == plan.id}"
ng-click="options.plan.planID = plan.id"
>
Select
</button>
</div>
</div>
</div>
<div ng-if="plan.pricePerRepo > 0">
<h3>Billing</h3>
<!-- name -->
<div class="form-group">
<label for="billing_name">The name & last name</label>
<input
type="text"
class="form-control"
name="billing_name"
id="billing_name"
required
ng-class="{'is-invalid': conference.billing_name.$invalid}"
ng-model="options.billing.name"
placeholder="Name & last name"
/>
</div>
<!-- email -->
<div class="form-group">
<label for="email">Email</label>
<input
type="email"
class="form-control"
name="email"
id="email"
required
ng-class="{'is-invalid': conference.email.$invalid}"
ng-model="options.billing.email"
placeholder="Email"
/>
</div>
<!-- address -->
<div class="form-group">
<label for="inputAddress">Address</label>
<input
type="text"
class="form-control"
name="inputAddress"
id="inputAddress"
required
placeholder="1234 Main St"
ng-model="options.billing.address"
ng-class="{'is-invalid': conference.inputAddress.$invalid}"
/>
</div>
<div class="form-group">
<input
type="text"
class="form-control"
name="inputAddress2"
id="inputAddress2"
ng-model="options.billing.address2"
ng-class="{'is-invalid': conference.inputAddress2.$invalid}"
placeholder="Apartment, studio, or floor"
/>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<input
type="text"
class="form-control"
name="city"
id="city"
ng-model="options.billing.city"
required
ng-class="{'is-invalid': conference.city.$invalid}"
placeholder="City"
/>
</div>
<div class="form-group col-md-4">
<input
type="text"
class="form-control"
name="country"
id="country"
ng-model="options.billing.country"
required
ng-class="{'is-invalid': conference.country.$invalid}"
placeholder="Country"
/>
</div>
<div class="form-group col-md-2">
<input
type="text"
class="form-control"
name="zip"
id="zip"
ng-model="options.billing.zip"
required
ng-class="{'is-invalid': conference.zip.$invalid}"
placeholder="Zip"
/>
</div>
</div>
<!-- VAT -->
<div class="form-group">
<label for="vat">VAT Number</label>
<input
type="text"
class="form-control"
name="vat"
id="vat"
ng-class="{'is-invalid': conference.vat.$invalid}"
ng-model="options.billing.vat"
placeholder="VAT Number"
/>
</div>
</div>
<div
class="alert alert-danger"
role="alert"
ng-if="error"
ng-bind="error"
></div>
<div
class="alert alert-success"
role="alert"
ng-if="message"
ng-bind="message"
></div>
<button
id="send"
type="submit"
class="btn btn-primary"
ng-click="submit($event)"
>
{{editionMode ? "Update" : "Create"}}
</button>
</form>
</div>
+316 -1
View File
@@ -43,6 +43,26 @@ angular
controller: "statusController", controller: "statusController",
title: "Repository status - Anonymous GitHub", title: "Repository status - Anonymous GitHub",
}) })
.when("/conferences", {
templateUrl: "/partials/conferences.htm",
controller: "conferencesController",
title: "Conferences - Anonymous GitHub",
})
.when("/conference/new", {
templateUrl: "/partials/newConference.htm",
controller: "newConferenceController",
title: "Add a conference - Anonymous GitHub",
})
.when("/conference/:conferenceId/edit", {
templateUrl: "/partials/newConference.htm",
controller: "newConferenceController",
title: "Edit conference - Anonymous GitHub",
})
.when("/conference/:conferenceId", {
templateUrl: "/partials/conference.htm",
controller: "conferenceController",
title: "Conference - Anonymous GitHub",
})
.when("/faq", { .when("/faq", {
templateUrl: "/partials/faq.htm", templateUrl: "/partials/faq.htm",
controller: "faqController", controller: "faqController",
@@ -619,7 +639,7 @@ angular
body: `The repository ${repo.repoId} is going to be removed.`, body: `The repository ${repo.repoId} is going to be removed.`,
}; };
$scope.toasts.push(toast); $scope.toasts.push(toast);
$http.post(`/api/repo/${repo.repoId}/refresh`).then(() => { $http.post(`/api/repo/${repo.repoId}/refresh`).then(() => {
toast.title = `${repo.repoId} is refreshed.`; toast.title = `${repo.repoId} is refreshed.`;
toast.body = `The repository ${repo.repoId} is refreshed.`; toast.body = `The repository ${repo.repoId} is refreshed.`;
@@ -878,6 +898,35 @@ angular
$scope.readme = res.data; $scope.readme = res.data;
} }
function getConference() {
if (!$scope.conference) return;
$http.get("/api/conferences/" + $scope.conference).then(
(res) => {
$scope.conference_data = res.data;
$scope.conference_data.startDate = new Date(
$scope.conference_data.startDate
);
$scope.conference_data.endDate = new Date(
$scope.conference_data.endDate
);
$scope.options.expirationDate = new Date(
$scope.conference_data.endDate
);
$scope.options.expirationMode = "remove";
$scope.options.update = $scope.conference_data.options.update;
$scope.options.image = $scope.conference_data.options.image;
$scope.options.pdf = $scope.conference_data.options.pdf;
$scope.options.notebook = $scope.conference_data.options.notebook;
$scope.options.link = $scope.conference_data.options.link;
},
(err) => {
$scope.conference_data = null;
}
);
}
function anonymize() { function anonymize() {
const urlRegex = const urlRegex =
/<?\b((https?|ftp|file):\/\/)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]\b\/?>?/g; /<?\b((https?|ftp|file):\/\/)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]\b\/?>?/g;
@@ -950,6 +999,7 @@ angular
$scope.anonymize.repoUrl.$setValidity("used", true); $scope.anonymize.repoUrl.$setValidity("used", true);
$scope.anonymize.repoUrl.$setValidity("missing", true); $scope.anonymize.repoUrl.$setValidity("missing", true);
$scope.anonymize.repoUrl.$setValidity("access", true); $scope.anonymize.repoUrl.$setValidity("access", true);
$scope.anonymize.conference.$setValidity("activated", true);
$scope.anonymize.terms.$setValidity("format", true); $scope.anonymize.terms.$setValidity("format", true);
$scope.anonymize.terms.$setValidity("format", true); $scope.anonymize.terms.$setValidity("format", true);
} }
@@ -980,6 +1030,9 @@ angular
case "repo_not_accessible": case "repo_not_accessible":
$scope.anonymize.repoUrl.$setValidity("access", false); $scope.anonymize.repoUrl.$setValidity("access", false);
break; break;
case "conf_not_activated":
$scope.anonymize.conference.$setValidity("activated", false);
break;
default: default:
$scope.anonymize.$setValidity("error", false); $scope.anonymize.$setValidity("error", false);
break; break;
@@ -1039,6 +1092,9 @@ angular
}); });
}; };
$scope.$watch("conference", async (v) => {
getConference();
});
$scope.$watch("source.branch", async (v) => { $scope.$watch("source.branch", async (v) => {
const selected = $scope.branches.filter( const selected = $scope.branches.filter(
(f) => f.name == $scope.source.branch (f) => f.name == $scope.source.branch
@@ -1318,4 +1374,263 @@ angular
init(); init();
}, },
])
.controller("conferencesController", [
"$scope",
"$http",
"$location",
function ($scope, $http, $location) {
$scope.$watch("user.status", () => {
if ($scope.user == null) {
$location.url("/");
}
});
if ($scope.user == null) {
$location.url("/");
}
$scope.conferences = [];
$scope.search = "";
$scope.filters = {
status: { ready: true, expired: false, removed: false },
};
$scope.orderBy = "name";
$scope.removeConference = function (conf) {
if (
confirm(
`Are you sure that you want to remove the conference ${conf.name}? All the repositories linked to this conference will expire.`
)
) {
const toast = {
title: `Removing ${conf.name}...`,
date: new Date(),
body: `The conference ${conf.name} is going to be removed.`,
};
$scope.toasts.push(toast);
$http.delete(`/api/conferences/${conf.conferenceID}`).then(() => {
toast.title = `${conf.name} is removed.`;
toast.body = `The conference ${conf.name} is removed.`;
getConferences();
});
}
};
function getConferences() {
$http.get("/api/conferences/").then(
(res) => {
$scope.conferences = res.data || [];
},
(err) => {
console.error(err);
}
);
}
getConferences();
$scope.conferenceFilter = (conference) => {
if ($scope.filters.status[conference.status] == false) return false;
if ($scope.search.trim().length == 0) return true;
if (conference.name.indexOf($scope.search) > -1) return true;
if (conference.conferenceID.indexOf($scope.search) > -1) return true;
return false;
};
},
])
.controller("newConferenceController", [
"$scope",
"$http",
"$location",
"$routeParams",
function ($scope, $http, $location, $routeParams) {
$scope.$watch("user.status", () => {
if ($scope.user == null) {
$location.url("/");
}
});
if ($scope.user == null) {
$location.url("/");
}
$scope.plans = [];
$scope.editionMode = false;
function getConference() {
$http
.get("/api/conferences/" + $routeParams.conferenceId)
.then((res) => {
$scope.options = res.data;
});
}
if ($routeParams.conferenceId) {
$scope.editionMode = true;
getConference();
}
function getPlans() {
$http.get("/api/conferences/plans").then((res) => {
$scope.plans = res.data;
$scope.plan = $scope.plans.filter(
(f) => f.id == $scope.options.plan.planID
)[0];
});
}
getPlans();
const start = new Date();
start.setDate(1);
console.log(start);
start.setMonth(start.getMonth() + 1);
const end = new Date();
end.setMonth(start.getMonth() + 7, 0);
$scope.options = {
startDate: start,
endDate: end,
plan: {
planID: "free_conference",
},
options: {
link: true,
image: true,
pdf: true,
notebook: true,
update: true,
page: true,
},
};
$scope.plan = null;
$scope.$watch("options.plan.planID", () => {
console.log($scope.plans, $scope.options);
$scope.plan = $scope.plans.filter(
(f) => f.id == $scope.options.plan.planID
)[0];
});
function resetValidity() {
$scope.conference.name.$setValidity("required", true);
$scope.conference.conferenceID.$setValidity("pattern", true);
$scope.conference.conferenceID.$setValidity("required", true);
$scope.conference.conferenceID.$setValidity("used", true);
$scope.conference.startDate.$setValidity("required", true);
$scope.conference.startDate.$setValidity("invalid", true);
$scope.conference.endDate.$setValidity("required", true);
$scope.conference.endDate.$setValidity("invalid", true);
$scope.conference.$setValidity("error", true);
}
function displayErrorMessage(message) {
switch (message) {
case "conf_name_missing":
$scope.conference.name.$setValidity("required", false);
break;
case "conf_id_missing":
$scope.conference.conferenceID.$setValidity("required", false);
break;
case "conf_id_format":
$scope.conference.conferenceID.$setValidity("pattern", false);
break;
case "conf_id_used":
$scope.conference.conferenceID.$setValidity("used", false);
break;
case "conf_start_date_missing":
$scope.conference.startDate.$setValidity("required", false);
break;
case "conf_end_date_missing":
$scope.conference.endDate.$setValidity("required", false);
break;
case "conf_start_date_invalid":
$scope.conference.startDate.$setValidity("invalid", false);
break;
case "conf_end_date_invalid":
$scope.conference.endDate.$setValidity("invalid", false);
break;
default:
$scope.conference.$setValidity("error", false);
break;
}
}
$scope.submit = function () {
const toast = {
title: `Creating ${$scope.options.name}...`,
date: new Date(),
body: `The conference ${$scope.options.conferenceID} is in creation.`,
};
if ($scope.editionMode) {
toast.title = `Updating ${$scope.options.name}...`;
toast.body = `The conference '${$scope.options.conferenceID}' is updating.`;
}
$scope.toasts.push(toast);
resetValidity();
$http
.post(
"/api/conferences/" +
($scope.editionMode ? $scope.options.conferenceID : ""),
$scope.options
)
.then(
() => {
if (!$scope.editionMode) {
toast.title = `${$scope.options.name} created`;
toast.body = `The conference '${$scope.options.conferenceID}' is created.`;
} else {
toast.title = `${$scope.options.name} updated`;
toast.body = `The conference '${$scope.options.conferenceID}' is updated.`;
}
$location.url("/conference/" + $scope.options.conferenceID);
},
(error) => {
displayErrorMessage(error.data.error);
$scope.removeToast(toast);
}
);
};
},
])
.controller("conferenceController", [
"$scope",
"$http",
"$location",
"$routeParams",
function ($scope, $http, $location, $routeParams) {
$scope.$watch("user.status", () => {
if ($scope.user == null) {
$location.url("/");
}
});
if ($scope.user == null) {
$location.url("/");
}
$scope.conference = null;
$scope.search = "";
$scope.filters = {
status: { ready: true, expired: false, removed: false },
};
$scope.orderBy = "-anonymizeDate";
$scope.repoFiler = (repo) => {
if ($scope.filters.status[repo.status] == false) return false;
if ($scope.search.trim().length == 0) return true;
if (repo.source.fullName.indexOf($scope.search) > -1) return true;
if (repo.repoId.indexOf($scope.search) > -1) return true;
return false;
};
function getConference() {
$http
.get("/api/conferences/" + $routeParams.conferenceId)
.then((res) => {
$scope.conference = res.data;
});
}
getConference();
},
]); ]);
+113
View File
@@ -0,0 +1,113 @@
import AnonymizedRepositoryModel from "./database/anonymizedRepositories/anonymizedRepositories.model";
import { IConferenceDocument } from "./database/conference/conferences.types";
import Repository from "./Repository";
import { ConferenceStatus } from "./types";
export default class Conference {
private _data: IConferenceDocument;
private _repositories: Repository[] = null;
constructor(data: IConferenceDocument) {
this._data = data;
}
/**
* Update the status of the conference
* @param status the new status
* @param errorMessage a potential error message to display
*/
async updateStatus(status: ConferenceStatus, errorMessage?: string) {
this._data.status = status;
return this._data.save();
}
/**
* Check if the conference is expired
*/
isExpired() {
return this._data.endDate < new Date();
}
/**
* Expire the conference
*/
async expire() {
await this.updateStatus("expired");
await Promise.all(
(await this.repositories()).map(async (conf) => await conf.expire())
);
}
/**
* Remove the conference
*/
async remove() {
await this.updateStatus("removed");
await Promise.all(
(await this.repositories()).map(async (conf) => await conf.remove())
);
}
/**
* Returns the list of repositories of this conference
*
* @returns the list of repositories of this conference
*/
async repositories(): Promise<Repository[]> {
if (this._repositories) return this._repositories;
const repoIds = this._data.repositories
.filter((r) => !r.removeDate)
.map((r) => r.id)
.filter((f) => f);
this._repositories = (
await AnonymizedRepositoryModel.find({
_id: { $in: repoIds },
})
).map((r) => new Repository(r));
return this._repositories;
}
get ownerIDs() {
return this._data?.owners;
}
get quota() {
return this._data.plan.quota;
}
get status() {
return this._data.status;
}
toJSON(opt?: { billing: boolean }): any {
const pricePerHourPerRepo = this._data.plan.pricePerRepository / 30;
let price = 0;
const today =
new Date() > this._data.endDate ? this._data.endDate : new Date();
this._data.repositories.forEach((r) => {
const removeDate =
r.removeDate && r.removeDate < today ? r.removeDate : today;
price +=
(Math.max(removeDate.getTime() - r.addDate.getTime(), 0) /
1000 /
60 /
60 /
24) *
pricePerHourPerRepo;
});
return {
conferenceID: this._data.conferenceID,
name: this._data.name,
url: this._data.url,
startDate: this._data.startDate,
endDate: this._data.endDate,
status: this._data.status,
billing: this._data.billing,
options: this._data.options,
plan: this._data.plan,
price,
nbRepositories: this._data.repositories.filter((r) => !r.removeDate)
.length,
};
}
}
+17
View File
@@ -11,6 +11,8 @@ import UserModel from "./database/users/users.model";
import { IAnonymizedRepositoryDocument } from "./database/anonymizedRepositories/anonymizedRepositories.types"; import { IAnonymizedRepositoryDocument } from "./database/anonymizedRepositories/anonymizedRepositories.types";
import { anonymizeStream } from "./anonymize-utils"; import { anonymizeStream } from "./anonymize-utils";
import GitHubBase from "./source/GitHubBase"; import GitHubBase from "./source/GitHubBase";
import Conference from "./Conference";
import ConferenceModel from "./database/conference/conferences.model";
export default class Repository { export default class Repository {
private _model: IAnonymizedRepositoryDocument; private _model: IAnonymizedRepositoryDocument;
@@ -247,6 +249,21 @@ export default class Repository {
return this._model.size; return this._model.size;
} }
/**
* Returns the conference of the repository
*
* @returns conference of the repository
*/
async conference(): Promise<Conference | null> {
if (!this._model.conference) {
return null;
}
const conference = await ConferenceModel.findOne({
conferenceID: this._model.conference,
});
return new Conference(conference);
}
/***** Getters ********/ /***** Getters ********/
get repoId() { get repoId() {
@@ -0,0 +1,12 @@
import * as mongoose from "mongoose";
const { model } = mongoose;
import { IConferenceDocument, IConferenceModel } from "./conferences.types";
import ConferenceSchema from "./conferences.schema";
const ConferenceModel = model<IConferenceDocument>(
"Conference",
ConferenceSchema
) as IConferenceModel;
export default ConferenceModel;
@@ -0,0 +1,59 @@
import * as mongoose from "mongoose";
const { Schema } = mongoose;
const RepositorySchema = new Schema({
name: String,
conferenceID: {
type: String,
index: { unique: true },
},
url: String,
startDate: Date,
endDate: Date,
status: String,
owners: { type: [mongoose.Schema.Types.ObjectId] },
repositories: {
type: [
{
id: { type: mongoose.Schema.Types.ObjectId },
addDate: { type: Date },
removeDate: { type: Date },
},
],
},
options: {
expirationMode: String,
expirationDate: Date,
update: Boolean,
image: Boolean,
pdf: Boolean,
notebook: Boolean,
link: Boolean,
page: Boolean,
},
dateOfEntry: {
type: Date,
default: new Date(),
},
plan: {
planID: String,
pricePerRepository: Number,
quota: {
repository: Number,
size: Number,
file: Number,
},
},
billing: {
name: String,
email: String,
address: String,
address2: String,
city: String,
zip: String,
country: String,
vat: String,
},
});
export default RepositorySchema;
@@ -0,0 +1,49 @@
import * as mongoose from "mongoose";
import { ConferenceStatus } from "../../types";
export interface IConference {
name: string;
conferenceID: string;
startDate: Date;
endDate: Date;
url: string;
status: ConferenceStatus;
owners: string[];
repositories: {
id: string;
addDate: Date;
removeDate?: Date;
}[];
options: {
expirationMode: "never" | "redirect" | "remove";
expirationDate?: Date;
update: boolean;
image: boolean;
pdf: boolean;
notebook: boolean;
link: boolean;
page: boolean;
};
plan: {
planID: string;
pricePerRepository: number;
quota: {
repository: number;
size: number;
file: number;
};
};
billing?: {
name: string;
email: string;
address: string;
address2?: string;
city: string;
zip: string;
country: string;
vat?: string;
};
}
export interface IConferenceDocument extends IConference, mongoose.Document {}
export interface IConferenceModel extends mongoose.Model<IConferenceDocument> {}
+199
View File
@@ -0,0 +1,199 @@
import * as express from "express";
import config from "../../config";
import Conference from "../Conference";
import AnonymizedRepositoryModel from "../database/anonymizedRepositories/anonymizedRepositories.model";
import ConferenceModel from "../database/conference/conferences.model";
import Repository from "../Repository";
import { ensureAuthenticated } from "./connection";
import { handleError, getUser } from "./route-utils";
const router = express.Router();
// user needs to be connected for all user API
router.use(ensureAuthenticated);
const plans = [
{
id: "free_conference",
name: "Free",
pricePerRepo: 0,
storagePerRepo: -1,
description: `<li><strong>Quota is deducted from user account</strong></li>
<li>No-download</li>
<li>Conference dashboard</li>`,
},
{
id: "premium_conference",
name: "Premium",
pricePerRepo: 0.5,
storagePerRepo: 500 * 8 * 1024,
description: `<li>500Mo / repository</li>
<li>Repository download</li>
<li>Conference dashboard</li>`,
},
{
id: "unlimited_conference",
name: "Unlimited",
pricePerRepo: 3,
storagePerRepo: 0,
description: `<li><strong>Unlimited</strong> repository size</li>
<li>Repository download</li>
<li>Conference dashboard</li>`,
},
];
router.get("/plans", async (req: express.Request, res: express.Response) => {
res.json(plans);
});
router.get("/", async (req: express.Request, res: express.Response) => {
try {
const user = await getUser(req);
const conferences = await Promise.all(
(
await ConferenceModel.find({
owners: { $in: user.model.id },
})
).map(async (data) => {
const conf = new Conference(data);
if (data.endDate < new Date() && data.status == "ready") {
await conf.updateStatus("expired");
}
return conf;
})
);
res.json(conferences.map((conf) => conf.toJSON()));
} catch (error) {
handleError(error, res);
}
});
function validateConferenceForm(conf) {
if (!conf.name) throw new Error("conf_name_missing");
if (!conf.conferenceID) throw new Error("conf_id_missing");
if (!conf.startDate) throw new Error("conf_start_date_missing");
if (!conf.endDate) throw new Error("conf_end_date_missing");
if (new Date(conf.startDate) > new Date(conf.endDate))
throw new Error("conf_start_date_invalid");
if (new Date() > new Date(conf.endDate))
throw new Error("conf_end_date_invalid");
if (plans.filter((p) => p.id == conf.plan.planID).length != 1)
throw new Error("invalid_plan");
const plan = plans.filter((p) => p.id == conf.plan.planID)[0];
if (plan.pricePerRepo > 0) {
const billing = conf.billing;
if (!billing) throw new Error("billing_missing");
if (!billing.name) throw new Error("billing_name_missing");
if (!billing.email) throw new Error("billing_email_missing");
if (!billing.address) throw new Error("billing_address_missing");
if (!billing.city) throw new Error("billing_city_missing");
if (!billing.zip) throw new Error("billing_zip_missing");
if (!billing.country) throw new Error("billing_country_missing");
}
}
router.post(
"/:conferenceID?",
async (req: express.Request, res: express.Response) => {
try {
const user = await getUser(req);
let model = new ConferenceModel();
if (req.params.conferenceID) {
model = await ConferenceModel.findOne({
conferenceID: req.params.conferenceID,
});
if (model.owners.indexOf(user.model.id) == -1)
throw new Error("not_authorized");
}
validateConferenceForm(req.body);
model.name = req.body.name;
model.startDate = new Date(req.body.startDate);
model.endDate = new Date(req.body.endDate);
model.status = "ready";
model.url = req.body.url;
model.repositories = [];
model.options = req.body.options;
if (!req.params.conferenceID) {
model.owners.push(user.model.id);
model.conferenceID = req.body.conferenceID;
model.plan = {
planID: req.body.plan.planID,
pricePerRepository: plans.filter(
(p) => p.id == req.body.plan.planID
)[0].pricePerRepo,
quota: {
size: plans.filter((p) => p.id == req.body.plan.planID)[0]
.storagePerRepo,
file: 0,
repository: 0,
},
};
if (req.body.billing)
model.billing = {
name: req.body.billing.name,
email: req.body.billing.email,
address: req.body.billing.address,
address2: req.body.billing.address2,
city: req.body.billing.city,
zip: req.body.billing.zip,
country: req.body.billing.country,
vat: req.body.billing.vat,
};
}
await model.save();
res.send("ok");
} catch (error) {
if (error.message?.indexOf(" duplicate key") > -1) {
return handleError(new Error("conf_id_used"), res);
}
handleError(error, res);
}
}
);
router.get(
"/:conferenceID",
async (req: express.Request, res: express.Response) => {
try {
const user = await getUser(req);
const data = await ConferenceModel.findOne({
conferenceID: req.params.conferenceID,
});
if (!data) throw new Error("conf_not_found");
const conference = new Conference(data);
if (conference.ownerIDs.indexOf(user.model.id) == -1)
throw new Error("not_authorized");
const o: any = conference.toJSON();
o.repositories = (await conference.repositories()).map((r) => r.toJSON());
res.json(o);
} catch (error) {
handleError(error, res);
}
}
);
router.delete(
"/:conferenceID",
async (req: express.Request, res: express.Response) => {
try {
const user = await getUser(req);
const data = await ConferenceModel.findOne({
conferenceID: req.params.conferenceID,
});
if (!data) throw new Error("conf_not_found");
const conference = new Conference(data);
if (conference.ownerIDs.indexOf(user.model.id) == -1)
throw new Error("not_authorized");
await conference.remove();
res.send("ok");
} catch (error) {
handleError(error, res);
}
}
);
export default router;
+2
View File
@@ -1,5 +1,6 @@
import repositoryPrivate from "./repository-private"; import repositoryPrivate from "./repository-private";
import repositoryPublic from "./repository-public"; import repositoryPublic from "./repository-public";
import conference from "./conference";
import file from "./file"; import file from "./file";
import webview from "./webview"; import webview from "./webview";
import user from "./user"; import user from "./user";
@@ -12,4 +13,5 @@ export default {
webview, webview,
user, user,
option, option,
conference
}; };
+66 -5
View File
@@ -10,6 +10,7 @@ import AnonymizedRepositoryModel from "../database/anonymizedRepositories/anonym
import config from "../../config"; import config from "../../config";
import { IAnonymizedRepositoryDocument } from "../database/anonymizedRepositories/anonymizedRepositories.types"; import { IAnonymizedRepositoryDocument } from "../database/anonymizedRepositories/anonymizedRepositories.types";
import Repository from "../Repository"; import Repository from "../Repository";
import ConferenceModel from "../database/conference/conferences.model";
const router = express.Router(); const router = express.Router();
@@ -61,7 +62,7 @@ router.post(
async (req: express.Request, res: express.Response) => { async (req: express.Request, res: express.Response) => {
try { try {
const repo = await getRepo(req, res, { nocheck: true }); const repo = await getRepo(req, res, { nocheck: true });
if (!repo) throw new Error("repo_not_found"); if (!repo) return;
const user = await getUser(req); const user = await getUser(req);
if (repo.owner.username != user.username) { if (repo.owner.username != user.username) {
@@ -163,7 +164,7 @@ router.get(
router.get("/:repoId/", async (req: express.Request, res: express.Response) => { router.get("/:repoId/", async (req: express.Request, res: express.Response) => {
try { try {
const repo = await getRepo(req, res, { nocheck: true }); const repo = await getRepo(req, res, { nocheck: true });
if (!repo) throw new Error("repo_not_found"); if (!repo) return;
const user = await getUser(req); const user = await getUser(req);
if (user.username != repo.model.owner) { if (user.username != repo.model.owner) {
@@ -215,7 +216,6 @@ function updateRepoModel(
} }
model.source.commit = repoUpdate.source.commit; model.source.commit = repoUpdate.source.commit;
model.source.branch = repoUpdate.source.branch; model.source.branch = repoUpdate.source.branch;
model.conference = repoUpdate.conference;
model.options = { model.options = {
terms: repoUpdate.terms, terms: repoUpdate.terms,
expirationMode: repoUpdate.options.expirationMode, expirationMode: repoUpdate.options.expirationMode,
@@ -238,7 +238,7 @@ router.post(
async (req: express.Request, res: express.Response) => { async (req: express.Request, res: express.Response) => {
try { try {
const repo = await getRepo(req, res, { nocheck: true }); const repo = await getRepo(req, res, { nocheck: true });
if (!repo) throw new Error("repo_not_found"); if (!repo) return;
const user = await getUser(req); const user = await getUser(req);
if (repo.owner.username != user.username) { if (repo.owner.username != user.username) {
@@ -257,6 +257,48 @@ router.post(
updateRepoModel(repo.model, repoUpdate); updateRepoModel(repo.model, repoUpdate);
async function removeRepoFromConference(conferenceID) {
const conf = await ConferenceModel.findOne({
conferenceID,
});
if (conf) {
const r = conf.repositories.filter((r) => r.id == repo.model.id);
if (r.length == 1) r[0].removeDate = new Date();
await conf.save();
}
}
if (!repoUpdate.conference) {
// remove conference
if (repo.model.conference) {
await removeRepoFromConference(repo.model.conference);
}
} else if (repoUpdate.conference != repo.model.conference) {
// update/add conference
const conf = await ConferenceModel.findOne({
conferenceID: repoUpdate.conference,
});
if (conf) {
if (new Date() < conf.startDate || new Date() > conf.endDate || conf.status !== "ready") {
throw new Error("conf_not_activated");
}
const f = conf.repositories.filter((r) => r.id == repo.model.id);
if (f.length) {
// the repository already referenced the conference
f[0].addDate = new Date();
f[0].removeDate = null;
} else {
conf.repositories.push({
id: repo.model.id,
addDate: new Date(),
});
}
if (repo.model.conference) {
await removeRepoFromConference(repo.model.conference);
}
await conf.save();
}
}
repo.model.conference = repoUpdate.conference;
await repo.updateStatus("preparing"); await repo.updateStatus("preparing");
res.send("ok"); res.send("ok");
new Repository(repo.model).anonymize(); new Repository(repo.model).anonymize();
@@ -285,7 +327,7 @@ router.post("/", async (req: express.Request, res: express.Response) => {
repo.repoId = repoUpdate.repoId; repo.repoId = repoUpdate.repoId;
repo.anonymizeDate = new Date(); repo.anonymizeDate = new Date();
repo.owner = user.username; repo.owner = user.username;
updateRepoModel(repo, repoUpdate); updateRepoModel(repo, repoUpdate);
repo.source.accessToken = user.accessToken; repo.source.accessToken = user.accessToken;
repo.source.repositoryId = repository.model.id; repo.source.repositoryId = repository.model.id;
@@ -297,8 +339,27 @@ router.post("/", async (req: express.Request, res: express.Response) => {
return res.status(500).send({ error: "invalid_mode" }); return res.status(500).send({ error: "invalid_mode" });
} }
} }
repo.conference = repoUpdate.conference;
await repo.save(); await repo.save();
if (repoUpdate.conference) {
const conf = await ConferenceModel.findOne({
conferenceID: repoUpdate.conference,
});
if (conf) {
if (new Date() < conf.startDate || new Date() > conf.endDate || conf.status !== "ready") {
await repo.remove()
throw new Error("conf_not_activated");
}
conf.repositories.push({
id: repo.id,
addDate: new Date(),
});
await conf.save();
}
}
res.send("ok"); res.send("ok");
new Repository(repo).anonymize(); new Repository(repo).anonymize();
} catch (error) { } catch (error) {
+12 -3
View File
@@ -48,7 +48,7 @@ router.get(
async (req: express.Request, res: express.Response) => { async (req: express.Request, res: express.Response) => {
try { try {
const repo = await getRepo(req, res, { nocheck: true }); const repo = await getRepo(req, res, { nocheck: true });
if (!repo) throw new Error("repo_not_found"); if (!repo) return;
let redirectURL = null; let redirectURL = null;
if ( if (
repo.status == "expired" && repo.status == "expired" &&
@@ -62,10 +62,19 @@ router.get(
await repo.updateIfNeeded(); await repo.updateIfNeeded();
let download = false;
const conference = await repo.conference();
if (conference) {
console.log(conference.quota)
download =
conference.quota.size > -1 &&
!!config.ENABLE_DOWNLOAD &&
repo.source.type == "GitHubDownload";
}
res.json({ res.json({
url: redirectURL, url: redirectURL,
download: download,
!!config.ENABLE_DOWNLOAD && repo.source.type == "GitHubDownload",
}); });
} catch (error) { } catch (error) {
handleError(error, res); handleError(error, res);
+17
View File
@@ -0,0 +1,17 @@
import * as schedule from "node-schedule";
import Conference from "./Conference";
import ConferenceModel from "./database/conference/conferences.model";
export function conferenceStatusCheck() {
// check every 6 hours the status of the conference
const job = schedule.scheduleJob("0 */6 * * *", async () => {
(await ConferenceModel.find({ status: { $eq: "ready" } })).forEach(
async (data) => {
const conference = new Conference(data);
if (conference.isExpired() && conference.status == "ready") {
await conference.expire();
}
}
);
});
}
+5
View File
@@ -12,6 +12,7 @@ import * as passport from "passport";
import * as connection from "./routes/connection"; import * as connection from "./routes/connection";
import router from "./routes"; import router from "./routes";
import AnonymizedRepositoryModel from "./database/anonymizedRepositories/anonymizedRepositories.model"; import AnonymizedRepositoryModel from "./database/anonymizedRepositories/anonymizedRepositories.model";
import { conferenceStatusCheck } from "./schedule";
function indexResponse(req: express.Request, res: express.Response) { function indexResponse(req: express.Request, res: express.Response) {
if ( if (
@@ -58,6 +59,7 @@ export default async function start() {
// api routes // api routes
app.use("/api/options", rate, router.option); app.use("/api/options", rate, router.option);
app.use("/api/conferences", rate, router.conference);
app.use("/api/user", rate, router.user); app.use("/api/user", rate, router.user);
app.use("/api/repo", rate, router.repositoryPublic); app.use("/api/repo", rate, router.repositoryPublic);
app.use("/api/repo", rate, router.file); app.use("/api/repo", rate, router.file);
@@ -96,6 +98,9 @@ export default async function start() {
app.get("*", indexResponse); app.get("*", indexResponse);
// start schedules
conferenceStatusCheck();
await db.connect(); await db.connect();
app.listen(config.PORT); app.listen(config.PORT);
console.log("Database connected and Server started on port: " + config.PORT); console.log("Database connected and Server started on port: " + config.PORT);
+2
View File
@@ -117,6 +117,8 @@ export type RepositoryStatus =
| "expired" | "expired"
| "removed"; | "removed";
export type ConferenceStatus = "ready" | "expired" | "removed";
export type SourceStatus = "available" | "unavailable"; export type SourceStatus = "available" | "unavailable";
export type TreeElement = Tree | TreeFile; export type TreeElement = Tree | TreeFile;