Compare commits

..

22 Commits

Author SHA1 Message Date
1a2a190828 10.89.0 2019-02-23 11:29:29 +09:00
251cf1d76f Update dependencies 🚀 2019-02-23 11:29:17 +09:00
52774bbe64 Introduce OpenAPI specs (#4351)
* wip

* wip

* wip

* Update index.ts

* Update gen-openapi-spec.ts

* Update api.ja-JP.md

* Fix

* Improve doc

* Update gen-openapi-spec.ts

* Update redoc.html

* Improve doc

* Update gen-openapi-spec.ts

* Improve doc

* Update CHANGELOG.md
2019-02-23 11:20:58 +09:00
68a6758302 投稿を削除したときにお気に入りからも削除するように 2019-02-22 14:54:52 +09:00
13e43a4f74 Refactor 2019-02-22 14:53:03 +09:00
b7d62d09ec Refactor 2019-02-22 14:46:49 +09:00
321ec18173 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-02-22 14:22:54 +09:00
a44ac3306e Fix error response format 2019-02-22 14:22:40 +09:00
951288ecf0 docs to run in production mode (#4347)
* run in production mode from systemd

* NODE_ENV=production npm run build

* npm start
2019-02-22 14:18:05 +09:00
cc8a7dd588 Fix bug 2019-02-22 14:09:24 +09:00
813c52f51e Remove unused imports 2019-02-22 14:06:17 +09:00
be3298639d Refactor 2019-02-22 14:02:56 +09:00
e8d2959717 New Crowdin translations (#4337)
* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)
2019-02-22 13:46:51 +09:00
e7680e08eb Update README.md [AUTOGEN] (#4344) 2019-02-22 13:46:26 +09:00
bd792d7661 Fix type annotation 2019-02-22 13:39:06 +09:00
4920983f23 Refactoring 2019-02-22 13:38:12 +09:00
2756f553c6 Improve error handling of API (#4345)
* wip

* wip

* wip

* Update attached_notes.ts

* wip

* Refactor

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update call.ts

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* ✌️

* Fix
2019-02-22 11:46:58 +09:00
fc52e95ad0 Update CHANGELOG.md 2019-02-22 02:10:32 +09:00
5d1d6bc028 Fix i18n 2019-02-22 02:08:45 +09:00
b106acac91 Fix #4340 2019-02-22 01:56:07 +09:00
a5071db864 Update note-detail.vue (#4342) 2019-02-21 23:20:55 +09:00
bae874eb45 /api/v1/instance/peers 復活 (#4339)
* /api/v1/instance/peers 復活

* use Instance
2019-02-21 23:20:25 +09:00
214 changed files with 3266 additions and 2449 deletions

View File

@ -1,6 +1,14 @@
ChangeLog
=========
10.89.0
----------
* APIのエラーの形式を統一
* APIドキュメント刷新
* /api/v1/instance/peers 復活
* 「返信が遷移後も残り続ける問題を修正」([9beddc9](https://github.com/syuilo/misskey/commit/9beddc941a716f1322ae0b7d71d159edd642a399)) によって遷移前に返信が表示されなくなった問題を修正
* デッキモードにてユーザーのプロフィールを連続で見たとき、アクティビティや画像が前のユーザーのもののまま表示される問題を修正
10.88.0
----------
* アカウントの削除を試験的に実装

View File

@ -106,7 +106,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/3?token-time=2145916800&token-hash=c8HeVqLtmdgH-gSBJg8i10gmOcwllM87MDHeznl3el0%3D" alt="Melilot" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/3?token-time=2145916800&token-hash=LtV2lRi3L2jOWMLwccr9qWYfPrFlzIo2jYZHKzHEb6k%3D" alt="Xeltica" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=Ch3iF81ZGP0LMo894Y9ajpLisgtE91SnxtZE7fxsgrM%3D" alt="べすれい" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=1FlxS9MEgmNGH_RHUVHbO5hIXB5I1z0lvA33CTvYvjA%3D" alt="gutfuckllc" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/weepjp">weep</a></td>
@ -114,7 +113,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
<td><a href="https://www.patreon.com/Xeltica">Xeltica</a></td>
<td><a href="https://www.patreon.com/user?u=3384329">べすれい</a></td>
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
</tr></table>
<table><tr>
@ -152,7 +150,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
</tr></table>
**Last updated:** Fri, 15 Feb 2019 19:12:06 UTC
**Last updated:** Thu, 21 Feb 2019 16:10:06 UTC
<!-- PATREON_END -->
:four_leaf_clover: Copyright

BIN
assets/api-doc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -55,14 +55,9 @@ As root:
*6.* Build Misskey
----------------------------------------------------------------
Before build, you need to set `NODE_ENV` to `production`. like this:
* Linux: `export NODE_ENV=production`
* Windows (PowerShell): `$env:NODE_ENV="production"`
* Windows (CMD): `set NODE_ENV=production`
Build misskey with the following:
`npm run build`
`NODE_ENV=production npm run build`
If you're on Debian, you will need to install the `build-essential`, `python` package.
@ -71,14 +66,14 @@ If you're still encountering errors about some modules, use node-gyp:
1. `npm install -g node-gyp`
2. `node-gyp configure`
3. `node-gyp build`
4. `npm run build`
4. `NODE_ENV=production npm run build`
*7.* That is it.
----------------------------------------------------------------
Well done! Now, you have an environment that run to Misskey.
### Launch normally
Just `npm start`. GLHF!
Just `NODE_ENV=production npm start`. GLHF!
### Launch with systemd
@ -94,6 +89,7 @@ Type=simple
User=misskey
ExecStart=/usr/bin/npm start
WorkingDirectory=/home/misskey/misskey
Environment="NODE_ENV=production"
TimeoutSec=60
StandardOutput=syslog
StandardError=syslog
@ -113,7 +109,7 @@ You can check if the service is running with `systemctl status misskey`.
1. `git fetch`
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
3. `npm install`
4. `npm run build`
4. `NODE_ENV=production npm run build`
5. Check [ChangeLog](../CHANGELOG.md) for migration information
6. Restart your Misskey process to apply changes
7. Enjoy

View File

@ -57,7 +57,7 @@ En root :
Construisez Misskey comme ceci :
`npm run build`
`NODE_ENV=production npm run build`
Si vous êtes sous Debian, vous serez amené à installer les paquets `build-essential` et `python`.
@ -66,14 +66,14 @@ Si vous rencontrez des erreurs concernant certains modules, utilisez node-gyp:
1. `npm install -g node-gyp`
2. `node-gyp configure`
3. `node-gyp build`
4. `npm run build`
4. `NODE_ENV=production npm run build`
*7.* C'est tout.
----------------------------------------------------------------
Excellent ! Maintenant, vous avez un environnement prêt pour lancer Misskey
### Lancement conventionnel
Lancez tout simplement `npm start`. Bonne chance et amusez-vous bien !
Lancez tout simplement `NODE_ENV=production npm start`. Bonne chance et amusez-vous bien !
### Démarrage avec systemd
@ -89,6 +89,7 @@ Type=simple
User=misskey
ExecStart=/usr/bin/npm start
WorkingDirectory=/home/misskey/misskey
Environment="NODE_ENV=production"
TimeoutSec=60
StandardOutput=syslog
StandardError=syslog
@ -108,7 +109,7 @@ Vous pouvez vérifier si le service a démarré en utilisant la commande `system
1. `git fetch`
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
3. `npm install`
4. `npm run build`
4. `NODE_ENV=production npm run build`
5. Consultez [ChangeLog](../CHANGELOG.md) pour les information de migration.
----------------------------------------------------------------

View File

@ -62,14 +62,9 @@ adduser --disabled-password --disabled-login misskey
*6.* Misskeyのビルド
----------------------------------------------------------------
ビルドする前に、`NODE_ENV``production`にする必要があります。例:
* Linux: `export NODE_ENV=production`
* Windows (PowerShell): `$env:NODE_ENV="production"`
* Windows (CMD): `set NODE_ENV=production`
次のコマンドでMisskeyをビルドしてください:
`npm run build`
`NODE_ENV=production npm run build`
Debianをお使いであれば、`build-essential`パッケージをインストールする必要があります。
@ -77,14 +72,14 @@ Debianをお使いであれば、`build-essential`パッケージをインスト
1. `npm install -g node-gyp`
2. `node-gyp configure`
3. `node-gyp build`
4. `npm run build`
4. `NODE_ENV=production npm run build`
*7.* 以上です!
----------------------------------------------------------------
お疲れ様でした。これでMisskeyを動かす準備は整いました。
### 通常起動
`npm start`するだけです。GLHF!
`NODE_ENV=production npm start`するだけです。GLHF!
### systemdを用いた起動
1. systemdサービスのファイルを作成: `/etc/systemd/system/misskey.service`
@ -99,6 +94,7 @@ Type=simple
User=misskey
ExecStart=/usr/bin/npm start
WorkingDirectory=/home/misskey/misskey
Environment="NODE_ENV=production"
TimeoutSec=60
StandardOutput=syslog
StandardError=syslog
@ -119,7 +115,7 @@ CentOSで1024以下のポートを使用してMisskeyを使用する場合は`Ex
1. `git fetch`
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
3. `npm install`
4. `npm run build`
4. `NODE_ENV=production npm run build`
5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する
なにか問題が発生した場合は、`npm run clean`すると直る場合があります。

View File

@ -8,12 +8,12 @@ common:
about: "Thank you for finding Misskey. Misskey is a <b>decentralized microblogging platform</b> born on Earth. Since it exists within the Fediverse (a universe where various social media platforms are organized), it is mutually linked with other social media platforms. Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet?"
intro:
title: "What is Misskey?"
about: "Misskey is an open-source, <b>decentralized microblogging software</b>. It has a sophisticated, fully customizable user interface, a variety of ways for expressing a reaction to posts, free file storage providing an integrated management system, and other advanced features are available. In addition, Misskey connects to a network system called the “Fediverse” enables us to communicate with users on other SNSs. For example, when you post something, it will be sent not only to Misskey users, but also those on Mastodon and Pleroma. Just imagine that the planet is sending a radio transmission to another planet, in order to communicate."
about: "Misskey is an open-source, <b>decentralized microblogging software</b>. It has a sophisticated, fully customizable user interface, a variety of ways for expressing a reaction to posts, free file storage providing an integrated management system, and other advanced features are available. In addition, Misskey connects to a network system called the “Fediverse”, which enables us to communicate with users on other SNSs. For example, when you post something, it will be sent not only to Misskey users, but also those on Mastodon and Pleroma. Just imagine that the planet is sending a radio transmission to another planet, in order to communicate."
features: "Features"
rich-contents: "Post"
rich-contents-desc: "Just post your idea, hot topics, and anything you want to share. You may want to decorate your words, attach your favorite pictures, send files, including videos, or create a poll - those are some of the things you can do with Misskey!"
reaction: "Reactions"
reaction-desc: "Easiest way to tell your emotions. Misskey allows you to add various type of reactions to others post. The emotional experience on Misskey will never be on other SNSs which only able to push “likes”."
reaction-desc: "The easiest way to express your emotions. Misskey allows you to add various kinds of reactions to other's posts. The emotional experience on Misskey will never be on other SNSs, which are only able to push “likes”."
ui: "Interface"
ui-desc: "No UI fits for everyone. Therefore, Misskey has a highly customizable UI for your taste. Make your original home by editing, adjusting layouts of timeline and placing selectable widgets you can easily customize."
drive: "Drive"
@ -60,7 +60,7 @@ common:
messaging: "Talk"
deck: "Deck"
timeline: "Timeline"
explore: "Discover"
explore: "Explore"
following: "Following"
followers: "Followers"
empty-timeline-info:

View File

@ -144,7 +144,7 @@ common:
is-remote-post: "이 글 정보는 복사본입니다."
view-on-remote: "정확한 정보 보기"
renoted-by: "{user}이(가) 리노트"
no-notes: "投稿がありません"
no-notes: "글이 없습니다"
error:
title: "오류가 발생했습니다"
retry: "다시 시도"
@ -538,10 +538,10 @@ common/views/components/profile-editor.vue:
mute-list: "뮤트"
blocking-list: "차단"
export-requested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 드라이브에 파일이 추가됩니다."
enter-password: "パスワードを入力してください"
danger-zone: "危険な設定"
delete-account: "アカウントを削除"
account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。"
enter-password: "비밀번호를 입력하여 주십시오"
danger-zone: "위험한 설정"
delete-account: "계정 삭제"
account-deleted: "계정이 삭제되었습니다. 데이터가 사라질 때까지 시간이 걸릴 수 있습니다."
common/views/components/user-list-editor.vue:
users: "사용자"
rename: "리스트 이름 바꾸기"

View File

@ -538,10 +538,10 @@ common/views/components/profile-editor.vue:
mute-list: "屏蔽列表"
blocking-list: "黑名单"
export-requested: "导出请求已提交。可能需要花一些时间。导出的文件将保存到网盘中。"
enter-password: "パスワードを入力してください"
danger-zone: "危険な設定"
delete-account: "アカウントを削除"
account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。"
enter-password: "请输入您的密码"
danger-zone: "危险选项"
delete-account: "删除帐户"
account-deleted: "帐户已被删除。 数据会在一段时间之后清除。"
common/views/components/user-list-editor.vue:
users: "用户"
rename: "重命名列表"

View File

@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "10.88.0",
"version": "10.89.0",
"codename": "nighthike",
"repository": {
"type": "git",
@ -95,14 +95,14 @@
"@types/websocket": "0.0.40",
"@types/ws": "6.0.1",
"animejs": "3.0.1",
"apexcharts": "3.3.0",
"apexcharts": "3.4.1",
"autobind-decorator": "2.4.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
"bcryptjs": "2.4.3",
"bee-queue": "1.2.2",
"bootstrap-vue": "2.0.0-rc.11",
"cafy": "14.0.1",
"cafy": "15.1.0",
"chai": "4.2.0",
"chai-http": "4.2.1",
"chalk": "2.4.2",
@ -228,20 +228,20 @@
"uuid": "3.3.2",
"v-animate-css": "0.0.3",
"video-thumbnail-generator": "1.1.3",
"vue": "2.6.6",
"vue": "2.6.7",
"vue-color": "2.7.0",
"vue-content-loading": "1.5.3",
"vue-cropperjs": "3.0.0",
"vue-i18n": "8.8.1",
"vue-i18n": "8.8.2",
"vue-js-modal": "1.3.28",
"vue-loader": "15.6.2",
"vue-loader": "15.6.4",
"vue-marquee-text-component": "1.1.1",
"vue-prism-component": "1.1.1",
"vue-router": "3.0.2",
"vue-sequential-entrance": "1.1.3",
"vue-style-loader": "4.1.2",
"vue-svg-inline-loader": "1.2.10",
"vue-template-compiler": "2.6.6",
"vue-template-compiler": "2.6.7",
"vuedraggable": "2.17.0",
"vuewordcloud": "18.7.11",
"vuex": "3.1.0",
@ -251,7 +251,7 @@
"webpack": "4.28.4",
"webpack-cli": "3.2.1",
"websocket": "1.0.28",
"ws": "6.1.3",
"ws": "6.1.4",
"xev": "2.0.1"
}
}

View File

@ -61,12 +61,14 @@ export default Vue.extend({
return {
withFiles: false,
images: [],
makePromise: null
makePromise: null,
chart: null as ApexCharts
};
},
watch: {
user() {
this.fetch();
this.genPromiseMaker();
}
},
@ -155,7 +157,9 @@ export default Vue.extend({
]);
}
const chart = new ApexCharts(this.$refs.chart, {
if (this.chart) this.chart.destroy();
this.chart = new ApexCharts(this.$refs.chart, {
chart: {
type: 'bar',
stacked: true,
@ -201,7 +205,7 @@ export default Vue.extend({
}
});
chart.render();
this.chart.render();
});
},
}

View File

@ -122,6 +122,10 @@ export default Vue.extend({
};
},
mounted() {
this.fetchReplies();
},
watch: {
note() {
this.fetchReplies();

View File

@ -4,7 +4,7 @@
<div v-if="!fetching && notes.length > 0">
<mk-note-card v-for="note in notes" :key="note.id" :note="note"/>
</div>
<p class="empty" v-if="!fetching && notes.length == 0">{{ $t('no-notes') }}</p>
<p class="empty" v-if="!fetching && notes.length == 0">{{ $t('@.no-notes') }}</p>
</div>
</template>

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>Misskey API</title>
<!-- needed for adaptive design -->
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<!--
ReDoc doesn't change outer page styles
-->
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url='/api.json'></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
</body>
</html>

View File

@ -74,7 +74,7 @@ APIはすべてリクエストのパラメータ・レスポンスともにJSON
ストリーミングAPIも提供しています。
APIリファレンスもご確認ください。
[APIリファレンス](/api-doc)もご確認ください。
### レートリミット
Misskey APIにはレートリミットがあり、短時間のうちに多数のリクエストを送信すると、一定時間APIを利用することができなくなることがあります。

View File

@ -1,40 +0,0 @@
@import "../style"
#url
padding 8px 12px 8px 8px
font-family Consolas, 'Courier New', Courier, Monaco, monospace
color #fff
background #222e40
border-radius 4px
overflow auto
white-space nowrap
> .method
display inline-block
margin 0 8px 0 0
padding 0 6px
color #fff
background #17afc7
border-radius 4px
user-select none
pointer-events none
> .host
opacity 0.7
#stability
padding 8px 12px
color #fff
border-radius 4px
&.deprecated
background #f42443
&.experimental
background #f2781a
&.stable
background #3dcc90
> b
margin-left 4px

View File

@ -1,81 +0,0 @@
extends ../../base
include ../mixins
block meta
link(rel="stylesheet" href="/docs/assets/api/endpoints/style.css")
block main
h1= title
p#url
span.method POST
span.host
= endpointUrl.host
| /
span.path= endpointUrl.path
- var stability = endpoint.stability || 'experimental';
p#stability(class=stability)
| Stability:
b= stability
if endpoint.desc
p#desc= endpoint.desc[lang] || endpoint.desc['ja-JP']
if endpoint.requireCredential
div.ui.info: p
i.fas.fa-id-card-alt(style="margin-right: 4px")
= i18n('docs.api.endpoints.require-credential')
if endpoint.kind
div.ui.info: p
i.fas.fa-unlock-alt(style="margin-right: 4px")
!= i18n('docs.api.endpoints.require-permission').replace('{permission}', `<code>${endpoint.kind}</code>`)
if endpoint.limit
div.ui.info.warn: p
i.far.fa-clock(style="margin-right: 4px")
b!= i18n('docs.api.endpoints.has-limit')
if endpoint.limit.duration
!= i18n('docs.api.endpoints.duration-limit').replace('{duration}', endpoint.limit.duration).replace('{max}', endpoint.limit.max)
if endpoint.limit.minInterval
!= i18n('docs.api.endpoints.min-interval-limit').replace('{interval}', endpoint.limit.minInterval)
if params && Object.keys(params).length > 0
section
h2= i18n('docs.api.endpoints.params')
+propTable(params)
if paramDefs
each paramDef in paramDefs
section(id= paramDef.name)
h3= paramDef.name
+propTable(paramDef.params)
if params && Object.keys(params).length == 0
section
h2= i18n('docs.api.endpoints.params')
p= i18n('docs.api.endpoints.no-params')
if res
section
h2= i18n('docs.api.endpoints.res')
if resProps
+propTable(resProps)
if resDefs
each resDef in resDefs
section(id= resDef.name)
h3= resDef.name
+propTable(resDef.props)
else
if res.type.startsWith('entity')
a(href=`/docs/${lang}/api/entities/${kebab(res.entity)}`)= res.entity
block footer
div.ui.info: p
i.fas.fa-info-circle(style="margin-right: 4px")
= i18n('docs.api.endpoints.generated')
p
= i18n('docs.api.endpoints.show-src')
a(href=src target="_blank")= i18n('docs.api.endpoints.show-src-link')

View File

@ -1,90 +0,0 @@
name: "DriveFile"
desc:
ja-JP: "ドライブのファイル。"
en-US: "A file of Drive."
props:
id:
type: "id"
optional: false
desc:
ja-JP: "ファイルID"
en-US: "The ID of this file"
createdAt:
type: "date"
optional: false
desc:
ja-JP: "アップロード日時"
en-US: "The upload date of this file"
userId:
type: "id(User)"
optional: false
desc:
ja-JP: "所有者ID"
en-US: "The ID of the owner of this file"
user:
type: "entity(User)"
optional: true
desc:
ja-JP: "所有者"
en-US: "The owner of this file"
name:
type: "string"
optional: false
desc:
ja-JP: "ファイル名"
en-US: "The name of this file"
md5:
type: "string"
optional: false
desc:
ja-JP: "ファイルのMD5ハッシュ値"
en-US: "The md5 hash value of this file"
type:
type: "string"
optional: false
desc:
ja-JP: "ファイルの種類"
en-US: "The type of this file"
datasize:
type: "number"
optional: false
desc:
ja-JP: "ファイルサイズ(bytes)"
en-US: "The size of this file (bytes)"
url:
type: "string"
optional: false
desc:
ja-JP: "ファイルのURL"
en-US: "The URL of this file"
folderId:
type: "id(DriveFolder)"
optional: true
desc:
ja-JP: "フォルダID"
en-US: "The ID of the folder of this file"
folder:
type: "entity(DriveFolder)"
optional: true
desc:
ja-JP: "フォルダ"
en-US: "The folder of this file"
isSensitive:
type: "boolean"
optional: true
desc:
ja-JP: "このメディアが「閲覧注意」(NSFW)かどうか"
en-US: "Whether this media is NSFW"

View File

@ -1,41 +0,0 @@
name: "DriveFolder"
desc:
ja-JP: "ドライブのフォルダを表します。"
en-US: "A folder of Drive."
props:
id:
type: "id"
optional: false
desc:
ja-JP: "フォルダID"
en-US: "The ID of this folder"
createdAt:
type: "date"
optional: false
desc:
ja-JP: "作成日時"
en-US: "The created date of this folder"
userId:
type: "id(User)"
optional: false
desc:
ja-JP: "所有者ID"
en-US: "The ID of the owner of this folder"
parentId:
type: "entity(DriveFolder)"
optional: false
desc:
ja-JP: "親フォルダのID (ルートなら null)"
en-US: "The ID of parent folder"
name:
type: "string"
optional: false
desc:
ja-JP: "フォルダ名"
en-US: "The name of this folder"

View File

@ -1,211 +0,0 @@
name: "Note"
desc:
ja-JP: "投稿。"
en-US: "A note."
props:
id:
type: "id"
optional: false
desc:
ja-JP: "投稿ID"
en-US: "The ID of this note"
createdAt:
type: "date"
optional: false
desc:
ja-JP: "投稿日時"
en-US: "The posted date of this note"
viaMobile:
type: "boolean"
optional: true
desc:
ja-JP: "モバイル端末から投稿したか否か(自己申告であることに留意)"
en-US: "Whether this note sent via a mobile device"
localOnly:
type: "boolean"
optional: true
desc:
ja-JP: "ローカルのみに公開する投稿か否か"
en-US: "Whether this note is no federation"
text:
type: "string"
optional: true
desc:
ja-JP: "投稿の本文"
en-US: "The text of this note"
fileIds:
type: "id(DriveFile)[]"
optional: true
desc:
ja-JP: "添付されているファイルのID (なければレスポンスでは空配列)"
en-US: "The IDs of the attached files (empty array for response if no files is attached)"
files:
type: "entity(DriveFile)[]"
optional: true
desc:
ja-JP: "添付されているファイル"
en-US: "The attached files"
userId:
type: "id(User)"
optional: false
desc:
ja-JP: "投稿者ID"
en-US: "The ID of author of this note"
user:
type: "entity(User)"
optional: true
desc:
ja-JP: "投稿者"
en-US: "The author of this note"
myReaction:
type: "string"
optional: true
desc:
ja-JP: "この投稿に対する自分の<a href='/docs/api/reactions'>リアクション</a>"
en-US: "The your <a href='/docs/api/reactions'>reaction</a> of this note"
renoteCount:
type: "number"
optional: false
desc:
ja-JP: "この投稿がRenoteされた数"
en-US: "The number of renotes for this post"
repliesCount:
type: "number"
optional: false
desc:
ja-JP: "この投稿に返信された数"
en-US: "The number of replies to this post"
reactionCounts:
type: "object"
optional: false
desc:
ja-JP: "<a href='/docs/api/reactions'>リアクション</a>をキーとし、この投稿に対するそのリアクションの数を値としたオブジェクト"
replyId:
type: "id(Note)"
optional: true
desc:
ja-JP: "返信した投稿のID"
en-US: "The ID of the replyed note"
reply:
type: "entity(Note)"
optional: true
desc:
ja-JP: "返信した投稿"
en-US: "The replyed note"
renoteId:
type: "id(Note)"
optional: true
desc:
ja-JP: "引用した投稿のID"
en-US: "The ID of the quoted note"
renote:
type: "entity(Note)"
optional: true
desc:
ja-JP: "引用した投稿"
en-US: "The quoted note"
poll:
type: "object"
optional: true
desc:
ja-JP: "投票"
en-US: "The poll"
props:
choices:
type: "object[]"
optional: false
desc:
ja-JP: "投票の選択肢"
en-US: "The choices of this poll"
props:
id:
type: "number"
optional: false
desc:
ja-JP: "選択肢ID"
en-US: "The ID of this choice"
isVoted:
type: "boolean"
optional: true
desc:
ja-JP: "自分がこの選択肢に投票したかどうか"
en-US: "Whether you voted to this choice"
text:
type: "string"
optional: false
desc:
ja-JP: "選択肢本文"
en-US: "The text of this choice"
votes:
type: "number"
optional: false
desc:
ja-JP: "この選択肢に投票された数"
en-US: "The number voted for this choice"
geo:
type: "object"
optional: true
desc:
ja-JP: "位置情報"
en-US: "Geo location"
props:
coordinates:
type: "number[]"
optional: false
desc:
ja-JP: "座標。最初に経度:-180〜180で表す。最後に緯度-90〜90で表す。"
altitude:
type: "number"
optional: false
desc:
ja-JP: "高度。メートル単位で表す。"
accuracy:
type: "number"
optional: false
desc:
ja-JP: "緯度、経度の精度。メートル単位で表す。"
altitudeAccuracy:
type: "number"
optional: false
desc:
ja-JP: "高度の精度。メートル単位で表す。"
heading:
type: "number"
optional: false
desc:
ja-JP: "方角。0〜360の角度で表す。0が北、90が東、180が南、270が西。"
speed:
type: "number"
optional: false
desc:
ja-JP: "速度。メートル / 秒数で表す。"

View File

@ -1 +0,0 @@
@import "../style"

View File

@ -1,174 +0,0 @@
name: "User"
desc:
ja-JP: "ユーザー。"
en-US: "A user."
props:
id:
type: "id"
optional: false
desc:
ja-JP: "ユーザーID"
en-US: "The ID of this user"
createdAt:
type: "date"
optional: false
desc:
ja-JP: "アカウント作成日時"
en-US: "The registered date of this user"
username:
type: "string"
optional: false
desc:
ja-JP: "ユーザー名"
en-US: "The username of this user"
description:
type: "string"
optional: false
desc:
ja-JP: "アカウントの説明(自己紹介)"
en-US: "The description of this user"
avatarId:
type: "id(DriveFile)"
optional: true
desc:
ja-JP: "アバターのID"
en-US: "The ID of the avatar of this user"
avatarUrl:
type: "string"
optional: false
desc:
ja-JP: "アバターのURL"
en-US: "The URL of the avatar of this user"
bannerId:
type: "id(DriveFile)"
optional: true
desc:
ja-JP: "バナーのID"
en-US: "The ID of the banner of this user"
bannerUrl:
type: "string"
optional: false
desc:
ja-JP: "バナーのURL"
en-US: "The URL of the banner of this user"
followersCount:
type: "number"
optional: false
desc:
ja-JP: "フォロワーの数"
en-US: "The number of the followers for this user"
followingCount:
type: "number"
optional: false
desc:
ja-JP: "フォローしているユーザーの数"
en-US: "The number of the following users for this user"
isFollowing:
type: "boolean"
optional: true
desc:
ja-JP: "自分がこのユーザーをフォローしているか"
isFollowed:
type: "boolean"
optional: true
desc:
ja-JP: "自分がこのユーザーにフォローされているか"
isMuted:
type: "boolean"
optional: true
desc:
ja-JP: "自分がこのユーザーをミュートしているか"
en-US: "Whether you muted this user"
notesCount:
type: "number"
optional: false
desc:
ja-JP: "投稿の数"
en-US: "The number of the notes of this user"
pinnedNotes:
type: "entity(Note)[]"
optional: true
desc:
ja-JP: "ピン留めされた投稿"
en-US: "The pinned note of this user"
pinnedNoteIds:
type: "id(Note)[]"
optional: true
desc:
ja-JP: "ピン留めされた投稿のID"
en-US: "The ID of the pinned note of this user"
host:
type: "string | null"
optional: false
desc:
ja-JP: "ホスト (例: example.com:3000)"
en-US: "Host (e.g. example.com:3000)"
twitter:
type: "object"
optional: true
desc:
ja-JP: "連携されているTwitterアカウント情報"
en-US: "The info of the connected twitter account of this user"
props:
userId:
type: "string"
optional: false
desc:
ja-JP: "ユーザーID"
en-US: "The user ID"
screenName:
type: "string"
optional: false
desc:
ja-JP: "ユーザー名"
en-US: "The screen name of this user"
isBot:
type: "boolean"
optional: true
desc:
ja-JP: "botか否か(自己申告であることに留意)"
en-US: "Whether is bot or not"
profile:
type: "object"
optional: false
desc:
ja-JP: "プロフィール"
en-US: "The profile of this user"
props:
location:
type: "string"
optional: true
desc:
ja-JP: "場所"
en-US: "The location of this user"
birthday:
type: "string"
optional: true
desc:
ja-JP: "誕生日 (YYYY-MM-DD)"
en-US: "The birthday of this user (YYYY-MM-DD)"

View File

@ -1,20 +0,0 @@
extends ../../base
include ../mixins
block meta
link(rel="stylesheet" href="/docs/assets/api/entities/style.css")
block main
h1= name
p#desc= desc[lang] || desc['ja-JP']
section
h2= i18n('docs.api.entities.properties')
+propTable(props)
if propDefs
each propDef in propDefs
section(id= propDef.name)
h3= propDef.name
+propTable(propDef.props)

View File

@ -1,34 +0,0 @@
mixin type(prop)
i= prop.type
if prop.kind == 'id'
if prop.entity
| (
a(href=`/docs/${lang}/api/entities/${kebab(prop.entity)}`)= prop.entity
| ID)
else
| (ID)
else if prop.kind == 'entity'
| (
a(href=`/docs/${lang}/api/entities/${kebab(prop.entity)}`)= prop.entity
| )
else if prop.kind == 'object'
if prop.hasDef
| (
a(href=`#${prop.name}`)= prop.name
| )
else if prop.kind == 'date'
| (Date)
mixin propTable(props)
table.props
thead: tr
th= i18n('docs.api.props.name')
th= i18n('docs.api.props.type')
th= i18n('docs.api.props.description')
tbody
each prop in props
tr
td.name= prop.name
td.type
+type(prop)
td.desc!= prop.desc ? prop.desc[lang] || prop.desc['ja-JP'] : null

View File

@ -1,11 +0,0 @@
@import "../style"
table.props
.name
font-weight bold
.name
.type
.optional
font-family Consolas, 'Courier New', Courier, Monaco, monospace

View File

@ -17,17 +17,6 @@ html(lang= lang)
ul
each doc in docs
li: a(href=`/docs/${lang}/${doc.name}`)= doc.title[lang] || doc.title['ja-JP']
section
h2 API
ul
li Entities
ul
each entity in entities
li: a(href=`/docs/${lang}/api/entities/${kebab(entity)}`)= entity
li Endpoints
ul
each endpoint in endpoints
li: a(href=`/docs/${lang}/api/endpoints/${kebab(endpoint.name)}`)= endpoint.name
main
article
block main

View File

@ -26,6 +26,8 @@ export type ObjectId = mongo.ObjectID;
* ID
*/
export default class ID<Maybe = string> extends Context<string | Maybe> {
public readonly name = 'ID';
constructor(optional = false, nullable = false) {
super(optional, nullable);
@ -38,7 +40,7 @@ export default class ID<Maybe = string> extends Context<string | Maybe> {
}
public getType() {
return super.getType('string');
return super.getType('String');
}
public makeOptional(): ID<undefined> {

View File

@ -0,0 +1,13 @@
/**
* ID付きエラー
*/
export class IdentifiableError extends Error {
public message: string;
public id: string;
constructor(id: string, message?: string) {
super(message);
this.message = message;
this.id = id;
}
}

View File

@ -48,7 +48,6 @@ export default async (ctx: Router.IRouterContext) => {
const partOf = `${config.url}/users/${userId}/followers`;
if (page) {
// Construct query
const query = {
followeeId: user._id
} as any;

View File

@ -48,7 +48,6 @@ export default async (ctx: Router.IRouterContext) => {
const partOf = `${config.url}/users/${userId}/following`;
if (page) {
// Construct query
const query = {
followerId: user._id
} as any;

View File

@ -78,7 +78,6 @@ export default async (ctx: Router.IRouterContext) => {
}
//#endregion
// Issue query
const notes = await Note
.find(query, {
limit: limit,

View File

@ -3,45 +3,36 @@ import * as Koa from 'koa';
import { IEndpoint } from './endpoints';
import authenticate from './authenticate';
import call from './call';
import { IUser } from '../../models/user';
import { IApp } from '../../models/app';
import { ApiError } from './error';
export default async (endpoint: IEndpoint, ctx: Koa.BaseContext) => {
export default (endpoint: IEndpoint, ctx: Koa.BaseContext) => new Promise((res) => {
const body = ctx.is('multipart/form-data') ? (ctx.req as any).body : ctx.request.body;
const reply = (x?: any, y?: any) => {
if (x === undefined) {
const reply = (x?: any, y?: ApiError) => {
if (x == null) {
ctx.status = 204;
} else if (typeof x === 'number') {
ctx.status = x;
ctx.body = {
error: x === 500 ? 'INTERNAL_ERROR' : y
};
ctx.body = { error: y };
} else {
ctx.body = x;
}
res();
};
let user: IUser;
let app: IApp;
// Authentication
try {
[user, app] = await authenticate(body['i']);
} catch (e) {
reply(403, 'AUTHENTICATION_FAILED');
return;
}
let res;
// API invoking
try {
res = await call(endpoint.name, user, app, body, (ctx.req as any).file);
} catch (e) {
reply(400, e);
return;
}
reply(res);
};
authenticate(body['i']).then(([user, app]) => {
// API invoking
call(endpoint.name, user, app, body, (ctx.req as any).file).then(res => {
reply(res);
}).catch(e => {
reply(e.httpStatusCode ? e.httpStatusCode : e.kind == 'client' ? 400 : 500, e);
});
}).catch(() => {
reply(403, new ApiError({
message: 'Authentication failed. Please ensure your token is correct.',
code: 'AUTHENTICATION_FAILED',
id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14'
}));
});
});

View File

@ -2,6 +2,14 @@ import limiter from './limiter';
import { IUser } from '../../models/user';
import { IApp } from '../../models/app';
import endpoints from './endpoints';
import { ApiError } from './error';
import { apiLogger } from './logger';
const accessDenied = {
message: 'Access denied.',
code: 'ACCESS_DENIED',
id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e'
};
export default async (endpoint: string, user: IUser, app: IApp, data: any, file?: any) => {
const isSecure = user != null && app == null;
@ -9,58 +17,72 @@ export default async (endpoint: string, user: IUser, app: IApp, data: any, file?
const ep = endpoints.find(e => e.name === endpoint);
if (ep == null) {
throw 'ENDPOINT_NOT_FOUND';
throw new ApiError({
message: 'No such endpoint.',
code: 'NO_SUCH_ENDPOINT',
id: 'f8080b67-5f9c-4eb7-8c18-7f1eeae8f709',
httpStatusCode: 404
});
}
if (ep.meta.secure && !isSecure) {
throw 'ACCESS_DENIED';
throw new ApiError(accessDenied);
}
if (ep.meta.requireCredential && user == null) {
throw 'CREDENTIAL_REQUIRED';
throw new ApiError({
message: 'Credential required.',
code: 'CREDENTIAL_REQUIRED',
id: '1384574d-a912-4b81-8601-c7b1c4085df1',
httpStatusCode: 401
});
}
if (ep.meta.requireCredential && user.isSuspended) {
throw 'YOUR_ACCOUNT_HAS_BEEN_SUSPENDED';
throw new ApiError(accessDenied, { reason: 'Your account has been suspended.' });
}
if (ep.meta.requireAdmin && !user.isAdmin) {
throw 'YOU_ARE_NOT_ADMIN';
throw new ApiError(accessDenied, { reason: 'You are not the admin.' });
}
if (ep.meta.requireModerator && !user.isAdmin && !user.isModerator) {
throw 'YOU_ARE_NOT_MODERATOR';
throw new ApiError(accessDenied, { reason: 'You are not a moderator.' });
}
if (app && ep.meta.kind && !app.permission.some(p => p === ep.meta.kind)) {
throw 'PERMISSION_DENIED';
throw new ApiError({
message: 'Your app does not have the necessary permissions to use this endpoint.',
code: 'PERMISSION_DENIED',
id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838',
});
}
if (ep.meta.requireCredential && ep.meta.limit) {
try {
await limiter(ep, user); // Rate limit
} catch (e) {
// drop request if limit exceeded
throw 'RATE_LIMIT_EXCEEDED';
}
// Rate limit
await limiter(ep, user).catch(e => {
throw new ApiError({
message: 'Rate limit exceeded. Please try again later.',
code: 'RATE_LIMIT_EXCEEDED',
id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
httpStatusCode: 429
});
});
}
let res;
// API invoking
try {
res = await ep.exec(data, user, app, file);
} catch (e) {
if (e && e.name == 'INVALID_PARAM') {
throw {
code: e.name,
param: e.param,
reason: e.message
};
} else {
return await ep.exec(data, user, app, file).catch((e: Error) => {
if (e instanceof ApiError) {
throw e;
} else {
apiLogger.error(e);
throw new ApiError(null, {
e: {
message: e.message,
code: e.name,
stack: e.stack
}
});
}
}
return res;
});
};

View File

@ -1,18 +1,19 @@
import * as mongo from 'mongodb';
import Note from "../../../models/note";
import User, { isRemoteUser, isLocalUser } from "../../../models/user";
import Note from '../../../models/note';
import User, { isRemoteUser, isLocalUser } from '../../../models/user';
import { IdentifiableError } from '../../../misc/identifiable-error';
/**
* Get valied note for API processing
* Get note for API processing
*/
export async function getValiedNote(noteId: mongo.ObjectID) {
export async function getNote(noteId: mongo.ObjectID) {
const note = await Note.findOne({
_id: noteId,
deletedAt: { $exists: false }
});
if (note === null) {
throw 'note not found';
throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.');
}
return note;
@ -23,11 +24,22 @@ export async function getValiedNote(noteId: mongo.ObjectID) {
*/
export async function getUser(userId: mongo.ObjectID) {
const user = await User.findOne({
_id: userId
_id: userId,
$or: [{
isDeleted: { $exists: false }
}, {
isDeleted: false
}]
}, {
fields: {
data: false,
profile: false,
clientSettings: false
}
});
if (user == null) {
throw 'user not found';
if (user === null) {
throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.');
}
return user;

View File

@ -2,6 +2,7 @@ import * as fs from 'fs';
import { ILocalUser } from '../../models/user';
import { IApp } from '../../models/app';
import { IEndpointMeta } from './endpoints';
import { ApiError } from './error';
type Params<T extends IEndpointMeta> = {
[P in keyof T['params']]: T['params'][P]['transform'] extends Function
@ -9,13 +10,19 @@ type Params<T extends IEndpointMeta> = {
: ReturnType<T['params'][P]['validator']['get']>[0];
};
export default function <T extends IEndpointMeta>(meta: T, cb: (params: Params<T>, user: ILocalUser, app: IApp, file?: any, cleanup?: Function) => Promise<any>): (params: any, user: ILocalUser, app: IApp, file?: any) => Promise<any> {
export type Response = Record<string, any> | void;
export default function <T extends IEndpointMeta>(meta: T, cb: (params: Params<T>, user: ILocalUser, app: IApp, file?: any, cleanup?: Function) => Promise<Response>): (params: any, user: ILocalUser, app: IApp, file?: any) => Promise<any> {
return (params: any, user: ILocalUser, app: IApp, file?: any) => {
function cleanup() {
fs.unlink(file.path, () => {});
}
if (meta.requireFile && file == null) return Promise.reject('file required');
if (meta.requireFile && file == null) return Promise.reject(new ApiError({
message: 'File required.',
code: 'FILE_REQUIRED',
id: '4267801e-70d1-416a-b011-4ee502885d8b',
}));
const [ps, pserr] = getParams(meta, params);
if (pserr) {
@ -27,17 +34,22 @@ export default function <T extends IEndpointMeta>(meta: T, cb: (params: Params<T
};
}
function getParams<T extends IEndpointMeta>(defs: T, params: any): [Params<T>, Error] {
function getParams<T extends IEndpointMeta>(defs: T, params: any): [Params<T>, ApiError] {
if (defs.params == null) return [params, null];
const x: any = {};
let err: Error = null;
let err: ApiError = null;
Object.entries(defs.params).some(([k, def]) => {
const [v, e] = def.validator.get(params[k]);
if (e) {
err = new Error(e.message);
err.name = 'INVALID_PARAM';
(err as any).param = k;
err = new ApiError({
message: 'Invalid param.',
code: 'INVALID_PARAM',
id: '3d81ceae-475f-4600-b2a8-2bc116157532',
}, {
param: k,
reason: e.message
});
return true;
} else {
if (v === undefined && def.hasOwnProperty('default')) {

View File

@ -2,18 +2,30 @@ import { Context } from 'cafy';
import * as path from 'path';
import * as glob from 'glob';
export type Param = {
validator: Context<any>;
transform?: any;
default?: any;
desc?: { [key: string]: string };
ref?: string;
};
export interface IEndpointMeta {
stability?: string; //'deprecated' | 'experimental' | 'stable';
desc?: { [key: string]: string };
tags?: string[];
params?: {
[key: string]: Param;
};
errors?: {
[key: string]: {
validator: Context<any>;
transform?: any;
default?: any;
desc?: { [key: string]: string };
ref?: string;
message: string;
code: string;
id: string;
};
};

View File

@ -4,6 +4,8 @@ import Report, { packMany } from '../../../../models/abuse-user-report';
import define from '../../define';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -25,11 +27,7 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
if (ps.sinceId && ps.untilId) {
return rej('cannot set sinceId and untilId');
}
export default define(meta, async (ps) => {
const sort = {
_id: -1
};
@ -51,5 +49,5 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
sort: sort
});
res(await packMany(reports));
}));
return await packMany(reports);
});

View File

@ -4,6 +4,8 @@ import define from '../../../define';
import { fallback } from '../../../../../prelude/symbol';
export const meta = {
tags: ['admin'],
requireCredential: false,
requireModerator: true,
@ -46,7 +48,7 @@ const sort: any = { // < https://github.com/Microsoft/TypeScript/issues/1863
[fallback]: { _id: -1 }
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
export default define(meta, async (ps, me) => {
const q = {
'metadata.deletedAt': { $exists: false },
} as any;
@ -61,5 +63,5 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
skip: ps.offset
});
res(await packMany(files, { detail: true, withUser: true, self: true }));
}));
return await packMany(files, { detail: true, withUser: true, self: true });
});

View File

@ -2,8 +2,11 @@ import $ from 'cafy';
import ID, { transform } from '../../../../../misc/cafy-id';
import define from '../../../define';
import DriveFile from '../../../../../models/drive-file';
import { ApiError } from '../../../error';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -12,17 +15,25 @@ export const meta = {
validator: $.type(ID),
transform: transform,
},
},
errors: {
noSuchFile: {
message: 'No such file.',
code: 'NO_SUCH_FILE',
id: 'caf3ca38-c6e5-472e-a30c-b05377dcc240'
}
}
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
export default define(meta, async (ps, me) => {
const file = await DriveFile.findOne({
_id: ps.fileId
});
if (file == null) {
return rej('file not found');
throw new ApiError(meta.errors.noSuchFile);
}
res(file);
}));
return file;
});

View File

@ -7,6 +7,8 @@ export const meta = {
'ja-JP': 'カスタム絵文字を追加します。'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -26,7 +28,7 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const emoji = await Emoji.insert({
updatedAt: new Date(),
name: ps.name,
@ -35,7 +37,7 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
url: ps.url
});
res({
return {
id: emoji._id
});
}));
};
});

View File

@ -7,6 +7,8 @@ export const meta = {
'ja-JP': 'カスタム絵文字を取得します。'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -18,16 +20,16 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const emojis = await Emoji.find({
host: ps.host
});
res(emojis.map(e => ({
return emojis.map(e => ({
id: e._id,
name: e.name,
aliases: e.aliases,
host: e.host,
url: e.url
})));
}));
}));
});

View File

@ -8,6 +8,8 @@ export const meta = {
'ja-JP': 'カスタム絵文字を削除します。'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -18,14 +20,14 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const emoji = await Emoji.findOne({
_id: ps.id
});
if (emoji == null) return rej('emoji not found');
if (emoji == null) throw new Error('emoji not found');
await Emoji.remove({ _id: emoji._id });
res();
}));
return;
});

View File

@ -8,6 +8,8 @@ export const meta = {
'ja-JP': 'カスタム絵文字を更新します。'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -30,12 +32,12 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const emoji = await Emoji.findOne({
_id: ps.id
});
if (emoji == null) return rej('emoji not found');
if (emoji == null) throw new Error('emoji not found');
await Emoji.update({ _id: emoji._id }, {
$set: {
@ -46,5 +48,5 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
}
});
res();
}));
return;
});

View File

@ -5,6 +5,8 @@ import User from '../../../../../models/user';
import deleteFollowing from '../../../../../services/following/delete';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -15,7 +17,7 @@ export const meta = {
}
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
export default define(meta, async (ps, me) => {
const followings = await Following.find({
'_follower.host': ps.host
});
@ -29,5 +31,5 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
deleteFollowing(pair[0], pair[1]);
}
res();
}));
return;
});

View File

@ -3,6 +3,8 @@ import define from '../../../define';
import Instance from '../../../../../models/instance';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -21,11 +23,11 @@ export const meta = {
}
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
export default define(meta, async (ps, me) => {
const instance = await Instance.findOne({ host: ps.host });
if (instance == null) {
return rej('instance not found');
throw new Error('instance not found');
}
Instance.update({ host: ps.host }, {
@ -35,5 +37,5 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
}
});
res();
}));
return;
});

View File

@ -7,13 +7,15 @@ export const meta = {
'ja-JP': '招待コードを発行します。'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
params: {}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const code = rndstr({ length: 5, chars: '0-9' });
await RegistrationTicket.insert({
@ -21,7 +23,7 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
code: code
});
res({
return {
code: code
});
}));
};
});

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Mark a user as moderator.'
},
tags: ['admin'],
requireCredential: true,
requireAdmin: true,
@ -24,13 +26,13 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
await User.update({
@ -41,5 +43,5 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
}
});
res();
}));
return;
});

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Unmark a user as moderator.'
},
tags: ['admin'],
requireCredential: true,
requireAdmin: true,
@ -24,13 +26,13 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
await User.update({
@ -41,5 +43,5 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
}
});
res();
}));
return;
});

View File

@ -2,14 +2,16 @@ import define from '../../../define';
import { destroy } from '../../../../../queue';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
params: {}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
destroy();
res();
}));
return;
});

View File

@ -4,6 +4,8 @@ import define from '../../define';
import AbuseUserReport from '../../../../models/abuse-user-report';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -15,18 +17,18 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const report = await AbuseUserReport.findOne({
_id: ps.reportId
});
if (report == null) {
return rej('report not found');
throw new Error('report not found');
}
await AbuseUserReport.remove({
_id: report._id
});
res();
}));
return;
});

View File

@ -10,6 +10,8 @@ export const meta = {
'ja-JP': '指定したユーザーのパスワードをリセットします。',
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -25,17 +27,17 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
if (user.isAdmin) {
return rej('cannot reset password of admin');
throw new Error('cannot reset password of admin');
}
const passwd = rndstr('a-zA-Z0-9', 8);
@ -46,12 +48,12 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
await User.findOneAndUpdate({
_id: user._id
}, {
$set: {
password: hash
}
});
res({
password: passwd
$set: {
password: hash
}
});
}));
return {
password: passwd
};
});

View File

@ -8,6 +8,8 @@ export const meta = {
'ja-JP': '指定したユーザーの情報を取得します。',
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -23,18 +25,18 @@ export const meta = {
}
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
export default define(meta, async (ps, me) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
if (me.isModerator && user.isAdmin) {
return rej('cannot show info of admin');
throw new Error('cannot show info of admin');
}
res(user);
}));
return user;
});

View File

@ -4,6 +4,8 @@ import define from '../../define';
import { fallback } from '../../../../prelude/symbol';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -63,7 +65,7 @@ const sort: any = { // < https://github.com/Microsoft/TypeScript/issues/1863
[fallback]: { _id: -1 }
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
export default define(meta, async (ps, me) => {
const q = {
$and: []
} as any;
@ -99,5 +101,5 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
skip: ps.offset
});
res(await Promise.all(users.map(user => pack(user, me, { detail: true }))));
}));
return await Promise.all(users.map(user => pack(user, me, { detail: true })));
});

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Make silence a user.'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -24,17 +26,17 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
if (user.isAdmin) {
return rej('cannot silence admin');
throw new Error('cannot silence admin');
}
await User.findOneAndUpdate({
@ -45,5 +47,5 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
}
});
res();
}));
return;
});

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Suspend a user.'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -24,30 +26,30 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
if (user.isAdmin) {
return rej('cannot suspend admin');
throw new Error('cannot suspend admin');
}
if (user.isModerator) {
return rej('cannot suspend moderator');
throw new Error('cannot suspend moderator');
}
await User.findOneAndUpdate({
_id: user._id
}, {
$set: {
isSuspended: true
}
});
$set: {
isSuspended: true
}
});
res();
}));
return;
});

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Unsilence a user.'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -24,13 +26,13 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
await User.findOneAndUpdate({
@ -41,5 +43,5 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
}
});
res();
}));
return;
});

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Unsuspend a user.'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -24,22 +26,22 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
await User.findOneAndUpdate({
_id: user._id
}, {
$set: {
isSuspended: false
}
});
$set: {
isSuspended: false
}
});
res();
}));
return;
});

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Mark a user as unverified.'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -24,22 +26,22 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
await User.findOneAndUpdate({
_id: user._id
}, {
$set: {
isVerified: false
}
});
$set: {
isVerified: false
}
});
res();
}));
return;
});

View File

@ -7,6 +7,8 @@ export const meta = {
'ja-JP': 'インスタンスの設定を更新します。'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -323,7 +325,7 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const set = {} as any;
if (ps.broadcasts) {
@ -506,5 +508,5 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
$set: set
}, { upsert: true });
res();
}));
return;
});

View File

@ -11,6 +11,8 @@ export const meta = {
'en-US': 'Update specified remote user information.'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -26,9 +28,10 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise((res, rej) => {
updatePersonById(ps.userId).then(() => res(), e => rej(e));
}));
export default define(meta, async (ps) => {
await updatePersonById(ps.userId);
return;
});
async function updatePersonById(userId: mongo.ObjectID) {
const user = await getRemoteUser(userId);

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Mark a user as verified.'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -24,22 +26,22 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
await User.findOneAndUpdate({
_id: user._id
}, {
$set: {
isVerified: true
}
});
$set: {
isVerified: true
}
});
res();
}));
return;
});

View File

@ -3,10 +3,12 @@ import define from '../../define';
import fetchMeta from '../../../../misc/fetch-meta';
export const meta = {
tags: ['hashtags'],
requireCredential: false,
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const instance = await fetchMeta();
const hidedTags = instance.hidedTags.map(t => t.toLowerCase());
@ -40,7 +42,7 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
//#endregion
if (data.length == 0) {
return res([]);
return [];
}
let tags: {
@ -66,5 +68,5 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
tags = tags.slice(0, 30);
res(tags);
}));
return tags;
});

View File

@ -7,8 +7,11 @@ import { createPerson } from '../../../../remote/activitypub/models/person';
import Note, { pack as packNote, INote } from '../../../../models/note';
import { createNote } from '../../../../remote/activitypub/models/note';
import Resolver from '../../../../remote/activitypub/resolver';
import { ApiError } from '../../error';
export const meta = {
tags: ['federation'],
desc: {
'ja-JP': 'URIを指定してActivityPubオブジェクトを参照します。'
},
@ -23,13 +26,24 @@ export const meta = {
}
},
},
errors: {
noSuchObject: {
message: 'No such object.',
code: 'NO_SUCH_OBJECT',
id: 'dc94d745-1262-4e63-a17d-fecaa57efc82'
}
}
};
export default define(meta, (ps) => new Promise((res, rej) => {
fetchAny(ps.uri)
.then(object => object != null ? res(object) : rej('object not found'))
.catch(e => rej(e));
}));
export default define(meta, async (ps) => {
const object = await fetchAny(ps.uri);
if (object) {
return object;
} else {
throw new ApiError(meta.errors.noSuchObject);
}
});
/***
* URIからUserかNoteを解決する

View File

@ -4,6 +4,8 @@ import App, { pack } from '../../../../models/app';
import define from '../../define';
export const meta = {
tags: ['app'],
requireCredential: false,
params: {
@ -27,7 +29,7 @@ export const meta = {
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
// Generate secret
const secret = rndstr('a-zA-Z0-9', 32);
@ -42,9 +44,8 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
secret: secret
});
// Response
res(await pack(app, null, {
return await pack(app, null, {
detail: true,
includeSecret: true
}));
}));
});
});

View File

@ -2,29 +2,39 @@ import $ from 'cafy';
import ID, { transform } from '../../../../misc/cafy-id';
import App, { pack } from '../../../../models/app';
import define from '../../define';
import { ApiError } from '../../error';
export const meta = {
tags: ['app'],
params: {
appId: {
validator: $.type(ID),
transform: transform
},
},
errors: {
noSuchApp: {
message: 'No such app.',
code: 'NO_SUCH_APP',
id: 'dce83913-2dc6-4093-8a7b-71dbb11718a3'
}
}
};
export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user, app) => {
const isSecure = user != null && app == null;
// Lookup app
const ap = await App.findOne({ _id: ps.appId });
if (ap === null) {
return rej('app not found');
throw new ApiError(meta.errors.noSuchApp);
}
// Send response
res(await pack(ap, user, {
return await pack(ap, user, {
detail: true,
includeSecret: isSecure && ap.userId.equals(user._id)
}));
}));
});
});

View File

@ -5,8 +5,11 @@ import App from '../../../../models/app';
import AuthSess from '../../../../models/auth-session';
import AccessToken from '../../../../models/access-token';
import define from '../../define';
import { ApiError } from '../../error';
export const meta = {
tags: ['auth'],
requireCredential: true,
secure: true,
@ -15,16 +18,24 @@ export const meta = {
token: {
validator: $.str
}
},
errors: {
noSuchSession: {
message: 'No such session.',
code: 'NO_SUCH_SESSION',
id: '9c72d8de-391a-43c1-9d06-08d29efde8df'
},
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
// Fetch token
const session = await AuthSess
.findOne({ token: ps.token });
if (session === null) {
return rej('session not found');
throw new ApiError(meta.errors.noSuchSession);
}
// Generate access token
@ -64,6 +75,5 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
}
});
// Response
res();
}));
return;
});

View File

@ -4,25 +4,36 @@ import App from '../../../../../models/app';
import AuthSess from '../../../../../models/auth-session';
import config from '../../../../../config';
import define from '../../../define';
import { ApiError } from '../../../error';
export const meta = {
tags: ['auth'],
requireCredential: false,
params: {
appSecret: {
validator: $.str
}
},
errors: {
noSuchApp: {
message: 'No such app.',
code: 'NO_SUCH_APP',
id: '92f93e63-428e-4f2f-a5a4-39e1407fe998'
}
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
// Lookup app
const app = await App.findOne({
secret: ps.appSecret
});
if (app == null) {
return rej('app not found');
throw new ApiError(meta.errors.noSuchApp);
}
// Generate token
@ -35,9 +46,8 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
token: token
});
// Response
res({
return {
token: doc.token,
url: `${config.auth_url}/${doc.token}`
});
}));
};
});

View File

@ -1,27 +1,37 @@
import $ from 'cafy';
import AuthSess, { pack } from '../../../../../models/auth-session';
import define from '../../../define';
import { ApiError } from '../../../error';
export const meta = {
tags: ['auth'],
requireCredential: false,
params: {
token: {
validator: $.str
}
},
errors: {
noSuchSession: {
message: 'No such session.',
code: 'NO_SUCH_SESSION',
id: 'bd72c97d-eba7-4adb-a467-f171b8847250'
}
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
// Lookup session
const session = await AuthSess.findOne({
token: ps.token
});
if (session == null) {
return rej('session not found');
throw new ApiError(meta.errors.noSuchSession);
}
// Response
res(await pack(session, user));
}));
return await pack(session, user);
});

View File

@ -4,8 +4,11 @@ import AuthSess from '../../../../../models/auth-session';
import AccessToken from '../../../../../models/access-token';
import { pack } from '../../../../../models/user';
import define from '../../../define';
import { ApiError } from '../../../error';
export const meta = {
tags: ['auth'],
requireCredential: false,
params: {
@ -16,17 +19,37 @@ export const meta = {
token: {
validator: $.str
}
},
errors: {
noSuchApp: {
message: 'No such app.',
code: 'NO_SUCH_APP',
id: 'fcab192a-2c5a-43b7-8ad8-9b7054d8d40d'
},
noSuchSession: {
message: 'No such session.',
code: 'NO_SUCH_SESSION',
id: '5b5a1503-8bc8-4bd0-8054-dc189e8cdcb3'
},
pendingSession: {
message: 'This session is not completed yet.',
code: 'PENDING_SESSION',
id: '8c8a4145-02cc-4cca-8e66-29ba60445a8e'
}
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
// Lookup app
const app = await App.findOne({
secret: ps.appSecret
});
if (app == null) {
return rej('app not found');
throw new ApiError(meta.errors.noSuchApp);
}
// Fetch token
@ -37,11 +60,11 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
});
if (session === null) {
return rej('session not found');
throw new ApiError(meta.errors.noSuchSession);
}
if (session.userId == null) {
return rej('this session is not allowed yet');
throw new ApiError(meta.errors.pendingSession);
}
// Lookup access token
@ -61,11 +84,10 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
_id: session._id
});
// Response
res({
return {
accessToken: accessToken.token,
user: await pack(session.userId, null, {
detail: true
})
});
}));
};
});

View File

@ -1,10 +1,12 @@
import $ from 'cafy';
import ID, { transform } from '../../../../misc/cafy-id';
import * as ms from 'ms';
import User, { pack } from '../../../../models/user';
import { pack } from '../../../../models/user';
import Blocking from '../../../../models/blocking';
import create from '../../../../services/blocking/create';
import define from '../../define';
import { ApiError } from '../../error';
import { getUser } from '../../common/getters';
export const meta = {
stability: 'stable',
@ -14,6 +16,8 @@ export const meta = {
'en-US': 'Block a user.'
},
tags: ['blocking', 'users'],
limit: {
duration: ms('1hour'),
max: 100
@ -32,31 +36,43 @@ export const meta = {
'en-US': 'Target user ID'
}
}
},
errors: {
noSuchUser: {
message: 'No such user.',
code: 'NO_SUCH_USER',
id: '7cc4f851-e2f1-4621-9633-ec9e1d00c01e'
},
blockeeIsYourself: {
message: 'Blockee is yourself.',
code: 'BLOCKEE_IS_YOURSELF',
id: '88b19138-f28d-42c0-8499-6a31bbd0fdc6'
},
alreadyBlocking: {
message: 'You are already blocking that user.',
code: 'ALREADY_BLOCKING',
id: '787fed64-acb9-464a-82eb-afbd745b9614'
},
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
const blocker = user;
// 自分自身
if (user._id.equals(ps.userId)) {
return rej('blockee is yourself');
throw new ApiError(meta.errors.blockeeIsYourself);
}
// Get blockee
const blockee = await User.findOne({
_id: ps.userId
}, {
fields: {
data: false,
profile: false
}
const blockee = await getUser(ps.userId).catch(e => {
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
throw e;
});
if (blockee === null) {
return rej('user not found');
}
// Check if already blocking
const exist = await Blocking.findOne({
blockerId: blocker._id,
@ -64,14 +80,13 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
});
if (exist !== null) {
return rej('already blocking');
throw new ApiError(meta.errors.alreadyBlocking);
}
// Create blocking
await create(blocker, blockee);
// Send response
res(await pack(blockee._id, user, {
return await pack(blockee._id, user, {
detail: true
}));
}));
});
});

View File

@ -1,10 +1,12 @@
import $ from 'cafy';
import ID, { transform } from '../../../../misc/cafy-id';
import * as ms from 'ms';
import User, { pack } from '../../../../models/user';
import { pack } from '../../../../models/user';
import Blocking from '../../../../models/blocking';
import deleteBlocking from '../../../../services/blocking/delete';
import define from '../../define';
import { ApiError } from '../../error';
import { getUser } from '../../common/getters';
export const meta = {
stability: 'stable',
@ -14,6 +16,8 @@ export const meta = {
'en-US': 'Unblock a user.'
},
tags: ['blocking', 'users'],
limit: {
duration: ms('1hour'),
max: 100
@ -32,31 +36,43 @@ export const meta = {
'en-US': 'Target user ID'
}
}
},
errors: {
noSuchUser: {
message: 'No such user.',
code: 'NO_SUCH_USER',
id: '8621d8bf-c358-4303-a066-5ea78610eb3f'
},
blockeeIsYourself: {
message: 'Blockee is yourself.',
code: 'BLOCKEE_IS_YOURSELF',
id: '06f6fac6-524b-473c-a354-e97a40ae6eac'
},
notBlocking: {
message: 'You are not blocking that user.',
code: 'NOT_BLOCKING',
id: '291b2efa-60c6-45c0-9f6a-045c8f9b02cd'
},
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
const blocker = user;
// Check if the blockee is yourself
if (user._id.equals(ps.userId)) {
return rej('blockee is yourself');
throw new ApiError(meta.errors.blockeeIsYourself);
}
// Get blockee
const blockee = await User.findOne({
_id: ps.userId
}, {
fields: {
data: false,
'profile': false
}
const blockee = await getUser(ps.userId).catch(e => {
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
throw e;
});
if (blockee === null) {
return rej('user not found');
}
// Check not blocking
const exist = await Blocking.findOne({
blockerId: blocker._id,
@ -64,14 +80,13 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
});
if (exist === null) {
return rej('already not blocking');
throw new ApiError(meta.errors.notBlocking);
}
// Delete blocking
await deleteBlocking(blocker, blockee);
// Send response
res(await pack(blockee._id, user, {
return await pack(blockee._id, user, {
detail: true
}));
}));
});
});

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Get blocking users.'
},
tags: ['blocking', 'account'],
requireCredential: true,
kind: 'following-read',
@ -31,12 +33,7 @@ export const meta = {
}
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
// Check if both of sinceId and untilId is specified
if (ps.sinceId && ps.untilId) {
return rej('cannot set sinceId and untilId');
}
export default define(meta, async (ps, me) => {
const query = {
blockerId: me._id
} as any;
@ -62,5 +59,5 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
sort: sort
});
res(await packMany(blockings, me));
}));
return await packMany(blockings, me);
});

View File

@ -9,6 +9,8 @@ export const meta = {
'ja-JP': 'アクティブユーザーのチャートを取得します。'
},
tags: ['charts', 'users'],
params: {
span: {
validator: $.str.or(['day', 'hour']),
@ -24,11 +26,16 @@ export const meta = {
'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
}
},
}
},
res: {
type: 'array',
items: {
type: 'object',
},
},
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
const stats = await activeUsersChart.getChart(ps.span as any, ps.limit);
res(stats);
}));
export default define(meta, async (ps) => {
return await activeUsersChart.getChart(ps.span as any, ps.limit);
});

View File

@ -9,6 +9,8 @@ export const meta = {
'ja-JP': 'ドライブのチャートを取得します。'
},
tags: ['charts', 'drive'],
params: {
span: {
validator: $.str.or(['day', 'hour']),
@ -24,11 +26,16 @@ export const meta = {
'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
}
},
}
},
res: {
type: 'array',
items: {
type: 'object',
},
},
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
const stats = await driveChart.getChart(ps.span as any, ps.limit);
res(stats);
}));
export default define(meta, async (ps) => {
return await driveChart.getChart(ps.span as any, ps.limit);
});

View File

@ -9,6 +9,8 @@ export const meta = {
'ja-JP': 'フェデレーションのチャートを取得します。'
},
tags: ['charts'],
params: {
span: {
validator: $.str.or(['day', 'hour']),
@ -24,11 +26,16 @@ export const meta = {
'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
}
},
}
},
res: {
type: 'array',
items: {
type: 'object',
},
},
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
const stats = await federationChart.getChart(ps.span as any, ps.limit);
res(stats);
}));
export default define(meta, async (ps) => {
return await federationChart.getChart(ps.span as any, ps.limit);
});

View File

@ -9,6 +9,8 @@ export const meta = {
'ja-JP': 'ハッシュタグごとのチャートを取得します。'
},
tags: ['charts', 'hashtags'],
params: {
span: {
validator: $.str.or(['day', 'hour']),
@ -31,11 +33,16 @@ export const meta = {
'ja-JP': '対象のハッシュタグ'
}
},
}
},
res: {
type: 'array',
items: {
type: 'object',
},
},
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
const stats = await hashtagChart.getChart(ps.span as any, ps.limit, ps.tag);
res(stats);
}));
export default define(meta, async (ps) => {
return await hashtagChart.getChart(ps.span as any, ps.limit, ps.tag);
});

View File

@ -9,6 +9,8 @@ export const meta = {
'ja-JP': 'インスタンスごとのチャートを取得します。'
},
tags: ['charts'],
params: {
span: {
validator: $.str.or(['day', 'hour']),
@ -32,11 +34,16 @@ export const meta = {
'en-US': 'Target instance host'
}
}
}
},
res: {
type: 'array',
items: {
type: 'object',
},
},
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
const stats = await instanceChart.getChart(ps.span as any, ps.limit, ps.host);
res(stats);
}));
export default define(meta, async (ps) => {
return await instanceChart.getChart(ps.span as any, ps.limit, ps.host);
});

View File

@ -9,6 +9,8 @@ export const meta = {
'ja-JP': 'ネットワークのチャートを取得します。'
},
tags: ['charts'],
params: {
span: {
validator: $.str.or(['day', 'hour']),
@ -24,11 +26,16 @@ export const meta = {
'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
}
},
}
},
res: {
type: 'array',
items: {
type: 'object',
},
},
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
const stats = await networkChart.getChart(ps.span as any, ps.limit);
res(stats);
}));
export default define(meta, async (ps) => {
return await networkChart.getChart(ps.span as any, ps.limit);
});

View File

@ -9,6 +9,8 @@ export const meta = {
'ja-JP': '投稿のチャートを取得します。'
},
tags: ['charts', 'notes'],
params: {
span: {
validator: $.str.or(['day', 'hour']),
@ -24,11 +26,16 @@ export const meta = {
'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
}
},
}
},
res: {
type: 'array',
items: {
type: 'object',
},
},
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
const stats = await notesChart.getChart(ps.span as any, ps.limit);
res(stats);
}));
export default define(meta, async (ps) => {
return await notesChart.getChart(ps.span as any, ps.limit);
});

View File

@ -10,6 +10,8 @@ export const meta = {
'ja-JP': 'ユーザーごとのドライブのチャートを取得します。'
},
tags: ['charts', 'drive', 'users'],
params: {
span: {
validator: $.str.or(['day', 'hour']),
@ -34,11 +36,16 @@ export const meta = {
'en-US': 'Target user ID'
}
}
}
},
res: {
type: 'array',
items: {
type: 'object',
},
},
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
const stats = await perUserDriveChart.getChart(ps.span as any, ps.limit, ps.userId);
res(stats);
}));
export default define(meta, async (ps) => {
return await perUserDriveChart.getChart(ps.span as any, ps.limit, ps.userId);
});

View File

@ -10,6 +10,8 @@ export const meta = {
'ja-JP': 'ユーザーごとのフォロー/フォロワーのチャートを取得します。'
},
tags: ['charts', 'users', 'following'],
params: {
span: {
validator: $.str.or(['day', 'hour']),
@ -34,11 +36,16 @@ export const meta = {
'en-US': 'Target user ID'
}
}
}
},
res: {
type: 'array',
items: {
type: 'object',
},
},
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
const stats = await perUserFollowingChart.getChart(ps.span as any, ps.limit, ps.userId);
res(stats);
}));
export default define(meta, async (ps) => {
return await perUserFollowingChart.getChart(ps.span as any, ps.limit, ps.userId);
});

View File

@ -10,6 +10,8 @@ export const meta = {
'ja-JP': 'ユーザーごとの投稿のチャートを取得します。'
},
tags: ['charts', 'users', 'notes'],
params: {
span: {
validator: $.str.or(['day', 'hour']),
@ -34,11 +36,16 @@ export const meta = {
'en-US': 'Target user ID'
}
}
}
},
res: {
type: 'array',
items: {
type: 'object',
},
},
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
const stats = await perUserNotesChart.getChart(ps.span as any, ps.limit, ps.userId);
res(stats);
}));
export default define(meta, async (ps) => {
return await perUserNotesChart.getChart(ps.span as any, ps.limit, ps.userId);
});

View File

@ -10,6 +10,8 @@ export const meta = {
'ja-JP': 'ユーザーごとの被リアクション数のチャートを取得します。'
},
tags: ['charts', 'users', 'reactions'],
params: {
span: {
validator: $.str.or(['day', 'hour']),
@ -34,11 +36,16 @@ export const meta = {
'en-US': 'Target user ID'
}
}
}
},
res: {
type: 'array',
items: {
type: 'object',
},
},
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
const stats = await perUserReactionsChart.getChart(ps.span as any, ps.limit, ps.userId);
res(stats);
}));
export default define(meta, async (ps) => {
return await perUserReactionsChart.getChart(ps.span as any, ps.limit, ps.userId);
});

View File

@ -9,6 +9,8 @@ export const meta = {
'ja-JP': 'ユーザーのチャートを取得します。'
},
tags: ['charts', 'users'],
params: {
span: {
validator: $.str.or(['day', 'hour']),
@ -24,11 +26,16 @@ export const meta = {
'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
}
},
}
},
res: {
type: 'array',
items: {
type: 'object',
},
},
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
const stats = await usersChart.getChart(ps.span as any, ps.limit);
res(stats);
}));
export default define(meta, async (ps) => {
return await usersChart.getChart(ps.span as any, ps.limit);
});

View File

@ -8,40 +8,41 @@ export const meta = {
'en-US': 'Get drive information.'
},
tags: ['drive', 'account'],
requireCredential: true,
kind: 'drive-read'
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
const instance = await fetchMeta();
// Calculate drive usage
const usage = await DriveFile
.aggregate([{
$match: {
'metadata.userId': user._id,
'metadata.deletedAt': { $exists: false }
}
}, {
$project: {
length: true
}
}, {
$group: {
_id: null,
usage: { $sum: '$length' }
}
}])
.then((aggregates: any[]) => {
if (aggregates.length > 0) {
return aggregates[0].usage;
}
return 0;
});
const usage = await DriveFile.aggregate([{
$match: {
'metadata.userId': user._id,
'metadata.deletedAt': { $exists: false }
}
}, {
$project: {
length: true
}
}, {
$group: {
_id: null,
usage: { $sum: '$length' }
}
}])
.then((aggregates: any[]) => {
if (aggregates.length > 0) {
return aggregates[0].usage;
}
return 0;
});
res({
return {
capacity: 1024 * 1024 * instance.localDriveCapacityMb,
usage: usage
});
}));
};
});

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Get files of drive.'
},
tags: ['drive'],
requireCredential: true,
kind: 'drive-read',
@ -38,15 +40,17 @@ export const meta = {
type: {
validator: $.optional.str.match(/^[a-zA-Z\/\-\*]+$/)
}
}
},
res: {
type: 'array',
items: {
type: 'DriveFile',
},
},
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Check if both of sinceId and untilId is specified
if (ps.sinceId && ps.untilId) {
return rej('cannot set sinceId and untilId');
}
export default define(meta, async (ps, user) => {
const sort = {
_id: -1
};
@ -78,5 +82,5 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
sort: sort
});
res(await packMany(files, { detail: false, self: true }));
}));
return await packMany(files, { detail: false, self: true });
});

View File

@ -3,6 +3,7 @@ import ID, { transform } from '../../../../../misc/cafy-id';
import DriveFile from '../../../../../models/drive-file';
import define from '../../../define';
import { packMany } from '../../../../../models/note';
import { ApiError } from '../../../error';
export const meta = {
stability: 'stable',
@ -12,6 +13,8 @@ export const meta = {
'en-US': 'Get the notes that specified file of drive attached.'
},
tags: ['drive', 'notes'],
requireCredential: true,
kind: 'drive-read',
@ -25,10 +28,18 @@ export const meta = {
'en-US': 'Target file ID'
}
}
},
errors: {
noSuchFile: {
message: 'No such file.',
code: 'NO_SUCH_FILE',
id: 'c118ece3-2e4b-4296-99d1-51756e32d232',
}
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
// Fetch file
const file = await DriveFile
.findOne({
@ -38,10 +49,10 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
});
if (file === null) {
return rej('file-not-found');
throw new ApiError(meta.errors.noSuchFile);
}
res(await packMany(file.metadata.attachedNoteIds || [], user, {
return await packMany(file.metadata.attachedNoteIds || [], user, {
detail: true
}));
}));
});
});

View File

@ -8,6 +8,8 @@ export const meta = {
'en-US': 'Returns whether the file with the given MD5 hash exists in the user\'s drive.'
},
tags: ['drive'],
requireCredential: true,
kind: 'drive-read',
@ -22,16 +24,12 @@ export const meta = {
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
const file = await DriveFile.findOne({
md5: ps.md5,
'metadata.userId': user._id,
'metadata.deletedAt': { $exists: false }
});
if (file === null) {
res({ file: null });
} else {
res({ file: await pack(file, { self: true }) });
}
}));
return { file: file ? await pack(file, { self: true }) : null };
});

View File

@ -5,6 +5,7 @@ import { validateFileName, pack } from '../../../../../models/drive-file';
import create from '../../../../../services/drive/add-file';
import define from '../../../define';
import { apiLogger } from '../../../logger';
import { ApiError } from '../../../error';
export const meta = {
desc: {
@ -12,6 +13,8 @@ export const meta = {
'en-US': 'Upload a file to drive.'
},
tags: ['drive'],
requireCredential: true,
limit: {
@ -34,7 +37,7 @@ export const meta = {
},
isSensitive: {
validator: $.optional.or($.bool, $.str),
validator: $.optional.either($.bool, $.str),
default: false,
transform: (v: any): boolean => v === true || v === 'true',
desc: {
@ -44,17 +47,29 @@ export const meta = {
},
force: {
validator: $.optional.or($.bool, $.str),
validator: $.optional.either($.bool, $.str),
default: false,
transform: (v: any): boolean => v === true || v === 'true',
desc: {
'ja-JP': 'true にすると、同じハッシュを持つファイルが既にアップロードされていても強制的にファイルを作成します。',
}
}
},
res: {
type: 'DriveFile',
},
errors: {
invalidFileName: {
message: 'Invalid file name.',
code: 'INVALID_FILE_NAME',
id: 'f449b209-0c60-4e51-84d5-29486263bfd4'
}
}
};
export default define(meta, (ps, user, app, file, cleanup) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user, app, file, cleanup) => {
// Get 'name' parameter
let name = file.originalname;
if (name !== undefined && name !== null) {
@ -64,7 +79,7 @@ export default define(meta, (ps, user, app, file, cleanup) => new Promise(async
} else if (name === 'blob') {
name = null;
} else if (!validateFileName(name)) {
return rej('invalid name');
throw new ApiError(meta.errors.invalidFileName);
}
} else {
name = null;
@ -73,15 +88,11 @@ export default define(meta, (ps, user, app, file, cleanup) => new Promise(async
try {
// Create file
const driveFile = await create(user, file.path, name, null, ps.folderId, ps.force, false, null, null, ps.isSensitive);
cleanup();
res(pack(driveFile, { self: true }));
return pack(driveFile, { self: true });
} catch (e) {
apiLogger.error(e);
throw new ApiError();
} finally {
cleanup();
rej(e);
}
}));
});

View File

@ -4,6 +4,7 @@ import DriveFile from '../../../../../models/drive-file';
import del from '../../../../../services/drive/delete-file';
import { publishDriveStream } from '../../../../../services/stream';
import define from '../../../define';
import { ApiError } from '../../../error';
export const meta = {
stability: 'stable',
@ -13,6 +14,8 @@ export const meta = {
'en-US': 'Delete a file of drive.'
},
tags: ['drive'],
requireCredential: true,
kind: 'drive-write',
@ -26,10 +29,24 @@ export const meta = {
'en-US': 'Target file ID'
}
}
},
errors: {
noSuchFile: {
message: 'No such file.',
code: 'NO_SUCH_FILE',
id: '908939ec-e52b-4458-b395-1025195cea58'
},
accessDenied: {
message: 'Access denied.',
code: 'ACCESS_DENIED',
id: '5eb8d909-2540-4970-90b8-dd6f86088121'
},
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
// Fetch file
const file = await DriveFile
.findOne({
@ -37,11 +54,11 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
});
if (file === null) {
return rej('file-not-found');
throw new ApiError(meta.errors.noSuchFile);
}
if (!user.isAdmin && !user.isModerator && !file.metadata.userId.equals(user._id)) {
return rej('access denied');
throw new ApiError(meta.errors.accessDenied);
}
// Delete
@ -50,5 +67,5 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Publish fileDeleted event
publishDriveStream(user._id, 'fileDeleted', file._id);
res();
}));
return;
});

View File

@ -6,6 +6,8 @@ import define from '../../../define';
export const meta = {
requireCredential: true,
tags: ['drive'],
kind: 'drive-read',
params: {
@ -24,7 +26,7 @@ export const meta = {
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
const files = await DriveFile
.find({
filename: ps.name,
@ -32,5 +34,5 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
'metadata.folderId': ps.folderId
});
res(await Promise.all(files.map(file => pack(file, { self: true }))));
}));
return await Promise.all(files.map(file => pack(file, { self: true })));
});

View File

@ -4,6 +4,7 @@ import ID, { transform } from '../../../../../misc/cafy-id';
import DriveFile, { pack, IDriveFile } from '../../../../../models/drive-file';
import define from '../../../define';
import config from '../../../../../config';
import { ApiError } from '../../../error';
export const meta = {
stability: 'stable',
@ -13,6 +14,8 @@ export const meta = {
'en-US': 'Get specified file of drive.'
},
tags: ['drive'],
requireCredential: true,
kind: 'drive-read',
@ -34,10 +37,34 @@ export const meta = {
'en-US': 'Target file URL'
}
}
},
res: {
type: 'DriveFile',
},
errors: {
noSuchFile: {
message: 'No such file.',
code: 'NO_SUCH_FILE',
id: '067bc436-2718-4795-b0fb-ecbe43949e31'
},
accessDenied: {
message: 'Access denied.',
code: 'ACCESS_DENIED',
id: '25b73c73-68b1-41d0-bad1-381cfdf6579f'
},
fileIdOrUrlRequired: {
message: 'fileId or url required.',
code: 'INVALID_PARAM',
id: '89674805-722c-440c-8d88-5641830dc3e4'
}
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
let file: IDriveFile;
if (ps.fileId) {
@ -69,22 +96,19 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
});
}
} else {
return rej('fileId or url required');
throw new ApiError(meta.errors.fileIdOrUrlRequired);
}
if (!user.isAdmin && !user.isModerator && !file.metadata.userId.equals(user._id)) {
return rej('access denied');
throw new ApiError(meta.errors.accessDenied);
}
if (file === null) {
return rej('file-not-found');
throw new ApiError(meta.errors.noSuchFile);
}
// Serialize
const _file = await pack(file, {
return await pack(file, {
detail: true,
self: true
});
res(_file);
}));
});

View File

@ -5,6 +5,7 @@ import DriveFile, { validateFileName, pack } from '../../../../../models/drive-f
import { publishDriveStream } from '../../../../../services/stream';
import define from '../../../define';
import Note from '../../../../../models/note';
import { ApiError } from '../../../error';
export const meta = {
desc: {
@ -12,6 +13,8 @@ export const meta = {
'en-US': 'Update specified file of drive.'
},
tags: ['drive'],
requireCredential: true,
kind: 'drive-write',
@ -51,10 +54,30 @@ export const meta = {
'en-US': 'Whether this media is NSFW'
}
}
},
errors: {
noSuchFile: {
message: 'No such file.',
code: 'NO_SUCH_FILE',
id: 'e7778c7e-3af9-49cd-9690-6dbc3e6c972d'
},
accessDenied: {
message: 'Access denied.',
code: 'ACCESS_DENIED',
id: '01a53b27-82fc-445b-a0c1-b558465a8ed2'
},
noSuchFolder: {
message: 'No such folder.',
code: 'NO_SUCH_FOLDER',
id: 'ea8fb7a5-af77-4a08-b608-c0218176cd73'
},
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
// Fetch file
const file = await DriveFile
.findOne({
@ -62,11 +85,11 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
});
if (file === null) {
return rej('file-not-found');
throw new ApiError(meta.errors.noSuchFile);
}
if (!user.isAdmin && !user.isModerator && !file.metadata.userId.equals(user._id)) {
return rej('access denied');
throw new ApiError(meta.errors.accessDenied);
}
if (ps.name) file.filename = ps.name;
@ -85,7 +108,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
});
if (folder === null) {
return rej('folder-not-found');
throw new ApiError(meta.errors.noSuchFolder);
}
file.metadata.folderId = folder._id;
@ -114,12 +137,10 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
}
});
// Serialize
const fileObj = await pack(file, { self: true });
// Response
res(fileObj);
// Publish fileUpdated event
publishDriveStream(user._id, 'fileUpdated', fileObj);
}));
return fileObj;
});

View File

@ -10,6 +10,8 @@ export const meta = {
'ja-JP': 'ドライブに指定されたURLに存在するファイルをアップロードします。'
},
tags: ['drive'],
limit: {
duration: ms('1hour'),
max: 60
@ -50,6 +52,6 @@ export const meta = {
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
res(pack(await uploadFromUrl(ps.url, user, ps.folderId, null, ps.isSensitive, ps.force), { self: true }));
}));
export default define(meta, async (ps, user) => {
return await pack(await uploadFromUrl(ps.url, user, ps.folderId, null, ps.isSensitive, ps.force), { self: true });
});

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Get folders of drive.'
},
tags: ['drive'],
requireCredential: true,
kind: 'drive-read',
@ -37,12 +39,7 @@ export const meta = {
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Check if both of sinceId and untilId is specified
if (ps.sinceId && ps.untilId) {
return rej('cannot set sinceId and untilId');
}
export default define(meta, async (ps, user) => {
const sort = {
_id: -1
};
@ -67,5 +64,5 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
sort: sort
});
res(await Promise.all(folders.map(folder => pack(folder))));
}));
return await Promise.all(folders.map(folder => pack(folder)));
});

View File

@ -3,6 +3,7 @@ import ID, { transform } from '../../../../../misc/cafy-id';
import DriveFolder, { isValidFolderName, pack } from '../../../../../models/drive-folder';
import { publishDriveStream } from '../../../../../services/stream';
import define from '../../../define';
import { ApiError } from '../../../error';
export const meta = {
stability: 'stable',
@ -12,6 +13,8 @@ export const meta = {
'en-US': 'Create a folder of drive.'
},
tags: ['drive'],
requireCredential: true,
kind: 'drive-write',
@ -34,10 +37,18 @@ export const meta = {
'en-US': 'Parent folder ID'
}
}
},
errors: {
noSuchFolder: {
message: 'No such folder.',
code: 'NO_SUCH_FOLDER',
id: '53326628-a00d-40a6-a3cd-8975105c0f95'
},
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
// If the parent folder is specified
let parent = null;
if (ps.parentId) {
@ -49,7 +60,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
});
if (parent === null) {
return rej('parent-not-found');
throw new ApiError(meta.errors.noSuchFolder);
}
}
@ -61,12 +72,10 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
userId: user._id
});
// Serialize
const folderObj = await pack(folder);
// Response
res(folderObj);
// Publish folderCreated event
publishDriveStream(user._id, 'folderCreated', folderObj);
}));
return folderObj;
});

View File

@ -4,6 +4,7 @@ import DriveFolder from '../../../../../models/drive-folder';
import define from '../../../define';
import { publishDriveStream } from '../../../../../services/stream';
import DriveFile from '../../../../../models/drive-file';
import { ApiError } from '../../../error';
export const meta = {
stability: 'stable',
@ -13,6 +14,8 @@ export const meta = {
'en-US': 'Delete specified folder of drive.'
},
tags: ['drive'],
requireCredential: true,
kind: 'drive-write',
@ -26,10 +29,24 @@ export const meta = {
'en-US': 'Target folder ID'
}
}
},
errors: {
noSuchFolder: {
message: 'No such folder.',
code: 'NO_SUCH_FOLDER',
id: '1069098f-c281-440f-b085-f9932edbe091'
},
hasChildFilesOrFolders: {
message: 'This folder has child files or folders.',
code: 'HAS_CHILD_FILES_OR_FOLDERS',
id: 'b0fc8a17-963c-405d-bfbc-859a487295e1'
},
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
// Get folder
const folder = await DriveFolder
.findOne({
@ -38,7 +55,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
});
if (folder === null) {
return rej('folder-not-found');
throw new ApiError(meta.errors.noSuchFolder);
}
const [childFoldersCount, childFilesCount] = await Promise.all([
@ -47,7 +64,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
]);
if (childFoldersCount !== 0 || childFilesCount !== 0) {
return rej('has-child-contents');
throw new ApiError(meta.errors.hasChildFilesOrFolders);
}
await DriveFolder.remove({ _id: folder._id });
@ -55,5 +72,5 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Publish folderCreated event
publishDriveStream(user._id, 'folderDeleted', folder._id);
res();
}));
return;
});

View File

@ -4,6 +4,8 @@ import DriveFolder, { pack } from '../../../../../models/drive-folder';
import define from '../../../define';
export const meta = {
tags: ['drive'],
requireCredential: true,
kind: 'drive-read',
@ -24,7 +26,7 @@ export const meta = {
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
const folders = await DriveFolder
.find({
name: ps.name,
@ -32,5 +34,5 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
parentId: ps.parentId
});
res(await Promise.all(folders.map(folder => pack(folder))));
}));
return await Promise.all(folders.map(folder => pack(folder)));
});

View File

@ -2,6 +2,7 @@ import $ from 'cafy';
import ID, { transform } from '../../../../../misc/cafy-id';
import DriveFolder, { pack } from '../../../../../models/drive-folder';
import define from '../../../define';
import { ApiError } from '../../../error';
export const meta = {
stability: 'stable',
@ -11,6 +12,8 @@ export const meta = {
'en-US': 'Get specified folder of drive.'
},
tags: ['drive'],
requireCredential: true,
kind: 'drive-read',
@ -24,10 +27,18 @@ export const meta = {
'en-US': 'Target folder ID'
}
}
},
errors: {
noSuchFolder: {
message: 'No such folder.',
code: 'NO_SUCH_FOLDER',
id: 'd74ab9eb-bb09-4bba-bf24-fb58f761e1e9'
},
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
// Get folder
const folder = await DriveFolder
.findOne({
@ -36,11 +47,10 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
});
if (folder === null) {
return rej('folder-not-found');
throw new ApiError(meta.errors.noSuchFolder);
}
// Serialize
res(await pack(folder, {
return await pack(folder, {
detail: true
}));
}));
});
});

View File

@ -3,6 +3,7 @@ import ID, { transform } from '../../../../../misc/cafy-id';
import DriveFolder, { isValidFolderName, pack } from '../../../../../models/drive-folder';
import { publishDriveStream } from '../../../../../services/stream';
import define from '../../../define';
import { ApiError } from '../../../error';
export const meta = {
stability: 'stable',
@ -12,6 +13,8 @@ export const meta = {
'en-US': 'Update specified folder of drive.'
},
tags: ['drive'],
requireCredential: true,
kind: 'drive-write',
@ -42,10 +45,30 @@ export const meta = {
'en-US': 'Parent folder ID'
}
}
},
errors: {
noSuchFolder: {
message: 'No such folder.',
code: 'NO_SUCH_FOLDER',
id: 'f7974dac-2c0d-4a27-926e-23583b28e98e'
},
noSuchParentFolder: {
message: 'No such parent folder.',
code: 'NO_SUCH_PARENT_FOLDER',
id: 'ce104e3a-faaf-49d5-b459-10ff0cbbcaa1'
},
recursiveNesting: {
message: 'It can not be structured like nesting folders recursively.',
code: 'NO_SUCH_PARENT_FOLDER',
id: 'ce104e3a-faaf-49d5-b459-10ff0cbbcaa1'
},
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
// Fetch folder
const folder = await DriveFolder
.findOne({
@ -54,7 +77,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
});
if (folder === null) {
return rej('folder-not-found');
throw new ApiError(meta.errors.noSuchFolder);
}
if (ps.name) folder.name = ps.name;
@ -71,7 +94,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
});
if (parent === null) {
return rej('parent-folder-not-found');
throw new ApiError(meta.errors.noSuchParentFolder);
}
// Check if the circular reference will occur
@ -95,7 +118,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
if (parent.parentId !== null) {
if (await checkCircle(parent.parentId)) {
return rej('detected-circular-definition');
throw new ApiError(meta.errors.recursiveNesting);
}
}
@ -111,12 +134,10 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
}
});
// Serialize
const folderObj = await pack(folder);
// Response
res(folderObj);
// Publish folderUpdated event
publishDriveStream(user._id, 'folderUpdated', folderObj);
}));
return folderObj;
});

Some files were not shown because too many files have changed in this diff Show More