Merge branch 'master' into greenkeeper/html-minifier-3.5.13

This commit is contained in:
syuilo 2018-04-01 19:57:36 +09:00 committed by GitHub
commit fabda94932
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 923 additions and 924 deletions

View file

@ -7,7 +7,7 @@ Misskey
[![][dependencies-badge]][dependencies-link] [![][dependencies-badge]][dependencies-link]
[![][himawari-badge]][himasaku] [![][himawari-badge]][himasaku]
[![][sakurako-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] > 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). 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]: 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 [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 [travis-link]: https://travis-ci.org/syuilo/misskey

View file

@ -89,7 +89,7 @@
"autwh": "0.0.1", "autwh": "0.0.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"body-parser": "1.18.2", "body-parser": "1.18.2",
"bootstrap-vue": "2.0.0-rc.1", "bootstrap-vue": "2.0.0-rc.4",
"cafy": "3.2.1", "cafy": "3.2.1",
"chai": "4.1.2", "chai": "4.1.2",
"chai-http": "4.0.0", "chai-http": "4.0.0",
@ -134,6 +134,7 @@
"hard-source-webpack-plugin": "0.6.4", "hard-source-webpack-plugin": "0.6.4",
"highlight.js": "9.12.0", "highlight.js": "9.12.0",
"html-minifier": "3.5.13", "html-minifier": "3.5.13",
"http-signature": "^1.2.0",
"inquirer": "5.2.0", "inquirer": "5.2.0",
"is-root": "2.0.0", "is-root": "2.0.0",
"is-url": "1.2.4", "is-url": "1.2.4",

View file

@ -4,7 +4,7 @@ import signin from './signin.vue';
import signup from './signup.vue'; import signup from './signup.vue';
import forkit from './forkit.vue'; import forkit from './forkit.vue';
import nav from './nav.vue'; import nav from './nav.vue';
import postHtml from './post-html.vue'; import postHtml from './post-html';
import poll from './poll.vue'; import poll from './poll.vue';
import pollEditor from './poll-editor.vue'; import pollEditor from './poll-editor.vue';
import reactionIcon from './reaction-icon.vue'; import reactionIcon from './reaction-icon.vue';

View file

@ -4,13 +4,13 @@
<img class="avatar" :src="`${message.user.avatarUrl}?thumbnail&size=80`" alt=""/> <img class="avatar" :src="`${message.user.avatarUrl}?thumbnail&size=80`" alt=""/>
</router-link> </router-link>
<div class="content"> <div class="content">
<div class="balloon" :data-no-text="message.textHtml == null"> <div class="balloon" :data-no-text="message.text == null">
<p class="read" v-if="isMe && message.isRead">%i18n:common.tags.mk-messaging-message.is-read%</p> <p class="read" v-if="isMe && message.isRead">%i18n:common.tags.mk-messaging-message.is-read%</p>
<button class="delete-button" v-if="isMe" title="%i18n:common.delete%"> <button class="delete-button" v-if="isMe" title="%i18n:common.delete%">
<img src="/assets/desktop/messaging/delete.png" alt="Delete"/> <img src="/assets/desktop/messaging/delete.png" alt="Delete"/>
</button> </button>
<div class="content" v-if="!message.isDeleted"> <div class="content" v-if="!message.isDeleted">
<mk-post-html class="text" v-if="message.textHtml" ref="text" :html="message.textHtml" :i="os.i"/> <mk-post-html class="text" v-if="message.text" ref="text" :text="message.text" :i="os.i"/>
<div class="file" v-if="message.file"> <div class="file" v-if="message.file">
<a :href="message.file.url" target="_blank" :title="message.file.name"> <a :href="message.file.url" target="_blank" :title="message.file.name">
<img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"/> <img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"/>
@ -35,35 +35,30 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import getAcct from '../../../../../common/user/get-acct'; import getAcct from '../../../../../common/user/get-acct';
import parse from '../../../../../common/text/parse';
export default Vue.extend({ export default Vue.extend({
props: ['message'], props: {
data() { message: {
return { required: true
urls: [] }
};
}, },
computed: { computed: {
acct() { acct(): string {
return getAcct(this.message.user); return getAcct(this.message.user);
}, },
isMe(): boolean { isMe(): boolean {
return this.message.userId == (this as any).os.i.id; return this.message.userId == (this as any).os.i.id;
} },
}, urls(): string[] {
watch: { if (this.message.text) {
message: { const ast = parse(this.message.text);
handler(newMessage, oldMessage) { return ast
if (!oldMessage || newMessage.textHtml !== oldMessage.textHtml) { .filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
this.$nextTick(() => { .map(t => t.url);
const elements = this.$refs.text.$el.getElementsByTagName('a'); } else {
return null;
this.urls = [].filter.call(elements, ({ origin }) => origin !== location.origin) }
.map(({ href }) => href);
});
}
},
immediate: true
} }
} }
}); });

View file

@ -0,0 +1,157 @@
import Vue from 'vue';
import * as emojilib from 'emojilib';
import parse from '../../../../../common/text/parse';
import getAcct from '../../../../../common/user/get-acct';
import { url } from '../../../config';
import MkUrl from './url.vue';
const flatten = list => list.reduce(
(a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []
);
export default Vue.component('mk-post-html', {
props: {
text: {
type: String,
required: true
},
ast: {
type: [],
required: false
},
shouldBreak: {
type: Boolean,
default: true
},
i: {
type: Object,
default: null
}
},
render(createElement) {
let ast;
if (this.ast == null) {
// Parse text to ast
ast = parse(this.text);
} else {
ast = this.ast;
}
// Parse ast to DOM
const els = flatten(ast.map(token => {
switch (token.type) {
case 'text':
const text = token.content.replace(/(\r\n|\n|\r)/g, '\n');
if (this.shouldBreak) {
const x = text.split('\n')
.map(t => t == '' ? [createElement('br')] : [createElement('span', t), createElement('br')]);
x[x.length - 1].pop();
return x;
} else {
return createElement('span', text.replace(/\n/g, ' '));
}
case 'bold':
return createElement('strong', token.bold);
case 'url':
return createElement(MkUrl, {
props: {
url: token.content,
target: '_blank'
}
});
case 'link':
return createElement('a', {
attrs: {
class: 'link',
href: token.url,
target: '_blank',
title: token.url
}
}, token.title);
case 'mention':
return (createElement as any)('a', {
attrs: {
href: `${url}/@${getAcct(token)}`,
target: '_blank',
dataIsMe: (this as any).i && getAcct((this as any).i) == getAcct(token)
},
directives: [{
name: 'user-preview',
value: token.content
}]
}, token.content);
case 'hashtag':
return createElement('a', {
attrs: {
href: `${url}/search?q=${token.content}`,
target: '_blank'
}
}, token.content);
case 'code':
return createElement('pre', [
createElement('code', {
domProps: {
innerHTML: token.html
}
})
]);
case 'inline-code':
return createElement('code', {
domProps: {
innerHTML: token.html
}
});
case 'quote':
const text2 = token.quote.replace(/(\r\n|\n|\r)/g, '\n');
if (this.shouldBreak) {
const x = text2.split('\n')
.map(t => [createElement('span', t), createElement('br')]);
x[x.length - 1].pop();
return createElement('div', {
attrs: {
class: 'quote'
}
}, x);
} else {
return createElement('span', {
attrs: {
class: 'quote'
}
}, text2.replace(/\n/g, ' '));
}
case 'emoji':
const emoji = emojilib.lib[token.emoji];
return createElement('span', emoji ? emoji.char : token.content);
default:
console.log('unknown ast type:', token.type);
}
}));
const _els = [];
els.forEach((el, i) => {
if (el.tag == 'br') {
if (els[i - 1].tag != 'div') {
_els.push(el);
}
} else {
_els.push(el);
}
});
return createElement('span', _els);
}
});

View file

@ -1,103 +0,0 @@
<template><div class="mk-post-html" v-html="html"></div></template>
<script lang="ts">
import Vue from 'vue';
import getAcct from '../../../../../common/user/get-acct';
import { url } from '../../../config';
function markUrl(a) {
while (a.firstChild) {
a.removeChild(a.firstChild);
}
const schema = document.createElement('span');
const delimiter = document.createTextNode('//');
const host = document.createElement('span');
const pathname = document.createElement('span');
const query = document.createElement('span');
const hash = document.createElement('span');
schema.className = 'schema';
schema.textContent = a.protocol;
host.className = 'host';
host.textContent = a.host;
pathname.className = 'pathname';
pathname.textContent = a.pathname;
query.className = 'query';
query.textContent = a.search;
hash.className = 'hash';
hash.textContent = a.hash;
a.appendChild(schema);
a.appendChild(delimiter);
a.appendChild(host);
a.appendChild(pathname);
a.appendChild(query);
a.appendChild(hash);
}
function markMe(me, a) {
a.setAttribute("data-is-me", me && `${url}/@${getAcct(me)}` == a.href);
}
function markTarget(a) {
a.setAttribute("target", "_blank");
}
export default Vue.component('mk-post-html', {
props: {
html: {
type: String,
required: true
},
i: {
type: Object,
default: null
}
},
watch {
html: {
handler() {
this.$nextTick(() => [].forEach.call(this.$el.getElementsByTagName('a'), a => {
if (a.href === a.textContent) {
markUrl(a);
} else {
markMe((this as any).i, a);
}
markTarget(a);
}));
},
immediate: true,
}
}
});
</script>
<style lang="stylus">
.mk-post-html
a
word-break break-all
> .schema
opacity 0.5
> .host
font-weight bold
> .pathname
opacity 0.8
> .query
opacity 0.5
> .hash
font-style italic
p
margin 0
</style>

View file

@ -0,0 +1,57 @@
<template>
<a class="mk-url" :href="url" :target="target">
<span class="schema">{{ schema }}//</span>
<span class="hostname">{{ hostname }}</span>
<span class="port" v-if="port != ''">:{{ port }}</span>
<span class="pathname" v-if="pathname != ''">{{ pathname }}</span>
<span class="query">{{ query }}</span>
<span class="hash">{{ hash }}</span>
%fa:external-link-square-alt%
</a>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: ['url', 'target'],
data() {
return {
schema: null,
hostname: null,
port: null,
pathname: null,
query: null,
hash: null
};
},
created() {
const url = new URL(this.url);
this.schema = url.protocol;
this.hostname = url.hostname;
this.port = url.port;
this.pathname = url.pathname;
this.query = url.search;
this.hash = url.hash;
}
});
</script>
<style lang="stylus" scoped>
.mk-url
word-break break-all
> [data-fa]
padding-left 2px
font-size .9em
font-weight 400
font-style normal
> .schema
opacity 0.5
> .hostname
font-weight bold
> .pathname
opacity 0.8
> .query
opacity 0.5
> .hash
font-style italic
</style>

View file

@ -15,7 +15,7 @@
</div> </div>
</header> </header>
<div class="text"> <div class="text">
<mk-post-html :html="post.textHtml"/> <mk-post-html :text="post.text"/>
</div> </div>
</div> </div>
</div> </div>

View file

@ -16,7 +16,7 @@
</div> </div>
</header> </header>
<div class="body"> <div class="body">
<mk-post-html v-if="post.textHtml" :html="post.textHtml" :i="os.i" :class="$style.text"/> <mk-post-html v-if="post.text" :text="post.text" :i="os.i" :class="$style.text"/>
<div class="media" v-if="post.media > 0"> <div class="media" v-if="post.media > 0">
<mk-media-list :media-list="post.media"/> <mk-media-list :media-list="post.media"/>
</div> </div>

View file

@ -27,18 +27,18 @@
</p> </p>
</div> </div>
<article> <article>
<router-link class="avatar-anchor" :to="`/@${acct}`"> <router-link class="avatar-anchor" :to="`/@${pAcct}`">
<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="p.user.id"/> <img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="p.user.id"/>
</router-link> </router-link>
<header> <header>
<router-link class="name" :to="`/@${acct}`" v-user-preview="p.user.id">{{ p.user.name }}</router-link> <router-link class="name" :to="`/@${pAcct}`" v-user-preview="p.user.id">{{ p.user.name }}</router-link>
<span class="username">@{{ acct }}</span> <span class="username">@{{ pAcct }}</span>
<router-link class="time" :to="`/@${acct}/${p.id}`"> <router-link class="time" :to="`/@${pAcct}/${p.id}`">
<mk-time :time="p.createdAt"/> <mk-time :time="p.createdAt"/>
</router-link> </router-link>
</header> </header>
<div class="body"> <div class="body">
<mk-post-html :class="$style.text" v-if="p.text" ref="text" :text="p.text" :i="os.i"/> <mk-post-html :class="$style.text" v-if="p.text" :text="p.text" :i="os.i"/>
<div class="media" v-if="p.media.length > 0"> <div class="media" v-if="p.media.length > 0">
<mk-media-list :media-list="p.media"/> <mk-media-list :media-list="p.media"/>
</div> </div>
@ -79,6 +79,7 @@
import Vue from 'vue'; import Vue from 'vue';
import dateStringify from '../../../common/scripts/date-stringify'; import dateStringify from '../../../common/scripts/date-stringify';
import getAcct from '../../../../../common/user/get-acct'; import getAcct from '../../../../../common/user/get-acct';
import parse from '../../../../../common/text/parse';
import MkPostFormWindow from './post-form-window.vue'; import MkPostFormWindow from './post-form-window.vue';
import MkRepostFormWindow from './repost-form-window.vue'; import MkRepostFormWindow from './repost-form-window.vue';
@ -90,6 +91,7 @@ export default Vue.extend({
components: { components: {
XSub XSub
}, },
props: { props: {
post: { post: {
type: Object, type: Object,
@ -99,19 +101,15 @@ export default Vue.extend({
default: false default: false
} }
}, },
computed: {
acct() {
return getAcct(this.post.user);
}
},
data() { data() {
return { return {
context: [], context: [],
contextFetching: false, contextFetching: false,
replies: [], replies: []
urls: []
}; };
}, },
computed: { computed: {
isRepost(): boolean { isRepost(): boolean {
return (this.post.repost && return (this.post.repost &&
@ -131,8 +129,25 @@ export default Vue.extend({
}, },
title(): string { title(): string {
return dateStringify(this.p.createdAt); return dateStringify(this.p.createdAt);
},
acct(): string {
return getAcct(this.post.user);
},
pAcct(): 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() { mounted() {
// Get replies // Get replies
if (!this.compact) { if (!this.compact) {
@ -162,21 +177,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: { methods: {
fetchContext() { fetchContext() {
this.contextFetching = true; this.contextFetching = true;

View file

@ -38,7 +38,7 @@
</p> </p>
<div class="text"> <div class="text">
<a class="reply" v-if="p.reply">%fa:reply%</a> <a class="reply" v-if="p.reply">%fa:reply%</a>
<mk-post-html v-if="p.textHtml" ref="text" :html="p.textHtml" :i="os.i" :class="$style.text"/> <mk-post-html v-if="p.textHtml" :text="p.text" :i="os.i" :class="$style.text"/>
<a class="rp" v-if="p.repost">RP:</a> <a class="rp" v-if="p.repost">RP:</a>
</div> </div>
<div class="media" v-if="p.media.length > 0"> <div class="media" v-if="p.media.length > 0">
@ -86,6 +86,8 @@
import Vue from 'vue'; import Vue from 'vue';
import dateStringify from '../../../common/scripts/date-stringify'; import dateStringify from '../../../common/scripts/date-stringify';
import getAcct from '../../../../../common/user/get-acct'; import getAcct from '../../../../../common/user/get-acct';
import parse from '../../../../../common/text/parse';
import MkPostFormWindow from './post-form-window.vue'; import MkPostFormWindow from './post-form-window.vue';
import MkRepostFormWindow from './repost-form-window.vue'; import MkRepostFormWindow from './repost-form-window.vue';
import MkPostMenu from '../../../common/views/components/post-menu.vue'; import MkPostMenu from '../../../common/views/components/post-menu.vue';
@ -107,17 +109,19 @@ export default Vue.extend({
components: { components: {
XSub XSub
}, },
props: ['post'], props: ['post'],
data() { data() {
return { return {
isDetailOpened: false, isDetailOpened: false,
connection: null, connection: null,
connectionId: null, connectionId: null
urls: []
}; };
}, },
computed: { computed: {
acct() { acct(): string {
return getAcct(this.p.user); return getAcct(this.p.user);
}, },
isRepost(): boolean { isRepost(): boolean {
@ -141,14 +145,26 @@ export default Vue.extend({
}, },
url(): string { url(): string {
return `/@${this.acct}/${this.p.id}`; 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() { created() {
if ((this as any).os.isSignedIn) { if ((this as any).os.isSignedIn) {
this.connection = (this as any).os.stream.getConnection(); this.connection = (this as any).os.stream.getConnection();
this.connectionId = (this as any).os.stream.use(); this.connectionId = (this as any).os.stream.use();
} }
}, },
mounted() { mounted() {
this.capture(true); this.capture(true);
@ -174,6 +190,7 @@ export default Vue.extend({
} }
} }
}, },
beforeDestroy() { beforeDestroy() {
this.decapture(true); this.decapture(true);
@ -182,21 +199,7 @@ export default Vue.extend({
(this as any).os.stream.dispose(this.connectionId); (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: { methods: {
capture(withHandler = false) { capture(withHandler = false) {
if ((this as any).os.isSignedIn) { if ((this as any).os.isSignedIn) {
@ -457,7 +460,7 @@ export default Vue.extend({
font-size 1.1em font-size 1.1em
color #717171 color #717171
>>> blockquote >>> .quote
margin 8px margin 8px
padding 6px 12px padding 6px 12px
color #aaa color #aaa

View file

@ -2,7 +2,7 @@
<div class="mk-sub-post-content"> <div class="mk-sub-post-content">
<div class="body"> <div class="body">
<a class="reply" v-if="post.replyId">%fa:reply%</a> <a class="reply" v-if="post.replyId">%fa:reply%</a>
<mk-post-html ref="text" :html="post.textHtml" :i="os.i"/> <mk-post-html :text="post.text" :i="os.i"/>
<a class="rp" v-if="post.repostId" :href="`/post:${post.repostId}`">RP: ...</a> <a class="rp" v-if="post.repostId" :href="`/post:${post.repostId}`">RP: ...</a>
</div> </div>
<details v-if="post.media.length > 0"> <details v-if="post.media.length > 0">

View file

@ -95,7 +95,7 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.header root(isDark)
position -webkit-sticky position -webkit-sticky
position sticky position sticky
top 0 top 0
@ -112,7 +112,7 @@ export default Vue.extend({
z-index 1000 z-index 1000
width 100% width 100%
height 48px height 48px
background #f7f7f7 background isDark ? #313543 : #f7f7f7
> .main > .main
z-index 1001 z-index 1001
@ -169,4 +169,10 @@ export default Vue.extend({
> .mk-ui-header-search > .mk-ui-header-search
display none display none
.header[data-is-darkmode]
root(true)
.header
root(false)
</style> </style>

View file

@ -81,6 +81,8 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import getAcct from '../../../../../common/user/get-acct'; import getAcct from '../../../../../common/user/get-acct';
import parse from '../../../../../common/text/parse';
import MkPostMenu from '../../../common/views/components/post-menu.vue'; import MkPostMenu from '../../../common/views/components/post-menu.vue';
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
import XSub from './post-detail.sub.vue'; import XSub from './post-detail.sub.vue';
@ -89,6 +91,7 @@ export default Vue.extend({
components: { components: {
XSub XSub
}, },
props: { props: {
post: { post: {
type: Object, type: Object,
@ -98,19 +101,20 @@ export default Vue.extend({
default: false default: false
} }
}, },
data() { data() {
return { return {
context: [], context: [],
contextFetching: false, contextFetching: false,
replies: [], replies: []
urls: []
}; };
}, },
computed: { computed: {
acct() { acct(): string {
return getAcct(this.post.user); return getAcct(this.post.user);
}, },
pAcct() { pAcct(): string {
return getAcct(this.p.user); return getAcct(this.p.user);
}, },
isRepost(): boolean { isRepost(): boolean {
@ -128,8 +132,19 @@ export default Vue.extend({
.map(key => this.p.reactionCounts[key]) .map(key => this.p.reactionCounts[key])
.reduce((a, b) => a + b) .reduce((a, b) => a + b)
: 0; : 0;
},
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() { mounted() {
// Get replies // Get replies
if (!this.compact) { if (!this.compact) {
@ -159,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: { methods: {
fetchContext() { fetchContext() {
this.contextFetching = true; this.contextFetching = true;

View file

@ -37,7 +37,7 @@
<a class="reply" v-if="p.reply"> <a class="reply" v-if="p.reply">
%fa:reply% %fa:reply%
</a> </a>
<mk-post-html v-if="p.text" ref="text" :text="p.text" :i="os.i" :class="$style.text"/> <mk-post-html v-if="p.text" :text="p.text" :i="os.i" :class="$style.text"/>
<a class="rp" v-if="p.repost != null">RP:</a> <a class="rp" v-if="p.repost != null">RP:</a>
</div> </div>
<div class="media" v-if="p.media.length > 0"> <div class="media" v-if="p.media.length > 0">
@ -78,6 +78,8 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import getAcct from '../../../../../common/user/get-acct'; import getAcct from '../../../../../common/user/get-acct';
import parse from '../../../../../common/text/parse';
import MkPostMenu from '../../../common/views/components/post-menu.vue'; import MkPostMenu from '../../../common/views/components/post-menu.vue';
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
import XSub from './post.sub.vue'; import XSub from './post.sub.vue';
@ -86,19 +88,21 @@ export default Vue.extend({
components: { components: {
XSub XSub
}, },
props: ['post'], props: ['post'],
data() { data() {
return { return {
connection: null, connection: null,
connectionId: null, connectionId: null
urls: []
}; };
}, },
computed: { computed: {
acct() { acct(): string {
return getAcct(this.post.user); return getAcct(this.post.user);
}, },
pAcct() { pAcct(): string {
return getAcct(this.p.user); return getAcct(this.p.user);
}, },
isRepost(): boolean { isRepost(): boolean {
@ -119,14 +123,26 @@ export default Vue.extend({
}, },
url(): string { url(): string {
return `/@${this.pAcct}/${this.p.id}`; return `/@${this.pAcct}/${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() { created() {
if ((this as any).os.isSignedIn) { if ((this as any).os.isSignedIn) {
this.connection = (this as any).os.stream.getConnection(); this.connection = (this as any).os.stream.getConnection();
this.connectionId = (this as any).os.stream.use(); this.connectionId = (this as any).os.stream.use();
} }
}, },
mounted() { mounted() {
this.capture(true); this.capture(true);
@ -152,6 +168,7 @@ export default Vue.extend({
} }
} }
}, },
beforeDestroy() { beforeDestroy() {
this.decapture(true); this.decapture(true);
@ -160,21 +177,7 @@ export default Vue.extend({
(this as any).os.stream.dispose(this.connectionId); (this as any).os.stream.dispose(this.connectionId);
} }
}, },
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: { methods: {
capture(withHandler = false) { capture(withHandler = false) {
if ((this as any).os.isSignedIn) { if ((this as any).os.isSignedIn) {
@ -396,7 +399,7 @@ export default Vue.extend({
font-size 1.1em font-size 1.1em
color #717171 color #717171
>>> blockquote >>> .quote
margin 8px margin 8px
padding 6px 12px padding 6px 12px
color #aaa color #aaa

View file

@ -2,7 +2,7 @@
<div class="mk-sub-post-content"> <div class="mk-sub-post-content">
<div class="body"> <div class="body">
<a class="reply" v-if="post.replyId">%fa:reply%</a> <a class="reply" v-if="post.replyId">%fa:reply%</a>
<mk-post-html v-if="post.text" :ast="post.text" :i="os.i"/> <mk-post-html v-if="post.text" :text="post.text" :i="os.i"/>
<a class="rp" v-if="post.repostId">RP: ...</a> <a class="rp" v-if="post.repostId">RP: ...</a>
</div> </div>
<details v-if="post.media.length > 0"> <details v-if="post.media.length > 0">

View file

@ -101,7 +101,7 @@ gulp.task('doc:api:endpoints', async () => {
} }
//console.log(files); //console.log(files);
files.forEach(file => { files.forEach(file => {
const ep = yaml.safeLoad(fs.readFileSync(file, 'utf-8')); const ep: any = yaml.safeLoad(fs.readFileSync(file, 'utf-8'));
const vars = { const vars = {
endpoint: ep.endpoint, endpoint: ep.endpoint,
url: { url: {

View file

@ -0,0 +1,5 @@
export default [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
{ Hashtag: 'as:Hashtag' }
];

View file

@ -0,0 +1,7 @@
import config from '../../../../conf';
export default ({ _id, contentType }) => ({
type: 'Document',
mediaType: contentType,
url: `${config.drive_url}/${_id}`
});

View file

@ -0,0 +1,7 @@
import config from '../../../../conf';
export default tag => ({
type: 'Hashtag',
href: `${config.url}/search?q=#${encodeURIComponent(tag)}`,
name: '#' + tag
});

View file

@ -0,0 +1,6 @@
import config from '../../../../conf';
export default ({ _id }) => ({
type: 'Image',
url: `${config.drive_url}/${_id}`
});

View file

@ -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)
});

View file

@ -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)
};
};

View file

@ -0,0 +1,6 @@
export default (id, totalItems, orderedItems) => ({
id,
type: 'OrderedCollection',
totalItems,
orderedItems
});

View file

@ -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)
};
};

View file

@ -62,6 +62,10 @@ export default async (value, usernameLower, hostLower, acctLower) => {
host: toUnicode(finger.subject.replace(/^.*?@/, '')), host: toUnicode(finger.subject.replace(/^.*?@/, '')),
hostLower, hostLower,
account: { account: {
publicKey: {
id: object.publicKey.id,
publicKeyPem: object.publicKey.publicKeyPem
},
uri: object.id, uri: object.id,
}, },
}); });

View file

@ -14,7 +14,7 @@ const elements = [
require('./elements/emoji') require('./elements/emoji')
]; ];
export default (source: string) => { export default (source: string): any[] => {
if (source == '') { if (source == '') {
return null; return null;

1
src/crypto_key.d.ts vendored
View file

@ -1 +1,2 @@
export function extractPublic(keypair: String): String;
export function generate(): String; export function generate(): String;

View file

@ -30,6 +30,7 @@ export type IPost = {
repostId: mongo.ObjectID; repostId: mongo.ObjectID;
poll: any; // todo poll: any; // todo
text: string; text: string;
tags: string[];
textHtml: string; textHtml: string;
cw: string; cw: string;
userId: mongo.ObjectID; userId: mongo.ObjectID;

View file

@ -71,6 +71,10 @@ export type ILocalAccount = {
export type IRemoteAccount = { export type IRemoteAccount = {
uri: string; uri: string;
publicKey: {
id: string;
publicKeyPem: string;
};
}; };
export type IUser = { export type IUser = {
@ -278,61 +282,6 @@ export const pack = (
resolve(_user); resolve(_user);
}); });
/**
* Pack a user for ActivityPub
*
* @param user target
* @return Packed user
*/
export const packForAp = (
user: string | mongo.ObjectID | IUser
) => new Promise<any>(async (resolve, reject) => {
let _user: any;
const fields = {
// something
};
// Populate the user if 'user' is ID
if (mongo.ObjectID.prototype.isPrototypeOf(user)) {
_user = await User.findOne({
_id: user
}, { fields });
} else if (typeof user === 'string') {
_user = await User.findOne({
_id: new mongo.ObjectID(user)
}, { fields });
} else {
_user = deepcopy(user);
}
if (!_user) return reject('invalid user arg.');
const userUrl = `${config.url}/@@${_user._id}`;
resolve({
"@context": ["https://www.w3.org/ns/activitystreams", {
"@language": "ja"
}],
"type": "Person",
"id": userUrl,
"following": `${userUrl}/following.json`,
"followers": `${userUrl}/followers.json`,
"liked": `${userUrl}/liked.json`,
"inbox": `${userUrl}/inbox.json`,
"outbox": `${userUrl}/outbox.json`,
"sharedInbox": `${config.url}/inbox`,
"url": `${config.url}/@${_user.username}`,
"preferredUsername": _user.username,
"name": _user.name,
"summary": _user.description,
"icon": [
`${config.drive_url}/${_user.avatarId}`
]
});
});
/* /*
function img(url) { function img(url) {
return { return {

View file

@ -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.post('/@: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.status(202).end();
});
export default app;

View file

@ -0,0 +1,16 @@
import * as express from 'express';
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(user);
app.use(inbox);
app.use(outbox);
app.use(post);
export default app;

View file

@ -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;

View file

@ -0,0 +1,44 @@
import * as express from 'express';
import context from '../../common/remote/activitypub/renderer/context';
import render from '../../common/remote/activitypub/renderer/note';
import parseAcct from '../../common/user/parse-acct';
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 any[]).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 rendered = await render(user, post);
rendered['@context'] = context;
res.json(rendered);
});
export default app;

View file

@ -0,0 +1,40 @@
import * as express from 'express';
import config from '../../conf';
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 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'] as Array<any>).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);
}
if (username !== user.username) {
return res.redirect(`${config.url}/@${user.username}`);
}
const rendered = render(user);
rendered['@context'] = context;
res.json(rendered);
});
export default app;

View file

@ -26,7 +26,7 @@ module.exports = (params, me) => new Promise(async (res, rej) => {
if (usernameErr) return rej('invalid username param'); if (usernameErr) return rej('invalid username param');
// Get 'host' parameter // Get 'host' parameter
const [host, hostErr] = $(params.host).optional.string().$; const [host, hostErr] = $(params.host).nullable.optional.string().$;
if (hostErr) return rej('invalid host param'); if (hostErr) return rej('invalid host param');
if (userId === undefined && typeof username !== 'string') { if (userId === undefined && typeof username !== 'string') {

View file

@ -9,6 +9,8 @@ import * as express from 'express';
import * as morgan from 'morgan'; import * as morgan from 'morgan';
import Accesses from 'accesses'; import Accesses from 'accesses';
import activityPub from './activitypub';
import webFinger from './webfinger';
import log from './log-request'; import log from './log-request';
import config from '../conf'; import config from '../conf';
@ -53,6 +55,8 @@ app.use((req, res, next) => {
*/ */
app.use('/api', require('./api')); app.use('/api', require('./api'));
app.use('/files', require('./file')); app.use('/files', require('./file'));
app.use(activityPub);
app.use(webFinger);
app.use(require('./web')); app.use(require('./web'));
function createServer() { function createServer() {

47
src/server/webfinger.ts Normal file
View file

@ -0,0 +1,47 @@
import config from '../conf';
import parseAcct from '../common/user/parse-acct';
import User from '../models/user';
const express = require('express');
const app = express();
app.get('/.well-known/webfinger', async (req, res) => {
if (typeof req.query.resource !== 'string') {
return res.sendStatus(400);
}
const resourceLower = req.query.resource.toLowerCase();
const webPrefix = config.url.toLowerCase() + '/@';
let acctLower;
if (resourceLower.startsWith(webPrefix)) {
acctLower = resourceLower.slice(webPrefix.length);
} else if (resourceLower.startsWith('acct:')) {
acctLower = resourceLower.slice('acct:'.length);
} else {
acctLower = resourceLower;
}
const parsedAcctLower = parseAcct(acctLower);
if (![null, config.host.toLowerCase()].includes(parsedAcctLower.host)) {
return res.sendStatus(422);
}
const user = await User.findOne({ usernameLower: parsedAcctLower.username, host: null });
if (user === null) {
return res.sendStatus(404);
}
return res.json({
subject: `acct:${user.username}@${config.host}`,
links: [
{
rel: 'self',
type: 'application/activity+json',
href: `${config.url}/@${user.username}`
}
]
});
});
export default app;

View file

@ -17,7 +17,7 @@ const should = _chai.should();
_chai.use(chaiHttp); _chai.use(chaiHttp);
const server = require('../built/server/api/server'); const server = require('../built/server/api');
const db = require('../built/db/mongodb').default; const db = require('../built/db/mongodb').default;
const async = fn => (done) => { const async = fn => (done) => {
@ -46,12 +46,12 @@ describe('API', () => {
beforeEach(() => Promise.all([ beforeEach(() => Promise.all([
db.get('users').drop(), db.get('users').drop(),
db.get('posts').drop(), db.get('posts').drop(),
db.get('drive_files.files').drop(), db.get('driveFiles.files').drop(),
db.get('drive_files.chunks').drop(), db.get('driveFiles.chunks').drop(),
db.get('drive_folders').drop(), db.get('driveFolders').drop(),
db.get('apps').drop(), db.get('apps').drop(),
db.get('access_tokens').drop(), db.get('accessTokens').drop(),
db.get('auth_sessions').drop() db.get('authSessions').drop()
])); ]));
it('greet server', done => { it('greet server', done => {
@ -195,7 +195,7 @@ describe('API', () => {
it('ユーザーが取得できる', async(async () => { it('ユーザーが取得できる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/users/show', { const res = await request('/users/show', {
user_id: me._id.toString() userId: me._id.toString()
}, me); }, me);
res.should.have.status(200); res.should.have.status(200);
res.body.should.be.a('object'); res.body.should.be.a('object');
@ -204,14 +204,14 @@ describe('API', () => {
it('ユーザーが存在しなかったら怒る', async(async () => { it('ユーザーが存在しなかったら怒る', async(async () => {
const res = await request('/users/show', { const res = await request('/users/show', {
user_id: '000000000000000000000000' userId: '000000000000000000000000'
}); });
res.should.have.status(400); res.should.have.status(400);
})); }));
it('間違ったIDで怒られる', async(async () => { it('間違ったIDで怒られる', async(async () => {
const res = await request('/users/show', { const res = await request('/users/show', {
user_id: 'kyoppie' userId: 'kyoppie'
}); });
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -226,32 +226,32 @@ describe('API', () => {
const res = await request('/posts/create', post, me); const res = await request('/posts/create', post, me);
res.should.have.status(200); res.should.have.status(200);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('created_post'); res.body.should.have.property('createdPost');
res.body.created_post.should.have.property('text').eql(post.text); res.body.createdPost.should.have.property('text').eql(post.text);
})); }));
it('ファイルを添付できる', async(async () => { it('ファイルを添付できる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const file = await insertDriveFile({ const file = await insertDriveFile({
user_id: me._id userId: me._id
}); });
const res = await request('/posts/create', { const res = await request('/posts/create', {
media_ids: [file._id.toString()] mediaIds: [file._id.toString()]
}, me); }, me);
res.should.have.status(200); res.should.have.status(200);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('created_post'); res.body.should.have.property('createdPost');
res.body.created_post.should.have.property('media_ids').eql([file._id.toString()]); res.body.createdPost.should.have.property('mediaIds').eql([file._id.toString()]);
})); }));
it('他人のファイルは添付できない', async(async () => { it('他人のファイルは添付できない', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const hima = await insertHimawari(); const hima = await insertHimawari();
const file = await insertDriveFile({ const file = await insertDriveFile({
user_id: hima._id userId: hima._id
}); });
const res = await request('/posts/create', { const res = await request('/posts/create', {
media_ids: [file._id.toString()] mediaIds: [file._id.toString()]
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -259,7 +259,7 @@ describe('API', () => {
it('存在しないファイルは添付できない', async(async () => { it('存在しないファイルは添付できない', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/posts/create', { const res = await request('/posts/create', {
media_ids: ['000000000000000000000000'] mediaIds: ['000000000000000000000000']
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -267,7 +267,7 @@ describe('API', () => {
it('不正なファイルIDで怒られる', async(async () => { it('不正なファイルIDで怒られる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/posts/create', { const res = await request('/posts/create', {
media_ids: ['kyoppie'] mediaIds: ['kyoppie']
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -275,65 +275,65 @@ describe('API', () => {
it('返信できる', async(async () => { it('返信できる', async(async () => {
const hima = await insertHimawari(); const hima = await insertHimawari();
const himaPost = await db.get('posts').insert({ const himaPost = await db.get('posts').insert({
user_id: hima._id, userId: hima._id,
text: 'ひま' text: 'ひま'
}); });
const me = await insertSakurako(); const me = await insertSakurako();
const post = { const post = {
text: 'さく', text: 'さく',
reply_id: himaPost._id.toString() replyId: himaPost._id.toString()
}; };
const res = await request('/posts/create', post, me); const res = await request('/posts/create', post, me);
res.should.have.status(200); res.should.have.status(200);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('created_post'); res.body.should.have.property('createdPost');
res.body.created_post.should.have.property('text').eql(post.text); res.body.createdPost.should.have.property('text').eql(post.text);
res.body.created_post.should.have.property('reply_id').eql(post.reply_id); res.body.createdPost.should.have.property('replyId').eql(post.replyId);
res.body.created_post.should.have.property('reply'); res.body.createdPost.should.have.property('reply');
res.body.created_post.reply.should.have.property('text').eql(himaPost.text); res.body.createdPost.reply.should.have.property('text').eql(himaPost.text);
})); }));
it('repostできる', async(async () => { it('repostできる', async(async () => {
const hima = await insertHimawari(); const hima = await insertHimawari();
const himaPost = await db.get('posts').insert({ const himaPost = await db.get('posts').insert({
user_id: hima._id, userId: hima._id,
text: 'こらっさくらこ!' text: 'こらっさくらこ!'
}); });
const me = await insertSakurako(); const me = await insertSakurako();
const post = { const post = {
repost_id: himaPost._id.toString() repostId: himaPost._id.toString()
}; };
const res = await request('/posts/create', post, me); const res = await request('/posts/create', post, me);
res.should.have.status(200); res.should.have.status(200);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('created_post'); res.body.should.have.property('createdPost');
res.body.created_post.should.have.property('repost_id').eql(post.repost_id); res.body.createdPost.should.have.property('repostId').eql(post.repostId);
res.body.created_post.should.have.property('repost'); res.body.createdPost.should.have.property('repost');
res.body.created_post.repost.should.have.property('text').eql(himaPost.text); res.body.createdPost.repost.should.have.property('text').eql(himaPost.text);
})); }));
it('引用repostできる', async(async () => { it('引用repostできる', async(async () => {
const hima = await insertHimawari(); const hima = await insertHimawari();
const himaPost = await db.get('posts').insert({ const himaPost = await db.get('posts').insert({
user_id: hima._id, userId: hima._id,
text: 'こらっさくらこ!' text: 'こらっさくらこ!'
}); });
const me = await insertSakurako(); const me = await insertSakurako();
const post = { const post = {
text: 'さく', text: 'さく',
repost_id: himaPost._id.toString() repostId: himaPost._id.toString()
}; };
const res = await request('/posts/create', post, me); const res = await request('/posts/create', post, me);
res.should.have.status(200); res.should.have.status(200);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('created_post'); res.body.should.have.property('createdPost');
res.body.created_post.should.have.property('text').eql(post.text); res.body.createdPost.should.have.property('text').eql(post.text);
res.body.created_post.should.have.property('repost_id').eql(post.repost_id); res.body.createdPost.should.have.property('repostId').eql(post.repostId);
res.body.created_post.should.have.property('repost'); res.body.createdPost.should.have.property('repost');
res.body.created_post.repost.should.have.property('text').eql(himaPost.text); res.body.createdPost.repost.should.have.property('text').eql(himaPost.text);
})); }));
it('文字数ぎりぎりで怒られない', async(async () => { it('文字数ぎりぎりで怒られない', async(async () => {
@ -358,7 +358,7 @@ describe('API', () => {
const me = await insertSakurako(); const me = await insertSakurako();
const post = { const post = {
text: 'さく', text: 'さく',
reply_id: '000000000000000000000000' replyId: '000000000000000000000000'
}; };
const res = await request('/posts/create', post, me); const res = await request('/posts/create', post, me);
res.should.have.status(400); res.should.have.status(400);
@ -367,7 +367,7 @@ describe('API', () => {
it('存在しないrepost対象で怒られる', async(async () => { it('存在しないrepost対象で怒られる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const post = { const post = {
repost_id: '000000000000000000000000' repostId: '000000000000000000000000'
}; };
const res = await request('/posts/create', post, me); const res = await request('/posts/create', post, me);
res.should.have.status(400); res.should.have.status(400);
@ -377,7 +377,7 @@ describe('API', () => {
const me = await insertSakurako(); const me = await insertSakurako();
const post = { const post = {
text: 'さく', text: 'さく',
reply_id: 'kyoppie' replyId: 'kyoppie'
}; };
const res = await request('/posts/create', post, me); const res = await request('/posts/create', post, me);
res.should.have.status(400); res.should.have.status(400);
@ -386,7 +386,7 @@ describe('API', () => {
it('不正なrepost対象IDで怒られる', async(async () => { it('不正なrepost対象IDで怒られる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const post = { const post = {
repost_id: 'kyoppie' repostId: 'kyoppie'
}; };
const res = await request('/posts/create', post, me); const res = await request('/posts/create', post, me);
res.should.have.status(400); res.should.have.status(400);
@ -402,8 +402,8 @@ describe('API', () => {
}, me); }, me);
res.should.have.status(200); res.should.have.status(200);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('created_post'); res.body.should.have.property('createdPost');
res.body.created_post.should.have.property('poll'); res.body.createdPost.should.have.property('poll');
})); }));
it('投票の選択肢が無くて怒られる', async(async () => { it('投票の選択肢が無くて怒られる', async(async () => {
@ -439,11 +439,11 @@ describe('API', () => {
it('投稿が取得できる', async(async () => { it('投稿が取得できる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const myPost = await db.get('posts').insert({ const myPost = await db.get('posts').insert({
user_id: me._id, userId: me._id,
text: 'お腹ペコい' text: 'お腹ペコい'
}); });
const res = await request('/posts/show', { const res = await request('/posts/show', {
post_id: myPost._id.toString() postId: myPost._id.toString()
}, me); }, me);
res.should.have.status(200); res.should.have.status(200);
res.body.should.be.a('object'); res.body.should.be.a('object');
@ -452,14 +452,14 @@ describe('API', () => {
it('投稿が存在しなかったら怒る', async(async () => { it('投稿が存在しなかったら怒る', async(async () => {
const res = await request('/posts/show', { const res = await request('/posts/show', {
post_id: '000000000000000000000000' postId: '000000000000000000000000'
}); });
res.should.have.status(400); res.should.have.status(400);
})); }));
it('間違ったIDで怒られる', async(async () => { it('間違ったIDで怒られる', async(async () => {
const res = await request('/posts/show', { const res = await request('/posts/show', {
post_id: 'kyoppie' postId: 'kyoppie'
}); });
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -469,13 +469,13 @@ describe('API', () => {
it('リアクションできる', async(async () => { it('リアクションできる', async(async () => {
const hima = await insertHimawari(); const hima = await insertHimawari();
const himaPost = await db.get('posts').insert({ const himaPost = await db.get('posts').insert({
user_id: hima._id, userId: hima._id,
text: 'ひま' text: 'ひま'
}); });
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/posts/reactions/create', { const res = await request('/posts/reactions/create', {
post_id: himaPost._id.toString(), postId: himaPost._id.toString(),
reaction: 'like' reaction: 'like'
}, me); }, me);
res.should.have.status(204); res.should.have.status(204);
@ -484,12 +484,12 @@ describe('API', () => {
it('自分の投稿にはリアクションできない', async(async () => { it('自分の投稿にはリアクションできない', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const myPost = await db.get('posts').insert({ const myPost = await db.get('posts').insert({
user_id: me._id, userId: me._id,
text: 'お腹ペコい' text: 'お腹ペコい'
}); });
const res = await request('/posts/reactions/create', { const res = await request('/posts/reactions/create', {
post_id: myPost._id.toString(), postId: myPost._id.toString(),
reaction: 'like' reaction: 'like'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
@ -498,19 +498,19 @@ describe('API', () => {
it('二重にリアクションできない', async(async () => { it('二重にリアクションできない', async(async () => {
const hima = await insertHimawari(); const hima = await insertHimawari();
const himaPost = await db.get('posts').insert({ const himaPost = await db.get('posts').insert({
user_id: hima._id, userId: hima._id,
text: 'ひま' text: 'ひま'
}); });
const me = await insertSakurako(); const me = await insertSakurako();
await db.get('post_reactions').insert({ await db.get('postReactions').insert({
user_id: me._id, userId: me._id,
post_id: himaPost._id, postId: himaPost._id,
reaction: 'like' reaction: 'like'
}); });
const res = await request('/posts/reactions/create', { const res = await request('/posts/reactions/create', {
post_id: himaPost._id.toString(), postId: himaPost._id.toString(),
reaction: 'like' reaction: 'like'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
@ -519,7 +519,7 @@ describe('API', () => {
it('存在しない投稿にはリアクションできない', async(async () => { it('存在しない投稿にはリアクションできない', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/posts/reactions/create', { const res = await request('/posts/reactions/create', {
post_id: '000000000000000000000000', postId: '000000000000000000000000',
reaction: 'like' reaction: 'like'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
@ -534,7 +534,7 @@ describe('API', () => {
it('間違ったIDで怒られる', async(async () => { it('間違ったIDで怒られる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/posts/reactions/create', { const res = await request('/posts/reactions/create', {
post_id: 'kyoppie', postId: 'kyoppie',
reaction: 'like' reaction: 'like'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
@ -545,19 +545,19 @@ describe('API', () => {
it('リアクションをキャンセルできる', async(async () => { it('リアクションをキャンセルできる', async(async () => {
const hima = await insertHimawari(); const hima = await insertHimawari();
const himaPost = await db.get('posts').insert({ const himaPost = await db.get('posts').insert({
user_id: hima._id, userId: hima._id,
text: 'ひま' text: 'ひま'
}); });
const me = await insertSakurako(); const me = await insertSakurako();
await db.get('post_reactions').insert({ await db.get('postReactions').insert({
user_id: me._id, userId: me._id,
post_id: himaPost._id, postId: himaPost._id,
reaction: 'like' reaction: 'like'
}); });
const res = await request('/posts/reactions/delete', { const res = await request('/posts/reactions/delete', {
post_id: himaPost._id.toString() postId: himaPost._id.toString()
}, me); }, me);
res.should.have.status(204); res.should.have.status(204);
})); }));
@ -565,13 +565,13 @@ describe('API', () => {
it('リアクションしていない投稿はリアクションをキャンセルできない', async(async () => { it('リアクションしていない投稿はリアクションをキャンセルできない', async(async () => {
const hima = await insertHimawari(); const hima = await insertHimawari();
const himaPost = await db.get('posts').insert({ const himaPost = await db.get('posts').insert({
user_id: hima._id, userId: hima._id,
text: 'ひま' text: 'ひま'
}); });
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/posts/reactions/delete', { const res = await request('/posts/reactions/delete', {
post_id: himaPost._id.toString() postId: himaPost._id.toString()
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -579,7 +579,7 @@ describe('API', () => {
it('存在しない投稿はリアクションをキャンセルできない', async(async () => { it('存在しない投稿はリアクションをキャンセルできない', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/posts/reactions/delete', { const res = await request('/posts/reactions/delete', {
post_id: '000000000000000000000000' postId: '000000000000000000000000'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -593,7 +593,7 @@ describe('API', () => {
it('間違ったIDで怒られる', async(async () => { it('間違ったIDで怒られる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/posts/reactions/delete', { const res = await request('/posts/reactions/delete', {
post_id: 'kyoppie' postId: 'kyoppie'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -604,7 +604,7 @@ describe('API', () => {
const hima = await insertHimawari(); const hima = await insertHimawari();
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/following/create', { const res = await request('/following/create', {
user_id: hima._id.toString() userId: hima._id.toString()
}, me); }, me);
res.should.have.status(204); res.should.have.status(204);
})); }));
@ -613,12 +613,12 @@ describe('API', () => {
const hima = await insertHimawari(); const hima = await insertHimawari();
const me = await insertSakurako(); const me = await insertSakurako();
await db.get('following').insert({ await db.get('following').insert({
followee_id: hima._id, followeeId: hima._id,
follower_id: me._id, followerId: me._id,
deleted_at: new Date() deletedAt: new Date()
}); });
const res = await request('/following/create', { const res = await request('/following/create', {
user_id: hima._id.toString() userId: hima._id.toString()
}, me); }, me);
res.should.have.status(204); res.should.have.status(204);
})); }));
@ -627,11 +627,11 @@ describe('API', () => {
const hima = await insertHimawari(); const hima = await insertHimawari();
const me = await insertSakurako(); const me = await insertSakurako();
await db.get('following').insert({ await db.get('following').insert({
followee_id: hima._id, followeeId: hima._id,
follower_id: me._id followerId: me._id
}); });
const res = await request('/following/create', { const res = await request('/following/create', {
user_id: hima._id.toString() userId: hima._id.toString()
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -639,7 +639,7 @@ describe('API', () => {
it('存在しないユーザーはフォローできない', async(async () => { it('存在しないユーザーはフォローできない', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/following/create', { const res = await request('/following/create', {
user_id: '000000000000000000000000' userId: '000000000000000000000000'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -647,7 +647,7 @@ describe('API', () => {
it('自分自身はフォローできない', async(async () => { it('自分自身はフォローできない', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/following/create', { const res = await request('/following/create', {
user_id: me._id.toString() userId: me._id.toString()
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -661,7 +661,7 @@ describe('API', () => {
it('間違ったIDで怒られる', async(async () => { it('間違ったIDで怒られる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/following/create', { const res = await request('/following/create', {
user_id: 'kyoppie' userId: 'kyoppie'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -672,11 +672,11 @@ describe('API', () => {
const hima = await insertHimawari(); const hima = await insertHimawari();
const me = await insertSakurako(); const me = await insertSakurako();
await db.get('following').insert({ await db.get('following').insert({
followee_id: hima._id, followeeId: hima._id,
follower_id: me._id followerId: me._id
}); });
const res = await request('/following/delete', { const res = await request('/following/delete', {
user_id: hima._id.toString() userId: hima._id.toString()
}, me); }, me);
res.should.have.status(204); res.should.have.status(204);
})); }));
@ -685,16 +685,16 @@ describe('API', () => {
const hima = await insertHimawari(); const hima = await insertHimawari();
const me = await insertSakurako(); const me = await insertSakurako();
await db.get('following').insert({ await db.get('following').insert({
followee_id: hima._id, followeeId: hima._id,
follower_id: me._id, followerId: me._id,
deleted_at: new Date() deletedAt: new Date()
}); });
await db.get('following').insert({ await db.get('following').insert({
followee_id: hima._id, followeeId: hima._id,
follower_id: me._id followerId: me._id
}); });
const res = await request('/following/delete', { const res = await request('/following/delete', {
user_id: hima._id.toString() userId: hima._id.toString()
}, me); }, me);
res.should.have.status(204); res.should.have.status(204);
})); }));
@ -703,7 +703,7 @@ describe('API', () => {
const hima = await insertHimawari(); const hima = await insertHimawari();
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/following/delete', { const res = await request('/following/delete', {
user_id: hima._id.toString() userId: hima._id.toString()
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -711,7 +711,7 @@ describe('API', () => {
it('存在しないユーザーはフォロー解除できない', async(async () => { it('存在しないユーザーはフォロー解除できない', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/following/delete', { const res = await request('/following/delete', {
user_id: '000000000000000000000000' userId: '000000000000000000000000'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -719,7 +719,7 @@ describe('API', () => {
it('自分自身はフォロー解除できない', async(async () => { it('自分自身はフォロー解除できない', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/following/delete', { const res = await request('/following/delete', {
user_id: me._id.toString() userId: me._id.toString()
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -733,7 +733,7 @@ describe('API', () => {
it('間違ったIDで怒られる', async(async () => { it('間違ったIDで怒られる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/following/delete', { const res = await request('/following/delete', {
user_id: 'kyoppie' userId: 'kyoppie'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -743,15 +743,15 @@ describe('API', () => {
it('ドライブ情報を取得できる', async(async () => { it('ドライブ情報を取得できる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
await insertDriveFile({ await insertDriveFile({
user_id: me._id, userId: me._id,
datasize: 256 datasize: 256
}); });
await insertDriveFile({ await insertDriveFile({
user_id: me._id, userId: me._id,
datasize: 512 datasize: 512
}); });
await insertDriveFile({ await insertDriveFile({
user_id: me._id, userId: me._id,
datasize: 1024 datasize: 1024
}); });
const res = await request('/drive', {}, me); const res = await request('/drive', {}, me);
@ -784,11 +784,11 @@ describe('API', () => {
it('名前を更新できる', async(async () => { it('名前を更新できる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const file = await insertDriveFile({ const file = await insertDriveFile({
user_id: me._id userId: me._id
}); });
const newName = 'いちごパスタ.png'; const newName = 'いちごパスタ.png';
const res = await request('/drive/files/update', { const res = await request('/drive/files/update', {
file_id: file._id.toString(), fileId: file._id.toString(),
name: newName name: newName
}, me); }, me);
res.should.have.status(200); res.should.have.status(200);
@ -800,10 +800,10 @@ describe('API', () => {
const me = await insertSakurako(); const me = await insertSakurako();
const hima = await insertHimawari(); const hima = await insertHimawari();
const file = await insertDriveFile({ const file = await insertDriveFile({
user_id: hima._id userId: hima._id
}); });
const res = await request('/drive/files/update', { const res = await request('/drive/files/update', {
file_id: file._id.toString(), fileId: file._id.toString(),
name: 'いちごパスタ.png' name: 'いちごパスタ.png'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
@ -812,47 +812,47 @@ describe('API', () => {
it('親フォルダを更新できる', async(async () => { it('親フォルダを更新できる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const file = await insertDriveFile({ const file = await insertDriveFile({
user_id: me._id userId: me._id
}); });
const folder = await insertDriveFolder({ const folder = await insertDriveFolder({
user_id: me._id userId: me._id
}); });
const res = await request('/drive/files/update', { const res = await request('/drive/files/update', {
file_id: file._id.toString(), fileId: file._id.toString(),
folder_id: folder._id.toString() folderId: folder._id.toString()
}, me); }, me);
res.should.have.status(200); res.should.have.status(200);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('folder_id').eql(folder._id.toString()); res.body.should.have.property('folderId').eql(folder._id.toString());
})); }));
it('親フォルダを無しにできる', async(async () => { it('親フォルダを無しにできる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const file = await insertDriveFile({ const file = await insertDriveFile({
user_id: me._id, userId: me._id,
folder_id: '000000000000000000000000' folderId: '000000000000000000000000'
}); });
const res = await request('/drive/files/update', { const res = await request('/drive/files/update', {
file_id: file._id.toString(), fileId: file._id.toString(),
folder_id: null folderId: null
}, me); }, me);
res.should.have.status(200); res.should.have.status(200);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('folder_id').eql(null); res.body.should.have.property('folderId').eql(null);
})); }));
it('他人のフォルダには入れられない', async(async () => { it('他人のフォルダには入れられない', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const hima = await insertHimawari(); const hima = await insertHimawari();
const file = await insertDriveFile({ const file = await insertDriveFile({
user_id: me._id userId: me._id
}); });
const folder = await insertDriveFolder({ const folder = await insertDriveFolder({
user_id: hima._id userId: hima._id
}); });
const res = await request('/drive/files/update', { const res = await request('/drive/files/update', {
file_id: file._id.toString(), fileId: file._id.toString(),
folder_id: folder._id.toString() folderId: folder._id.toString()
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -860,11 +860,11 @@ describe('API', () => {
it('存在しないフォルダで怒られる', async(async () => { it('存在しないフォルダで怒られる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const file = await insertDriveFile({ const file = await insertDriveFile({
user_id: me._id userId: me._id
}); });
const res = await request('/drive/files/update', { const res = await request('/drive/files/update', {
file_id: file._id.toString(), fileId: file._id.toString(),
folder_id: '000000000000000000000000' folderId: '000000000000000000000000'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -872,11 +872,11 @@ describe('API', () => {
it('不正なフォルダIDで怒られる', async(async () => { it('不正なフォルダIDで怒られる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const file = await insertDriveFile({ const file = await insertDriveFile({
user_id: me._id userId: me._id
}); });
const res = await request('/drive/files/update', { const res = await request('/drive/files/update', {
file_id: file._id.toString(), fileId: file._id.toString(),
folder_id: 'kyoppie' folderId: 'kyoppie'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -884,7 +884,7 @@ describe('API', () => {
it('ファイルが存在しなかったら怒る', async(async () => { it('ファイルが存在しなかったら怒る', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/drive/files/update', { const res = await request('/drive/files/update', {
file_id: '000000000000000000000000', fileId: '000000000000000000000000',
name: 'いちごパスタ.png' name: 'いちごパスタ.png'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
@ -893,7 +893,7 @@ describe('API', () => {
it('間違ったIDで怒られる', async(async () => { it('間違ったIDで怒られる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/drive/files/update', { const res = await request('/drive/files/update', {
file_id: 'kyoppie', fileId: 'kyoppie',
name: 'いちごパスタ.png' name: 'いちごパスタ.png'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
@ -916,10 +916,10 @@ describe('API', () => {
it('名前を更新できる', async(async () => { it('名前を更新できる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const folder = await insertDriveFolder({ const folder = await insertDriveFolder({
user_id: me._id userId: me._id
}); });
const res = await request('/drive/folders/update', { const res = await request('/drive/folders/update', {
folder_id: folder._id.toString(), folderId: folder._id.toString(),
name: 'new name' name: 'new name'
}, me); }, me);
res.should.have.status(200); res.should.have.status(200);
@ -931,10 +931,10 @@ describe('API', () => {
const me = await insertSakurako(); const me = await insertSakurako();
const hima = await insertHimawari(); const hima = await insertHimawari();
const folder = await insertDriveFolder({ const folder = await insertDriveFolder({
user_id: hima._id userId: hima._id
}); });
const res = await request('/drive/folders/update', { const res = await request('/drive/folders/update', {
folder_id: folder._id.toString(), folderId: folder._id.toString(),
name: 'new name' name: 'new name'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
@ -943,47 +943,47 @@ describe('API', () => {
it('親フォルダを更新できる', async(async () => { it('親フォルダを更新できる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const folder = await insertDriveFolder({ const folder = await insertDriveFolder({
user_id: me._id userId: me._id
}); });
const parentFolder = await insertDriveFolder({ const parentFolder = await insertDriveFolder({
user_id: me._id userId: me._id
}); });
const res = await request('/drive/folders/update', { const res = await request('/drive/folders/update', {
folder_id: folder._id.toString(), folderId: folder._id.toString(),
parent_id: parentFolder._id.toString() parentId: parentFolder._id.toString()
}, me); }, me);
res.should.have.status(200); res.should.have.status(200);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('parent_id').eql(parentFolder._id.toString()); res.body.should.have.property('parentId').eql(parentFolder._id.toString());
})); }));
it('親フォルダを無しに更新できる', async(async () => { it('親フォルダを無しに更新できる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const folder = await insertDriveFolder({ const folder = await insertDriveFolder({
user_id: me._id, userId: me._id,
parent_id: '000000000000000000000000' parentId: '000000000000000000000000'
}); });
const res = await request('/drive/folders/update', { const res = await request('/drive/folders/update', {
folder_id: folder._id.toString(), folderId: folder._id.toString(),
parent_id: null parentId: null
}, me); }, me);
res.should.have.status(200); res.should.have.status(200);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('parent_id').eql(null); res.body.should.have.property('parentId').eql(null);
})); }));
it('他人のフォルダを親フォルダに設定できない', async(async () => { it('他人のフォルダを親フォルダに設定できない', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const hima = await insertHimawari(); const hima = await insertHimawari();
const folder = await insertDriveFolder({ const folder = await insertDriveFolder({
user_id: me._id userId: me._id
}); });
const parentFolder = await insertDriveFolder({ const parentFolder = await insertDriveFolder({
user_id: hima._id userId: hima._id
}); });
const res = await request('/drive/folders/update', { const res = await request('/drive/folders/update', {
folder_id: folder._id.toString(), folderId: folder._id.toString(),
parent_id: parentFolder._id.toString() parentId: parentFolder._id.toString()
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -992,11 +992,11 @@ describe('API', () => {
const me = await insertSakurako(); const me = await insertSakurako();
const folder = await insertDriveFolder(); const folder = await insertDriveFolder();
const parentFolder = await insertDriveFolder({ const parentFolder = await insertDriveFolder({
parent_id: folder._id parentId: folder._id
}); });
const res = await request('/drive/folders/update', { const res = await request('/drive/folders/update', {
folder_id: folder._id.toString(), folderId: folder._id.toString(),
parent_id: parentFolder._id.toString() parentId: parentFolder._id.toString()
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -1005,14 +1005,14 @@ describe('API', () => {
const me = await insertSakurako(); const me = await insertSakurako();
const folderA = await insertDriveFolder(); const folderA = await insertDriveFolder();
const folderB = await insertDriveFolder({ const folderB = await insertDriveFolder({
parent_id: folderA._id parentId: folderA._id
}); });
const folderC = await insertDriveFolder({ const folderC = await insertDriveFolder({
parent_id: folderB._id parentId: folderB._id
}); });
const res = await request('/drive/folders/update', { const res = await request('/drive/folders/update', {
folder_id: folderA._id.toString(), folderId: folderA._id.toString(),
parent_id: folderC._id.toString() parentId: folderC._id.toString()
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -1021,8 +1021,8 @@ describe('API', () => {
const me = await insertSakurako(); const me = await insertSakurako();
const folder = await insertDriveFolder(); const folder = await insertDriveFolder();
const res = await request('/drive/folders/update', { const res = await request('/drive/folders/update', {
folder_id: folder._id.toString(), folderId: folder._id.toString(),
parent_id: '000000000000000000000000' parentId: '000000000000000000000000'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -1031,8 +1031,8 @@ describe('API', () => {
const me = await insertSakurako(); const me = await insertSakurako();
const folder = await insertDriveFolder(); const folder = await insertDriveFolder();
const res = await request('/drive/folders/update', { const res = await request('/drive/folders/update', {
folder_id: folder._id.toString(), folderId: folder._id.toString(),
parent_id: 'kyoppie' parentId: 'kyoppie'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -1040,7 +1040,7 @@ describe('API', () => {
it('存在しないフォルダを更新できない', async(async () => { it('存在しないフォルダを更新できない', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/drive/folders/update', { const res = await request('/drive/folders/update', {
folder_id: '000000000000000000000000' folderId: '000000000000000000000000'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -1048,7 +1048,7 @@ describe('API', () => {
it('不正なフォルダIDで怒られる', async(async () => { it('不正なフォルダIDで怒られる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/drive/folders/update', { const res = await request('/drive/folders/update', {
folder_id: 'kyoppie' folderId: 'kyoppie'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -1059,7 +1059,7 @@ describe('API', () => {
const me = await insertSakurako(); const me = await insertSakurako();
const hima = await insertHimawari(); const hima = await insertHimawari();
const res = await request('/messaging/messages/create', { const res = await request('/messaging/messages/create', {
user_id: hima._id.toString(), userId: hima._id.toString(),
text: 'Hey hey ひまわり' text: 'Hey hey ひまわり'
}, me); }, me);
res.should.have.status(200); res.should.have.status(200);
@ -1070,7 +1070,7 @@ describe('API', () => {
it('自分自身にはメッセージを送信できない', async(async () => { it('自分自身にはメッセージを送信できない', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/messaging/messages/create', { const res = await request('/messaging/messages/create', {
user_id: me._id.toString(), userId: me._id.toString(),
text: 'Yo' text: 'Yo'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
@ -1079,7 +1079,7 @@ describe('API', () => {
it('存在しないユーザーにはメッセージを送信できない', async(async () => { it('存在しないユーザーにはメッセージを送信できない', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/messaging/messages/create', { const res = await request('/messaging/messages/create', {
user_id: '000000000000000000000000', userId: '000000000000000000000000',
text: 'Yo' text: 'Yo'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
@ -1088,7 +1088,7 @@ describe('API', () => {
it('不正なユーザーIDで怒られる', async(async () => { it('不正なユーザーIDで怒られる', async(async () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await request('/messaging/messages/create', { const res = await request('/messaging/messages/create', {
user_id: 'kyoppie', userId: 'kyoppie',
text: 'Yo' text: 'Yo'
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
@ -1098,7 +1098,7 @@ describe('API', () => {
const me = await insertSakurako(); const me = await insertSakurako();
const hima = await insertHimawari(); const hima = await insertHimawari();
const res = await request('/messaging/messages/create', { const res = await request('/messaging/messages/create', {
user_id: hima._id.toString() userId: hima._id.toString()
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -1107,7 +1107,7 @@ describe('API', () => {
const me = await insertSakurako(); const me = await insertSakurako();
const hima = await insertHimawari(); const hima = await insertHimawari();
const res = await request('/messaging/messages/create', { const res = await request('/messaging/messages/create', {
user_id: hima._id.toString(), userId: hima._id.toString(),
text: '!'.repeat(1001) text: '!'.repeat(1001)
}, me); }, me);
res.should.have.status(400); res.should.have.status(400);
@ -1118,7 +1118,7 @@ describe('API', () => {
it('認証セッションを作成できる', async(async () => { it('認証セッションを作成できる', async(async () => {
const app = await insertApp(); const app = await insertApp();
const res = await request('/auth/session/generate', { const res = await request('/auth/session/generate', {
app_secret: app.secret appSecret: app.secret
}); });
res.should.have.status(200); res.should.have.status(200);
res.body.should.be.a('object'); res.body.should.be.a('object');
@ -1126,14 +1126,14 @@ describe('API', () => {
res.body.should.have.property('url'); res.body.should.have.property('url');
})); }));
it('app_secret 無しで怒られる', async(async () => { it('appSecret 無しで怒られる', async(async () => {
const res = await request('/auth/session/generate', {}); const res = await request('/auth/session/generate', {});
res.should.have.status(400); res.should.have.status(400);
})); }));
it('誤った app secret で怒られる', async(async () => { it('誤った appSecret で怒られる', async(async () => {
const res = await request('/auth/session/generate', { const res = await request('/auth/session/generate', {
app_secret: 'kyoppie' appSecret: 'kyoppie'
}); });
res.should.have.status(400); res.should.have.status(400);
})); }));
@ -1159,14 +1159,14 @@ function deepAssign(destination, ...sources) {
function insertSakurako(opts) { function insertSakurako(opts) {
return db.get('users').insert(deepAssign({ return db.get('users').insert(deepAssign({
username: 'sakurako', username: 'sakurako',
username_lower: 'sakurako', usernameLower: 'sakurako',
account: { account: {
keypair: '-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAtdTG9rlFWjNqhgbg2V6X5XF1WpQXZS3KNXykEWl2UAiMyfVV\nBvf3zQP0dDEdNtcqdPJgis03bpiHCzQusc/YLyHYB0m+TJXsxJatb8cqUogOFeE4\ngQ4Dc5kAT6gLh/d4yz03EIg9bizX07EiGWnZqWxb+21ypqsPxST64sAtG9f5O/G4\nXe2m3cSbfAAvEUP1Ig1LUNyJB4jhM60w1cQic/qO8++sk/+GoX9g71X+i4NArGv+\n1c11acDIIPGAAQpFeYVeGaKakNDNp8RtJJp8R8FLwJXZ4/gATBnScCiHUSrGfRly\nYyR0w/BNlQ6/NijAdB9pR5csPvyIPkx1gauZewIDAQABAoIBAQCwWf/mhuY2h6uG\n9eDZsZ7Mj2/sO7k9Dl4R5iMSKCDxmnlB3slqitExa+aJUqEs8R5icjkkJcjfYNuJ\nCEFJf3YCsGZfGyyQBtCuEh2ATcBEb2SJ3/f3YuoCEaB1oVwdsOzc4TAovpol4yQo\nUqHp1/mdElVb01jhQQN4h1c02IJnfzvfU1C8szBni+Etfd+MxqGfv006DY3KOEb3\nlCrCS3GmooJW2Fjj7q1kCcaEQbMB1/aQHLXd1qe3KJOzXh3Voxsp/jEH0hvp2TII\nfY9UK+b7mA+xlvXwKuTkHVaZm0ylg0nbembS8MF4GfFMujinSexvLrVKaQhdMFoF\nvBLxHYHRAoGBANfNVYJYeCDPFNLmak5Xg33Rfvc2II8UmrZOVdhOWs8ZK0pis9e+\nPo2MKtTzrzipXI2QXv5w7kO+LJWNDva+xRlW8Wlj9Dde9QdQ7Y8+dk7SJgf24DzM\n023elgX5DvTeLODjStk6SMPRL0FmGovUqAAA8ZeHtJzkIr1HROWnQiwnAoGBANez\nhFwKnVoQu0RpBz/i4W0RKIxOwltN2zmlN8KjJPhSy00A7nBUfKLRbcwiSHE98Yi/\nUrXwMwR5QeD2ngngRppddJnpiRfjNjnsaqeqNtpO8AxB3XjpCC5zmHUMFHKvPpDj\n1zU/F44li0YjKcMBebZy9PbfAjrIgJfxhPo/oXiNAoGAfx6gaTjOAp2ZaaZ7Jozc\nkyft/5et1DrR6+P3I4T8bxQncRj1UXfqhxzzOiAVrm3tbCKIIp/JarRCtRGzp9u2\nZPfXGzra6CcSdW3Rkli7/jBCYNynOIl7XjQI8ZnFmq6phwu80ntH07mMeZy4tHff\nQqlLpvQ0i1rDr/Wkexdsnm8CgYBgxha9ILoF/Xm3MJPjEsxmnYsen/tM8XpIu5pv\nxbhBfQvfKWrQlOcyOVnUexEbVVo3KvdVz0VkXW60GpE/BxNGEGXO49rxD6x1gl87\nh/+CJGZIaYiOxaY5CP2+jcPizEL6yG32Yq8TxD5fIkmLRu8vbxX+aIFclDY1dVNe\n3wt3xQKBgGEL0EjwRch+P2V+YHAhbETPrEqJjHRWT95pIdF9XtC8fasSOVH81cLX\nXXsX1FTvOJNwG9Nk8rQjYJXGTb2O/2unaazlYUwxKwVpwuGzz/vhH/roHZBAkIVT\njvpykpn9QMezEdpzj5BEv01QzSYBPzIh5myrpoJIoSW7py7zFG3h\n-----END RSA PRIVATE KEY-----\n', keypair: '-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAtdTG9rlFWjNqhgbg2V6X5XF1WpQXZS3KNXykEWl2UAiMyfVV\nBvf3zQP0dDEdNtcqdPJgis03bpiHCzQusc/YLyHYB0m+TJXsxJatb8cqUogOFeE4\ngQ4Dc5kAT6gLh/d4yz03EIg9bizX07EiGWnZqWxb+21ypqsPxST64sAtG9f5O/G4\nXe2m3cSbfAAvEUP1Ig1LUNyJB4jhM60w1cQic/qO8++sk/+GoX9g71X+i4NArGv+\n1c11acDIIPGAAQpFeYVeGaKakNDNp8RtJJp8R8FLwJXZ4/gATBnScCiHUSrGfRly\nYyR0w/BNlQ6/NijAdB9pR5csPvyIPkx1gauZewIDAQABAoIBAQCwWf/mhuY2h6uG\n9eDZsZ7Mj2/sO7k9Dl4R5iMSKCDxmnlB3slqitExa+aJUqEs8R5icjkkJcjfYNuJ\nCEFJf3YCsGZfGyyQBtCuEh2ATcBEb2SJ3/f3YuoCEaB1oVwdsOzc4TAovpol4yQo\nUqHp1/mdElVb01jhQQN4h1c02IJnfzvfU1C8szBni+Etfd+MxqGfv006DY3KOEb3\nlCrCS3GmooJW2Fjj7q1kCcaEQbMB1/aQHLXd1qe3KJOzXh3Voxsp/jEH0hvp2TII\nfY9UK+b7mA+xlvXwKuTkHVaZm0ylg0nbembS8MF4GfFMujinSexvLrVKaQhdMFoF\nvBLxHYHRAoGBANfNVYJYeCDPFNLmak5Xg33Rfvc2II8UmrZOVdhOWs8ZK0pis9e+\nPo2MKtTzrzipXI2QXv5w7kO+LJWNDva+xRlW8Wlj9Dde9QdQ7Y8+dk7SJgf24DzM\n023elgX5DvTeLODjStk6SMPRL0FmGovUqAAA8ZeHtJzkIr1HROWnQiwnAoGBANez\nhFwKnVoQu0RpBz/i4W0RKIxOwltN2zmlN8KjJPhSy00A7nBUfKLRbcwiSHE98Yi/\nUrXwMwR5QeD2ngngRppddJnpiRfjNjnsaqeqNtpO8AxB3XjpCC5zmHUMFHKvPpDj\n1zU/F44li0YjKcMBebZy9PbfAjrIgJfxhPo/oXiNAoGAfx6gaTjOAp2ZaaZ7Jozc\nkyft/5et1DrR6+P3I4T8bxQncRj1UXfqhxzzOiAVrm3tbCKIIp/JarRCtRGzp9u2\nZPfXGzra6CcSdW3Rkli7/jBCYNynOIl7XjQI8ZnFmq6phwu80ntH07mMeZy4tHff\nQqlLpvQ0i1rDr/Wkexdsnm8CgYBgxha9ILoF/Xm3MJPjEsxmnYsen/tM8XpIu5pv\nxbhBfQvfKWrQlOcyOVnUexEbVVo3KvdVz0VkXW60GpE/BxNGEGXO49rxD6x1gl87\nh/+CJGZIaYiOxaY5CP2+jcPizEL6yG32Yq8TxD5fIkmLRu8vbxX+aIFclDY1dVNe\n3wt3xQKBgGEL0EjwRch+P2V+YHAhbETPrEqJjHRWT95pIdF9XtC8fasSOVH81cLX\nXXsX1FTvOJNwG9Nk8rQjYJXGTb2O/2unaazlYUwxKwVpwuGzz/vhH/roHZBAkIVT\njvpykpn9QMezEdpzj5BEv01QzSYBPzIh5myrpoJIoSW7py7zFG3h\n-----END RSA PRIVATE KEY-----\n',
token: '!00000000000000000000000000000000', token: '!00000000000000000000000000000000',
password: '$2a$08$FnHXg3tP.M/kINWgQSXNqeoBsiVrkj.ecXX8mW9rfBzMRkibYfjYy', // HimawariDaisuki06160907 password: '$2a$08$FnHXg3tP.M/kINWgQSXNqeoBsiVrkj.ecXX8mW9rfBzMRkibYfjYy', // HimawariDaisuki06160907
profile: {}, profile: {},
settings: {}, settings: {},
client_settings: {} clientSettings: {}
} }
}, opts)); }, opts));
} }
@ -1174,20 +1174,20 @@ function insertSakurako(opts) {
function insertHimawari(opts) { function insertHimawari(opts) {
return db.get('users').insert(deepAssign({ return db.get('users').insert(deepAssign({
username: 'himawari', username: 'himawari',
username_lower: 'himawari', usernameLower: 'himawari',
account: { account: {
keypair: '-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAtdTG9rlFWjNqhgbg2V6X5XF1WpQXZS3KNXykEWl2UAiMyfVV\nBvf3zQP0dDEdNtcqdPJgis03bpiHCzQusc/YLyHYB0m+TJXsxJatb8cqUogOFeE4\ngQ4Dc5kAT6gLh/d4yz03EIg9bizX07EiGWnZqWxb+21ypqsPxST64sAtG9f5O/G4\nXe2m3cSbfAAvEUP1Ig1LUNyJB4jhM60w1cQic/qO8++sk/+GoX9g71X+i4NArGv+\n1c11acDIIPGAAQpFeYVeGaKakNDNp8RtJJp8R8FLwJXZ4/gATBnScCiHUSrGfRly\nYyR0w/BNlQ6/NijAdB9pR5csPvyIPkx1gauZewIDAQABAoIBAQCwWf/mhuY2h6uG\n9eDZsZ7Mj2/sO7k9Dl4R5iMSKCDxmnlB3slqitExa+aJUqEs8R5icjkkJcjfYNuJ\nCEFJf3YCsGZfGyyQBtCuEh2ATcBEb2SJ3/f3YuoCEaB1oVwdsOzc4TAovpol4yQo\nUqHp1/mdElVb01jhQQN4h1c02IJnfzvfU1C8szBni+Etfd+MxqGfv006DY3KOEb3\nlCrCS3GmooJW2Fjj7q1kCcaEQbMB1/aQHLXd1qe3KJOzXh3Voxsp/jEH0hvp2TII\nfY9UK+b7mA+xlvXwKuTkHVaZm0ylg0nbembS8MF4GfFMujinSexvLrVKaQhdMFoF\nvBLxHYHRAoGBANfNVYJYeCDPFNLmak5Xg33Rfvc2II8UmrZOVdhOWs8ZK0pis9e+\nPo2MKtTzrzipXI2QXv5w7kO+LJWNDva+xRlW8Wlj9Dde9QdQ7Y8+dk7SJgf24DzM\n023elgX5DvTeLODjStk6SMPRL0FmGovUqAAA8ZeHtJzkIr1HROWnQiwnAoGBANez\nhFwKnVoQu0RpBz/i4W0RKIxOwltN2zmlN8KjJPhSy00A7nBUfKLRbcwiSHE98Yi/\nUrXwMwR5QeD2ngngRppddJnpiRfjNjnsaqeqNtpO8AxB3XjpCC5zmHUMFHKvPpDj\n1zU/F44li0YjKcMBebZy9PbfAjrIgJfxhPo/oXiNAoGAfx6gaTjOAp2ZaaZ7Jozc\nkyft/5et1DrR6+P3I4T8bxQncRj1UXfqhxzzOiAVrm3tbCKIIp/JarRCtRGzp9u2\nZPfXGzra6CcSdW3Rkli7/jBCYNynOIl7XjQI8ZnFmq6phwu80ntH07mMeZy4tHff\nQqlLpvQ0i1rDr/Wkexdsnm8CgYBgxha9ILoF/Xm3MJPjEsxmnYsen/tM8XpIu5pv\nxbhBfQvfKWrQlOcyOVnUexEbVVo3KvdVz0VkXW60GpE/BxNGEGXO49rxD6x1gl87\nh/+CJGZIaYiOxaY5CP2+jcPizEL6yG32Yq8TxD5fIkmLRu8vbxX+aIFclDY1dVNe\n3wt3xQKBgGEL0EjwRch+P2V+YHAhbETPrEqJjHRWT95pIdF9XtC8fasSOVH81cLX\nXXsX1FTvOJNwG9Nk8rQjYJXGTb2O/2unaazlYUwxKwVpwuGzz/vhH/roHZBAkIVT\njvpykpn9QMezEdpzj5BEv01QzSYBPzIh5myrpoJIoSW7py7zFG3h\n-----END RSA PRIVATE KEY-----\n', keypair: '-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAtdTG9rlFWjNqhgbg2V6X5XF1WpQXZS3KNXykEWl2UAiMyfVV\nBvf3zQP0dDEdNtcqdPJgis03bpiHCzQusc/YLyHYB0m+TJXsxJatb8cqUogOFeE4\ngQ4Dc5kAT6gLh/d4yz03EIg9bizX07EiGWnZqWxb+21ypqsPxST64sAtG9f5O/G4\nXe2m3cSbfAAvEUP1Ig1LUNyJB4jhM60w1cQic/qO8++sk/+GoX9g71X+i4NArGv+\n1c11acDIIPGAAQpFeYVeGaKakNDNp8RtJJp8R8FLwJXZ4/gATBnScCiHUSrGfRly\nYyR0w/BNlQ6/NijAdB9pR5csPvyIPkx1gauZewIDAQABAoIBAQCwWf/mhuY2h6uG\n9eDZsZ7Mj2/sO7k9Dl4R5iMSKCDxmnlB3slqitExa+aJUqEs8R5icjkkJcjfYNuJ\nCEFJf3YCsGZfGyyQBtCuEh2ATcBEb2SJ3/f3YuoCEaB1oVwdsOzc4TAovpol4yQo\nUqHp1/mdElVb01jhQQN4h1c02IJnfzvfU1C8szBni+Etfd+MxqGfv006DY3KOEb3\nlCrCS3GmooJW2Fjj7q1kCcaEQbMB1/aQHLXd1qe3KJOzXh3Voxsp/jEH0hvp2TII\nfY9UK+b7mA+xlvXwKuTkHVaZm0ylg0nbembS8MF4GfFMujinSexvLrVKaQhdMFoF\nvBLxHYHRAoGBANfNVYJYeCDPFNLmak5Xg33Rfvc2II8UmrZOVdhOWs8ZK0pis9e+\nPo2MKtTzrzipXI2QXv5w7kO+LJWNDva+xRlW8Wlj9Dde9QdQ7Y8+dk7SJgf24DzM\n023elgX5DvTeLODjStk6SMPRL0FmGovUqAAA8ZeHtJzkIr1HROWnQiwnAoGBANez\nhFwKnVoQu0RpBz/i4W0RKIxOwltN2zmlN8KjJPhSy00A7nBUfKLRbcwiSHE98Yi/\nUrXwMwR5QeD2ngngRppddJnpiRfjNjnsaqeqNtpO8AxB3XjpCC5zmHUMFHKvPpDj\n1zU/F44li0YjKcMBebZy9PbfAjrIgJfxhPo/oXiNAoGAfx6gaTjOAp2ZaaZ7Jozc\nkyft/5et1DrR6+P3I4T8bxQncRj1UXfqhxzzOiAVrm3tbCKIIp/JarRCtRGzp9u2\nZPfXGzra6CcSdW3Rkli7/jBCYNynOIl7XjQI8ZnFmq6phwu80ntH07mMeZy4tHff\nQqlLpvQ0i1rDr/Wkexdsnm8CgYBgxha9ILoF/Xm3MJPjEsxmnYsen/tM8XpIu5pv\nxbhBfQvfKWrQlOcyOVnUexEbVVo3KvdVz0VkXW60GpE/BxNGEGXO49rxD6x1gl87\nh/+CJGZIaYiOxaY5CP2+jcPizEL6yG32Yq8TxD5fIkmLRu8vbxX+aIFclDY1dVNe\n3wt3xQKBgGEL0EjwRch+P2V+YHAhbETPrEqJjHRWT95pIdF9XtC8fasSOVH81cLX\nXXsX1FTvOJNwG9Nk8rQjYJXGTb2O/2unaazlYUwxKwVpwuGzz/vhH/roHZBAkIVT\njvpykpn9QMezEdpzj5BEv01QzSYBPzIh5myrpoJIoSW7py7zFG3h\n-----END RSA PRIVATE KEY-----\n',
token: '!00000000000000000000000000000001', token: '!00000000000000000000000000000001',
password: '$2a$08$OPESxR2RE/ZijjGanNKk6ezSqGFitqsbZqTjWUZPLhORMKxHCbc4O', // ilovesakurako password: '$2a$08$OPESxR2RE/ZijjGanNKk6ezSqGFitqsbZqTjWUZPLhORMKxHCbc4O', // ilovesakurako
profile: {}, profile: {},
settings: {}, settings: {},
client_settings: {} clientSettings: {}
} }
}, opts)); }, opts));
} }
function insertDriveFile(opts) { function insertDriveFile(opts) {
return db.get('drive_files.files').insert({ return db.get('driveFiles.files').insert({
length: opts.datasize, length: opts.datasize,
filename: 'strawberry-pasta.png', filename: 'strawberry-pasta.png',
metadata: opts metadata: opts
@ -1195,9 +1195,9 @@ function insertDriveFile(opts) {
} }
function insertDriveFolder(opts) { function insertDriveFolder(opts) {
return db.get('drive_folders').insert(deepAssign({ return db.get('driveFolders').insert(deepAssign({
name: 'my folder', name: 'my folder',
parent_id: null parentId: null
}, opts)); }, opts));
} }

View file

@ -4,8 +4,8 @@
const assert = require('assert'); const assert = require('assert');
const analyze = require('../built/server/api/common/text').default; const analyze = require('../built/common/text/parse').default;
const syntaxhighlighter = require('../built/server/api/common/text/core/syntax-highlighter').default; const syntaxhighlighter = require('../built/common/text/core/syntax-highlighter').default;
describe('Text', () => { describe('Text', () => {
it('can be analyzed', () => { it('can be analyzed', () => {

View file

@ -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\

View file

@ -1 +1,13 @@
db.posts.update({ mediaIds: null }, { $set: { mediaIds: [] } }, false, true); db.posts.update({
$or: [{
mediaIds: null
}, {
mediaIds: {
$exist: false
}
}]
}, {
$set: {
mediaIds: []
}
}, false, true);

View file

@ -1,16 +1,40 @@
// for Node.js interpretation // for Node.js interpret
const Message = require('../../../built/models/messaging-message').default; const { default: Post } = require('../../../built/api/models/post');
const Post = require('../../../built/models/post').default; const { default: zip } = require('@prezzemolo/zip')
const html = require('../../../built/common/text/html').default; const html = require('../../../built/common/text/html').default;
const parse = require('../../../built/common/text/parse').default; const parse = require('../../../built/common/text/parse').default;
Promise.all([Message, Post].map(async model => { const migrate = async (post) => {
const documents = await model.find(); const result = await Post.update(post._id, {
return Promise.all(documents.map(({ _id, text }) => model.update(_id, {
$set: { $set: {
textHtml: html(parse(text)) textHtml: post.text ? html(parse(post.text)) : null
} }
}))); });
})).catch(console.error).then(process.exit); return result.ok === 1;
}
async function main() {
const count = await Post.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 Post.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)

View file

@ -1,27 +1,21 @@
// for Node.js interpret // for Node.js interpret
const { default: Othello } = require('../../built/api/models/othello-game') const { default: Message } = require('../../../built/api/models/message');
const { default: zip } = require('@prezzemolo/zip') const { default: zip } = require('@prezzemolo/zip')
const html = require('../../../built/common/text/html').default;
const parse = require('../../../built/common/text/parse').default;
const migrate = async (doc) => { const migrate = async (message) => {
const x = {}; const result = await Message.update(message._id, {
doc.logs.forEach(log => {
log.color = log.color == 'black';
});
const result = await Othello.update(doc._id, {
$set: { $set: {
logs: doc.logs textHtml: message.text ? html(parse(message.text)) : null
} }
}); });
return result.ok === 1; return result.ok === 1;
} }
async function main() { async function main() {
const count = await Message.count({});
const count = await Othello.count({});
const dop = Number.parseInt(process.argv[2]) || 5 const dop = Number.parseInt(process.argv[2]) || 5
const idop = ((count - (count % dop)) / dop) + 1 const idop = ((count - (count % dop)) / dop) + 1
@ -30,7 +24,7 @@ async function main() {
1, 1,
async (time) => { async (time) => {
console.log(`${time} / ${idop}`) console.log(`${time} / ${idop}`)
const doc = await Othello.find({}, { const doc = await Message.find({}, {
limit: dop, skip: time * dop limit: dop, skip: time * dop
}) })
return Promise.all(doc.map(migrate)) return Promise.all(doc.map(migrate))

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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);
});

View file

@ -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({})

View file

@ -1,5 +0,0 @@
db.posts.update({}, {
$rename: {
reply_to_id: 'reply_id'
}
}, false, true);