diff --git a/ketr.ketran/.gitignore b/ketr.ketran/.gitignore new file mode 100644 index 0000000..fca2ec9 --- /dev/null +++ b/ketr.ketran/.gitignore @@ -0,0 +1,6 @@ +!.gitignore +!package.json +node_modules +config/local.json +package-lock.json +*.log diff --git a/ketr.ketran/README.md b/ketr.ketran/README.md new file mode 100644 index 0000000..76d2136 --- /dev/null +++ b/ketr.ketran/README.md @@ -0,0 +1,74 @@ +# Ketr.Ketran REST API + +## POST /api/v1/game + +### Request + +```json +{} +```` + +### Response + +```json +{ + gameId: id + gameState: { + tiles: [] + + } +} +``` + +# Configuring / installing + + +## Build +```bash +git clone ... +cd server +npm install +``` + +## Install +```bash +sudo cp ketr.ketran /etc/logrotate.d/ +sudo cp ketr.ketran.service /etc/systemd/system/ +sudo systemctl daemon-reload +``` + +Install the following into your nginx server configuration: + +```nginx + location ~ /ketr.ketran/api/.* { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_set_header X-NginX-Proxy true; + proxy_pass_header Set-Cookie; + proxy_pass_header P3P; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_pass http://localhost:8930; + } +``` + +Add security tokens in ketr.ketran/config/local.json: + +```bash +cat << EOF > config/local.json +{ + "tokens": [ { + "$(whoami)": "$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo;)" + } ] +} +EOF +``` + +## Launch +```bash +sudo systemctl start ketr.ketran +``` + diff --git a/ketr.ketran/config/default.json b/ketr.ketran/config/default.json new file mode 100755 index 0000000..95da1ca --- /dev/null +++ b/ketr.ketran/config/default.json @@ -0,0 +1,6 @@ +{ + "http": { + "base": "ketr.ketran", + "port": 8930 + } +} diff --git a/ketr.ketran/config/production.json b/ketr.ketran/config/production.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/ketr.ketran/config/production.json @@ -0,0 +1 @@ +{} diff --git a/ketr.ketran/ketr.ketran b/ketr.ketran/ketr.ketran new file mode 100644 index 0000000..007f9b6 --- /dev/null +++ b/ketr.ketran/ketr.ketran @@ -0,0 +1,11 @@ +/var/log/osgc-aptly-rest.log +{ + rotate 4 + weekly + missingok + notifempty + compress + postrotate + /usr/lib/rsyslog/rsyslog-rotate + endscript +} diff --git a/ketr.ketran/ketr.ketran.service b/ketr.ketran/ketr.ketran.service new file mode 100755 index 0000000..b4697c0 --- /dev/null +++ b/ketr.ketran/ketr.ketran.service @@ -0,0 +1,19 @@ +[Unit] +Description=OSGC-Aptly REST API +Requires= +After=networking.service + +[Service] +Environment="HTTP_PROXY=http://proxy-chain.intel.com:911/" "NO_PROXY=localhost,127.0.0.1,.intel.com" +ExecStart=/usr/bin/npm run backend +WorkingDirectory=/home/jketreno/docker/osgc-aptly/osgc-aptly-rest +Restart=always +# Restart service after 10 seconds if node service crashes +RestartSec=10 +StandardOutput=syslog +StandardError=syslog +SyslogIdentifier=osgc-aptly-rest + +[Install] +WantedBy=multi-user.target + diff --git a/ketr.ketran/manifest.json b/ketr.ketran/manifest.json new file mode 100644 index 0000000..9a86cff --- /dev/null +++ b/ketr.ketran/manifest.json @@ -0,0 +1,569 @@ +[ + { + "directory": true, + "date_modified": "2020-03-30T18:29:16+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux", + "size": 4096, + "path": "linux" + }, + { + "directory": true, + "date_modified": "2020-03-30T18:29:16+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu", + "size": 4096, + "path": "linux/ubuntu" + }, + { + "directory": true, + "date_modified": "2020-03-30T18:29:23+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10", + "size": 8192, + "path": "linux/ubuntu/19.10" + }, + { + "directory": false, + "date_modified": "2020-03-30T18:15:08+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/intel-cmc-2.0+embargo-169.u19.10-release.x86_64.deb", + "size": 31039028, + "path": "linux/ubuntu/19.10/intel-cmc-2.0+embargo-169.u19.10-release.x86_64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:22:21+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/intel-gpu-tools-dbg_1.24+embargo169_amd64.deb", + "size": 6465620, + "path": "linux/ubuntu/19.10/intel-gpu-tools-dbg_1.24+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:22:21+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/intel-gpu-tools_1.24+embargo169_amd64.deb", + "size": 1955740, + "path": "linux/ubuntu/19.10/intel-gpu-tools_1.24+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T18:13:12+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/intel-level-zero-gpu_0.8.016262+embargo169_amd64.deb", + "size": 1107520, + "path": "linux/ubuntu/19.10/intel-level-zero-gpu_0.8.016262+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:35:29+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/intel-media-va-driver-non-free_20.1~pre+embargo169_amd64.deb", + "size": 6244784, + "path": "linux/ubuntu/19.10/intel-media-va-driver-non-free_20.1~pre+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T18:18:25+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/intel-metrics-discovery_1.5.114+embargo169_amd64.deb", + "size": 715152, + "path": "linux/ubuntu/19.10/intel-metrics-discovery_1.5.114+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T18:19:21+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/intel-metrics-library_1.0.1+embargo169_amd64.deb", + "size": 157448, + "path": "linux/ubuntu/19.10/intel-metrics-library_1.0.1+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:57:35+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/intel-opencl-icd_016320+embargo169_amd64.deb", + "size": 1223400, + "path": "linux/ubuntu/19.10/intel-opencl-icd_016320+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:57:52+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/level-zero-dev_0.91.7+embargo169_amd64.deb", + "size": 90364, + "path": "linux/ubuntu/19.10/level-zero-dev_0.91.7+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:57:52+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/level-zero_0.91.7+embargo169_amd64.deb", + "size": 49700, + "path": "linux/ubuntu/19.10/level-zero_0.91.7+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:20:36+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libdrm-amdgpu1_2.4.100+embargo169_amd64.deb", + "size": 28272, + "path": "linux/ubuntu/19.10/libdrm-amdgpu1_2.4.100+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:20:36+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libdrm-common_2.4.100+embargo169_all.deb", + "size": 14208, + "path": "linux/ubuntu/19.10/libdrm-common_2.4.100+embargo169_all.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:20:36+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libdrm-dev_2.4.100+embargo169_amd64.deb", + "size": 120324, + "path": "linux/ubuntu/19.10/libdrm-dev_2.4.100+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:20:36+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libdrm-intel1_2.4.100+embargo169_amd64.deb", + "size": 70724, + "path": "linux/ubuntu/19.10/libdrm-intel1_2.4.100+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:20:36+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libdrm-nouveau2_2.4.100+embargo169_amd64.deb", + "size": 26316, + "path": "linux/ubuntu/19.10/libdrm-nouveau2_2.4.100+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:20:36+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libdrm-radeon1_2.4.100+embargo169_amd64.deb", + "size": 29580, + "path": "linux/ubuntu/19.10/libdrm-radeon1_2.4.100+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:20:36+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libdrm2_2.4.100+embargo169_amd64.deb", + "size": 41172, + "path": "linux/ubuntu/19.10/libdrm2_2.4.100+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libegl-mesa0_20.1.0-devel+embargo169_amd64.deb", + "size": 139724, + "path": "linux/ubuntu/19.10/libegl-mesa0_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libegl1-mesa-dev_20.1.0-devel+embargo169_amd64.deb", + "size": 51120, + "path": "linux/ubuntu/19.10/libegl1-mesa-dev_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libegl1-mesa_20.1.0-devel+embargo169_amd64.deb", + "size": 49392, + "path": "linux/ubuntu/19.10/libegl1-mesa_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libgbm-dev_20.1.0-devel+embargo169_amd64.deb", + "size": 52940, + "path": "linux/ubuntu/19.10/libgbm-dev_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libgbm1_20.1.0-devel+embargo169_amd64.deb", + "size": 71004, + "path": "linux/ubuntu/19.10/libgbm1_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libgl1-mesa-dev_20.1.0-devel+embargo169_amd64.deb", + "size": 49408, + "path": "linux/ubuntu/19.10/libgl1-mesa-dev_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libgl1-mesa-dri_20.1.0-devel+embargo169_amd64.deb", + "size": 9929788, + "path": "linux/ubuntu/19.10/libgl1-mesa-dri_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libgl1-mesa-glx_20.1.0-devel+embargo169_amd64.deb", + "size": 49412, + "path": "linux/ubuntu/19.10/libgl1-mesa-glx_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libglapi-mesa_20.1.0-devel+embargo169_amd64.deb", + "size": 69888, + "path": "linux/ubuntu/19.10/libglapi-mesa_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libgles2-mesa-dev_20.1.0-devel+embargo169_amd64.deb", + "size": 49416, + "path": "linux/ubuntu/19.10/libgles2-mesa-dev_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libgles2-mesa_20.1.0-devel+embargo169_amd64.deb", + "size": 49400, + "path": "linux/ubuntu/19.10/libgles2-mesa_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libglx-mesa0_20.1.0-devel+embargo169_amd64.deb", + "size": 182580, + "path": "linux/ubuntu/19.10/libglx-mesa0_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:48:07+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libigc-dev_8020561+embargo169_amd64.deb", + "size": 2376, + "path": "linux/ubuntu/19.10/libigc-dev_8020561+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:48:07+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libigc-tools_8020561+embargo169_amd64.deb", + "size": 1830108, + "path": "linux/ubuntu/19.10/libigc-tools_8020561+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:48:07+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libigc1_8020561+embargo169_amd64.deb", + "size": 11786756, + "path": "linux/ubuntu/19.10/libigc1_8020561+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:48:07+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libigdfcl-dev_8020561+embargo169_amd64.deb", + "size": 125484, + "path": "linux/ubuntu/19.10/libigdfcl-dev_8020561+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:48:07+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libigdfcl1_8020561+embargo169_amd64.deb", + "size": 18982892, + "path": "linux/ubuntu/19.10/libigdfcl1_8020561+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:45+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libigdgmm-dev_8020561+embargo169_amd64.deb", + "size": 1126848, + "path": "linux/ubuntu/19.10/libigdgmm-dev_8020561+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:45+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libigdgmm6_8020561+embargo169_amd64.deb", + "size": 127364, + "path": "linux/ubuntu/19.10/libigdgmm6_8020561+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:35:29+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libigfxcmrt-dev_20.1~pre+embargo169_amd64.deb", + "size": 69096, + "path": "linux/ubuntu/19.10/libigfxcmrt-dev_20.1~pre+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:35:29+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libigfxcmrt7_20.1~pre+embargo169_amd64.deb", + "size": 30668, + "path": "linux/ubuntu/19.10/libigfxcmrt7_20.1~pre+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T18:17:58+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libmfx-dev_20.1~pre+embargo169_amd64.deb", + "size": 39952, + "path": "linux/ubuntu/19.10/libmfx-dev_20.1~pre+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T18:17:58+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libmfx-tools_20.1~pre+embargo169_amd64.deb", + "size": 1473148, + "path": "linux/ubuntu/19.10/libmfx-tools_20.1~pre+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T18:17:58+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libmfx1_20.1~pre+embargo169_amd64.deb", + "size": 2833028, + "path": "linux/ubuntu/19.10/libmfx1_20.1~pre+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libosmesa6-dev_20.1.0-devel+embargo169_amd64.deb", + "size": 52776, + "path": "linux/ubuntu/19.10/libosmesa6-dev_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libosmesa6_20.1.0-devel+embargo169_amd64.deb", + "size": 2741820, + "path": "linux/ubuntu/19.10/libosmesa6_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:23:06+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libva-dev_2.7.0.1+embargo169_amd64.deb", + "size": 104500, + "path": "linux/ubuntu/19.10/libva-dev_2.7.0.1+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:23:06+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libva-drm2_2.7.0.1+embargo169_amd64.deb", + "size": 17760, + "path": "linux/ubuntu/19.10/libva-drm2_2.7.0.1+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:23:06+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libva-glx2_2.7.0.1+embargo169_amd64.deb", + "size": 21344, + "path": "linux/ubuntu/19.10/libva-glx2_2.7.0.1+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:23:06+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libva-wayland2_2.7.0.1+embargo169_amd64.deb", + "size": 19664, + "path": "linux/ubuntu/19.10/libva-wayland2_2.7.0.1+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:23:06+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libva-x11-2_2.7.0.1+embargo169_amd64.deb", + "size": 22392, + "path": "linux/ubuntu/19.10/libva-x11-2_2.7.0.1+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:23:06+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libva2_2.7.0.1+embargo169_amd64.deb", + "size": 60736, + "path": "linux/ubuntu/19.10/libva2_2.7.0.1+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libwayland-egl1-mesa_20.1.0-devel+embargo169_amd64.deb", + "size": 49416, + "path": "linux/ubuntu/19.10/libwayland-egl1-mesa_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libxatracker-dev_20.1.0-devel+embargo169_amd64.deb", + "size": 52896, + "path": "linux/ubuntu/19.10/libxatracker-dev_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/libxatracker2_20.1.0-devel+embargo169_amd64.deb", + "size": 1644200, + "path": "linux/ubuntu/19.10/libxatracker2_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:20:20+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/linux-headers-5.4.17-169+_5.4.17-169+-1_amd64.deb", + "size": 11420780, + "path": "linux/ubuntu/19.10/linux-headers-5.4.17-169+_5.4.17-169+-1_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T16:55:17+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/linux-i915-firmware_2020.12+embargo169.deb", + "size": 4758832, + "path": "linux/ubuntu/19.10/linux-i915-firmware_2020.12+embargo169.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:20:21+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/linux-image-5.4.17-169+-dbg_5.4.17-169+-1_amd64.deb", + "size": 903956216, + "path": "linux/ubuntu/19.10/linux-image-5.4.17-169+-dbg_5.4.17-169+-1_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:20:21+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/linux-image-5.4.17-169+_5.4.17-169+-1_amd64.deb", + "size": 60596400, + "path": "linux/ubuntu/19.10/linux-image-5.4.17-169+_5.4.17-169+-1_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:20:21+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/linux-libc-dev_5.4.17-169+-1_amd64.deb", + "size": 1069796, + "path": "linux/ubuntu/19.10/linux-libc-dev_5.4.17-169+-1_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T16:55:07+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/manifest.yml", + "size": 5595, + "path": "linux/ubuntu/19.10/manifest.yml" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/mesa-common-dev_20.1.0-devel+embargo169_amd64.deb", + "size": 677236, + "path": "linux/ubuntu/19.10/mesa-common-dev_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/mesa-opencl-icd_20.1.0-devel+embargo169_amd64.deb", + "size": 10299076, + "path": "linux/ubuntu/19.10/mesa-opencl-icd_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/mesa-va-drivers_20.1.0-devel+embargo169_amd64.deb", + "size": 2684040, + "path": "linux/ubuntu/19.10/mesa-va-drivers_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/mesa-vdpau-drivers_20.1.0-devel+embargo169_amd64.deb", + "size": 2810196, + "path": "linux/ubuntu/19.10/mesa-vdpau-drivers_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:27:11+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/mesa-vulkan-drivers_20.1.0-devel+embargo169_amd64.deb", + "size": 3587736, + "path": "linux/ubuntu/19.10/mesa-vulkan-drivers_20.1.0-devel+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:23:06+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/va-driver-all_2.7.0.1+embargo169_amd64.deb", + "size": 13948, + "path": "linux/ubuntu/19.10/va-driver-all_2.7.0.1+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T17:23:06+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/linux/ubuntu/19.10/vainfo_2.6.0.0+embargo169_amd64.deb", + "size": 18908, + "path": "linux/ubuntu/19.10/vainfo_2.6.0.0+embargo169_amd64.deb" + }, + { + "directory": true, + "date_modified": "2020-03-30T18:29:26+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/utils", + "size": 4096, + "path": "utils" + }, + { + "directory": true, + "date_modified": "2020-03-30T18:29:26+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/utils/ubuntu", + "size": 4096, + "path": "utils/ubuntu" + }, + { + "directory": true, + "date_modified": "2020-03-30T18:29:27+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/utils/ubuntu/19.10", + "size": 4096, + "path": "utils/ubuntu/19.10" + }, + { + "directory": false, + "date_modified": "2020-03-30T18:29:15+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/utils/ubuntu/19.10/crucible_1.0.20200316+i169_amd64.deb", + "size": 14641300, + "path": "utils/ubuntu/19.10/crucible_1.0.20200316+i169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T18:20:14+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/utils/ubuntu/19.10/libwaffle-1-0_1.6.90+embargo169_amd64.deb", + "size": 28100, + "path": "utils/ubuntu/19.10/libwaffle-1-0_1.6.90+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T18:20:14+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/utils/ubuntu/19.10/libwaffle-dev_1.6.90+embargo169_amd64.deb", + "size": 7444, + "path": "utils/ubuntu/19.10/libwaffle-dev_1.6.90+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T18:20:14+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/utils/ubuntu/19.10/libwaffle-doc_1.6.90+embargo169_all.deb", + "size": 4504, + "path": "utils/ubuntu/19.10/libwaffle-doc_1.6.90+embargo169_all.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T18:29:15+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/utils/ubuntu/19.10/piglit-shards_20200330+embargo169.deb", + "size": 15768, + "path": "utils/ubuntu/19.10/piglit-shards_20200330+embargo169.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T18:28:06+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/utils/ubuntu/19.10/piglit_1.0.20200212+embargo169_amd64.deb", + "size": 33672144, + "path": "utils/ubuntu/19.10/piglit_1.0.20200212+embargo169_amd64.deb" + }, + { + "directory": false, + "date_modified": "2020-03-30T18:20:14+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/utils/ubuntu/19.10/waffle-utils_1.6.90+embargo169_amd64.deb", + "size": 11236, + "path": "utils/ubuntu/19.10/waffle-utils_1.6.90+embargo169_amd64.deb" + }, + { + "directory": true, + "date_modified": "2020-03-30T18:29:27+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/TestTools", + "size": 4096, + "path": "TestTools" + }, + { + "directory": true, + "date_modified": "2020-03-30T18:29:27+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/TestTools/Linux", + "size": 4096, + "path": "TestTools/Linux" + }, + { + "directory": false, + "date_modified": "2020-03-30T18:29:16+0000", + "url": "https://ubitstore.intel.com/webstores/fm/sfa/Artifacts/Graphics/Builds/cogd/dynamic/run/builds/b561/8020561/artifacts/TestTools/Linux/TestTools-Release-Internal-x64.tar.xz", + "size": 368, + "path": "TestTools/Linux/TestTools-Release-Internal-x64.tar.xz" + } +] \ No newline at end of file diff --git a/ketr.ketran/package.json b/ketr.ketran/package.json new file mode 100644 index 0000000..486bc52 --- /dev/null +++ b/ketr.ketran/package.json @@ -0,0 +1,31 @@ +{ + "name": "ketr.ketran", + "version": "1.0.0", + "description": "Peddlers of Ketran: REST API", + "main": "index.html", + "scripts": { + "start": "NODE_CONFIG_ENV='devel' node server/app.js", + "backend": "NODE_CONFIG_ENV='production' node server/app.js" + }, + "repository": "ssh://git@gitlab.ketrenos.com:/jketreno/ketr.ketran", + "author": "James Ketrenos ", + "license": "MIT", + "private": true, + "dependencies": { + "bluebird": "^3.5.5", + "config": "^3.1.0", + "connect-sqlite3": "^0.9.11", + "cookie-parser": "^1.4.4", + "express": "^4.17.1", + "express-session": "^1.17.1", + "handlebars": "^4.7.6", + "moment": "^2.24.0", + "morgan": "^1.9.1", + "node-fetch": "^2.6.0", + "node-gzip": "^1.1.2", + "nodemailer": "^6.3.0", + "sequelize": "^5.21.6", + "sqlite3": "^4.1.1" + }, + "devDependencies": {} +} diff --git a/ketr.ketran/server/app.js b/ketr.ketran/server/app.js new file mode 100755 index 0000000..65201a8 --- /dev/null +++ b/ketr.ketran/server/app.js @@ -0,0 +1,308 @@ +"use strict"; + +process.env.TZ = "Etc/GMT"; + +console.log("Loading ketr.ketran"); + +const express = require("express"), + morgan = require("morgan"), + bodyParser = require("body-parser"), + config = require("config"), + session = require('express-session'), + hb = require("handlebars"), + SQLiteStore = require('connect-sqlite3')(session), + scanner = require("./scanner"); + +require("./console-line.js"); /* Monkey-patch console.log with line numbers */ + +const serverConfig = config.get("server"); + +let basePath = config.get("basePath"); +basePath = "/" + basePath.replace(/^\/+/, "").replace(/\/+$/, "") + "/"; +if (basePath == "//") { + basePath = "/"; +} + +console.log("Hosting server from: " + basePath); + +const app = express(); + +app.set("basePath", basePath); + +/* App is behind an nginx proxy which we trust, so use the remote address + * set in the headers */ +app.set("trust proxy", true); + +app.use(basePath, require("./routes/basepath.js")); + +/* Handle static files first so excessive logging doesn't occur */ +app.use(basePath, express.static("frontend", { index: false })); + +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ + extended: false +})); + +/* ******************************************************************************* + * Logging - begin + * + * This runs before after cookie parsing, but before routes. If we set + * immediate: true on the morgan options, it happens before cookie parsing + * */ + + morgan.token('remote-user', function (req) { + return req.user ? req.user.username : "N/A"; +}); + +/* Any path starting with the following won't be logged via morgan */ +const logSkipPaths = new RegExp("^" + basePath + "(" + [ + ".*thumbs\\/", + "bower_components", +].join(")|(") + ")"); +app.use(morgan('common', { + skip: function (req) { + return logSkipPaths.exec(req.originalUrl); + } +})); + +/* + * Logging - end + * ******************************************************************************* */ + +/* body-parser does not support text/*, so add support for that here */ +app.use(function(req, res, next){ + if (!req.is('text/*')) { + return next(); + } + req.setEncoding('utf8'); + let text = ''; + req.on('data', function(chunk) { + text += chunk; + }); + req.on('end', function() { + req.text = text; + next(); + }); +}); + +app.use(session({ + store: new SQLiteStore({ db: config.get("sessions.db") }), + secret: config.get("sessions.store-secret"), + cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 }, // 1 week + saveUninitialized: false, + resave: true +})); + +const index = require("./routes/index"); + +if (config.has("admin.mail") && + config.has("smtp.host") && + config.has("smtp.sender")) { + app.set("transporter", require("nodemailer").createTransport({ + host: config.get("smtp.host"), + pool: true, + port: config.has("smtp.port") ? config.get("smtp.port") : 25 + })); +} else { + console.log("SMTP disabled. To enable SMTP, configure admin.mail, smtp.host, and smtp.sender"); +} + +const templates = { + "html": [ + "

The user {{displayName}} has verified their email address ({{mail}}).

", + "", + "

They indicated they know:

", + "
{{notes}}
", + "", + "

To authenticate:

", + "

echo 'UPDATE users SET authenticated=1 WHERE id={{id}};' | sqlite3 users.db

", + "", + "

Sincerely,
", + "James

" + ].join("\n"), + "text": [ + "The user {{displayName}} has verified their email address ({{mail}}).", + "", + "They indicated they know:", + "{{notes}}", + "", + "To authenticate:", + "echo 'UPDATE users SET authenticated=1 WHERE id={{id}};' | sqlite3 users.db", + "", + "Sincerely,", + "James" + ].join("\n") +}; + +/* Look for action-token URLs and process; this does not require a user to be logged + * in */ +app.use(basePath, function(req, res, next) { + let match = req.url.match(/^\/([0-9a-f]+)$/); + if (!match) { + return next(); + } + + let key = match[1]; + return userDB.sequelize.query("SELECT * FROM authentications WHERE key=:key", { + replacements: { + key: key + }, + type: userDB.sequelize.QueryTypes.SELECT + }).then(function(results) { + let token; + if (results.length == 0) { + console.log("Invalid key. Ignoring."); + return next(); + } + + token = results[0]; + + console.log("Matched token: " + JSON.stringify(token, null, 2)); + switch (token.type) { + case "account-setup": + return userDB.sequelize.query("UPDATE users SET mailVerified=1 WHERE id=:userId", { + replacements: token + }).then(function() { + return userDB.sequelize.query("DELETE FROM authentications WHERE key=:key", { + replacements: token + }); + }).then(function() { + return userDB.sequelize.query("SELECT * FROM users WHERE id=:userId", { + replacements: token, + type: userDB.sequelize.QueryTypes.SELECT + }).then(function(results) { + if (results.length == 0) { + throw "DB mis-match between authentications and users table"; + } + const transporter = app.get("transporter"); + if (!transporter) { + console.log("Not sending VERIFIED email; SMTP not configured."); + return; + } + + let user = results[0], + envelope = { + to: config.get("admin.mail"), + from: config.get("smtp.sender"), + subject: "VERIFIED: Account'" + user.displayName + "'", + cc: "", + bcc: "", + text: hb.compile(templates.text)(user), + html: hb.compile(templates.html)(user) + }; + + req.session.userId = user.id; + + return new Promise(function (resolve, reject) { + let attempts = 10; + + function send(envelope) { + /* Rate limit to ten per second */ + transporter.sendMail(envelope, function (error, info) { + if (!error) { + console.log('Message sent: ' + info.response); + return resolve(); + } + + if (attempts == 0) { + console.log("Error sending email: ", error) + return reject(error); + } + + attempts--; + console.log("Unable to send mail. Trying again in 100ms (" + attempts + " attempts remain): ", error); + setTimeout(send.bind(undefined, envelope), 100); + }); + } + + send(envelope); + }); + }).then(function() { + return res.redirect(308, basePath); + }); + }); + } + + return next(); + }); +}); + +/* Allow loading of the app w/out being logged in */ +app.use(basePath, index); + +/* Allow access to the 'users' API w/out being logged in */ +const users = require("./routes/users"); +app.use(basePath + "api/v1/users", users.router); + +app.use(function(err, req, res, next) { + res.status(err.status || 500).json({ + message: err.message, + error: {} + }); +}); + +/* Check authentication */ +app.use(basePath, function(req, res, next) { + return users.getSessionUser(req).then(function(user) { + if (user.restriction) { + return res.status(401).send(user.restriction); + } + req.user = user; + return next(); + }).catch(function(error) { + return res.status(403).send(error); + }); +}); + +/* Everything below here requires a successful authentication */ +app.use(basePath, express.static(picturesPath, { index: false })); + +app.use(basePath + "api/v1/games", require("./routes/games")); + +/* Declare the "catch all" index route last; the final route is a 404 dynamic router */ +app.use(basePath, index); + +/** + * Create HTTP server and listen for new connections + */ +app.set("port", serverConfig.port); + +const server = require("http").createServer(app); + +require("./db/games").then(function(db) { + gamesDB = db; +}).then(function() { + return require("./db/users").then(function(db) { + userDB = db; + }); +}).then(function() { + console.log("DB connected. Opening server."); + server.listen(serverConfig.port); +}).catch(function(error) { + console.error(error); + process.exit(-1); +}); + +server.on("error", function(error) { + if (error.syscall !== "listen") { + throw error; + } + + // handle specific listen errors with friendly messages + switch (error.code) { + case "EACCES": + console.error(serverConfig.port + " requires elevated privileges"); + process.exit(1); + break; + case "EADDRINUSE": + console.error(serverConfig.port + " is already in use"); + process.exit(1); + break; + default: + throw error; + } +}); + +server.on("listening", function() { + console.log("Listening on " + serverConfig.port); +}); diff --git a/ketr.ketran/server/console-line.js b/ketr.ketran/server/console-line.js new file mode 100644 index 0000000..fb47e05 --- /dev/null +++ b/ketr.ketran/server/console-line.js @@ -0,0 +1,30 @@ +/* monkey-patch console.log to prefix with file/line-number */ +if (process.env.LOG_LINE) { + let cwd = process.cwd(), + cwdRe = new RegExp("^[^/]*" + cwd.replace("/", "\\/") + "\/([^:]*:[0-9]*).*$"); + [ "log", "warn", "error" ].forEach(function(method) { + console[method] = (function () { + let orig = console[method]; + return function () { + function getErrorObject() { + try { + throw Error(''); + } catch (err) { + return err; + } + } + + let err = getErrorObject(), + caller_line = err.stack.split("\n")[4], + args = [caller_line.replace(cwdRe, "$1 -")]; + + /* arguments.unshift() doesn't exist... */ + for (var i = 0; i < arguments.length; i++) { + args.push(arguments[i]); + } + + orig.apply(this, args); + }; + })(); + }); +} diff --git a/ketr.ketran/server/db/games.db b/ketr.ketran/server/db/games.db new file mode 100644 index 0000000..4a06899 Binary files /dev/null and b/ketr.ketran/server/db/games.db differ diff --git a/ketr.ketran/server/db/users.db b/ketr.ketran/server/db/users.db new file mode 100644 index 0000000..11eac67 Binary files /dev/null and b/ketr.ketran/server/db/users.db differ diff --git a/ketr.ketran/server/face-recognizer.js b/ketr.ketran/server/face-recognizer.js new file mode 100644 index 0000000..da8fcc3 --- /dev/null +++ b/ketr.ketran/server/face-recognizer.js @@ -0,0 +1,404 @@ +/* +* Face recognition: +* 1. For each photo, extract all faces. Store face rectangles. +* face_id unique +* photo_id foreign key +* top left bottom right +* identity_id +* distance (0 == truth; manually assigned identity) +* 2. For each face_id, create: +* /${picturesPath}face-data/${face_id % 100}/ +* ${face_id}-normalized +* ${face_id}-original +* ${face_id}-data +*/ + +"use strict"; + +process.env.TZ = "Etc/GMT"; + +console.log("Loading face-recognizer"); + +require('@tensorflow/tfjs-node'); + +const config = require("config"), + Promise = require("bluebird"), + { exists, mkdir, unlink } = require("./lib/util"), + faceapi = require("face-api.js"), + fs = require("fs"), + canvas = require("canvas"); + +const { createCanvas, Canvas, Image, ImageData } = canvas; + +faceapi.env.monkeyPatch({ Canvas, Image, ImageData }); + +const maxConcurrency = require("os").cpus().length; + +require("./console-line.js"); /* Monkey-patch console.log with line numbers */ + +const picturesPath = config.get("picturesPath").replace(/\/$/, "") + "/", + faceData = picturesPath + "face-data/"; + +let photoDB = null; + +console.log("Loading pictures out of: " + picturesPath); + +function alignFromLandmarks(image, landmarks) { + const faceMargin = 0.3, + width = 256, height = 256, + dY = landmarks._positions[45]._y - landmarks._positions[36]._y, + dX = landmarks._positions[45]._x - landmarks._positions[36]._x, + mid = { + x: landmarks._positions[36]._x + 0.5 * dX, + y: landmarks._positions[36]._y + 0.5 * dY + }, + rotation = -Math.atan2(dY, dX), + cosRotation = Math.cos(rotation), + sinRotation = Math.sin(rotation), + eyeDistance = Math.sqrt(dY * dY + dX * dX), + scale = width * (1.0 - 2. * faceMargin) / eyeDistance, + canvas = createCanvas(width, height), + ctx = canvas.getContext("2d"); + + const prime = { + x: mid.x * cosRotation - mid.y * sinRotation, + y: mid.y * cosRotation + mid.x * sinRotation + }; + + mid.x = prime.x; + mid.y = prime.y; + + ctx.translate( + 0.5 * width - mid.x * scale, + 0.5 * height - (height * (0.5 - faceMargin)) - mid.y * scale); + ctx.rotate(rotation); + ctx.scale(scale, scale); + ctx.drawImage(image, 0, 0); + /* + ctx.strokeStyle = "red"; + ctx.strokeWidth = "1"; + ctx.beginPath(); + landmarks._positions.forEach((point, index) => { + if (index == 0) { + ctx.moveTo(point._x, point._y); + } else { + ctx.lineTo(point._x, point._y); + } + }); + ctx.stroke(); + */ + return canvas; +} + +process.stdout.write("Loading DB."); +require("./db/photos").then(function(db) { + process.stdout.write("done\n"); + photoDB = db; +}).then(() => { + console.log("DB connected."); + process.stdout.write("Loading models."); + return faceapi.nets.ssdMobilenetv1.loadFromDisk('./models'); +}).then(() => { + process.stdout.write("."); + return faceapi.nets.faceLandmark68Net.loadFromDisk('./models'); +}).then(() => { + process.stdout.write("."); + return faceapi.nets.faceRecognitionNet.loadFromDisk('./models'); +}).then(() => { + console.log("Beginning face detection scanning."); + return photoDB.sequelize.query("SELECT photos.id,photos.filename,photos.width,photos.height,albums.path " + + "FROM photos " + + "LEFT JOIN albums ON (albums.id=photos.albumId) " + + "WHERE faces=-1 AND photos.duplicate=0 AND photos.deleted=0 ORDER BY albums.path,photos.filename", { + type: photoDB.sequelize.QueryTypes.SELECT, + raw: true + }); +}).then((needToScan) => { + const total = needToScan.length; + let remaining = total, + processed = 0, + lastStatus = Date.now(); + + console.log(`${needToScan.length} photos have not had faces scanned.`); + + return Promise.map(needToScan, (photo) => { + const photoPath = photo.path + photo.filename; + + console.log(`Processing ${photoPath}...`); + + /* Remove any existing face data for this photo */ + return photoDB.sequelize.query("SELECT id FROM faces WHERE photoId=:id", { + replacements: photo, + type: photoDB.sequelize.QueryTypes.SELECT, + raw: true + }).then((faces) => { + /* For each face-id, remove any face-data files, and then remove all the entries + * from the DB */ + return Promise.map(faces, (face) => { + return Promise.map([ "-data.json", "-original.png" ], (suffix) => { + const id = face.id, + dataPath = faceData + (id % 100) + "/" + id + suffix; + return exists(dataPath).then((result) => { + if (result) { + console.log(`...removing ${dataPath}`); + return unlink(dataPath); + } + }); + }); + }).then(() => { + return photoDB.sequelize.query("DELETE FROM faces WHERE photoId=:id", { + replacements: photo, + }); + }); + }).then(async () => { + /* Process image for faces data */ + const image = await canvas.loadImage(picturesPath + photoPath); + const detections = await faceapi.detectAllFaces(image, + new faceapi.SsdMobilenetv1Options({ + minConfidence: 0.9 + }) + ).withFaceLandmarks(); + + if (detections.length > 0) { + console.log(`...${detections.length} faces identified in ${photoPath}.`); + } + + return Promise.map(detections, async (face) => { + const detection = face.detection, + canvas = alignFromLandmarks(image, face.landmarks); + face.descriptor = await faceapi.computeFaceDescriptor(canvas); + + const width = detection._box._width, + height = detection._box._height, + replacements = { + id: photo.id, + top: detection._box._y / detection._imageDims.height, + left: detection._box._x / detection._imageDims.width, + bottom: (detection._box._y + height) / detection._imageDims.height, + right: (detection._box._x + width) / detection._imageDims.width, + faceConfidence: detection._score + }; + + return photoDB.sequelize.query("INSERT INTO faces (photoId,top,left,bottom,right,faceConfidence) " + + "VALUES (:id,:top,:left,:bottom,:right,:faceConfidence)", { + replacements: replacements + }).spread((results, metadata) => { + return metadata.lastID; + }).then((id) => { + const path = faceData + (id % 100); + return mkdir(path).then(() => { + const dataPath = `${path}/${id}-data.json`, data = []; + console.log(`...writing descriptor data to ${dataPath}...`); + /* Confert from sparse object to dense array */ + for (let i = 0; i < 128; i++) { + data.push(face.descriptor[i]); + } + fs.writeFileSync(dataPath, JSON.stringify(data)); + }).then(() => { + const target = `${path}/${id}-original.png`; + console.log(`...writing aligned face crop to ${target}.`); + fs.writeFileSync(target, canvas.toBuffer("image/png", { + quality: 0.95, + chromaSubsampling: false + })); + }).catch((error) => { + console.error(error); + process.exit(-1); + }); + }); + }).then(() => { + return photoDB.sequelize.query("UPDATE photos SET faces=:faces WHERE id=:id", { + replacements: { + id: photo.id, + faces: detections.length + }, + }); + }); + }).catch((error) => { + console.log(error); + console.warn("Skipping out on image " + photoPath + " and marking to 0 faces to prevent future scanning."); + return photoDB.sequelize.query("UPDATE photos SET faces=:faces WHERE id=:id", { + replacements: { + id: photo.id, + faces: 0 + }, + }); + }).then(() => { + processed++; + const now = Date.now(); + if (now - lastStatus > 5000) { + const rate = Math.round(10000 * (remaining - (total - processed)) / (now - lastStatus)) / 10, + eta = Math.round((total - processed) / rate); + lastStatus = now; + remaining = total - processed; + console.log(`Processing ${rate} images per second. ${remaining} images to be processed. ETA: ${eta}s`); + } + }); + }, { + concurrency: maxConcurrency + }); +}).then(() => { + console.log("Looking for face distances that need to be updated..."); + let maxId; + + return photoDB.sequelize.query("SELECT faces.id FROM faces ORDER BY faces.id DESC LIMIT 1", { + type: photoDB.sequelize.QueryTypes.SELECT, + raw: true + }).then((results) => { + if (!results.length) { + console.log("...no faces exist yet to generate distances."); + maxId = 0; + return []; + } + maxId = results[0].id; + return photoDB.sequelize.query( + "SELECT faces.id,faces.lastComparedId " + + "FROM faces INNER JOIN photos ON photos.duplicate=0 AND photos.deleted=0 AND photos.id=faces.photoId " + + "WHERE faces.lastComparedId<:maxId OR faces.lastComparedId IS NULL " + + "ORDER BY faces.id ASC", { + replacements: { + maxId: maxId + }, + type: photoDB.sequelize.QueryTypes.SELECT, + raw: true + }); + }).then((facesToUpdate) => { + console.log(`...${facesToUpdate.length} faces need distances updated.`); + console.log("---- run scanner/scanner !! ---"); + return []; + if (facesToUpdate.length == 0) { + return facesToUpdate; + } + + const descriptors = {}; + + return photoDB.sequelize.query( + "SELECT id FROM faces ORDER BY id ASC", { + type: photoDB.sequelize.QueryTypes.SELECT, + raw: true + }).then((allFaces) => { + console.log(`...reading ${allFaces.length} descriptors...`); + return Promise.map(allFaces, (face) => { + const id = face.id, + dataPath = faceData + "/" + (id % 100) + "/" + id + "-data.json"; + + if (id in descriptors) { + return; + } + + return exists(dataPath).then((doesExist) => { + if (!doesExist) { + console.warn(`${dataPath} is missing!`); + return; + } + + descriptors[id] = JSON.parse(fs.readFileSync(dataPath)); + }); + }); + }).then(() => { + const total = facesToUpdate.length; + let remaining = total, + processed = 0, + lastStatus = Date.now(), + targets = []; + + for (let target in descriptors) { + targets.push({ id: target, descriptor: descriptors[target] }); + } + + return Promise.mapSeries(facesToUpdate, (face) => { + if (!(face.id in descriptors)) { + console.warn(`...attempt to compare distance with no descriptor for ${face.id}`); + return; + } + + const faceDescriptor = descriptors[face.id]; + + return photoDB.sequelize.transaction((transaction) => { + return photoDB.sequelize.query( + "SELECT distance,face1Id,face2Id " + + "FROM facedistances " + + "WHERE face1Id=:id OR face2Id=:id " + + "ORDER BY face1Id ASC", { + replacements: { + id: face.id + }, + type: photoDB.sequelize.QueryTypes.SELECT, + raw: true, + transaction: transaction + }).then((distances) => { + return Promise.map(targets, (target) => { + /* Skip comparing to self */ + if (target.id == face.id) { + return; + } + + /* Only compare against newer faces */ + if (face.lastComparedId && target.id <= face.lastComparedId) { + return; + } + + const index = distances.findIndex((distance) => { + return distance.face1Id == target.id || distance.face2Id == target.id + }); + + if (index != -1) { + /* A distance has already been calculated between face and target */ + return; + } + + const distance = faceapi.euclideanDistance(faceDescriptor, target.descriptor); + + /* If the distance > 0.6, we don't want to store this in the DB */ + if (distance > 0.6) { + return; + } + + if (distance < 0.4) { + process.stdout.write("."); +// console.log(`Face ${face.id} and ${target.id} have a distance of: ${distance}`); + } + + return photoDB.sequelize.query( + "INSERT INTO facedistances (face1Id,face2Id,distance) " + + "VALUES (:first,:second,:distance)", { + replacements: { + first: Math.min(face.id, target.id), + second: Math.max(face.id, target.id), + distance: distance + }, + transaction: transaction + }); + }, { + concurrency: maxConcurrency + }); + }).then(() => { + return photoDB.sequelize.query( + "UPDATE faces SET lastComparedId=:lastId WHERE id=:id", { + replacements: { + lastId: maxId, + id: face.id + }, + transaction: transaction + }); + }); + }).then(() => { + processed++; + const now = Date.now(); + if (now - lastStatus > 5000) { + const rate = Math.round(10000 * (remaining - (total - processed)) / (now - lastStatus)) / 10, + eta = Math.round((total - processed) / rate); + lastStatus = now; + remaining = total - processed; + console.log(`\nProcessing ${rate} faces per second. ${remaining} faces to be processed. ETA: ${eta}s`); + } + }); + }); + }); + }); +}).then(() => { + console.log("Face detection scanning completed."); +}).catch((error) => { + console.error(error); + process.exit(-1); +}); diff --git a/ketr.ketran/server/face.js b/ketr.ketran/server/face.js new file mode 100644 index 0000000..eccce41 --- /dev/null +++ b/ketr.ketran/server/face.js @@ -0,0 +1,281 @@ +"use strict"; + +process.env.TZ = "Etc/GMT"; + +require('@tensorflow/tfjs-node'); + +let photoDB = null; + +const config = require("config"), + Promise = require("bluebird"), + { exists, mkdir, unlink } = require("./lib/util"), + faceapi = require("face-api.js"), + fs = require("fs"), + canvas = require("canvas"); + +const { createCanvas, Canvas, Image, ImageData } = canvas; + +faceapi.env.monkeyPatch({ Canvas, Image, ImageData }); + +const maxConcurrency = require("os").cpus().length; + +require("./console-line.js"); /* Monkey-patch console.log with line numbers */ + +const picturesPath = config.get("picturesPath").replace(/\/$/, "") + "/", + faceData = picturesPath + "face-data/"; + +function alignFromLandmarks(image, landmarks, drawLandmarks) { + const faceMargin = 0.45, + width = 512, height = 512, + dY = landmarks._positions[45]._y - landmarks._positions[36]._y, + dX = landmarks._positions[45]._x - landmarks._positions[36]._x, + mid = { + x: landmarks._positions[36]._x + 0.5 * dX, + y: landmarks._positions[36]._y + 0.5 * dY + }, + rotation = -Math.atan2(dY, dX), + cosRotation = Math.cos(rotation), + sinRotation = Math.sin(rotation), + eyeDistance = Math.sqrt(dY * dY + dX * dX), + scale = width * (1.0 - 2. * faceMargin) / eyeDistance, + canvas = createCanvas(width, height), + ctx = canvas.getContext("2d"); + + const prime = { + x: mid.x * cosRotation - mid.y * sinRotation, + y: mid.y * cosRotation + mid.x * sinRotation + }; + + mid.x = prime.x; + mid.y = prime.y; + + ctx.translate( + 0.5 * width - mid.x * scale, + 0.5 * height - (height * (0.5 - faceMargin)) - mid.y * scale); + ctx.rotate(rotation); + ctx.scale(scale, scale); + ctx.drawImage(image, 0, 0); + + if (drawLandmarks) { + ctx.strokeStyle = "red"; + ctx.strokeWidth = "1"; + ctx.beginPath(); + landmarks._positions.forEach((point, index) => { + if (index == 0) { + ctx.moveTo(point._x, point._y); + } else { + ctx.lineTo(point._x, point._y); + } + }); + ctx.stroke(); + } + + return canvas; +} + +process.stdout.write("Loading DB."); +require("./db/photos").then(function(db) { + process.stdout.write("done\n"); + photoDB = db; +}).then(() => { + console.log("DB connected."); + process.stdout.write("Loading models."); + return faceapi.nets.ssdMobilenetv1.loadFromDisk('./models'); +}).then(() => { + process.stdout.write("."); + return faceapi.nets.faceLandmark68Net.loadFromDisk('./models'); +}).then(() => { + process.stdout.write("."); + return faceapi.nets.faceRecognitionNet.loadFromDisk('./models'); +}).then(async () => { + process.stdout.write(".done\n"); + + if (process.argv[0].match(/node/)) { + process.argv.shift(); /* node */ + } + process.argv.shift(); /* script name */ + + return Promise.resolve().then(() => { + if (process.argv.length != 0) { + return process.argv; + } + + /* If no parameters provided, scan all faces to create image crops */ + return photoDB.sequelize.query("SELECT id FROM faces ORDER BY id ASC", { + type: photoDB.sequelize.QueryTypes.SELECT, + raw: true + }).then((results) => { + return results.map(result => result.id); + }); + }); +}).then((args) => { + const faces = []; + + console.log(`Scanning ${args.length} faces.`); + return Promise.map(args, (arg) => { + const file = arg; + let id = parseInt(arg); + + let loader; + + if (id == file) { + /* This is a face id */ + console.log(`Looking up face-id ${id}...`); + loader = photoDB.sequelize.query( + "SELECT albums.path,photos.filename,photos.width,photos.height,faces.* " + + "FROM faces,photos,albums " + + "WHERE photos.id=faces.photoId " + + "AND albums.id=photos.albumId " + + "AND faces.id=:id", { + replacements: { + id: id + }, + type: photoDB.sequelize.QueryTypes.SELECT, + raw: true + }).then((results) => { + if (results.length != 1) { + console.error(`...error. No face-id found: ${id}.\n`); + process.exit(-1); + } + const photo = results[0]; + console.log(`...loading ${photo.filename}`); + + const file = photo.path + photo.filename; + return canvas.loadImage(picturesPath + file).then(async (image) => { + const detectors = [ { + detection: { + _box: { + _x: photo.left * photo.width, + _y: photo.top * photo.height, + _width: (photo.right - photo.left) * photo.width, + _height: (photo.bottom - photo.top) * photo.height, + } + }, + descriptor: JSON.parse(fs.readFileSync(faceData + (id % 100) + "/" + id + "-data.json")) + } ]; + return [ file, image, detectors ]; + }); + }); + } else { + /* This is a file */ + console.log(`Loading ${file}...`); + id = undefined; + loader = canvas.loadImage(picturesPath + file).then(async (image) => { + const detectors = await faceapi.detectAllFaces(image, + new faceapi.SsdMobilenetv1Options({ + minConfidence: 0.9 + }) + ).withFaceLandmarks(); + + await detectors.forEach(async (detector, index) => { + const canvas = alignFromLandmarks(image, detector.landmarks, false); + fs.writeFileSync(`rotation-pre-${index}.png`, canvas.toBuffer("image/png", { + quality: 0.95, + chromaSubsampling: false + })); + const detected = await faceapi.detectSingleFace(canvas, + new faceapi.SsdMobilenetv1Options({ + minConfidence: 0.1 + }) + ).withFaceLandmarks(); + const descriptor = await faceapi.computeFaceDescriptor(canvas); + console.log(`Processing face ${index}...`); + console.log(`...pre aligned score: ${detector.detection._score}`); + if (!detected) { + console.log("No face found in re-scaled and aligned image"); + return; + } + console.log(`...post-aligned score: ${detected.detection._score}`); + const newCanvas = alignFromLandmarks(canvas, detected.landmarks, true); + + fs.writeFileSync(`rotation-post-${index}.png`, newCanvas.toBuffer("image/png", { + quality: 0.95, + chromaSubsampling: false + })); + + console.log(`Wrote rotation-${index}.png`); + + const data = []; + /* Confert from sparse object to dense array */ + for (let i = 0; i < 128; i++) { + data.push(descriptor[i]); + } + detector.descriptor = data; + }); + + return [ file, image, detectors ]; + }); + } + + return loader.then((results) => { + const filepath = results[0], + image = results[1], + detectors = results[2]; + + process.stdout.write(`${detectors.length} faces.\n`); + + return Promise.map(detectors, (face, index) => { + faces.push({ + filepath: filepath, + index: index, + descriptor: face.descriptor + }) + + /* If this is a face-id, output the -original.png + * meta-data file */ + if (!id) { + return; + } + + const path = "face-data/" + (id % 100), + target = `${path}/${id}-original.png`, + box = face.detection._box, + aspect = box._width / box._height, + dx = (aspect > 1.0) ? 200 : (200 * aspect), + dy = (aspect < 1.0) ? 200 : (200 / aspect); + + return exists(target).then((doesExist) => { + if (doesExist) { + console.log(`...${target} already exists.`); + return; + } + const canvas = createCanvas(200, 200), + ctx = canvas.getContext('2d'); + + ctx.fillStyle = "rgba(0, 0, 0, 0)"; + ctx.fillRect(0, 0, 200, 200); + ctx.drawImage(image, box._x, box._y, box._width, box._height, + Math.floor((200 - dx) * 0.5), + Math.floor((200 - dy) * 0.5), dx, dy); + + console.log(`...writing to ${target}.`); + + return mkdir(path).then(() => { + fs.writeFileSync(picturesPath + target, canvas.toBuffer("image/png", { + quality: 0.95, + chromaSubsampling: false + })); + }); + }); + }); + }); + }, { + concurrency: maxConcurrency + }).then(() => { + console.log("Face detection scanning completed."); + if (0) faces.forEach((a, i) => { + faces.forEach((b, j) => { + if (i == j) { + return; + } + const distance = faceapi.euclideanDistance(a.descriptor, b.descriptor); + if (distance < 0.4) { + console.log(`${a.filepath}.${a.index} is similar to ${b.filepath}.${b.index}: ${distance}`); + } + }) + }); + }); +}).catch((error) => { + console.error(error); + process.exit(-1); +}); diff --git a/ketr.ketran/server/http-server.js b/ketr.ketran/server/http-server.js new file mode 100644 index 0000000..8ad8e46 --- /dev/null +++ b/ketr.ketran/server/http-server.js @@ -0,0 +1,137 @@ + +"use strict"; + +const express = require('express'), + morgan = require('morgan'), + cookieParser = require('cookie-parser'), + bodyParser = require('body-parser'), + http = require('http'), + config = require('config'), + app = express(), + { timestamp } = require('./timestamp'), + fs = require('fs'), + util = require('util'), + mkdir = util.promisify(fs.mkdir), + unlink = util.promisify(fs.unlink), + path = require('path'), + fetch = require('node-fetch'), + Promise = require('bluebird'), + url = require('url'), + { exec } = require('child_process'); + +fetch.Promise = Promise; + +const basePath = "/" + config.get("http.base").replace(/^\/*/, "").replace(/\/*$/, "") + "/", + dataPath = "/" + config.get("dataPath").replace(/^\/*/, "").replace(/\/*$/, "") + "/"; +/* */ + +if (!config.has("auth.idsid") || !config.has("auth.password")) { + console.error("You need to provide credentials to connect to ubit-gfx in config/local.json"); + console.error(' "auth": { "idsid": "USERNAME", "password": "PASSWORD" }'); + process.exit(-1); +} + +app.use(morgan('common')); + +app.use(bodyParser.json({ + verify: function(req,res,buf) { + req.rawBody = buf; + } +})); + +app.use(bodyParser.urlencoded({ + extended: false +})); +app.use(cookieParser()); + +/* Routes: + * /api/v1/publish Publish content to repository + */ + +app.get("/*", (req, res, next) => { + /* */ + return res.status(400).send({ usage: `POST ${basePath}api/v1/publish/:distro/:releaseStream/:url` }); +}); + +const auth = new Buffer(config.get("auth.idsid") + ":" + config.get("auth.password"), 'ascii').toString('base64'); + +app.post(basePath + 'api/v1/publish/:distro/:releaseStream/:url', function (req, res, next) { + const distro = req.params.distro, + releaseStream = req.params.releaseStream, + remoteUrl = req.params.url; + let filename; + + try { + filename = path.basename(url.parse(remoteUrl).pathname); + } catch (error) { + return res.status(400).send({ error: `Unparsable URL: ${remoteUrl}` }); + } + + if (distro.match(/\//) || releaseStream.match(/\//)) { + return res.status(400).send({ error: "Neither distro nor releaseStream may contain '/'" }); + } + + console.log(`POST publish/${distro}-${releaseStream}/${filename}`); + + const filepath = `${dataPath}${distro}-${releaseStream}`; + + return mkdir(filepath, { recursive: true }, () => { + const pathname = `${filepath}/${filename}`; + if (fs.existsSync(pathname)) { + return res.status(409).send({ message: `'${distro}-${releaseStream}/${filename}' already exists.` }); + } + + return fetch(remoteUrl, { + method: "GET", + headers: { + 'Authorization': `Basic ${auth}` + } + }).then(result => { + const dest = fs.createWriteStream(pathname); + dest.on('finish', () => { + exec(`./update-repository.sh ${distro}-${releaseStream}`, { + cwd: ".." , + shell: "/bin/bash" + }, (error, stdout, stderr) => { + if (error) { + return unlink(pathname).catch(() => { + console.error(`Unable to remove ${pathname} after update-repository.sh failed.`); + }).then(() => { + return res.status(500).send({ message: "Error while updating aptly database.", error: error, stderr: stderr, stdout: stdout }); + }); + } + return res.status(200).send({ message: "OK", stdout: stdout || "", stderr: stderr || "" }); + }); + }); + result.body.pipe(dest); + }).catch((error )=> { + const message = `Unable to download ${remoteUrl}: ${error}`; + console.error(message); + return res.status(500).send({ message: message }); + }); + }).catch((error) => { + const message = `Unable to mkdir ${filepath}: ${error}`; + console.error(message); + return res.status(500).send({ message: message }); + }); +}); + +app.post("/*", (req, res, next) => { + /* */ + return res.status(400).send({ usage: `POST /${basePath}/api/v1/publish/:distro/:releaseStream/:url` }); +}); + + +const server = http.createServer(app), + port = config.has("port") ? config.get("port") : 6543; + +server.listen(port); +server.on('listening', function() { + let addr = server.address(); + let bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + console.log(timestamp() + ` Now serving ${basePath} on ${bind}`); +}); + +module.exports = server; diff --git a/ketr.ketran/server/lib/mail.js b/ketr.ketran/server/lib/mail.js new file mode 100644 index 0000000..944c272 --- /dev/null +++ b/ketr.ketran/server/lib/mail.js @@ -0,0 +1,183 @@ +"use strict"; + +const config = require("config"), + crypto = require("crypto"), + hb = require("handlebars"); + +const templates = { + "verify": { + "html": [ + "

Hello {{username}},

", + "", + "

Welcome to ketrenos.com. You are almost done creating your account. ", + "Before you can access the system, you must verify your email address.

", + "", + "

To do so, simply access this link:

", + "

VERIFY {{mail}} ADDRESS

", + "", + "

Sincerely,

", + "

James

" + ].join("\n"), + "text": [ + "Hello {{username}},", + "", + "Welcome to ketrenos.com. You are almost done creating your account. ", + "Before you can access the system, you must verify your email address.", + "", + "To do so, simply access this link:", + "", + "{{url}}{{secret}}", + "", + "Sincerely,", + "James" + ].join("\n") + }, + "password": { + "html": [ + "

Hello {{username}},

", + "", + "

You changed your password on ketrenos.com.

", + "", + "

Sincerely,

", + "

James

" + ].join("\n"), + "text": [ + "Hello {{username}},", + "", + "You changed your password on ketrenos.com.", + "", + "Sincerely,

", + "James" + ].join("\n") + } +}; + +const sendVerifyMail = function(userDB, req, user) { + return userDB.sequelize.query("DELETE FROM authentications WHERE userId=:id AND type='account-setup'", { + replacements: { + id: user.id + } + }).then(function() { + return new Promise(function(resolve, reject) { + crypto.randomBytes(16, function(error, buffer) { + if (error) { + return reject(error); + } + return resolve(buffer.toString('hex')); + }); + }); + }).then(function(secret) { + return userDB.sequelize.query( + "INSERT INTO authentications " + + "(userId,issued,key,type) " + + "VALUES (:userId,CURRENT_TIMESTAMP,:key,'account-setup')", { + replacements: { + key: secret, + userId: user.id + } + }).then(function() { + return secret; + }).catch(function(error) { + console.log(error); + throw error; + }); + }).then(function(secret) { + const transporter = req.app.get("transporter"); + if (!transporter) { + console.log("Not sending VERIFY email; SMTP not configured."); + return; + } + + let data = { + username: user.displayName, + mail: user.mail, + secret: secret, + url: req.protocol + "://" + req.hostname + req.app.get("basePath") + }, envelope = { + to: data.mail, + from: config.get("smtp.sender"), + subject: "Request to ketrenos.com create account for '" + data.username + "'", + cc: "", + bcc: config.get("admin.mail"), + text: hb.compile(templates.verify.text)(data), + html: hb.compile(templates.verify.html)(data) + }; + return new Promise(function (resolve, reject) { + let attempts = 10; + + function send(envelope) { + /* Rate limit to ten per second */ + transporter.sendMail(envelope, function (error, info) { + if (!error) { + console.log('Message sent: ' + info.response); + return resolve(); + } + + if (attempts == 0) { + console.log("Error sending email: ", error); + return reject(error); + } + + attempts--; + console.log("Unable to send mail. Trying again in 100ms (" + attempts + " attempts remain): ", error); + setTimeout(send.bind(undefined, envelope), 100); + }); + } + + send(envelope); + }); + }).catch(function(error) { + console.log("Error creating account: ", error); + }); +}; + +const sendPasswordChangedMail = function(userDB, req, user) { + const transporter = req.app.get("transporter"); + if (!transporter) { + console.log("Not sending VERIFY email; SMTP not configured."); + return; + } + + let data = { + username: user.displayName, + mail: user.mail, + url: req.protocol + "://" + req.hostname + req.app.get("basePath") + }, envelope = { + to: data.mail, + from: config.get("smtp.sender"), + subject: "Password changed on ketrenos.com for '" + data.username + "'", + cc: "", + bcc: config.get("admin.mail"), + text: hb.compile(templates.password.text)(data), + html: hb.compile(templates.password.html)(data) + }; + return new Promise(function (resolve, reject) { + let attempts = 10; + + function send(envelope) { + /* Rate limit to ten per second */ + transporter.sendMail(envelope, function (error, info) { + if (!error) { + console.log('Message sent: ' + info.response); + return resolve(); + } + + if (attempts == 0) { + console.log("Error sending email: ", error); + return reject(error); + } + + attempts--; + console.log("Unable to send mail. Trying again in 100ms (" + attempts + " attempts remain): ", error); + setTimeout(send.bind(undefined, envelope), 100); + }); + } + + send(envelope); + }); +}; + +module.exports = { + sendVerifyMail, + sendPasswordChangedMail +} diff --git a/ketr.ketran/server/lib/pascha-dates.js b/ketr.ketran/server/lib/pascha-dates.js new file mode 100644 index 0000000..c2c516a --- /dev/null +++ b/ketr.ketran/server/lib/pascha-dates.js @@ -0,0 +1,512 @@ +/* +EasterB - What date does Easter Sunday come on in a given year? +Version 1.23, last revised: 2007/07/22 +Copyright (c) 1981-2007 by author: Harry J. Smith, +19628 Via Monte Dr., Saratoga CA 95070. All rights reserved. + +Will write file: EasterB.Out for years 1875 - 2124 + +Gregorian Calendar +Easter Sunday: 1875 3/28 Western 4/25 Orthodox (April 13 Julian date) +Easter Sunday: 1876 4/16 Western Same Orthodox (April 4 Julian date) +Easter Sunday: 1877 4/ 1 Western 4/ 8 Orthodox (March 27 Julian date) +Easter Sunday: 1878 4/21 Western 4/28 Orthodox (April 16 Julian date) +Easter Sunday: 1879 4/13 Western Same Orthodox (April 1 Julian date) +Easter Sunday: 1880 3/28 Western 5/ 2 Orthodox (April 20 Julian date) +Easter Sunday: 1881 4/17 Western 4/24 Orthodox (April 12 Julian date) +Easter Sunday: 1882 4/ 9 Western Same Orthodox (March 28 Julian date) +Easter Sunday: 1883 3/25 Western 4/29 Orthodox (April 17 Julian date) +Easter Sunday: 1884 4/13 Western 4/20 Orthodox (April 8 Julian date) +Easter Sunday: 1885 4/ 5 Western Same Orthodox (March 24 Julian date) +Easter Sunday: 1886 4/25 Western Same Orthodox (April 13 Julian date) +Easter Sunday: 1887 4/10 Western 4/17 Orthodox (April 5 Julian date) +Easter Sunday: 1888 4/ 1 Western 5/ 6 Orthodox (April 24 Julian date) +Easter Sunday: 1889 4/21 Western Same Orthodox (April 9 Julian date) +Easter Sunday: 1890 4/ 6 Western 4/13 Orthodox (April 1 Julian date) +Easter Sunday: 1891 3/29 Western 5/ 3 Orthodox (April 21 Julian date) +Easter Sunday: 1892 4/17 Western Same Orthodox (April 5 Julian date) +Easter Sunday: 1893 4/ 2 Western 4/ 9 Orthodox (March 28 Julian date) +Easter Sunday: 1894 3/25 Western 4/29 Orthodox (April 17 Julian date) +Easter Sunday: 1895 4/14 Western Same Orthodox (April 2 Julian date) +Easter Sunday: 1896 4/ 5 Western Same Orthodox (March 24 Julian date) +Easter Sunday: 1897 4/18 Western 4/25 Orthodox (April 13 Julian date) +Easter Sunday: 1898 4/10 Western 4/17 Orthodox (April 5 Julian date) +Easter Sunday: 1899 4/ 2 Western 4/30 Orthodox (April 18 Julian date) +Easter Sunday: 1900 4/15 Western 4/22 Orthodox (April 9 Julian date) +Easter Sunday: 1901 4/ 7 Western 4/14 Orthodox (April 1 Julian date) +Easter Sunday: 1902 3/30 Western 4/27 Orthodox (April 14 Julian date) +Easter Sunday: 1903 4/12 Western 4/19 Orthodox (April 6 Julian date) +Easter Sunday: 1904 4/ 3 Western 4/10 Orthodox (March 28 Julian date) +Easter Sunday: 1905 4/23 Western 4/30 Orthodox (April 17 Julian date) +Easter Sunday: 1906 4/15 Western Same Orthodox (April 2 Julian date) +Easter Sunday: 1907 3/31 Western 5/ 5 Orthodox (April 22 Julian date) +Easter Sunday: 1908 4/19 Western 4/26 Orthodox (April 13 Julian date) +Easter Sunday: 1909 4/11 Western Same Orthodox (March 29 Julian date) +Easter Sunday: 1910 3/27 Western 5/ 1 Orthodox (April 18 Julian date) +Easter Sunday: 1911 4/16 Western 4/23 Orthodox (April 10 Julian date) +Easter Sunday: 1912 4/ 7 Western Same Orthodox (March 25 Julian date) +Easter Sunday: 1913 3/23 Western 4/27 Orthodox (April 14 Julian date) +Easter Sunday: 1914 4/12 Western 4/19 Orthodox (April 6 Julian date) +Easter Sunday: 1915 4/ 4 Western Same Orthodox (March 22 Julian date) +Easter Sunday: 1916 4/23 Western Same Orthodox (April 10 Julian date) +Easter Sunday: 1917 4/ 8 Western 4/15 Orthodox (April 2 Julian date) +Easter Sunday: 1918 3/31 Western 5/ 5 Orthodox (April 22 Julian date) +Easter Sunday: 1919 4/20 Western Same Orthodox (April 7 Julian date) +Easter Sunday: 1920 4/ 4 Western 4/11 Orthodox (March 29 Julian date) +Easter Sunday: 1921 3/27 Western 5/ 1 Orthodox (April 18 Julian date) +Easter Sunday: 1922 4/16 Western Same Orthodox (April 3 Julian date) +Easter Sunday: 1923 4/ 1 Western 4/ 8 Orthodox (March 26 Julian date) +Easter Sunday: 1924 4/20 Western 4/27 Orthodox (April 14 Julian date) +Easter Sunday: 1925 4/12 Western 4/19 Orthodox (April 6 Julian date) +Easter Sunday: 1926 4/ 4 Western 5/ 2 Orthodox (April 19 Julian date) +Easter Sunday: 1927 4/17 Western 4/24 Orthodox (April 11 Julian date) +Easter Sunday: 1928 4/ 8 Western 4/15 Orthodox (April 2 Julian date) +Easter Sunday: 1929 3/31 Western 5/ 5 Orthodox (April 22 Julian date) +Easter Sunday: 1930 4/20 Western Same Orthodox (April 7 Julian date) +Easter Sunday: 1931 4/ 5 Western 4/12 Orthodox (March 30 Julian date) +Easter Sunday: 1932 3/27 Western 5/ 1 Orthodox (April 18 Julian date) +Easter Sunday: 1933 4/16 Western Same Orthodox (April 3 Julian date) +Easter Sunday: 1934 4/ 1 Western 4/ 8 Orthodox (March 26 Julian date) +Easter Sunday: 1935 4/21 Western 4/28 Orthodox (April 15 Julian date) +Easter Sunday: 1936 4/12 Western Same Orthodox (March 30 Julian date) +Easter Sunday: 1937 3/28 Western 5/ 2 Orthodox (April 19 Julian date) +Easter Sunday: 1938 4/17 Western 4/24 Orthodox (April 11 Julian date) +Easter Sunday: 1939 4/ 9 Western Same Orthodox (March 27 Julian date) +Easter Sunday: 1940 3/24 Western 4/28 Orthodox (April 15 Julian date) +Easter Sunday: 1941 4/13 Western 4/20 Orthodox (April 7 Julian date) +Easter Sunday: 1942 4/ 5 Western Same Orthodox (March 23 Julian date) +Easter Sunday: 1943 4/25 Western Same Orthodox (April 12 Julian date) +Easter Sunday: 1944 4/ 9 Western 4/16 Orthodox (April 3 Julian date) +Easter Sunday: 1945 4/ 1 Western 5/ 6 Orthodox (April 23 Julian date) +Easter Sunday: 1946 4/21 Western Same Orthodox (April 8 Julian date) +Easter Sunday: 1947 4/ 6 Western 4/13 Orthodox (March 31 Julian date) +Easter Sunday: 1948 3/28 Western 5/ 2 Orthodox (April 19 Julian date) +Easter Sunday: 1949 4/17 Western 4/24 Orthodox (April 11 Julian date) +Easter Sunday: 1950 4/ 9 Western Same Orthodox (March 27 Julian date) +Easter Sunday: 1951 3/25 Western 4/29 Orthodox (April 16 Julian date) +Easter Sunday: 1952 4/13 Western 4/20 Orthodox (April 7 Julian date) +Easter Sunday: 1953 4/ 5 Western Same Orthodox (March 23 Julian date) +Easter Sunday: 1954 4/18 Western 4/25 Orthodox (April 12 Julian date) +Easter Sunday: 1955 4/10 Western 4/17 Orthodox (April 4 Julian date) +Easter Sunday: 1956 4/ 1 Western 5/ 6 Orthodox (April 23 Julian date) +Easter Sunday: 1957 4/21 Western Same Orthodox (April 8 Julian date) +Easter Sunday: 1958 4/ 6 Western 4/13 Orthodox (March 31 Julian date) +Easter Sunday: 1959 3/29 Western 5/ 3 Orthodox (April 20 Julian date) +Easter Sunday: 1960 4/17 Western Same Orthodox (April 4 Julian date) +Easter Sunday: 1961 4/ 2 Western 4/ 9 Orthodox (March 27 Julian date) +Easter Sunday: 1962 4/22 Western 4/29 Orthodox (April 16 Julian date) +Easter Sunday: 1963 4/14 Western Same Orthodox (April 1 Julian date) +Easter Sunday: 1964 3/29 Western 5/ 3 Orthodox (April 20 Julian date) +Easter Sunday: 1965 4/18 Western 4/25 Orthodox (April 12 Julian date) +Easter Sunday: 1966 4/10 Western Same Orthodox (March 28 Julian date) +Easter Sunday: 1967 3/26 Western 4/30 Orthodox (April 17 Julian date) +Easter Sunday: 1968 4/14 Western 4/21 Orthodox (April 8 Julian date) +Easter Sunday: 1969 4/ 6 Western 4/13 Orthodox (March 31 Julian date) +Easter Sunday: 1970 3/29 Western 4/26 Orthodox (April 13 Julian date) +Easter Sunday: 1971 4/11 Western 4/18 Orthodox (April 5 Julian date) +Easter Sunday: 1972 4/ 2 Western 4/ 9 Orthodox (March 27 Julian date) +Easter Sunday: 1973 4/22 Western 4/29 Orthodox (April 16 Julian date) +Easter Sunday: 1974 4/14 Western Same Orthodox (April 1 Julian date) +Easter Sunday: 1975 3/30 Western 5/ 4 Orthodox (April 21 Julian date) +Easter Sunday: 1976 4/18 Western 4/25 Orthodox (April 12 Julian date) +Easter Sunday: 1977 4/10 Western Same Orthodox (March 28 Julian date) +Easter Sunday: 1978 3/26 Western 4/30 Orthodox (April 17 Julian date) +Easter Sunday: 1979 4/15 Western 4/22 Orthodox (April 9 Julian date) +Easter Sunday: 1980 4/ 6 Western Same Orthodox (March 24 Julian date) +Easter Sunday: 1981 4/19 Western 4/26 Orthodox (April 13 Julian date) +Easter Sunday: 1982 4/11 Western 4/18 Orthodox (April 5 Julian date) +Easter Sunday: 1983 4/ 3 Western 5/ 8 Orthodox (April 25 Julian date) +Easter Sunday: 1984 4/22 Western Same Orthodox (April 9 Julian date) +Easter Sunday: 1985 4/ 7 Western 4/14 Orthodox (April 1 Julian date) +Easter Sunday: 1986 3/30 Western 5/ 4 Orthodox (April 21 Julian date) +Easter Sunday: 1987 4/19 Western Same Orthodox (April 6 Julian date) +Easter Sunday: 1988 4/ 3 Western 4/10 Orthodox (March 28 Julian date) +Easter Sunday: 1989 3/26 Western 4/30 Orthodox (April 17 Julian date) +Easter Sunday: 1990 4/15 Western Same Orthodox (April 2 Julian date) +Easter Sunday: 1991 3/31 Western 4/ 7 Orthodox (March 25 Julian date) +Easter Sunday: 1992 4/19 Western 4/26 Orthodox (April 13 Julian date) +Easter Sunday: 1993 4/11 Western 4/18 Orthodox (April 5 Julian date) +Easter Sunday: 1994 4/ 3 Western 5/ 1 Orthodox (April 18 Julian date) +Easter Sunday: 1995 4/16 Western 4/23 Orthodox (April 10 Julian date) +Easter Sunday: 1996 4/ 7 Western 4/14 Orthodox (April 1 Julian date) +Easter Sunday: 1997 3/30 Western 4/27 Orthodox (April 14 Julian date) +Easter Sunday: 1998 4/12 Western 4/19 Orthodox (April 6 Julian date) +Easter Sunday: 1999 4/ 4 Western 4/11 Orthodox (March 29 Julian date) +Easter Sunday: 2000 4/23 Western 4/30 Orthodox (April 17 Julian date) +Easter Sunday: 2001 4/15 Western Same Orthodox (April 2 Julian date) +Easter Sunday: 2002 3/31 Western 5/ 5 Orthodox (April 22 Julian date) +Easter Sunday: 2003 4/20 Western 4/27 Orthodox (April 14 Julian date) +Easter Sunday: 2004 4/11 Western Same Orthodox (March 29 Julian date) +Easter Sunday: 2005 3/27 Western 5/ 1 Orthodox (April 18 Julian date) +Easter Sunday: 2006 4/16 Western 4/23 Orthodox (April 10 Julian date) +Easter Sunday: 2007 4/ 8 Western Same Orthodox (March 26 Julian date) +Easter Sunday: 2008 3/23 Western 4/27 Orthodox (April 14 Julian date) +Easter Sunday: 2009 4/12 Western 4/19 Orthodox (April 6 Julian date) +Easter Sunday: 2010 4/ 4 Western Same Orthodox (March 22 Julian date) +Easter Sunday: 2011 4/24 Western Same Orthodox (April 11 Julian date) +Easter Sunday: 2012 4/ 8 Western 4/15 Orthodox (April 2 Julian date) +Easter Sunday: 2013 3/31 Western 5/ 5 Orthodox (April 22 Julian date) +Easter Sunday: 2014 4/20 Western Same Orthodox (April 7 Julian date) +Easter Sunday: 2015 4/ 5 Western 4/12 Orthodox (March 30 Julian date) +Easter Sunday: 2016 3/27 Western 5/ 1 Orthodox (April 18 Julian date) +Easter Sunday: 2017 4/16 Western Same Orthodox (April 3 Julian date) +Easter Sunday: 2018 4/ 1 Western 4/ 8 Orthodox (March 26 Julian date) +Easter Sunday: 2019 4/21 Western 4/28 Orthodox (April 15 Julian date) +Easter Sunday: 2020 4/12 Western 4/19 Orthodox (April 6 Julian date) +Easter Sunday: 2021 4/ 4 Western 5/ 2 Orthodox (April 19 Julian date) +Easter Sunday: 2022 4/17 Western 4/24 Orthodox (April 11 Julian date) +Easter Sunday: 2023 4/ 9 Western 4/16 Orthodox (April 3 Julian date) +Easter Sunday: 2024 3/31 Western 5/ 5 Orthodox (April 22 Julian date) +Easter Sunday: 2025 4/20 Western Same Orthodox (April 7 Julian date) +Easter Sunday: 2026 4/ 5 Western 4/12 Orthodox (March 30 Julian date) +Easter Sunday: 2027 3/28 Western 5/ 2 Orthodox (April 19 Julian date) +Easter Sunday: 2028 4/16 Western Same Orthodox (April 3 Julian date) +Easter Sunday: 2029 4/ 1 Western 4/ 8 Orthodox (March 26 Julian date) +Easter Sunday: 2030 4/21 Western 4/28 Orthodox (April 15 Julian date) +Easter Sunday: 2031 4/13 Western Same Orthodox (March 31 Julian date) +Easter Sunday: 2032 3/28 Western 5/ 2 Orthodox (April 19 Julian date) +Easter Sunday: 2033 4/17 Western 4/24 Orthodox (April 11 Julian date) +Easter Sunday: 2034 4/ 9 Western Same Orthodox (March 27 Julian date) +Easter Sunday: 2035 3/25 Western 4/29 Orthodox (April 16 Julian date) +Easter Sunday: 2036 4/13 Western 4/20 Orthodox (April 7 Julian date) +Easter Sunday: 2037 4/ 5 Western Same Orthodox (March 23 Julian date) +Easter Sunday: 2038 4/25 Western Same Orthodox (April 12 Julian date) +Easter Sunday: 2039 4/10 Western 4/17 Orthodox (April 4 Julian date) +Easter Sunday: 2040 4/ 1 Western 5/ 6 Orthodox (April 23 Julian date) +Easter Sunday: 2041 4/21 Western Same Orthodox (April 8 Julian date) +Easter Sunday: 2042 4/ 6 Western 4/13 Orthodox (March 31 Julian date) +Easter Sunday: 2043 3/29 Western 5/ 3 Orthodox (April 20 Julian date) +Easter Sunday: 2044 4/17 Western 4/24 Orthodox (April 11 Julian date) +Easter Sunday: 2045 4/ 9 Western Same Orthodox (March 27 Julian date) +Easter Sunday: 2046 3/25 Western 4/29 Orthodox (April 16 Julian date) +Easter Sunday: 2047 4/14 Western 4/21 Orthodox (April 8 Julian date) +Easter Sunday: 2048 4/ 5 Western Same Orthodox (March 23 Julian date) +Easter Sunday: 2049 4/18 Western 4/25 Orthodox (April 12 Julian date) +Easter Sunday: 2050 4/10 Western 4/17 Orthodox (April 4 Julian date) +Easter Sunday: 2051 4/ 2 Western 5/ 7 Orthodox (April 24 Julian date) +Easter Sunday: 2052 4/21 Western Same Orthodox (April 8 Julian date) +Easter Sunday: 2053 4/ 6 Western 4/13 Orthodox (March 31 Julian date) +Easter Sunday: 2054 3/29 Western 5/ 3 Orthodox (April 20 Julian date) +Easter Sunday: 2055 4/18 Western Same Orthodox (April 5 Julian date) +Easter Sunday: 2056 4/ 2 Western 4/ 9 Orthodox (March 27 Julian date) +Easter Sunday: 2057 4/22 Western 4/29 Orthodox (April 16 Julian date) +Easter Sunday: 2058 4/14 Western Same Orthodox (April 1 Julian date) +Easter Sunday: 2059 3/30 Western 5/ 4 Orthodox (April 21 Julian date) +Easter Sunday: 2060 4/18 Western 4/25 Orthodox (April 12 Julian date) +Easter Sunday: 2061 4/10 Western Same Orthodox (March 28 Julian date) +Easter Sunday: 2062 3/26 Western 4/30 Orthodox (April 17 Julian date) +Easter Sunday: 2063 4/15 Western 4/22 Orthodox (April 9 Julian date) +Easter Sunday: 2064 4/ 6 Western 4/13 Orthodox (March 31 Julian date) +Easter Sunday: 2065 3/29 Western 4/26 Orthodox (April 13 Julian date) +Easter Sunday: 2066 4/11 Western 4/18 Orthodox (April 5 Julian date) +Easter Sunday: 2067 4/ 3 Western 4/10 Orthodox (March 28 Julian date) +Easter Sunday: 2068 4/22 Western 4/29 Orthodox (April 16 Julian date) +Easter Sunday: 2069 4/14 Western Same Orthodox (April 1 Julian date) +Easter Sunday: 2070 3/30 Western 5/ 4 Orthodox (April 21 Julian date) +Easter Sunday: 2071 4/19 Western Same Orthodox (April 6 Julian date) +Easter Sunday: 2072 4/10 Western Same Orthodox (March 28 Julian date) +Easter Sunday: 2073 3/26 Western 4/30 Orthodox (April 17 Julian date) +Easter Sunday: 2074 4/15 Western 4/22 Orthodox (April 9 Julian date) +Easter Sunday: 2075 4/ 7 Western Same Orthodox (March 25 Julian date) +Easter Sunday: 2076 4/19 Western 4/26 Orthodox (April 13 Julian date) +Easter Sunday: 2077 4/11 Western 4/18 Orthodox (April 5 Julian date) +Easter Sunday: 2078 4/ 3 Western 5/ 8 Orthodox (April 25 Julian date) +Easter Sunday: 2079 4/23 Western Same Orthodox (April 10 Julian date) +Easter Sunday: 2080 4/ 7 Western 4/14 Orthodox (April 1 Julian date) +Easter Sunday: 2081 3/30 Western 5/ 4 Orthodox (April 21 Julian date) +Easter Sunday: 2082 4/19 Western Same Orthodox (April 6 Julian date) +Easter Sunday: 2083 4/ 4 Western 4/11 Orthodox (March 29 Julian date) +Easter Sunday: 2084 3/26 Western 4/30 Orthodox (April 17 Julian date) +Easter Sunday: 2085 4/15 Western Same Orthodox (April 2 Julian date) +Easter Sunday: 2086 3/31 Western 4/ 7 Orthodox (March 25 Julian date) +Easter Sunday: 2087 4/20 Western 4/27 Orthodox (April 14 Julian date) +Easter Sunday: 2088 4/11 Western 4/18 Orthodox (April 5 Julian date) +Easter Sunday: 2089 4/ 3 Western 5/ 1 Orthodox (April 18 Julian date) +Easter Sunday: 2090 4/16 Western 4/23 Orthodox (April 10 Julian date) +Easter Sunday: 2091 4/ 8 Western Same Orthodox (March 26 Julian date) +Easter Sunday: 2092 3/30 Western 4/27 Orthodox (April 14 Julian date) +Easter Sunday: 2093 4/12 Western 4/19 Orthodox (April 6 Julian date) +Easter Sunday: 2094 4/ 4 Western 4/11 Orthodox (March 29 Julian date) +Easter Sunday: 2095 4/24 Western Same Orthodox (April 11 Julian date) +Easter Sunday: 2096 4/15 Western Same Orthodox (April 2 Julian date) +Easter Sunday: 2097 3/31 Western 5/ 5 Orthodox (April 22 Julian date) +Easter Sunday: 2098 4/20 Western 4/27 Orthodox (April 14 Julian date) +Easter Sunday: 2099 4/12 Western Same Orthodox (March 30 Julian date) +Easter Sunday: 2100 3/28 Western 5/ 2 Orthodox (April 18 Julian date) +Easter Sunday: 2101 4/17 Western 4/24 Orthodox (April 10 Julian date) +Easter Sunday: 2102 4/ 9 Western Same Orthodox (March 26 Julian date) +Easter Sunday: 2103 3/25 Western 4/29 Orthodox (April 15 Julian date) +Easter Sunday: 2104 4/13 Western 4/20 Orthodox (April 6 Julian date) +Easter Sunday: 2105 4/ 5 Western Same Orthodox (March 22 Julian date) +Easter Sunday: 2106 4/18 Western 4/25 Orthodox (April 11 Julian date) +Easter Sunday: 2107 4/10 Western 4/17 Orthodox (April 3 Julian date) +Easter Sunday: 2108 4/ 1 Western 5/ 6 Orthodox (April 22 Julian date) +Easter Sunday: 2109 4/21 Western Same Orthodox (April 7 Julian date) +Easter Sunday: 2110 4/ 6 Western 4/13 Orthodox (March 30 Julian date) +Easter Sunday: 2111 3/29 Western 5/ 3 Orthodox (April 19 Julian date) +Easter Sunday: 2112 4/17 Western Same Orthodox (April 3 Julian date) +Easter Sunday: 2113 4/ 2 Western 4/ 9 Orthodox (March 26 Julian date) +Easter Sunday: 2114 4/22 Western 4/29 Orthodox (April 15 Julian date) +Easter Sunday: 2115 4/14 Western Same Orthodox (March 31 Julian date) +Easter Sunday: 2116 3/29 Western 5/ 3 Orthodox (April 19 Julian date) +Easter Sunday: 2117 4/18 Western 4/25 Orthodox (April 11 Julian date) +Easter Sunday: 2118 4/10 Western 4/17 Orthodox (April 3 Julian date) +Easter Sunday: 2119 3/26 Western 4/30 Orthodox (April 16 Julian date) +Easter Sunday: 2120 4/14 Western 4/21 Orthodox (April 7 Julian date) +Easter Sunday: 2121 4/ 6 Western 4/13 Orthodox (March 30 Julian date) +Easter Sunday: 2122 3/29 Western 5/ 3 Orthodox (April 19 Julian date) +Easter Sunday: 2123 4/11 Western 4/18 Orthodox (April 4 Julian date) +Easter Sunday: 2124 4/ 2 Western 4/ 9 Orthodox (March 26 Julian date) +*/ +module.exports = { +"1875": "1875-04-25", +"1876": "1876-04-16", +"1877": "1877-04-08", +"1878": "1878-04-28", +"1879": "1879-04-13", +"1880": "1880-05-02", +"1881": "1881-04-24", +"1882": "1882-04-09", +"1883": "1883-04-29", +"1884": "1884-04-20", +"1885": "1885-04-05", +"1886": "1886-04-25", +"1887": "1887-04-17", +"1888": "1888-05-06", +"1889": "1889-04-21", +"1890": "1890-04-13", +"1891": "1891-05-03", +"1892": "1892-04-17", +"1893": "1893-04-09", +"1894": "1894-04-29", +"1895": "1895-04-14", +"1896": "1896-04-05", +"1897": "1897-04-25", +"1898": "1898-04-17", +"1899": "1899-04-30", +"1900": "1900-04-22", +"1901": "1901-04-14", +"1902": "1902-04-27", +"1903": "1903-04-19", +"1904": "1904-04-10", +"1905": "1905-04-30", +"1906": "1906-04-15", +"1907": "1907-05-05", +"1908": "1908-04-26", +"1909": "1909-04-11", +"1910": "1910-05-01", +"1911": "1911-04-23", +"1912": "1912-04-07", +"1913": "1913-04-27", +"1914": "1914-04-19", +"1915": "1915-04-04", +"1916": "1916-04-23", +"1917": "1917-04-15", +"1918": "1918-05-05", +"1919": "1919-04-20", +"1920": "1920-04-11", +"1921": "1921-05-01", +"1922": "1922-04-16", +"1923": "1923-04-08", +"1924": "1924-04-27", +"1925": "1925-04-19", +"1926": "1926-05-02", +"1927": "1927-04-24", +"1928": "1928-04-15", +"1929": "1929-05-05", +"1930": "1930-04-20", +"1931": "1931-04-12", +"1932": "1932-05-01", +"1933": "1933-04-16", +"1934": "1934-04-08", +"1935": "1935-04-28", +"1936": "1936-04-12", +"1937": "1937-05-02", +"1938": "1938-04-24", +"1939": "1939-04-09", +"1940": "1940-04-28", +"1941": "1941-04-20", +"1942": "1942-04-05", +"1943": "1943-04-25", +"1944": "1944-04-16", +"1945": "1945-05-06", +"1946": "1946-04-21", +"1947": "1947-04-13", +"1948": "1948-05-02", +"1949": "1949-04-24", +"1950": "1950-04-09", +"1951": "1951-04-29", +"1952": "1952-04-20", +"1953": "1953-04-05", +"1954": "1954-04-25", +"1955": "1955-04-17", +"1956": "1956-05-06", +"1957": "1957-04-21", +"1958": "1958-04-13", +"1959": "1959-05-03", +"1960": "1960-04-17", +"1961": "1961-04-09", +"1962": "1962-04-29", +"1963": "1963-04-14", +"1964": "1964-05-03", +"1965": "1965-04-25", +"1966": "1966-04-10", +"1967": "1967-04-30", +"1968": "1968-04-21", +"1969": "1969-04-13", +"1970": "1970-04-26", +"1971": "1971-04-18", +"1972": "1972-04-09", +"1973": "1973-04-29", +"1974": "1974-04-14", +"1975": "1975-05-04", +"1976": "1976-04-25", +"1977": "1977-04-10", +"1978": "1978-04-30", +"1979": "1979-04-22", +"1980": "1980-04-06", +"1981": "1981-04-26", +"1982": "1982-04-18", +"1983": "1983-05-08", +"1984": "1984-04-22", +"1985": "1985-04-14", +"1986": "1986-05-04", +"1987": "1987-04-19", +"1988": "1988-04-10", +"1989": "1989-04-30", +"1990": "1990-04-15", +"1991": "1991-04-07", +"1992": "1992-04-26", +"1993": "1993-04-18", +"1994": "1994-05-01", +"1995": "1995-04-23", +"1996": "1996-04-14", +"1997": "1997-04-27", +"1998": "1998-04-19", +"1999": "1999-04-11", +"2000": "2000-04-30", +"2001": "2001-04-15", +"2002": "2002-05-05", +"2003": "2003-04-27", +"2004": "2004-04-11", +"2005": "2005-05-01", +"2006": "2006-04-23", +"2007": "2007-04-08", +"2008": "2008-04-27", +"2009": "2009-04-19", +"2010": "2010-04-04", +"2011": "2011-04-24", +"2012": "2012-04-15", +"2013": "2013-05-05", +"2014": "2014-04-20", +"2015": "2015-04-12", +"2016": "2016-05-01", +"2017": "2017-04-16", +"2018": "2018-04-08", +"2019": "2019-04-28", +"2020": "2020-04-19", +"2021": "2021-05-02", +"2022": "2022-04-24", +"2023": "2023-04-16", +"2024": "2024-05-05", +"2025": "2025-04-20", +"2026": "2026-04-12", +"2027": "2027-05-02", +"2028": "2028-04-16", +"2029": "2029-04-08", +"2030": "2030-04-28", +"2031": "2031-04-13", +"2032": "2032-05-02", +"2033": "2033-04-24", +"2034": "2034-04-09", +"2035": "2035-04-29", +"2036": "2036-04-20", +"2037": "2037-04-05", +"2038": "2038-04-25", +"2039": "2039-04-17", +"2040": "2040-05-06", +"2041": "2041-04-21", +"2042": "2042-04-13", +"2043": "2043-05-03", +"2044": "2044-04-24", +"2045": "2045-04-09", +"2046": "2046-04-29", +"2047": "2047-04-21", +"2048": "2048-04-05", +"2049": "2049-04-25", +"2050": "2050-04-17", +"2051": "2051-05-07", +"2052": "2052-04-21", +"2053": "2053-04-13", +"2054": "2054-05-03", +"2055": "2055-04-18", +"2056": "2056-04-09", +"2057": "2057-04-29", +"2058": "2058-04-14", +"2059": "2059-05-04", +"2060": "2060-04-25", +"2061": "2061-04-10", +"2062": "2062-04-30", +"2063": "2063-04-22", +"2064": "2064-04-13", +"2065": "2065-04-26", +"2066": "2066-04-18", +"2067": "2067-04-10", +"2068": "2068-04-29", +"2069": "2069-04-14", +"2070": "2070-05-04", +"2071": "2071-04-19", +"2072": "2072-04-10", +"2073": "2073-04-30", +"2074": "2074-04-22", +"2075": "2075-04-07", +"2076": "2076-04-26", +"2077": "2077-04-18", +"2078": "2078-05-08", +"2079": "2079-04-23", +"2080": "2080-04-14", +"2081": "2081-05-04", +"2082": "2082-04-19", +"2083": "2083-04-11", +"2084": "2084-04-30", +"2085": "2085-04-15", +"2086": "2086-04-07", +"2087": "2087-04-27", +"2088": "2088-04-18", +"2089": "2089-05-01", +"2090": "2090-04-23", +"2091": "2091-04-08", +"2092": "2092-04-27", +"2093": "2093-04-19", +"2094": "2094-04-11", +"2095": "2095-04-24", +"2096": "2096-04-15", +"2097": "2097-05-05", +"2098": "2098-04-27", +"2099": "2099-04-12", +"2100": "2100-05-02", +"2101": "2101-04-24", +"2102": "2102-04-09", +"2103": "2103-04-29", +"2104": "2104-04-20", +"2105": "2105-04-05", +"2106": "2106-04-25", +"2107": "2107-04-17", +"2108": "2108-05-06", +"2109": "2109-04-21", +"2110": "2110-04-13", +"2111": "2111-05-03", +"2112": "2112-04-17", +"2113": "2113-04-09", +"2114": "2114-04-29", +"2115": "2115-04-14", +"2116": "2116-05-03", +"2117": "2117-04-25", +"2118": "2118-04-17", +"2119": "2119-04-30", +"2120": "2120-04-21", +"2121": "2121-04-13", +"2122": "2122-05-03", +"2123": "2123-04-18", +"2124": "2124-04-09", +}; diff --git a/ketr.ketran/server/lib/pascha.js b/ketr.ketran/server/lib/pascha.js new file mode 100644 index 0000000..9f9526d --- /dev/null +++ b/ketr.ketran/server/lib/pascha.js @@ -0,0 +1,116 @@ +//! moment-holiday.js locale configuration +//! locale : pascha Related Holidays +//! author : Kodie Grantham : https://github.com/kodie + +//(function() { +// var moment = (typeof require !== 'undefined' && require !== null) && !require.amd ? require('moment') : this.moment; +function init(moment) { +// moment.holidays.pascha = { + moment.modifyHolidays.add({ + "Lent": { + date: 'pascha-46|pascha-3' + }, + /* + "Holy Monday": { + date: 'pascha-6', + keywords_y: ['great', 'monday'] + }, + "Holy Tuesday": { + date: 'pascha-5', + keywords_y: ['great', 'tuesday'] + }, + "Holy Wednesday": { + date: 'pascha-4', + keywords_y: ['great', 'wednesday'] + }, + "Holy Thursday": { + date: 'pascha-3', + keywords_y: ['great', 'thursday'] + }, + "Holy Friday": { + date: 'pascha-2', + keywords_y: ['great', 'friday'] + }, + "Holy Saturday": { + date: 'pascha-1', + keywords_y: ['holy', 'saturday'] + }, + */ + "Pascha Sunday": { + date: 'pascha', + keywords_y: ['pascha'], + keywords: ['sunday'] + }, + "Bright Week": { + date: 'pascha+1|pascha+6' + }, + "Pentecost Sunday": { + date: 'pascha+49', + keywords_y: ['pentecost'], + keywords: ['sunday'] + }, + //}; + }); + +/* + const orthodoxDates = require("./pascha-dates.js"); + + var pascha = function(year) { + var date = orthodoxDates[year].split("-"); + date[1] -= 1; // month needs to be zero index + return moment(date); + } +*/ + + /** + * Calculates Easter in the Gregorian/Western (Catholic and Protestant) calendar + * based on the algorithm by Oudin (1940) from http://www.tondering.dk/claus/cal/easter.php + * @returns {array} [int month, int day] + */ + var pascha = function(year) { + var f = Math.floor, + // Golden Number - 1 + G = year % 19, + C = f(year / 100), + // related to Epact + H = (C - f(C / 4) - f((8 * C + 13)/25) + 19 * G + 15) % 30, + // number of days from 21 March to the Paschal full moon + I = H - f(H/28) * (1 - f(29/(H + 1)) * f((21-G)/11)), + // weekday for the Paschal full moon + J = (year + f(year / 4) + I + 2 - C + f(C / 4)) % 7, + // number of days from 21 March to the Sunday on or before the Paschal full moon + L = I - J, + month = 3 + f((L + 40)/44), + day = L + 28 - 31 * f(month / 4); + + return moment([year, (month - 1),day]); + } + + moment.modifyHolidays.extendParser(function(m, date){ + if (~date.indexOf('pascha')) { + var dates = date.split('|'); + var ds = []; + + for (var i = 0; i < dates.length; i++) { + if (dates[i].substring(0, 6) === 'pascha') { + var e = pascha(m.year()); + + if (dates[i].charAt(6) === '-') { e.subtract(dates[i].substring(7), 'days'); } + if (dates[i].charAt(6) === '+') { e.add(dates[i].substring(7), 'days'); } + + if (dates.length === 1) { return e; } + ds.push(e.format('M/D')); + } else { + ds.push(dates[i]); + } + } + + if (ds.length) { return ds.join('|'); } + } + }); +} + +module.exports = init; + +// if ((typeof module !== 'undefined' && module !== null ? module.exports : void 0) != null) { module.exports = moment; } +//}).call(this); \ No newline at end of file diff --git a/ketr.ketran/server/lib/util.js b/ketr.ketran/server/lib/util.js new file mode 100644 index 0000000..dcbb545 --- /dev/null +++ b/ketr.ketran/server/lib/util.js @@ -0,0 +1,88 @@ +"use strict"; + +const config = require("config"), + fs = require("fs"), + Promise = require("bluebird"), + picturesPath = config.get("picturesPath").replace(/\/$/, "") + "/"; + +const stat = function (_path) { + if (_path.indexOf(picturesPath.replace(/\/$/, "")) == 0) { + _path = _path.substring(picturesPath.length); + } + + let path = picturesPath + _path; + + return new Promise(function (resolve, reject) { + fs.stat(path, function (error, stats) { + if (error) { + return reject(error); + } + return resolve(stats); + }); + }); +} + +const unlink = function (_path) { + if (_path.indexOf(picturesPath.replace(/\/$/, "")) == 0) { + _path = _path.substring(picturesPath.length); + } + + let path = picturesPath + _path; + + return new Promise(function (resolve, reject) { + fs.unlink(path, function (error) { + if (error) { + return reject(error); + } + return resolve(); + }); + }); +} + +const mkdir = function (_path) { + if (_path.indexOf(picturesPath) == 0) { + _path = _path.substring(picturesPath.length); + } + + let parts = _path.split("/"), path; + + parts.unshift(picturesPath); + return Promise.mapSeries(parts, function (part) { + if (!path) { + path = picturesPath.replace(/\/$/, ""); + } else { + path += "/" + part; + } + + return stat(path).catch(function (error) { + if (error.code != "ENOENT") { + throw error; + } + + return new Promise(function (resolve, reject) { + fs.mkdir(path, function (error) { + if (error) { + return reject(error); + } + + return resolve(); + }); + }); + }); + }); +} + +const exists = function(path) { + return stat(path).then(function() { + return true; + }).catch(function() { + return false; + }); +} + +module.exports = { + stat, + exists, + mkdir, + unlink +}; \ No newline at end of file diff --git a/ketr.ketran/server/mail.js b/ketr.ketran/server/mail.js new file mode 100644 index 0000000..87f741e --- /dev/null +++ b/ketr.ketran/server/mail.js @@ -0,0 +1,61 @@ +"use strict"; + +const createTransport = require('nodemailer').createTransport, + { timestamp } = require("./timestamp"); + +const transporter = createTransport({ + host: 'email.ketrenos.com', + pool: true, + port: 25 +}); + +function sendMail(to, subject, message, cc) { + let envelope = { + subject: subject, + from: 'Ketr.Ketran ', + to: to || '', + cc: cc || '' + }; + + /* If there isn't a To: but there is a Cc:, promote Cc: to To: */ + if (!envelope.to && envelope.cc) { + envelope.to = envelope.cc; + delete envelope.cc; + } + + envelope.text = message + envelope.html = message.replace(/\n/g, "
\n"); + + return new Promise(function (resolve, reject) { + let attempts = 10; + + function attemptSend(envelope) { + /* Rate limit to ten per second */ + transporter.sendMail(envelope, function (error, info) { + if (error) { + if (attempts) { + attempts--; + console.warn(timestamp() + " Unable to send mail. Trying again in 100ms (" + attempts + " attempts remain): ", error); + setTimeout(send.bind(undefined, envelope), 100); + } else { + console.error(timestamp() + " Error sending email: ", error) + return reject(error); + } + } + + console.log(timestamp() + " Mail sent to: " + envelope.to); + return resolve(true); + }); + } + + attemptSend(envelope); + }).then(function(success) { + if (!success) { + console.error(timestamp() + " Mail not sent to: " + envelope.to); + } + }); +} + +module.exports = { + sendMail: sendMail +}; diff --git a/ketr.ketran/server/routes/.identities.js.swp b/ketr.ketran/server/routes/.identities.js.swp new file mode 100644 index 0000000..419252d Binary files /dev/null and b/ketr.ketran/server/routes/.identities.js.swp differ diff --git a/ketr.ketran/server/routes/basepath.js b/ketr.ketran/server/routes/basepath.js new file mode 100644 index 0000000..a8fb0bb --- /dev/null +++ b/ketr.ketran/server/routes/basepath.js @@ -0,0 +1,33 @@ +"use strict"; + +const express = require("express"), + fs = require("fs"), + url = require("url"); + +const router = express.Router(); + +/* This router only handles HTML files and is used + * to replace BASEPATH */ +router.get("/*", function(req, res, next) { + const parts = url.parse(req.url), + basePath = req.app.get("basePath"); + + if (!/^\/[^/]+\.html$/.exec(parts.pathname)) { + return next(); + } + + console.log("Attempting to parse 'frontend" + parts.pathname + "'"); + + /* Replace in index.html with + * the basePath */ + fs.readFile("frontend" + parts.pathname, "utf8", function(error, content) { + if (error) { + return next(); + } + res.send(content.replace( + / in index.html with + * the basePath */ + const index = fs.readFileSync("frontend/index.html", "utf8"); + + res.send(index.replace( + /