Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
1a2a190828 | |||
251cf1d76f | |||
52774bbe64 | |||
68a6758302 | |||
13e43a4f74 | |||
b7d62d09ec | |||
321ec18173 | |||
a44ac3306e | |||
951288ecf0 | |||
cc8a7dd588 | |||
813c52f51e | |||
be3298639d | |||
e8d2959717 | |||
e7680e08eb | |||
bd792d7661 | |||
4920983f23 | |||
2756f553c6 | |||
fc52e95ad0 | |||
5d1d6bc028 | |||
b106acac91 | |||
a5071db864 | |||
bae874eb45 |
@ -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
|
||||
----------
|
||||
* アカウントの削除を試験的に実装
|
||||
|
@ -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
BIN
assets/api-doc.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.9 KiB |
@ -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
|
||||
|
@ -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.
|
||||
|
||||
----------------------------------------------------------------
|
||||
|
@ -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`すると直る場合があります。
|
||||
|
@ -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 other’s 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:
|
||||
|
@ -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: "리스트 이름 바꾸기"
|
||||
|
@ -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: "重命名列表"
|
||||
|
16
package.json
16
package.json
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
},
|
||||
}
|
||||
|
@ -122,6 +122,10 @@ export default Vue.extend({
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetchReplies();
|
||||
},
|
||||
|
||||
watch: {
|
||||
note() {
|
||||
this.fetchReplies();
|
||||
|
@ -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>
|
||||
|
||||
|
24
src/client/assets/redoc.html
Normal file
24
src/client/assets/redoc.html
Normal 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>
|
@ -74,7 +74,7 @@ APIはすべてリクエストのパラメータ・レスポンスともにJSON
|
||||
|
||||
ストリーミングAPIも提供しています。
|
||||
|
||||
APIリファレンスもご確認ください。
|
||||
[APIリファレンス](/api-doc)もご確認ください。
|
||||
|
||||
### レートリミット
|
||||
Misskey APIにはレートリミットがあり、短時間のうちに多数のリクエストを送信すると、一定時間APIを利用することができなくなることがあります。
|
||||
|
@ -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
|
@ -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')
|
@ -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"
|
@ -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"
|
@ -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: "速度。メートル / 秒数で表す。"
|
@ -1 +0,0 @@
|
||||
@import "../style"
|
@ -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)"
|
@ -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)
|
@ -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
|
@ -1,11 +0,0 @@
|
||||
@import "../style"
|
||||
|
||||
table.props
|
||||
.name
|
||||
font-weight bold
|
||||
|
||||
.name
|
||||
.type
|
||||
.optional
|
||||
font-family Consolas, 'Courier New', Courier, Monaco, monospace
|
||||
|
@ -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
|
||||
|
@ -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> {
|
||||
|
13
src/misc/identifiable-error.ts
Normal file
13
src/misc/identifiable-error.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -78,7 +78,6 @@ export default async (ctx: Router.IRouterContext) => {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
// Issue query
|
||||
const notes = await Note
|
||||
.find(query, {
|
||||
limit: limit,
|
||||
|
@ -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'
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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')) {
|
||||
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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 });
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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
|
||||
});
|
||||
}));
|
||||
};
|
||||
});
|
||||
|
@ -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
|
||||
})));
|
||||
}));
|
||||
}));
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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
|
||||
});
|
||||
}));
|
||||
};
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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
|
||||
};
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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 })));
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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を解決する
|
||||
|
@ -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
|
||||
}));
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@ -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)
|
||||
}));
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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}`
|
||||
});
|
||||
}));
|
||||
};
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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
|
||||
})
|
||||
});
|
||||
}));
|
||||
};
|
||||
});
|
||||
|
@ -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
|
||||
}));
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@ -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
|
||||
}));
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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
|
||||
});
|
||||
}));
|
||||
};
|
||||
});
|
||||
|
@ -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 });
|
||||
});
|
||||
|
@ -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
|
||||
}));
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@ -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 };
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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 })));
|
||||
});
|
||||
|
@ -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);
|
||||
}));
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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 });
|
||||
});
|
||||
|
@ -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)));
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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)));
|
||||
});
|
||||
|
@ -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
|
||||
}));
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@ -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
Reference in New Issue
Block a user