From ba23c63a3824328cb87de03e98da6db6a7114673 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 15 Aug 2017 21:51:32 +0000 Subject: [PATCH 001/327] chore(package): update @types/js-yaml to version 3.9.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 875ffc3c6..1632eed24 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "@types/inquirer": "0.0.34", "@types/is-root": "1.0.0", "@types/is-url": "1.2.28", - "@types/js-yaml": "3.9.0", + "@types/js-yaml": "3.9.1", "@types/mocha": "2.2.41", "@types/mongodb": "2.2.9", "@types/monk": "1.0.5", From cdee14faa262b284c6d10cb53d6bf4ccaf4da5db Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 21 Aug 2017 21:49:00 +0000 Subject: [PATCH 002/327] chore(package): update @types/bcryptjs to version 2.4.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 141e95c88..ff3f6c79f 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "test": "gulp test" }, "devDependencies": { - "@types/bcryptjs": "2.4.0", + "@types/bcryptjs": "2.4.1", "@types/body-parser": "1.16.4", "@types/chai": "4.0.3", "@types/chai-http": "3.0.2", From e2b359399fb38d423490e1c418ce5f6c2e43ea84 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 21 Aug 2017 23:28:05 +0000 Subject: [PATCH 003/327] chore(package): update @types/rimraf to version 2.0.2 Closes #697 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 47006ae5d..f16c3c001 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "@types/ratelimiter": "2.1.28", "@types/redis": "2.6.0", "@types/request": "2.0.1", - "@types/rimraf": "2.0.0", + "@types/rimraf": "2.0.2", "@types/riot": "3.6.0", "@types/serve-favicon": "2.2.28", "@types/uuid": "3.4.0", From 73c85a52be7e06c087237761e887620e5f2f9221 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 21 Aug 2017 23:28:23 +0000 Subject: [PATCH 004/327] chore(package): update @types/serve-favicon to version 2.2.29 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 47006ae5d..639261cb9 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "@types/request": "2.0.1", "@types/rimraf": "2.0.0", "@types/riot": "3.6.0", - "@types/serve-favicon": "2.2.28", + "@types/serve-favicon": "2.2.29", "@types/uuid": "3.4.0", "@types/webpack": "3.0.9", "@types/webpack-stream": "3.2.7", From 98e6ce6d6a1ebf31ccb2b1748deae671f8222a70 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 28 Sep 2017 07:55:56 +0000 Subject: [PATCH 005/327] chore(package): update @types/elasticsearch to version 5.0.17 Closes #698 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0a437eefb..455204111 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@types/cors": "2.8.1", "@types/debug": "0.0.30", "@types/deep-equal": "1.0.1", - "@types/elasticsearch": "5.0.14", + "@types/elasticsearch": "5.0.17", "@types/event-stream": "3.3.32", "@types/express": "4.0.37", "@types/gm": "1.17.32", From 29edeb2dc08fdb7072e288177abe0aa26c4c2109 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 6 Oct 2017 03:32:55 +0000 Subject: [PATCH 006/327] chore(package): update mocha to version 4.0.1 Closes #810 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b56f97c79..4dc11b02f 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "gulp-typescript": "3.2.2", "gulp-uglify": "3.0.0", "gulp-util": "3.0.8", - "mocha": "3.5.3", + "mocha": "4.0.1", "riot-tag-loader": "1.0.0", "string-replace-webpack-plugin": "0.1.3", "style-loader": "0.19.0", From c5b9e01288c42c6411f8c36aa042002e3d4199be Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 17 Oct 2017 00:15:25 +0000 Subject: [PATCH 007/327] chore(package): update @types/gm to version 1.17.33 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 688664fa6..0c6d3ff3d 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@types/elasticsearch": "5.0.14", "@types/event-stream": "3.3.32", "@types/express": "4.0.37", - "@types/gm": "1.17.32", + "@types/gm": "1.17.33", "@types/gulp": "4.0.3", "@types/gulp-htmlmin": "1.3.30", "@types/gulp-mocha": "0.0.30", From 97bebddbc03b4a28aa0ba52e79e9fe67879e615c Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 17 Oct 2017 07:08:45 +0000 Subject: [PATCH 008/327] fix(package): update file-type to version 7.2.0 Closes #821 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 688664fa6..a7f52d064 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "elasticsearch": "13.3.1", "escape-regexp": "0.0.1", "express": "4.15.4", - "file-type": "6.2.0", + "file-type": "7.2.0", "fuckadblock": "3.2.1", "gm": "1.23.0", "inquirer": "3.3.0", From 7dc81a3bda40fed661be414ee45ef6e161998977 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 17 Oct 2017 18:37:46 +0000 Subject: [PATCH 009/327] chore(package): update @types/uuid to version 3.4.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 688664fa6..daf3e3dc0 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "@types/rimraf": "2.0.0", "@types/riot": "3.6.0", "@types/serve-favicon": "2.2.28", - "@types/uuid": "3.4.2", + "@types/uuid": "3.4.3", "@types/webpack": "3.0.13", "@types/webpack-stream": "3.2.7", "@types/websocket": "0.0.34", From 73a209359fad3cfdb6b96064bc228686a4f2d4fa Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 20 Oct 2017 19:19:57 +0000 Subject: [PATCH 010/327] chore(package): update tslint to version 5.8.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dd1622a37..09a8b7c88 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "stylus": "0.54.5", "stylus-loader": "3.0.1", "swagger-jsdoc": "1.9.7", - "tslint": "5.7.0", + "tslint": "5.8.0", "uglify-es": "3.0.27", "uglify-es-webpack-plugin": "0.10.0", "uglify-js": "git+https://github.com/mishoo/UglifyJS2.git#harmony", From a39168b081eb4b977930e73c050801df97ea0845 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 23 Oct 2017 20:25:17 +0000 Subject: [PATCH 011/327] chore(package): update gulp-typescript to version 3.2.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4ddb3cb45..aa98122de 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "gulp-rename": "1.2.2", "gulp-replace": "0.6.1", "gulp-tslint": "8.1.2", - "gulp-typescript": "3.2.2", + "gulp-typescript": "3.2.3", "gulp-uglify": "3.0.0", "gulp-util": "3.0.8", "mocha": "3.5.3", From fdcff509d48081bdf58291f9c383b0ab8273c280 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 24 Oct 2017 02:40:07 +0000 Subject: [PATCH 012/327] chore(package): update uglifyjs-webpack-plugin to version 1.0.1 Closes #841 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4ddb3cb45..0b990ea40 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "tslint": "5.7.0", "uglify-es": "3.0.27", "uglify-js": "git+https://github.com/mishoo/UglifyJS2.git#harmony", - "uglifyjs-webpack-plugin": "1.0.0-beta.2", + "uglifyjs-webpack-plugin": "1.0.1", "webpack": "3.8.1" }, "dependencies": { From 7d95989fa03021fbaf163d661afb144ba580161e Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 24 Oct 2017 04:31:04 +0000 Subject: [PATCH 013/327] fix(package): update chalk to version 2.3.0 Closes #833 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4ddb3cb45..2284bb01b 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "bcryptjs": "2.4.3", "body-parser": "1.18.2", "cafy": "3.0.0", - "chalk": "2.1.0", + "chalk": "2.3.0", "compression": "1.7.1", "cors": "2.8.4", "cropperjs": "1.1.3", From 51476ed4c89b0d4f507cd5aa92ef32cf5e4cfa5d Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 25 Oct 2017 00:30:04 +0000 Subject: [PATCH 014/327] chore(package): update @types/gulp to version 4.0.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4ddb3cb45..7be9e49d6 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@types/event-stream": "3.3.32", "@types/express": "4.0.37", "@types/gm": "1.17.32", - "@types/gulp": "4.0.3", + "@types/gulp": "4.0.5", "@types/gulp-htmlmin": "1.3.30", "@types/gulp-mocha": "0.0.30", "@types/gulp-rename": "0.0.32", From 513ed7d90122069ee850cc6d830e5476d7f0b042 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 25 Oct 2017 01:05:05 +0000 Subject: [PATCH 015/327] chore(package): update @types/gulp-uglify to version 3.0.3 Closes #664 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4ddb3cb45..96d397307 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "@types/gulp-replace": "0.0.30", "@types/gulp-tslint": "3.6.31", "@types/gulp-typescript": "2.13.0", - "@types/gulp-uglify": "0.0.30", + "@types/gulp-uglify": "3.0.3", "@types/gulp-util": "3.0.31", "@types/inquirer": "0.0.34", "@types/is-root": "1.0.0", From 58b097620a4e6638dc288dc9e1effb238baf913a Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 25 Oct 2017 01:30:35 +0000 Subject: [PATCH 016/327] chore(package): update @types/mongodb to version 2.2.15 Closes #836 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4ddb3cb45..5105ea727 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "@types/is-url": "1.2.28", "@types/js-yaml": "3.9.0", "@types/mocha": "2.2.43", - "@types/mongodb": "2.2.13", + "@types/mongodb": "2.2.15", "@types/monk": "1.0.6", "@types/morgan": "1.7.33", "@types/ms": "0.7.30", From ee9ff7edd8234482c1f5f168169117647276e59d Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 25 Oct 2017 02:13:31 +0000 Subject: [PATCH 017/327] chore(package): update @types/node to version 8.0.47 Closes #822 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4ddb3cb45..0f63f1f0a 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "@types/morgan": "1.7.33", "@types/ms": "0.7.30", "@types/multer": "1.3.2", - "@types/node": "8.0.33", + "@types/node": "8.0.47", "@types/ratelimiter": "2.1.28", "@types/redis": "2.6.0", "@types/request": "2.0.4", From e830dd71e1644b8627d3833cb663faa90af9aae2 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 25 Oct 2017 16:48:59 +0000 Subject: [PATCH 018/327] chore(package): update @types/riot to version 3.6.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 43a015961..1eb0e94cd 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "@types/redis": "2.6.0", "@types/request": "2.0.4", "@types/rimraf": "2.0.0", - "@types/riot": "3.6.0", + "@types/riot": "3.6.1", "@types/serve-favicon": "2.2.28", "@types/uuid": "3.4.2", "@types/webpack": "3.0.13", From 781ca218e7760c2d3ce33d5169779e303f2a658a Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 25 Oct 2017 18:05:35 +0000 Subject: [PATCH 019/327] chore(package): update @types/webpack to version 3.0.14 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 43a015961..d15c25a28 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@types/riot": "3.6.0", "@types/serve-favicon": "2.2.28", "@types/uuid": "3.4.2", - "@types/webpack": "3.0.13", + "@types/webpack": "3.0.14", "@types/webpack-stream": "3.2.7", "@types/websocket": "0.0.34", "awesome-typescript-loader": "3.2.3", From c8c16c8190751589144f350d8658ab57adaa735a Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 25 Oct 2017 19:26:49 +0000 Subject: [PATCH 020/327] chore(package): update @types/webpack-stream to version 3.2.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 43a015961..f83f7ff0b 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "@types/serve-favicon": "2.2.28", "@types/uuid": "3.4.2", "@types/webpack": "3.0.13", - "@types/webpack-stream": "3.2.7", + "@types/webpack-stream": "3.2.8", "@types/websocket": "0.0.34", "awesome-typescript-loader": "3.2.3", "chai": "4.1.2", From e25ae9ad6e921f6a5c65b11553b26df924006bfe Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 25 Oct 2017 20:20:52 +0000 Subject: [PATCH 021/327] chore(package): update @types/request to version 2.0.7 Closes #827 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 43a015961..473878e39 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "@types/node": "8.0.33", "@types/ratelimiter": "2.1.28", "@types/redis": "2.6.0", - "@types/request": "2.0.4", + "@types/request": "2.0.7", "@types/rimraf": "2.0.0", "@types/riot": "3.6.0", "@types/serve-favicon": "2.2.28", From 753df86030e6c503850333a1c0d7c6bd5334603c Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 26 Oct 2017 15:08:12 +0000 Subject: [PATCH 022/327] chore(package): update @types/gulp-util to version 3.0.33 Closes #846 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 43a015961..1da36f5be 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@types/gulp-tslint": "3.6.31", "@types/gulp-typescript": "2.13.0", "@types/gulp-uglify": "0.0.30", - "@types/gulp-util": "3.0.31", + "@types/gulp-util": "3.0.33", "@types/inquirer": "0.0.34", "@types/is-root": "1.0.0", "@types/is-url": "1.2.28", From a2621f40d7f1f311089975c4b255f108fad0fdee Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 26 Oct 2017 15:09:38 +0000 Subject: [PATCH 023/327] chore(package): update @types/chalk to version 2.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 43a015961..0deb7dbb0 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@types/body-parser": "1.16.5", "@types/chai": "4.0.4", "@types/chai-http": "3.0.3", - "@types/chalk": "0.4.31", + "@types/chalk": "2.2.0", "@types/compression": "0.0.34", "@types/cors": "2.8.1", "@types/debug": "0.0.30", From cdff672a8354ee119a8b704075ff69b3ff760c25 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 26 Oct 2017 18:06:50 +0000 Subject: [PATCH 024/327] chore(package): update @types/mocha to version 2.2.44 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 43a015961..163c2da57 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@types/is-root": "1.0.0", "@types/is-url": "1.2.28", "@types/js-yaml": "3.9.0", - "@types/mocha": "2.2.43", + "@types/mocha": "2.2.44", "@types/mongodb": "2.2.13", "@types/monk": "1.0.6", "@types/morgan": "1.7.33", From 55c480cd7e43d5a272acc87f905f9b9f3bab6a48 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 26 Oct 2017 19:32:50 +0000 Subject: [PATCH 025/327] chore(package): update @types/body-parser to version 1.16.7 Closes #843 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 43a015961..8b32db9a8 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }, "devDependencies": { "@types/bcryptjs": "2.4.0", - "@types/body-parser": "1.16.5", + "@types/body-parser": "1.16.7", "@types/chai": "4.0.4", "@types/chai-http": "3.0.3", "@types/chalk": "0.4.31", From c4806958fc86c2e65351ce23bba2688ac81a2ddf Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 26 Oct 2017 19:34:24 +0000 Subject: [PATCH 026/327] chore(package): update @types/express to version 4.0.39 Closes #844 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 43a015961..c6da419d2 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "@types/deep-equal": "1.0.1", "@types/elasticsearch": "5.0.14", "@types/event-stream": "3.3.32", - "@types/express": "4.0.37", + "@types/express": "4.0.39", "@types/gm": "1.17.32", "@types/gulp": "4.0.3", "@types/gulp-htmlmin": "1.3.30", From cccf267f70bbe8c033c9b6cf3ab5053d4b3f9ddd Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 26 Oct 2017 19:35:00 +0000 Subject: [PATCH 027/327] chore(package): update @types/morgan to version 1.7.35 Closes #847 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 43a015961..d2c8ac38a 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@types/mocha": "2.2.43", "@types/mongodb": "2.2.13", "@types/monk": "1.0.6", - "@types/morgan": "1.7.33", + "@types/morgan": "1.7.35", "@types/ms": "0.7.30", "@types/multer": "1.3.2", "@types/node": "8.0.33", From b6733c57d15d1767acbb40c1a9b0fc05618d3259 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 26 Oct 2017 19:35:25 +0000 Subject: [PATCH 028/327] chore(package): update @types/multer to version 1.3.5 Closes #725 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 43a015961..7e1d481a4 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@types/monk": "1.0.6", "@types/morgan": "1.7.33", "@types/ms": "0.7.30", - "@types/multer": "1.3.2", + "@types/multer": "1.3.5", "@types/node": "8.0.33", "@types/ratelimiter": "2.1.28", "@types/redis": "2.6.0", From 4da5fd57c7e49111e9116f0990ad081fcd4d7963 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 28 Oct 2017 00:55:09 +0000 Subject: [PATCH 029/327] chore(package): update @types/redis to version 2.8.1 Closes #829 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 43a015961..e278cadd8 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "@types/multer": "1.3.2", "@types/node": "8.0.33", "@types/ratelimiter": "2.1.28", - "@types/redis": "2.6.0", + "@types/redis": "2.8.1", "@types/request": "2.0.4", "@types/rimraf": "2.0.0", "@types/riot": "3.6.0", From 1ecc35ca6fa2e8a5b4a3df1b93893b31e192a9f4 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 31 Oct 2017 17:03:16 +0000 Subject: [PATCH 030/327] fix(package): update typescript to version 2.6.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7a81bed7a..4c6cfb5f3 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ "tcp-port-used": "0.1.2", "textarea-caret": "3.0.2", "ts-node": "3.3.0", - "typescript": "2.5.3", + "typescript": "2.6.1", "uuid": "3.1.0", "vhost": "3.0.2", "websocket": "1.0.25", From fcdf2c4f89c4eb7bb666337d7e162e1c5e727e61 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 1 Nov 2017 06:55:03 +0000 Subject: [PATCH 031/327] chore(package): update awesome-typescript-loader to version 3.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 87db0c8e1..dd5a57015 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@types/webpack": "3.0.13", "@types/webpack-stream": "3.2.7", "@types/websocket": "0.0.34", - "awesome-typescript-loader": "3.2.3", + "awesome-typescript-loader": "3.3.0", "chai": "4.1.2", "chai-http": "3.0.0", "css-loader": "0.28.7", From 77dc3702bfc6ece94e8b14ff8e3dc02357606e88 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 5 Nov 2017 09:08:29 +0000 Subject: [PATCH 032/327] fix(package): update cafy to version 3.1.1 Closes #857 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 051eb1cb8..995fb1bb3 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "autwh": "0.0.1", "bcryptjs": "2.4.3", "body-parser": "1.18.2", - "cafy": "3.0.0", + "cafy": "3.1.1", "chalk": "2.1.0", "compression": "1.7.1", "cors": "2.8.4", From 78487934c7e55b36b07f30b73127577ddec31f32 Mon Sep 17 00:00:00 2001 From: otofune Date: Sun, 5 Nov 2017 21:11:16 +0900 Subject: [PATCH 034/327] selializers - posts: unneed async-await Promise.all resolves all Promise, and selializeDriveFile returns Promise. --- src/api/serializers/post.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/serializers/post.ts b/src/api/serializers/post.ts index 7c3690ef7..b2c54e9df 100644 --- a/src/api/serializers/post.ts +++ b/src/api/serializers/post.ts @@ -84,8 +84,8 @@ const self = ( // Populate media if (_post.media_ids) { - _post.media = await Promise.all(_post.media_ids.map(async fileId => - await serializeDriveFile(fileId) + _post.media = await Promise.all(_post.media_ids.map(fileId => + serializeDriveFile(fileId) )); } From 11190f56ad85a12faaa3653f8743dd75948ff11e Mon Sep 17 00:00:00 2001 From: otofune Date: Sun, 5 Nov 2017 22:13:28 +0900 Subject: [PATCH 035/327] serializers/post - run promises in parallel now w/ opts.detail, returns my_reaction field as 'null' w/ no reaction (before: field appears w/ some reaction) --- package.json | 1 + src/api/serializers/post.ts | 122 ++++++++++++++++++++---------------- 2 files changed, 70 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index 051eb1cb8..1e6e8d813 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "webpack": "3.8.1" }, "dependencies": { + "@prezzemolo/rap": "^0.1.0", "accesses": "2.5.0", "animejs": "2.2.0", "autwh": "0.0.1", diff --git a/src/api/serializers/post.ts b/src/api/serializers/post.ts index b2c54e9df..352932acf 100644 --- a/src/api/serializers/post.ts +++ b/src/api/serializers/post.ts @@ -12,6 +12,7 @@ import serializeChannel from './channel'; import serializeUser from './user'; import serializeDriveFile from './drive-file'; import parse from '../common/text'; +import rap from '@prezzemolo/rap' /** * Serialize a post @@ -70,21 +71,21 @@ const self = ( } // Populate user - _post.user = await serializeUser(_post.user_id, meId); + _post.user = serializeUser(_post.user_id, meId); // Populate app if (_post.app_id) { - _post.app = await serializeApp(_post.app_id); + _post.app = serializeApp(_post.app_id); } // Populate channel if (_post.channel_id) { - _post.channel = await serializeChannel(_post.channel_id); + _post.channel = serializeChannel(_post.channel_id); } // Populate media if (_post.media_ids) { - _post.media = await Promise.all(_post.media_ids.map(fileId => + _post.media = Promise.all(_post.media_ids.map(fileId => serializeDriveFile(fileId) )); } @@ -92,82 +93,97 @@ const self = ( // When requested a detailed post data if (opts.detail) { // Get previous post info - const prev = await Post.findOne({ - user_id: _post.user_id, - _id: { - $lt: id - } - }, { - fields: { - _id: true - }, - sort: { - _id: -1 - } - }); - _post.prev = prev ? prev._id : null; + _post.prev = (async () => { + const prev = Post.findOne({ + user_id: _post.user_id, + _id: { + $lt: id + } + }, { + fields: { + _id: true + }, + sort: { + _id: -1 + } + }); + return prev ? prev._id : null; + })() // Get next post info - const next = await Post.findOne({ - user_id: _post.user_id, - _id: { - $gt: id - } - }, { - fields: { - _id: true - }, - sort: { - _id: 1 - } - }); - _post.next = next ? next._id : null; + _post.next = (async () => { + const next = await Post.findOne({ + user_id: _post.user_id, + _id: { + $gt: id + } + }, { + fields: { + _id: true + }, + sort: { + _id: 1 + } + }); + return next ? next._id : null; + })() if (_post.reply_id) { // Populate reply to post - _post.reply = await self(_post.reply_id, meId, { + _post.reply = self(_post.reply_id, meId, { detail: false }); } if (_post.repost_id) { // Populate repost - _post.repost = await self(_post.repost_id, meId, { + _post.repost = self(_post.repost_id, meId, { detail: _post.text == null }); } // Poll if (meId && _post.poll) { - const vote = await Vote - .findOne({ - user_id: meId, - post_id: id - }); + _post.poll = (async (poll) => { + const vote = await Vote + .findOne({ + user_id: meId, + post_id: id + }); - if (vote != null) { - const myChoice = _post.poll.choices - .filter(c => c.id == vote.choice)[0]; + if (vote != null) { + const myChoice = poll.choices + .filter(c => c.id == vote.choice)[0]; - myChoice.is_voted = true; - } + myChoice.is_voted = true; + } + + return poll + })(_post.poll) } // Fetch my reaction if (meId) { - const reaction = await Reaction - .findOne({ - user_id: meId, - post_id: id, - deleted_at: { $exists: false } - }); + _post.my_reaction = (async () => { + const reaction = await Reaction + .findOne({ + user_id: meId, + post_id: id, + deleted_at: { $exists: false } + }); - if (reaction) { - _post.my_reaction = reaction.reaction; - } + if (reaction) { + return reaction.reaction; + } + + return null + })(); } } + // resolve promises in _post object + _post = await rap(_post) + resolve(_post); }); From 5aa5e5cc7074003cec3417636ea1972b6d88150d Mon Sep 17 00:00:00 2001 From: otofune Date: Sun, 5 Nov 2017 22:22:49 +0900 Subject: [PATCH 036/327] serializers - user: run promises in parallel as possible --- src/api/serializers/post.ts | 2 +- src/api/serializers/user.ts | 40 +++++++++++++++++++++---------------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/api/serializers/post.ts b/src/api/serializers/post.ts index 352932acf..99e9bb667 100644 --- a/src/api/serializers/post.ts +++ b/src/api/serializers/post.ts @@ -12,7 +12,7 @@ import serializeChannel from './channel'; import serializeUser from './user'; import serializeDriveFile from './drive-file'; import parse from '../common/text'; -import rap from '@prezzemolo/rap' +import rap from '@prezzemolo/rap'; /** * Serialize a post diff --git a/src/api/serializers/user.ts b/src/api/serializers/user.ts index 3deff2d00..3527921de 100644 --- a/src/api/serializers/user.ts +++ b/src/api/serializers/user.ts @@ -8,6 +8,7 @@ import serializePost from './post'; import Following from '../models/following'; import getFriends from '../common/get-friends'; import config from '../../conf'; +import rap from '@prezzemolo/rap'; /** * Serialize a user @@ -104,26 +105,30 @@ export default ( if (meId && !meId.equals(_user.id)) { // If the user is following - const follow = await Following.findOne({ - follower_id: meId, - followee_id: _user.id, - deleted_at: { $exists: false } - }); - _user.is_following = follow !== null; + _user.is_following = (async () => { + const follow = await Following.findOne({ + follower_id: meId, + followee_id: _user.id, + deleted_at: { $exists: false } + }); + return follow !== null; + })() // If the user is followed - const follow2 = await Following.findOne({ - follower_id: _user.id, - followee_id: meId, - deleted_at: { $exists: false } - }); - _user.is_followed = follow2 !== null; + _user.is_followed = (async () => { + const follow2 = await Following.findOne({ + follower_id: _user.id, + followee_id: meId, + deleted_at: { $exists: false } + }); + return follow2 !== null; + })() } if (opts.detail) { if (_user.pinned_post_id) { // Populate pinned post - _user.pinned_post = await serializePost(_user.pinned_post_id, meId, { + _user.pinned_post = serializePost(_user.pinned_post_id, meId, { detail: true }); } @@ -132,23 +137,24 @@ export default ( const myFollowingIds = await getFriends(meId); // Get following you know count - const followingYouKnowCount = await Following.count({ + _user.following_you_know_count = Following.count({ followee_id: { $in: myFollowingIds }, follower_id: _user.id, deleted_at: { $exists: false } }); - _user.following_you_know_count = followingYouKnowCount; // Get followers you know count - const followersYouKnowCount = await Following.count({ + _user.followers_you_know_count = Following.count({ followee_id: _user.id, follower_id: { $in: myFollowingIds }, deleted_at: { $exists: false } }); - _user.followers_you_know_count = followersYouKnowCount; } } + // resolve promises in _user object + _user = await rap(_user) + resolve(_user); }); /* From 7cd6b1c666605c7a256e4a8dd8db5edeb02da6db Mon Sep 17 00:00:00 2001 From: otofune Date: Sun, 5 Nov 2017 22:26:16 +0900 Subject: [PATCH 037/327] follow lint --- src/api/serializers/post.ts | 12 ++++++------ src/api/serializers/user.ts | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/api/serializers/post.ts b/src/api/serializers/post.ts index 99e9bb667..e1ab78435 100644 --- a/src/api/serializers/post.ts +++ b/src/api/serializers/post.ts @@ -108,7 +108,7 @@ const self = ( } }); return prev ? prev._id : null; - })() + })(); // Get next post info _post.next = (async () => { @@ -126,7 +126,7 @@ const self = ( } }); return next ? next._id : null; - })() + })(); if (_post.reply_id) { // Populate reply to post @@ -158,8 +158,8 @@ const self = ( myChoice.is_voted = true; } - return poll - })(_post.poll) + return poll; + })(_post.poll); } // Fetch my reaction @@ -176,13 +176,13 @@ const self = ( return reaction.reaction; } - return null + return null; })(); } } // resolve promises in _post object - _post = await rap(_post) + _post = await rap(_post); resolve(_post); }); diff --git a/src/api/serializers/user.ts b/src/api/serializers/user.ts index 3527921de..d00f07389 100644 --- a/src/api/serializers/user.ts +++ b/src/api/serializers/user.ts @@ -112,7 +112,7 @@ export default ( deleted_at: { $exists: false } }); return follow !== null; - })() + })(); // If the user is followed _user.is_followed = (async () => { @@ -122,7 +122,7 @@ export default ( deleted_at: { $exists: false } }); return follow2 !== null; - })() + })(); } if (opts.detail) { @@ -153,7 +153,7 @@ export default ( } // resolve promises in _user object - _user = await rap(_user) + _user = await rap(_user); resolve(_user); }); From 09baf205ead75eab3eaf0f3de82215665c2a3e73 Mon Sep 17 00:00:00 2001 From: otofune Date: Sun, 5 Nov 2017 22:29:58 +0900 Subject: [PATCH 038/327] remove ^ from @prezzemolo/rap dependency --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 1e6e8d813..c3a093420 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "clean": "gulp clean", "cleanall": "gulp cleanall", "lint": "gulp lint", - "test": "gulp test" + "test": "gulp test" }, "devDependencies": { "@types/bcryptjs": "2.4.0", @@ -95,7 +95,7 @@ "webpack": "3.8.1" }, "dependencies": { - "@prezzemolo/rap": "^0.1.0", + "@prezzemolo/rap": "0.1.0", "accesses": "2.5.0", "animejs": "2.2.0", "autwh": "0.0.1", From 327d2705b4a3dad6ef8a8dfa8165c25a3a40d109 Mon Sep 17 00:00:00 2001 From: otofune Date: Sun, 5 Nov 2017 22:37:00 +0900 Subject: [PATCH 039/327] update @prezzemolo/rap to 0.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c3a093420..27e292cc1 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "webpack": "3.8.1" }, "dependencies": { - "@prezzemolo/rap": "0.1.0", + "@prezzemolo/rap": "0.1.1", "accesses": "2.5.0", "animejs": "2.2.0", "autwh": "0.0.1", From ac2a0f46cd9ee877adda57bb939a1b31f7109911 Mon Sep 17 00:00:00 2001 From: otofune Date: Sun, 5 Nov 2017 22:47:04 +0900 Subject: [PATCH 040/327] update @prezzemolo/rap to 0.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 27e292cc1..6ea91a7f5 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "webpack": "3.8.1" }, "dependencies": { - "@prezzemolo/rap": "0.1.1", + "@prezzemolo/rap": "0.1.2", "accesses": "2.5.0", "animejs": "2.2.0", "autwh": "0.0.1", From 55fc8de44d18deb6cd89c887895b6b6d30bcd229 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 5 Nov 2017 20:40:07 +0000 Subject: [PATCH 041/327] fix(package): update riot to version 3.7.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 051eb1cb8..7a7cd1c09 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,7 @@ "redis": "2.8.0", "request": "2.83.0", "rimraf": "2.6.2", - "riot": "3.7.3", + "riot": "3.7.4", "rndstr": "1.0.0", "s-age": "1.1.0", "serve-favicon": "2.4.5", From 7e81e0db6ac1289ae9504f7e3da5db6e56f41a51 Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 14:37:00 +0900 Subject: [PATCH 042/327] support GridFS --- src/api/common/add-file-to-drive.ts | 37 ++++++++++++++++++----------- src/api/models/drive-file.ts | 15 ++++++++++-- src/db/mongodb.ts | 35 +++++++++++++++++++++++---- 3 files changed, 67 insertions(+), 20 deletions(-) diff --git a/src/api/common/add-file-to-drive.ts b/src/api/common/add-file-to-drive.ts index 714eeb520..f48f0cbcf 100644 --- a/src/api/common/add-file-to-drive.ts +++ b/src/api/common/add-file-to-drive.ts @@ -4,14 +4,27 @@ import * as gm from 'gm'; import * as debug from 'debug'; import fileType = require('file-type'); import prominence = require('prominence'); -import DriveFile from '../models/drive-file'; +import DriveFile, { getGridFSBucket } from '../models/drive-file'; import DriveFolder from '../models/drive-folder'; import serialize from '../serializers/drive-file'; import event from '../event'; import config from '../../conf'; +import { Duplex } from 'stream'; const log = debug('misskey:register-drive-file'); +const addToGridFS = (name, binary, metadata): Promise => new Promise(async (resolve, reject) => { + const dataStream = new Duplex() + dataStream.push(binary) + dataStream.push(null) + + const bucket = await getGridFSBucket() + const writeStream = bucket.openUploadStream(name, { metadata }) + writeStream.once('finish', (doc) => { resolve(doc) }) + writeStream.on('error', reject) + dataStream.pipe(writeStream) +}) + /** * Add file to drive * @@ -58,7 +71,7 @@ export default ( // Generate hash const hash = crypto - .createHash('sha256') + .createHash('md5') .update(data) .digest('hex') as string; @@ -67,8 +80,10 @@ export default ( if (!force) { // Check if there is a file with the same hash const much = await DriveFile.findOne({ - user_id: user._id, - hash: hash + md5: hash, + metadata: { + user_id: user._id + } }); if (much !== null) { @@ -82,13 +97,13 @@ export default ( // Calculate drive usage const usage = ((await DriveFile .aggregate([ - { $match: { user_id: user._id } }, + { $match: { metadata: { user_id: user._id } } }, { $project: { - datasize: true + length: true }}, { $group: { _id: null, - usage: { $sum: '$datasize' } + usage: { $sum: '$length' } }} ]))[0] || { usage: 0 @@ -131,21 +146,15 @@ export default ( } // Create DriveFile document - const file = await DriveFile.insert({ - created_at: new Date(), + const file = await addToGridFS(`${user._id}/${name}`, data, { user_id: user._id, folder_id: folder !== null ? folder._id : null, - data: data, - datasize: size, type: mime, name: name, comment: comment, - hash: hash, properties: properties }); - delete file.data; - log(`drive file has been created ${file._id}`); resolve(file); diff --git a/src/api/models/drive-file.ts b/src/api/models/drive-file.ts index 8d158cf56..79a87f657 100644 --- a/src/api/models/drive-file.ts +++ b/src/api/models/drive-file.ts @@ -1,11 +1,22 @@ -import db from '../../db/mongodb'; +import * as mongodb from 'mongodb'; +import monkDb, { nativeDbConn } from '../../db/mongodb'; -const collection = db.get('drive_files'); +const collection = monkDb.get('drive_files.files'); (collection as any).createIndex('hash'); // fuck type definition export default collection as any; // fuck type definition +const getGridFSBucket = async (): Promise => { + const db = await nativeDbConn() + const bucket = new mongodb.GridFSBucket(db, { + bucketName: 'drive_files' + }) + return bucket +} + +export { getGridFSBucket } + export function validateFileName(name: string): boolean { return ( (name.trim().length > 0) && diff --git a/src/db/mongodb.ts b/src/db/mongodb.ts index 6ee7f4534..75f1a1d3c 100644 --- a/src/db/mongodb.ts +++ b/src/db/mongodb.ts @@ -1,11 +1,38 @@ -import * as mongo from 'monk'; - import config from '../conf'; const uri = config.mongodb.user && config.mongodb.pass - ? `mongodb://${config.mongodb.user}:${config.mongodb.pass}@${config.mongodb.host}:${config.mongodb.port}/${config.mongodb.db}` - : `mongodb://${config.mongodb.host}:${config.mongodb.port}/${config.mongodb.db}`; +? `mongodb://${config.mongodb.user}:${config.mongodb.pass}@${config.mongodb.host}:${config.mongodb.port}/${config.mongodb.db}` +: `mongodb://${config.mongodb.host}:${config.mongodb.port}/${config.mongodb.db}`; + +/** + * monk + */ +import * as mongo from 'monk'; const db = mongo(uri); export default db; + +/** + * MongoDB native module (officialy) + */ +import * as mongodb from 'mongodb' + +let mdb: mongodb.Db; + +const nativeDbConn = async (): Promise => { + if (mdb) return mdb; + + const db = await ((): Promise => new Promise((resolve, reject) => { + mongodb.MongoClient.connect(uri, (e, db) => { + if (e) return reject(e) + resolve(db) + }) + }))() + + mdb = db + + return db +} + +export { nativeDbConn } From 18b1ef29adc6166c2b1a327b378c3e159a18b80c Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 15:18:45 +0900 Subject: [PATCH 043/327] migration to GridFS's DriveFile --- src/api/common/add-file-to-drive.ts | 1 + src/api/endpoints/drive.ts | 6 ++-- src/api/endpoints/drive/files.ts | 9 +++-- src/api/endpoints/drive/files/find.ts | 10 +++--- src/api/endpoints/drive/files/show.ts | 6 ++-- src/api/endpoints/drive/files/update.ts | 31 +++++++++-------- .../endpoints/messaging/messages/create.ts | 6 ++-- src/api/endpoints/posts/create.ts | 6 ++-- src/api/endpoints/posts/timeline.ts | 24 +++++++------- src/api/serializers/drive-file.ts | 33 ++++++++----------- src/api/serializers/drive-folder.ts | 4 ++- 11 files changed, 66 insertions(+), 70 deletions(-) diff --git a/src/api/common/add-file-to-drive.ts b/src/api/common/add-file-to-drive.ts index f48f0cbcf..376c470e9 100644 --- a/src/api/common/add-file-to-drive.ts +++ b/src/api/common/add-file-to-drive.ts @@ -154,6 +154,7 @@ export default ( comment: comment, properties: properties }); + console.dir(file) log(`drive file has been created ${file._id}`); diff --git a/src/api/endpoints/drive.ts b/src/api/endpoints/drive.ts index 41ad6301d..b9c4e3e50 100644 --- a/src/api/endpoints/drive.ts +++ b/src/api/endpoints/drive.ts @@ -14,16 +14,16 @@ module.exports = (params, user) => new Promise(async (res, rej) => { // Calculate drive usage const usage = ((await DriveFile .aggregate([ - { $match: { user_id: user._id } }, + { $match: { metadata: { user_id: user._id } } }, { $project: { - datasize: true + length: true } }, { $group: { _id: null, - usage: { $sum: '$datasize' } + usage: { $sum: '$length' } } } ]))[0] || { diff --git a/src/api/endpoints/drive/files.ts b/src/api/endpoints/drive/files.ts index a68ae3481..eb0bfe6ba 100644 --- a/src/api/endpoints/drive/files.ts +++ b/src/api/endpoints/drive/files.ts @@ -40,8 +40,10 @@ module.exports = (params, user, app) => new Promise(async (res, rej) => { _id: -1 }; const query = { - user_id: user._id, - folder_id: folderId + metadata: { + user_id: user._id, + folder_id: folderId + } } as any; if (sinceId) { sort._id = 1; @@ -57,9 +59,6 @@ module.exports = (params, user, app) => new Promise(async (res, rej) => { // Issue query const files = await DriveFile .find(query, { - fields: { - data: false - }, limit: limit, sort: sort }); diff --git a/src/api/endpoints/drive/files/find.ts b/src/api/endpoints/drive/files/find.ts index cd0b33f2c..255faf94e 100644 --- a/src/api/endpoints/drive/files/find.ts +++ b/src/api/endpoints/drive/files/find.ts @@ -24,12 +24,10 @@ module.exports = (params, user) => new Promise(async (res, rej) => { // Issue query const files = await DriveFile .find({ - name: name, - user_id: user._id, - folder_id: folderId - }, { - fields: { - data: false + metadata: { + name: name, + user_id: user._id, + folder_id: folderId } }); diff --git a/src/api/endpoints/drive/files/show.ts b/src/api/endpoints/drive/files/show.ts index 8dbc297e4..9135a04c5 100644 --- a/src/api/endpoints/drive/files/show.ts +++ b/src/api/endpoints/drive/files/show.ts @@ -21,10 +21,8 @@ module.exports = (params, user) => new Promise(async (res, rej) => { const file = await DriveFile .findOne({ _id: fileId, - user_id: user._id - }, { - fields: { - data: false + metadata: { + user_id: user._id } }); diff --git a/src/api/endpoints/drive/files/update.ts b/src/api/endpoints/drive/files/update.ts index 1cfbdd8f0..c4d267368 100644 --- a/src/api/endpoints/drive/files/update.ts +++ b/src/api/endpoints/drive/files/update.ts @@ -20,25 +20,29 @@ module.exports = (params, user) => new Promise(async (res, rej) => { const [fileId, fileIdErr] = $(params.file_id).id().$; if (fileIdErr) return rej('invalid file_id param'); + console.dir(user) + // Fetch file const file = await DriveFile .findOne({ _id: fileId, - user_id: user._id - }, { - fields: { - data: false + metadata: { + user_id: user._id } }); + console.dir(file) + if (file === null) { return rej('file-not-found'); } + const updateQuery: any = {} + // Get 'name' parameter const [name, nameErr] = $(params.name).optional.string().pipe(validateFileName).$; if (nameErr) return rej('invalid name param'); - if (name) file.name = name; + if (name) updateQuery.name = name; // Get 'folder_id' parameter const [folderId, folderIdErr] = $(params.folder_id).optional.nullable.id().$; @@ -46,7 +50,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { if (folderId !== undefined) { if (folderId === null) { - file.folder_id = null; + updateQuery.folder_id = null; } else { // Fetch folder const folder = await DriveFolder @@ -59,19 +63,20 @@ module.exports = (params, user) => new Promise(async (res, rej) => { return rej('folder-not-found'); } - file.folder_id = folder._id; + updateQuery.folder_id = folder._id; } } - DriveFile.update(file._id, { - $set: { - name: file.name, - folder_id: file.folder_id - } + const updated = await DriveFile.update(file._id, { + $set: { metadata: updateQuery } }); + console.dir(updated) + // Serialize - const fileObj = await serialize(file); + const fileObj = await serialize(updated); + + console.dir(fileObj) // Response res(fileObj); diff --git a/src/api/endpoints/messaging/messages/create.ts b/src/api/endpoints/messaging/messages/create.ts index 8af55d850..1d186268f 100644 --- a/src/api/endpoints/messaging/messages/create.ts +++ b/src/api/endpoints/messaging/messages/create.ts @@ -54,9 +54,9 @@ module.exports = (params, user) => new Promise(async (res, rej) => { if (fileId !== undefined) { file = await DriveFile.findOne({ _id: fileId, - user_id: user._id - }, { - data: false + metadata: { + user_id: user._id + } }); if (file === null) { diff --git a/src/api/endpoints/posts/create.ts b/src/api/endpoints/posts/create.ts index f982b9ee9..150763977 100644 --- a/src/api/endpoints/posts/create.ts +++ b/src/api/endpoints/posts/create.ts @@ -44,9 +44,9 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => { // SELECT _id const entity = await DriveFile.findOne({ _id: mediaId, - user_id: user._id - }, { - _id: true + metadata: { + user_id: user._id + } }); if (entity === null) { diff --git a/src/api/endpoints/posts/timeline.ts b/src/api/endpoints/posts/timeline.ts index aa5aff5ba..496de62b6 100644 --- a/src/api/endpoints/posts/timeline.ts +++ b/src/api/endpoints/posts/timeline.ts @@ -2,6 +2,7 @@ * Module dependencies */ import $ from 'cafy'; +import rap from '@prezzemolo/rap'; import Post from '../../models/post'; import ChannelWatching from '../../models/channel-watching'; import getFriends from '../../common/get-friends'; @@ -33,14 +34,15 @@ module.exports = (params, user, app) => new Promise(async (res, rej) => { return rej('cannot set since_id and max_id'); } - // ID list of the user itself and other users who the user follows - const followingIds = await getFriends(user._id); - - // Watchしているチャンネルを取得 - const watches = await ChannelWatching.find({ - user_id: user._id, - // 削除されたドキュメントは除く - deleted_at: { $exists: false } + const { followingIds, watchChannelIds } = await rap({ + // ID list of the user itself and other users who the user follows + followingIds: getFriends(user._id), + // Watchしているチャンネルを取得 + watchChannelIds: ChannelWatching.find({ + user_id: user._id, + // 削除されたドキュメントは除く + deleted_at: { $exists: false } + }).then(watches => watches.map(w => w.channel_id)) }); //#region Construct query @@ -65,7 +67,7 @@ module.exports = (params, user, app) => new Promise(async (res, rej) => { }, { // Watchしているチャンネルへの投稿 channel_id: { - $in: watches.map(w => w.channel_id) + $in: watchChannelIds } }] } as any; @@ -90,7 +92,5 @@ module.exports = (params, user, app) => new Promise(async (res, rej) => { }); // Serialize - res(await Promise.all(timeline.map(async post => - await serialize(post, user) - ))); + res(Promise.all(timeline.map(post => serialize(post, user)))); }); diff --git a/src/api/serializers/drive-file.ts b/src/api/serializers/drive-file.ts index b4e2ab064..4c750f4c6 100644 --- a/src/api/serializers/drive-file.ts +++ b/src/api/serializers/drive-file.ts @@ -31,44 +31,37 @@ export default ( if (mongo.ObjectID.prototype.isPrototypeOf(file)) { _file = await DriveFile.findOne({ _id: file - }, { - fields: { - data: false - } - }); + }); } else if (typeof file === 'string') { _file = await DriveFile.findOne({ _id: new mongo.ObjectID(file) - }, { - fields: { - data: false - } - }); + }); } else { _file = deepcopy(file); } - // Rename _id to id - _file.id = _file._id; - delete _file._id; + // rendered target + let _target: any = {}; - delete _file.data; + _target.id = _file._id; - _file.url = `${config.drive_url}/${_file.id}/${encodeURIComponent(_file.name)}`; + _target = Object.assign(_target, _file.metadata); - if (opts.detail && _file.folder_id) { + _target.url = `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`; + + if (opts.detail && _target.folder_id) { // Populate folder - _file.folder = await serializeDriveFolder(_file.folder_id, { + _target.folder = await serializeDriveFolder(_target.folder_id, { detail: true }); } - if (opts.detail && _file.tags) { + if (opts.detail && _target.tags) { // Populate tags - _file.tags = await _file.tags.map(async (tag: any) => + _target.tags = await _target.tags.map(async (tag: any) => await serializeDriveTag(tag) ); } - resolve(_file); + resolve(_target); }); diff --git a/src/api/serializers/drive-folder.ts b/src/api/serializers/drive-folder.ts index a42846410..3b5f61aee 100644 --- a/src/api/serializers/drive-folder.ts +++ b/src/api/serializers/drive-folder.ts @@ -44,7 +44,9 @@ const self = ( }); const childFilesCount = await DriveFile.count({ - folder_id: _folder.id + metadata: { + folder_id: _folder.id + } }); _folder.folders_count = childFoldersCount; From d0dab265f40a37cd715b7d4b64a364c78a7a35b9 Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 15:27:16 +0900 Subject: [PATCH 044/327] serializers - drive-file: add created_at field by uploadedDate --- src/api/serializers/drive-file.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/serializers/drive-file.ts b/src/api/serializers/drive-file.ts index 4c750f4c6..f98cdaa59 100644 --- a/src/api/serializers/drive-file.ts +++ b/src/api/serializers/drive-file.ts @@ -44,6 +44,7 @@ export default ( let _target: any = {}; _target.id = _file._id; + _target.created_at = _file.uploadDate _target = Object.assign(_target, _file.metadata); From a5160a1bbaa3dd75d7ef45b305a90020317e95a8 Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 15:35:20 +0900 Subject: [PATCH 045/327] fileserver - support DriveFile w/ GridFS --- src/file/server.ts | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/file/server.ts b/src/file/server.ts index ee67cf786..bd29e13c5 100644 --- a/src/file/server.ts +++ b/src/file/server.ts @@ -9,7 +9,7 @@ import * as cors from 'cors'; import * as mongodb from 'mongodb'; import * as gm from 'gm'; -import File from '../api/models/drive-file'; +import DriveFile, { getGridFSBucket } from '../api/models/drive-file'; /** * Init app @@ -97,17 +97,28 @@ app.get('/:id', async (req, res) => { return; } - const file = await File.findOne({ _id: new mongodb.ObjectID(req.params.id) }); + const fileId = new mongodb.ObjectID(req.params.id) + const file = await DriveFile.findOne({ _id: fileId }); if (file == null) { res.status(404).sendFile(`${__dirname} / assets / dummy.png`); return; - } else if (file.data == null) { - res.sendStatus(400); - return; } - send(file.data.buffer, file.type, req, res); + const bucket = await getGridFSBucket() + + const buffer = await ((id): Promise => new Promise((resolve, reject) => { + const chunks = [] + const readableStream = bucket.openDownloadStream(id) + readableStream.on('data', chunk => { + chunks.push(chunk); + }) + readableStream.on('end', () => { + resolve(Buffer.concat(chunks)) + }) + }))(fileId) + + send(buffer, file.metadata.type, req, res); }); app.get('/:id/:name', async (req, res) => { From 2ce3179d5000501391b020dd98385aab9fed8094 Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 15:37:04 +0900 Subject: [PATCH 046/327] fileserver - fix dummy path --- src/file/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/file/server.ts b/src/file/server.ts index bd29e13c5..068e88546 100644 --- a/src/file/server.ts +++ b/src/file/server.ts @@ -101,7 +101,7 @@ app.get('/:id', async (req, res) => { const file = await DriveFile.findOne({ _id: fileId }); if (file == null) { - res.status(404).sendFile(`${__dirname} / assets / dummy.png`); + res.status(404).sendFile(`${__dirname}/assets/dummy.png`); return; } From 28a39bccf96549a35ef77c10dce5f90f9f8cc654 Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 15:39:16 +0900 Subject: [PATCH 047/327] file-server - support new DriveFile w/ GridFS on '/:id/:name' --- src/file/server.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/file/server.ts b/src/file/server.ts index 068e88546..f38599b89 100644 --- a/src/file/server.ts +++ b/src/file/server.ts @@ -128,17 +128,28 @@ app.get('/:id/:name', async (req, res) => { return; } - const file = await File.findOne({ _id: new mongodb.ObjectID(req.params.id) }); + const fileId = new mongodb.ObjectID(req.params.id) + const file = await DriveFile.findOne({ _id: fileId }); if (file == null) { res.status(404).sendFile(`${__dirname}/assets/dummy.png`); return; - } else if (file.data == null) { - res.sendStatus(400); - return; } - send(file.data.buffer, file.type, req, res); + const bucket = await getGridFSBucket() + + const buffer = await ((id): Promise => new Promise((resolve, reject) => { + const chunks = [] + const readableStream = bucket.openDownloadStream(id) + readableStream.on('data', chunk => { + chunks.push(chunk); + }) + readableStream.on('end', () => { + resolve(Buffer.concat(chunks)) + }) + }))(fileId) + + send(buffer, file.metadata.type, req, res); }); module.exports = app; From 0ee6d6592113c5b2df071f4451cb1c1697b59d61 Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 15:45:21 +0900 Subject: [PATCH 048/327] fix timeline --- src/api/endpoints/posts/timeline.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/endpoints/posts/timeline.ts b/src/api/endpoints/posts/timeline.ts index 496de62b6..19578e59b 100644 --- a/src/api/endpoints/posts/timeline.ts +++ b/src/api/endpoints/posts/timeline.ts @@ -92,5 +92,5 @@ module.exports = (params, user, app) => new Promise(async (res, rej) => { }); // Serialize - res(Promise.all(timeline.map(post => serialize(post, user)))); + res(await Promise.all(timeline.map(post => serialize(post, user)))); }); From 7553c6dd38c6f8574894a009238d946d50c53477 Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 15:52:09 +0900 Subject: [PATCH 049/327] serializers - posts: no need Promise wrapping --- src/api/serializers/post.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/serializers/post.ts b/src/api/serializers/post.ts index e1ab78435..d1dcb6600 100644 --- a/src/api/serializers/post.ts +++ b/src/api/serializers/post.ts @@ -22,13 +22,13 @@ import rap from '@prezzemolo/rap'; * @param options? serialize options * @return response */ -const self = ( +const self = async ( post: string | mongo.ObjectID | IPost, me?: string | mongo.ObjectID | IUser, options?: { detail: boolean } -) => new Promise(async (resolve, reject) => { +) => { const opts = options || { detail: true, }; @@ -184,7 +184,7 @@ const self = ( // resolve promises in _post object _post = await rap(_post); - resolve(_post); -}); + return _post; +}; export default self; From 7b1fc2c5d62e229542e9411a29e078236a9d96db Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 15:55:47 +0900 Subject: [PATCH 050/327] api - endpoint:timeline: unneed promise wrapping --- src/api/endpoints/posts/timeline.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/api/endpoints/posts/timeline.ts b/src/api/endpoints/posts/timeline.ts index 19578e59b..978825a10 100644 --- a/src/api/endpoints/posts/timeline.ts +++ b/src/api/endpoints/posts/timeline.ts @@ -16,22 +16,22 @@ import serialize from '../../serializers/post'; * @param {any} app * @return {Promise} */ -module.exports = (params, user, app) => new Promise(async (res, rej) => { +module.exports = async (params, user, app) => { // Get 'limit' parameter const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); + if (limitErr) throw 'invalid limit param'; // Get 'since_id' parameter const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; - if (sinceIdErr) return rej('invalid since_id param'); + if (sinceIdErr) throw 'invalid since_id param'; // Get 'max_id' parameter const [maxId, maxIdErr] = $(params.max_id).optional.id().$; - if (maxIdErr) return rej('invalid max_id param'); + if (maxIdErr) throw 'invalid max_id param'; // Check if both of since_id and max_id is specified if (sinceId && maxId) { - return rej('cannot set since_id and max_id'); + throw 'cannot set since_id and max_id'; } const { followingIds, watchChannelIds } = await rap({ @@ -92,5 +92,6 @@ module.exports = (params, user, app) => new Promise(async (res, rej) => { }); // Serialize - res(await Promise.all(timeline.map(post => serialize(post, user)))); -}); + const _timeline = await Promise.all(timeline.map(post => serialize(post, user))) + return _timeline +}; From b50813649afed671b75189551342b179d8cd60f7 Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 15:58:39 +0900 Subject: [PATCH 051/327] serializers - posts: fix awaiting --- src/api/serializers/post.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/serializers/post.ts b/src/api/serializers/post.ts index d1dcb6600..5788b226f 100644 --- a/src/api/serializers/post.ts +++ b/src/api/serializers/post.ts @@ -94,7 +94,7 @@ const self = async ( if (opts.detail) { // Get previous post info _post.prev = (async () => { - const prev = Post.findOne({ + const prev = await Post.findOne({ user_id: _post.user_id, _id: { $lt: id From 5279d062df205514f1f3cf95e3aab4fee425a3e4 Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 16:09:51 +0900 Subject: [PATCH 052/327] fix --- src/api/endpoints/drive/files.ts | 18 +++++++++--------- src/api/endpoints/drive/files/show.ts | 14 ++++++++------ src/api/endpoints/drive/folders/find.ts | 3 +-- src/api/serializers/drive-file.ts | 2 ++ 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/api/endpoints/drive/files.ts b/src/api/endpoints/drive/files.ts index eb0bfe6ba..41687c499 100644 --- a/src/api/endpoints/drive/files.ts +++ b/src/api/endpoints/drive/files.ts @@ -13,27 +13,27 @@ import serialize from '../../serializers/drive-file'; * @param {any} app * @return {Promise} */ -module.exports = (params, user, app) => new Promise(async (res, rej) => { +module.exports = async (params, user, app) => { // Get 'limit' parameter const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); + if (limitErr) throw 'invalid limit param'; // Get 'since_id' parameter const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; - if (sinceIdErr) return rej('invalid since_id param'); + if (sinceIdErr) throw 'invalid since_id param'; // Get 'max_id' parameter const [maxId, maxIdErr] = $(params.max_id).optional.id().$; - if (maxIdErr) return rej('invalid max_id param'); + if (maxIdErr) throw 'invalid max_id param'; // Check if both of since_id and max_id is specified if (sinceId && maxId) { - return rej('cannot set since_id and max_id'); + throw 'cannot set since_id and max_id'; } // Get 'folder_id' parameter const [folderId = null, folderIdErr] = $(params.folder_id).optional.nullable.id().$; - if (folderIdErr) return rej('invalid folder_id param'); + if (folderIdErr) throw 'invalid folder_id param'; // Construct query const sort = { @@ -64,6 +64,6 @@ module.exports = (params, user, app) => new Promise(async (res, rej) => { }); // Serialize - res(await Promise.all(files.map(async file => - await serialize(file)))); -}); + const _files = await Promise.all(files.map(file => serialize(file))); + return _files +}; diff --git a/src/api/endpoints/drive/files/show.ts b/src/api/endpoints/drive/files/show.ts index 9135a04c5..883034600 100644 --- a/src/api/endpoints/drive/files/show.ts +++ b/src/api/endpoints/drive/files/show.ts @@ -12,10 +12,10 @@ import serialize from '../../../serializers/drive-file'; * @param {any} user * @return {Promise} */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = async (params, user) => { // Get 'file_id' parameter const [fileId, fileIdErr] = $(params.file_id).id().$; - if (fileIdErr) return rej('invalid file_id param'); + if (fileIdErr) throw 'invalid file_id param'; // Fetch file const file = await DriveFile @@ -27,11 +27,13 @@ module.exports = (params, user) => new Promise(async (res, rej) => { }); if (file === null) { - return rej('file-not-found'); + throw 'file-not-found'; } // Serialize - res(await serialize(file, { + const _file = await serialize(file, { detail: true - })); -}); + }); + + return _file +}; diff --git a/src/api/endpoints/drive/folders/find.ts b/src/api/endpoints/drive/folders/find.ts index cdf055839..a5eb8e015 100644 --- a/src/api/endpoints/drive/folders/find.ts +++ b/src/api/endpoints/drive/folders/find.ts @@ -30,6 +30,5 @@ module.exports = (params, user) => new Promise(async (res, rej) => { }); // Serialize - res(await Promise.all(folders.map(async folder => - await serialize(folder)))); + res(await Promise.all(folders.map(folder => serialize(folder)))); }); diff --git a/src/api/serializers/drive-file.ts b/src/api/serializers/drive-file.ts index f98cdaa59..9858c3b3c 100644 --- a/src/api/serializers/drive-file.ts +++ b/src/api/serializers/drive-file.ts @@ -25,6 +25,8 @@ export default ( detail: false }, options); + if (!file) return reject('invalid file arg.') + let _file: any; // Populate the file if 'file' is ID From b266ed3e4f98ab16d95e52cff517d6519b78742a Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 16:11:24 +0900 Subject: [PATCH 053/327] fix --- src/api/serializers/drive-file.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/serializers/drive-file.ts b/src/api/serializers/drive-file.ts index 9858c3b3c..e749f8038 100644 --- a/src/api/serializers/drive-file.ts +++ b/src/api/serializers/drive-file.ts @@ -25,8 +25,6 @@ export default ( detail: false }, options); - if (!file) return reject('invalid file arg.') - let _file: any; // Populate the file if 'file' is ID @@ -42,6 +40,8 @@ export default ( _file = deepcopy(file); } + if (!_file) return reject('invalid file arg.') + // rendered target let _target: any = {}; From 64be0d6deddef4b8caced377dc22f94425cc4358 Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 16:22:18 +0900 Subject: [PATCH 054/327] =?UTF-8?q?MongoDB=E3=81=AE=E9=9A=8E=E5=B1=A4?= =?UTF-8?q?=E6=A7=8B=E9=80=A0=E6=A4=9C=E7=B4=A2=E3=81=AB=E9=96=A2=E3=81=99?= =?UTF-8?q?=E3=82=8B=E6=80=9D=E3=81=84=E9=81=95=E3=81=84=E3=81=AE=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/endpoints/drive.ts | 2 +- src/api/endpoints/drive/files.ts | 6 ++---- src/api/endpoints/drive/files/find.ts | 8 +++----- src/api/endpoints/drive/files/show.ts | 4 +--- src/api/endpoints/drive/files/update.ts | 19 +++++-------------- .../endpoints/messaging/messages/create.ts | 4 +--- src/api/endpoints/posts/create.ts | 4 +--- src/api/serializers/drive-folder.ts | 4 +--- 8 files changed, 15 insertions(+), 36 deletions(-) diff --git a/src/api/endpoints/drive.ts b/src/api/endpoints/drive.ts index b9c4e3e50..d92473633 100644 --- a/src/api/endpoints/drive.ts +++ b/src/api/endpoints/drive.ts @@ -14,7 +14,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { // Calculate drive usage const usage = ((await DriveFile .aggregate([ - { $match: { metadata: { user_id: user._id } } }, + { $match: { 'metadata.user_id': user._id } }, { $project: { length: true diff --git a/src/api/endpoints/drive/files.ts b/src/api/endpoints/drive/files.ts index 41687c499..035916b30 100644 --- a/src/api/endpoints/drive/files.ts +++ b/src/api/endpoints/drive/files.ts @@ -40,10 +40,8 @@ module.exports = async (params, user, app) => { _id: -1 }; const query = { - metadata: { - user_id: user._id, - folder_id: folderId - } + 'metadata.user_id': user._id, + 'metadata.folder_id': folderId } as any; if (sinceId) { sort._id = 1; diff --git a/src/api/endpoints/drive/files/find.ts b/src/api/endpoints/drive/files/find.ts index 255faf94e..1c818131d 100644 --- a/src/api/endpoints/drive/files/find.ts +++ b/src/api/endpoints/drive/files/find.ts @@ -24,11 +24,9 @@ module.exports = (params, user) => new Promise(async (res, rej) => { // Issue query const files = await DriveFile .find({ - metadata: { - name: name, - user_id: user._id, - folder_id: folderId - } + 'metadata.name': name, + 'metadata.user_id': user._id, + 'metadata.folder_id': folderId }); // Serialize diff --git a/src/api/endpoints/drive/files/show.ts b/src/api/endpoints/drive/files/show.ts index 883034600..0a19b1993 100644 --- a/src/api/endpoints/drive/files/show.ts +++ b/src/api/endpoints/drive/files/show.ts @@ -21,9 +21,7 @@ module.exports = async (params, user) => { const file = await DriveFile .findOne({ _id: fileId, - metadata: { - user_id: user._id - } + 'metadata.user_id': user._id }); if (file === null) { diff --git a/src/api/endpoints/drive/files/update.ts b/src/api/endpoints/drive/files/update.ts index c4d267368..7a6d2562f 100644 --- a/src/api/endpoints/drive/files/update.ts +++ b/src/api/endpoints/drive/files/update.ts @@ -20,19 +20,14 @@ module.exports = (params, user) => new Promise(async (res, rej) => { const [fileId, fileIdErr] = $(params.file_id).id().$; if (fileIdErr) return rej('invalid file_id param'); - console.dir(user) // Fetch file const file = await DriveFile .findOne({ _id: fileId, - metadata: { - user_id: user._id - } + 'metadata.user_id': user._id }); - console.dir(file) - if (file === null) { return rej('file-not-found'); } @@ -42,7 +37,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { // Get 'name' parameter const [name, nameErr] = $(params.name).optional.string().pipe(validateFileName).$; if (nameErr) return rej('invalid name param'); - if (name) updateQuery.name = name; + if (name) updateQuery['metadata.name'] = name; // Get 'folder_id' parameter const [folderId, folderIdErr] = $(params.folder_id).optional.nullable.id().$; @@ -50,7 +45,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { if (folderId !== undefined) { if (folderId === null) { - updateQuery.folder_id = null; + updateQuery['metadata.folder_id'] = null; } else { // Fetch folder const folder = await DriveFolder @@ -63,21 +58,17 @@ module.exports = (params, user) => new Promise(async (res, rej) => { return rej('folder-not-found'); } - updateQuery.folder_id = folder._id; + updateQuery['metadata.folder_id'] = folder._id; } } const updated = await DriveFile.update(file._id, { - $set: { metadata: updateQuery } + $set: { updateQuery } }); - console.dir(updated) - // Serialize const fileObj = await serialize(updated); - console.dir(fileObj) - // Response res(fileObj); diff --git a/src/api/endpoints/messaging/messages/create.ts b/src/api/endpoints/messaging/messages/create.ts index 1d186268f..149852c09 100644 --- a/src/api/endpoints/messaging/messages/create.ts +++ b/src/api/endpoints/messaging/messages/create.ts @@ -54,9 +54,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { if (fileId !== undefined) { file = await DriveFile.findOne({ _id: fileId, - metadata: { - user_id: user._id - } + 'metadata.user_id': user._id }); if (file === null) { diff --git a/src/api/endpoints/posts/create.ts b/src/api/endpoints/posts/create.ts index 150763977..4f4b7e2e8 100644 --- a/src/api/endpoints/posts/create.ts +++ b/src/api/endpoints/posts/create.ts @@ -44,9 +44,7 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => { // SELECT _id const entity = await DriveFile.findOne({ _id: mediaId, - metadata: { - user_id: user._id - } + 'metadata.user_id': user._id }); if (entity === null) { diff --git a/src/api/serializers/drive-folder.ts b/src/api/serializers/drive-folder.ts index 3b5f61aee..6ebf454a2 100644 --- a/src/api/serializers/drive-folder.ts +++ b/src/api/serializers/drive-folder.ts @@ -44,9 +44,7 @@ const self = ( }); const childFilesCount = await DriveFile.count({ - metadata: { - folder_id: _folder.id - } + 'metadata.folder_id': _folder.id }); _folder.folders_count = childFoldersCount; From 4c5a4d259738ba617bf29d2158d180cc5fa8401c Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 16:26:17 +0900 Subject: [PATCH 055/327] core - fix metadata searching --- src/api/common/add-file-to-drive.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/api/common/add-file-to-drive.ts b/src/api/common/add-file-to-drive.ts index 376c470e9..1f882389a 100644 --- a/src/api/common/add-file-to-drive.ts +++ b/src/api/common/add-file-to-drive.ts @@ -81,9 +81,7 @@ export default ( // Check if there is a file with the same hash const much = await DriveFile.findOne({ md5: hash, - metadata: { - user_id: user._id - } + 'metadata.user_id': user._id }); if (much !== null) { @@ -97,7 +95,7 @@ export default ( // Calculate drive usage const usage = ((await DriveFile .aggregate([ - { $match: { metadata: { user_id: user._id } } }, + { $match: { 'metadata.user_id': user._id } }, { $project: { length: true }}, From 04648db1c235b0de14d3e0a2dc83f9346d0408f8 Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 16:29:13 +0900 Subject: [PATCH 056/327] remove console --- src/api/common/add-file-to-drive.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/common/add-file-to-drive.ts b/src/api/common/add-file-to-drive.ts index 1f882389a..dff2d5235 100644 --- a/src/api/common/add-file-to-drive.ts +++ b/src/api/common/add-file-to-drive.ts @@ -152,7 +152,6 @@ export default ( comment: comment, properties: properties }); - console.dir(file) log(`drive file has been created ${file._id}`); From d5cc4cc9c28eb6a981ce37859def97cd7c57abc6 Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 16:32:01 +0900 Subject: [PATCH 057/327] fix lint (automattic) --- src/api/common/add-file-to-drive.ts | 18 ++++++------- src/api/endpoints/drive/files.ts | 2 +- src/api/endpoints/drive/files/show.ts | 2 +- src/api/endpoints/drive/files/update.ts | 3 +-- src/api/endpoints/posts/timeline.ts | 4 +-- src/api/models/drive-file.ts | 10 +++---- src/api/serializers/drive-file.ts | 4 +-- src/db/mongodb.ts | 18 ++++++------- src/file/server.ts | 36 ++++++++++++------------- 9 files changed, 48 insertions(+), 49 deletions(-) diff --git a/src/api/common/add-file-to-drive.ts b/src/api/common/add-file-to-drive.ts index dff2d5235..f9c22ccac 100644 --- a/src/api/common/add-file-to-drive.ts +++ b/src/api/common/add-file-to-drive.ts @@ -14,16 +14,16 @@ import { Duplex } from 'stream'; const log = debug('misskey:register-drive-file'); const addToGridFS = (name, binary, metadata): Promise => new Promise(async (resolve, reject) => { - const dataStream = new Duplex() - dataStream.push(binary) - dataStream.push(null) + const dataStream = new Duplex(); + dataStream.push(binary); + dataStream.push(null); - const bucket = await getGridFSBucket() - const writeStream = bucket.openUploadStream(name, { metadata }) - writeStream.once('finish', (doc) => { resolve(doc) }) - writeStream.on('error', reject) - dataStream.pipe(writeStream) -}) + const bucket = await getGridFSBucket(); + const writeStream = bucket.openUploadStream(name, { metadata }); + writeStream.once('finish', (doc) => { resolve(doc); }); + writeStream.on('error', reject); + dataStream.pipe(writeStream); +}); /** * Add file to drive diff --git a/src/api/endpoints/drive/files.ts b/src/api/endpoints/drive/files.ts index 035916b30..53b48a8be 100644 --- a/src/api/endpoints/drive/files.ts +++ b/src/api/endpoints/drive/files.ts @@ -63,5 +63,5 @@ module.exports = async (params, user, app) => { // Serialize const _files = await Promise.all(files.map(file => serialize(file))); - return _files + return _files; }; diff --git a/src/api/endpoints/drive/files/show.ts b/src/api/endpoints/drive/files/show.ts index 0a19b1993..3c7cf774f 100644 --- a/src/api/endpoints/drive/files/show.ts +++ b/src/api/endpoints/drive/files/show.ts @@ -33,5 +33,5 @@ module.exports = async (params, user) => { detail: true }); - return _file + return _file; }; diff --git a/src/api/endpoints/drive/files/update.ts b/src/api/endpoints/drive/files/update.ts index 7a6d2562f..4e56b30ac 100644 --- a/src/api/endpoints/drive/files/update.ts +++ b/src/api/endpoints/drive/files/update.ts @@ -20,7 +20,6 @@ module.exports = (params, user) => new Promise(async (res, rej) => { const [fileId, fileIdErr] = $(params.file_id).id().$; if (fileIdErr) return rej('invalid file_id param'); - // Fetch file const file = await DriveFile .findOne({ @@ -32,7 +31,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { return rej('file-not-found'); } - const updateQuery: any = {} + const updateQuery: any = {}; // Get 'name' parameter const [name, nameErr] = $(params.name).optional.string().pipe(validateFileName).$; diff --git a/src/api/endpoints/posts/timeline.ts b/src/api/endpoints/posts/timeline.ts index 978825a10..203413e23 100644 --- a/src/api/endpoints/posts/timeline.ts +++ b/src/api/endpoints/posts/timeline.ts @@ -92,6 +92,6 @@ module.exports = async (params, user, app) => { }); // Serialize - const _timeline = await Promise.all(timeline.map(post => serialize(post, user))) - return _timeline + const _timeline = await Promise.all(timeline.map(post => serialize(post, user))); + return _timeline; }; diff --git a/src/api/models/drive-file.ts b/src/api/models/drive-file.ts index 79a87f657..8968d065c 100644 --- a/src/api/models/drive-file.ts +++ b/src/api/models/drive-file.ts @@ -8,14 +8,14 @@ const collection = monkDb.get('drive_files.files'); export default collection as any; // fuck type definition const getGridFSBucket = async (): Promise => { - const db = await nativeDbConn() + const db = await nativeDbConn(); const bucket = new mongodb.GridFSBucket(db, { bucketName: 'drive_files' - }) - return bucket -} + }); + return bucket; +}; -export { getGridFSBucket } +export { getGridFSBucket }; export function validateFileName(name: string): boolean { return ( diff --git a/src/api/serializers/drive-file.ts b/src/api/serializers/drive-file.ts index e749f8038..2af7db572 100644 --- a/src/api/serializers/drive-file.ts +++ b/src/api/serializers/drive-file.ts @@ -40,13 +40,13 @@ export default ( _file = deepcopy(file); } - if (!_file) return reject('invalid file arg.') + if (!_file) return reject('invalid file arg.'); // rendered target let _target: any = {}; _target.id = _file._id; - _target.created_at = _file.uploadDate + _target.created_at = _file.uploadDate; _target = Object.assign(_target, _file.metadata); diff --git a/src/db/mongodb.ts b/src/db/mongodb.ts index 75f1a1d3c..c978e6460 100644 --- a/src/db/mongodb.ts +++ b/src/db/mongodb.ts @@ -16,7 +16,7 @@ export default db; /** * MongoDB native module (officialy) */ -import * as mongodb from 'mongodb' +import * as mongodb from 'mongodb'; let mdb: mongodb.Db; @@ -25,14 +25,14 @@ const nativeDbConn = async (): Promise => { const db = await ((): Promise => new Promise((resolve, reject) => { mongodb.MongoClient.connect(uri, (e, db) => { - if (e) return reject(e) - resolve(db) - }) - }))() + if (e) return reject(e); + resolve(db); + }); + }))(); - mdb = db + mdb = db; - return db -} + return db; +}; -export { nativeDbConn } +export { nativeDbConn }; diff --git a/src/file/server.ts b/src/file/server.ts index f38599b89..375f29487 100644 --- a/src/file/server.ts +++ b/src/file/server.ts @@ -97,7 +97,7 @@ app.get('/:id', async (req, res) => { return; } - const fileId = new mongodb.ObjectID(req.params.id) + const fileId = new mongodb.ObjectID(req.params.id); const file = await DriveFile.findOne({ _id: fileId }); if (file == null) { @@ -105,18 +105,18 @@ app.get('/:id', async (req, res) => { return; } - const bucket = await getGridFSBucket() + const bucket = await getGridFSBucket(); const buffer = await ((id): Promise => new Promise((resolve, reject) => { - const chunks = [] - const readableStream = bucket.openDownloadStream(id) - readableStream.on('data', chunk => { + const chunks = []; + const readableStream = bucket.openDownloadStream(id); + readableStream.on('data', chunk => { chunks.push(chunk); - }) + }); readableStream.on('end', () => { - resolve(Buffer.concat(chunks)) - }) - }))(fileId) + resolve(Buffer.concat(chunks)); + }); + }))(fileId); send(buffer, file.metadata.type, req, res); }); @@ -128,7 +128,7 @@ app.get('/:id/:name', async (req, res) => { return; } - const fileId = new mongodb.ObjectID(req.params.id) + const fileId = new mongodb.ObjectID(req.params.id); const file = await DriveFile.findOne({ _id: fileId }); if (file == null) { @@ -136,18 +136,18 @@ app.get('/:id/:name', async (req, res) => { return; } - const bucket = await getGridFSBucket() + const bucket = await getGridFSBucket(); const buffer = await ((id): Promise => new Promise((resolve, reject) => { - const chunks = [] - const readableStream = bucket.openDownloadStream(id) - readableStream.on('data', chunk => { + const chunks = []; + const readableStream = bucket.openDownloadStream(id); + readableStream.on('data', chunk => { chunks.push(chunk); - }) + }); readableStream.on('end', () => { - resolve(Buffer.concat(chunks)) - }) - }))(fileId) + resolve(Buffer.concat(chunks)); + }); + }))(fileId); send(buffer, file.metadata.type, req, res); }); From 3be69a8cb7bacca181fa400f234fd77c1d1d5bde Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 16:49:07 +0900 Subject: [PATCH 058/327] /drive/files/update - return collectly value --- src/api/endpoints/drive/files/update.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/api/endpoints/drive/files/update.ts b/src/api/endpoints/drive/files/update.ts index 4e56b30ac..d7b858c2b 100644 --- a/src/api/endpoints/drive/files/update.ts +++ b/src/api/endpoints/drive/files/update.ts @@ -31,12 +31,10 @@ module.exports = (params, user) => new Promise(async (res, rej) => { return rej('file-not-found'); } - const updateQuery: any = {}; - // Get 'name' parameter const [name, nameErr] = $(params.name).optional.string().pipe(validateFileName).$; if (nameErr) return rej('invalid name param'); - if (name) updateQuery['metadata.name'] = name; + if (name) file.metadata.name = name; // Get 'folder_id' parameter const [folderId, folderIdErr] = $(params.folder_id).optional.nullable.id().$; @@ -44,7 +42,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { if (folderId !== undefined) { if (folderId === null) { - updateQuery['metadata.folder_id'] = null; + file.metadata.folder_id = null; } else { // Fetch folder const folder = await DriveFolder @@ -57,16 +55,19 @@ module.exports = (params, user) => new Promise(async (res, rej) => { return rej('folder-not-found'); } - updateQuery['metadata.folder_id'] = folder._id; + file.metadata.folder_id = folder._id; } } - const updated = await DriveFile.update(file._id, { - $set: { updateQuery } + await DriveFile.update(file._id, { + $set: { + 'metadata.name': file.metadata.name, + 'metadata.folder_id': file.metadata.folder_id + } }); // Serialize - const fileObj = await serialize(updated); + const fileObj = await serialize(file); // Response res(fileObj); From 73bb81de8f17fe603dfde57ae70aa61669161bfc Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 16:59:09 +0900 Subject: [PATCH 059/327] update test for GridFS --- test/api.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/api.js b/test/api.js index b43eb7ff6..c0da9d6c5 100644 --- a/test/api.js +++ b/test/api.js @@ -1152,9 +1152,12 @@ async function insertHimawari(opts) { } async function insertDriveFile(opts) { - return await db.get('drive_files').insert(Object.assign({ - name: 'strawberry-pasta.png' - }, opts)); + return await db.get('drive_files.files').insert({ + length: opts.datasize, + metadata: Object.assign({ + name: 'strawberry-pasta.png' + }, opts) + }); } async function insertDriveFolder(opts) { From 26602dcd209198dead66081f54b1800627e0bff8 Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 17:57:03 +0900 Subject: [PATCH 060/327] migration - add GridFS migration --- tools/migration/use-gridfs.js | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 tools/migration/use-gridfs.js diff --git a/tools/migration/use-gridfs.js b/tools/migration/use-gridfs.js new file mode 100644 index 000000000..d41514416 --- /dev/null +++ b/tools/migration/use-gridfs.js @@ -0,0 +1,49 @@ +// for Node.js interpret + +const { default: db } = require('../../built/db/mongodb') +const { default: DriveFile, getGridFSBucket } = require('../../built/api/models/drive-file') +const { Duplex } = require('stream') + +const writeToGridFS = (bucket, buffer, ...rest) => new Promise((resolve, reject) => { + const writeStream = bucket.openUploadStreamWithId(...rest) + + const dataStream = new Duplex() + dataStream.push(buffer) + dataStream.push(null) + + writeStream.once('finish', resolve) + writeStream.on('error', reject) + + dataStream.pipe(writeStream) +}) + +const migrateToGridFS = async (doc) => { + const id = doc._id + const buffer = doc.data.buffer + const created_at = doc.created_at + + delete doc._id + delete doc.created_at + delete doc.datasize + delete doc.hash + delete doc.data + + const bucket = await getGridFSBucket() + const added = await writeToGridFS(bucket, buffer, id, `${id}/${doc.name}`, { metadata: doc }) + + const result = await DriveFile.update(id, { + $set: { + uploadDate: created_at + } + }) + + return added && result.ok === 1 +} + +const main = async () => { + const docs = await db.get('drive_files').find() + const all = await Promise.all(docs.map(migrateToGridFS)) + return all +} + +main().then(console.dir).catch(console.error) From c1fc3b9f6ec176999932958a7856d160317b7762 Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 18:30:49 +0900 Subject: [PATCH 061/327] add safety guard to serializers & fix importing uncorrect serializer --- src/api/endpoints/drive/folders/update.ts | 2 +- src/api/serializers/post.ts | 2 ++ src/api/serializers/user.ts | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/api/endpoints/drive/folders/update.ts b/src/api/endpoints/drive/folders/update.ts index eec275787..4f2e3d2a7 100644 --- a/src/api/endpoints/drive/folders/update.ts +++ b/src/api/endpoints/drive/folders/update.ts @@ -4,7 +4,7 @@ import $ from 'cafy'; import DriveFolder from '../../../models/drive-folder'; import { isValidFolderName } from '../../../models/drive-folder'; -import serialize from '../../../serializers/drive-file'; +import serialize from '../../../serializers/drive-folder'; import event from '../../../event'; /** diff --git a/src/api/serializers/post.ts b/src/api/serializers/post.ts index 5788b226f..5a63384f0 100644 --- a/src/api/serializers/post.ts +++ b/src/api/serializers/post.ts @@ -57,6 +57,8 @@ const self = async ( _post = deepcopy(post); } + if (!_post) throw 'invalid post arg.'; + const id = _post._id; // Rename _id to id diff --git a/src/api/serializers/user.ts b/src/api/serializers/user.ts index d00f07389..0d24d6cc0 100644 --- a/src/api/serializers/user.ts +++ b/src/api/serializers/user.ts @@ -56,6 +56,8 @@ export default ( _user = deepcopy(user); } + if (!_user) return reject('invalid user arg.'); + // Me const meId: mongo.ObjectID = me ? mongo.ObjectID.prototype.isPrototypeOf(me) From d7e1ffb0055f0786a707015350a14351b8a0fbf0 Mon Sep 17 00:00:00 2001 From: otofune Date: Mon, 6 Nov 2017 18:38:59 +0900 Subject: [PATCH 062/327] remove whitespace --- src/api/serializers/post.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/serializers/post.ts b/src/api/serializers/post.ts index 5a63384f0..03fd12077 100644 --- a/src/api/serializers/post.ts +++ b/src/api/serializers/post.ts @@ -57,7 +57,7 @@ const self = async ( _post = deepcopy(post); } - if (!_post) throw 'invalid post arg.'; + if (!_post) throw 'invalid post arg.'; const id = _post._id; From bc44ea9915adc9fa93ab83cee47dc88ee75eea8b Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 6 Nov 2017 19:16:14 +0900 Subject: [PATCH 063/327] Use tab for indentation --- package.json | 316 +++++++++++++++++++++++++-------------------------- 1 file changed, 158 insertions(+), 158 deletions(-) diff --git a/package.json b/package.json index 2bf77fa14..7fa56321d 100644 --- a/package.json +++ b/package.json @@ -1,161 +1,161 @@ { - "name": "misskey", - "author": "syuilo ", - "version": "0.0.2807", - "license": "MIT", - "description": "A miniblog-based SNS", - "bugs": "https://github.com/syuilo/misskey/issues", - "repository": "https://github.com/syuilo/misskey.git", - "main": "./built/index.js", - "private": true, - "scripts": { - "config": "node ./tools/init.js", - "start": "node ./built", - "debug": "DEBUG=misskey:* node ./built", - "swagger": "node ./swagger.js", - "build": "gulp build", - "rebuild": "gulp rebuild", - "clean": "gulp clean", - "cleanall": "gulp cleanall", - "lint": "gulp lint", + "name": "misskey", + "author": "syuilo ", + "version": "0.0.2807", + "license": "MIT", + "description": "A miniblog-based SNS", + "bugs": "https://github.com/syuilo/misskey/issues", + "repository": "https://github.com/syuilo/misskey.git", + "main": "./built/index.js", + "private": true, + "scripts": { + "config": "node ./tools/init.js", + "start": "node ./built", + "debug": "DEBUG=misskey:* node ./built", + "swagger": "node ./swagger.js", + "build": "gulp build", + "rebuild": "gulp rebuild", + "clean": "gulp clean", + "cleanall": "gulp cleanall", + "lint": "gulp lint", "test": "gulp test" - }, - "devDependencies": { - "@types/bcryptjs": "2.4.1", - "@types/body-parser": "1.16.7", - "@types/chai": "4.0.4", - "@types/chai-http": "3.0.3", - "@types/chalk": "2.2.0", - "@types/compression": "0.0.34", - "@types/cors": "2.8.1", - "@types/debug": "0.0.30", - "@types/deep-equal": "1.0.1", - "@types/elasticsearch": "5.0.17", - "@types/event-stream": "3.3.32", - "@types/express": "4.0.37", - "@types/gm": "1.17.33", - "@types/gulp": "4.0.3", - "@types/gulp-htmlmin": "1.3.30", - "@types/gulp-mocha": "0.0.30", - "@types/gulp-rename": "0.0.32", - "@types/gulp-replace": "0.0.30", - "@types/gulp-tslint": "3.6.31", - "@types/gulp-typescript": "2.13.0", - "@types/gulp-uglify": "3.0.3", - "@types/gulp-util": "3.0.33", - "@types/inquirer": "0.0.34", - "@types/is-root": "1.0.0", - "@types/is-url": "1.2.28", - "@types/js-yaml": "3.9.1", - "@types/mocha": "2.2.44", - "@types/mongodb": "2.2.13", - "@types/monk": "1.0.6", - "@types/morgan": "1.7.35", - "@types/ms": "0.7.30", - "@types/multer": "1.3.5", - "@types/node": "8.0.47", - "@types/ratelimiter": "2.1.28", - "@types/redis": "2.8.1", - "@types/request": "2.0.7", - "@types/rimraf": "2.0.2", - "@types/riot": "3.6.1", - "@types/serve-favicon": "2.2.29", - "@types/uuid": "3.4.3", - "@types/webpack": "3.0.14", - "@types/uuid": "3.4.3", - "@types/webpack": "3.0.13", - "@types/webpack-stream": "3.2.8", - "@types/websocket": "0.0.34", - "awesome-typescript-loader": "3.3.0", - "chai": "4.1.2", - "chai-http": "3.0.0", - "css-loader": "0.28.7", - "event-stream": "3.3.4", - "gulp": "3.9.1", - "gulp-cssnano": "2.1.2", - "gulp-htmlmin": "3.0.0", - "gulp-imagemin": "3.4.0", - "gulp-mocha": "4.3.1", - "gulp-pug": "3.3.0", - "gulp-rename": "1.2.2", - "gulp-replace": "0.6.1", - "gulp-tslint": "8.1.2", - "gulp-typescript": "3.2.3", - "gulp-uglify": "3.0.0", - "gulp-util": "3.0.8", - "mocha": "4.0.1", - "riot-tag-loader": "1.0.0", - "string-replace-webpack-plugin": "0.1.3", - "style-loader": "0.19.0", - "stylus": "0.54.5", - "stylus-loader": "3.0.1", - "swagger-jsdoc": "1.9.7", - "tslint": "5.8.0", - "uglify-es": "3.0.27", - "uglify-js": "git+https://github.com/mishoo/UglifyJS2.git#harmony", - "uglifyjs-webpack-plugin": "1.0.1", - "webpack": "3.8.1" - }, - "dependencies": { - "@prezzemolo/rap": "0.1.2", - "accesses": "2.5.0", - "animejs": "2.2.0", - "autwh": "0.0.1", - "bcryptjs": "2.4.3", - "body-parser": "1.18.2", - "cafy": "3.1.1", - "chalk": "2.3.0", - "compression": "1.7.1", - "cors": "2.8.4", - "cropperjs": "1.1.3", - "crypto": "1.0.1", - "debug": "3.1.0", - "deep-equal": "1.0.1", - "deepcopy": "0.6.3", - "diskusage": "0.2.2", - "download": "6.2.5", - "elasticsearch": "13.3.1", - "escape-regexp": "0.0.1", - "express": "4.15.4", - "file-type": "7.2.0", - "fuckadblock": "3.2.1", - "gm": "1.23.0", - "inquirer": "3.3.0", - "is-root": "1.0.0", - "is-url": "1.2.2", - "js-yaml": "3.10.0", - "mecab-async": "^0.1.0", - "moji": "^0.5.1", - "mongodb": "2.2.33", - "monk": "6.0.5", - "morgan": "1.9.0", - "ms": "2.0.0", - "multer": "1.3.0", - "nprogress": "0.2.0", - "os-utils": "0.0.14", - "page": "1.7.1", - "pictograph": "2.0.4", - "prominence": "0.2.0", - "pug": "2.0.0-rc.4", - "ratelimiter": "3.0.3", - "recaptcha-promise": "0.1.3", - "reconnecting-websocket": "3.2.2", - "redis": "2.8.0", - "request": "2.83.0", - "rimraf": "2.6.2", - "riot": "3.7.4", - "rndstr": "1.0.0", - "s-age": "1.1.0", - "serve-favicon": "2.4.5", - "summaly": "2.0.3", - "syuilo-password-strength": "0.0.1", - "tcp-port-used": "0.1.2", - "textarea-caret": "3.0.2", - "ts-node": "3.3.0", - "typescript": "2.6.1", - "uuid": "3.1.0", - "vhost": "3.0.2", - "websocket": "1.0.25", - "xev": "2.0.0" - } + }, + "devDependencies": { + "@types/bcryptjs": "2.4.1", + "@types/body-parser": "1.16.7", + "@types/chai": "4.0.4", + "@types/chai-http": "3.0.3", + "@types/chalk": "2.2.0", + "@types/compression": "0.0.34", + "@types/cors": "2.8.1", + "@types/debug": "0.0.30", + "@types/deep-equal": "1.0.1", + "@types/elasticsearch": "5.0.17", + "@types/event-stream": "3.3.32", + "@types/express": "4.0.37", + "@types/gm": "1.17.33", + "@types/gulp": "4.0.3", + "@types/gulp-htmlmin": "1.3.30", + "@types/gulp-mocha": "0.0.30", + "@types/gulp-rename": "0.0.32", + "@types/gulp-replace": "0.0.30", + "@types/gulp-tslint": "3.6.31", + "@types/gulp-typescript": "2.13.0", + "@types/gulp-uglify": "3.0.3", + "@types/gulp-util": "3.0.33", + "@types/inquirer": "0.0.34", + "@types/is-root": "1.0.0", + "@types/is-url": "1.2.28", + "@types/js-yaml": "3.9.1", + "@types/mocha": "2.2.44", + "@types/mongodb": "2.2.13", + "@types/monk": "1.0.6", + "@types/morgan": "1.7.35", + "@types/ms": "0.7.30", + "@types/multer": "1.3.5", + "@types/node": "8.0.47", + "@types/ratelimiter": "2.1.28", + "@types/redis": "2.8.1", + "@types/request": "2.0.7", + "@types/rimraf": "2.0.2", + "@types/riot": "3.6.1", + "@types/serve-favicon": "2.2.29", + "@types/uuid": "3.4.3", + "@types/webpack": "3.0.14", + "@types/uuid": "3.4.3", + "@types/webpack": "3.0.13", + "@types/webpack-stream": "3.2.8", + "@types/websocket": "0.0.34", + "awesome-typescript-loader": "3.3.0", + "chai": "4.1.2", + "chai-http": "3.0.0", + "css-loader": "0.28.7", + "event-stream": "3.3.4", + "gulp": "3.9.1", + "gulp-cssnano": "2.1.2", + "gulp-htmlmin": "3.0.0", + "gulp-imagemin": "3.4.0", + "gulp-mocha": "4.3.1", + "gulp-pug": "3.3.0", + "gulp-rename": "1.2.2", + "gulp-replace": "0.6.1", + "gulp-tslint": "8.1.2", + "gulp-typescript": "3.2.3", + "gulp-uglify": "3.0.0", + "gulp-util": "3.0.8", + "mocha": "4.0.1", + "riot-tag-loader": "1.0.0", + "string-replace-webpack-plugin": "0.1.3", + "style-loader": "0.19.0", + "stylus": "0.54.5", + "stylus-loader": "3.0.1", + "swagger-jsdoc": "1.9.7", + "tslint": "5.8.0", + "uglify-es": "3.0.27", + "uglify-js": "git+https://github.com/mishoo/UglifyJS2.git#harmony", + "uglifyjs-webpack-plugin": "1.0.1", + "webpack": "3.8.1" + }, + "dependencies": { + "@prezzemolo/rap": "0.1.2", + "accesses": "2.5.0", + "animejs": "2.2.0", + "autwh": "0.0.1", + "bcryptjs": "2.4.3", + "body-parser": "1.18.2", + "cafy": "3.1.1", + "chalk": "2.3.0", + "compression": "1.7.1", + "cors": "2.8.4", + "cropperjs": "1.1.3", + "crypto": "1.0.1", + "debug": "3.1.0", + "deep-equal": "1.0.1", + "deepcopy": "0.6.3", + "diskusage": "0.2.2", + "download": "6.2.5", + "elasticsearch": "13.3.1", + "escape-regexp": "0.0.1", + "express": "4.15.4", + "file-type": "7.2.0", + "fuckadblock": "3.2.1", + "gm": "1.23.0", + "inquirer": "3.3.0", + "is-root": "1.0.0", + "is-url": "1.2.2", + "js-yaml": "3.10.0", + "mecab-async": "^0.1.0", + "moji": "^0.5.1", + "mongodb": "2.2.33", + "monk": "6.0.5", + "morgan": "1.9.0", + "ms": "2.0.0", + "multer": "1.3.0", + "nprogress": "0.2.0", + "os-utils": "0.0.14", + "page": "1.7.1", + "pictograph": "2.0.4", + "prominence": "0.2.0", + "pug": "2.0.0-rc.4", + "ratelimiter": "3.0.3", + "recaptcha-promise": "0.1.3", + "reconnecting-websocket": "3.2.2", + "redis": "2.8.0", + "request": "2.83.0", + "rimraf": "2.6.2", + "riot": "3.7.4", + "rndstr": "1.0.0", + "s-age": "1.1.0", + "serve-favicon": "2.4.5", + "summaly": "2.0.3", + "syuilo-password-strength": "0.0.1", + "tcp-port-used": "0.1.2", + "textarea-caret": "3.0.2", + "ts-node": "3.3.0", + "typescript": "2.6.1", + "uuid": "3.1.0", + "vhost": "3.0.2", + "websocket": "1.0.25", + "xev": "2.0.0" + } } From 1d107b714f363e1edcd0923af1764ccd06e356f8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 6 Nov 2017 19:39:55 +0900 Subject: [PATCH 064/327] Remove needless dependent This is a stub types definition for chalk (https://github.com/chalk/chalk). chalk provides its own type definitions, so yo u don't need @types/chalk installed! --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 7fa56321d..da5badc94 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "@types/body-parser": "1.16.7", "@types/chai": "4.0.4", "@types/chai-http": "3.0.3", - "@types/chalk": "2.2.0", "@types/compression": "0.0.34", "@types/cors": "2.8.1", "@types/debug": "0.0.30", From 7940c0901d4acff93169f62f4b80d58b287ca48e Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 6 Nov 2017 19:59:14 +0900 Subject: [PATCH 065/327] :v: --- gulpfile.ts | 4 ++-- package.json | 3 --- src/index.ts | 2 +- src/utils/cli/progressbar.ts | 2 +- src/utils/logger.ts | 4 ++-- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/gulpfile.ts b/gulpfile.ts index 4ee5fbce0..448b6b7fe 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -13,7 +13,7 @@ import cssnano = require('gulp-cssnano'); import * as uglifyComposer from 'gulp-uglify/composer'; import pug = require('gulp-pug'); import * as rimraf from 'rimraf'; -import * as chalk from 'chalk'; +import chalk from 'chalk'; import imagemin = require('gulp-imagemin'); import * as rename from 'gulp-rename'; import * as mocha from 'gulp-mocha'; @@ -123,7 +123,7 @@ gulp.task('build:client:script', () => .pipe(replace('VERSION', JSON.stringify(version))) .pipe(isProduction ? uglify({ toplevel: true - }) : gutil.noop()) + } as any) : gutil.noop()) .pipe(gulp.dest('./built/web/assets/')) as any ); diff --git a/package.json b/package.json index da5badc94..606ce9f0f 100644 --- a/package.json +++ b/package.json @@ -60,8 +60,6 @@ "@types/riot": "3.6.1", "@types/serve-favicon": "2.2.29", "@types/uuid": "3.4.3", - "@types/webpack": "3.0.14", - "@types/uuid": "3.4.3", "@types/webpack": "3.0.13", "@types/webpack-stream": "3.2.8", "@types/websocket": "0.0.34", @@ -91,7 +89,6 @@ "swagger-jsdoc": "1.9.7", "tslint": "5.8.0", "uglify-es": "3.0.27", - "uglify-js": "git+https://github.com/mishoo/UglifyJS2.git#harmony", "uglifyjs-webpack-plugin": "1.0.1", "webpack": "3.8.1" }, diff --git a/src/index.ts b/src/index.ts index aa53c9123..218455d6f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,7 @@ import * as fs from 'fs'; import * as os from 'os'; import * as cluster from 'cluster'; import * as debug from 'debug'; -import * as chalk from 'chalk'; +import chalk from 'chalk'; // import portUsed = require('tcp-port-used'); import isRoot = require('is-root'); import { master } from 'accesses'; diff --git a/src/utils/cli/progressbar.ts b/src/utils/cli/progressbar.ts index 4afb4b090..72496fded 100644 --- a/src/utils/cli/progressbar.ts +++ b/src/utils/cli/progressbar.ts @@ -1,6 +1,6 @@ import { EventEmitter } from 'events'; import * as readline from 'readline'; -import * as chalk from 'chalk'; +import chalk from 'chalk'; /** * Progress bar diff --git a/src/utils/logger.ts b/src/utils/logger.ts index ecfacbc95..fae1042c3 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,8 +1,8 @@ -import * as chalk from 'chalk'; +import chalk, { Chalk } from 'chalk'; export type LogLevel = 'Error' | 'Warn' | 'Info'; -function toLevelColor(level: LogLevel): chalk.ChalkStyle { +function toLevelColor(level: LogLevel): Chalk { switch (level) { case 'Error': return chalk.red; case 'Warn': return chalk.yellow; From 26d4ebfe1a22615f4f69bf94cc6872659e6e4125 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 6 Nov 2017 20:03:50 +0900 Subject: [PATCH 066/327] Refactor --- src/api/endpoints/posts/timeline.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/api/endpoints/posts/timeline.ts b/src/api/endpoints/posts/timeline.ts index 203413e23..7af435e82 100644 --- a/src/api/endpoints/posts/timeline.ts +++ b/src/api/endpoints/posts/timeline.ts @@ -34,11 +34,11 @@ module.exports = async (params, user, app) => { throw 'cannot set since_id and max_id'; } - const { followingIds, watchChannelIds } = await rap({ + const { followingIds, watchingChannelIds } = await rap({ // ID list of the user itself and other users who the user follows followingIds: getFriends(user._id), // Watchしているチャンネルを取得 - watchChannelIds: ChannelWatching.find({ + watchingChannelIds: ChannelWatching.find({ user_id: user._id, // 削除されたドキュメントは除く deleted_at: { $exists: false } @@ -67,7 +67,7 @@ module.exports = async (params, user, app) => { }, { // Watchしているチャンネルへの投稿 channel_id: { - $in: watchChannelIds + $in: watchingChannelIds } }] } as any; @@ -92,6 +92,5 @@ module.exports = async (params, user, app) => { }); // Serialize - const _timeline = await Promise.all(timeline.map(post => serialize(post, user))); - return _timeline; + return await Promise.all(timeline.map(post => serialize(post, user))); }; From 6df3db23d6277e48e68bd4a9ee78d1ac4576332c Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 6 Nov 2017 22:10:40 +0900 Subject: [PATCH 067/327] Fix index creation --- src/api/models/drive-file.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/models/drive-file.ts b/src/api/models/drive-file.ts index 8968d065c..976c97e18 100644 --- a/src/api/models/drive-file.ts +++ b/src/api/models/drive-file.ts @@ -3,7 +3,7 @@ import monkDb, { nativeDbConn } from '../../db/mongodb'; const collection = monkDb.get('drive_files.files'); -(collection as any).createIndex('hash'); // fuck type definition +(collection as any).createIndex('md5'); // fuck type definition export default collection as any; // fuck type definition From b06950d5e70bc9b6321f6e144042caf1f3397d0d Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 6 Nov 2017 22:22:48 +0900 Subject: [PATCH 068/327] =?UTF-8?q?=E3=83=8F=E3=83=83=E3=82=B7=E3=83=A5?= =?UTF-8?q?=E5=80=A4=E3=81=AFMongoDB=E5=81=B4=E3=81=A7=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84=E3=82=8B=E3=81=8B=E3=82=89?= =?UTF-8?q?=E6=89=8B=E5=8B=95=E3=81=A7=E3=82=A4=E3=83=B3=E3=83=87=E3=83=83?= =?UTF-8?q?=E3=82=AF=E3=82=B9=E3=82=92=E6=98=8E=E7=A4=BA=E3=81=99=E3=82=8B?= =?UTF-8?q?=E5=BF=85=E8=A6=81=E3=81=AF=E3=81=AA=E3=81=95=E3=81=9D=E3=81=86?= =?UTF-8?q?=E3=81=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/models/drive-file.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/api/models/drive-file.ts b/src/api/models/drive-file.ts index 976c97e18..802ee5a5f 100644 --- a/src/api/models/drive-file.ts +++ b/src/api/models/drive-file.ts @@ -3,8 +3,6 @@ import monkDb, { nativeDbConn } from '../../db/mongodb'; const collection = monkDb.get('drive_files.files'); -(collection as any).createIndex('md5'); // fuck type definition - export default collection as any; // fuck type definition const getGridFSBucket = async (): Promise => { From 041cc01d5ee507413aff17340831227ffa686258 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 6 Nov 2017 19:02:39 +0000 Subject: [PATCH 069/327] chore(package): update @types/webpack to version 3.8.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 606ce9f0f..15239c719 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "@types/riot": "3.6.1", "@types/serve-favicon": "2.2.29", "@types/uuid": "3.4.3", - "@types/webpack": "3.0.13", + "@types/webpack": "3.8.0", "@types/webpack-stream": "3.2.8", "@types/websocket": "0.0.34", "awesome-typescript-loader": "3.3.0", From 3117029ba9b0ab52c8c3368ed35d477f6ee4575b Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 6 Nov 2017 19:54:17 +0000 Subject: [PATCH 070/327] chore(package): update @types/node to version 8.0.49 Closes #878 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 606ce9f0f..0048e63ba 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@types/morgan": "1.7.35", "@types/ms": "0.7.30", "@types/multer": "1.3.5", - "@types/node": "8.0.47", + "@types/node": "8.0.49", "@types/ratelimiter": "2.1.28", "@types/redis": "2.8.1", "@types/request": "2.0.7", From 34ffae865a76913c560dbc15998e74faed8a4c4d Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 6 Nov 2017 22:58:57 +0000 Subject: [PATCH 071/327] chore(package): update uglify-es to version 3.1.8 Closes #602 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 606ce9f0f..710cb0e6c 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "stylus-loader": "3.0.1", "swagger-jsdoc": "1.9.7", "tslint": "5.8.0", - "uglify-es": "3.0.27", + "uglify-es": "3.1.8", "uglifyjs-webpack-plugin": "1.0.1", "webpack": "3.8.1" }, From 3ec29fec6beefd485e2189dba7573d8e2fc79897 Mon Sep 17 00:00:00 2001 From: otofune Date: Tue, 7 Nov 2017 09:03:56 +0900 Subject: [PATCH 072/327] remove unneed dependencies --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index 606ce9f0f..c384edb22 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,6 @@ "@types/gulp-mocha": "0.0.30", "@types/gulp-rename": "0.0.32", "@types/gulp-replace": "0.0.30", - "@types/gulp-tslint": "3.6.31", - "@types/gulp-typescript": "2.13.0", "@types/gulp-uglify": "3.0.3", "@types/gulp-util": "3.0.33", "@types/inquirer": "0.0.34", @@ -104,7 +102,6 @@ "compression": "1.7.1", "cors": "2.8.4", "cropperjs": "1.1.3", - "crypto": "1.0.1", "debug": "3.1.0", "deep-equal": "1.0.1", "deepcopy": "0.6.3", From f80283a94f4fb08f1df7d2ad95848591420f8478 Mon Sep 17 00:00:00 2001 From: otofune Date: Tue, 7 Nov 2017 09:04:16 +0900 Subject: [PATCH 073/327] add 'format' script to use autofix w/ tslint --- gulpfile.ts | 9 +++++++++ package.json | 3 ++- src/file/server.ts | 4 ++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/gulpfile.ts b/gulpfile.ts index 448b6b7fe..04bd2b1c4 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -81,6 +81,15 @@ gulp.task('lint', () => .pipe(tslint.report()) ); +gulp.task('format', () => +gulp.src('./src/**/*.ts') + .pipe(tslint({ + formatter: 'verbose', + fix: true + })) + .pipe(tslint.report()) +); + gulp.task('mocha', () => gulp.src([]) .pipe(mocha({ diff --git a/package.json b/package.json index c384edb22..1593cd7d0 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "clean": "gulp clean", "cleanall": "gulp cleanall", "lint": "gulp lint", - "test": "gulp test" + "test": "gulp test", + "format": "gulp format" }, "devDependencies": { "@types/bcryptjs": "2.4.1", diff --git a/src/file/server.ts b/src/file/server.ts index 375f29487..39e21d10e 100644 --- a/src/file/server.ts +++ b/src/file/server.ts @@ -110,7 +110,7 @@ app.get('/:id', async (req, res) => { const buffer = await ((id): Promise => new Promise((resolve, reject) => { const chunks = []; const readableStream = bucket.openDownloadStream(id); - readableStream.on('data', chunk => { + readableStream.on('data', chunk => { chunks.push(chunk); }); readableStream.on('end', () => { @@ -141,7 +141,7 @@ app.get('/:id/:name', async (req, res) => { const buffer = await ((id): Promise => new Promise((resolve, reject) => { const chunks = []; const readableStream = bucket.openDownloadStream(id); - readableStream.on('data', chunk => { + readableStream.on('data', chunk => { chunks.push(chunk); }); readableStream.on('end', () => { From 8e62cc1efd6dc1710e7faa7d3ad2086425573cf5 Mon Sep 17 00:00:00 2001 From: otofune Date: Tue, 7 Nov 2017 09:14:39 +0900 Subject: [PATCH 074/327] file - unify '/:id' & '/:id/:name' --- src/file/server.ts | 94 ++++++++++++++++------------------------------ 1 file changed, 33 insertions(+), 61 deletions(-) diff --git a/src/file/server.ts b/src/file/server.ts index 39e21d10e..e83acd4f2 100644 --- a/src/file/server.ts +++ b/src/file/server.ts @@ -86,70 +86,42 @@ function send(data: Buffer, type: string, req: express.Request, res: express.Res } } +async function sendFileById (req: express.Request, res: express.Response): Promise { + // Validate id + if (!mongodb.ObjectID.isValid(req.params.id)) { + res.status(400).send('incorrect id'); + return; + } + + const fileId = new mongodb.ObjectID(req.params.id); + const file = await DriveFile.findOne({ _id: fileId }); + + if (file == null) { + res.status(404).sendFile(`${__dirname}/assets/dummy.png`); + return; + } + + const bucket = await getGridFSBucket(); + + const buffer = await ((id): Promise => new Promise((resolve, reject) => { + const chunks = []; + const readableStream = bucket.openDownloadStream(id); + readableStream.on('data', chunk => { + chunks.push(chunk); + }); + readableStream.on('end', () => { + resolve(Buffer.concat(chunks)); + }); + }))(fileId); + + send(buffer, file.metadata.type, req, res); +} + /** * Routing */ -app.get('/:id', async (req, res) => { - // Validate id - if (!mongodb.ObjectID.isValid(req.params.id)) { - res.status(400).send('incorrect id'); - return; - } - - const fileId = new mongodb.ObjectID(req.params.id); - const file = await DriveFile.findOne({ _id: fileId }); - - if (file == null) { - res.status(404).sendFile(`${__dirname}/assets/dummy.png`); - return; - } - - const bucket = await getGridFSBucket(); - - const buffer = await ((id): Promise => new Promise((resolve, reject) => { - const chunks = []; - const readableStream = bucket.openDownloadStream(id); - readableStream.on('data', chunk => { - chunks.push(chunk); - }); - readableStream.on('end', () => { - resolve(Buffer.concat(chunks)); - }); - }))(fileId); - - send(buffer, file.metadata.type, req, res); -}); - -app.get('/:id/:name', async (req, res) => { - // Validate id - if (!mongodb.ObjectID.isValid(req.params.id)) { - res.status(400).send('incorrect id'); - return; - } - - const fileId = new mongodb.ObjectID(req.params.id); - const file = await DriveFile.findOne({ _id: fileId }); - - if (file == null) { - res.status(404).sendFile(`${__dirname}/assets/dummy.png`); - return; - } - - const bucket = await getGridFSBucket(); - - const buffer = await ((id): Promise => new Promise((resolve, reject) => { - const chunks = []; - const readableStream = bucket.openDownloadStream(id); - readableStream.on('data', chunk => { - chunks.push(chunk); - }); - readableStream.on('end', () => { - resolve(Buffer.concat(chunks)); - }); - }))(fileId); - - send(buffer, file.metadata.type, req, res); -}); +app.get('/:id', sendFileById); +app.get('/:id/:name', sendFileById); module.exports = app; From a7762aea4fa0cade3614323a83d6f8d74ade924a Mon Sep 17 00:00:00 2001 From: otofune Date: Tue, 7 Nov 2017 09:18:40 +0900 Subject: [PATCH 075/327] file - if 'name' param given, validate --- src/file/server.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/file/server.ts b/src/file/server.ts index e83acd4f2..1152b650b 100644 --- a/src/file/server.ts +++ b/src/file/server.ts @@ -86,7 +86,7 @@ function send(data: Buffer, type: string, req: express.Request, res: express.Res } } -async function sendFileById (req: express.Request, res: express.Response): Promise { +async function sendFileById(req: express.Request, res: express.Response): Promise { // Validate id if (!mongodb.ObjectID.isValid(req.params.id)) { res.status(400).send('incorrect id'); @@ -96,6 +96,12 @@ async function sendFileById (req: express.Request, res: express.Response): Promi const fileId = new mongodb.ObjectID(req.params.id); const file = await DriveFile.findOne({ _id: fileId }); + // validate name + if (req.params.name !== undefined && req.params.name !== file.metadata.name) { + res.status(404).send('there is no file has given name'); + return; + } + if (file == null) { res.status(404).sendFile(`${__dirname}/assets/dummy.png`); return; From fb422b4d603c53a70712caba55b35a48a8c2e619 Mon Sep 17 00:00:00 2001 From: otofune Date: Tue, 7 Nov 2017 09:30:51 +0900 Subject: [PATCH 076/327] use 'name' param as GridFS file's 'filename' --- src/api/common/add-file-to-drive.ts | 3 +-- src/api/endpoints/drive/files/find.ts | 2 +- src/api/endpoints/drive/files/update.ts | 4 ++-- src/api/serializers/drive-file.ts | 1 + src/file/server.ts | 2 +- tools/migration/use-gridfs.js | 4 +++- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/api/common/add-file-to-drive.ts b/src/api/common/add-file-to-drive.ts index f9c22ccac..e1baf0819 100644 --- a/src/api/common/add-file-to-drive.ts +++ b/src/api/common/add-file-to-drive.ts @@ -144,11 +144,10 @@ export default ( } // Create DriveFile document - const file = await addToGridFS(`${user._id}/${name}`, data, { + const file = await addToGridFS(name, data, { user_id: user._id, folder_id: folder !== null ? folder._id : null, type: mime, - name: name, comment: comment, properties: properties }); diff --git a/src/api/endpoints/drive/files/find.ts b/src/api/endpoints/drive/files/find.ts index 1c818131d..a1cdf1643 100644 --- a/src/api/endpoints/drive/files/find.ts +++ b/src/api/endpoints/drive/files/find.ts @@ -24,7 +24,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { // Issue query const files = await DriveFile .find({ - 'metadata.name': name, + filename: name, 'metadata.user_id': user._id, 'metadata.folder_id': folderId }); diff --git a/src/api/endpoints/drive/files/update.ts b/src/api/endpoints/drive/files/update.ts index d7b858c2b..f265142c4 100644 --- a/src/api/endpoints/drive/files/update.ts +++ b/src/api/endpoints/drive/files/update.ts @@ -34,7 +34,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { // Get 'name' parameter const [name, nameErr] = $(params.name).optional.string().pipe(validateFileName).$; if (nameErr) return rej('invalid name param'); - if (name) file.metadata.name = name; + if (name) file.filename = name; // Get 'folder_id' parameter const [folderId, folderIdErr] = $(params.folder_id).optional.nullable.id().$; @@ -61,7 +61,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { await DriveFile.update(file._id, { $set: { - 'metadata.name': file.metadata.name, + filename: file.filename, 'metadata.folder_id': file.metadata.folder_id } }); diff --git a/src/api/serializers/drive-file.ts b/src/api/serializers/drive-file.ts index 2af7db572..57b74cd97 100644 --- a/src/api/serializers/drive-file.ts +++ b/src/api/serializers/drive-file.ts @@ -47,6 +47,7 @@ export default ( _target.id = _file._id; _target.created_at = _file.uploadDate; + _target.name = _file.filename; _target = Object.assign(_target, _file.metadata); diff --git a/src/file/server.ts b/src/file/server.ts index 1152b650b..39c2cdd2a 100644 --- a/src/file/server.ts +++ b/src/file/server.ts @@ -97,7 +97,7 @@ async function sendFileById(req: express.Request, res: express.Response): Promis const file = await DriveFile.findOne({ _id: fileId }); // validate name - if (req.params.name !== undefined && req.params.name !== file.metadata.name) { + if (req.params.name !== undefined && req.params.name !== file.filename) { res.status(404).send('there is no file has given name'); return; } diff --git a/tools/migration/use-gridfs.js b/tools/migration/use-gridfs.js index d41514416..148f9be26 100644 --- a/tools/migration/use-gridfs.js +++ b/tools/migration/use-gridfs.js @@ -21,15 +21,17 @@ const migrateToGridFS = async (doc) => { const id = doc._id const buffer = doc.data.buffer const created_at = doc.created_at + const name = doc.name delete doc._id delete doc.created_at delete doc.datasize delete doc.hash delete doc.data + delete doc.name const bucket = await getGridFSBucket() - const added = await writeToGridFS(bucket, buffer, id, `${id}/${doc.name}`, { metadata: doc }) + const added = await writeToGridFS(bucket, buffer, id, name, { metadata: doc }) const result = await DriveFile.update(id, { $set: { From 67d32b73f4840b0a602ef11523165019efabb35c Mon Sep 17 00:00:00 2001 From: otofune Date: Tue, 7 Nov 2017 09:47:59 +0900 Subject: [PATCH 077/327] test - remove unneed async wrappings, drop GridFS drive_file correctly --- test/api.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/test/api.js b/test/api.js index c0da9d6c5..7e26dfe07 100644 --- a/test/api.js +++ b/test/api.js @@ -46,7 +46,8 @@ describe('API', () => { beforeEach(() => Promise.all([ db.get('users').drop(), db.get('posts').drop(), - db.get('drive_files').drop(), + db.get('drive_files.files').drop(), + db.get('drive_files.chunks').drop(), db.get('drive_folders').drop(), db.get('apps').drop(), db.get('access_tokens').drop(), @@ -1131,8 +1132,8 @@ describe('API', () => { }); }); -async function insertSakurako(opts) { - return await db.get('users').insert(Object.assign({ +function insertSakurako(opts) { + return db.get('users').insert(Object.assign({ token: '!00000000000000000000000000000000', username: 'sakurako', username_lower: 'sakurako', @@ -1141,8 +1142,8 @@ async function insertSakurako(opts) { }, opts)); } -async function insertHimawari(opts) { - return await db.get('users').insert(Object.assign({ +function insertHimawari(opts) { + return db.get('users').insert(Object.assign({ token: '!00000000000000000000000000000001', username: 'himawari', username_lower: 'himawari', @@ -1151,8 +1152,8 @@ async function insertHimawari(opts) { }, opts)); } -async function insertDriveFile(opts) { - return await db.get('drive_files.files').insert({ +function insertDriveFile(opts) { + return db.get('drive_files.files').insert({ length: opts.datasize, metadata: Object.assign({ name: 'strawberry-pasta.png' @@ -1160,15 +1161,15 @@ async function insertDriveFile(opts) { }); } -async function insertDriveFolder(opts) { - return await db.get('drive_folders').insert(Object.assign({ +function insertDriveFolder(opts) { + return db.get('drive_folders').insert(Object.assign({ name: 'my folder', parent_id: null }, opts)); } -async function insertApp(opts) { - return await db.get('apps').insert(Object.assign({ +function insertApp(opts) { + return db.get('apps').insert(Object.assign({ name: 'my app', secret: 'mysecret' }, opts)); From 2c234beb313204fb3286f0744587262abc0a5c7a Mon Sep 17 00:00:00 2001 From: otofune Date: Tue, 7 Nov 2017 09:49:48 +0900 Subject: [PATCH 078/327] gulpfile - shutdown mocha after test run see: - http://mochajs.org/#usage - https://boneskull.com/mocha-v4-nears-release/#mochawontforceexit --- gulpfile.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/gulpfile.ts b/gulpfile.ts index 04bd2b1c4..93002cbf3 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -93,6 +93,7 @@ gulp.src('./src/**/*.ts') gulp.task('mocha', () => gulp.src([]) .pipe(mocha({ + exit: true //compilers: 'ts:ts-node/register' } as any)) ); From 696cd3b0e35673966c9aee5089c9a821b9e00d04 Mon Sep 17 00:00:00 2001 From: otofune Date: Tue, 7 Nov 2017 09:58:02 +0900 Subject: [PATCH 079/327] migration - add migration to support changed filename usage --- ...change-gridfs-metadata-name-to-filename.js | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tools/migration/change-gridfs-metadata-name-to-filename.js diff --git a/tools/migration/change-gridfs-metadata-name-to-filename.js b/tools/migration/change-gridfs-metadata-name-to-filename.js new file mode 100644 index 000000000..0d9e977c6 --- /dev/null +++ b/tools/migration/change-gridfs-metadata-name-to-filename.js @@ -0,0 +1,30 @@ +// for Node.js interpret +/** + * change usage of GridFS filename + * see commit fb422b4d603c53a70712caba55b35a48a8c2e619 + */ + +const { default: DriveFile } = require('../../built/api/models/drive-file') + +async function applyNewChange (doc) { + const result = await DriveFile.update(doc._id, { + $set: { + filename: doc.metadata.name + }, + $unset: { + 'metadata.name': '' + } + }) + return result.ok === 1 +} + +async function main () { + const oldTypeDocs = await DriveFile.find({ + 'metadata.name': { + $exists: true + } + }) + return await Promise.all(oldTypeDocs.map(applyNewChange)) +} + +main().then(console.dir).catch(console.error) From 16bed0ca03f632c7a36ae35a8dcc95688633dfe4 Mon Sep 17 00:00:00 2001 From: otofune Date: Tue, 7 Nov 2017 09:59:58 +0900 Subject: [PATCH 080/327] test - fix insertDriveFile (use GridFS filename) --- test/api.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/api.js b/test/api.js index 7e26dfe07..a2eaa4e62 100644 --- a/test/api.js +++ b/test/api.js @@ -1155,9 +1155,8 @@ function insertHimawari(opts) { function insertDriveFile(opts) { return db.get('drive_files.files').insert({ length: opts.datasize, - metadata: Object.assign({ - name: 'strawberry-pasta.png' - }, opts) + filename: 'strawberry-pasta.png', + metadata: opts }); } From fe1e01a108369bcaead2959590ad3addb9683988 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 7 Nov 2017 12:08:44 +0900 Subject: [PATCH 081/327] Disable minification due to artifacts --- webpack/plugins/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webpack/plugins/index.ts b/webpack/plugins/index.ts index 345af7df9..d37047b67 100644 --- a/webpack/plugins/index.ts +++ b/webpack/plugins/index.ts @@ -2,7 +2,7 @@ const StringReplacePlugin = require('string-replace-webpack-plugin'); import constant from './const'; import hoist from './hoist'; -import minify from './minify'; +//import minify from './minify'; import banner from './banner'; const env = process.env.NODE_ENV; @@ -16,7 +16,7 @@ export default (version, lang) => { ]; if (isProduction) { - plugins.push(minify()); + //plugins.push(minify()); } plugins.push(banner(version)); From 3aa0980f634a49b7c788306cd75bb40310b4ecb6 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 7 Nov 2017 12:11:52 +0900 Subject: [PATCH 082/327] v2944 --- CHANGELOG.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8018e4e2..cfafcc3b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ChangeLog (Release Notes) ========================= 主に notable な changes を書いていきます +2944 (2017/11/07) +----------------- +* パフォーマンスの向上 + * GirdFSになるなどした +* 依存関係の更新 + 2807 (2017/11/02) ----------------- * いい感じに diff --git a/package.json b/package.json index 21f2c57de..cf291fe2b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo ", - "version": "0.0.2807", + "version": "0.0.2944", "license": "MIT", "description": "A miniblog-based SNS", "bugs": "https://github.com/syuilo/misskey/issues", From 68cf16882fa23f061763090498f51a58c52be8f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=93=E3=81=B4=E3=81=AA=E3=81=9F=E3=81=BF=E3=81=BD?= Date: Tue, 7 Nov 2017 18:47:23 +0900 Subject: [PATCH 083/327] Fix bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit アップロードのバグなのか知らないけどなぜか data が存在しない drive_file ドキュメントがまれにあることがわかったので --- tools/migration/use-gridfs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/migration/use-gridfs.js b/tools/migration/use-gridfs.js index 148f9be26..f13d3c874 100644 --- a/tools/migration/use-gridfs.js +++ b/tools/migration/use-gridfs.js @@ -19,7 +19,7 @@ const writeToGridFS = (bucket, buffer, ...rest) => new Promise((resolve, reject) const migrateToGridFS = async (doc) => { const id = doc._id - const buffer = doc.data.buffer + const buffer = doc.data ? doc.data.buffer : Buffer.from([0x00]) // アップロードのバグなのか知らないけどなぜか data が存在しない drive_file ドキュメントがまれにあることがわかったので const created_at = doc.created_at const name = doc.name From b2a2f98d6a12d752cb282cb381ddf7be86feeb48 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 7 Nov 2017 19:17:42 +0900 Subject: [PATCH 084/327] Better memory usage --- tools/migration/use-gridfs.js | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/tools/migration/use-gridfs.js b/tools/migration/use-gridfs.js index f13d3c874..313b10859 100644 --- a/tools/migration/use-gridfs.js +++ b/tools/migration/use-gridfs.js @@ -6,7 +6,7 @@ const { Duplex } = require('stream') const writeToGridFS = (bucket, buffer, ...rest) => new Promise((resolve, reject) => { const writeStream = bucket.openUploadStreamWithId(...rest) - + const dataStream = new Duplex() dataStream.push(buffer) dataStream.push(null) @@ -42,10 +42,30 @@ const migrateToGridFS = async (doc) => { return added && result.ok === 1 } -const main = async () => { - const docs = await db.get('drive_files').find() - const all = await Promise.all(docs.map(migrateToGridFS)) - return all +async function main() { + let i = 0; + + const count = db.get('drive_files').count; + + const iterate = async () => { + if (i == count) return true; + const doc = await db.get('drive_files').find({}, { skip: i }) + const res = await migrateToGridFS(doc); + if (!res) { + return false; + } else { + i++ + return await iterate(); + } + } + + const res = await iterate(); + + if (res) { + return 'ok'; + } else { + throw 'something happened'; + } } main().then(console.dir).catch(console.error) From 109dd7f747da7b70398b878292fb83aa91efc244 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 7 Nov 2017 19:19:19 +0900 Subject: [PATCH 085/327] Fix bug --- tools/migration/use-gridfs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/migration/use-gridfs.js b/tools/migration/use-gridfs.js index 313b10859..02c0b2f6a 100644 --- a/tools/migration/use-gridfs.js +++ b/tools/migration/use-gridfs.js @@ -49,7 +49,7 @@ async function main() { const iterate = async () => { if (i == count) return true; - const doc = await db.get('drive_files').find({}, { skip: i }) + const doc = await db.get('drive_files').find({}, { limit: 1, skip: i }) const res = await migrateToGridFS(doc); if (!res) { return false; From 09783e5fb0fa2a98845651a3629cca4a867f94ed Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 7 Nov 2017 19:27:02 +0900 Subject: [PATCH 086/327] Fix bug --- tools/migration/use-gridfs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/migration/use-gridfs.js b/tools/migration/use-gridfs.js index 02c0b2f6a..4401e8301 100644 --- a/tools/migration/use-gridfs.js +++ b/tools/migration/use-gridfs.js @@ -45,7 +45,7 @@ const migrateToGridFS = async (doc) => { async function main() { let i = 0; - const count = db.get('drive_files').count; + const count = await db.get('drive_files').count({}); const iterate = async () => { if (i == count) return true; From dab9231fd8f3fa10b4c3f4991fc98aa03d3b19f0 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 7 Nov 2017 19:33:54 +0900 Subject: [PATCH 087/327] Fix bug --- tools/migration/use-gridfs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/migration/use-gridfs.js b/tools/migration/use-gridfs.js index 4401e8301..804f2aa27 100644 --- a/tools/migration/use-gridfs.js +++ b/tools/migration/use-gridfs.js @@ -49,7 +49,7 @@ async function main() { const iterate = async () => { if (i == count) return true; - const doc = await db.get('drive_files').find({}, { limit: 1, skip: i }) + const doc = (await db.get('drive_files').find({}, { limit: 1, skip: i }))[0] const res = await migrateToGridFS(doc); if (!res) { return false; From 307f0389acc8725c0111f3606edb66a5d94a802f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=93=E3=81=B4=E3=81=AA=E3=81=9F=E3=81=BF=E3=81=BD?= Date: Tue, 7 Nov 2017 19:42:29 +0900 Subject: [PATCH 088/327] Display progress --- tools/migration/use-gridfs.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/migration/use-gridfs.js b/tools/migration/use-gridfs.js index 804f2aa27..c5883e456 100644 --- a/tools/migration/use-gridfs.js +++ b/tools/migration/use-gridfs.js @@ -49,6 +49,7 @@ async function main() { const iterate = async () => { if (i == count) return true; + console.log(`${i} / ${count}`); const doc = (await db.get('drive_files').find({}, { limit: 1, skip: i }))[0] const res = await migrateToGridFS(doc); if (!res) { From a85d5ac4bbfe35652be1bef47f59a79ace7b2c73 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 7 Nov 2017 21:04:32 +0900 Subject: [PATCH 089/327] #882 --- src/api/common/add-file-to-drive.ts | 7 ++--- src/api/serializers/drive-file.ts | 1 + tools/migration/issue_882.js | 44 +++++++++++++++++++++++++++++ tools/migration/use-gridfs.js | 4 ++- 4 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 tools/migration/issue_882.js diff --git a/src/api/common/add-file-to-drive.ts b/src/api/common/add-file-to-drive.ts index e1baf0819..a96906d29 100644 --- a/src/api/common/add-file-to-drive.ts +++ b/src/api/common/add-file-to-drive.ts @@ -13,13 +13,13 @@ import { Duplex } from 'stream'; const log = debug('misskey:register-drive-file'); -const addToGridFS = (name, binary, metadata): Promise => new Promise(async (resolve, reject) => { +const addToGridFS = (name, binary, type, metadata): Promise => new Promise(async (resolve, reject) => { const dataStream = new Duplex(); dataStream.push(binary); dataStream.push(null); const bucket = await getGridFSBucket(); - const writeStream = bucket.openUploadStream(name, { metadata }); + const writeStream = bucket.openUploadStream(name, { contentType: type, metadata }); writeStream.once('finish', (doc) => { resolve(doc); }); writeStream.on('error', reject); dataStream.pipe(writeStream); @@ -144,10 +144,9 @@ export default ( } // Create DriveFile document - const file = await addToGridFS(name, data, { + const file = await addToGridFS(name, data, mime, { user_id: user._id, folder_id: folder !== null ? folder._id : null, - type: mime, comment: comment, properties: properties }); diff --git a/src/api/serializers/drive-file.ts b/src/api/serializers/drive-file.ts index 57b74cd97..3b76979a4 100644 --- a/src/api/serializers/drive-file.ts +++ b/src/api/serializers/drive-file.ts @@ -48,6 +48,7 @@ export default ( _target.id = _file._id; _target.created_at = _file.uploadDate; _target.name = _file.filename; + _target.type = _file.contentType; _target = Object.assign(_target, _file.metadata); diff --git a/tools/migration/issue_882.js b/tools/migration/issue_882.js new file mode 100644 index 000000000..67a1551e0 --- /dev/null +++ b/tools/migration/issue_882.js @@ -0,0 +1,44 @@ +// for Node.js interpret + +const { default: DriveFile } = require('../../built/api/models/drive-file') + +const migrate = async (doc) => { + const result = await DriveFile.update(doc._id, { + $set: { + contentType: doc.metadata.type + }, + $unset: { + 'metadata.type': '' + } + }) + return result.ok === 1 +} + +async function main() { + let i = 0; + + const count = await db.get('drive_files').count({}); + + const iterate = async () => { + if (i == count) return true; + console.log(`${i} / ${count}`); + const doc = (await db.get('drive_files').find({}, { limit: 1, skip: i }))[0] + const res = await migrate(doc); + if (!res) { + return false; + } else { + i++ + return await iterate(); + } + } + + const res = await iterate(); + + if (res) { + return 'ok'; + } else { + throw 'something happened'; + } +} + +main().then(console.dir).catch(console.error) diff --git a/tools/migration/use-gridfs.js b/tools/migration/use-gridfs.js index c5883e456..106cbd388 100644 --- a/tools/migration/use-gridfs.js +++ b/tools/migration/use-gridfs.js @@ -22,6 +22,7 @@ const migrateToGridFS = async (doc) => { const buffer = doc.data ? doc.data.buffer : Buffer.from([0x00]) // アップロードのバグなのか知らないけどなぜか data が存在しない drive_file ドキュメントがまれにあることがわかったので const created_at = doc.created_at const name = doc.name + const type = doc.type delete doc._id delete doc.created_at @@ -29,9 +30,10 @@ const migrateToGridFS = async (doc) => { delete doc.hash delete doc.data delete doc.name + delete doc.type const bucket = await getGridFSBucket() - const added = await writeToGridFS(bucket, buffer, id, name, { metadata: doc }) + const added = await writeToGridFS(bucket, buffer, id, name, { contentType: type, metadata: doc }) const result = await DriveFile.update(id, { $set: { From 71b1de8367f1436ad3e4c2c9585029949ea7372f Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 7 Nov 2017 21:12:08 +0900 Subject: [PATCH 090/327] Fix bug --- tools/migration/issue_882.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/migration/issue_882.js b/tools/migration/issue_882.js index 67a1551e0..8dab9bb43 100644 --- a/tools/migration/issue_882.js +++ b/tools/migration/issue_882.js @@ -17,12 +17,12 @@ const migrate = async (doc) => { async function main() { let i = 0; - const count = await db.get('drive_files').count({}); + const count = await DriveFile.count({}); const iterate = async () => { if (i == count) return true; console.log(`${i} / ${count}`); - const doc = (await db.get('drive_files').find({}, { limit: 1, skip: i }))[0] + const doc = (await DriveFile.find({}, { limit: 1, skip: i }))[0] const res = await migrate(doc); if (!res) { return false; From bb8fd5b90f2f1c4545cb66e2b2865461d6403a5d Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 7 Nov 2017 21:16:13 +0900 Subject: [PATCH 091/327] Fix bug --- src/file/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/file/server.ts b/src/file/server.ts index 39c2cdd2a..2007667a8 100644 --- a/src/file/server.ts +++ b/src/file/server.ts @@ -120,7 +120,7 @@ async function sendFileById(req: express.Request, res: express.Response): Promis }); }))(fileId); - send(buffer, file.metadata.type, req, res); + send(buffer, file.contentType, req, res); } /** From 6b0db9b8778e39b0c5b0ddcf8afc7068d1d80076 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 7 Nov 2017 21:23:09 +0900 Subject: [PATCH 092/327] :v: --- src/file/assets/not-an-image.png | Bin 0 -> 4711 bytes src/file/server.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 src/file/assets/not-an-image.png diff --git a/src/file/assets/not-an-image.png b/src/file/assets/not-an-image.png new file mode 100644 index 0000000000000000000000000000000000000000..bf98b293f74bbe4419960fdb297bde8c0c953327 GIT binary patch literal 4711 zcmeHL`#;l*8~@CuRW2i^B(XZaha5tQ#TrVbay#n#O)gC(qOdUx%f?apeh-;*=*azc z&Y9bWQe$FDD3_6IXg0)TY-4RRcKJI0#P^5qs~?`%`*~i^>-D^z*YkRw=k{OQ0$h%OfSo@8K)D)lC>H=8d`o^C z!vFCm>=R1Y6k;*`Ih8obRl==6z_ot5q>rgwma`g7&~?CleBiKD94+us-G5aBEr6-G z?d}kXl?!`koIqc5;ggeFbZ+TEMykrt17UOU%NO3}5A=mnep^6lWqTRI1DFa+0;9#Y zKcd`}mbz8Th}EaqrZlyVRl-lUmNVbIws63!Wa*}edrWz;zHv8h>muDRc7Uweu4-j1 z%GN=-0Hd1_$-K9bKz*sIRwga#T%~uyfrMzL;ip-vS-C1(L2%PbfdPV~tiTkeD%&*+ z*Si#E@~R>Vq#d6=4F{a&o81An$cPHS!5wm?6KJE)Nm)!lJ*Cg;mSGMNmnT*Xc3 z0Y>=*f2FG$+q-sTOEG(3X-g)PM6dI^2$zS8mra;lu|~?4a%V`k8k=vv3%ysixe1cS z1-7>dApPoP3tg-GnLfua5I!t;N$lu6rLU;#rl_LwAApub>CYeO}&GdbxqgM8#aD@?{17ugWTShpqDcklT1($SED*BrYrz2Hd zepwW=0#>c%#xB4wJoE;zl$EE|!Qs%M3ZHrU9jWxFYkiP9 z#t8b1+`KCJ4YVOsAl=hhi9&4MF}i3$IfN*8y4CGD|A|7Ba52bgk-X?* zeK#3vPah?Sl+$naR{bfP{2HQFI)b6`?48%P3M^WZLMf7hpdCk@==yQBX@%x_Z*pKC z@HG3R>)!)iF(`nBojuwUSKL>&dC;pr`=R(`P}?Q2Cv8q#z#<1aUr5Y{y#Kv~RjVse zF8+cGJ@(0QL+krPK-X`e=BR~;=hJNFv~qIE#DV#lj`;XFIsHh-P(zUwg zOQ=eDFnc%m;AoC-hq16VKPjef=4S@9=sMEZQv6}^N8#4OtffSPh|`z5ugZ(aTtZNh ze|)lFJydJ30Eqww-p)*oFPI2JX2Pfz)qzxIqH#2`PmQf(Z45))YBgTgI5^IMGW%^7|8nhL7Jh(YEpvR=88nfNjq!#tryOIz4RR z?yR-NS4cbNZZKm7b4gYEMNEzOgmGZkyjk+Rl=)8twOap;Xs-e%Yzlpdlt;)oVE2)D z>XZzb%dy9tihbx|TobAnqCY^K8gF?=b3<44B`AS@qpagC%*R^Eeyg{Z*S}O*gbRk? z5Cdu@PbH##^h!KzzF}g#^^0bvM{@s$A1fA5wVN;TLuJrE)M_-Hv?dcbdx9k`i?Wg@ zoN))^qPg$@6!kcId_mWuuC&msxiwkkr|~cSYx|9-;regh=d{*od>MC4T>Qic+F|g~ z-yvh~_`;b2jfjo99xWv6k+Ahe%w0VaR?yPsQ!N~ZMUiS5{BsCFpSVzev8zcMsZCfK z^0GFi5Rw`r7S9cs!M+5ddv2}k0Ny2Q`7Z;KmgGci0eFxN-YWR&mYKs7p~ zUPJiub2yL<98lX!c1Jz^L0oTO4Ad5QO_&1Qp$Xt`@`5s@_P7p%Mf+P`C$+5!A>~^K{rY(-3(M^Au_{* z|K-Krs&1=?+{f|WK~M6AWh0<4P7LFOCZ!^P1}S-7X1W?{HXwjNLr__!nZ5SH*0iKs zSBTWQ$5-zJyAmy|tDq|hA}N^xeRn^SE4%0A$XZz~>1k@b>sFQsU*nSH=f`f*(rq}8 z8eTacOi4|(a=$rzHusLxskv}sdt~)$UG>5F$7yv$heDf;!hXK-qB?uLp(u9nKKmxe znU^`57VV#mcU$HKPhArcqdIDWpjJ8PI&K09h;_To^EYVU^N8v{{wCKb&&;Xl4GBAP z?JnG+rqcQ3aaX+)v$yT@S%_S{LVUwQU~BJRn?zc_50-_+wkc#;qljO_B}0X(qL4JY z<1pgqG;*)1+Kstbv4jLxRzBY-`J)Hyfc?AC(5dcph~RcD-R@d9dM!2o4hhIa5==A$cT&MER*%+j zR)aX3^f)NS^i@Roq7C`;2dxV2^F2!=leh5_!NyF|L`QScSmPHhe`mkrqaMJ`)d6F$F20!3eLNnIK?!xOSg?A+jsNhU=oxL zEb4O5`H~@1VQc!9t7J&PL0wh$;zYePH7DuSKoFITzWTJ}k1D73DGWM{oR zgw^PfKeRh1RBr|c+{;^ex61(g;#8Z|u$vd>jm>H-ek$aDzob3bd}V68UmM|`u)(ht z4$RP{Wv^~BWsHL@dR8Bv;}5~eFm$@Pqwqwim#{Zp!{s^tP9UjGV^;BU>R<4vOT54x zMyuuh#k`oh^ZWeEUj82D80zMkL&{lKj0mE7x$qr6Xl8<;)r8(+!3(! z?J}x6gnf!@vLpW3qR!P)mQSxeN&9NzI(zNYX2xp_-E35I=Ice|GzD}m?9WkhrqP0< z9Yn&k+~mha-pU`h8O zQT%9u13kF^(~ZO=hk-)+p1I>@7=6#{G>y#8IkA=tDiR@re6%w(aPB9WDH3FL;%lgY zmFcdYAm^ZhfY@m6_?FOt+ldpN7q+&Mq;`ppVEQL)KXvo|HT@pkwVm!JnYm&6lP-6IMyi;KSdD<2UyFO+xse>nLC7N(ICN X2F4M0y0f;$A>iUUud|dhH-7#vA}?BQ literal 0 HcmV?d00001 diff --git a/src/file/server.ts b/src/file/server.ts index 2007667a8..449fa2d74 100644 --- a/src/file/server.ts +++ b/src/file/server.ts @@ -54,7 +54,7 @@ async function raw(data: Buffer, type: string, download: boolean, res: express.R async function thumbnail(data: Buffer, type: string, resize: number, res: express.Response): Promise { if (!/^image\/.*$/.test(type)) { - data = fs.readFileSync(`${__dirname}/assets/dummy.png`); + data = fs.readFileSync(`${__dirname}/assets/not-an-image.png`); } let g = gm(data); From bf7601fa8d2728969823fb5a6ce24ae47cf4a992 Mon Sep 17 00:00:00 2001 From: otofune Date: Tue, 7 Nov 2017 22:08:21 +0900 Subject: [PATCH 093/327] run parallely --- package.json | 1 + tools/migration/use-gridfs.js | 33 +++++++++++---------------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index cf291fe2b..96831a3c4 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "format": "gulp format" }, "devDependencies": { + "@prezzemolo/zip": "0.0.3", "@types/bcryptjs": "2.4.1", "@types/body-parser": "1.16.7", "@types/chai": "4.0.4", diff --git a/tools/migration/use-gridfs.js b/tools/migration/use-gridfs.js index 106cbd388..228943f49 100644 --- a/tools/migration/use-gridfs.js +++ b/tools/migration/use-gridfs.js @@ -3,6 +3,7 @@ const { default: db } = require('../../built/db/mongodb') const { default: DriveFile, getGridFSBucket } = require('../../built/api/models/drive-file') const { Duplex } = require('stream') +const { default: zip } = require('@prezzemolo/zip') const writeToGridFS = (bucket, buffer, ...rest) => new Promise((resolve, reject) => { const writeStream = bucket.openUploadStreamWithId(...rest) @@ -45,30 +46,18 @@ const migrateToGridFS = async (doc) => { } async function main() { - let i = 0; + const count = await DriveFile.count({}); - const count = await db.get('drive_files').count({}); + const dop = Number.parseInt(process.argv[2]) || 5 - const iterate = async () => { - if (i == count) return true; - console.log(`${i} / ${count}`); - const doc = (await db.get('drive_files').find({}, { limit: 1, skip: i }))[0] - const res = await migrateToGridFS(doc); - if (!res) { - return false; - } else { - i++ - return await iterate(); - } - } - - const res = await iterate(); - - if (res) { - return 'ok'; - } else { - throw 'something happened'; - } + return zip( + 1, + async (time) => { + const doc = await DriveFile.find({}, { limit: dop, skip: time * dop }) + return Promise.all(doc.map(migrateToGridFS)) + }, + ((count - (count % dop)) / dop) + 1 + ) } main().then(console.dir).catch(console.error) From 36a95c003aa9d66ea29befbcd9ac5c10e6d76938 Mon Sep 17 00:00:00 2001 From: otofune Date: Tue, 7 Nov 2017 22:39:17 +0900 Subject: [PATCH 094/327] fix --- tools/migration/use-gridfs.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tools/migration/use-gridfs.js b/tools/migration/use-gridfs.js index 228943f49..bb7070f55 100644 --- a/tools/migration/use-gridfs.js +++ b/tools/migration/use-gridfs.js @@ -48,16 +48,24 @@ const migrateToGridFS = async (doc) => { async function main() { const count = await DriveFile.count({}); + console.log(`there are ${count} files.`) + const dop = Number.parseInt(process.argv[2]) || 5 + const idop = ((count - (count % dop)) / dop) + 1 return zip( 1, async (time) => { - const doc = await DriveFile.find({}, { limit: dop, skip: time * dop }) + console.log(`${time} / ${idop}`) + const doc = await db.get('drive_files').find({}, { limit: dop, skip: time * dop }) return Promise.all(doc.map(migrateToGridFS)) }, - ((count - (count % dop)) / dop) + 1 - ) + idop + ).then(a => { + const rv = [] + a.forEach(e => rv.push(...e)) + return rv + }) } main().then(console.dir).catch(console.error) From 9f2dc28088ef546f2b552aebb54346e0bcebcb37 Mon Sep 17 00:00:00 2001 From: otofune Date: Tue, 7 Nov 2017 22:57:48 +0900 Subject: [PATCH 095/327] fix --- tools/migration/use-gridfs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/migration/use-gridfs.js b/tools/migration/use-gridfs.js index bb7070f55..a9d2b12e9 100644 --- a/tools/migration/use-gridfs.js +++ b/tools/migration/use-gridfs.js @@ -46,7 +46,7 @@ const migrateToGridFS = async (doc) => { } async function main() { - const count = await DriveFile.count({}); + const count = await db.get('drive_files').count({}); console.log(`there are ${count} files.`) From 0903b0428c1a890e5dbb6db0d5a0a5d6b346bf77 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 7 Nov 2017 23:09:15 +0900 Subject: [PATCH 096/327] #860 --- tools/migration/README.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 tools/migration/README.md diff --git a/tools/migration/README.md b/tools/migration/README.md new file mode 100644 index 000000000..266be9240 --- /dev/null +++ b/tools/migration/README.md @@ -0,0 +1,4 @@ +Misskeyの破壊的変更に対応するいくつかのスニペットがあります。 +MongoDBシェルで実行する必要のあるものとnodeで直接実行する必要のあるものがあります。 + +MongoDBシェルで実行する場合、`use`でデータベースを選択しておく必要があります。 From 6875b68f3e80513274dce925b9bd3e2f012f324e Mon Sep 17 00:00:00 2001 From: otofune Date: Tue, 7 Nov 2017 23:10:38 +0900 Subject: [PATCH 097/327] change all migrations to parallely --- ...change-gridfs-metadata-name-to-filename.js | 24 ++++++++++- tools/migration/issue_882.js | 43 ++++++++++--------- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/tools/migration/change-gridfs-metadata-name-to-filename.js b/tools/migration/change-gridfs-metadata-name-to-filename.js index 0d9e977c6..9128d852c 100644 --- a/tools/migration/change-gridfs-metadata-name-to-filename.js +++ b/tools/migration/change-gridfs-metadata-name-to-filename.js @@ -19,12 +19,32 @@ async function applyNewChange (doc) { } async function main () { - const oldTypeDocs = await DriveFile.find({ + const query = { 'metadata.name': { $exists: true } + } + + const count = await DriveFile.count(query) + + const dop = Number.parseInt(process.argv[2]) || 5 + const idop = ((count - (count % dop)) / dop) + 1 + + return zip( + 1, + async (time) => { + console.log(`${time} / ${idop}`) + const doc = await db.get('drive_files').find(query, { + limit: dop, skip: time * dop + }) + return Promise.all(doc.map(applyNewChange)) + }, + idop + ).then(a => { + const rv = [] + a.forEach(e => rv.push(...e)) + return rv }) - return await Promise.all(oldTypeDocs.map(applyNewChange)) } main().then(console.dir).catch(console.error) diff --git a/tools/migration/issue_882.js b/tools/migration/issue_882.js index 8dab9bb43..aa1141325 100644 --- a/tools/migration/issue_882.js +++ b/tools/migration/issue_882.js @@ -1,6 +1,7 @@ // for Node.js interpret const { default: DriveFile } = require('../../built/api/models/drive-file') +const { default: zip } = require('@prezzemolo/zip') const migrate = async (doc) => { const result = await DriveFile.update(doc._id, { @@ -15,30 +16,32 @@ const migrate = async (doc) => { } async function main() { - let i = 0; - - const count = await DriveFile.count({}); - - const iterate = async () => { - if (i == count) return true; - console.log(`${i} / ${count}`); - const doc = (await DriveFile.find({}, { limit: 1, skip: i }))[0] - const res = await migrate(doc); - if (!res) { - return false; - } else { - i++ - return await iterate(); + const query = { + 'metadata.type': { + $exists: true } } - const res = await iterate(); + const count = await DriveFile.count(query); - if (res) { - return 'ok'; - } else { - throw 'something happened'; - } + const dop = Number.parseInt(process.argv[2]) || 5 + const idop = ((count - (count % dop)) / dop) + 1 + + return zip( + 1, + async (time) => { + console.log(`${time} / ${idop}`) + const doc = await db.get('drive_files').find(query, { + limit: dop, skip: time * dop + }) + return Promise.all(doc.map(migrate)) + }, + idop + ).then(a => { + const rv = [] + a.forEach(e => rv.push(...e)) + return rv + }) } main().then(console.dir).catch(console.error) From 66a08aa22f295e6a29741a202811c2e5e3f650a1 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 7 Nov 2017 23:21:54 +0900 Subject: [PATCH 098/327] :v: --- tools/migration/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/migration/README.md b/tools/migration/README.md index 266be9240..cd553efb9 100644 --- a/tools/migration/README.md +++ b/tools/migration/README.md @@ -2,3 +2,7 @@ Misskeyの破壊的変更に対応するいくつかのスニペットがあり MongoDBシェルで実行する必要のあるものとnodeで直接実行する必要のあるものがあります。 MongoDBシェルで実行する場合、`use`でデータベースを選択しておく必要があります。 + +nodeで実行するいくつかのスニペットは、並列処理させる数を引数で設定できるものがあります。 +処理中にエラーで落ちる場合は、メモリが足りていない可能性があるので、少ない数に設定してみてください。 +※デフォルトは`5`です。 From 04dd2cb12bdc5a72ad004802be68c9867efe05a6 Mon Sep 17 00:00:00 2001 From: otofune Date: Tue, 7 Nov 2017 23:38:18 +0900 Subject: [PATCH 099/327] rename with execution order, add desc & initial creation script --- tools/init-migration-file.sh | 37 +++++++++++++++++++ tools/migration/README.md | 3 ++ ...ridfs.js => node.1509958623.use-gridfs.js} | 0 ...hange-gridfs-metadata-name-to-filename.js} | 0 ...ue_882.js => node.1510056272.issue_882.js} | 0 ...le.js => shell.1487734995.user-profile.js} | 0 ... => shell.1489951459.like-to-reactions.js} | 0 ... => shell.1509507382.reply_to-to-reply.js} | 0 8 files changed, 40 insertions(+) create mode 100755 tools/init-migration-file.sh rename tools/migration/{use-gridfs.js => node.1509958623.use-gridfs.js} (100%) rename tools/migration/{change-gridfs-metadata-name-to-filename.js => node.1510016282.change-gridfs-metadata-name-to-filename.js} (100%) rename tools/migration/{issue_882.js => node.1510056272.issue_882.js} (100%) rename tools/migration/{user-profile.js => shell.1487734995.user-profile.js} (100%) rename tools/migration/{like-to-reactions.js => shell.1489951459.like-to-reactions.js} (100%) rename tools/migration/{reply_to-to-reply.js => shell.1509507382.reply_to-to-reply.js} (100%) diff --git a/tools/init-migration-file.sh b/tools/init-migration-file.sh new file mode 100755 index 000000000..c6a2b862e --- /dev/null +++ b/tools/init-migration-file.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +usage() { + echo "$0 [-t type] [-n name]" + echo " type: [node | shell]" + echo " name: if no present, set untitled" + exit 0 +} + +while getopts :t:n:h OPT +do + case $OPT in + t) type=$OPTARG + ;; + n) name=$OPTARG + ;; + h) usage + ;; + \?) usage + ;; + :) usage + ;; + esac +done + +if [ "$type" = "" ] +then + echo "no type present!!!" + usage +fi + +if [ "$name" = "" ] +then + name="untitled" +fi + +touch "$(realpath $(dirname $BASH_SOURCE))/migration/$type.$(date +%s).$name.js" diff --git a/tools/migration/README.md b/tools/migration/README.md index cd553efb9..d52e84b35 100644 --- a/tools/migration/README.md +++ b/tools/migration/README.md @@ -1,8 +1,11 @@ Misskeyの破壊的変更に対応するいくつかのスニペットがあります。 MongoDBシェルで実行する必要のあるものとnodeで直接実行する必要のあるものがあります。 +ファイル名が `shell.` から始まるものは前者、 `node.` から始まるものは後者です。 MongoDBシェルで実行する場合、`use`でデータベースを選択しておく必要があります。 nodeで実行するいくつかのスニペットは、並列処理させる数を引数で設定できるものがあります。 処理中にエラーで落ちる場合は、メモリが足りていない可能性があるので、少ない数に設定してみてください。 ※デフォルトは`5`です。 + +ファイルを作成する際は `../init-migration-file.sh -t _type_ -n _name_` を実行すると _type_._unixtime_._name_.js が生成されます diff --git a/tools/migration/use-gridfs.js b/tools/migration/node.1509958623.use-gridfs.js similarity index 100% rename from tools/migration/use-gridfs.js rename to tools/migration/node.1509958623.use-gridfs.js diff --git a/tools/migration/change-gridfs-metadata-name-to-filename.js b/tools/migration/node.1510016282.change-gridfs-metadata-name-to-filename.js similarity index 100% rename from tools/migration/change-gridfs-metadata-name-to-filename.js rename to tools/migration/node.1510016282.change-gridfs-metadata-name-to-filename.js diff --git a/tools/migration/issue_882.js b/tools/migration/node.1510056272.issue_882.js similarity index 100% rename from tools/migration/issue_882.js rename to tools/migration/node.1510056272.issue_882.js diff --git a/tools/migration/user-profile.js b/tools/migration/shell.1487734995.user-profile.js similarity index 100% rename from tools/migration/user-profile.js rename to tools/migration/shell.1487734995.user-profile.js diff --git a/tools/migration/like-to-reactions.js b/tools/migration/shell.1489951459.like-to-reactions.js similarity index 100% rename from tools/migration/like-to-reactions.js rename to tools/migration/shell.1489951459.like-to-reactions.js diff --git a/tools/migration/reply_to-to-reply.js b/tools/migration/shell.1509507382.reply_to-to-reply.js similarity index 100% rename from tools/migration/reply_to-to-reply.js rename to tools/migration/shell.1509507382.reply_to-to-reply.js From 253747ecfb8245d94fe0110e01d8f2ca65a28f5d Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 8 Nov 2017 14:43:42 +0900 Subject: [PATCH 100/327] Fix bugs --- src/api/endpoints/drive/stream.ts | 5 +---- src/api/serializers/drive-file.ts | 2 ++ src/web/app/mobile/tags/drive/file-viewer.tag | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/api/endpoints/drive/stream.ts b/src/api/endpoints/drive/stream.ts index 32f7ac7e0..58e7d1167 100644 --- a/src/api/endpoints/drive/stream.ts +++ b/src/api/endpoints/drive/stream.ts @@ -52,15 +52,12 @@ module.exports = (params, user) => new Promise(async (res, rej) => { }; } if (type) { - query.type = new RegExp(`^${type.replace(/\*/g, '.+?')}$`); + query.contentType = new RegExp(`^${type.replace(/\*/g, '.+?')}$`); } // Issue query const files = await DriveFile .find(query, { - fields: { - data: false - }, limit: limit, sort: sort }); diff --git a/src/api/serializers/drive-file.ts b/src/api/serializers/drive-file.ts index 3b76979a4..dcdaa01fa 100644 --- a/src/api/serializers/drive-file.ts +++ b/src/api/serializers/drive-file.ts @@ -49,6 +49,8 @@ export default ( _target.created_at = _file.uploadDate; _target.name = _file.filename; _target.type = _file.contentType; + _target.datasize = _file.length; + _target.md5 = _file.md5; _target = Object.assign(_target, _file.metadata); diff --git a/src/web/app/mobile/tags/drive/file-viewer.tag b/src/web/app/mobile/tags/drive/file-viewer.tag index e6129652b..8dc49a086 100644 --- a/src/web/app/mobile/tags/drive/file-viewer.tag +++ b/src/web/app/mobile/tags/drive/file-viewer.tag @@ -44,7 +44,7 @@

%i18n:mobile.tags.mk-drive-file-viewer.hash%

- { file.hash } + { file.md5 } + + diff --git a/src/web/app/desktop/tags/pages/selectdrive.tag b/src/web/app/desktop/tags/pages/selectdrive.tag index 63fc588fa..9c3ac16eb 100644 --- a/src/web/app/desktop/tags/pages/selectdrive.tag +++ b/src/web/app/desktop/tags/pages/selectdrive.tag @@ -1,15 +1,16 @@
- - - + + +
diff --git a/src/web/app/desktop/tags/index.js b/src/web/app/desktop/tags/index.js index 7997bcc7f..c36a06e49 100644 --- a/src/web/app/desktop/tags/index.js +++ b/src/web/app/desktop/tags/index.js @@ -57,6 +57,7 @@ require('./pages/entrance.tag'); require('./pages/entrance/signin.tag'); require('./pages/entrance/signup.tag'); require('./pages/home.tag'); +require('./pages/home-customize.tag'); require('./pages/user.tag'); require('./pages/post.tag'); require('./pages/search.tag'); diff --git a/src/web/app/desktop/tags/pages/home-customize.tag b/src/web/app/desktop/tags/pages/home-customize.tag new file mode 100644 index 000000000..443401561 --- /dev/null +++ b/src/web/app/desktop/tags/pages/home-customize.tag @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/web/app/desktop/tags/settings.tag b/src/web/app/desktop/tags/settings.tag index eabddfb43..4c16f9eaa 100644 --- a/src/web/app/desktop/tags/settings.tag +++ b/src/web/app/desktop/tags/settings.tag @@ -38,6 +38,7 @@

デザイン

+ ホームをカスタマイズ
diff --git a/src/web/app/desktop/tags/ui.tag b/src/web/app/desktop/tags/ui.tag index 0a3e8d9c5..6a4982877 100644 --- a/src/web/app/desktop/tags/ui.tag +++ b/src/web/app/desktop/tags/ui.tag @@ -37,7 +37,7 @@ - +
diff --git a/src/web/app/init.js b/src/web/app/init.js index 5a6899ed4..7e3c2ee37 100644 --- a/src/web/app/init.js +++ b/src/web/app/init.js @@ -11,7 +11,6 @@ import checkForUpdate from './common/scripts/check-for-update'; import Connection from './common/scripts/home-stream'; import Progress from './common/scripts/loading'; import mixin from './common/mixins'; -import generateDefaultUserdata from './common/scripts/generate-default-userdata'; import CONFIG from './common/scripts/config'; require('./common/tags'); @@ -156,9 +155,7 @@ function fetchme(token, cb) { res.json().then(i => { me = i; me.token = token; - - // initialize it if user data is empty - me.data ? done() : init(); + done(); }); }, () => { // When failure // Render the error screen @@ -170,17 +167,6 @@ function fetchme(token, cb) { function done() { if (cb) cb(me); } - - // Initialize user data - function init() { - const data = generateDefaultUserdata(); - api(token, 'i/appdata/set', { - data - }).then(() => { - me.data = data; - done(); - }); - } } // BSoD diff --git a/tools/migration/node.2017-11-08..js b/tools/migration/node.2017-11-08..js new file mode 100644 index 000000000..e25b83b3f --- /dev/null +++ b/tools/migration/node.2017-11-08..js @@ -0,0 +1,89 @@ +const uuid = require('uuid'); +const { default: User } = require('../../built/api/models/user') +const { default: zip } = require('@prezzemolo/zip') + +const home = { + left: [ + 'profile', + 'calendar', + 'activity', + 'rss-reader', + 'trends', + 'photo-stream', + 'version' + ], + right: [ + 'broadcast', + 'notifications', + 'user-recommendation', + 'recommended-polls', + 'server', + 'donation', + 'nav', + 'tips' + ] +}; + + +const migrate = async (doc) => { + + //#region Construct home data + const homeData = []; + + home.left.forEach(widget => { + homeData.push({ + name: widget, + id: uuid(), + place: 'left', + data: {} + }); + }); + + home.right.forEach(widget => { + homeData.push({ + name: widget, + id: uuid(), + place: 'right', + data: {} + }); + }); + //#endregion + + const result = await User.update(doc._id, { + $unset: { + data: '' + }, + $set: { + 'settings': {}, + 'client_settings.home': homeData, + 'client_settings.show_donation': false + } + }) + + return added && result.ok === 1 +} + +async function main() { + const count = await db.get('users').count(); + + console.log(`there are ${count} users.`) + + const dop = Number.parseInt(process.argv[2]) || 5 + const idop = ((count - (count % dop)) / dop) + 1 + + return zip( + 1, + async (time) => { + console.log(`${time} / ${idop}`) + const docs = await db.get('users').find({}, { limit: dop, skip: time * dop }) + return Promise.all(docs.map(migrate)) + }, + idop + ).then(a => { + const rv = [] + a.forEach(e => rv.push(...e)) + return rv + }) +} + +main().then(console.dir).catch(console.error) From 6ff292ab1ba1ea510ed48ecbc2b9694976adc0ff Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 8 Nov 2017 23:44:22 +0900 Subject: [PATCH 109/327] v2974 --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d387bff55..fbbb1acbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ChangeLog (Release Notes) ========================= 主に notable な changes を書いていきます -unreleased +2974 (2017/11/08) ----------------- * ホームのカスタマイズを実装するなど diff --git a/package.json b/package.json index 62006e067..7eecfec57 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo ", - "version": "0.0.2971", + "version": "0.0.2974", "license": "MIT", "description": "A miniblog-based SNS", "bugs": "https://github.com/syuilo/misskey/issues", From df9ca34175bd89298f8d894dc924517fae8f8310 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 8 Nov 2017 23:48:23 +0900 Subject: [PATCH 110/327] oops --- tools/migration/{node.2017-11-08..js => node.2017-11-08.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tools/migration/{node.2017-11-08..js => node.2017-11-08.js} (100%) diff --git a/tools/migration/node.2017-11-08..js b/tools/migration/node.2017-11-08.js similarity index 100% rename from tools/migration/node.2017-11-08..js rename to tools/migration/node.2017-11-08.js From 759a392fe9b7aeb66e380c4dfcd388de304c2d75 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 8 Nov 2017 23:54:02 +0900 Subject: [PATCH 111/327] Fix test --- test/api.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/api.js b/test/api.js index a2eaa4e62..49f1faa53 100644 --- a/test/api.js +++ b/test/api.js @@ -1138,7 +1138,9 @@ function insertSakurako(opts) { username: 'sakurako', username_lower: 'sakurako', password: '$2a$08$FnHXg3tP.M/kINWgQSXNqeoBsiVrkj.ecXX8mW9rfBzMRkibYfjYy', // HimawariDaisuki06160907 - profile: {} + profile: {}, + settings: {}, + client_settings: {} }, opts)); } @@ -1148,7 +1150,9 @@ function insertHimawari(opts) { username: 'himawari', username_lower: 'himawari', password: '$2a$08$OPESxR2RE/ZijjGanNKk6ezSqGFitqsbZqTjWUZPLhORMKxHCbc4O', // ilovesakurako - profile: {} + profile: {}, + settings: {}, + client_settings: {} }, opts)); } From 888473f4d4c7cf549b9815d7ef297f797516c4f3 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 8 Nov 2017 23:55:17 +0900 Subject: [PATCH 112/327] Remove blank line --- tools/migration/node.2017-11-08.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/migration/node.2017-11-08.js b/tools/migration/node.2017-11-08.js index e25b83b3f..097f94044 100644 --- a/tools/migration/node.2017-11-08.js +++ b/tools/migration/node.2017-11-08.js @@ -24,7 +24,6 @@ const home = { ] }; - const migrate = async (doc) => { //#region Construct home data From 4da2439d20cbdcc7779dcca155a41310c38a688b Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 9 Nov 2017 00:12:47 +0900 Subject: [PATCH 113/327] Fix --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7eecfec57..a65e8de67 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "format": "gulp format" }, "devDependencies": { - "@prezzemolo/zip": "0.0.3", "@types/bcryptjs": "2.4.1", "@types/body-parser": "1.16.7", "@types/chai": "4.0.4", @@ -93,6 +92,7 @@ "webpack": "3.8.1" }, "dependencies": { + "@prezzemolo/zip": "0.0.3", "@prezzemolo/rap": "0.1.2", "accesses": "2.5.0", "animejs": "2.2.0", From dc53f52c65c4a19389ecf179566586aaa3463d08 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 9 Nov 2017 00:13:32 +0900 Subject: [PATCH 114/327] Fix bug --- tools/migration/node.2017-11-08.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/migration/node.2017-11-08.js b/tools/migration/node.2017-11-08.js index 097f94044..c5a5176fa 100644 --- a/tools/migration/node.2017-11-08.js +++ b/tools/migration/node.2017-11-08.js @@ -63,7 +63,7 @@ const migrate = async (doc) => { } async function main() { - const count = await db.get('users').count(); + const count = await User.count(); console.log(`there are ${count} users.`) @@ -74,7 +74,7 @@ async function main() { 1, async (time) => { console.log(`${time} / ${idop}`) - const docs = await db.get('users').find({}, { limit: dop, skip: time * dop }) + const docs = await User.find({}, { limit: dop, skip: time * dop }) return Promise.all(docs.map(migrate)) }, idop From 2baf318517b2ccb60c2d3f0c7505acf6fd82af90 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 9 Nov 2017 00:17:24 +0900 Subject: [PATCH 115/327] Fix --- tools/migration/node.2017-11-08.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/migration/node.2017-11-08.js b/tools/migration/node.2017-11-08.js index c5a5176fa..196a5a90c 100644 --- a/tools/migration/node.2017-11-08.js +++ b/tools/migration/node.2017-11-08.js @@ -59,7 +59,7 @@ const migrate = async (doc) => { } }) - return added && result.ok === 1 + return result.ok === 1 } async function main() { From bdffc00dca64672b837f7e4b20a3e0a1aaaf766f Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 9 Nov 2017 02:28:01 +0900 Subject: [PATCH 116/327] Clean up --- locales/en.yml | 1 - locales/ja.yml | 1 - src/web/app/desktop/tags/drive/file.tag | 3 --- 3 files changed, 5 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 679651033..9e19728af 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -225,7 +225,6 @@ desktop: mk-drive-browser-file: avatar: "Avatar" banner: "Banner" - wallpaper: "Wallpaper" mk-drive-browser-folder-contextmenu: move-to-this-folder: "Move to this folder" diff --git a/locales/ja.yml b/locales/ja.yml index c146475b2..39cc41d0a 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -225,7 +225,6 @@ desktop: mk-drive-browser-file: avatar: "アバター" banner: "バナー" - wallpaper: "壁紙" mk-drive-browser-folder-contextmenu: move-to-this-folder: "このフォルダへ移動" diff --git a/src/web/app/desktop/tags/drive/file.tag b/src/web/app/desktop/tags/drive/file.tag index 64838d681..bf9d38bd2 100644 --- a/src/web/app/desktop/tags/drive/file.tag +++ b/src/web/app/desktop/tags/drive/file.tag @@ -5,9 +5,6 @@

%i18n:desktop.tags.mk-drive-browser-file.banner%

-
-

%i18n:desktop.tags.mk-drive-browser-file.wallpaper%

-

{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }{ file.name.substr(file.name.lastIndexOf('.')) }

+ + diff --git a/src/web/app/desktop/tags/home.tag b/src/web/app/desktop/tags/home.tag index f0c71a7ea..531adca4c 100644 --- a/src/web/app/desktop/tags/home.tag +++ b/src/web/app/desktop/tags/home.tag @@ -9,6 +9,7 @@ + diff --git a/src/web/app/desktop/tags/index.js b/src/web/app/desktop/tags/index.js index c36a06e49..75bbdae95 100644 --- a/src/web/app/desktop/tags/index.js +++ b/src/web/app/desktop/tags/index.js @@ -12,6 +12,7 @@ require('./drive/nav-folder.tag'); require('./drive/browser-window.tag'); require('./drive/browser.tag'); require('./select-file-from-drive-window.tag'); +require('./select-folder-from-drive-window.tag'); require('./crop-window.tag'); require('./settings.tag'); require('./settings-window.tag'); @@ -38,6 +39,7 @@ require('./home-widgets/recommended-polls.tag'); require('./home-widgets/trends.tag'); require('./home-widgets/activity.tag'); require('./home-widgets/server.tag'); +require('./home-widgets/slideshow.tag'); require('./timeline.tag'); require('./messaging/window.tag'); require('./messaging/room-window.tag'); diff --git a/src/web/app/desktop/tags/select-folder-from-drive-window.tag b/src/web/app/desktop/tags/select-folder-from-drive-window.tag new file mode 100644 index 000000000..375f428bf --- /dev/null +++ b/src/web/app/desktop/tags/select-folder-from-drive-window.tag @@ -0,0 +1,112 @@ + + + + + + + +
+ + +
+
+
+ + +
From 3dbdd3e04883490a5731847d07b569cce0378c1b Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 9 Nov 2017 02:58:04 +0900 Subject: [PATCH 119/327] v2984 --- CHANGELOG.md | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbeac2d74..81b0246f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,8 @@ ChangeLog (Release Notes) ========================= 主に notable な changes を書いていきます -unreleased ----------- +2984 (2017/11/09) +----------------- * スライドショーウィジェットを追加 2974 (2017/11/08) diff --git a/package.json b/package.json index a65e8de67..7e621f782 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo ", - "version": "0.0.2974", + "version": "0.0.2984", "license": "MIT", "description": "A miniblog-based SNS", "bugs": "https://github.com/syuilo/misskey/issues", From f7408f4e82f0c6c94fb8b27dbfc0f9bdd2a62b5d Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 9 Nov 2017 03:05:26 +0900 Subject: [PATCH 120/327] Fix glitch --- src/web/app/desktop/tags/home-widgets/slideshow.tag | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/web/app/desktop/tags/home-widgets/slideshow.tag b/src/web/app/desktop/tags/home-widgets/slideshow.tag index da65bf91b..f70238860 100644 --- a/src/web/app/desktop/tags/home-widgets/slideshow.tag +++ b/src/web/app/desktop/tags/home-widgets/slideshow.tag @@ -1,7 +1,7 @@

クリックしてフォルダを指定してください

-

このフォルダには画像がありません

+

このフォルダには画像がありません

@@ -55,7 +55,7 @@ this.size = this.opts.data.hasOwnProperty('size') ? this.opts.data.size : 0; this.folder = this.opts.data.hasOwnProperty('folder') ? this.opts.data.folder : undefined; this.images = []; - this.fetching = false; + this.fetching = true; this.on('mount', () => { this.applySize(); From 173ee4bb0eb970b0f619233ee7cec649e90d510f Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 9 Nov 2017 13:33:41 +0900 Subject: [PATCH 121/327] =?UTF-8?q?=E3=82=B9=E3=83=A9=E3=82=A4=E3=83=89?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=BC=E3=82=92=E3=83=A9=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=A0=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/web/app/desktop/tags/home-widgets/slideshow.tag | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/web/app/desktop/tags/home-widgets/slideshow.tag b/src/web/app/desktop/tags/home-widgets/slideshow.tag index f70238860..aa1e45fad 100644 --- a/src/web/app/desktop/tags/home-widgets/slideshow.tag +++ b/src/web/app/desktop/tags/home-widgets/slideshow.tag @@ -100,14 +100,12 @@ this.change = () => { if (this.images.length == 0) return; - if (this.index >= this.images.length) this.index = 0; - const img = `url(${ this.images[this.index].url }?thumbnail&size=1024)`; + const index = Math.floor(Math.random() * this.images.length); + const img = `url(${ this.images[index].url }?thumbnail&size=1024)`; this.refs.slideB.style.backgroundImage = img; - this.index++; - anime({ targets: this.refs.slideB, opacity: 1, @@ -136,8 +134,7 @@ }).then(images => { this.update({ fetching: false, - images: images, - index: 0 + images: images }); this.refs.slideA.style.backgroundImage = ''; this.refs.slideB.style.backgroundImage = ''; From 70ba9c57a7440a82a279e422afba7102f00f080e Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 9 Nov 2017 15:07:09 +0900 Subject: [PATCH 122/327] #886 --- CHANGELOG.md | 4 + locales/en.yml | 4 + locales/ja.yml | 4 + .../app/desktop/tags/home-widgets/channel.tag | 309 ++++++++++++++++++ src/web/app/desktop/tags/home.tag | 1 + src/web/app/desktop/tags/index.js | 1 + 6 files changed, 323 insertions(+) create mode 100644 src/web/app/desktop/tags/home-widgets/channel.tag diff --git a/CHANGELOG.md b/CHANGELOG.md index 81b0246f0..da2b187d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ChangeLog (Release Notes) ========================= 主に notable な changes を書いていきます +unlereased +----------------- +* チャンネルウィジェットを追加 + 2984 (2017/11/09) ----------------- * スライドショーウィジェットを追加 diff --git a/locales/en.yml b/locales/en.yml index 9e19728af..c69dc22b1 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -365,6 +365,10 @@ desktop: title: "Donation" text: "To manage Misskey we spend money for our domain server etc.. There's no incomes for us so we need your tip. If you're interested contact {}. Thank you for your contribution!" + mk-channel-home-widget: + title: "Channel" + settings: "Widget settings" + mk-repost-form: quote: "Quote..." cancel: "Cancel" diff --git a/locales/ja.yml b/locales/ja.yml index 39cc41d0a..782b87bd8 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -365,6 +365,10 @@ desktop: title: "寄付のお願い" text: "Misskeyの運営にはドメイン、サーバー等のコストが掛かります。Misskeyは広告を掲載したりしないため、収入を皆様からの寄付に頼っています。もしご興味があれば、{}までご連絡ください。ご協力ありがとうございます。" + mk-channel-home-widget: + title: "チャンネル" + settings: "ウィジェットの設定" + mk-repost-form: quote: "引用する..." cancel: "キャンセル" diff --git a/src/web/app/desktop/tags/home-widgets/channel.tag b/src/web/app/desktop/tags/home-widgets/channel.tag new file mode 100644 index 000000000..b85579045 --- /dev/null +++ b/src/web/app/desktop/tags/home-widgets/channel.tag @@ -0,0 +1,309 @@ + +

{ + channel ? channel.title : '%i18n:desktop.tags.mk-channel-home-widget.title%' + }

+ + + + +
+ + +

読み込み中

+
+

まだ投稿がありません

+ +
+ + + +
+ + +
+ { post.index }: + { post.user.name } + ID:{ post.user.username } +
+
+ >>{ post.reply.index } + { post.text } +
+ + + { + + +
+
+ + +
+ + +

>>{ reply.index } ({ reply.user.name }): [x]

+ + + +
diff --git a/src/web/app/desktop/tags/home.tag b/src/web/app/desktop/tags/home.tag index 531adca4c..5fc5e0d6a 100644 --- a/src/web/app/desktop/tags/home.tag +++ b/src/web/app/desktop/tags/home.tag @@ -15,6 +15,7 @@ + diff --git a/src/web/app/desktop/tags/index.js b/src/web/app/desktop/tags/index.js index 75bbdae95..ff513657a 100644 --- a/src/web/app/desktop/tags/index.js +++ b/src/web/app/desktop/tags/index.js @@ -40,6 +40,7 @@ require('./home-widgets/trends.tag'); require('./home-widgets/activity.tag'); require('./home-widgets/server.tag'); require('./home-widgets/slideshow.tag'); +require('./home-widgets/channel.tag'); require('./timeline.tag'); require('./messaging/window.tag'); require('./messaging/room-window.tag'); From fbd9ef9cad41e27c584576d91068c8132996b67d Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 9 Nov 2017 15:07:43 +0900 Subject: [PATCH 123/327] v2988 --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da2b187d1..c0d49f476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ChangeLog (Release Notes) ========================= 主に notable な changes を書いていきます -unlereased +2988 (2017/11/09) ----------------- * チャンネルウィジェットを追加 diff --git a/package.json b/package.json index 7e621f782..d537af4f6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo ", - "version": "0.0.2984", + "version": "0.0.2988", "license": "MIT", "description": "A miniblog-based SNS", "bugs": "https://github.com/syuilo/misskey/issues", From 515a8740816ab6f3a2a8754b8a85001b0c844bc8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 9 Nov 2017 21:19:08 +0900 Subject: [PATCH 124/327] :art: --- .../app/desktop/tags/home-widgets/channel.tag | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/web/app/desktop/tags/home-widgets/channel.tag b/src/web/app/desktop/tags/home-widgets/channel.tag index b85579045..0434b6e88 100644 --- a/src/web/app/desktop/tags/home-widgets/channel.tag +++ b/src/web/app/desktop/tags/home-widgets/channel.tag @@ -103,11 +103,20 @@ -

>>{ reply.index } ({ reply.user.name }): [x]

+ + diff --git a/src/web/app/desktop/tags/home.tag b/src/web/app/desktop/tags/home.tag index ecfe23ade..452499d70 100644 --- a/src/web/app/desktop/tags/home.tag +++ b/src/web/app/desktop/tags/home.tag @@ -5,6 +5,7 @@ +
+

Tip: 一部のウィジェットは、クリックすることで表示を変更することができます。

@@ -213,11 +215,22 @@ break; } - this.home.push(riot.mount(el, { + const tag = riot.mount(el, { id: widget.id, data: widget.data, tl: this.refs.tl - })[0]); + })[0]; + + this.home.push(tag); + + if (this.opts.customize) { + actualEl.oncontextmenu = e => { + e.preventDefault(); + e.stopImmediatePropagation(); + if (tag.func) tag.func(); + return false; + }; + } }; this.addWidget = () => { From f988328b5ad67c51fa5f170d6fb36d21ff2fce0c Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 12 Nov 2017 05:25:51 +0900 Subject: [PATCH 161/327] v3028 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbf993b9d..737c28536 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ChangeLog (Release Notes) ========================= 主に notable な changes を書いていきます +3028 (2017/11/12) +----------------- +* ウィジェットの表示をコンパクトにできるように + 3026 (2017/11/12) ----------------- * バグ修正 diff --git a/package.json b/package.json index 1391264d3..a364822a0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo ", - "version": "0.0.3026", + "version": "0.0.3028", "license": "MIT", "description": "A miniblog-based SNS", "bugs": "https://github.com/syuilo/misskey/issues", From 0ff8c11ee1a932b90b400c2d239dbee14ad233ba Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 12 Nov 2017 05:38:48 +0900 Subject: [PATCH 162/327] :v: --- src/web/app/desktop/mixins/widget.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/web/app/desktop/mixins/widget.js b/src/web/app/desktop/mixins/widget.js index d21811fa2..2bf858ebe 100644 --- a/src/web/app/desktop/mixins/widget.js +++ b/src/web/app/desktop/mixins/widget.js @@ -16,6 +16,7 @@ riot.mixin('widget', { }, save: function() { + this.update(); this.api('i/update_home', { id: this.id, data: this.data From 393113a2822a55e77e195a8a34a486528f335fa4 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 12 Nov 2017 05:52:35 +0900 Subject: [PATCH 163/327] Improve widgets --- .../app/desktop/tags/home-widgets/channel.tag | 18 +++++--- .../app/desktop/tags/home-widgets/profile.tag | 43 +++++++++++++++++-- .../tags/home-widgets/recommended-polls.tag | 17 ++++++-- .../desktop/tags/home-widgets/rss-reader.tag | 17 +++++++- .../app/desktop/tags/home-widgets/trends.tag | 17 ++++++-- 5 files changed, 96 insertions(+), 16 deletions(-) diff --git a/src/web/app/desktop/tags/home-widgets/channel.tag b/src/web/app/desktop/tags/home-widgets/channel.tag index c1a29f60b..b5a57277f 100644 --- a/src/web/app/desktop/tags/home-widgets/channel.tag +++ b/src/web/app/desktop/tags/home-widgets/channel.tag @@ -1,8 +1,10 @@ -

{ - channel ? channel.title : '%i18n:desktop.tags.mk-channel-home-widget.title%' - }

- + +

{ + channel ? channel.title : '%i18n:desktop.tags.mk-channel-home-widget.title%' + }

+ +

%i18n:desktop.tags.mk-channel-home-widget.get-started%

diff --git a/src/web/app/desktop/tags/home-widgets/profile.tag b/src/web/app/desktop/tags/home-widgets/profile.tag index e6a875211..3fdf4343e 100644 --- a/src/web/app/desktop/tags/home-widgets/profile.tag +++ b/src/web/app/desktop/tags/home-widgets/profile.tag @@ -1,5 +1,7 @@ - - avatar{ I.name } + + + avatar + { I.name }

@{ I.username }

diff --git a/src/web/app/desktop/tags/home-widgets/rss-reader.tag b/src/web/app/desktop/tags/home-widgets/rss-reader.tag index e9b740762..9c213cf79 100644 --- a/src/web/app/desktop/tags/home-widgets/rss-reader.tag +++ b/src/web/app/desktop/tags/home-widgets/rss-reader.tag @@ -1,6 +1,8 @@ -

RSS

- + +

RSS

+ +
@@ -62,6 +64,12 @@
diff --git a/src/web/app/desktop/tags/home-widgets/trends.tag b/src/web/app/desktop/tags/home-widgets/trends.tag index 021df3f72..f824d89cf 100644 --- a/src/web/app/desktop/tags/home-widgets/trends.tag +++ b/src/web/app/desktop/tags/home-widgets/trends.tag @@ -1,6 +1,8 @@ -

%i18n:desktop.tags.mk-trends-home-widget.title%

- + +

%i18n:desktop.tags.mk-trends-home-widget.title%

+ +

{ post.text }

@{ post.user.username }

@@ -72,7 +74,11 @@ From 62a7dd28651d1b6316fb4d2cac80a8d3f8359b7d Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 12 Nov 2017 05:53:29 +0900 Subject: [PATCH 164/327] v3031 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 737c28536..bb44e74af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ChangeLog (Release Notes) ========================= 主に notable な changes を書いていきます +3031 (2017/11/12) +----------------- +* ウィジェットの強化 + 3028 (2017/11/12) ----------------- * ウィジェットの表示をコンパクトにできるように diff --git a/package.json b/package.json index a364822a0..84589ec42 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo ", - "version": "0.0.3028", + "version": "0.0.3031", "license": "MIT", "description": "A miniblog-based SNS", "bugs": "https://github.com/syuilo/misskey/issues", From 64c66daa02ae3ea550a2b34cafc8d09cf92d8b42 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 12 Nov 2017 06:06:09 +0900 Subject: [PATCH 165/327] :art: --- src/web/app/desktop/tags/home-widgets/channel.tag | 1 + src/web/app/desktop/tags/home-widgets/profile.tag | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/web/app/desktop/tags/home-widgets/channel.tag b/src/web/app/desktop/tags/home-widgets/channel.tag index b5a57277f..8f3d39d0e 100644 --- a/src/web/app/desktop/tags/home-widgets/channel.tag +++ b/src/web/app/desktop/tags/home-widgets/channel.tag @@ -11,6 +11,7 @@ :scope display block background #fff + overflow hidden > .title z-index 2 diff --git a/src/web/app/desktop/tags/home-widgets/profile.tag b/src/web/app/desktop/tags/home-widgets/profile.tag index 3fdf4343e..1439a423b 100644 --- a/src/web/app/desktop/tags/home-widgets/profile.tag +++ b/src/web/app/desktop/tags/home-widgets/profile.tag @@ -1,6 +1,6 @@ - avatar + avatar { I.name }

@{ I.username }

diff --git a/src/web/app/desktop/tags/home-widgets/photo-stream.tag b/src/web/app/desktop/tags/home-widgets/photo-stream.tag index 7f8dd8563..84d0020a6 100644 --- a/src/web/app/desktop/tags/home-widgets/photo-stream.tag +++ b/src/web/app/desktop/tags/home-widgets/photo-stream.tag @@ -1,5 +1,5 @@ - - + +

%i18n:desktop.tags.mk-photo-stream-home-widget.title%

%i18n:common.loading%

@@ -14,6 +14,17 @@ display block background #fff + &[data-melt] + background transparent !important + border none !important + + > .stream + padding 0 + + > .img + border solid 4px transparent + border-radius 8px + > .title z-index 1 margin 0 @@ -58,7 +69,7 @@ diff --git a/src/web/app/desktop/tags/home-widgets/profile.tag b/src/web/app/desktop/tags/home-widgets/profile.tag index 1439a423b..7d4cf862f 100644 --- a/src/web/app/desktop/tags/home-widgets/profile.tag +++ b/src/web/app/desktop/tags/home-widgets/profile.tag @@ -1,4 +1,4 @@ - + avatar { I.name } @@ -36,6 +36,20 @@ > .username display none + &[data-melt] + background transparent !important + border none !important + + > .banner + visibility hidden + + > .avatar + box-shadow none + + > .name + color #666 + text-shadow none + > .banner height 100px background-color #f5f5f5 @@ -77,7 +91,7 @@ import updateBanner from '../../scripts/update-banner'; this.data = { - compact: false + design: 0 }; this.mixin('widget'); @@ -93,7 +107,7 @@ }; this.func = () => { - this.data.compact = !this.data.compact; + if (++this.data.design == 3) this.data.design = 0; this.save(); }; diff --git a/src/web/app/desktop/tags/home-widgets/server.tag b/src/web/app/desktop/tags/home-widgets/server.tag index b2e3b9d90..094af8759 100644 --- a/src/web/app/desktop/tags/home-widgets/server.tag +++ b/src/web/app/desktop/tags/home-widgets/server.tag @@ -1,5 +1,5 @@ - - + +

%i18n:desktop.tags.mk-server-home-widget.title%

@@ -15,6 +15,10 @@ display block background #fff + &[data-melt] + background transparent !important + border none !important + > .title z-index 1 margin 0 @@ -60,7 +64,7 @@ this.data = { view: 0, - compact: false + design: 0 }; this.mixin('widget'); @@ -90,7 +94,7 @@ }; this.func = () => { - this.data.compact = !this.data.compact; + if (++this.data.design == 3) this.data.design = 0; this.save(); }; diff --git a/src/web/app/desktop/tags/home-widgets/timemachine.tag b/src/web/app/desktop/tags/home-widgets/timemachine.tag index 75e9f3a33..984258d2b 100644 --- a/src/web/app/desktop/tags/home-widgets/timemachine.tag +++ b/src/web/app/desktop/tags/home-widgets/timemachine.tag @@ -1,10 +1,12 @@ - - -

{ '%i18n:desktop.tags.mk-timemachine-home-widget.title%'.replace('{1}', year).replace('{2}', month) }

- + + + +

{ '%i18n:desktop.tags.mk-timemachine-home-widget.title%'.replace('{1}', year).replace('{2}', month) }

+ +
-
{ weekdayText[i] }
@@ -22,6 +24,10 @@ color #777 background #fff + &[data-melt] + background transparent !important + border none !important + > .title z-index 1 margin 0 @@ -130,6 +136,12 @@ From 16c8950e4c9189106cacac750e1af97083904aae Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 12 Nov 2017 07:01:04 +0900 Subject: [PATCH 168/327] v3035 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eb5a57ba..a7a7fa9b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ChangeLog (Release Notes) ========================= 主に notable な changes を書いていきます +3035 (2017/11/12) +----------------- +* ウィジェットの強化 + 3033 (2017/11/12) ----------------- * デザインの調整 diff --git a/package.json b/package.json index c874ba911..c89f89bc1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo ", - "version": "0.0.3033", + "version": "0.0.3035", "license": "MIT", "description": "A miniblog-based SNS", "bugs": "https://github.com/syuilo/misskey/issues", From 800d409949e990047dd0dd712a135fe957db7488 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 12 Nov 2017 16:26:09 +0900 Subject: [PATCH 169/327] Refactor --- src/web/app/desktop/tags/home-widgets/profile.tag | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/web/app/desktop/tags/home-widgets/profile.tag b/src/web/app/desktop/tags/home-widgets/profile.tag index 7d4cf862f..ddb6472a8 100644 --- a/src/web/app/desktop/tags/home-widgets/profile.tag +++ b/src/web/app/desktop/tags/home-widgets/profile.tag @@ -18,8 +18,8 @@ background rgba(0, 0, 0, 0.5) > .avatar - top 21px - left 21px + top ((100px - 58px) / 2) + left ((100px - 58px) / 2) border none border-radius 100% box-shadow 0 0 16px rgba(0, 0, 0, 0.5) From ca26fc736308e0bc17465c97396fd67eedb7af8f Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 12 Nov 2017 18:23:33 +0900 Subject: [PATCH 170/327] :v: --- locales/en.yml | 5 + locales/ja.yml | 5 + src/web/app/desktop/mixins/widget.js | 9 +- .../desktop/tags/home-widgets/broadcast.tag | 7 +- .../desktop/tags/home-widgets/donation.tag | 5 +- src/web/app/desktop/tags/home-widgets/nav.tag | 3 + .../desktop/tags/home-widgets/post-form.tag | 101 ++++++++++++++++++ .../app/desktop/tags/home-widgets/tips.tag | 2 + .../app/desktop/tags/home-widgets/version.tag | 2 + src/web/app/desktop/tags/home.tag | 55 ++++++++-- src/web/app/desktop/tags/index.js | 1 + 11 files changed, 179 insertions(+), 16 deletions(-) create mode 100644 src/web/app/desktop/tags/home-widgets/post-form.tag diff --git a/locales/en.yml b/locales/en.yml index 2845eec6a..574af26a6 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -385,6 +385,11 @@ desktop: next: "Next month" go: "Click to travel" + mk-post-form-home-widget: + title: "Post" + post: "Post" + placeholder: "What's happening?" + mk-repost-form: quote: "Quote..." cancel: "Cancel" diff --git a/locales/ja.yml b/locales/ja.yml index 2d9aceb2d..9e6251d0d 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -385,6 +385,11 @@ desktop: next: "来月" go: "クリックして時間遡行" + mk-post-form-home-widget: + title: "投稿" + post: "投稿" + placeholder: "いまどうしてる?" + mk-repost-form: quote: "引用する..." cancel: "キャンセル" diff --git a/src/web/app/desktop/mixins/widget.js b/src/web/app/desktop/mixins/widget.js index 2bf858ebe..cb04295fc 100644 --- a/src/web/app/desktop/mixins/widget.js +++ b/src/web/app/desktop/mixins/widget.js @@ -9,10 +9,13 @@ riot.mixin('widget', { this.mixin('api'); this.id = this.opts.id; + this.place = this.opts.place; - Object.keys(this.data).forEach(prop => { - this.data[prop] = this.opts.data.hasOwnProperty(prop) ? this.opts.data[prop] : this.data[prop]; - }); + if (this.data) { + Object.keys(this.data).forEach(prop => { + this.data[prop] = this.opts.data.hasOwnProperty(prop) ? this.opts.data[prop] : this.data[prop]; + }); + } }, save: function() { diff --git a/src/web/app/desktop/tags/home-widgets/broadcast.tag b/src/web/app/desktop/tags/home-widgets/broadcast.tag index 1102e22c7..c33c5f367 100644 --- a/src/web/app/desktop/tags/home-widgets/broadcast.tag +++ b/src/web/app/desktop/tags/home-widgets/broadcast.tag @@ -75,9 +75,8 @@ a color #555 - - - - + diff --git a/src/web/app/desktop/tags/home-widgets/donation.tag b/src/web/app/desktop/tags/home-widgets/donation.tag index d533e8283..9d56d12f0 100644 --- a/src/web/app/desktop/tags/home-widgets/donation.tag +++ b/src/web/app/desktop/tags/home-widgets/donation.tag @@ -28,5 +28,8 @@ color #999 - + diff --git a/src/web/app/desktop/tags/home-widgets/nav.tag b/src/web/app/desktop/tags/home-widgets/nav.tag index 54bfb87a1..1061b36f4 100644 --- a/src/web/app/desktop/tags/home-widgets/nav.tag +++ b/src/web/app/desktop/tags/home-widgets/nav.tag @@ -14,4 +14,7 @@ color #ccc + diff --git a/src/web/app/desktop/tags/home-widgets/post-form.tag b/src/web/app/desktop/tags/home-widgets/post-form.tag new file mode 100644 index 000000000..2aa3cda50 --- /dev/null +++ b/src/web/app/desktop/tags/home-widgets/post-form.tag @@ -0,0 +1,101 @@ + + + + +

%i18n:desktop.tags.mk-post-form-home-widget.title%

+
+ + +
+ + +
diff --git a/src/web/app/desktop/tags/home-widgets/tips.tag b/src/web/app/desktop/tags/home-widgets/tips.tag index 5a535099a..fd5ec801f 100644 --- a/src/web/app/desktop/tags/home-widgets/tips.tag +++ b/src/web/app/desktop/tags/home-widgets/tips.tag @@ -31,6 +31,8 @@ diff --git a/src/web/app/desktop/tags/home.tag b/src/web/app/desktop/tags/home.tag index 60625bde6..8da58eb51 100644 --- a/src/web/app/desktop/tags/home.tag +++ b/src/web/app/desktop/tags/home.tag @@ -16,6 +16,7 @@ + @@ -32,12 +33,13 @@
-
-
+
+
+
-
+
+ diff --git a/src/web/app/desktop/tags/window.tag b/src/web/app/desktop/tags/window.tag index aefb6499b..cc8dc4c1a 100644 --- a/src/web/app/desktop/tags/window.tag +++ b/src/web/app/desktop/tags/window.tag @@ -4,7 +4,10 @@

- +
+ + +
@@ -117,8 +120,12 @@ box-shadow 0 2px 6px 0 rgba(0, 0, 0, 0.2) > header + $header-height = 40px + z-index 128 + height $header-height overflow hidden + white-space nowrap cursor move background #fff border-radius 6px 6px 0 0 @@ -130,39 +137,45 @@ > h1 pointer-events none display block - margin 0 - height 40px + margin 0 auto + width s('calc(100% - (%s * 2))', $header-height) + overflow hidden + text-overflow ellipsis text-align center font-size 1em - line-height 40px + line-height $header-height font-weight normal color #666 - > .close - cursor pointer - display block + > div:last-child position absolute top 0 right 0 + display block z-index 1 - margin 0 - padding 0 - font-size 1.2em - color rgba(#000, 0.4) - border none - outline none - background transparent - &:hover - color rgba(#000, 0.6) - - &:active - color darken(#000, 30%) - - > i + > * + display inline-block + margin 0 padding 0 - width 40px - line-height 40px + cursor pointer + font-size 1.2em + color rgba(#000, 0.4) + border none + outline none + background transparent + + &:hover + color rgba(#000, 0.6) + + &:active + color darken(#000, 30%) + + > i + padding 0 + width $header-height + line-height $header-height + text-align center > .content height 100% @@ -181,6 +194,8 @@ this.isModal = this.opts.isModal != null ? this.opts.isModal : false; this.canClose = this.opts.canClose != null ? this.opts.canClose : true; + this.popoutOption = this.opts.popoutOption; + console.log(this.popoutOption); this.isFlexible = this.opts.height == null; this.canResize = !this.isFlexible; @@ -247,6 +262,19 @@ }, 300); }; + this.popout = () => { + const position = this.refs.main.getBoundingClientRect(); + + const x = window.screenX + position.left; + const y = window.screenY + position.top; + + window.open(this.popoutOption.url, + this.popoutOption.url, + `height=${this.popoutOption.height},width=${this.popoutOption.width},left=${x},top=${y}`); + + this.close(); + }; + this.close = () => { this.trigger('closing'); From 588e6ae1623abeb7d315068a6fac64bde17eb56c Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 13 Nov 2017 07:33:47 +0900 Subject: [PATCH 186/327] v3055 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5697e3773..ae39bc076 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ChangeLog (Release Notes) ========================= 主に notable な changes を書いていきます +3055 (2017/11/13) +----------------- +* メッセージのウィンドウのポップアウト (#911) + 3050 (2017/11/13) ----------------- * 通信の最適化 diff --git a/package.json b/package.json index 9f8a744b4..5b21dc25b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo ", - "version": "0.0.3050", + "version": "0.0.3055", "license": "MIT", "description": "A miniblog-based SNS", "bugs": "https://github.com/syuilo/misskey/issues", From 178a861e6652fd9b8b77d6a65f378143df3cc1b5 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 13 Nov 2017 07:46:33 +0900 Subject: [PATCH 187/327] Fix glitch --- src/web/app/desktop/tags/pages/messaging-room.tag | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/web/app/desktop/tags/pages/messaging-room.tag b/src/web/app/desktop/tags/pages/messaging-room.tag index 9aba6c95d..9747b4b83 100644 --- a/src/web/app/desktop/tags/pages/messaging-room.tag +++ b/src/web/app/desktop/tags/pages/messaging-room.tag @@ -4,9 +4,6 @@ @@ -21,6 +18,8 @@ this.on('mount', () => { Progress.start(); + document.documentElement.style.background = '#fff'; + this.api('users/show', { username: this.opts.user }).then(user => { From e53f2fd66ef5c96bbfd2edaeb6ff00f4c69fe7ce Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 13 Nov 2017 07:47:14 +0900 Subject: [PATCH 188/327] v3057 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae39bc076..c8f43f0c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ChangeLog (Release Notes) ========================= 主に notable な changes を書いていきます +3057 (2017/11/13) +----------------- +* グリッチ修正 + 3055 (2017/11/13) ----------------- * メッセージのウィンドウのポップアウト (#911) diff --git a/package.json b/package.json index 5b21dc25b..2861fd9f0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo ", - "version": "0.0.3055", + "version": "0.0.3057", "license": "MIT", "description": "A miniblog-based SNS", "bugs": "https://github.com/syuilo/misskey/issues", From 94835583722bf00ef182cadb4b8fd5d3949f2764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=93=E3=81=B4=E3=81=AA=E3=81=9F=E3=81=BF=E3=81=BD?= Date: Mon, 13 Nov 2017 08:53:38 +0900 Subject: [PATCH 189/327] :v: --- src/web/app/desktop/tags/messaging/room-window.tag | 8 ++------ src/web/app/desktop/tags/window.tag | 13 +++++++------ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/web/app/desktop/tags/messaging/room-window.tag b/src/web/app/desktop/tags/messaging/room-window.tag index 4d4f5c626..dca0172be 100644 --- a/src/web/app/desktop/tags/messaging/room-window.tag +++ b/src/web/app/desktop/tags/messaging/room-window.tag @@ -1,5 +1,5 @@ - + メッセージ: { parent.user.name } @@ -23,11 +23,7 @@ this.user = this.opts.user; - this.popout = { - url: `${CONFIG.url}/i/messaging/${this.user.username}`, - width: 420, - height: 540 - }; + this.popout = `${CONFIG.url}/i/messaging/${this.user.username}`; this.on('mount', () => { this.refs.window.on('closed', () => { diff --git a/src/web/app/desktop/tags/window.tag b/src/web/app/desktop/tags/window.tag index cc8dc4c1a..f0e1a3fdd 100644 --- a/src/web/app/desktop/tags/window.tag +++ b/src/web/app/desktop/tags/window.tag @@ -5,7 +5,7 @@

- +
@@ -194,8 +194,7 @@ this.isModal = this.opts.isModal != null ? this.opts.isModal : false; this.canClose = this.opts.canClose != null ? this.opts.canClose : true; - this.popoutOption = this.opts.popoutOption; - console.log(this.popoutOption); + this.popoutUrl = this.opts.popout; this.isFlexible = this.opts.height == null; this.canResize = !this.isFlexible; @@ -265,12 +264,14 @@ this.popout = () => { const position = this.refs.main.getBoundingClientRect(); + const width = parseInt(getComputedStyle(this.refs.main, '').width, 10); + const left = parseInt(getComputedStyle(this.refs.main, '').left, 10); const x = window.screenX + position.left; const y = window.screenY + position.top; - window.open(this.popoutOption.url, - this.popoutOption.url, - `height=${this.popoutOption.height},width=${this.popoutOption.width},left=${x},top=${y}`); + window.open(this.popoutUrl, + this.popoutUrl, + `height=${height},width=${width},left=${x},top=${y}`); this.close(); }; From f8dddc81e25ceb47d8969505e6ef19267907afa7 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 13 Nov 2017 15:04:20 +0900 Subject: [PATCH 190/327] :v: --- src/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.ts b/src/server.ts index 240800c1e..3e9bd44ee 100644 --- a/src/server.ts +++ b/src/server.ts @@ -35,7 +35,7 @@ app.use(morgan(process.env.NODE_ENV == 'production' ? 'combined' : 'dev', { stream: config.accesslog ? fs.createWriteStream(config.accesslog) : null })); -// Drop request that without 'Host' header +// Drop request when without 'Host' header app.use((req, res, next) => { if (!req.headers['host']) { res.sendStatus(400); From bc9a8283c66d7588f931d4b802f7ab1fa7aa3226 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 13 Nov 2017 18:05:35 +0900 Subject: [PATCH 191/327] =?UTF-8?q?=E3=81=AA=E3=82=93=E3=81=8B=E3=82=82?= =?UTF-8?q?=E3=81=86=E3=82=81=E3=81=A3=E3=81=A1=E3=82=83=E5=A4=89=E3=81=88?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 7 +++- src/web/app/auth/{script.js => script.ts} | 0 src/web/app/auth/tags/{index.js => index.ts} | 0 src/web/app/ch/{router.js => router.ts} | 4 +-- src/web/app/ch/{script.js => script.ts} | 0 src/web/app/ch/tags/channel.tag | 4 +-- src/web/app/ch/tags/{index.js => index.ts} | 0 src/web/app/common/mixins/{api.js => api.ts} | 2 +- src/web/app/common/mixins/{i.js => i.ts} | 2 +- src/web/app/common/mixins/index.js | 13 -------- src/web/app/common/mixins/index.ts | 14 ++++++++ src/web/app/common/scripts/{api.js => api.ts} | 4 +-- .../{bytes-to-size.js => bytes-to-size.ts} | 4 +-- .../{channel-stream.js => channel-stream.ts} | 0 ...heck-for-update.js => check-for-update.ts} | 4 ++- .../common/scripts/{config.js => config.ts} | 0 .../scripts/{contains.js => contains.ts} | 0 ...y-to-clipboard.js => copy-to-clipboard.ts} | 0 .../{date-stringify.js => date-stringify.ts} | 0 src/web/app/common/scripts/{gcd.js => gcd.ts} | 0 .../common/scripts/{get-kao.js => get-kao.ts} | 2 +- .../{home-stream.js => home-stream.ts} | 4 +-- .../scripts/{is-promise.js => is-promise.ts} | 0 .../common/scripts/{loading.js => loading.ts} | 0 ...essaging-stream.js => messaging-stream.ts} | 2 +- .../common/scripts/server-stream-manager.ts | 31 ++--------------- .../{server-stream.js => server-stream.ts} | 0 .../common/scripts/{signout.js => signout.ts} | 0 src/web/app/common/scripts/stream-manager.ts | 33 +++++++++++++++++++ .../common/scripts/{stream.js => stream.ts} | 31 +++++++++-------- .../{text-compiler.js => text-compiler.ts} | 4 +-- .../app/common/tags/{index.js => index.ts} | 0 src/web/app/common/tags/messaging/message.tag | 2 +- .../app/desktop/mixins/{index.js => index.ts} | 0 .../{user-preview.js => user-preview.ts} | 2 +- .../desktop/mixins/{widget.js => widget.ts} | 2 +- src/web/app/desktop/{router.js => router.ts} | 6 ++-- src/web/app/desktop/{script.js => script.ts} | 8 ++--- .../{autocomplete.js => autocomplete.ts} | 26 ++++++++------- .../desktop/scripts/{dialog.js => dialog.ts} | 4 +-- .../{fuck-ad-block.js => fuck-ad-block.ts} | 2 ++ .../{input-dialog.js => input-dialog.ts} | 2 +- ...eption.js => not-implemented-exception.ts} | 0 .../desktop/scripts/{notify.js => notify.ts} | 2 +- ...{password-dialog.js => password-dialog.ts} | 2 +- .../{update-avatar.js => update-avatar.ts} | 8 ++--- .../{update-banner.js => update-banner.ts} | 8 ++--- .../desktop/tags/autocomplete-suggestion.tag | 2 +- src/web/app/desktop/tags/drive/browser.tag | 4 +-- src/web/app/desktop/tags/drive/folder.tag | 2 +- src/web/app/desktop/tags/drive/nav-folder.tag | 2 +- .../app/desktop/tags/{index.js => index.ts} | 0 src/web/app/desktop/tags/post-detail-sub.tag | 2 +- src/web/app/desktop/tags/post-detail.tag | 2 +- src/web/app/desktop/tags/post-form.tag | 6 ++-- src/web/app/desktop/tags/sub-post-content.tag | 2 +- src/web/app/desktop/tags/timeline.tag | 2 +- src/web/app/dev/{router.js => router.ts} | 4 +-- src/web/app/dev/{script.js => script.ts} | 0 src/web/app/dev/tags/{index.js => index.ts} | 0 src/web/app/{init.js => init.ts} | 28 +++------------- src/web/app/mobile/{router.js => router.ts} | 6 ++-- src/web/app/mobile/{script.js => script.ts} | 0 .../{open-post-form.js => open-post-form.ts} | 0 .../scripts/{ui-event.js => ui-event.ts} | 0 src/web/app/mobile/tags/drive.tag | 2 +- .../app/mobile/tags/{index.js => index.ts} | 0 src/web/app/mobile/tags/post-detail.tag | 2 +- src/web/app/mobile/tags/post-form.tag | 4 +-- src/web/app/mobile/tags/sub-post-content.tag | 2 +- src/web/app/mobile/tags/timeline.tag | 2 +- src/web/app/stats/{script.js => script.ts} | 0 src/web/app/stats/tags/{index.js => index.ts} | 0 src/web/app/status/{script.js => script.ts} | 0 .../app/status/tags/{index.js => index.ts} | 0 tslint.json | 1 + webpack/webpack.config.ts | 21 +++++++----- 77 files changed, 170 insertions(+), 165 deletions(-) rename src/web/app/auth/{script.js => script.ts} (100%) rename src/web/app/auth/tags/{index.js => index.ts} (100%) rename src/web/app/ch/{router.js => router.ts} (92%) rename src/web/app/ch/{script.js => script.ts} (100%) rename src/web/app/ch/tags/{index.js => index.ts} (100%) rename src/web/app/common/mixins/{api.js => api.ts} (82%) rename src/web/app/common/mixins/{i.js => i.ts} (91%) delete mode 100644 src/web/app/common/mixins/index.js create mode 100644 src/web/app/common/mixins/index.ts rename src/web/app/common/scripts/{api.js => api.ts} (88%) rename src/web/app/common/scripts/{bytes-to-size.js => bytes-to-size.ts} (58%) rename src/web/app/common/scripts/{channel-stream.js => channel-stream.ts} (100%) rename src/web/app/common/scripts/{check-for-update.js => check-for-update.ts} (91%) rename src/web/app/common/scripts/{config.js => config.ts} (100%) rename src/web/app/common/scripts/{contains.js => contains.ts} (100%) rename src/web/app/common/scripts/{copy-to-clipboard.js => copy-to-clipboard.ts} (100%) rename src/web/app/common/scripts/{date-stringify.js => date-stringify.ts} (100%) rename src/web/app/common/scripts/{gcd.js => gcd.ts} (100%) rename src/web/app/common/scripts/{get-kao.js => get-kao.ts} (65%) rename src/web/app/common/scripts/{home-stream.js => home-stream.ts} (83%) rename src/web/app/common/scripts/{is-promise.js => is-promise.ts} (100%) rename src/web/app/common/scripts/{loading.js => loading.ts} (100%) rename src/web/app/common/scripts/{messaging-stream.js => messaging-stream.ts} (87%) rename src/web/app/common/scripts/{server-stream.js => server-stream.ts} (100%) rename src/web/app/common/scripts/{signout.js => signout.ts} (100%) create mode 100644 src/web/app/common/scripts/stream-manager.ts rename src/web/app/common/scripts/{stream.js => stream.ts} (83%) rename src/web/app/common/scripts/{text-compiler.js => text-compiler.ts} (94%) rename src/web/app/common/tags/{index.js => index.ts} (100%) rename src/web/app/desktop/mixins/{index.js => index.ts} (100%) rename src/web/app/desktop/mixins/{user-preview.js => user-preview.ts} (95%) rename src/web/app/desktop/mixins/{widget.js => widget.ts} (95%) rename src/web/app/desktop/{router.js => router.ts} (96%) rename src/web/app/desktop/{script.js => script.ts} (87%) rename src/web/app/desktop/scripts/{autocomplete.js => autocomplete.ts} (86%) rename src/web/app/desktop/scripts/{dialog.js => dialog.ts} (77%) rename src/web/app/desktop/scripts/{fuck-ad-block.js => fuck-ad-block.ts} (94%) rename src/web/app/desktop/scripts/{input-dialog.js => input-dialog.ts} (88%) rename src/web/app/desktop/scripts/{not-implemented-exception.js => not-implemented-exception.ts} (100%) rename src/web/app/desktop/scripts/{notify.js => notify.ts} (83%) rename src/web/app/desktop/scripts/{password-dialog.js => password-dialog.ts} (86%) rename src/web/app/desktop/scripts/{update-avatar.js => update-avatar.ts} (82%) rename src/web/app/desktop/scripts/{update-banner.js => update-banner.ts} (82%) rename src/web/app/desktop/tags/{index.js => index.ts} (100%) rename src/web/app/dev/{router.js => router.ts} (94%) rename src/web/app/dev/{script.js => script.ts} (100%) rename src/web/app/dev/tags/{index.js => index.ts} (100%) rename src/web/app/{init.js => init.ts} (81%) rename src/web/app/mobile/{router.js => router.ts} (97%) rename src/web/app/mobile/{script.js => script.ts} (100%) rename src/web/app/mobile/scripts/{open-post-form.js => open-post-form.ts} (100%) rename src/web/app/mobile/scripts/{ui-event.js => ui-event.ts} (100%) rename src/web/app/mobile/tags/{index.js => index.ts} (100%) rename src/web/app/stats/{script.js => script.ts} (100%) rename src/web/app/stats/tags/{index.js => index.ts} (100%) rename src/web/app/status/{script.js => script.ts} (100%) rename src/web/app/status/tags/{index.js => index.ts} (100%) diff --git a/package.json b/package.json index 2861fd9f0..879f4af92 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,9 @@ "@types/ms": "0.7.30", "@types/multer": "1.3.5", "@types/node": "8.0.49", + "@types/page": "1.5.32", + "@types/proxy-addr": "2.0.0", + "@types/seedrandom": "2.4.27", "@types/ratelimiter": "2.1.28", "@types/redis": "2.8.1", "@types/request": "2.0.7", @@ -92,8 +95,8 @@ "webpack": "3.8.1" }, "dependencies": { - "@prezzemolo/zip": "0.0.3", "@prezzemolo/rap": "0.1.2", + "@prezzemolo/zip": "0.0.3", "accesses": "2.5.0", "animejs": "2.2.0", "autwh": "0.0.1", @@ -131,6 +134,7 @@ "page": "1.7.1", "pictograph": "2.0.4", "prominence": "0.2.0", + "proxy-addr": "^2.0.2", "pug": "2.0.0-rc.4", "ratelimiter": "3.0.3", "recaptcha-promise": "0.1.3", @@ -141,6 +145,7 @@ "riot": "3.7.4", "rndstr": "1.0.0", "s-age": "1.1.0", + "seedrandom": "^2.4.3", "serve-favicon": "2.4.5", "sortablejs": "1.7.0", "summaly": "2.0.3", diff --git a/src/web/app/auth/script.js b/src/web/app/auth/script.ts similarity index 100% rename from src/web/app/auth/script.js rename to src/web/app/auth/script.ts diff --git a/src/web/app/auth/tags/index.js b/src/web/app/auth/tags/index.ts similarity index 100% rename from src/web/app/auth/tags/index.js rename to src/web/app/auth/tags/index.ts diff --git a/src/web/app/ch/router.js b/src/web/app/ch/router.ts similarity index 92% rename from src/web/app/ch/router.js rename to src/web/app/ch/router.ts index 424158f40..fe014d4e3 100644 --- a/src/web/app/ch/router.js +++ b/src/web/app/ch/router.ts @@ -1,5 +1,5 @@ import * as riot from 'riot'; -const route = require('page'); +import * as route from 'page'; let page = null; export default me => { @@ -22,7 +22,7 @@ export default me => { } // EXEC - route(); + (route as any)(); }; function mount(content) { diff --git a/src/web/app/ch/script.js b/src/web/app/ch/script.ts similarity index 100% rename from src/web/app/ch/script.js rename to src/web/app/ch/script.ts diff --git a/src/web/app/ch/tags/channel.tag b/src/web/app/ch/tags/channel.tag index 4ae62e7b3..48c5c705d 100644 --- a/src/web/app/ch/tags/channel.tag +++ b/src/web/app/ch/tags/channel.tag @@ -343,7 +343,7 @@ }; this.changeFile = () => { - this.refs.file.files.forEach(this.upload); + Array.from(this.refs.file.files).forEach(this.upload); }; this.selectFile = () => { @@ -367,7 +367,7 @@ }; this.onpaste = e => { - e.clipboardData.items.forEach(item => { + Array.from(e.clipboardData.items).forEach(item => { if (item.kind == 'file') { this.upload(item.getAsFile()); } diff --git a/src/web/app/ch/tags/index.js b/src/web/app/ch/tags/index.ts similarity index 100% rename from src/web/app/ch/tags/index.js rename to src/web/app/ch/tags/index.ts diff --git a/src/web/app/common/mixins/api.js b/src/web/app/common/mixins/api.ts similarity index 82% rename from src/web/app/common/mixins/api.js rename to src/web/app/common/mixins/api.ts index 42d96db55..9726caf51 100644 --- a/src/web/app/common/mixins/api.js +++ b/src/web/app/common/mixins/api.ts @@ -2,7 +2,7 @@ import * as riot from 'riot'; import api from '../scripts/api'; export default me => { - riot.mixin('api', { + (riot as any).mixin('api', { api: api.bind(null, me ? me.token : null) }); }; diff --git a/src/web/app/common/mixins/i.js b/src/web/app/common/mixins/i.ts similarity index 91% rename from src/web/app/common/mixins/i.js rename to src/web/app/common/mixins/i.ts index 522514776..0879d02d3 100644 --- a/src/web/app/common/mixins/i.js +++ b/src/web/app/common/mixins/i.ts @@ -1,7 +1,7 @@ import * as riot from 'riot'; export default me => { - riot.mixin('i', { + (riot as any).mixin('i', { init: function() { this.I = me; this.SIGNIN = me != null; diff --git a/src/web/app/common/mixins/index.js b/src/web/app/common/mixins/index.js deleted file mode 100644 index 19e0690d7..000000000 --- a/src/web/app/common/mixins/index.js +++ /dev/null @@ -1,13 +0,0 @@ -import * as riot from 'riot'; - -import activateMe from './i'; -import activateApi from './api'; - -export default (me, stream, serverStreamManager) => { - activateMe(me); - activateApi(me); - - riot.mixin('stream', { stream }); - - riot.mixin('server-stream', { serverStream: serverStreamManager }); -}; diff --git a/src/web/app/common/mixins/index.ts b/src/web/app/common/mixins/index.ts new file mode 100644 index 000000000..45427fb9d --- /dev/null +++ b/src/web/app/common/mixins/index.ts @@ -0,0 +1,14 @@ +import * as riot from 'riot'; + +import activateMe from './i'; +import activateApi from './api'; +import ServerStreamManager from '../scripts/server-stream-manager'; + +export default (me, stream) => { + activateMe(me); + activateApi(me); + + (riot as any).mixin('stream', { stream }); + + (riot as any).mixin('server-stream', { serverStream: new ServerStreamManager() }); +}; diff --git a/src/web/app/common/scripts/api.js b/src/web/app/common/scripts/api.ts similarity index 88% rename from src/web/app/common/scripts/api.js rename to src/web/app/common/scripts/api.ts index 4855f736c..2a9d78e87 100644 --- a/src/web/app/common/scripts/api.js +++ b/src/web/app/common/scripts/api.ts @@ -14,7 +14,7 @@ let pending = 0; * @param {any} [data={}] Data * @return {Promise} Response */ -export default (i, endpoint, data = {}) => { +export default (i, endpoint, data = {}): Promise => { if (++pending === 1) { spinner = document.createElement('div'); spinner.setAttribute('id', 'wait'); @@ -22,7 +22,7 @@ export default (i, endpoint, data = {}) => { } // Append the credential - if (i != null) data.i = typeof i === 'object' ? i.token : i; + if (i != null) (data as any).i = typeof i === 'object' ? i.token : i; return new Promise((resolve, reject) => { // Send request diff --git a/src/web/app/common/scripts/bytes-to-size.js b/src/web/app/common/scripts/bytes-to-size.ts similarity index 58% rename from src/web/app/common/scripts/bytes-to-size.js rename to src/web/app/common/scripts/bytes-to-size.ts index af0268dbd..1d2b1e7ce 100644 --- a/src/web/app/common/scripts/bytes-to-size.js +++ b/src/web/app/common/scripts/bytes-to-size.ts @@ -1,6 +1,6 @@ export default (bytes, digits = 0) => { - var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; if (bytes == 0) return '0Byte'; - var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); + const i = Math.floor(Math.log(bytes) / Math.log(1024)); return (bytes / Math.pow(1024, i)).toFixed(digits).replace(/\.0+$/, '') + sizes[i]; }; diff --git a/src/web/app/common/scripts/channel-stream.js b/src/web/app/common/scripts/channel-stream.ts similarity index 100% rename from src/web/app/common/scripts/channel-stream.js rename to src/web/app/common/scripts/channel-stream.ts diff --git a/src/web/app/common/scripts/check-for-update.js b/src/web/app/common/scripts/check-for-update.ts similarity index 91% rename from src/web/app/common/scripts/check-for-update.js rename to src/web/app/common/scripts/check-for-update.ts index 7cb7839d2..99d8b5d05 100644 --- a/src/web/app/common/scripts/check-for-update.js +++ b/src/web/app/common/scripts/check-for-update.ts @@ -1,5 +1,7 @@ import CONFIG from './config'; +declare var VERSION: string; + export default function() { fetch(CONFIG.apiUrl + '/meta', { method: 'POST' @@ -11,4 +13,4 @@ export default function() { } }); }); -}; +} diff --git a/src/web/app/common/scripts/config.js b/src/web/app/common/scripts/config.ts similarity index 100% rename from src/web/app/common/scripts/config.js rename to src/web/app/common/scripts/config.ts diff --git a/src/web/app/common/scripts/contains.js b/src/web/app/common/scripts/contains.ts similarity index 100% rename from src/web/app/common/scripts/contains.js rename to src/web/app/common/scripts/contains.ts diff --git a/src/web/app/common/scripts/copy-to-clipboard.js b/src/web/app/common/scripts/copy-to-clipboard.ts similarity index 100% rename from src/web/app/common/scripts/copy-to-clipboard.js rename to src/web/app/common/scripts/copy-to-clipboard.ts diff --git a/src/web/app/common/scripts/date-stringify.js b/src/web/app/common/scripts/date-stringify.ts similarity index 100% rename from src/web/app/common/scripts/date-stringify.js rename to src/web/app/common/scripts/date-stringify.ts diff --git a/src/web/app/common/scripts/gcd.js b/src/web/app/common/scripts/gcd.ts similarity index 100% rename from src/web/app/common/scripts/gcd.js rename to src/web/app/common/scripts/gcd.ts diff --git a/src/web/app/common/scripts/get-kao.js b/src/web/app/common/scripts/get-kao.ts similarity index 65% rename from src/web/app/common/scripts/get-kao.js rename to src/web/app/common/scripts/get-kao.ts index 0b77ee285..2168c5be8 100644 --- a/src/web/app/common/scripts/get-kao.js +++ b/src/web/app/common/scripts/get-kao.ts @@ -1,5 +1,5 @@ export default () => [ '(=^・・^=)', 'v(‘ω’)v', - '🐡( '-' 🐡 )フグパンチ!!!!' + '🐡( \'-\' 🐡 )フグパンチ!!!!' ][Math.floor(Math.random() * 3)]; diff --git a/src/web/app/common/scripts/home-stream.js b/src/web/app/common/scripts/home-stream.ts similarity index 83% rename from src/web/app/common/scripts/home-stream.js rename to src/web/app/common/scripts/home-stream.ts index de9ceb3b5..c549f2b93 100644 --- a/src/web/app/common/scripts/home-stream.js +++ b/src/web/app/common/scripts/home-stream.ts @@ -17,9 +17,9 @@ class Connection extends Stream { this.send({ type: 'alive' }); }, 1000 * 60); - this.on('i_updated', me.update); + (this as any).on('i_updated', me.update); - this.on('my_token_regenerated', () => { + (this as any).on('my_token_regenerated', () => { alert('%i18n:common.my-token-regenerated%'); signout(); }); diff --git a/src/web/app/common/scripts/is-promise.js b/src/web/app/common/scripts/is-promise.ts similarity index 100% rename from src/web/app/common/scripts/is-promise.js rename to src/web/app/common/scripts/is-promise.ts diff --git a/src/web/app/common/scripts/loading.js b/src/web/app/common/scripts/loading.ts similarity index 100% rename from src/web/app/common/scripts/loading.js rename to src/web/app/common/scripts/loading.ts diff --git a/src/web/app/common/scripts/messaging-stream.js b/src/web/app/common/scripts/messaging-stream.ts similarity index 87% rename from src/web/app/common/scripts/messaging-stream.js rename to src/web/app/common/scripts/messaging-stream.ts index 261525d5f..63830f7b1 100644 --- a/src/web/app/common/scripts/messaging-stream.js +++ b/src/web/app/common/scripts/messaging-stream.ts @@ -12,7 +12,7 @@ class Connection extends Stream { otherparty }); - this.on('_connected_', () => { + (this as any).on('_connected_', () => { this.send({ i: me.token }); diff --git a/src/web/app/common/scripts/server-stream-manager.ts b/src/web/app/common/scripts/server-stream-manager.ts index 54333c8cf..a170daebb 100644 --- a/src/web/app/common/scripts/server-stream-manager.ts +++ b/src/web/app/common/scripts/server-stream-manager.ts @@ -1,14 +1,7 @@ +import StreamManager from './stream-manager'; import Connection from './server-stream'; -import * as uuid from 'uuid'; - -export default class ServerStreamManager { - private connection = null; - - /** - * コネクションを必要としているユーザー - */ - private users = []; +export default class ServerStreamManager extends StreamManager { public getConnection() { if (this.connection == null) { this.connection = new Connection(); @@ -16,24 +9,4 @@ export default class ServerStreamManager { return this.connection; } - - public use() { - // ユーザーID生成 - const userId = uuid(); - - this.users.push(userId); - - return userId; - } - - public dispose(userId) { - this.users = this.users.filter(id => id != userId); - - // 誰もコネクションの利用者がいなくなったら - if (this.users.length == 0) { - // コネクションを切断する - this.connection.close(); - this.connection = null; - } - } } diff --git a/src/web/app/common/scripts/server-stream.js b/src/web/app/common/scripts/server-stream.ts similarity index 100% rename from src/web/app/common/scripts/server-stream.js rename to src/web/app/common/scripts/server-stream.ts diff --git a/src/web/app/common/scripts/signout.js b/src/web/app/common/scripts/signout.ts similarity index 100% rename from src/web/app/common/scripts/signout.js rename to src/web/app/common/scripts/signout.ts diff --git a/src/web/app/common/scripts/stream-manager.ts b/src/web/app/common/scripts/stream-manager.ts new file mode 100644 index 000000000..4eaf0f9a4 --- /dev/null +++ b/src/web/app/common/scripts/stream-manager.ts @@ -0,0 +1,33 @@ +import * as uuid from 'uuid'; +import Connection from './stream'; + +export default abstract class StreamManager { + protected connection: T = null; + + /** + * コネクションを必要としているユーザー + */ + private users = []; + + public abstract getConnection(): T; + + public use() { + // ユーザーID生成 + const userId = uuid(); + + this.users.push(userId); + + return userId; + } + + public dispose(userId) { + this.users = this.users.filter(id => id != userId); + + // 誰もコネクションの利用者がいなくなったら + if (this.users.length == 0) { + // コネクションを切断する + this.connection.close(); + this.connection = null; + } + } +} diff --git a/src/web/app/common/scripts/stream.js b/src/web/app/common/scripts/stream.ts similarity index 83% rename from src/web/app/common/scripts/stream.js rename to src/web/app/common/scripts/stream.ts index a03b7bf20..959524687 100644 --- a/src/web/app/common/scripts/stream.js +++ b/src/web/app/common/scripts/stream.ts @@ -8,7 +8,11 @@ import CONFIG from './config'; * Misskey stream connection */ class Connection { - constructor(endpoint, params) { + private state: string; + private buffer: any[]; + private socket: ReconnectingWebsocket; + + constructor(endpoint, params?) { // BIND ----------------------------------- this.onOpen = this.onOpen.bind(this); this.onClose = this.onClose.bind(this); @@ -37,11 +41,10 @@ class Connection { /** * Callback of when open connection - * @private */ - onOpen() { + private onOpen() { this.state = 'connected'; - this.trigger('_connected_'); + (this as any).trigger('_connected_'); // バッファーを処理 const _buffer = [].concat(this.buffer); // Shallow copy @@ -53,45 +56,41 @@ class Connection { /** * Callback of when close connection - * @private */ - onClose() { + private onClose() { this.state = 'reconnecting'; - this.trigger('_closed_'); + (this as any).trigger('_closed_'); } /** * Callback of when received a message from connection - * @private */ - onMessage(message) { + private onMessage(message) { try { const msg = JSON.parse(message.data); - if (msg.type) this.trigger(msg.type, msg.body); - } catch(e) { + if (msg.type) (this as any).trigger(msg.type, msg.body); + } catch (e) { // noop } } /** * Send a message to connection - * @public */ - send(message) { + public send(message) { // まだ接続が確立されていなかったらバッファリングして次に接続した時に送信する if (this.state != 'connected') { this.buffer.push(message); return; - }; + } this.socket.send(JSON.stringify(message)); } /** * Close this connection - * @public */ - close() { + public close() { this.socket.removeEventListener('open', this.onOpen); this.socket.removeEventListener('message', this.onMessage); } diff --git a/src/web/app/common/scripts/text-compiler.js b/src/web/app/common/scripts/text-compiler.ts similarity index 94% rename from src/web/app/common/scripts/text-compiler.js rename to src/web/app/common/scripts/text-compiler.ts index 0a9b8022d..8c65d6a06 100644 --- a/src/web/app/common/scripts/text-compiler.js +++ b/src/web/app/common/scripts/text-compiler.ts @@ -1,5 +1,5 @@ import * as riot from 'riot'; -const pictograph = require('pictograph'); +import * as pictograph from 'pictograph'; import CONFIG from './config'; const escape = text => @@ -12,7 +12,7 @@ export default (tokens, shouldBreak) => { shouldBreak = true; } - const me = riot.mixin('i').me; + const me = (riot as any).mixin('i').me; let text = tokens.map(token => { switch (token.type) { diff --git a/src/web/app/common/tags/index.js b/src/web/app/common/tags/index.ts similarity index 100% rename from src/web/app/common/tags/index.js rename to src/web/app/common/tags/index.ts diff --git a/src/web/app/common/tags/messaging/message.tag b/src/web/app/common/tags/messaging/message.tag index d6db9070e..ea1ea2310 100644 --- a/src/web/app/common/tags/messaging/message.tag +++ b/src/web/app/common/tags/messaging/message.tag @@ -219,7 +219,7 @@ this.refs.text.innerHTML = compile(tokens); - this.refs.text.children.forEach(e => { + Array.from(this.refs.text.children).forEach(e => { if (e.tagName == 'MK-URL') riot.mount(e); }); diff --git a/src/web/app/desktop/mixins/index.js b/src/web/app/desktop/mixins/index.ts similarity index 100% rename from src/web/app/desktop/mixins/index.js rename to src/web/app/desktop/mixins/index.ts diff --git a/src/web/app/desktop/mixins/user-preview.js b/src/web/app/desktop/mixins/user-preview.ts similarity index 95% rename from src/web/app/desktop/mixins/user-preview.js rename to src/web/app/desktop/mixins/user-preview.ts index 3f483beb3..614de72be 100644 --- a/src/web/app/desktop/mixins/user-preview.js +++ b/src/web/app/desktop/mixins/user-preview.ts @@ -52,7 +52,7 @@ function attach(el) { clearTimeout(showTimer); hideTimer = setTimeout(close, 500); }); - tag = riot.mount(document.body.appendChild(preview), { + tag = (riot as any).mount(document.body.appendChild(preview), { user: user })[0]; }; diff --git a/src/web/app/desktop/mixins/widget.js b/src/web/app/desktop/mixins/widget.ts similarity index 95% rename from src/web/app/desktop/mixins/widget.js rename to src/web/app/desktop/mixins/widget.ts index cb04295fc..04131cd8f 100644 --- a/src/web/app/desktop/mixins/widget.js +++ b/src/web/app/desktop/mixins/widget.ts @@ -3,7 +3,7 @@ import * as riot from 'riot'; // ミックスインにオプションを渡せないのアレ // SEE: https://github.com/riot/riot/issues/2434 -riot.mixin('widget', { +(riot as any).mixin('widget', { init: function() { this.mixin('i'); this.mixin('api'); diff --git a/src/web/app/desktop/router.js b/src/web/app/desktop/router.ts similarity index 96% rename from src/web/app/desktop/router.js rename to src/web/app/desktop/router.ts index 4675b967d..a74299b28 100644 --- a/src/web/app/desktop/router.js +++ b/src/web/app/desktop/router.ts @@ -3,7 +3,7 @@ */ import * as riot from 'riot'; -const route = require('page'); +import * as route from 'page'; let page = null; export default me => { @@ -83,12 +83,12 @@ export default me => { mount(document.createElement('mk-not-found')); } - riot.mixin('page', { + (riot as any).mixin('page', { page: route }); // EXEC - route(); + (route as any)(); }; function mount(content) { diff --git a/src/web/app/desktop/script.js b/src/web/app/desktop/script.ts similarity index 87% rename from src/web/app/desktop/script.js rename to src/web/app/desktop/script.ts index 46a7fce70..a0453865e 100644 --- a/src/web/app/desktop/script.js +++ b/src/web/app/desktop/script.ts @@ -11,7 +11,7 @@ import * as riot from 'riot'; import init from '../init'; import route from './router'; import fuckAdBlock from './scripts/fuck-ad-block'; -import getPostSummary from '../../../common/get-post-summary.ts'; +import getPostSummary from '../../../common/get-post-summary'; /** * init @@ -27,11 +27,11 @@ init(async (me, stream) => { */ if ('Notification' in window) { // 許可を得ていなかったらリクエスト - if (Notification.permission == 'default') { + if ((Notification as any).permission == 'default') { await Notification.requestPermission(); } - if (Notification.permission == 'granted') { + if ((Notification as any).permission == 'granted') { registerNotifications(stream); } } @@ -82,7 +82,7 @@ function registerNotifications(stream) { }); n.onclick = () => { n.close(); - riot.mount(document.body.appendChild(document.createElement('mk-messaging-room-window')), { + (riot as any).mount(document.body.appendChild(document.createElement('mk-messaging-room-window')), { user: message.user }); }; diff --git a/src/web/app/desktop/scripts/autocomplete.js b/src/web/app/desktop/scripts/autocomplete.ts similarity index 86% rename from src/web/app/desktop/scripts/autocomplete.js rename to src/web/app/desktop/scripts/autocomplete.ts index 8ca516e2a..9df7aae08 100644 --- a/src/web/app/desktop/scripts/autocomplete.js +++ b/src/web/app/desktop/scripts/autocomplete.ts @@ -1,10 +1,12 @@ -const getCaretCoordinates = require('textarea-caret'); +import getCaretCoordinates = require('textarea-caret'); import * as riot from 'riot'; /** * オートコンプリートを管理するクラス。 */ class Autocomplete { + private suggestion: any; + private textarea: any; /** * 対象のテキストエリアを与えてインスタンスを初期化します。 @@ -23,22 +25,22 @@ class Autocomplete { /** * このインスタンスにあるテキストエリアの入力のキャプチャを開始します。 */ - attach() { + public attach() { this.textarea.addEventListener('input', this.onInput); } /** * このインスタンスにあるテキストエリアの入力のキャプチャを解除します。 */ - detach() { + public detach() { this.textarea.removeEventListener('input', this.onInput); this.close(); } /** - * [Private] テキスト入力時 + * テキスト入力時 */ - onInput() { + private onInput() { this.close(); const caret = this.textarea.selectionStart; @@ -56,9 +58,9 @@ class Autocomplete { } /** - * [Private] サジェストを提示します。 + * サジェストを提示します。 */ - open(type, q) { + private open(type, q) { // 既に開いているサジェストは閉じる this.close(); @@ -81,7 +83,7 @@ class Autocomplete { const el = document.body.appendChild(tag); // マウント - this.suggestion = riot.mount(el, { + this.suggestion = (riot as any).mount(el, { textarea: this.textarea, complete: this.complete, close: this.close, @@ -91,9 +93,9 @@ class Autocomplete { } /** - * [Private] サジェストを閉じます。 + * サジェストを閉じます。 */ - close() { + private close() { if (this.suggestion == null) return; this.suggestion.unmount(); @@ -103,9 +105,9 @@ class Autocomplete { } /** - * [Private] オートコンプリートする + * オートコンプリートする */ - complete(user) { + private complete(user) { this.close(); const value = user.username; diff --git a/src/web/app/desktop/scripts/dialog.js b/src/web/app/desktop/scripts/dialog.ts similarity index 77% rename from src/web/app/desktop/scripts/dialog.js rename to src/web/app/desktop/scripts/dialog.ts index c502d3fcb..816ba4b5f 100644 --- a/src/web/app/desktop/scripts/dialog.js +++ b/src/web/app/desktop/scripts/dialog.ts @@ -1,9 +1,9 @@ import * as riot from 'riot'; -export default (title, text, buttons, canThrough, onThrough) => { +export default (title, text, buttons, canThrough?, onThrough?) => { const dialog = document.body.appendChild(document.createElement('mk-dialog')); const controller = riot.observable(); - riot.mount(dialog, { + (riot as any).mount(dialog, { controller: controller, title: title, text: text, diff --git a/src/web/app/desktop/scripts/fuck-ad-block.js b/src/web/app/desktop/scripts/fuck-ad-block.ts similarity index 94% rename from src/web/app/desktop/scripts/fuck-ad-block.js rename to src/web/app/desktop/scripts/fuck-ad-block.ts index ccfc43ce6..3307ba2f3 100644 --- a/src/web/app/desktop/scripts/fuck-ad-block.js +++ b/src/web/app/desktop/scripts/fuck-ad-block.ts @@ -1,6 +1,8 @@ require('fuckadblock'); import dialog from './dialog'; +declare var fuckAdBlock: any; + export default () => { if (fuckAdBlock === undefined) { adBlockDetected(); diff --git a/src/web/app/desktop/scripts/input-dialog.js b/src/web/app/desktop/scripts/input-dialog.ts similarity index 88% rename from src/web/app/desktop/scripts/input-dialog.js rename to src/web/app/desktop/scripts/input-dialog.ts index 954fabfb6..b06d011c6 100644 --- a/src/web/app/desktop/scripts/input-dialog.js +++ b/src/web/app/desktop/scripts/input-dialog.ts @@ -2,7 +2,7 @@ import * as riot from 'riot'; export default (title, placeholder, defaultValue, onOk, onCancel) => { const dialog = document.body.appendChild(document.createElement('mk-input-dialog')); - return riot.mount(dialog, { + return (riot as any).mount(dialog, { title: title, placeholder: placeholder, 'default': defaultValue, diff --git a/src/web/app/desktop/scripts/not-implemented-exception.js b/src/web/app/desktop/scripts/not-implemented-exception.ts similarity index 100% rename from src/web/app/desktop/scripts/not-implemented-exception.js rename to src/web/app/desktop/scripts/not-implemented-exception.ts diff --git a/src/web/app/desktop/scripts/notify.js b/src/web/app/desktop/scripts/notify.ts similarity index 83% rename from src/web/app/desktop/scripts/notify.js rename to src/web/app/desktop/scripts/notify.ts index e58a8e4d3..2e6cbdeed 100644 --- a/src/web/app/desktop/scripts/notify.js +++ b/src/web/app/desktop/scripts/notify.ts @@ -2,7 +2,7 @@ import * as riot from 'riot'; export default message => { const notification = document.body.appendChild(document.createElement('mk-ui-notification')); - riot.mount(notification, { + (riot as any).mount(notification, { message: message }); }; diff --git a/src/web/app/desktop/scripts/password-dialog.js b/src/web/app/desktop/scripts/password-dialog.ts similarity index 86% rename from src/web/app/desktop/scripts/password-dialog.js rename to src/web/app/desktop/scripts/password-dialog.ts index 2bdc93e42..39d7f3db7 100644 --- a/src/web/app/desktop/scripts/password-dialog.js +++ b/src/web/app/desktop/scripts/password-dialog.ts @@ -2,7 +2,7 @@ import * as riot from 'riot'; export default (title, onOk, onCancel) => { const dialog = document.body.appendChild(document.createElement('mk-input-dialog')); - return riot.mount(dialog, { + return (riot as any).mount(dialog, { title: title, type: 'password', onOk: onOk, diff --git a/src/web/app/desktop/scripts/update-avatar.js b/src/web/app/desktop/scripts/update-avatar.ts similarity index 82% rename from src/web/app/desktop/scripts/update-avatar.js rename to src/web/app/desktop/scripts/update-avatar.ts index 165c90567..5fd7f2d3d 100644 --- a/src/web/app/desktop/scripts/update-avatar.js +++ b/src/web/app/desktop/scripts/update-avatar.ts @@ -5,7 +5,7 @@ import api from '../../common/scripts/api'; export default (I, cb, file = null) => { const fileSelected = file => { - const cropper = riot.mount(document.body.appendChild(document.createElement('mk-crop-window')), { + const cropper = (riot as any).mount(document.body.appendChild(document.createElement('mk-crop-window')), { file: file, title: 'アバターとして表示する部分を選択', aspectRatio: 1 / 1 @@ -37,7 +37,7 @@ export default (I, cb, file = null) => { }; const upload = (data, folder) => { - const progress = riot.mount(document.body.appendChild(document.createElement('mk-progress-dialog')), { + const progress = (riot as any).mount(document.body.appendChild(document.createElement('mk-progress-dialog')), { title: '新しいアバターをアップロードしています' })[0]; @@ -46,7 +46,7 @@ export default (I, cb, file = null) => { const xhr = new XMLHttpRequest(); xhr.open('POST', CONFIG.apiUrl + '/drive/files/create', true); xhr.onload = e => { - const file = JSON.parse(e.target.response); + const file = JSON.parse((e.target as any).response); progress.close(); set(file); }; @@ -75,7 +75,7 @@ export default (I, cb, file = null) => { if (file) { fileSelected(file); } else { - const browser = riot.mount(document.body.appendChild(document.createElement('mk-select-file-from-drive-window')), { + const browser = (riot as any).mount(document.body.appendChild(document.createElement('mk-select-file-from-drive-window')), { multiple: false, title: 'アバターにする画像を選択' })[0]; diff --git a/src/web/app/desktop/scripts/update-banner.js b/src/web/app/desktop/scripts/update-banner.ts similarity index 82% rename from src/web/app/desktop/scripts/update-banner.js rename to src/web/app/desktop/scripts/update-banner.ts index d83b2bf1b..23a671c44 100644 --- a/src/web/app/desktop/scripts/update-banner.js +++ b/src/web/app/desktop/scripts/update-banner.ts @@ -5,7 +5,7 @@ import api from '../../common/scripts/api'; export default (I, cb, file = null) => { const fileSelected = file => { - const cropper = riot.mount(document.body.appendChild(document.createElement('mk-crop-window')), { + const cropper = (riot as any).mount(document.body.appendChild(document.createElement('mk-crop-window')), { file: file, title: 'バナーとして表示する部分を選択', aspectRatio: 16 / 9 @@ -37,7 +37,7 @@ export default (I, cb, file = null) => { }; const upload = (data, folder) => { - const progress = riot.mount(document.body.appendChild(document.createElement('mk-progress-dialog')), { + const progress = (riot as any).mount(document.body.appendChild(document.createElement('mk-progress-dialog')), { title: '新しいバナーをアップロードしています' })[0]; @@ -46,7 +46,7 @@ export default (I, cb, file = null) => { const xhr = new XMLHttpRequest(); xhr.open('POST', CONFIG.apiUrl + '/drive/files/create', true); xhr.onload = e => { - const file = JSON.parse(e.target.response); + const file = JSON.parse((e.target as any).response); progress.close(); set(file); }; @@ -75,7 +75,7 @@ export default (I, cb, file = null) => { if (file) { fileSelected(file); } else { - const browser = riot.mount(document.body.appendChild(document.createElement('mk-select-file-from-drive-window')), { + const browser = (riot as any).mount(document.body.appendChild(document.createElement('mk-select-file-from-drive-window')), { multiple: false, title: 'バナーにする画像を選択' })[0]; diff --git a/src/web/app/desktop/tags/autocomplete-suggestion.tag b/src/web/app/desktop/tags/autocomplete-suggestion.tag index b93636040..731160669 100644 --- a/src/web/app/desktop/tags/autocomplete-suggestion.tag +++ b/src/web/app/desktop/tags/autocomplete-suggestion.tag @@ -177,7 +177,7 @@ }; this.applySelect = () => { - this.refs.users.children.forEach(el => { + Array.from(this.refs.users.children).forEach(el => { el.removeAttribute('data-selected'); }); diff --git a/src/web/app/desktop/tags/drive/browser.tag b/src/web/app/desktop/tags/drive/browser.tag index be16a782d..18e27f24b 100644 --- a/src/web/app/desktop/tags/drive/browser.tag +++ b/src/web/app/desktop/tags/drive/browser.tag @@ -408,7 +408,7 @@ // ドロップされてきたものがファイルだったら if (e.dataTransfer.files.length > 0) { - e.dataTransfer.files.forEach(file => { + Array.from(e.dataTransfer.files).forEach(file => { this.upload(file, this.folder); }); return false; @@ -510,7 +510,7 @@ }; this.changeFileInput = () => { - this.refs.fileInput.files.forEach(file => { + Array.from(this.refs.fileInput.files).forEach(file => { this.upload(file, this.folder); }); }; diff --git a/src/web/app/desktop/tags/drive/folder.tag b/src/web/app/desktop/tags/drive/folder.tag index e03c4e353..1c361c8e4 100644 --- a/src/web/app/desktop/tags/drive/folder.tag +++ b/src/web/app/desktop/tags/drive/folder.tag @@ -109,7 +109,7 @@ // ファイルだったら if (e.dataTransfer.files.length > 0) { - e.dataTransfer.files.forEach(file => { + Array.from(e.dataTransfer.files).forEach(file => { this.browser.upload(file, this.folder); }); return false; diff --git a/src/web/app/desktop/tags/drive/nav-folder.tag b/src/web/app/desktop/tags/drive/nav-folder.tag index c89d9edc1..0a9421353 100644 --- a/src/web/app/desktop/tags/drive/nav-folder.tag +++ b/src/web/app/desktop/tags/drive/nav-folder.tag @@ -55,7 +55,7 @@ // ファイルだったら if (e.dataTransfer.files.length > 0) { - e.dataTransfer.files.forEach(file => { + Array.from(e.dataTransfer.files).forEach(file => { this.browser.upload(file, this.folder); }); return false; diff --git a/src/web/app/desktop/tags/index.js b/src/web/app/desktop/tags/index.ts similarity index 100% rename from src/web/app/desktop/tags/index.js rename to src/web/app/desktop/tags/index.ts diff --git a/src/web/app/desktop/tags/post-detail-sub.tag b/src/web/app/desktop/tags/post-detail-sub.tag index 8a0ada5f2..e22386df9 100644 --- a/src/web/app/desktop/tags/post-detail-sub.tag +++ b/src/web/app/desktop/tags/post-detail-sub.tag @@ -129,7 +129,7 @@ this.refs.text.innerHTML = compile(tokens); - this.refs.text.children.forEach(e => { + Array.from(this.refs.text.children).forEach(e => { if (e.tagName == 'MK-URL') riot.mount(e); }); } diff --git a/src/web/app/desktop/tags/post-detail.tag b/src/web/app/desktop/tags/post-detail.tag index ce7f81e32..1a0eefe13 100644 --- a/src/web/app/desktop/tags/post-detail.tag +++ b/src/web/app/desktop/tags/post-detail.tag @@ -273,7 +273,7 @@ this.refs.text.innerHTML = compile(tokens); - this.refs.text.children.forEach(e => { + Array.from(this.refs.text.children).forEach(e => { if (e.tagName == 'MK-URL') riot.mount(e); }); diff --git a/src/web/app/desktop/tags/post-form.tag b/src/web/app/desktop/tags/post-form.tag index 5041078be..e49beeedb 100644 --- a/src/web/app/desktop/tags/post-form.tag +++ b/src/web/app/desktop/tags/post-form.tag @@ -405,7 +405,7 @@ // ファイルだったら if (e.dataTransfer.files.length > 0) { - e.dataTransfer.files.forEach(this.upload); + Array.from(e.dataTransfer.files).forEach(this.upload); } }; @@ -414,7 +414,7 @@ }; this.onpaste = e => { - e.clipboardData.items.forEach(item => { + Array.from(e.clipboardData.items).forEach(item => { if (item.kind == 'file') { this.upload(item.getAsFile()); } @@ -435,7 +435,7 @@ }; this.changeFile = () => { - this.refs.file.files.forEach(this.upload); + Array.from(this.refs.file.files).forEach(this.upload); }; this.upload = file => { diff --git a/src/web/app/desktop/tags/sub-post-content.tag b/src/web/app/desktop/tags/sub-post-content.tag index c75ae2911..86269fdbe 100644 --- a/src/web/app/desktop/tags/sub-post-content.tag +++ b/src/web/app/desktop/tags/sub-post-content.tag @@ -45,7 +45,7 @@ const tokens = this.post.ast; this.refs.text.innerHTML = compile(tokens, false); - this.refs.text.children.forEach(e => { + Array.from(this.refs.text.children).forEach(e => { if (e.tagName == 'MK-URL') riot.mount(e); }); } diff --git a/src/web/app/desktop/tags/timeline.tag b/src/web/app/desktop/tags/timeline.tag index 44f3d5d8e..5e3b883b4 100644 --- a/src/web/app/desktop/tags/timeline.tag +++ b/src/web/app/desktop/tags/timeline.tag @@ -498,7 +498,7 @@ this.refs.text.innerHTML = this.refs.text.innerHTML.replace('

', compile(tokens)); - this.refs.text.children.forEach(e => { + Array.from(this.refs.text.children).forEach(e => { if (e.tagName == 'MK-URL') riot.mount(e); }); diff --git a/src/web/app/dev/router.js b/src/web/app/dev/router.ts similarity index 94% rename from src/web/app/dev/router.js rename to src/web/app/dev/router.ts index 7fde30fa5..532ec23c7 100644 --- a/src/web/app/dev/router.js +++ b/src/web/app/dev/router.ts @@ -1,5 +1,5 @@ import * as riot from 'riot'; -const route = require('page'); +import * as route from 'page'; let page = null; export default me => { @@ -32,7 +32,7 @@ export default me => { } // EXEC - route(); + (route as any)(); }; function mount(content) { diff --git a/src/web/app/dev/script.js b/src/web/app/dev/script.ts similarity index 100% rename from src/web/app/dev/script.js rename to src/web/app/dev/script.ts diff --git a/src/web/app/dev/tags/index.js b/src/web/app/dev/tags/index.ts similarity index 100% rename from src/web/app/dev/tags/index.js rename to src/web/app/dev/tags/index.ts diff --git a/src/web/app/init.js b/src/web/app/init.ts similarity index 81% rename from src/web/app/init.js rename to src/web/app/init.ts index d3817fe97..e68a7c915 100644 --- a/src/web/app/init.js +++ b/src/web/app/init.ts @@ -2,14 +2,13 @@ * App initializer */ -'use strict'; +declare var VERSION: string; +declare var LANG: string; import * as riot from 'riot'; -import api from './common/scripts/api'; import signout from './common/scripts/signout'; import checkForUpdate from './common/scripts/check-for-update'; import Connection from './common/scripts/home-stream'; -import ServerStreamManager from './common/scripts/server-stream-manager.ts'; import Progress from './common/scripts/loading'; import mixin from './common/mixins'; import CONFIG from './common/scripts/config'; @@ -37,21 +36,7 @@ console.info(`Misskey v${VERSION} (葵 aoi)`); document.domain = CONFIG.host; // Set global configuration -riot.mixin({ CONFIG }); - -// ↓ NodeList、HTMLCollection、FileList、DataTransferItemListで forEach を使えるようにする -if (NodeList.prototype.forEach === undefined) { - NodeList.prototype.forEach = Array.prototype.forEach; -} -if (HTMLCollection.prototype.forEach === undefined) { - HTMLCollection.prototype.forEach = Array.prototype.forEach; -} -if (FileList.prototype.forEach === undefined) { - FileList.prototype.forEach = Array.prototype.forEach; -} -if (window.DataTransferItemList && DataTransferItemList.prototype.forEach === undefined) { - DataTransferItemList.prototype.forEach = Array.prototype.forEach; -} +(riot as any).mixin({ CONFIG }); // iOSでプライベートモードだとlocalStorageが使えないので既存のメソッドを上書きする try { @@ -72,7 +57,7 @@ setTimeout(checkForUpdate, 3000); // ユーザーをフェッチしてコールバックする export default callback => { // Get cached account data - let cachedMe = JSON.parse(localStorage.getItem('me')); + const cachedMe = JSON.parse(localStorage.getItem('me')); if (cachedMe) { fetched(cachedMe); @@ -112,11 +97,8 @@ export default callback => { // Init home stream connection const stream = me ? new Connection(me) : null; - // Init server stream connection manager - const serverStreamManager = new ServerStreamManager(); - // ミックスイン初期化 - mixin(me, stream, serverStreamManager); + mixin(me, stream); // ローディング画面クリア const ini = document.getElementById('ini'); diff --git a/src/web/app/mobile/router.js b/src/web/app/mobile/router.ts similarity index 97% rename from src/web/app/mobile/router.js rename to src/web/app/mobile/router.ts index 01eb3c814..7fae9db54 100644 --- a/src/web/app/mobile/router.js +++ b/src/web/app/mobile/router.ts @@ -3,7 +3,7 @@ */ import * as riot from 'riot'; -const route = require('page'); +import * as route from 'page'; let page = null; export default me => { @@ -131,12 +131,12 @@ export default me => { mount(document.createElement('mk-not-found')); } - riot.mixin('page', { + (riot as any).mixin('page', { page: route }); // EXEC - route(); + (route as any)(); }; function mount(content) { diff --git a/src/web/app/mobile/script.js b/src/web/app/mobile/script.ts similarity index 100% rename from src/web/app/mobile/script.js rename to src/web/app/mobile/script.ts diff --git a/src/web/app/mobile/scripts/open-post-form.js b/src/web/app/mobile/scripts/open-post-form.ts similarity index 100% rename from src/web/app/mobile/scripts/open-post-form.js rename to src/web/app/mobile/scripts/open-post-form.ts diff --git a/src/web/app/mobile/scripts/ui-event.js b/src/web/app/mobile/scripts/ui-event.ts similarity index 100% rename from src/web/app/mobile/scripts/ui-event.js rename to src/web/app/mobile/scripts/ui-event.ts diff --git a/src/web/app/mobile/tags/drive.tag b/src/web/app/mobile/tags/drive.tag index 6929c50ab..870a451ac 100644 --- a/src/web/app/mobile/tags/drive.tag +++ b/src/web/app/mobile/tags/drive.tag @@ -561,7 +561,7 @@ }; this.changeLocalFile = () => { - this.refs.file.files.forEach(f => this.refs.uploader.upload(f, this.folder)); + Array.from(this.refs.file.files).forEach(f => this.refs.uploader.upload(f, this.folder)); }; diff --git a/src/web/app/mobile/tags/index.js b/src/web/app/mobile/tags/index.ts similarity index 100% rename from src/web/app/mobile/tags/index.js rename to src/web/app/mobile/tags/index.ts diff --git a/src/web/app/mobile/tags/post-detail.tag b/src/web/app/mobile/tags/post-detail.tag index 8a3210103..28071a5ca 100644 --- a/src/web/app/mobile/tags/post-detail.tag +++ b/src/web/app/mobile/tags/post-detail.tag @@ -285,7 +285,7 @@ this.refs.text.innerHTML = compile(tokens); - this.refs.text.children.forEach(e => { + Array.from(this.refs.text.children).forEach(e => { if (e.tagName == 'MK-URL') riot.mount(e); }); diff --git a/src/web/app/mobile/tags/post-form.tag b/src/web/app/mobile/tags/post-form.tag index d7d382c9e..2912bfdfa 100644 --- a/src/web/app/mobile/tags/post-form.tag +++ b/src/web/app/mobile/tags/post-form.tag @@ -207,7 +207,7 @@ }; this.onpaste = e => { - e.clipboardData.items.forEach(item => { + Array.from(e.clipboardData.items).forEach(item => { if (item.kind == 'file') { this.upload(item.getAsFile()); } @@ -228,7 +228,7 @@ }; this.changeFile = () => { - this.refs.file.files.forEach(this.upload); + Array.from(this.refs.file.files).forEach(this.upload); }; this.upload = file => { diff --git a/src/web/app/mobile/tags/sub-post-content.tag b/src/web/app/mobile/tags/sub-post-content.tag index e32e24518..c14233d3b 100644 --- a/src/web/app/mobile/tags/sub-post-content.tag +++ b/src/web/app/mobile/tags/sub-post-content.tag @@ -37,7 +37,7 @@ const tokens = this.post.ast; this.refs.text.innerHTML = compile(tokens, false); - this.refs.text.children.forEach(e => { + Array.from(this.refs.text.children).forEach(e => { if (e.tagName == 'MK-URL') riot.mount(e); }); } diff --git a/src/web/app/mobile/tags/timeline.tag b/src/web/app/mobile/tags/timeline.tag index f9ec2cca6..52f6f27ea 100644 --- a/src/web/app/mobile/tags/timeline.tag +++ b/src/web/app/mobile/tags/timeline.tag @@ -538,7 +538,7 @@ this.refs.text.innerHTML = this.refs.text.innerHTML.replace('

', compile(tokens)); - this.refs.text.children.forEach(e => { + Array.from(this.refs.text.children).forEach(e => { if (e.tagName == 'MK-URL') riot.mount(e); }); diff --git a/src/web/app/stats/script.js b/src/web/app/stats/script.ts similarity index 100% rename from src/web/app/stats/script.js rename to src/web/app/stats/script.ts diff --git a/src/web/app/stats/tags/index.js b/src/web/app/stats/tags/index.ts similarity index 100% rename from src/web/app/stats/tags/index.js rename to src/web/app/stats/tags/index.ts diff --git a/src/web/app/status/script.js b/src/web/app/status/script.ts similarity index 100% rename from src/web/app/status/script.js rename to src/web/app/status/script.ts diff --git a/src/web/app/status/tags/index.js b/src/web/app/status/tags/index.ts similarity index 100% rename from src/web/app/status/tags/index.js rename to src/web/app/status/tags/index.ts diff --git a/tslint.json b/tslint.json index 1c4457951..d3f96000b 100644 --- a/tslint.json +++ b/tslint.json @@ -13,6 +13,7 @@ "object-literal-sort-keys": false, "curly": false, "no-console": [false], + "no-empty":false, "ordered-imports": [false], "arrow-parens": false, "object-literal-shorthand": false, diff --git a/webpack/webpack.config.ts b/webpack/webpack.config.ts index 97782a410..f2bcf48f3 100644 --- a/webpack/webpack.config.ts +++ b/webpack/webpack.config.ts @@ -14,13 +14,13 @@ module.exports = langs.map(([lang, locale]) => { // Entries const entry = { - desktop: './src/web/app/desktop/script.js', - mobile: './src/web/app/mobile/script.js', - ch: './src/web/app/ch/script.js', - stats: './src/web/app/stats/script.js', - status: './src/web/app/status/script.js', - dev: './src/web/app/dev/script.js', - auth: './src/web/app/auth/script.js' + desktop: './src/web/app/desktop/script.ts', + mobile: './src/web/app/mobile/script.ts', + ch: './src/web/app/ch/script.ts', + stats: './src/web/app/stats/script.ts', + status: './src/web/app/status/script.ts', + dev: './src/web/app/dev/script.ts', + auth: './src/web/app/auth/script.ts' }; const output = { @@ -33,6 +33,11 @@ module.exports = langs.map(([lang, locale]) => { entry, module: module_(lang, locale), plugins: plugins(version, lang), - output + output, + resolve: { + extensions: [ + '.js', '.ts' + ] + } }; }); From 0a994e5b9885265873e02b3b3ab9add7ec7e7e6b Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 13 Nov 2017 19:58:29 +0900 Subject: [PATCH 192/327] Add access log widget --- locales/en.yml | 3 + locales/ja.yml | 3 + src/api/stream/requests.ts | 19 ++++ src/api/streaming.ts | 6 ++ src/log-request.ts | 21 +++++ src/server.ts | 6 ++ src/web/app/common/mixins/index.ts | 2 + .../common/scripts/requests-stream-manager.ts | 12 +++ src/web/app/common/scripts/requests-stream.ts | 14 +++ .../desktop/tags/home-widgets/access-log.tag | 93 +++++++++++++++++++ src/web/app/desktop/tags/home.tag | 1 + src/web/app/desktop/tags/index.ts | 1 + 12 files changed, 181 insertions(+) create mode 100644 src/api/stream/requests.ts create mode 100644 src/log-request.ts create mode 100644 src/web/app/common/scripts/requests-stream-manager.ts create mode 100644 src/web/app/common/scripts/requests-stream.ts create mode 100644 src/web/app/desktop/tags/home-widgets/access-log.tag diff --git a/locales/en.yml b/locales/en.yml index 574af26a6..d464b2fe2 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -390,6 +390,9 @@ desktop: post: "Post" placeholder: "What's happening?" + mk-access-log-home-widget: + title: "Access log" + mk-repost-form: quote: "Quote..." cancel: "Cancel" diff --git a/locales/ja.yml b/locales/ja.yml index 9e6251d0d..5c6f8e38e 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -390,6 +390,9 @@ desktop: post: "投稿" placeholder: "いまどうしてる?" + mk-access-log-home-widget: + title: "アクセスログ" + mk-repost-form: quote: "引用する..." cancel: "キャンセル" diff --git a/src/api/stream/requests.ts b/src/api/stream/requests.ts new file mode 100644 index 000000000..2c36e58b6 --- /dev/null +++ b/src/api/stream/requests.ts @@ -0,0 +1,19 @@ +import * as websocket from 'websocket'; +import Xev from 'xev'; + +const ev = new Xev(); + +export default function homeStream(request: websocket.request, connection: websocket.connection): void { + const onRequest = request => { + connection.send(JSON.stringify({ + type: 'request', + body: request + })); + }; + + ev.addListener('request', onRequest); + + connection.on('close', () => { + ev.removeListener('request', onRequest); + }); +} diff --git a/src/api/streaming.ts b/src/api/streaming.ts index 0e512fb21..6caf7db3e 100644 --- a/src/api/streaming.ts +++ b/src/api/streaming.ts @@ -9,6 +9,7 @@ import isNativeToken from './common/is-native-token'; import homeStream from './stream/home'; import messagingStream from './stream/messaging'; import serverStream from './stream/server'; +import requestsStream from './stream/requests'; import channelStream from './stream/channel'; module.exports = (server: http.Server) => { @@ -27,6 +28,11 @@ module.exports = (server: http.Server) => { return; } + if (request.resourceURL.pathname === '/requests') { + requestsStream(request, connection); + return; + } + // Connect to Redis const subscriber = redis.createClient( config.redis.port, config.redis.host); diff --git a/src/log-request.ts b/src/log-request.ts new file mode 100644 index 000000000..e431aa271 --- /dev/null +++ b/src/log-request.ts @@ -0,0 +1,21 @@ +import * as crypto from 'crypto'; +import * as express from 'express'; +import * as proxyAddr from 'proxy-addr'; +import Xev from 'xev'; + +const ev = new Xev(); + +export default function(req: express.Request) { + const ip = proxyAddr(req, () => true); + + const md5 = crypto.createHash('md5'); + md5.update(ip); + const hashedIp = md5.digest('hex').substr(0, 3); + + ev.emit('request', { + ip: hashedIp, + method: req.method, + hostname: req.hostname, + path: req.originalUrl + }); +} diff --git a/src/server.ts b/src/server.ts index 3e9bd44ee..9cf44eb0d 100644 --- a/src/server.ts +++ b/src/server.ts @@ -11,6 +11,7 @@ import * as morgan from 'morgan'; import Accesses from 'accesses'; import vhost = require('vhost'); +import log from './log-request'; import config from './conf'; /** @@ -35,6 +36,11 @@ app.use(morgan(process.env.NODE_ENV == 'production' ? 'combined' : 'dev', { stream: config.accesslog ? fs.createWriteStream(config.accesslog) : null })); +app.use((req, res, next) => { + log(req); + next(); +}); + // Drop request when without 'Host' header app.use((req, res, next) => { if (!req.headers['host']) { diff --git a/src/web/app/common/mixins/index.ts b/src/web/app/common/mixins/index.ts index 45427fb9d..a11bfa7b6 100644 --- a/src/web/app/common/mixins/index.ts +++ b/src/web/app/common/mixins/index.ts @@ -3,6 +3,7 @@ import * as riot from 'riot'; import activateMe from './i'; import activateApi from './api'; import ServerStreamManager from '../scripts/server-stream-manager'; +import RequestsStreamManager from '../scripts/requests-stream-manager'; export default (me, stream) => { activateMe(me); @@ -11,4 +12,5 @@ export default (me, stream) => { (riot as any).mixin('stream', { stream }); (riot as any).mixin('server-stream', { serverStream: new ServerStreamManager() }); + (riot as any).mixin('requests-stream', { requestsStream: new RequestsStreamManager() }); }; diff --git a/src/web/app/common/scripts/requests-stream-manager.ts b/src/web/app/common/scripts/requests-stream-manager.ts new file mode 100644 index 000000000..44db913e7 --- /dev/null +++ b/src/web/app/common/scripts/requests-stream-manager.ts @@ -0,0 +1,12 @@ +import StreamManager from './stream-manager'; +import Connection from './requests-stream'; + +export default class RequestsStreamManager extends StreamManager { + public getConnection() { + if (this.connection == null) { + this.connection = new Connection(); + } + + return this.connection; + } +} diff --git a/src/web/app/common/scripts/requests-stream.ts b/src/web/app/common/scripts/requests-stream.ts new file mode 100644 index 000000000..325224587 --- /dev/null +++ b/src/web/app/common/scripts/requests-stream.ts @@ -0,0 +1,14 @@ +'use strict'; + +import Stream from './stream'; + +/** + * Requests stream connection + */ +class Connection extends Stream { + constructor() { + super('requests'); + } +} + +export default Connection; diff --git a/src/web/app/desktop/tags/home-widgets/access-log.tag b/src/web/app/desktop/tags/home-widgets/access-log.tag new file mode 100644 index 000000000..a14857756 --- /dev/null +++ b/src/web/app/desktop/tags/home-widgets/access-log.tag @@ -0,0 +1,93 @@ + + +

%i18n:desktop.tags.mk-access-log-home-widget.title%

+
+
+

+ { ip } + { method } + { path } +

+
+ + +
diff --git a/src/web/app/desktop/tags/home.tag b/src/web/app/desktop/tags/home.tag index fd286851d..88d06d2ba 100644 --- a/src/web/app/desktop/tags/home.tag +++ b/src/web/app/desktop/tags/home.tag @@ -20,6 +20,7 @@ + diff --git a/src/web/app/desktop/tags/index.ts b/src/web/app/desktop/tags/index.ts index 15677471c..8b5a52d67 100644 --- a/src/web/app/desktop/tags/index.ts +++ b/src/web/app/desktop/tags/index.ts @@ -43,6 +43,7 @@ require('./home-widgets/slideshow.tag'); require('./home-widgets/channel.tag'); require('./home-widgets/timemachine.tag'); require('./home-widgets/post-form.tag'); +require('./home-widgets/access-log.tag'); require('./timeline.tag'); require('./messaging/window.tag'); require('./messaging/room-window.tag'); From ab2293aa4c0832f9e57d64aa22d2fce319fbfcb1 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 14 Nov 2017 00:54:16 +0900 Subject: [PATCH 193/327] Add messaging widget --- locales/en.yml | 3 + locales/ja.yml | 3 + src/api/common/read-messaging-message.ts | 2 + .../endpoints/messaging/messages/create.ts | 3 + src/api/event.ts | 6 ++ src/api/stream/messaging-index.ts | 10 +++ src/api/streaming.ts | 2 + src/web/app/common/mixins/index.ts | 2 + .../scripts/messaging-index-stream-manager.ts | 20 +++++ .../common/scripts/messaging-index-stream.ts | 14 ++++ src/web/app/common/tags/messaging/index.tag | 76 +++++++++++++++++-- .../desktop/tags/home-widgets/messaging.tag | 50 ++++++++++++ src/web/app/desktop/tags/home.tag | 1 + src/web/app/desktop/tags/index.ts | 1 + 14 files changed, 186 insertions(+), 7 deletions(-) create mode 100644 src/api/stream/messaging-index.ts create mode 100644 src/web/app/common/scripts/messaging-index-stream-manager.ts create mode 100644 src/web/app/common/scripts/messaging-index-stream.ts create mode 100644 src/web/app/desktop/tags/home-widgets/messaging.tag diff --git a/locales/en.yml b/locales/en.yml index d464b2fe2..d6fcd1e6d 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -393,6 +393,9 @@ desktop: mk-access-log-home-widget: title: "Access log" + mk-messaging-home-widget: + title: "Messaging" + mk-repost-form: quote: "Quote..." cancel: "Cancel" diff --git a/locales/ja.yml b/locales/ja.yml index 5c6f8e38e..478afac13 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -393,6 +393,9 @@ desktop: mk-access-log-home-widget: title: "アクセスログ" + mk-messaging-home-widget: + title: "メッセージ" + mk-repost-form: quote: "引用する..." cancel: "キャンセル" diff --git a/src/api/common/read-messaging-message.ts b/src/api/common/read-messaging-message.ts index 3257ec8b0..8e5e5b2b6 100644 --- a/src/api/common/read-messaging-message.ts +++ b/src/api/common/read-messaging-message.ts @@ -3,6 +3,7 @@ import Message from '../models/messaging-message'; import { IMessagingMessage as IMessage } from '../models/messaging-message'; import publishUserStream from '../event'; import { publishMessagingStream } from '../event'; +import { publishMessagingIndexStream } from '../event'; /** * Mark as read message(s) @@ -49,6 +50,7 @@ export default ( // Publish event publishMessagingStream(otherpartyId, userId, 'read', ids.map(id => id.toString())); + publishMessagingIndexStream(userId, 'read', ids.map(id => id.toString())); // Calc count of my unread messages const count = await Message diff --git a/src/api/endpoints/messaging/messages/create.ts b/src/api/endpoints/messaging/messages/create.ts index 149852c09..29a4671f8 100644 --- a/src/api/endpoints/messaging/messages/create.ts +++ b/src/api/endpoints/messaging/messages/create.ts @@ -10,6 +10,7 @@ import DriveFile from '../../../models/drive-file'; import serialize from '../../../serializers/messaging-message'; import publishUserStream from '../../../event'; import { publishMessagingStream } from '../../../event'; +import { publishMessagingIndexStream } from '../../../event'; import config from '../../../../conf'; /** @@ -85,10 +86,12 @@ module.exports = (params, user) => new Promise(async (res, rej) => { // 自分のストリーム publishMessagingStream(message.user_id, message.recipient_id, 'message', messageObj); + publishMessagingIndexStream(message.user_id, 'message', messageObj); publishUserStream(message.user_id, 'messaging_message', messageObj); // 相手のストリーム publishMessagingStream(message.recipient_id, message.user_id, 'message', messageObj); + publishMessagingIndexStream(message.recipient_id, 'message', messageObj); publishUserStream(message.recipient_id, 'messaging_message', messageObj); // 3秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する diff --git a/src/api/event.ts b/src/api/event.ts index 909b0d255..927883737 100644 --- a/src/api/event.ts +++ b/src/api/event.ts @@ -25,6 +25,10 @@ class MisskeyEvent { this.publish(`messaging-stream:${userId}-${otherpartyId}`, type, typeof value === 'undefined' ? null : value); } + public publishMessagingIndexStream(userId: ID, type: string, value?: any): void { + this.publish(`messaging-index-stream:${userId}`, type, typeof value === 'undefined' ? null : value); + } + public publishChannelStream(channelId: ID, type: string, value?: any): void { this.publish(`channel-stream:${channelId}`, type, typeof value === 'undefined' ? null : value); } @@ -46,4 +50,6 @@ export const publishPostStream = ev.publishPostStream.bind(ev); export const publishMessagingStream = ev.publishMessagingStream.bind(ev); +export const publishMessagingIndexStream = ev.publishMessagingIndexStream.bind(ev); + export const publishChannelStream = ev.publishChannelStream.bind(ev); diff --git a/src/api/stream/messaging-index.ts b/src/api/stream/messaging-index.ts new file mode 100644 index 000000000..c1b2fbc80 --- /dev/null +++ b/src/api/stream/messaging-index.ts @@ -0,0 +1,10 @@ +import * as websocket from 'websocket'; +import * as redis from 'redis'; + +export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void { + // Subscribe messaging index stream + subscriber.subscribe(`misskey:messaging-index-stream:${user._id}`); + subscriber.on('message', (_, data) => { + connection.send(data); + }); +} diff --git a/src/api/streaming.ts b/src/api/streaming.ts index 6caf7db3e..1f0ba848c 100644 --- a/src/api/streaming.ts +++ b/src/api/streaming.ts @@ -8,6 +8,7 @@ import isNativeToken from './common/is-native-token'; import homeStream from './stream/home'; import messagingStream from './stream/messaging'; +import messagingIndexStream from './stream/messaging-index'; import serverStream from './stream/server'; import requestsStream from './stream/requests'; import channelStream from './stream/channel'; @@ -58,6 +59,7 @@ module.exports = (server: http.Server) => { const channel = request.resourceURL.pathname === '/' ? homeStream : request.resourceURL.pathname === '/messaging' ? messagingStream : + request.resourceURL.pathname === '/messaging-index' ? messagingIndexStream : null; if (channel !== null) { diff --git a/src/web/app/common/mixins/index.ts b/src/web/app/common/mixins/index.ts index a11bfa7b6..c0c1c0555 100644 --- a/src/web/app/common/mixins/index.ts +++ b/src/web/app/common/mixins/index.ts @@ -4,6 +4,7 @@ import activateMe from './i'; import activateApi from './api'; import ServerStreamManager from '../scripts/server-stream-manager'; import RequestsStreamManager from '../scripts/requests-stream-manager'; +import MessagingIndexStream from '../scripts/messaging-index-stream-manager'; export default (me, stream) => { activateMe(me); @@ -13,4 +14,5 @@ export default (me, stream) => { (riot as any).mixin('server-stream', { serverStream: new ServerStreamManager() }); (riot as any).mixin('requests-stream', { requestsStream: new RequestsStreamManager() }); + (riot as any).mixin('messaging-index-stream', { messagingIndexStream: new MessagingIndexStream(me) }); }; diff --git a/src/web/app/common/scripts/messaging-index-stream-manager.ts b/src/web/app/common/scripts/messaging-index-stream-manager.ts new file mode 100644 index 000000000..dc386204c --- /dev/null +++ b/src/web/app/common/scripts/messaging-index-stream-manager.ts @@ -0,0 +1,20 @@ +import StreamManager from './stream-manager'; +import Connection from './messaging-index-stream'; + +export default class ServerStreamManager extends StreamManager { + private me; + + constructor(me) { + super(); + + this.me = me; + } + + public getConnection() { + if (this.connection == null) { + this.connection = new Connection(this.me); + } + + return this.connection; + } +} diff --git a/src/web/app/common/scripts/messaging-index-stream.ts b/src/web/app/common/scripts/messaging-index-stream.ts new file mode 100644 index 000000000..c194e663c --- /dev/null +++ b/src/web/app/common/scripts/messaging-index-stream.ts @@ -0,0 +1,14 @@ +import Stream from './stream'; + +/** + * Messaging index stream connection + */ +class Connection extends Stream { + constructor(me) { + super('messaging-index', { + i: me.token + }); + } +} + +export default Connection; diff --git a/src/web/app/common/tags/messaging/index.tag b/src/web/app/common/tags/messaging/index.tag index 731c9da2c..4c1bf0c6e 100644 --- a/src/web/app/common/tags/messaging/index.tag +++ b/src/web/app/common/tags/messaging/index.tag @@ -1,5 +1,5 @@ - - + +

%i18n:desktop.tags.mk-user.frequently-replied-users.title%

+

%i18n:desktop.tags.mk-user.frequently-replied-users.loading%

+
+ + + +
+ { _user.name } +

@{ _user.username }

+
+ +
+

%i18n:desktop.tags.mk-user.frequently-replied-users.no-users%

+ + +
+ + +

%i18n:desktop.tags.mk-user.followers-you-know.title%

+

%i18n:desktop.tags.mk-user.followers-you-know.loading%

+
0 }> + + { + +
+

%i18n:desktop.tags.mk-user.followers-you-know.no-users%

+ + +
+
+
@@ -405,6 +591,7 @@
+
From 4de91739ff84dbca4854d96b4cddab1a76f75871 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 15 Nov 2017 06:45:54 +0900 Subject: [PATCH 237/327] nanka iroiro --- locales/en.yml | 20 +- locales/ja.yml | 20 +- src/web/app/common/tags/index.ts | 1 + src/web/app/common/tags/nav-links.tag | 7 + src/web/app/desktop/assets/index.jpg | Bin 0 -> 410409 bytes src/web/app/desktop/tags/home-widgets/nav.tag | 3 +- src/web/app/desktop/tags/index.ts | 2 - src/web/app/desktop/tags/pages/entrance.tag | 326 +++++++++++++++--- .../desktop/tags/pages/entrance/signin.tag | 134 ------- .../desktop/tags/pages/entrance/signup.tag | 47 --- src/web/app/desktop/tags/user.tag | 24 ++ 11 files changed, 336 insertions(+), 248 deletions(-) create mode 100644 src/web/app/common/tags/nav-links.tag create mode 100644 src/web/app/desktop/assets/index.jpg delete mode 100644 src/web/app/desktop/tags/pages/entrance/signin.tag delete mode 100644 src/web/app/desktop/tags/pages/entrance/signup.tag diff --git a/locales/en.yml b/locales/en.yml index e7d878f20..a2bd9aa1b 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -50,6 +50,15 @@ common: my-token-regenerated: "Your token is just regenerated, so you will signout." tags: + mk-nav-links: + about: "About" + stats: "Stats" + status: "Status" + wiki: "Wiki" + donors: "Donors" + repository: "Repository" + develop: "Developers" + mk-messaging-form: attach-from-local: "Attach file from your pc" attach-from-drive: "Attach file from the drive" @@ -256,15 +265,6 @@ desktop: cancel: "Cancel" upload: "Upload a file(s) from you PC" - mk-nav-home-widget: - about: "About" - stats: "Stats" - status: "Status" - wiki: "Wiki" - donors: "Donors" - repository: "Repository" - develop: "Developers" - mk-ui-header-nav: home: "Home" messaging: "Messages" @@ -408,6 +408,8 @@ desktop: title: "Are you sure you want to repost this post?" mk-user: + last-used-at: "Last used at" + photos: title: "Photos" loading: "Loading" diff --git a/locales/ja.yml b/locales/ja.yml index f386a3824..d0292e8c4 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -50,6 +50,15 @@ common: my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。" tags: + mk-nav-links: + about: "Misskeyについて" + stats: "統計" + status: "ステータス" + wiki: "Wiki" + donors: "ドナー" + repository: "リポジトリ" + develop: "開発者" + mk-messaging-form: attach-from-local: "PCからファイルを添付する" attach-from-drive: "ドライブからファイルを添付する" @@ -256,15 +265,6 @@ desktop: cancel: "キャンセル" upload: "PCからドライブにファイルをアップロード" - mk-nav-home-widget: - about: "Misskeyについて" - stats: "統計" - status: "ステータス" - wiki: "Wiki" - donors: "ドナー" - repository: "リポジトリ" - develop: "開発者" - mk-ui-header-nav: home: "ホーム" messaging: "メッセージ" @@ -408,6 +408,8 @@ desktop: title: "この投稿をRepostしますか?" mk-user: + last-used-at: "最終アクセス" + photos: title: "フォト" loading: "読み込み中" diff --git a/src/web/app/common/tags/index.ts b/src/web/app/common/tags/index.ts index 35a9f4586..2f4e1181d 100644 --- a/src/web/app/common/tags/index.ts +++ b/src/web/app/common/tags/index.ts @@ -28,3 +28,4 @@ require('./reaction-picker.tag'); require('./reactions-viewer.tag'); require('./reaction-icon.tag'); require('./post-menu.tag'); +require('./nav-links.tag'); diff --git a/src/web/app/common/tags/nav-links.tag b/src/web/app/common/tags/nav-links.tag new file mode 100644 index 000000000..b09e376b3 --- /dev/null +++ b/src/web/app/common/tags/nav-links.tag @@ -0,0 +1,7 @@ + + %i18n:common.tags.mk-nav-links.about%%i18n:common.tags.mk-nav-links.stats%%i18n:common.tags.mk-nav-links.status%%i18n:common.tags.mk-nav-links.wiki%%i18n:common.tags.mk-nav-links.donors%%i18n:common.tags.mk-nav-links.repository%%i18n:common.tags.mk-nav-links.develop%Follow us on + + diff --git a/src/web/app/desktop/assets/index.jpg b/src/web/app/desktop/assets/index.jpg new file mode 100644 index 0000000000000000000000000000000000000000..10c412efe2f198aaec24fa3114563543ad26795b GIT binary patch literal 410409 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

bDAp0{*X-CR4zzr(v$4? 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

@@ -619,6 +620,7 @@ + diff --git a/src/web/app/desktop/tags/home-widgets/broadcast.tag b/src/web/app/desktop/tags/home-widgets/broadcast.tag index 7caf4dcdd..00fef8374 100644 --- a/src/web/app/desktop/tags/home-widgets/broadcast.tag +++ b/src/web/app/desktop/tags/home-widgets/broadcast.tag @@ -1,4 +1,4 @@ - +
@@ -8,22 +8,35 @@
-

開発者募集中!

-

Misskeyはオープンソースで開発されています。リポジトリはこちら。

+

%i18n:desktop.tags.mk-broadcast-home-widget.fetching%

+

{ + broadcasts.length == 0 ? '%i18n:desktop.tags.mk-broadcast-home-widget.no-broadcasts%' : broadcasts[i].title + }

+

%i18n:desktop.tags.mk-broadcast-home-widget.have-a-nice-day%

+ 1 } onclick={ next }>%i18n:desktop.tags.mk-broadcast-home-widget.next% >>
From 0f6c5f506af6d40e426496fe7ddd2fa49c31d3e6 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 16 Nov 2017 14:21:08 +0900 Subject: [PATCH 248/327] v3121 --- CHANGELOG.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 396108552..ec522bbfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ChangeLog (Release Notes) ========================= 主に notable な changes を書いていきます +3121 (2017/11/16) +----------------- +* ブロードキャストウィジェットの強化 +* デザインのグリッチの修正 +* 通信の最適化 + 3113 (2017/11/15) ----------------- * アクティビティのレンダリングの問題の修正など diff --git a/package.json b/package.json index 4535ebcb1..68c558ec2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo ", - "version": "0.0.3113", + "version": "0.0.3121", "license": "MIT", "description": "A miniblog-based SNS", "bugs": "https://github.com/syuilo/misskey/issues", From 598500223a762ada9abe084f81248432731792e4 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 16 Nov 2017 22:49:58 +0900 Subject: [PATCH 249/327] Fix #924 and some readability improvements --- src/api/common/add-file-to-drive.ts | 116 +++++++++++++++------------- 1 file changed, 64 insertions(+), 52 deletions(-) diff --git a/src/api/common/add-file-to-drive.ts b/src/api/common/add-file-to-drive.ts index 9ed5e8874..f96f58cb6 100644 --- a/src/api/common/add-file-to-drive.ts +++ b/src/api/common/add-file-to-drive.ts @@ -1,18 +1,20 @@ +import { Buffer } from 'buffer'; +import * as fs from 'fs'; +import * as tmp from 'tmp'; +import * as stream from 'stream'; + import * as mongodb from 'mongodb'; import * as crypto from 'crypto'; import * as gm from 'gm'; import * as debug from 'debug'; import fileType = require('file-type'); import prominence = require('prominence'); + import DriveFile, { getGridFSBucket } from '../models/drive-file'; import DriveFolder from '../models/drive-folder'; import serialize from '../serializers/drive-file'; import event from '../event'; import config from '../../conf'; -import { Buffer } from 'buffer'; -import * as fs from 'fs'; -import * as tmp from 'tmp'; -import * as stream from 'stream'; const log = debug('misskey:register-drive-file'); @@ -67,10 +69,12 @@ const addFile = async ( .once('data', (buffer: Buffer) => { readable.destroy(); const type = fileType(buffer); - if (!type) { + if (type) { + return res([type.mime, type.ext]); + } else { + // 種類が同定できなかったら application/octet-stream にする return res(['application/octet-stream', null]); } - return res([type.mime, type.ext]); }); }))(), // size @@ -105,9 +109,18 @@ const addFile = async ( const [properties, folder] = await Promise.all([ // properties (async () => { + // 画像かどうか if (!/^image\/.*$/.test(mime)) { return null; } + + const imageType = mime.split('/')[1]; + + // 画像でもPNGかJPEGでないならスキップ + if (imageType != 'png' && imageType != 'jpeg') { + return null; + } + // If the file is an image, calculate width and height to save in property const g = gm(fs.createReadStream(path), name); const size = await prominence(g).size(); @@ -115,7 +128,9 @@ const addFile = async ( width: size.width, height: size.height }; + log('image width and height is calculated'); + return properties; })(), // folder @@ -136,20 +151,18 @@ const addFile = async ( (async () => { // Calculate drive usage const usage = await DriveFile - .aggregate([ - { $match: { 'metadata.user_id': user._id } }, - { - $project: { - length: true - } - }, - { - $group: { - _id: null, - usage: { $sum: '$length' } - } + .aggregate([{ + $match: { 'metadata.user_id': user._id } + }, { + $project: { + length: true } - ]) + }, { + $group: { + _id: null, + usage: { $sum: '$length' } + } + }]) .then((aggregates: any[]) => { if (aggregates.length > 0) { return aggregates[0].usage; @@ -211,41 +224,40 @@ export default (user: any, file: string | stream.Readable, ...args) => new Promi } rej(new Error('un-compatible file.')); }) - .then(([path, remove]): Promise => new Promise((res, rej) => { - addFile(user, path, ...args) - .then(file => { - res(file); - if (remove) { - fs.unlink(path, (e) => { - if (e) log(e.stack); - }); - } - }) - .catch(rej); - })) - .then(file => { - log(`drive file has been created ${file._id}`); - resolve(file); + .then(([path, remove]): Promise => new Promise((res, rej) => { + addFile(user, path, ...args) + .then(file => { + res(file); + if (remove) { + fs.unlink(path, (e) => { + if (e) log(e.stack); + }); + } + }) + .catch(rej); + })) + .then(file => { + log(`drive file has been created ${file._id}`); + resolve(file); - serialize(file) - .then(serializedFile => { - // Publish drive_file_created event - event(user._id, 'drive_file_created', serializedFile); + serialize(file).then(serializedFile => { + // Publish drive_file_created event + event(user._id, 'drive_file_created', serializedFile); - // Register to search database - if (config.elasticsearch.enable) { - const es = require('../../db/elasticsearch'); - es.index({ - index: 'misskey', - type: 'drive_file', - id: file._id.toString(), - body: { - name: file.name, - user_id: user._id.toString() - } - }); + // Register to search database + if (config.elasticsearch.enable) { + const es = require('../../db/elasticsearch'); + es.index({ + index: 'misskey', + type: 'drive_file', + id: file._id.toString(), + body: { + name: file.name, + user_id: user._id.toString() } }); - }) - .catch(reject); + } + }); + }) + .catch(reject); }); From 16d4861ff67c057737c9253bcb37fac4fe40eeab Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 16 Nov 2017 22:51:28 +0900 Subject: [PATCH 250/327] Fix bug --- src/web/app/mobile/tags/drive/file-viewer.tag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/app/mobile/tags/drive/file-viewer.tag b/src/web/app/mobile/tags/drive/file-viewer.tag index 8dc49a086..2cec4f329 100644 --- a/src/web/app/mobile/tags/drive/file-viewer.tag +++ b/src/web/app/mobile/tags/drive/file-viewer.tag @@ -2,7 +2,7 @@
{ -
0 }> +
@@ -161,22 +163,20 @@ > .contents > .folders - &:after - content "" - display block - clear both + > .files + display flex + flex-wrap wrap > .folder - float left - - > .files - &:after - content "" - display block - clear both - > .file - float left + flex-grow 1 + width 144px + margin 4px + + > .padding + flex-grow 1 + pointer-events none + width 144px + 8px // 8px is margin > .empty padding 16px diff --git a/src/web/app/desktop/tags/drive/file.tag b/src/web/app/desktop/tags/drive/file.tag index bf9d38bd2..0f019d95b 100644 --- a/src/web/app/desktop/tags/drive/file.tag +++ b/src/web/app/desktop/tags/drive/file.tag @@ -10,9 +10,7 @@ diff --git a/src/web/app/desktop/tags/messaging/room-window.tag b/src/web/app/desktop/tags/messaging/room-window.tag index dca0172be..1c6ff7c4b 100644 --- a/src/web/app/desktop/tags/messaging/room-window.tag +++ b/src/web/app/desktop/tags/messaging/room-window.tag @@ -19,11 +19,9 @@ diff --git a/src/web/app/mobile/tags/timeline.tag b/src/web/app/mobile/tags/timeline.tag index 1d6ce2359..074422a20 100644 --- a/src/web/app/mobile/tags/timeline.tag +++ b/src/web/app/mobile/tags/timeline.tag @@ -164,7 +164,7 @@
-

{ p.channel.title }:

+

{ p.channel.title }:

diff --git a/src/web/app/mobile/tags/ui.tag b/src/web/app/mobile/tags/ui.tag index 0c969d390..bad6bf73f 100644 --- a/src/web/app/mobile/tags/ui.tag +++ b/src/web/app/mobile/tags/ui.tag @@ -239,7 +239,7 @@
  • %i18n:mobile.tags.mk-ui-nav.messaging%
  • -

    %i18n:mobile.tags.mk-ui-nav.about%

    +

    %i18n:mobile.tags.mk-ui-nav.about%