From 08fefc0184e83ad6af919a05f3e0e4ad8074e116 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 31 Mar 2018 21:41:08 +0900 Subject: [PATCH 01/28] Use Vue rendering function and some refactors --- .../app/common/views/components/index.ts | 2 +- .../components/messaging-room.message.vue | 41 ++--- .../app/common/views/components/post-html.ts | 157 ++++++++++++++++++ .../app/common/views/components/post-html.vue | 103 ------------ .../app/common/views/components/url.vue | 57 +++++++ .../views/components/welcome-timeline.vue | 2 +- .../views/components/post-detail.sub.vue | 2 +- .../desktop/views/components/post-detail.vue | 42 +++-- .../desktop/views/components/posts.post.vue | 41 ++--- .../views/components/sub-post-content.vue | 2 +- .../mobile/views/components/post-detail.vue | 37 +++-- .../app/mobile/views/components/post.vue | 43 ++--- .../views/components/sub-post-content.vue | 2 +- src/common/text/parse/index.ts | 2 +- 14 files changed, 322 insertions(+), 211 deletions(-) create mode 100644 src/client/app/common/views/components/post-html.ts delete mode 100644 src/client/app/common/views/components/post-html.vue create mode 100644 src/client/app/common/views/components/url.vue diff --git a/src/client/app/common/views/components/index.ts b/src/client/app/common/views/components/index.ts index 8c10bdee2..b58ba37ec 100644 --- a/src/client/app/common/views/components/index.ts +++ b/src/client/app/common/views/components/index.ts @@ -4,7 +4,7 @@ import signin from './signin.vue'; import signup from './signup.vue'; import forkit from './forkit.vue'; import nav from './nav.vue'; -import postHtml from './post-html.vue'; +import postHtml from './post-html'; import poll from './poll.vue'; import pollEditor from './poll-editor.vue'; import reactionIcon from './reaction-icon.vue'; diff --git a/src/client/app/common/views/components/messaging-room.message.vue b/src/client/app/common/views/components/messaging-room.message.vue index 25ceab85a..91af26bff 100644 --- a/src/client/app/common/views/components/messaging-room.message.vue +++ b/src/client/app/common/views/components/messaging-room.message.vue @@ -4,13 +4,13 @@
-
+

%i18n:common.tags.mk-messaging-message.is-read%

- +
@@ -35,35 +35,30 @@ - - diff --git a/src/client/app/common/views/components/url.vue b/src/client/app/common/views/components/url.vue new file mode 100644 index 000000000..e6ffe4466 --- /dev/null +++ b/src/client/app/common/views/components/url.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/src/client/app/common/views/components/welcome-timeline.vue b/src/client/app/common/views/components/welcome-timeline.vue index f379029f9..09b090bdc 100644 --- a/src/client/app/common/views/components/welcome-timeline.vue +++ b/src/client/app/common/views/components/welcome-timeline.vue @@ -15,7 +15,7 @@
- +
diff --git a/src/client/app/desktop/views/components/post-detail.sub.vue b/src/client/app/desktop/views/components/post-detail.sub.vue index b6148d9b2..1d5649cf9 100644 --- a/src/client/app/desktop/views/components/post-detail.sub.vue +++ b/src/client/app/desktop/views/components/post-detail.sub.vue @@ -16,7 +16,7 @@
- +
diff --git a/src/client/app/desktop/views/components/post-detail.vue b/src/client/app/desktop/views/components/post-detail.vue index e75ebe34b..70bfdbba3 100644 --- a/src/client/app/desktop/views/components/post-detail.vue +++ b/src/client/app/desktop/views/components/post-detail.vue @@ -38,7 +38,7 @@
- +
@@ -79,6 +79,7 @@ import Vue from 'vue'; import dateStringify from '../../../common/scripts/date-stringify'; import getAcct from '../../../../../common/user/get-acct'; +import parse from '../../../../../common/text/parse'; import MkPostFormWindow from './post-form-window.vue'; import MkRepostFormWindow from './repost-form-window.vue'; @@ -90,6 +91,7 @@ export default Vue.extend({ components: { XSub }, + props: { post: { type: Object, @@ -99,19 +101,15 @@ export default Vue.extend({ default: false } }, - computed: { - acct() { - return getAcct(this.post.user); - } - }, + data() { return { context: [], contextFetching: false, - replies: [], - urls: [] + replies: [] }; }, + computed: { isRepost(): boolean { return (this.post.repost && @@ -131,8 +129,22 @@ export default Vue.extend({ }, title(): string { return dateStringify(this.p.createdAt); + }, + acct(): string { + return getAcct(this.p.user); + }, + urls(): string[] { + if (this.p.text) { + const ast = parse(this.p.text); + return ast + .filter(t => (t.type == 'url' || t.type == 'link') && !t.silent) + .map(t => t.url); + } else { + return null; + } } }, + mounted() { // Get replies if (!this.compact) { @@ -162,21 +174,7 @@ export default Vue.extend({ } } }, - watch: { - post: { - handler(newPost, oldPost) { - if (!oldPost || newPost.text !== oldPost.text) { - this.$nextTick(() => { - const elements = this.$refs.text.$el.getElementsByTagName('a'); - this.urls = [].filter.call(elements, ({ origin }) => origin !== location.origin) - .map(({ href }) => href); - }); - } - }, - immediate: true - } - }, methods: { fetchContext() { this.contextFetching = true; diff --git a/src/client/app/desktop/views/components/posts.post.vue b/src/client/app/desktop/views/components/posts.post.vue index f3566c81b..c31e28d67 100644 --- a/src/client/app/desktop/views/components/posts.post.vue +++ b/src/client/app/desktop/views/components/posts.post.vue @@ -38,7 +38,7 @@

@@ -86,6 +86,8 @@ import Vue from 'vue'; import dateStringify from '../../../common/scripts/date-stringify'; import getAcct from '../../../../../common/user/get-acct'; +import parse from '../../../../../common/text/parse'; + import MkPostFormWindow from './post-form-window.vue'; import MkRepostFormWindow from './repost-form-window.vue'; import MkPostMenu from '../../../common/views/components/post-menu.vue'; @@ -107,17 +109,19 @@ export default Vue.extend({ components: { XSub }, + props: ['post'], + data() { return { isDetailOpened: false, connection: null, - connectionId: null, - urls: [] + connectionId: null }; }, + computed: { - acct() { + acct(): string { return getAcct(this.p.user); }, isRepost(): boolean { @@ -141,14 +145,26 @@ export default Vue.extend({ }, url(): string { return `/@${this.acct}/${this.p.id}`; + }, + urls(): string[] { + if (this.p.text) { + const ast = parse(this.p.text); + return ast + .filter(t => (t.type == 'url' || t.type == 'link') && !t.silent) + .map(t => t.url); + } else { + return null; + } } }, + created() { if ((this as any).os.isSignedIn) { this.connection = (this as any).os.stream.getConnection(); this.connectionId = (this as any).os.stream.use(); } }, + mounted() { this.capture(true); @@ -174,6 +190,7 @@ export default Vue.extend({ } } }, + beforeDestroy() { this.decapture(true); @@ -182,21 +199,7 @@ export default Vue.extend({ (this as any).os.stream.dispose(this.connectionId); } }, - watch: { - post: { - handler(newPost, oldPost) { - if (!oldPost || newPost.textHtml !== oldPost.textHtml) { - this.$nextTick(() => { - const elements = this.$refs.text.$el.getElementsByTagName('a'); - this.urls = [].filter.call(elements, ({ origin }) => origin !== location.origin) - .map(({ href }) => href); - }); - } - }, - immediate: true - } - }, methods: { capture(withHandler = false) { if ((this as any).os.isSignedIn) { @@ -457,7 +460,7 @@ export default Vue.extend({ font-size 1.1em color #717171 - >>> blockquote + >>> .quote margin 8px padding 6px 12px color #aaa diff --git a/src/client/app/desktop/views/components/sub-post-content.vue b/src/client/app/desktop/views/components/sub-post-content.vue index 58c81e755..17899af28 100644 --- a/src/client/app/desktop/views/components/sub-post-content.vue +++ b/src/client/app/desktop/views/components/sub-post-content.vue @@ -2,7 +2,7 @@
diff --git a/src/client/app/mobile/views/components/post-detail.vue b/src/client/app/mobile/views/components/post-detail.vue index 77a73426f..0a4e36fc6 100644 --- a/src/client/app/mobile/views/components/post-detail.vue +++ b/src/client/app/mobile/views/components/post-detail.vue @@ -81,6 +81,8 @@ From b8ff725aff77cae2812d5e00917a5eb81b01d053 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 1 Apr 2018 15:58:49 +0900 Subject: [PATCH 16/28] Implement inbox --- package.json | 1 + .../remote/activitypub/resolve-person.ts | 4 ++ src/models/user.ts | 4 ++ src/server/activitypub/inbox.ts | 42 +++++++++++++++++++ src/server/activitypub/index.ts | 12 ++++++ .../{activitypub.ts => activitypub/user.ts} | 12 +++--- 6 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 src/server/activitypub/inbox.ts create mode 100644 src/server/activitypub/index.ts rename src/server/{activitypub.ts => activitypub/user.ts} (79%) diff --git a/package.json b/package.json index 4275c1c1c..14c89927e 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,7 @@ "hard-source-webpack-plugin": "0.6.4", "highlight.js": "9.12.0", "html-minifier": "3.5.12", + "http-signature": "^1.2.0", "inquirer": "5.2.0", "is-root": "2.0.0", "is-url": "1.2.4", diff --git a/src/common/remote/activitypub/resolve-person.ts b/src/common/remote/activitypub/resolve-person.ts index c7c131b0e..999a37eea 100644 --- a/src/common/remote/activitypub/resolve-person.ts +++ b/src/common/remote/activitypub/resolve-person.ts @@ -62,6 +62,10 @@ export default async (value, usernameLower, hostLower, acctLower) => { host: toUnicode(finger.subject.replace(/^.*?@/, '')), hostLower, account: { + publicKey: { + id: object.publicKey.id, + publicKeyPem: object.publicKey.publicKeyPem + }, uri: object.id, }, }); diff --git a/src/models/user.ts b/src/models/user.ts index 02e6a570b..9588c4515 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -71,6 +71,10 @@ export type ILocalAccount = { export type IRemoteAccount = { uri: string; + publicKey: { + id: string; + publicKeyPem: string; + }; }; export type IUser = { diff --git a/src/server/activitypub/inbox.ts b/src/server/activitypub/inbox.ts new file mode 100644 index 000000000..0d4af7c49 --- /dev/null +++ b/src/server/activitypub/inbox.ts @@ -0,0 +1,42 @@ +import * as bodyParser from 'body-parser'; +import * as express from 'express'; +import { parseRequest, verifySignature } from 'http-signature'; +import User, { IRemoteAccount } from '../../models/user'; +import queue from '../../queue'; + +const app = express(); +app.disable('x-powered-by'); +app.use(bodyParser.json()); + +app.get('/@:user/inbox', async (req, res) => { + let parsed; + + try { + parsed = parseRequest(req); + } catch (exception) { + return res.sendStatus(401); + } + + const user = await User.findOne({ + host: { $ne: null }, + account: { publicKey: { id: parsed.keyId } } + }); + + if (user === null) { + return res.sendStatus(401); + } + + if (!verifySignature(parsed, (user.account as IRemoteAccount).publicKey.publicKeyPem)) { + return res.sendStatus(401); + } + + queue.create('http', { + type: 'performActivityPub', + actor: user._id, + outbox: req.body, + }).save(); + + return res.sendStatus(200); +}); + +export default app; diff --git a/src/server/activitypub/index.ts b/src/server/activitypub/index.ts new file mode 100644 index 000000000..07ff407a7 --- /dev/null +++ b/src/server/activitypub/index.ts @@ -0,0 +1,12 @@ +import * as express from 'express'; + +import user from './user'; +import inbox from './inbox'; + +const app = express(); +app.disable('x-powered-by'); + +app.use(user); +app.use(inbox); + +export default app; diff --git a/src/server/activitypub.ts b/src/server/activitypub/user.ts similarity index 79% rename from src/server/activitypub.ts rename to src/server/activitypub/user.ts index a48a8e643..488de93a9 100644 --- a/src/server/activitypub.ts +++ b/src/server/activitypub/user.ts @@ -1,16 +1,15 @@ import * as express from 'express'; - -import config from '../conf'; -import { extractPublic } from '../crypto_key'; -import parseAcct from '../common/user/parse-acct'; -import User, { ILocalAccount } from '../models/user'; +import config from '../../conf'; +import { extractPublic } from '../../crypto_key'; +import parseAcct from '../../common/user/parse-acct'; +import User, { ILocalAccount } from '../../models/user'; const app = express(); app.disable('x-powered-by'); app.get('/@:user', async (req, res, next) => { const accepted = req.accepts(['html', 'application/activity+json', 'application/ld+json']); - if (!['application/activity+json', 'application/ld+json'].includes(accepted)) { + if (!(['application/activity+json', 'application/ld+json'] as Array).includes(accepted)) { return next(); } @@ -40,6 +39,7 @@ app.get('/@:user', async (req, res, next) => { ], type: 'Person', id, + inbox: `${id}/inbox`, preferredUsername: user.username, name: user.name, summary: user.description, From 13949270d288ff7d80c08e51974350cb35986b75 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 1 Apr 2018 16:04:23 +0900 Subject: [PATCH 17/28] Use dot notation --- src/server/activitypub/inbox.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/activitypub/inbox.ts b/src/server/activitypub/inbox.ts index 0d4af7c49..f76e750d2 100644 --- a/src/server/activitypub/inbox.ts +++ b/src/server/activitypub/inbox.ts @@ -19,7 +19,7 @@ app.get('/@:user/inbox', async (req, res) => { const user = await User.findOne({ host: { $ne: null }, - account: { publicKey: { id: parsed.keyId } } + 'account.publicKey.id': parsed.keyId }); if (user === null) { From 7fe9c9e73fa00edb51a9c4907ee929f18977e42d Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 1 Apr 2018 16:46:33 +0900 Subject: [PATCH 18/28] Fix --- src/server/activitypub/inbox.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/activitypub/inbox.ts b/src/server/activitypub/inbox.ts index f76e750d2..b4761d997 100644 --- a/src/server/activitypub/inbox.ts +++ b/src/server/activitypub/inbox.ts @@ -8,7 +8,7 @@ const app = express(); app.disable('x-powered-by'); app.use(bodyParser.json()); -app.get('/@:user/inbox', async (req, res) => { +app.post('/@:user/inbox', async (req, res) => { let parsed; try { From 2212a55bad63f5cfc482d35db07267036160fdca Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 1 Apr 2018 08:40:47 +0000 Subject: [PATCH 19/28] fix(package): update bootstrap-vue to version 2.0.0-rc.4 Closes #1349 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 14c89927e..d5fa1f3cb 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "autwh": "0.0.1", "bcryptjs": "2.4.3", "body-parser": "1.18.2", - "bootstrap-vue": "2.0.0-rc.1", + "bootstrap-vue": "2.0.0-rc.4", "cafy": "3.2.1", "chai": "4.1.2", "chai-http": "4.0.0", From 65478160ca2a4570adaad47bfbe79f093c07fdb7 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 1 Apr 2018 18:07:29 +0900 Subject: [PATCH 20/28] Add missing property --- src/models/post.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/models/post.ts b/src/models/post.ts index 6c853e4f8..4daad306d 100644 --- a/src/models/post.ts +++ b/src/models/post.ts @@ -30,6 +30,7 @@ export type IPost = { repostId: mongo.ObjectID; poll: any; // todo text: string; + tags: string[]; textHtml: string; cw: string; userId: mongo.ObjectID; From f4c4ce83310323965d28bac8ad9deb7c6574cf4a Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 1 Apr 2018 18:12:51 +0900 Subject: [PATCH 21/28] Implement post Activity Streams --- src/common/remote/activitypub/context.ts | 5 ++ src/models/post.ts | 1 + src/server/activitypub/index.ts | 2 + src/server/activitypub/post.ts | 85 ++++++++++++++++++++++++ src/server/activitypub/user.ts | 6 +- 5 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 src/common/remote/activitypub/context.ts create mode 100644 src/server/activitypub/post.ts diff --git a/src/common/remote/activitypub/context.ts b/src/common/remote/activitypub/context.ts new file mode 100644 index 000000000..b56f727ae --- /dev/null +++ b/src/common/remote/activitypub/context.ts @@ -0,0 +1,5 @@ +export default [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', + { Hashtag: 'as:Hashtag' } +]; diff --git a/src/models/post.ts b/src/models/post.ts index 6c853e4f8..64c46c263 100644 --- a/src/models/post.ts +++ b/src/models/post.ts @@ -47,6 +47,7 @@ export type IPost = { heading: number; speed: number; }; + tags: string[]; }; /** diff --git a/src/server/activitypub/index.ts b/src/server/activitypub/index.ts index 07ff407a7..6618c291f 100644 --- a/src/server/activitypub/index.ts +++ b/src/server/activitypub/index.ts @@ -1,11 +1,13 @@ import * as express from 'express'; +import post from './post'; import user from './user'; import inbox from './inbox'; const app = express(); app.disable('x-powered-by'); +app.use(post); app.use(user); app.use(inbox); diff --git a/src/server/activitypub/post.ts b/src/server/activitypub/post.ts new file mode 100644 index 000000000..4fb3a1319 --- /dev/null +++ b/src/server/activitypub/post.ts @@ -0,0 +1,85 @@ +import * as express from 'express'; +import context from '../../common/remote/activitypub/context'; +import parseAcct from '../../common/user/parse-acct'; +import config from '../../conf'; +import DriveFile from '../../models/drive-file'; +import Post from '../../models/post'; +import User from '../../models/user'; + +const app = express(); +app.disable('x-powered-by'); + +app.get('/@:user/:post', async (req, res, next) => { + const accepted = req.accepts(['html', 'application/activity+json', 'application/ld+json']); + if (!(['application/activity+json', 'application/ld+json'] as Array).includes(accepted)) { + return next(); + } + + const { username, host } = parseAcct(req.params.user); + if (host !== null) { + return res.sendStatus(422); + } + + const user = await User.findOne({ + usernameLower: username.toLowerCase(), + host: null + }); + if (user === null) { + return res.sendStatus(404); + } + + const post = await Post.findOne({ + _id: req.params.post, + userId: user._id + }); + if (post === null) { + return res.sendStatus(404); + } + + const asyncFiles = DriveFile.find({ _id: { $in: post.mediaIds } }); + let inReplyTo; + + if (post.replyId) { + const inReplyToPost = await Post.findOne({ + _id: post.replyId, + }); + + if (inReplyToPost !== null) { + const inReplyToUser = await User.findOne({ + _id: post.userId, + }); + + if (inReplyToUser !== null) { + inReplyTo = `${config.url}@${inReplyToUser.username}/${inReplyToPost._id}`; + } + } + } else { + inReplyTo = null; + } + + const attributedTo = `${config.url}/@${user.username}`; + + res.json({ + '@context': context, + id: `${attributedTo}/${post._id}`, + type: 'Note', + attributedTo, + content: post.textHtml, + published: post.createdAt.toISOString(), + to: 'https://www.w3.org/ns/activitystreams#Public', + cc: `${attributedTo}/followers`, + inReplyTo, + attachment: (await asyncFiles).map(({ _id, contentType }) => ({ + type: 'Document', + mediaType: contentType, + url: `${config.drive_url}/${_id}` + })), + tag: post.tags.map(tag => ({ + type: 'Hashtag', + href: `${config.url}/search?q=#${encodeURIComponent(tag)}`, + name: '#' + tag + })) + }); +}); + +export default app; diff --git a/src/server/activitypub/user.ts b/src/server/activitypub/user.ts index 488de93a9..ef365c207 100644 --- a/src/server/activitypub/user.ts +++ b/src/server/activitypub/user.ts @@ -1,6 +1,7 @@ import * as express from 'express'; import config from '../../conf'; import { extractPublic } from '../../crypto_key'; +import context from '../../common/remote/activitypub/context'; import parseAcct from '../../common/user/parse-acct'; import User, { ILocalAccount } from '../../models/user'; @@ -33,10 +34,7 @@ app.get('/@:user', async (req, res, next) => { } res.json({ - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], + '@context': context, type: 'Person', id, inbox: `${id}/inbox`, From f53497e2c0a5a3fc53589c3ee3735d3ddea5584b Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 1 Apr 2018 18:16:47 +0900 Subject: [PATCH 22/28] Respond with 202 for inbox request --- src/server/activitypub/inbox.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/activitypub/inbox.ts b/src/server/activitypub/inbox.ts index b4761d997..915129748 100644 --- a/src/server/activitypub/inbox.ts +++ b/src/server/activitypub/inbox.ts @@ -36,7 +36,7 @@ app.post('/@:user/inbox', async (req, res) => { outbox: req.body, }).save(); - return res.sendStatus(200); + return res.status(202).end(); }); export default app; From f9c290a9dfd00d4c6fce6ed990a89fd147be0b2d Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 1 Apr 2018 18:17:04 +0900 Subject: [PATCH 23/28] Resolve conflict --- src/models/post.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/models/post.ts b/src/models/post.ts index e7b54180f..4daad306d 100644 --- a/src/models/post.ts +++ b/src/models/post.ts @@ -48,7 +48,6 @@ export type IPost = { heading: number; speed: number; }; - tags: string[]; }; /** From 40c9adfef5d0c061a2d5c9c844cc3597d463a617 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 1 Apr 2018 18:20:17 +0900 Subject: [PATCH 24/28] Fix type annotation --- src/server/activitypub/post.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/activitypub/post.ts b/src/server/activitypub/post.ts index 4fb3a1319..261d7ca4a 100644 --- a/src/server/activitypub/post.ts +++ b/src/server/activitypub/post.ts @@ -11,7 +11,7 @@ app.disable('x-powered-by'); app.get('/@:user/:post', async (req, res, next) => { const accepted = req.accepts(['html', 'application/activity+json', 'application/ld+json']); - if (!(['application/activity+json', 'application/ld+json'] as Array).includes(accepted)) { + if (!(['application/activity+json', 'application/ld+json'] as any[]).includes(accepted)) { return next(); } From e494fdd6da66ae1dabcd1262557f05b54ca2b0d2 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 1 Apr 2018 18:28:10 +0900 Subject: [PATCH 25/28] Refactor: Better variable name --- src/server/activitypub/post.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/activitypub/post.ts b/src/server/activitypub/post.ts index 261d7ca4a..bdfce0606 100644 --- a/src/server/activitypub/post.ts +++ b/src/server/activitypub/post.ts @@ -36,7 +36,7 @@ app.get('/@:user/:post', async (req, res, next) => { return res.sendStatus(404); } - const asyncFiles = DriveFile.find({ _id: { $in: post.mediaIds } }); + const promisedFiles = DriveFile.find({ _id: { $in: post.mediaIds } }); let inReplyTo; if (post.replyId) { @@ -69,7 +69,7 @@ app.get('/@:user/:post', async (req, res, next) => { to: 'https://www.w3.org/ns/activitystreams#Public', cc: `${attributedTo}/followers`, inReplyTo, - attachment: (await asyncFiles).map(({ _id, contentType }) => ({ + attachment: (await promisedFiles).map(({ _id, contentType }) => ({ type: 'Document', mediaType: contentType, url: `${config.drive_url}/${_id}` From 344c6b7b3977a486c046a040f90cd8106e72fd34 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 1 Apr 2018 19:18:36 +0900 Subject: [PATCH 26/28] Implement outbox --- .../activitypub/{ => renderer}/context.ts | 0 .../remote/activitypub/renderer/document.ts | 7 +++ .../remote/activitypub/renderer/hashtag.ts | 7 +++ .../remote/activitypub/renderer/image.ts | 6 +++ src/common/remote/activitypub/renderer/key.ts | 9 ++++ .../remote/activitypub/renderer/note.ts | 44 ++++++++++++++++ .../renderer/ordered-collection.ts | 6 +++ .../remote/activitypub/renderer/person.ts | 20 ++++++++ src/server/activitypub/index.ts | 6 ++- src/server/activitypub/outbox.ts | 45 ++++++++++++++++ src/server/activitypub/post.ts | 51 ++----------------- src/server/activitypub/user.ts | 36 +++---------- 12 files changed, 161 insertions(+), 76 deletions(-) rename src/common/remote/activitypub/{ => renderer}/context.ts (100%) create mode 100644 src/common/remote/activitypub/renderer/document.ts create mode 100644 src/common/remote/activitypub/renderer/hashtag.ts create mode 100644 src/common/remote/activitypub/renderer/image.ts create mode 100644 src/common/remote/activitypub/renderer/key.ts create mode 100644 src/common/remote/activitypub/renderer/note.ts create mode 100644 src/common/remote/activitypub/renderer/ordered-collection.ts create mode 100644 src/common/remote/activitypub/renderer/person.ts create mode 100644 src/server/activitypub/outbox.ts diff --git a/src/common/remote/activitypub/context.ts b/src/common/remote/activitypub/renderer/context.ts similarity index 100% rename from src/common/remote/activitypub/context.ts rename to src/common/remote/activitypub/renderer/context.ts diff --git a/src/common/remote/activitypub/renderer/document.ts b/src/common/remote/activitypub/renderer/document.ts new file mode 100644 index 000000000..4a456416a --- /dev/null +++ b/src/common/remote/activitypub/renderer/document.ts @@ -0,0 +1,7 @@ +import config from '../../../../conf'; + +export default ({ _id, contentType }) => ({ + type: 'Document', + mediaType: contentType, + url: `${config.drive_url}/${_id}` +}); diff --git a/src/common/remote/activitypub/renderer/hashtag.ts b/src/common/remote/activitypub/renderer/hashtag.ts new file mode 100644 index 000000000..ad4270020 --- /dev/null +++ b/src/common/remote/activitypub/renderer/hashtag.ts @@ -0,0 +1,7 @@ +import config from '../../../../conf'; + +export default tag => ({ + type: 'Hashtag', + href: `${config.url}/search?q=#${encodeURIComponent(tag)}`, + name: '#' + tag +}); diff --git a/src/common/remote/activitypub/renderer/image.ts b/src/common/remote/activitypub/renderer/image.ts new file mode 100644 index 000000000..345fbbec5 --- /dev/null +++ b/src/common/remote/activitypub/renderer/image.ts @@ -0,0 +1,6 @@ +import config from '../../../../conf'; + +export default ({ _id }) => ({ + type: 'Image', + url: `${config.drive_url}/${_id}` +}); diff --git a/src/common/remote/activitypub/renderer/key.ts b/src/common/remote/activitypub/renderer/key.ts new file mode 100644 index 000000000..7148c5974 --- /dev/null +++ b/src/common/remote/activitypub/renderer/key.ts @@ -0,0 +1,9 @@ +import config from '../../../../conf'; +import { extractPublic } from '../../../../crypto_key'; +import { ILocalAccount } from '../../../../models/user'; + +export default ({ username, account }) => ({ + type: 'Key', + owner: `${config.url}/@${username}`, + publicKeyPem: extractPublic((account as ILocalAccount).keypair) +}); diff --git a/src/common/remote/activitypub/renderer/note.ts b/src/common/remote/activitypub/renderer/note.ts new file mode 100644 index 000000000..2fe20b213 --- /dev/null +++ b/src/common/remote/activitypub/renderer/note.ts @@ -0,0 +1,44 @@ +import renderDocument from './document'; +import renderHashtag from './hashtag'; +import config from '../../../../conf'; +import DriveFile from '../../../../models/drive-file'; +import Post from '../../../../models/post'; +import User from '../../../../models/user'; + +export default async (user, post) => { + const promisedFiles = DriveFile.find({ _id: { $in: post.mediaIds } }); + let inReplyTo; + + if (post.replyId) { + const inReplyToPost = await Post.findOne({ + _id: post.replyId, + }); + + if (inReplyToPost !== null) { + const inReplyToUser = await User.findOne({ + _id: post.userId, + }); + + if (inReplyToUser !== null) { + inReplyTo = `${config.url}@${inReplyToUser.username}/${inReplyToPost._id}`; + } + } + } else { + inReplyTo = null; + } + + const attributedTo = `${config.url}/@${user.username}`; + + return { + id: `${attributedTo}/${post._id}`, + type: 'Note', + attributedTo, + content: post.textHtml, + published: post.createdAt.toISOString(), + to: 'https://www.w3.org/ns/activitystreams#Public', + cc: `${attributedTo}/followers`, + inReplyTo, + attachment: (await promisedFiles).map(renderDocument), + tag: post.tags.map(renderHashtag) + }; +}; diff --git a/src/common/remote/activitypub/renderer/ordered-collection.ts b/src/common/remote/activitypub/renderer/ordered-collection.ts new file mode 100644 index 000000000..2ca0f7735 --- /dev/null +++ b/src/common/remote/activitypub/renderer/ordered-collection.ts @@ -0,0 +1,6 @@ +export default (id, totalItems, orderedItems) => ({ + id, + type: 'OrderedCollection', + totalItems, + orderedItems +}); diff --git a/src/common/remote/activitypub/renderer/person.ts b/src/common/remote/activitypub/renderer/person.ts new file mode 100644 index 000000000..7303b3038 --- /dev/null +++ b/src/common/remote/activitypub/renderer/person.ts @@ -0,0 +1,20 @@ +import renderImage from './image'; +import renderKey from './key'; +import config from '../../../../conf'; + +export default user => { + const id = `${config.url}/@${user.username}`; + + return { + type: 'Person', + id, + inbox: `${id}/inbox`, + outbox: `${id}/outbox`, + preferredUsername: user.username, + name: user.name, + summary: user.description, + icon: user.avatarId && renderImage({ _id: user.avatarId }), + image: user.bannerId && renderImage({ _id: user.bannerId }), + publicKey: renderKey(user) + }; +}; diff --git a/src/server/activitypub/index.ts b/src/server/activitypub/index.ts index 6618c291f..c81024d15 100644 --- a/src/server/activitypub/index.ts +++ b/src/server/activitypub/index.ts @@ -1,14 +1,16 @@ import * as express from 'express'; -import post from './post'; import user from './user'; import inbox from './inbox'; +import outbox from './outbox'; +import post from './post'; const app = express(); app.disable('x-powered-by'); -app.use(post); app.use(user); app.use(inbox); +app.use(outbox); +app.use(post); export default app; diff --git a/src/server/activitypub/outbox.ts b/src/server/activitypub/outbox.ts new file mode 100644 index 000000000..c5a42ae0a --- /dev/null +++ b/src/server/activitypub/outbox.ts @@ -0,0 +1,45 @@ +import * as express from 'express'; +import context from '../../common/remote/activitypub/renderer/context'; +import renderNote from '../../common/remote/activitypub/renderer/note'; +import renderOrderedCollection from '../../common/remote/activitypub/renderer/ordered-collection'; +import parseAcct from '../../common/user/parse-acct'; +import config from '../../conf'; +import Post from '../../models/post'; +import User from '../../models/user'; + +const app = express(); +app.disable('x-powered-by'); + +app.get('/@:user/outbox', async (req, res) => { + const { username, host } = parseAcct(req.params.user); + if (host !== null) { + return res.sendStatus(422); + } + + const user = await User.findOne({ + usernameLower: username.toLowerCase(), + host: null + }); + if (user === null) { + return res.sendStatus(404); + } + + const id = `${config.url}/@${user.username}/inbox`; + + if (username !== user.username) { + return res.redirect(id); + } + + const posts = await Post.find({ userId: user._id }, { + limit: 20, + sort: { _id: -1 } + }); + + const renderedPosts = await Promise.all(posts.map(post => renderNote(user, post))); + const rendered = renderOrderedCollection(id, user.postsCount, renderedPosts); + rendered['@context'] = context; + + res.json(rendered); +}); + +export default app; diff --git a/src/server/activitypub/post.ts b/src/server/activitypub/post.ts index bdfce0606..6644563d8 100644 --- a/src/server/activitypub/post.ts +++ b/src/server/activitypub/post.ts @@ -1,8 +1,7 @@ import * as express from 'express'; -import context from '../../common/remote/activitypub/context'; +import context from '../../common/remote/activitypub/renderer/context'; +import render from '../../common/remote/activitypub/renderer/note'; import parseAcct from '../../common/user/parse-acct'; -import config from '../../conf'; -import DriveFile from '../../models/drive-file'; import Post from '../../models/post'; import User from '../../models/user'; @@ -36,50 +35,10 @@ app.get('/@:user/:post', async (req, res, next) => { return res.sendStatus(404); } - const promisedFiles = DriveFile.find({ _id: { $in: post.mediaIds } }); - let inReplyTo; + const rendered = await render(user, post); + rendered['@context'] = context; - if (post.replyId) { - const inReplyToPost = await Post.findOne({ - _id: post.replyId, - }); - - if (inReplyToPost !== null) { - const inReplyToUser = await User.findOne({ - _id: post.userId, - }); - - if (inReplyToUser !== null) { - inReplyTo = `${config.url}@${inReplyToUser.username}/${inReplyToPost._id}`; - } - } - } else { - inReplyTo = null; - } - - const attributedTo = `${config.url}/@${user.username}`; - - res.json({ - '@context': context, - id: `${attributedTo}/${post._id}`, - type: 'Note', - attributedTo, - content: post.textHtml, - published: post.createdAt.toISOString(), - to: 'https://www.w3.org/ns/activitystreams#Public', - cc: `${attributedTo}/followers`, - inReplyTo, - attachment: (await promisedFiles).map(({ _id, contentType }) => ({ - type: 'Document', - mediaType: contentType, - url: `${config.drive_url}/${_id}` - })), - tag: post.tags.map(tag => ({ - type: 'Hashtag', - href: `${config.url}/search?q=#${encodeURIComponent(tag)}`, - name: '#' + tag - })) - }); + res.json(rendered); }); export default app; diff --git a/src/server/activitypub/user.ts b/src/server/activitypub/user.ts index ef365c207..d43a9793d 100644 --- a/src/server/activitypub/user.ts +++ b/src/server/activitypub/user.ts @@ -1,9 +1,9 @@ import * as express from 'express'; import config from '../../conf'; -import { extractPublic } from '../../crypto_key'; -import context from '../../common/remote/activitypub/context'; +import context from '../../common/remote/activitypub/renderer/context'; +import render from '../../common/remote/activitypub/renderer/person'; import parseAcct from '../../common/user/parse-acct'; -import User, { ILocalAccount } from '../../models/user'; +import User from '../../models/user'; const app = express(); app.disable('x-powered-by'); @@ -27,34 +27,14 @@ app.get('/@:user', async (req, res, next) => { return res.sendStatus(404); } - const id = `${config.url}/@${user.username}`; - if (username !== user.username) { - return res.redirect(id); + return res.redirect(`${config.url}/@${user.username}`); } - res.json({ - '@context': context, - type: 'Person', - id, - inbox: `${id}/inbox`, - preferredUsername: user.username, - name: user.name, - summary: user.description, - icon: user.avatarId && { - type: 'Image', - url: `${config.drive_url}/${user.avatarId}` - }, - image: user.bannerId && { - type: 'Image', - url: `${config.drive_url}/${user.bannerId}` - }, - publicKey: { - type: 'Key', - owner: id, - publicKeyPem: extractPublic((user.account as ILocalAccount).keypair) - } - }); + const rendered = render(user); + rendered['@context'] = context; + + res.json(rendered); }); export default app; From 07543da1608dde3a93cc8d1ec4aaf5acec403e49 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 1 Apr 2018 19:43:27 +0900 Subject: [PATCH 27/28] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f7d67247a..4c0506709 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Misskey [![][dependencies-badge]][dependencies-link] [![][himawari-badge]][himasaku] [![][sakurako-badge]][himasaku] -[![][agpl-3.0-badge]][AGPL-3.0] +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) > Lead Maintainer: [syuilo][syuilo-link] @@ -50,6 +50,8 @@ If you want to donate to Misskey, please see [this](./docs/donate.ja.md). Misskey is an open-source software licensed under [GNU AGPLv3](LICENSE). +[![][agpl-3.0-badge]][AGPL-3.0] + [agpl-3.0]: https://www.gnu.org/licenses/agpl-3.0.en.html [agpl-3.0-badge]: https://img.shields.io/badge/license-AGPL--3.0-444444.svg?style=flat-square [travis-link]: https://travis-ci.org/syuilo/misskey From b2c29f630c9e48eaa8bb9bf6957a11a9f13c2ce3 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 1 Apr 2018 19:53:35 +0900 Subject: [PATCH 28/28] Clean up --- tools/letsencrypt/get-cert.sh | 12 --- tools/migration/node.1509958623.use-gridfs.js | 71 --------------- ...change-gridfs-metadata-name-to-filename.js | 50 ----------- tools/migration/node.1510056272.issue_882.js | 47 ---------- tools/migration/node.2017-11-08.js | 88 ------------------- tools/migration/node.2017-12-11.js | 71 --------------- tools/migration/node.2017-12-22.hiseikika.js | 67 -------------- tools/migration/node.2018-03-13.othello.js | 46 ---------- .../shell.1487734995.user-profile.js | 18 ---- .../shell.1489951459.like-to-reactions.js | 22 ----- .../shell.1509507382.reply_to-to-reply.js | 5 -- 11 files changed, 497 deletions(-) delete mode 100644 tools/letsencrypt/get-cert.sh delete mode 100644 tools/migration/node.1509958623.use-gridfs.js delete mode 100644 tools/migration/node.1510016282.change-gridfs-metadata-name-to-filename.js delete mode 100644 tools/migration/node.1510056272.issue_882.js delete mode 100644 tools/migration/node.2017-11-08.js delete mode 100644 tools/migration/node.2017-12-11.js delete mode 100644 tools/migration/node.2017-12-22.hiseikika.js delete mode 100644 tools/migration/node.2018-03-13.othello.js delete mode 100644 tools/migration/shell.1487734995.user-profile.js delete mode 100644 tools/migration/shell.1489951459.like-to-reactions.js delete mode 100644 tools/migration/shell.1509507382.reply_to-to-reply.js diff --git a/tools/letsencrypt/get-cert.sh b/tools/letsencrypt/get-cert.sh deleted file mode 100644 index d44deb144..000000000 --- a/tools/letsencrypt/get-cert.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -certbot certonly --standalone\ - -d $1\ - -d api.$1\ - -d auth.$1\ - -d docs.$1\ - -d ch.$1\ - -d stats.$1\ - -d status.$1\ - -d dev.$1\ - -d file.$2\ diff --git a/tools/migration/node.1509958623.use-gridfs.js b/tools/migration/node.1509958623.use-gridfs.js deleted file mode 100644 index a9d2b12e9..000000000 --- a/tools/migration/node.1509958623.use-gridfs.js +++ /dev/null @@ -1,71 +0,0 @@ -// 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 { default: zip } = require('@prezzemolo/zip') - -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 ? 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 - delete doc.datasize - delete doc.hash - delete doc.data - delete doc.name - delete doc.type - - const bucket = await getGridFSBucket() - const added = await writeToGridFS(bucket, buffer, id, name, { contentType: type, metadata: doc }) - - const result = await DriveFile.update(id, { - $set: { - uploadDate: created_at - } - }) - - return added && result.ok === 1 -} - -async function main() { - const count = await db.get('drive_files').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) => { - console.log(`${time} / ${idop}`) - const doc = await db.get('drive_files').find({}, { limit: dop, skip: time * dop }) - return Promise.all(doc.map(migrateToGridFS)) - }, - idop - ).then(a => { - const rv = [] - a.forEach(e => rv.push(...e)) - return rv - }) -} - -main().then(console.dir).catch(console.error) diff --git a/tools/migration/node.1510016282.change-gridfs-metadata-name-to-filename.js b/tools/migration/node.1510016282.change-gridfs-metadata-name-to-filename.js deleted file mode 100644 index d7b2a6eff..000000000 --- a/tools/migration/node.1510016282.change-gridfs-metadata-name-to-filename.js +++ /dev/null @@ -1,50 +0,0 @@ -// 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 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 DriveFile.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 - }) -} - -main().then(console.dir).catch(console.error) diff --git a/tools/migration/node.1510056272.issue_882.js b/tools/migration/node.1510056272.issue_882.js deleted file mode 100644 index 302ef3de6..000000000 --- a/tools/migration/node.1510056272.issue_882.js +++ /dev/null @@ -1,47 +0,0 @@ -// 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, { - $set: { - contentType: doc.metadata.type - }, - $unset: { - 'metadata.type': '' - } - }) - return result.ok === 1 -} - -async function main() { - const query = { - 'metadata.type': { - $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 DriveFile.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) diff --git a/tools/migration/node.2017-11-08.js b/tools/migration/node.2017-11-08.js deleted file mode 100644 index 196a5a90c..000000000 --- a/tools/migration/node.2017-11-08.js +++ /dev/null @@ -1,88 +0,0 @@ -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 result.ok === 1 -} - -async function main() { - const count = await User.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 User.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) diff --git a/tools/migration/node.2017-12-11.js b/tools/migration/node.2017-12-11.js deleted file mode 100644 index b9686b8b4..000000000 --- a/tools/migration/node.2017-12-11.js +++ /dev/null @@ -1,71 +0,0 @@ -// for Node.js interpret - -const { default: DriveFile, getGridFSBucket } = require('../../built/api/models/drive-file') -const { default: zip } = require('@prezzemolo/zip') - -const _gm = require('gm'); -const gm = _gm.subClass({ - imageMagick: true -}); - -const migrate = doc => new Promise(async (res, rej) => { - const bucket = await getGridFSBucket(); - - const readable = bucket.openDownloadStream(doc._id); - - gm(readable) - .setFormat('ppm') - .resize(1, 1) - .toBuffer(async (err, buffer) => { - if (err) { - console.error(err); - res(false); - return; - } - const r = buffer.readUInt8(buffer.length - 3); - const g = buffer.readUInt8(buffer.length - 2); - const b = buffer.readUInt8(buffer.length - 1); - - const result = await DriveFile.update(doc._id, { - $set: { - 'metadata.properties.average_color': [r, g, b] - } - }) - - res(result.ok === 1); - }); -}); - -async function main() { - const query = { - contentType: { - $in: [ - 'image/png', - 'image/jpeg' - ] - } - } - - 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 DriveFile.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) diff --git a/tools/migration/node.2017-12-22.hiseikika.js b/tools/migration/node.2017-12-22.hiseikika.js deleted file mode 100644 index ff8294c8d..000000000 --- a/tools/migration/node.2017-12-22.hiseikika.js +++ /dev/null @@ -1,67 +0,0 @@ -// for Node.js interpret - -const { default: Post } = require('../../built/api/models/post') -const { default: zip } = require('@prezzemolo/zip') - -const migrate = async (post) => { - const x = {}; - if (post.reply_id != null) { - const reply = await Post.findOne({ - _id: post.reply_id - }); - x['_reply.user_id'] = reply.user_id; - } - if (post.repost_id != null) { - const repost = await Post.findOne({ - _id: post.repost_id - }); - x['_repost.user_id'] = repost.user_id; - } - if (post.reply_id != null || post.repost_id != null) { - const result = await Post.update(post._id, { - $set: x, - }); - return result.ok === 1; - } else { - return true; - } -} - -async function main() { - const query = { - $or: [{ - reply_id: { - $exists: true, - $ne: null - } - }, { - repost_id: { - $exists: true, - $ne: null - } - }] - } - - const count = await Post.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 Post.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) diff --git a/tools/migration/node.2018-03-13.othello.js b/tools/migration/node.2018-03-13.othello.js deleted file mode 100644 index 4598f8d83..000000000 --- a/tools/migration/node.2018-03-13.othello.js +++ /dev/null @@ -1,46 +0,0 @@ -// for Node.js interpret - -const { default: Othello } = require('../../built/api/models/othello-game') -const { default: zip } = require('@prezzemolo/zip') - -const migrate = async (doc) => { - const x = {}; - - doc.logs.forEach(log => { - log.color = log.color == 'black'; - }); - - const result = await Othello.update(doc._id, { - $set: { - logs: doc.logs - } - }); - - return result.ok === 1; -} - -async function main() { - - const count = await Othello.count({}); - - 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 Othello.find({}, { - 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) diff --git a/tools/migration/shell.1487734995.user-profile.js b/tools/migration/shell.1487734995.user-profile.js deleted file mode 100644 index e6666319e..000000000 --- a/tools/migration/shell.1487734995.user-profile.js +++ /dev/null @@ -1,18 +0,0 @@ -db.users.find({}).forEach(function(user) { - print(user._id); - db.users.update({ _id: user._id }, { - $rename: { - bio: 'description' - }, - $unset: { - location: '', - birthday: '' - }, - $set: { - profile: { - location: user.location || null, - birthday: user.birthday || null - } - } - }, false, false); -}); diff --git a/tools/migration/shell.1489951459.like-to-reactions.js b/tools/migration/shell.1489951459.like-to-reactions.js deleted file mode 100644 index 962a0f00e..000000000 --- a/tools/migration/shell.1489951459.like-to-reactions.js +++ /dev/null @@ -1,22 +0,0 @@ -db.users.update({}, { - $unset: { - likes_count: 1, - liked_count: 1 - } -}, false, true) - -db.likes.renameCollection('post_reactions') - -db.post_reactions.update({}, { - $set: { - reaction: 'like' - } -}, false, true) - -db.posts.update({}, { - $rename: { - likes_count: 'reaction_counts.like' - } -}, false, true); - -db.notifications.remove({}) diff --git a/tools/migration/shell.1509507382.reply_to-to-reply.js b/tools/migration/shell.1509507382.reply_to-to-reply.js deleted file mode 100644 index ceb272ebc..000000000 --- a/tools/migration/shell.1509507382.reply_to-to-reply.js +++ /dev/null @@ -1,5 +0,0 @@ -db.posts.update({}, { - $rename: { - reply_to_id: 'reply_id' - } -}, false, true);