diff options
Diffstat (limited to 'src/server')
137 files changed, 2130 insertions, 1685 deletions
diff --git a/src/server/activitypub.ts b/src/server/activitypub.ts index f8a01a6ffe..17cd34ee6f 100644 --- a/src/server/activitypub.ts +++ b/src/server/activitypub.ts @@ -11,7 +11,6 @@ import renderNote from '../remote/activitypub/renderer/note'; import renderKey from '../remote/activitypub/renderer/key'; import renderPerson from '../remote/activitypub/renderer/person'; import renderOrderedCollection from '../remote/activitypub/renderer/ordered-collection'; -import parseAcct from '../acct/parse'; import config from '../config'; // Init router @@ -112,13 +111,13 @@ router.get('/users/:user/publickey', async ctx => { }); // user -function userInfo(ctx: Router.IRouterContext, user: IUser) { +async function userInfo(ctx: Router.IRouterContext, user: IUser) { if (user === null) { ctx.status = 404; return; } - ctx.body = pack(renderPerson(user as ILocalUser)); + ctx.body = pack(await renderPerson(user as ILocalUser)); } router.get('/users/:user', async ctx => { @@ -129,7 +128,7 @@ router.get('/users/:user', async ctx => { host: null }); - userInfo(ctx, user); + await userInfo(ctx, user); }); router.get('/@:user', async (ctx, next) => { @@ -140,7 +139,7 @@ router.get('/@:user', async (ctx, next) => { host: null }); - userInfo(ctx, user); + await userInfo(ctx, user); }); //#endregion diff --git a/src/server/api/api-handler.ts b/src/server/api/api-handler.ts index e716dcdc01..128c1a98d2 100644 --- a/src/server/api/api-handler.ts +++ b/src/server/api/api-handler.ts @@ -1,12 +1,12 @@ import * as Koa from 'koa'; -import { Endpoint } from './endpoints'; +import { IEndpoint } from './endpoints'; import authenticate from './authenticate'; import call from './call'; import { IUser } from '../../models/user'; import { IApp } from '../../models/app'; -export default async (endpoint: Endpoint, ctx: Koa.Context) => { +export default async (endpoint: IEndpoint, ctx: Koa.Context) => { const body = ctx.is('multipart/form-data') ? (ctx.req as any).body : ctx.request.body; const reply = (x?: any, y?: any) => { @@ -37,7 +37,7 @@ export default async (endpoint: Endpoint, ctx: Koa.Context) => { // API invoking try { - res = await call(endpoint, user, app, body, (ctx.req as any).file); + res = await call(endpoint.name, user, app, body, (ctx.req as any).file); } catch (e) { reply(400, e); return; diff --git a/src/server/api/call.ts b/src/server/api/call.ts index fd3cea7743..fc7dff42a7 100644 --- a/src/server/api/call.ts +++ b/src/server/api/call.ts @@ -1,28 +1,33 @@ -import endpoints, { Endpoint } from './endpoints'; +import { performance } from 'perf_hooks'; import limitter from './limitter'; import { IUser } from '../../models/user'; import { IApp } from '../../models/app'; +import endpoints from './endpoints'; -export default (endpoint: string | Endpoint, user: IUser, app: IApp, data: any, file?: any) => new Promise<any>(async (ok, rej) => { +export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any) => new Promise<any>(async (ok, rej) => { const isSecure = user != null && app == null; - const ep = typeof endpoint == 'string' ? endpoints.find(e => e.name == endpoint) : endpoint; + const ep = endpoints.find(e => e.name === endpoint); - if (ep.secure && !isSecure) { + if (ep.meta.secure && !isSecure) { return rej('ACCESS_DENIED'); } - if (ep.withCredential && user == null) { + if (ep.meta.requireCredential && user == null) { return rej('SIGNIN_REQUIRED'); } - if (app && ep.kind) { - if (!app.permission.some(p => p === ep.kind)) { + if (ep.meta.requireCredential && user.isSuspended) { + return rej('YOUR_ACCOUNT_HAS_BEEN_SUSPENDED'); + } + + if (app && ep.meta.kind) { + if (!app.permission.some(p => p === ep.meta.kind)) { return rej('PERMISSION_DENIED'); } } - if (ep.withCredential && ep.limit) { + if (ep.meta.requireCredential && ep.meta.limit) { try { await limitter(ep, user); // Rate limit } catch (e) { @@ -31,9 +36,9 @@ export default (endpoint: string | Endpoint, user: IUser, app: IApp, data: any, } } - let exec = require(`${__dirname}/endpoints/${ep.name}`); + let exec = ep.exec; - if (ep.withFile && file) { + if (ep.meta.requireFile && file) { exec = exec.bind(null, file); } @@ -41,7 +46,15 @@ export default (endpoint: string | Endpoint, user: IUser, app: IApp, data: any, // API invoking try { + const before = performance.now(); res = await exec(data, user, app); + const after = performance.now(); + + const time = after - before; + + if (time > 500) { + console.warn(`SLOW API CALL DETECTED: ${ep.name} (${ time }ms)`); + } } catch (e) { rej(e); return; diff --git a/src/server/api/common/get-friends.ts b/src/server/api/common/get-friends.ts index 50ba71ea96..e0b05f73df 100644 --- a/src/server/api/common/get-friends.ts +++ b/src/server/api/common/get-friends.ts @@ -23,12 +23,16 @@ export const getFriendIds = async (me: mongodb.ObjectID, includeMe = true) => { return myfollowingIds; }; -export const getFriends = async (me: mongodb.ObjectID, includeMe = true) => { +export const getFriends = async (me: mongodb.ObjectID, includeMe = true, remoteOnly = false) => { + const q: any = remoteOnly ? { + followerId: me, + '_followee.host': { $ne: null } + } : { + followerId: me + }; // Fetch relation to other users who the I follows const followings = await Following - .find({ - followerId: me - }); + .find(q); // ID list of other users who the I follows const myfollowings = followings.map(following => ({ diff --git a/src/server/api/common/read-messaging-message.ts b/src/server/api/common/read-messaging-message.ts index fd5e9f242c..a34fd8a703 100644 --- a/src/server/api/common/read-messaging-message.ts +++ b/src/server/api/common/read-messaging-message.ts @@ -1,13 +1,13 @@ import * as mongo from 'mongodb'; import Message from '../../../models/messaging-message'; import { IMessagingMessage as IMessage } from '../../../models/messaging-message'; -import publishUserStream from '../../../publishers/stream'; -import { publishMessagingStream } from '../../../publishers/stream'; -import { publishMessagingIndexStream } from '../../../publishers/stream'; +import publishUserStream from '../../../stream'; +import { publishMessagingStream } from '../../../stream'; +import { publishMessagingIndexStream } from '../../../stream'; import User from '../../../models/user'; /** - * Mark as read message(s) + * Mark messages as read */ export default ( user: string | mongo.ObjectID, @@ -42,12 +42,12 @@ export default ( recipientId: userId, isRead: false }, { - $set: { - isRead: true - } - }, { - multi: true - }); + $set: { + isRead: true + } + }, { + multi: true + }); // Publish event publishMessagingStream(otherpartyId, userId, 'read', ids.map(id => id.toString())); @@ -59,8 +59,8 @@ export default ( recipientId: userId, isRead: false }, { - limit: 1 - }); + limit: 1 + }); if (count == 0) { // Update flag diff --git a/src/server/api/common/read-notification.ts b/src/server/api/common/read-notification.ts index 6505c58c39..3a1f4cfbde 100644 --- a/src/server/api/common/read-notification.ts +++ b/src/server/api/common/read-notification.ts @@ -1,11 +1,11 @@ import * as mongo from 'mongodb'; import { default as Notification, INotification } from '../../../models/notification'; -import publishUserStream from '../../../publishers/stream'; +import publishUserStream from '../../../stream'; import Mute from '../../../models/mute'; import User from '../../../models/user'; /** - * Mark as read notification(s) + * Mark notifications as read */ export default ( user: string | mongo.ObjectID, @@ -38,12 +38,12 @@ export default ( _id: { $in: ids }, isRead: false }, { - $set: { - isRead: true - } - }, { - multi: true - }); + $set: { + isRead: true + } + }, { + multi: true + }); // Calc count of my unread notifications const count = await Notification @@ -54,8 +54,8 @@ export default ( }, isRead: false }, { - limit: 1 - }); + limit: 1 + }); if (count == 0) { // Update flag diff --git a/src/server/api/endpoints.ts b/src/server/api/endpoints.ts index 146de67253..332a051ae1 100644 --- a/src/server/api/endpoints.ts +++ b/src/server/api/endpoints.ts @@ -1,20 +1,18 @@ -const ms = require('ms'); +import * as path from 'path'; +import * as glob from 'glob'; -/** - * エンドポイントを表します。 - */ -export type Endpoint = { +export interface IEndpointMeta { + desc?: any; - /** - * エンドポイント名 - */ - name: string; + params?: any; + + res?: any; /** * このエンドポイントにリクエストするのにユーザー情報が必須か否か * 省略した場合は false として解釈されます。 */ - withCredential?: boolean; + requireCredential?: boolean; /** * エンドポイントのリミテーションに関するやつ @@ -50,7 +48,7 @@ export type Endpoint = { * ファイルの添付を必要とするか否か * 省略した場合は false として解釈されます。 */ - withFile?: boolean; + requireFile?: boolean; /** * サードパーティアプリからはリクエストすることができないか否か @@ -63,590 +61,26 @@ export type Endpoint = { * パーミッションの実現に利用されます。 */ kind?: string; -}; - -const endpoints: Endpoint[] = [ - { - name: 'meta' - }, - { - name: 'stats' - }, - { - name: 'username/available' - }, - { - name: 'my/apps', - withCredential: true - }, - { - name: 'app/create', - withCredential: true, - limit: { - duration: ms('1day'), - max: 3 - } - }, - { - name: 'app/show' - }, - { - name: 'app/name_id/available' - }, - { - name: 'auth/session/generate' - }, - { - name: 'auth/session/show' - }, - { - name: 'auth/session/userkey' - }, - { - name: 'auth/accept', - withCredential: true, - secure: true - }, - { - name: 'auth/deny', - withCredential: true, - secure: true - }, - { - name: 'aggregation/notes', - }, - { - name: 'aggregation/users', - }, - { - name: 'aggregation/users/activity', - }, - { - name: 'aggregation/users/note', - }, - { - name: 'aggregation/users/followers' - }, - { - name: 'aggregation/users/following' - }, - { - name: 'aggregation/users/reaction' - }, - { - name: 'aggregation/notes/renote' - }, - { - name: 'aggregation/notes/reply' - }, - { - name: 'aggregation/notes/reaction' - }, - { - name: 'aggregation/notes/reactions' - }, - - { - name: 'sw/register', - withCredential: true - }, - - { - name: 'i', - withCredential: true - }, - { - name: 'i/2fa/register', - withCredential: true, - secure: true - }, - { - name: 'i/2fa/unregister', - withCredential: true, - secure: true - }, - { - name: 'i/2fa/done', - withCredential: true, - secure: true - }, - { - name: 'i/update', - withCredential: true, - limit: { - duration: ms('1day'), - max: 50 - }, - kind: 'account-write' - }, - { - name: 'i/update_home', - withCredential: true, - secure: true - }, - { - name: 'i/update_mobile_home', - withCredential: true, - secure: true - }, - { - name: 'i/update_widget', - withCredential: true, - secure: true - }, - { - name: 'i/change_password', - withCredential: true, - secure: true - }, - { - name: 'i/regenerate_token', - withCredential: true, - secure: true - }, - { - name: 'i/update_client_setting', - withCredential: true, - secure: true - }, - { - name: 'i/pin', - kind: 'account-write' - }, - { - name: 'i/appdata/get', - withCredential: true - }, - { - name: 'i/appdata/set', - withCredential: true - }, - { - name: 'i/signin_history', - withCredential: true, - kind: 'account-read' - }, - { - name: 'i/authorized_apps', - withCredential: true, - secure: true - }, - - { - name: 'i/notifications', - withCredential: true, - kind: 'notification-read' - }, - - { - name: 'i/favorites', - withCredential: true, - kind: 'favorites-read' - }, - - { - name: 'reversi/match', - withCredential: true - }, - - { - name: 'reversi/match/cancel', - withCredential: true - }, +} - { - name: 'reversi/invitations', - withCredential: true - }, - - { - name: 'reversi/games', - withCredential: true - }, - - { - name: 'reversi/games/show' - }, - - { - name: 'mute/create', - withCredential: true, - kind: 'account/write' - }, - { - name: 'mute/delete', - withCredential: true, - kind: 'account/write' - }, - { - name: 'mute/list', - withCredential: true, - kind: 'account/read' - }, - - { - name: 'notifications/delete', - withCredential: true, - kind: 'notification-write' - }, - { - name: 'notifications/delete_all', - withCredential: true, - kind: 'notification-write' - }, - { - name: 'notifications/mark_as_read_all', - withCredential: true, - kind: 'notification-write' - }, - - { - name: 'drive', - withCredential: true, - kind: 'drive-read' - }, - { - name: 'drive/stream', - withCredential: true, - kind: 'drive-read' - }, - { - name: 'drive/files', - withCredential: true, - kind: 'drive-read' - }, - { - name: 'drive/files/create', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 100 - }, - withFile: true, - kind: 'drive-write' - }, - { - name: 'drive/files/upload_from_url', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 10 - }, - kind: 'drive-write' - }, - { - name: 'drive/files/show', - withCredential: true, - kind: 'drive-read' - }, - { - name: 'drive/files/find', - withCredential: true, - kind: 'drive-read' - }, - { - name: 'drive/files/delete', - withCredential: true, - kind: 'drive-write' - }, - { - name: 'drive/files/update', - withCredential: true, - kind: 'drive-write' - }, - { - name: 'drive/folders', - withCredential: true, - kind: 'drive-read' - }, - { - name: 'drive/folders/create', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 50 - }, - kind: 'drive-write' - }, - { - name: 'drive/folders/show', - withCredential: true, - kind: 'drive-read' - }, - { - name: 'drive/folders/find', - withCredential: true, - kind: 'drive-read' - }, - { - name: 'drive/folders/update', - withCredential: true, - kind: 'drive-write' - }, - - { - name: 'users' - }, - { - name: 'users/show' - }, - { - name: 'users/search' - }, - { - name: 'users/search_by_username' - }, - { - name: 'users/notes' - }, - { - name: 'users/following' - }, - { - name: 'users/followers' - }, - { - name: 'users/recommendation', - withCredential: true, - kind: 'account-read' - }, - { - name: 'users/get_frequently_replied_users' - }, - - { - name: 'users/lists/show', - withCredential: true, - kind: 'account-read' - }, - { - name: 'users/lists/create', - withCredential: true, - kind: 'account-write' - }, - { - name: 'users/lists/push', - withCredential: true, - kind: 'account-write' - }, - { - name: 'users/lists/list', - withCredential: true, - kind: 'account-read' - }, - - { - name: 'following/create', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 100 - }, - kind: 'following-write' - }, - { - name: 'following/delete', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 100 - }, - kind: 'following-write' - }, - { - name: 'following/requests/accept', - withCredential: true, - kind: 'following-write' - }, - { - name: 'following/requests/reject', - withCredential: true, - kind: 'following-write' - }, - { - name: 'following/requests/cancel', - withCredential: true, - kind: 'following-write' - }, - { - name: 'following/requests/list', - withCredential: true, - kind: 'following-read' - }, - { - name: 'following/stalk', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 100 - }, - kind: 'following-write' - }, - { - name: 'following/unstalk', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 100 - }, - kind: 'following-write' - }, +export interface IEndpoint { + name: string; + exec: any; + meta: IEndpointMeta; +} - { - name: 'notes' - }, - { - name: 'notes/show' - }, - { - name: 'notes/replies' - }, - { - name: 'notes/conversation' - }, - { - name: 'notes/create', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 300, - minInterval: ms('1second') - }, - kind: 'note-write' - }, - { - name: 'notes/delete', - withCredential: true, - kind: 'note-write' - }, - { - name: 'notes/renotes' - }, - { - name: 'notes/search' - }, - { - name: 'notes/search_by_tag' - }, - { - name: 'notes/timeline', - withCredential: true, - limit: { - duration: ms('10minutes'), - max: 100 - } - }, - { - name: 'notes/local-timeline', - limit: { - duration: ms('10minutes'), - max: 100 - } - }, - { - name: 'notes/global-timeline', - limit: { - duration: ms('10minutes'), - max: 100 - } - }, - { - name: 'notes/user-list-timeline', - withCredential: true, - limit: { - duration: ms('10minutes'), - max: 100 - } - }, - { - name: 'notes/mentions', - withCredential: true, - limit: { - duration: ms('10minutes'), - max: 100 - } - }, - { - name: 'notes/trend', - withCredential: true - }, - { - name: 'notes/categorize', - withCredential: true - }, - { - name: 'notes/reactions', - withCredential: true - }, - { - name: 'notes/reactions/create', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 300 - }, - kind: 'reaction-write' - }, - { - name: 'notes/reactions/delete', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 100 - }, - kind: 'reaction-write' - }, - { - name: 'notes/favorites/create', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 100 - }, - kind: 'favorite-write' - }, - { - name: 'notes/favorites/delete', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 100 - }, - kind: 'favorite-write' - }, - { - name: 'notes/polls/vote', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 100 - }, - kind: 'vote-write' - }, - { - name: 'notes/polls/recommendation', - withCredential: true - }, +const files = glob.sync('**/*.js', { + cwd: path.resolve(__dirname + '/endpoints/') +}); - { - name: 'hashtags/trend' - }, +const endpoints: IEndpoint[] = files.map(f => { + const ep = require('./endpoints/' + f); - { - name: 'messaging/history', - withCredential: true, - kind: 'messaging-read' - }, - { - name: 'messaging/messages', - withCredential: true, - kind: 'messaging-read' - }, - { - name: 'messaging/messages/create', - withCredential: true, - kind: 'messaging-write' - } -]; + return { + name: f.replace('.js', ''), + exec: ep.default, + meta: ep.meta || {} + }; +}); export default endpoints; diff --git a/src/server/api/endpoints/aggregation/posts.ts b/src/server/api/endpoints/aggregation/posts.ts index 48e344312d..629bb19108 100644 --- a/src/server/api/endpoints/aggregation/posts.ts +++ b/src/server/api/endpoints/aggregation/posts.ts @@ -4,9 +4,9 @@ import Note from '../../../../models/note'; /** * Aggregate notes */ -module.exports = (params: any) => new Promise(async (res, rej) => { +export default (params: any) => new Promise(async (res, rej) => { // Get 'limit' parameter - const [limit = 365, limitErr] = $.num.optional().range(1, 365).get(params.limit); + const [limit = 365, limitErr] = $.num.optional.range(1, 365).get(params.limit); if (limitErr) return rej('invalid limit param'); const datas = await Note diff --git a/src/server/api/endpoints/aggregation/users.ts b/src/server/api/endpoints/aggregation/users.ts index c084404d0a..f1e41cf170 100644 --- a/src/server/api/endpoints/aggregation/users.ts +++ b/src/server/api/endpoints/aggregation/users.ts @@ -4,9 +4,9 @@ import User from '../../../../models/user'; /** * Aggregate users */ -module.exports = (params: any) => new Promise(async (res, rej) => { +export default (params: any) => new Promise(async (res, rej) => { // Get 'limit' parameter - const [limit = 365, limitErr] = $.num.optional().range(1, 365).get(params.limit); + const [limit = 365, limitErr] = $.num.optional.range(1, 365).get(params.limit); if (limitErr) return rej('invalid limit param'); const users = await User diff --git a/src/server/api/endpoints/aggregation/users/activity.ts b/src/server/api/endpoints/aggregation/users/activity.ts index d4c716d65b..0ec3f0db76 100644 --- a/src/server/api/endpoints/aggregation/users/activity.ts +++ b/src/server/api/endpoints/aggregation/users/activity.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import User from '../../../../../models/user'; import Note from '../../../../../models/note'; @@ -7,9 +7,9 @@ import Note from '../../../../../models/note'; /** * Aggregate activity of a user */ -module.exports = (params: any) => new Promise(async (res, rej) => { +export default (params: any) => new Promise(async (res, rej) => { // Get 'limit' parameter - const [limit = 365, limitErr] = $.num.optional().range(1, 365).get(params.limit); + const [limit = 365, limitErr] = $.num.optional.range(1, 365).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'userId' parameter diff --git a/src/server/api/endpoints/aggregation/users/followers.ts b/src/server/api/endpoints/aggregation/users/followers.ts index 847f376079..94eb83febc 100644 --- a/src/server/api/endpoints/aggregation/users/followers.ts +++ b/src/server/api/endpoints/aggregation/users/followers.ts @@ -1,14 +1,14 @@ /** * Module dependencies */ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import User from '../../../../../models/user'; import FollowedLog from '../../../../../models/followed-log'; /** * Aggregate followers of a user */ -module.exports = (params: any) => new Promise(async (res, rej) => { +export default (params: any) => new Promise(async (res, rej) => { // Get 'userId' parameter const [userId, userIdErr] = $.type(ID).get(params.userId); if (userIdErr) return rej('invalid userId param'); diff --git a/src/server/api/endpoints/aggregation/users/following.ts b/src/server/api/endpoints/aggregation/users/following.ts index 6c52752f98..d2e4d256fe 100644 --- a/src/server/api/endpoints/aggregation/users/following.ts +++ b/src/server/api/endpoints/aggregation/users/following.ts @@ -1,14 +1,14 @@ /** * Module dependencies */ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import User from '../../../../../models/user'; import FollowingLog from '../../../../../models/following-log'; /** * Aggregate following of a user */ -module.exports = (params: any) => new Promise(async (res, rej) => { +export default (params: any) => new Promise(async (res, rej) => { // Get 'userId' parameter const [userId, userIdErr] = $.type(ID).get(params.userId); if (userIdErr) return rej('invalid userId param'); diff --git a/src/server/api/endpoints/aggregation/users/post.ts b/src/server/api/endpoints/aggregation/users/post.ts index 28ba1482bf..090f6d2f09 100644 --- a/src/server/api/endpoints/aggregation/users/post.ts +++ b/src/server/api/endpoints/aggregation/users/post.ts @@ -1,11 +1,11 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import User from '../../../../../models/user'; import Note from '../../../../../models/note'; /** * Aggregate note of a user */ -module.exports = (params: any) => new Promise(async (res, rej) => { +export default (params: any) => new Promise(async (res, rej) => { // Get 'userId' parameter const [userId, userIdErr] = $.type(ID).get(params.userId); if (userIdErr) return rej('invalid userId param'); diff --git a/src/server/api/endpoints/aggregation/users/reaction.ts b/src/server/api/endpoints/aggregation/users/reaction.ts index adb5acfb4e..ce9e150966 100644 --- a/src/server/api/endpoints/aggregation/users/reaction.ts +++ b/src/server/api/endpoints/aggregation/users/reaction.ts @@ -1,11 +1,11 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import User from '../../../../../models/user'; import Reaction from '../../../../../models/note-reaction'; /** * Aggregate reaction of a user */ -module.exports = (params: any) => new Promise(async (res, rej) => { +export default (params: any) => new Promise(async (res, rej) => { // Get 'userId' parameter const [userId, userIdErr] = $.type(ID).get(params.userId); if (userIdErr) return rej('invalid userId param'); diff --git a/src/server/api/endpoints/app/create.ts b/src/server/api/endpoints/app/create.ts index c7bc91a079..5df8bd2f25 100644 --- a/src/server/api/endpoints/app/create.ts +++ b/src/server/api/endpoints/app/create.ts @@ -3,63 +3,14 @@ import $ from 'cafy'; import App, { isValidNameId, pack } from '../../../../models/app'; import { ILocalUser } from '../../../../models/user'; -/** - * @swagger - * /app/create: - * note: - * summary: Create an application - * parameters: - * - $ref: "#/parameters/AccessToken" - * - - * name: nameId - * description: Application unique name - * in: formData - * required: true - * type: string - * - - * name: name - * description: Application name - * in: formData - * required: true - * type: string - * - - * name: description - * description: Application description - * in: formData - * required: true - * type: string - * - - * name: permission - * description: Permissions that application has - * in: formData - * required: true - * type: array - * items: - * type: string - * collectionFormat: csv - * - - * name: callbackUrl - * description: URL called back after authentication - * in: formData - * required: false - * type: string - * - * responses: - * 200: - * description: Created application's information - * schema: - * $ref: "#/definitions/Application" - * - * default: - * description: Failed - * schema: - * $ref: "#/definitions/Error" - */ +export const meta = { + requireCredential: true +}; /** * Create an app */ -module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'nameId' parameter const [nameId, nameIdErr] = $.str.pipe(isValidNameId).get(params.nameId); if (nameIdErr) return rej('invalid nameId param'); @@ -78,7 +29,7 @@ module.exports = async (params: any, user: ILocalUser) => new Promise(async (res // Get 'callbackUrl' parameter // TODO: Check it is valid url - const [callbackUrl = null, callbackUrlErr] = $.str.optional().nullable().get(params.callbackUrl); + const [callbackUrl = null, callbackUrlErr] = $.str.optional.nullable.get(params.callbackUrl); if (callbackUrlErr) return rej('invalid callbackUrl param'); // Generate secret diff --git a/src/server/api/endpoints/app/name_id/available.ts b/src/server/api/endpoints/app/name_id/available.ts index 58101a7e6a..2cd56e92d6 100644 --- a/src/server/api/endpoints/app/name_id/available.ts +++ b/src/server/api/endpoints/app/name_id/available.ts @@ -6,41 +6,12 @@ import App from '../../../../../models/app'; import { isValidNameId } from '../../../../../models/app'; /** - * @swagger - * /app/nameId/available: - * note: - * summary: Check available nameId on creation an application - * parameters: - * - - * name: nameId - * description: Application unique name - * in: formData - * required: true - * type: string - * - * responses: - * 200: - * description: Success - * schema: - * type: object - * properties: - * available: - * description: Whether nameId is available - * type: boolean - * - * default: - * description: Failed - * schema: - * $ref: "#/definitions/Error" - */ - -/** * Check available nameId of app * * @param {any} params * @return {Promise<any>} */ -module.exports = async (params: any) => new Promise(async (res, rej) => { +export default async (params: any) => new Promise(async (res, rej) => { // Get 'nameId' parameter const [nameId, nameIdErr] = $.str.pipe(isValidNameId).get(params.nameId); if (nameIdErr) return rej('invalid nameId param'); diff --git a/src/server/api/endpoints/app/show.ts b/src/server/api/endpoints/app/show.ts index 2b98a3f142..6668d0f243 100644 --- a/src/server/api/endpoints/app/show.ts +++ b/src/server/api/endpoints/app/show.ts @@ -1,49 +1,19 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import App, { pack, IApp } from '../../../../models/app'; import { ILocalUser } from '../../../../models/user'; /** - * @swagger - * /app/show: - * note: - * summary: Show an application's information - * description: Require appId or nameId - * parameters: - * - - * name: appId - * description: Application ID - * in: formData - * type: string - * - - * name: nameId - * description: Application unique name - * in: formData - * type: string - * - * responses: - * 200: - * description: Success - * schema: - * $ref: "#/definitions/Application" - * - * default: - * description: Failed - * schema: - * $ref: "#/definitions/Error" - */ - -/** * Show an app */ -module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { +export default (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { const isSecure = user != null && app == null; // Get 'appId' parameter - const [appId, appIdErr] = $.type(ID).optional().get(params.appId); + const [appId, appIdErr] = $.type(ID).optional.get(params.appId); if (appIdErr) return rej('invalid appId param'); // Get 'nameId' parameter - const [nameId, nameIdErr] = $.str.optional().get(params.nameId); + const [nameId, nameIdErr] = $.str.optional.get(params.nameId); if (nameIdErr) return rej('invalid nameId param'); if (appId === undefined && nameId === undefined) { diff --git a/src/server/api/endpoints/auth/accept.ts b/src/server/api/endpoints/auth/accept.ts index fc6cbc473d..fee68a20a6 100644 --- a/src/server/api/endpoints/auth/accept.ts +++ b/src/server/api/endpoints/auth/accept.ts @@ -6,33 +6,15 @@ import AuthSess from '../../../../models/auth-session'; import AccessToken from '../../../../models/access-token'; import { ILocalUser } from '../../../../models/user'; -/** - * @swagger - * /auth/accept: - * note: - * summary: Accept a session - * parameters: - * - $ref: "#/parameters/NativeToken" - * - - * name: token - * description: Session Token - * in: formData - * required: true - * type: string - * responses: - * 204: - * description: OK - * - * default: - * description: Failed - * schema: - * $ref: "#/definitions/Error" - */ +export const meta = { + requireCredential: true, + secure: true +}; /** * Accept */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'token' parameter const [token, tokenErr] = $.str.get(params.token); if (tokenErr) return rej('invalid token param'); diff --git a/src/server/api/endpoints/auth/session/generate.ts b/src/server/api/endpoints/auth/session/generate.ts index 5a4d99ff3b..bd1face9e3 100644 --- a/src/server/api/endpoints/auth/session/generate.ts +++ b/src/server/api/endpoints/auth/session/generate.ts @@ -8,43 +8,12 @@ import AuthSess from '../../../../../models/auth-session'; import config from '../../../../../config'; /** - * @swagger - * /auth/session/generate: - * note: - * summary: Generate a session - * parameters: - * - - * name: appSecret - * description: App Secret - * in: formData - * required: true - * type: string - * - * responses: - * 200: - * description: OK - * schema: - * type: object - * properties: - * token: - * type: string - * description: Session Token - * url: - * type: string - * description: Authentication form's URL - * default: - * description: Failed - * schema: - * $ref: "#/definitions/Error" - */ - -/** * Generate a session * * @param {any} params * @return {Promise<any>} */ -module.exports = (params: any) => new Promise(async (res, rej) => { +export default (params: any) => new Promise(async (res, rej) => { // Get 'appSecret' parameter const [appSecret, appSecretErr] = $.str.get(params.appSecret); if (appSecretErr) return rej('invalid appSecret param'); diff --git a/src/server/api/endpoints/auth/session/show.ts b/src/server/api/endpoints/auth/session/show.ts index 3d3b6bbf61..f2cbfe388e 100644 --- a/src/server/api/endpoints/auth/session/show.ts +++ b/src/server/api/endpoints/auth/session/show.ts @@ -3,49 +3,9 @@ import AuthSess, { pack } from '../../../../../models/auth-session'; import { ILocalUser } from '../../../../../models/user'; /** - * @swagger - * /auth/session/show: - * note: - * summary: Show a session information - * parameters: - * - - * name: token - * description: Session Token - * in: formData - * required: true - * type: string - * - * responses: - * 200: - * description: OK - * schema: - * type: object - * properties: - * createdAt: - * type: string - * format: date-time - * description: Date and time of the session creation - * appId: - * type: string - * description: Application ID - * token: - * type: string - * description: Session Token - * userId: - * type: string - * description: ID of user who create the session - * app: - * $ref: "#/definitions/Application" - * default: - * description: Failed - * schema: - * $ref: "#/definitions/Error" - */ - -/** * Show a session */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'token' parameter const [token, tokenErr] = $.str.get(params.token); if (tokenErr) return rej('invalid token param'); diff --git a/src/server/api/endpoints/auth/session/userkey.ts b/src/server/api/endpoints/auth/session/userkey.ts index 3ea48fbe34..97f28464a5 100644 --- a/src/server/api/endpoints/auth/session/userkey.ts +++ b/src/server/api/endpoints/auth/session/userkey.ts @@ -8,48 +8,12 @@ import AccessToken from '../../../../../models/access-token'; import { pack } from '../../../../../models/user'; /** - * @swagger - * /auth/session/userkey: - * note: - * summary: Get an access token(userkey) - * parameters: - * - - * name: appSecret - * description: App Secret - * in: formData - * required: true - * type: string - * - - * name: token - * description: Session Token - * in: formData - * required: true - * type: string - * - * responses: - * 200: - * description: OK - * schema: - * type: object - * properties: - * userkey: - * type: string - * description: Access Token - * user: - * $ref: "#/definitions/User" - * default: - * description: Failed - * schema: - * $ref: "#/definitions/Error" - */ - -/** * Generate a session * * @param {any} params * @return {Promise<any>} */ -module.exports = (params: any) => new Promise(async (res, rej) => { +export default (params: any) => new Promise(async (res, rej) => { // Get 'appSecret' parameter const [appSecret, appSecretErr] = $.str.get(params.appSecret); if (appSecretErr) return rej('invalid appSecret param'); diff --git a/src/server/api/endpoints/drive.ts b/src/server/api/endpoints/drive.ts index 9caad273c8..8ad961494f 100644 --- a/src/server/api/endpoints/drive.ts +++ b/src/server/api/endpoints/drive.ts @@ -1,10 +1,19 @@ import DriveFile from '../../../models/drive-file'; import { ILocalUser } from '../../../models/user'; +import config from '../../../config'; -/** - * Get drive information - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: 'ドライブの情報を取得します。', + en: 'Get drive information.' + }, + + requireCredential: true, + + kind: 'drive-read' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Calculate drive usage const usage = await DriveFile .aggregate([{ @@ -30,7 +39,7 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) }); res({ - capacity: user.driveCapacity, + capacity: 1024 * 1024 * config.localDriveCapacityMb, usage: usage }); }); diff --git a/src/server/api/endpoints/drive/files.ts b/src/server/api/endpoints/drive/files.ts index efce750747..063b4adde1 100644 --- a/src/server/api/endpoints/drive/files.ts +++ b/src/server/api/endpoints/drive/files.ts @@ -1,21 +1,29 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import DriveFile, { pack } from '../../../../models/drive-file'; import { ILocalUser } from '../../../../models/user'; -/** - * Get drive files - */ -module.exports = async (params: any, user: ILocalUser) => { +export const meta = { + desc: { + ja: 'ドライブのファイル一覧を取得します。', + en: 'Get files of drive.' + }, + + requireCredential: true, + + kind: 'drive-read' +}; + +export default async (params: any, user: ILocalUser) => { // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) throw 'invalid limit param'; // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId); + const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); if (sinceIdErr) throw 'invalid sinceId param'; // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId); + const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); if (untilIdErr) throw 'invalid untilId param'; // Check if both of sinceId and untilId is specified @@ -24,11 +32,11 @@ module.exports = async (params: any, user: ILocalUser) => { } // Get 'folderId' parameter - const [folderId = null, folderIdErr] = $.type(ID).optional().nullable().get(params.folderId); + const [folderId = null, folderIdErr] = $.type(ID).optional.nullable.get(params.folderId); if (folderIdErr) throw 'invalid folderId param'; // Get 'type' parameter - const [type, typeErr] = $.str.optional().match(/^[a-zA-Z\/\-\*]+$/).get(params.type); + const [type, typeErr] = $.str.optional.match(/^[a-zA-Z\/\-\*]+$/).get(params.type); if (typeErr) throw 'invalid type param'; // Construct query diff --git a/src/server/api/endpoints/drive/files/create.ts b/src/server/api/endpoints/drive/files/create.ts index db2626af09..41b7e04b46 100644 --- a/src/server/api/endpoints/drive/files/create.ts +++ b/src/server/api/endpoints/drive/files/create.ts @@ -1,13 +1,50 @@ import * as fs from 'fs'; -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +const ms = require('ms'); +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import { validateFileName, pack } from '../../../../../models/drive-file'; import create from '../../../../../services/drive/add-file'; import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; + +export const meta = { + desc: { + ja: 'ドライブにファイルをアップロードします。', + en: 'Upload a file to drive.' + }, + + requireCredential: true, + + limit: { + duration: ms('1hour'), + max: 100 + }, + + requireFile: true, + + kind: 'drive-write', + + params: { + folderId: $.type(ID).optional.nullable.note({ + default: null, + desc: { + ja: 'フォルダID' + } + }), + + isSensitive: $.bool.optional.note({ + default: false, + desc: { + ja: 'このメディアが「閲覧注意」(NSFW)かどうか', + en: 'Whether this media is NSFW' + } + }) + } +}; /** * Create a file */ -module.exports = async (file: any, params: any, user: ILocalUser): Promise<any> => { +export default async (file: any, params: any, user: ILocalUser): Promise<any> => { if (file == null) { throw 'file is required'; } @@ -27,17 +64,19 @@ module.exports = async (file: any, params: any, user: ILocalUser): Promise<any> name = null; } - // Get 'folderId' parameter - const [folderId = null, folderIdErr] = $.type(ID).optional().nullable().get(params.folderId); - if (folderIdErr) throw 'invalid folderId param'; - function cleanup() { fs.unlink(file.path, () => {}); } + const [ps, psErr] = getParams(meta, params); + if (psErr) { + cleanup(); + throw psErr; + } + try { // Create file - const driveFile = await create(user, file.path, name, null, folderId); + const driveFile = await create(user, file.path, name, null, ps.folderId, false, false, null, null, ps.isSensitive); cleanup(); diff --git a/src/server/api/endpoints/drive/files/delete.ts b/src/server/api/endpoints/drive/files/delete.ts index 17eb0eb4b9..02cd96dd8f 100644 --- a/src/server/api/endpoints/drive/files/delete.ts +++ b/src/server/api/endpoints/drive/files/delete.ts @@ -1,13 +1,21 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import DriveFile from '../../../../../models/drive-file'; import del from '../../../../../services/drive/delete-file'; -import { publishDriveStream } from '../../../../../publishers/stream'; +import { publishDriveStream } from '../../../../../stream'; import { ILocalUser } from '../../../../../models/user'; -/** - * Delete a file - */ -module.exports = async (params: any, user: ILocalUser) => { +export const meta = { + desc: { + ja: 'ドライブのファイルを削除します。', + en: 'Delete a file of drive.' + }, + + requireCredential: true, + + kind: 'drive-write' +}; + +export default async (params: any, user: ILocalUser) => { // Get 'fileId' parameter const [fileId, fileIdErr] = $.type(ID).get(params.fileId); if (fileIdErr) throw 'invalid fileId param'; diff --git a/src/server/api/endpoints/drive/files/find.ts b/src/server/api/endpoints/drive/files/find.ts index 75ab91f0a1..aa44ee688e 100644 --- a/src/server/api/endpoints/drive/files/find.ts +++ b/src/server/api/endpoints/drive/files/find.ts @@ -1,17 +1,20 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import DriveFile, { pack } from '../../../../../models/drive-file'; import { ILocalUser } from '../../../../../models/user'; -/** - * Find a file(s) - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + requireCredential: true, + + kind: 'drive-read' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'name' parameter const [name, nameErr] = $.str.get(params.name); if (nameErr) return rej('invalid name param'); // Get 'folderId' parameter - const [folderId = null, folderIdErr] = $.type(ID).optional().nullable().get(params.folderId); + const [folderId = null, folderIdErr] = $.type(ID).optional.nullable.get(params.folderId); if (folderIdErr) return rej('invalid folderId param'); // Issue query diff --git a/src/server/api/endpoints/drive/files/show.ts b/src/server/api/endpoints/drive/files/show.ts index e7dca486c5..6a66c7a272 100644 --- a/src/server/api/endpoints/drive/files/show.ts +++ b/src/server/api/endpoints/drive/files/show.ts @@ -1,11 +1,19 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import DriveFile, { pack } from '../../../../../models/drive-file'; import { ILocalUser } from '../../../../../models/user'; -/** - * Show a file - */ -module.exports = async (params: any, user: ILocalUser) => { +export const meta = { + desc: { + ja: '指定したドライブのファイルの情報を取得します。', + en: 'Get specified file of drive.' + }, + + requireCredential: true, + + kind: 'drive-read' +}; + +export default async (params: any, user: ILocalUser) => { // Get 'fileId' parameter const [fileId, fileIdErr] = $.type(ID).get(params.fileId); if (fileIdErr) throw 'invalid fileId param'; diff --git a/src/server/api/endpoints/drive/files/update.ts b/src/server/api/endpoints/drive/files/update.ts index 825683b214..bac04bae78 100644 --- a/src/server/api/endpoints/drive/files/update.ts +++ b/src/server/api/endpoints/drive/files/update.ts @@ -1,21 +1,60 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import DriveFolder from '../../../../../models/drive-folder'; import DriveFile, { validateFileName, pack } from '../../../../../models/drive-file'; -import { publishDriveStream } from '../../../../../publishers/stream'; +import { publishDriveStream } from '../../../../../stream'; import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; -/** - * Update a file - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - // Get 'fileId' parameter - const [fileId, fileIdErr] = $.type(ID).get(params.fileId); - if (fileIdErr) return rej('invalid fileId param'); +export const meta = { + desc: { + ja: '指定したドライブのファイルの情報を更新します。', + en: 'Update specified file of drive.' + }, + + requireCredential: true, + + kind: 'drive-write', + + params: { + fileId: $.type(ID).note({ + desc: { + ja: '対象のファイルID' + } + }), + + folderId: $.type(ID).optional.nullable.note({ + default: undefined, + desc: { + ja: 'フォルダID' + } + }), + + name: $.str.optional.pipe(validateFileName).note({ + default: undefined, + desc: { + ja: 'ファイル名', + en: 'Name of the file' + } + }), + + isSensitive: $.bool.optional.note({ + default: undefined, + desc: { + ja: 'このメディアが「閲覧注意」(NSFW)かどうか', + en: 'Whether this media is NSFW' + } + }) + } +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Fetch file const file = await DriveFile .findOne({ - _id: fileId, + _id: ps.fileId, 'metadata.userId': user._id }); @@ -23,23 +62,18 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) return rej('file-not-found'); } - // Get 'name' parameter - const [name, nameErr] = $.str.optional().pipe(validateFileName).get(params.name); - if (nameErr) return rej('invalid name param'); - if (name) file.filename = name; + if (ps.name) file.filename = ps.name; - // Get 'folderId' parameter - const [folderId, folderIdErr] = $.type(ID).optional().nullable().get(params.folderId); - if (folderIdErr) return rej('invalid folderId param'); + if (ps.isSensitive) file.metadata.isSensitive = ps.isSensitive; - if (folderId !== undefined) { - if (folderId === null) { + if (ps.folderId !== undefined) { + if (ps.folderId === null) { file.metadata.folderId = null; } else { // Fetch folder const folder = await DriveFolder .findOne({ - _id: folderId, + _id: ps.folderId, userId: user._id }); @@ -54,7 +88,8 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) await DriveFile.update(file._id, { $set: { filename: file.filename, - 'metadata.folderId': file.metadata.folderId + 'metadata.folderId': file.metadata.folderId, + 'metadata.isSensitive': file.metadata.isSensitive } }); diff --git a/src/server/api/endpoints/drive/files/upload_from_url.ts b/src/server/api/endpoints/drive/files/upload_from_url.ts index cb617d851f..d634cf46db 100644 --- a/src/server/api/endpoints/drive/files/upload_from_url.ts +++ b/src/server/api/endpoints/drive/files/upload_from_url.ts @@ -1,22 +1,35 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +const ms = require('ms'); import { pack } from '../../../../../models/drive-file'; import uploadFromUrl from '../../../../../services/drive/upload-from-url'; import { ILocalUser } from '../../../../../models/user'; +export const meta = { + desc: { + ja: 'ドライブに指定されたURLに存在するファイルをアップロードします。' + }, + + limit: { + duration: ms('1hour'), + max: 10 + }, + + requireCredential: true, + + kind: 'drive-write' +}; + /** * Create a file from a URL */ -module.exports = async (params: any, user: ILocalUser): Promise<any> => { +export default async (params: any, user: ILocalUser): Promise<any> => { // Get 'url' parameter // TODO: Validate this url const [url, urlErr] = $.str.get(params.url); if (urlErr) throw 'invalid url param'; // Get 'folderId' parameter - const [folderId = null, folderIdErr] = $.type(ID).optional().nullable().get(params.folderId); + const [folderId = null, folderIdErr] = $.type(ID).optional.nullable.get(params.folderId); if (folderIdErr) throw 'invalid folderId param'; return pack(await uploadFromUrl(url, user, folderId)); diff --git a/src/server/api/endpoints/drive/folders.ts b/src/server/api/endpoints/drive/folders.ts index 3413778950..de398eb720 100644 --- a/src/server/api/endpoints/drive/folders.ts +++ b/src/server/api/endpoints/drive/folders.ts @@ -1,21 +1,29 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import DriveFolder, { pack } from '../../../../models/drive-folder'; import { ILocalUser } from '../../../../models/user'; -/** - * Get drive folders - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: 'ドライブのフォルダ一覧を取得します。', + en: 'Get folders of drive.' + }, + + requireCredential: true, + + kind: 'drive-read' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId); + const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); if (sinceIdErr) return rej('invalid sinceId param'); // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId); + const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); if (untilIdErr) return rej('invalid untilId param'); // Check if both of sinceId and untilId is specified @@ -24,7 +32,7 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) } // Get 'folderId' parameter - const [folderId = null, folderIdErr] = $.type(ID).optional().nullable().get(params.folderId); + const [folderId = null, folderIdErr] = $.type(ID).optional.nullable.get(params.folderId); if (folderIdErr) return rej('invalid folderId param'); // Construct query diff --git a/src/server/api/endpoints/drive/folders/create.ts b/src/server/api/endpoints/drive/folders/create.ts index 8f06b0f668..03f9504774 100644 --- a/src/server/api/endpoints/drive/folders/create.ts +++ b/src/server/api/endpoints/drive/folders/create.ts @@ -1,18 +1,26 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import DriveFolder, { isValidFolderName, pack } from '../../../../../models/drive-folder'; -import { publishDriveStream } from '../../../../../publishers/stream'; +import { publishDriveStream } from '../../../../../stream'; import { ILocalUser } from '../../../../../models/user'; -/** - * Create drive folder - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: 'ドライブのフォルダを作成します。', + en: 'Create a folder of drive.' + }, + + requireCredential: true, + + kind: 'drive-write' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'name' parameter - const [name = '無題のフォルダー', nameErr] = $.str.optional().pipe(isValidFolderName).get(params.name); + const [name = '無題のフォルダー', nameErr] = $.str.optional.pipe(isValidFolderName).get(params.name); if (nameErr) return rej('invalid name param'); // Get 'parentId' parameter - const [parentId = null, parentIdErr] = $.type(ID).optional().nullable().get(params.parentId); + const [parentId = null, parentIdErr] = $.type(ID).optional.nullable.get(params.parentId); if (parentIdErr) return rej('invalid parentId param'); // If the parent folder is specified diff --git a/src/server/api/endpoints/drive/folders/find.ts b/src/server/api/endpoints/drive/folders/find.ts index b3238b5c32..ec3c1d2e36 100644 --- a/src/server/api/endpoints/drive/folders/find.ts +++ b/src/server/api/endpoints/drive/folders/find.ts @@ -1,17 +1,20 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import DriveFolder, { pack } from '../../../../../models/drive-folder'; import { ILocalUser } from '../../../../../models/user'; -/** - * Find a folder(s) - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + requireCredential: true, + + kind: 'drive-read' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'name' parameter const [name, nameErr] = $.str.get(params.name); if (nameErr) return rej('invalid name param'); // Get 'parentId' parameter - const [parentId = null, parentIdErr] = $.type(ID).optional().nullable().get(params.parentId); + const [parentId = null, parentIdErr] = $.type(ID).optional.nullable.get(params.parentId); if (parentIdErr) return rej('invalid parentId param'); // Issue query diff --git a/src/server/api/endpoints/drive/folders/show.ts b/src/server/api/endpoints/drive/folders/show.ts index c9b4930a76..6a6c879a01 100644 --- a/src/server/api/endpoints/drive/folders/show.ts +++ b/src/server/api/endpoints/drive/folders/show.ts @@ -1,11 +1,18 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import DriveFolder, { pack } from '../../../../../models/drive-folder'; import { ILocalUser } from '../../../../../models/user'; -/** - * Show a folder - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '指定したドライブのフォルダの情報を取得します。' + }, + + requireCredential: true, + + kind: 'drive-read' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'folderId' parameter const [folderId, folderIdErr] = $.type(ID).get(params.folderId); if (folderIdErr) return rej('invalid folderId param'); diff --git a/src/server/api/endpoints/drive/folders/update.ts b/src/server/api/endpoints/drive/folders/update.ts index f126c09f5b..1b449428a6 100644 --- a/src/server/api/endpoints/drive/folders/update.ts +++ b/src/server/api/endpoints/drive/folders/update.ts @@ -1,12 +1,20 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import DriveFolder, { isValidFolderName, pack } from '../../../../../models/drive-folder'; -import { publishDriveStream } from '../../../../../publishers/stream'; +import { publishDriveStream } from '../../../../../stream'; import { ILocalUser } from '../../../../../models/user'; -/** - * Update a folder - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '指定したドライブのフォルダの情報を更新します。', + en: 'Update specified folder of drive.' + }, + + requireCredential: true, + + kind: 'drive-write' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'folderId' parameter const [folderId, folderIdErr] = $.type(ID).get(params.folderId); if (folderIdErr) return rej('invalid folderId param'); @@ -23,12 +31,12 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) } // Get 'name' parameter - const [name, nameErr] = $.str.optional().pipe(isValidFolderName).get(params.name); + const [name, nameErr] = $.str.optional.pipe(isValidFolderName).get(params.name); if (nameErr) return rej('invalid name param'); if (name) folder.name = name; // Get 'parentId' parameter - const [parentId, parentIdErr] = $.type(ID).optional().nullable().get(params.parentId); + const [parentId, parentIdErr] = $.type(ID).optional.nullable.get(params.parentId); if (parentIdErr) return rej('invalid parentId param'); if (parentId !== undefined) { if (parentId === null) { diff --git a/src/server/api/endpoints/drive/stream.ts b/src/server/api/endpoints/drive/stream.ts index 515f74645a..53f94a2639 100644 --- a/src/server/api/endpoints/drive/stream.ts +++ b/src/server/api/endpoints/drive/stream.ts @@ -1,21 +1,24 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import DriveFile, { pack } from '../../../../models/drive-file'; import { ILocalUser } from '../../../../models/user'; -/** - * Get drive stream - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + requireCredential: true, + + kind: 'drive-read' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId); + const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); if (sinceIdErr) return rej('invalid sinceId param'); // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId); + const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); if (untilIdErr) return rej('invalid untilId param'); // Check if both of sinceId and untilId is specified @@ -24,7 +27,7 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) } // Get 'type' parameter - const [type, typeErr] = $.str.optional().match(/^[a-zA-Z\/\-\*]+$/).get(params.type); + const [type, typeErr] = $.str.optional.match(/^[a-zA-Z\/\-\*]+$/).get(params.type); if (typeErr) return rej('invalid type param'); // Construct query diff --git a/src/server/api/endpoints/following/create.ts b/src/server/api/endpoints/following/create.ts index 3e45b8da53..ebe319e0cf 100644 --- a/src/server/api/endpoints/following/create.ts +++ b/src/server/api/endpoints/following/create.ts @@ -1,12 +1,26 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +const ms = require('ms'); import User, { pack, ILocalUser } from '../../../../models/user'; import Following from '../../../../models/following'; import create from '../../../../services/following/create'; -/** - * Follow a user - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '指定したユーザーをフォローします。', + en: 'Follow a user.' + }, + + limit: { + duration: ms('1hour'), + max: 100 + }, + + requireCredential: true, + + kind: 'following-write' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { const follower = user; // Get 'userId' parameter diff --git a/src/server/api/endpoints/following/delete.ts b/src/server/api/endpoints/following/delete.ts index 0af8813cf9..4806fe4e39 100644 --- a/src/server/api/endpoints/following/delete.ts +++ b/src/server/api/endpoints/following/delete.ts @@ -1,12 +1,26 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +const ms = require('ms'); import User, { pack, ILocalUser } from '../../../../models/user'; import Following from '../../../../models/following'; import deleteFollowing from '../../../../services/following/delete'; -/** - * Unfollow a user - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '指定したユーザーのフォローを解除します。', + en: 'Unfollow a user.' + }, + + limit: { + duration: ms('1hour'), + max: 100 + }, + + requireCredential: true, + + kind: 'following-write' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { const follower = user; // Get 'userId' parameter diff --git a/src/server/api/endpoints/following/requests/accept.ts b/src/server/api/endpoints/following/requests/accept.ts index a09e32e4d9..b3bf2dd667 100644 --- a/src/server/api/endpoints/following/requests/accept.ts +++ b/src/server/api/endpoints/following/requests/accept.ts @@ -1,11 +1,19 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import acceptFollowRequest from '../../../../../services/following/requests/accept'; import User, { ILocalUser } from '../../../../../models/user'; -/** - * Accept a follow request - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '自分に届いた、指定したフォローリクエストを承認します。', + en: 'Accept a follow request.' + }, + + requireCredential: true, + + kind: 'following-write' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'userId' parameter const [followerId, followerIdErr] = $.type(ID).get(params.userId); if (followerIdErr) return rej('invalid userId param'); diff --git a/src/server/api/endpoints/following/requests/cancel.ts b/src/server/api/endpoints/following/requests/cancel.ts index f2a40854c2..9bfc40ce65 100644 --- a/src/server/api/endpoints/following/requests/cancel.ts +++ b/src/server/api/endpoints/following/requests/cancel.ts @@ -1,11 +1,19 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import cancelFollowRequest from '../../../../../services/following/requests/cancel'; import User, { pack, ILocalUser } from '../../../../../models/user'; -/** - * Cancel a follow request - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '自分が作成した、指定したフォローリクエストをキャンセルします。', + en: 'Cancel a follow request.' + }, + + requireCredential: true, + + kind: 'following-write' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'userId' parameter const [followeeId, followeeIdErr] = $.type(ID).get(params.userId); if (followeeIdErr) return rej('invalid userId param'); diff --git a/src/server/api/endpoints/following/requests/list.ts b/src/server/api/endpoints/following/requests/list.ts index 287c5a8e46..b06a158c08 100644 --- a/src/server/api/endpoints/following/requests/list.ts +++ b/src/server/api/endpoints/following/requests/list.ts @@ -2,10 +2,18 @@ import FollowRequest, { pack } from '../../../../../models/follow-request'; import { ILocalUser } from '../../../../../models/user'; -/** - * Get all pending received follow requests - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '自分に届いたフォローリクエストの一覧を取得します。', + en: 'Get all pending received follow requests.' + }, + + requireCredential: true, + + kind: 'following-read' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { const reqs = await FollowRequest.find({ followeeId: user._id }); diff --git a/src/server/api/endpoints/following/requests/reject.ts b/src/server/api/endpoints/following/requests/reject.ts index 69dddf1355..a232549bb8 100644 --- a/src/server/api/endpoints/following/requests/reject.ts +++ b/src/server/api/endpoints/following/requests/reject.ts @@ -1,11 +1,19 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import rejectFollowRequest from '../../../../../services/following/requests/reject'; import User, { ILocalUser } from '../../../../../models/user'; -/** - * Reject a follow request - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '自分に届いた、指定したフォローリクエストを拒否します。', + en: 'Reject a follow request.' + }, + + requireCredential: true, + + kind: 'following-write' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'userId' parameter const [followerId, followerIdErr] = $.type(ID).get(params.userId); if (followerIdErr) return rej('invalid userId param'); diff --git a/src/server/api/endpoints/following/stalk.ts b/src/server/api/endpoints/following/stalk.ts index b9d19d57b4..79a3fb976c 100644 --- a/src/server/api/endpoints/following/stalk.ts +++ b/src/server/api/endpoints/following/stalk.ts @@ -1,11 +1,19 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Following from '../../../../models/following'; import { ILocalUser } from '../../../../models/user'; -/** - * Stalk a user - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '指定したユーザーをストーキングします。', + en: 'Stalk a user.' + }, + + requireCredential: true, + + kind: 'following-write' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { const follower = user; // Get 'userId' parameter diff --git a/src/server/api/endpoints/following/unstalk.ts b/src/server/api/endpoints/following/unstalk.ts index 255f22ca1f..71a7a97eeb 100644 --- a/src/server/api/endpoints/following/unstalk.ts +++ b/src/server/api/endpoints/following/unstalk.ts @@ -1,11 +1,19 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Following from '../../../../models/following'; import { ILocalUser } from '../../../../models/user'; -/** - * Unstalk a user - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '指定したユーザーのストーキングをやめます。', + en: 'Unstalk a user.' + }, + + requireCredential: true, + + kind: 'following-write' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { const follower = user; // Get 'userId' parameter diff --git a/src/server/api/endpoints/reversi/games.ts b/src/server/api/endpoints/games/reversi/games.ts index 1455f191f7..b72af06d22 100644 --- a/src/server/api/endpoints/reversi/games.ts +++ b/src/server/api/endpoints/games/reversi/games.ts @@ -1,22 +1,26 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; -import ReversiGame, { pack } from '../../../../models/reversi-game'; -import { ILocalUser } from '../../../../models/user'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import ReversiGame, { pack } from '../../../../../models/games/reversi/game'; +import { ILocalUser } from '../../../../../models/user'; -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + requireCredential: true +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'my' parameter - const [my = false, myErr] = $.bool.optional().get(params.my); + const [my = false, myErr] = $.bool.optional.get(params.my); if (myErr) return rej('invalid my param'); // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId); + const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); if (sinceIdErr) return rej('invalid sinceId param'); // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId); + const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); if (untilIdErr) return rej('invalid untilId param'); // Check if both of sinceId and untilId is specified diff --git a/src/server/api/endpoints/reversi/games/show.ts b/src/server/api/endpoints/games/reversi/games/show.ts index d70ee547a2..8d7cd987a0 100644 --- a/src/server/api/endpoints/reversi/games/show.ts +++ b/src/server/api/endpoints/games/reversi/games/show.ts @@ -1,9 +1,9 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; -import ReversiGame, { pack } from '../../../../../models/reversi-game'; -import Reversi from '../../../../../reversi/core'; -import { ILocalUser } from '../../../../../models/user'; +import $ from 'cafy'; import ID from '../../../../../../misc/cafy-id'; +import ReversiGame, { pack } from '../../../../../../models/games/reversi/game'; +import Reversi from '../../../../../../games/reversi/core'; +import { ILocalUser } from '../../../../../../models/user'; -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'gameId' parameter const [gameId, gameIdErr] = $.type(ID).get(params.gameId); if (gameIdErr) return rej('invalid gameId param'); diff --git a/src/server/api/endpoints/games/reversi/invitations.ts b/src/server/api/endpoints/games/reversi/invitations.ts new file mode 100644 index 0000000000..3962282759 --- /dev/null +++ b/src/server/api/endpoints/games/reversi/invitations.ts @@ -0,0 +1,20 @@ +import Matching, { pack as packMatching } from '../../../../../models/games/reversi/matching'; +import { ILocalUser } from '../../../../../models/user'; + +export const meta = { + requireCredential: true +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + // Find session + const invitations = await Matching.find({ + childId: user._id + }, { + sort: { + _id: -1 + } + }); + + // Reponse + res(Promise.all(invitations.map(async (i) => await packMatching(i, user)))); +}); diff --git a/src/server/api/endpoints/reversi/match.ts b/src/server/api/endpoints/games/reversi/match.ts index 907df7cf43..24746170ff 100644 --- a/src/server/api/endpoints/reversi/match.ts +++ b/src/server/api/endpoints/games/reversi/match.ts @@ -1,11 +1,15 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; -import Matching, { pack as packMatching } from '../../../../models/reversi-matching'; -import ReversiGame, { pack as packGame } from '../../../../models/reversi-game'; -import User, { ILocalUser } from '../../../../models/user'; -import publishUserStream, { publishReversiStream } from '../../../../publishers/stream'; -import { eighteight } from '../../../../reversi/maps'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import Matching, { pack as packMatching } from '../../../../../models/games/reversi/matching'; +import ReversiGame, { pack as packGame } from '../../../../../models/games/reversi/game'; +import User, { ILocalUser } from '../../../../../models/user'; +import publishUserStream, { publishReversiStream } from '../../../../../stream'; +import { eighteight } from '../../../../../games/reversi/maps'; -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + requireCredential: true +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'userId' parameter const [childId, childIdErr] = $.type(ID).get(params.userId); if (childIdErr) return rej('invalid userId param'); diff --git a/src/server/api/endpoints/games/reversi/match/cancel.ts b/src/server/api/endpoints/games/reversi/match/cancel.ts new file mode 100644 index 0000000000..d5c186409c --- /dev/null +++ b/src/server/api/endpoints/games/reversi/match/cancel.ts @@ -0,0 +1,14 @@ +import Matching from '../../../../../../models/games/reversi/matching'; +import { ILocalUser } from '../../../../../../models/user'; + +export const meta = { + requireCredential: true +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + await Matching.remove({ + parentId: user._id + }); + + res(); +}); diff --git a/src/server/api/endpoints/hashtags/search.ts b/src/server/api/endpoints/hashtags/search.ts new file mode 100644 index 0000000000..262370cacc --- /dev/null +++ b/src/server/api/endpoints/hashtags/search.ts @@ -0,0 +1,52 @@ +import $ from 'cafy'; +import Hashtag from '../../../../models/hashtag'; +import getParams from '../../get-params'; +const escapeRegexp = require('escape-regexp'); + +export const meta = { + desc: { + ja: 'ハッシュタグを検索します。' + }, + + requireCredential: false, + + params: { + limit: $.num.optional.range(1, 100).note({ + default: 10, + desc: { + ja: '最大数' + } + }), + + query: $.str.note({ + desc: { + ja: 'クエリ' + } + }), + + offset: $.num.optional.min(0).note({ + default: 0, + desc: { + ja: 'オフセット' + } + }) + } +}; + +export default (params: any) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; + + const hashtags = await Hashtag + .find({ + tag: new RegExp('^' + escapeRegexp(ps.query.toLowerCase())) + }, { + sort: { + count: -1 + }, + limit: ps.limit, + skip: ps.offset + }); + + res(hashtags.map(tag => tag.tag)); +}); diff --git a/src/server/api/endpoints/hashtags/trend.ts b/src/server/api/endpoints/hashtags/trend.ts index 17af9d6a9a..01dfccc71c 100644 --- a/src/server/api/endpoints/hashtags/trend.ts +++ b/src/server/api/endpoints/hashtags/trend.ts @@ -15,7 +15,7 @@ const max = 5; /** * Get trends of hashtags */ -module.exports = () => new Promise(async (res, rej) => { +export default () => new Promise(async (res, rej) => { //#region 1. 直近Aの内に投稿されたハッシュタグ(とユーザーのペア)を集計 const data = await Note.aggregate([{ $match: { diff --git a/src/server/api/endpoints/i.ts b/src/server/api/endpoints/i.ts index 5c769a02fd..7f25c07957 100644 --- a/src/server/api/endpoints/i.ts +++ b/src/server/api/endpoints/i.ts @@ -1,10 +1,22 @@ import User, { pack, ILocalUser } from '../../../models/user'; import { IApp } from '../../../models/app'; -/** - * Show myself - */ -module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '自分のアカウント情報を取得します。' + }, + + requireCredential: true, + + params: {}, + + res: { + type: 'entity', + entity: 'User' + } +}; + +export default (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { const isSecure = user != null && app == null; // Serialize diff --git a/src/server/api/endpoints/i/2fa/done.ts b/src/server/api/endpoints/i/2fa/done.ts index 61f13c4c61..6d38ca1de1 100644 --- a/src/server/api/endpoints/i/2fa/done.ts +++ b/src/server/api/endpoints/i/2fa/done.ts @@ -2,7 +2,12 @@ import $ from 'cafy'; import * as speakeasy from 'speakeasy'; import User, { ILocalUser } from '../../../../../models/user'; -module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + requireCredential: true, + secure: true +}; + +export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'token' parameter const [token, tokenErr] = $.str.get(params.token); if (tokenErr) return rej('invalid token param'); diff --git a/src/server/api/endpoints/i/2fa/register.ts b/src/server/api/endpoints/i/2fa/register.ts index d05892c84b..0466a4f366 100644 --- a/src/server/api/endpoints/i/2fa/register.ts +++ b/src/server/api/endpoints/i/2fa/register.ts @@ -5,7 +5,12 @@ import * as QRCode from 'qrcode'; import User, { ILocalUser } from '../../../../../models/user'; import config from '../../../../../config'; -module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + requireCredential: true, + secure: true +}; + +export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'password' parameter const [password, passwordErr] = $.str.get(params.password); if (passwordErr) return rej('invalid password param'); diff --git a/src/server/api/endpoints/i/2fa/unregister.ts b/src/server/api/endpoints/i/2fa/unregister.ts index fc197cb1e4..accf3ea0f2 100644 --- a/src/server/api/endpoints/i/2fa/unregister.ts +++ b/src/server/api/endpoints/i/2fa/unregister.ts @@ -2,7 +2,12 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import User, { ILocalUser } from '../../../../../models/user'; -module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + requireCredential: true, + secure: true +}; + +export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'password' parameter const [password, passwordErr] = $.str.get(params.password); if (passwordErr) return rej('invalid password param'); diff --git a/src/server/api/endpoints/i/authorized_apps.ts b/src/server/api/endpoints/i/authorized_apps.ts index cfc93c1518..313bb474f4 100644 --- a/src/server/api/endpoints/i/authorized_apps.ts +++ b/src/server/api/endpoints/i/authorized_apps.ts @@ -3,20 +3,22 @@ import AccessToken from '../../../../models/access-token'; import { pack } from '../../../../models/app'; import { ILocalUser } from '../../../../models/user'; -/** - * Get authorized apps of my account - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + requireCredential: true, + secure: true +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset); + const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); if (offsetErr) return rej('invalid offset param'); // Get 'sort' parameter - const [sort = 'desc', sortError] = $.str.optional().or('desc asc').get(params.sort); + const [sort = 'desc', sortError] = $.str.optional.or('desc asc').get(params.sort); if (sortError) return rej('invalid sort param'); // Get tokens diff --git a/src/server/api/endpoints/i/change_password.ts b/src/server/api/endpoints/i/change_password.ts index 9851fa895a..dc0f060c08 100644 --- a/src/server/api/endpoints/i/change_password.ts +++ b/src/server/api/endpoints/i/change_password.ts @@ -2,10 +2,12 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import User, { ILocalUser } from '../../../../models/user'; -/** - * Change password - */ -module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + requireCredential: true, + secure: true +}; + +export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'currentPasword' parameter const [currentPassword, currentPasswordErr] = $.str.get(params.currentPasword); if (currentPasswordErr) return rej('invalid currentPasword param'); diff --git a/src/server/api/endpoints/i/favorites.ts b/src/server/api/endpoints/i/favorites.ts index dc343afaed..47c8a87fd9 100644 --- a/src/server/api/endpoints/i/favorites.ts +++ b/src/server/api/endpoints/i/favorites.ts @@ -1,21 +1,29 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Favorite, { pack } from '../../../../models/favorite'; import { ILocalUser } from '../../../../models/user'; -/** - * Get favorited notes - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: 'お気に入りに登録した投稿一覧を取得します。', + en: 'Get favorited notes' + }, + + requireCredential: true, + + kind: 'favorites-read' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId); + const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); if (sinceIdErr) return rej('invalid sinceId param'); // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId); + const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); if (untilIdErr) return rej('invalid untilId param'); // Check if both of sinceId and untilId is specified diff --git a/src/server/api/endpoints/i/notifications.ts b/src/server/api/endpoints/i/notifications.ts index ce283fe48f..b6865fba52 100644 --- a/src/server/api/endpoints/i/notifications.ts +++ b/src/server/api/endpoints/i/notifications.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Notification from '../../../../models/notification'; import Mute from '../../../../models/mute'; import { pack } from '../../../../models/notification'; @@ -9,30 +9,30 @@ import { ILocalUser } from '../../../../models/user'; /** * Get notifications */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'following' parameter const [following = false, followingError] = - $.bool.optional().get(params.following); + $.bool.optional.get(params.following); if (followingError) return rej('invalid following param'); // Get 'markAsRead' parameter - const [markAsRead = true, markAsReadErr] = $.bool.optional().get(params.markAsRead); + const [markAsRead = true, markAsReadErr] = $.bool.optional.get(params.markAsRead); if (markAsReadErr) return rej('invalid markAsRead param'); // Get 'type' parameter - const [type, typeErr] = $.arr($.str).optional().unique().get(params.type); + const [type, typeErr] = $.arr($.str).optional.unique().get(params.type); if (typeErr) return rej('invalid type param'); // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId); + const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); if (sinceIdErr) return rej('invalid sinceId param'); // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId); + const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); if (untilIdErr) return rej('invalid untilId param'); // Check if both of sinceId and untilId is specified @@ -96,7 +96,7 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) // Serialize res(await Promise.all(notifications.map(notification => pack(notification)))); - // Mark as read all + // Mark all as read if (notifications.length > 0 && markAsRead) { read(user._id, notifications); } diff --git a/src/server/api/endpoints/i/pin.ts b/src/server/api/endpoints/i/pin.ts index 7f4a45e1f5..ae03a86336 100644 --- a/src/server/api/endpoints/i/pin.ts +++ b/src/server/api/endpoints/i/pin.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import User, { ILocalUser } from '../../../../models/user'; import Note from '../../../../models/note'; import { pack } from '../../../../models/user'; @@ -6,7 +6,7 @@ import { pack } from '../../../../models/user'; /** * Pin note */ -module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); diff --git a/src/server/api/endpoints/i/regenerate_token.ts b/src/server/api/endpoints/i/regenerate_token.ts index 3ffab5428e..374861daaf 100644 --- a/src/server/api/endpoints/i/regenerate_token.ts +++ b/src/server/api/endpoints/i/regenerate_token.ts @@ -1,13 +1,15 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import User, { ILocalUser } from '../../../../models/user'; -import event from '../../../../publishers/stream'; +import event from '../../../../stream'; import generateUserToken from '../../common/generate-native-user-token'; -/** - * Regenerate native token - */ -module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + requireCredential: true, + secure: true +}; + +export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'password' parameter const [password, passwordErr] = $.str.get(params.password); if (passwordErr) return rej('invalid password param'); diff --git a/src/server/api/endpoints/i/signin_history.ts b/src/server/api/endpoints/i/signin_history.ts index 4ab9881f34..5a3c122f3a 100644 --- a/src/server/api/endpoints/i/signin_history.ts +++ b/src/server/api/endpoints/i/signin_history.ts @@ -1,21 +1,23 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Signin, { pack } from '../../../../models/signin'; import { ILocalUser } from '../../../../models/user'; -/** - * Get signin history of my account - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + requireCredential: true, + secure: true +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId); + const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); if (sinceIdErr) return rej('invalid sinceId param'); // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId); + const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); if (untilIdErr) return rej('invalid untilId param'); // Check if both of sinceId and untilId is specified diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 57b050ebc4..4002bcdc39 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -1,70 +1,78 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import User, { isValidName, isValidDescription, isValidLocation, isValidBirthday, pack, ILocalUser } from '../../../../models/user'; -import event from '../../../../publishers/stream'; +import event from '../../../../stream'; import DriveFile from '../../../../models/drive-file'; import acceptAllFollowRequests from '../../../../services/following/requests/accept-all'; import { IApp } from '../../../../models/app'; -/** - * Update myself - */ -module.exports = async (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: 'アカウント情報を更新します。', + en: 'Update myself' + }, + + requireCredential: true, + + kind: 'account-write' +}; + +export default async (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { const isSecure = user != null && app == null; const updates = {} as any; // Get 'name' parameter - const [name, nameErr] = $.str.optional().nullable().pipe(isValidName).get(params.name); + const [name, nameErr] = $.str.optional.nullable.pipe(isValidName).get(params.name); if (nameErr) return rej('invalid name param'); if (name) updates.name = name; // Get 'description' parameter - const [description, descriptionErr] = $.str.optional().nullable().pipe(isValidDescription).get(params.description); + const [description, descriptionErr] = $.str.optional.nullable.pipe(isValidDescription).get(params.description); if (descriptionErr) return rej('invalid description param'); if (description !== undefined) updates.description = description; // Get 'location' parameter - const [location, locationErr] = $.str.optional().nullable().pipe(isValidLocation).get(params.location); + const [location, locationErr] = $.str.optional.nullable.pipe(isValidLocation).get(params.location); if (locationErr) return rej('invalid location param'); if (location !== undefined) updates['profile.location'] = location; // Get 'birthday' parameter - const [birthday, birthdayErr] = $.str.optional().nullable().pipe(isValidBirthday).get(params.birthday); + const [birthday, birthdayErr] = $.str.optional.nullable.pipe(isValidBirthday).get(params.birthday); if (birthdayErr) return rej('invalid birthday param'); if (birthday !== undefined) updates['profile.birthday'] = birthday; // Get 'avatarId' parameter - const [avatarId, avatarIdErr] = $.type(ID).optional().nullable().get(params.avatarId); + const [avatarId, avatarIdErr] = $.type(ID).optional.nullable.get(params.avatarId); if (avatarIdErr) return rej('invalid avatarId param'); if (avatarId !== undefined) updates.avatarId = avatarId; // Get 'bannerId' parameter - const [bannerId, bannerIdErr] = $.type(ID).optional().nullable().get(params.bannerId); + const [bannerId, bannerIdErr] = $.type(ID).optional.nullable.get(params.bannerId); if (bannerIdErr) return rej('invalid bannerId param'); if (bannerId !== undefined) updates.bannerId = bannerId; // Get 'wallpaperId' parameter - const [wallpaperId, wallpaperIdErr] = $.type(ID).optional().nullable().get(params.wallpaperId); + const [wallpaperId, wallpaperIdErr] = $.type(ID).optional.nullable.get(params.wallpaperId); if (wallpaperIdErr) return rej('invalid wallpaperId param'); if (wallpaperId !== undefined) updates.wallpaperId = wallpaperId; // Get 'isLocked' parameter - const [isLocked, isLockedErr] = $.bool.optional().get(params.isLocked); + const [isLocked, isLockedErr] = $.bool.optional.get(params.isLocked); if (isLockedErr) return rej('invalid isLocked param'); if (isLocked != null) updates.isLocked = isLocked; // Get 'isBot' parameter - const [isBot, isBotErr] = $.bool.optional().get(params.isBot); + const [isBot, isBotErr] = $.bool.optional.get(params.isBot); if (isBotErr) return rej('invalid isBot param'); if (isBot != null) updates.isBot = isBot; // Get 'isCat' parameter - const [isCat, isCatErr] = $.bool.optional().get(params.isCat); + const [isCat, isCatErr] = $.bool.optional.get(params.isCat); if (isCatErr) return rej('invalid isCat param'); if (isCat != null) updates.isCat = isCat; // Get 'autoWatch' parameter - const [autoWatch, autoWatchErr] = $.bool.optional().get(params.autoWatch); + const [autoWatch, autoWatchErr] = $.bool.optional.get(params.autoWatch); if (autoWatchErr) return rej('invalid autoWatch param'); if (autoWatch != null) updates['settings.autoWatch'] = autoWatch; diff --git a/src/server/api/endpoints/i/update_client_setting.ts b/src/server/api/endpoints/i/update_client_setting.ts index 6d6e8ed24a..9342f5dadc 100644 --- a/src/server/api/endpoints/i/update_client_setting.ts +++ b/src/server/api/endpoints/i/update_client_setting.ts @@ -1,17 +1,19 @@ import $ from 'cafy'; import User, { ILocalUser } from '../../../../models/user'; -import event from '../../../../publishers/stream'; +import event from '../../../../stream'; -/** - * Update myself - */ -module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + requireCredential: true, + secure: true +}; + +export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'name' parameter const [name, nameErr] = $.str.get(params.name); if (nameErr) return rej('invalid name param'); // Get 'value' parameter - const [value, valueErr] = $.any.nullable().get(params.value); + const [value, valueErr] = $.any.nullable.get(params.value); if (valueErr) return rej('invalid value param'); const x: any = {}; diff --git a/src/server/api/endpoints/i/update_home.ts b/src/server/api/endpoints/i/update_home.ts index 511a647d88..6f39854290 100644 --- a/src/server/api/endpoints/i/update_home.ts +++ b/src/server/api/endpoints/i/update_home.ts @@ -1,16 +1,20 @@ import $ from 'cafy'; import User, { ILocalUser } from '../../../../models/user'; -import event from '../../../../publishers/stream'; +import event from '../../../../stream'; -module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + requireCredential: true, + secure: true +}; + +export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'home' parameter - const [home, homeErr] = $.arr( - $.obj.strict() - .have('name', $.str) - .have('id', $.str) - .have('place', $.str) - .have('data', $.obj)) - .get(params.home); + const [home, homeErr] = $.arr($.obj({ + name: $.str, + id: $.str, + place: $.str, + data: $.obj() + }).strict()).get(params.home); if (homeErr) return rej('invalid home param'); await User.update(user._id, { diff --git a/src/server/api/endpoints/i/update_mobile_home.ts b/src/server/api/endpoints/i/update_mobile_home.ts index b1f25624fd..1babe409e9 100644 --- a/src/server/api/endpoints/i/update_mobile_home.ts +++ b/src/server/api/endpoints/i/update_mobile_home.ts @@ -1,15 +1,19 @@ import $ from 'cafy'; import User, { ILocalUser } from '../../../../models/user'; -import event from '../../../../publishers/stream'; +import event from '../../../../stream'; -module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + requireCredential: true, + secure: true +}; + +export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'home' parameter - const [home, homeErr] = $.arr( - $.obj.strict() - .have('name', $.str) - .have('id', $.str) - .have('data', $.obj)) - .get(params.home); + const [home, homeErr] = $.arr($.obj({ + name: $.str, + id: $.str, + data: $.obj() + }).strict()).get(params.home); if (homeErr) return rej('invalid home param'); await User.update(user._id, { diff --git a/src/server/api/endpoints/i/update_widget.ts b/src/server/api/endpoints/i/update_widget.ts index 82bb04d1f4..5bf9c23053 100644 --- a/src/server/api/endpoints/i/update_widget.ts +++ b/src/server/api/endpoints/i/update_widget.ts @@ -1,14 +1,19 @@ import $ from 'cafy'; import User, { ILocalUser } from '../../../../models/user'; -import event from '../../../../publishers/stream'; +import event from '../../../../stream'; -module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + requireCredential: true, + secure: true +}; + +export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'id' parameter const [id, idErr] = $.str.get(params.id); if (idErr) return rej('invalid id param'); // Get 'data' parameter - const [data, dataErr] = $.obj.get(params.data); + const [data, dataErr] = $.obj().get(params.data); if (dataErr) return rej('invalid data param'); if (id == null && data == null) return rej('you need to set id and data params if home param unset'); diff --git a/src/server/api/endpoints/messaging/history.ts b/src/server/api/endpoints/messaging/history.ts index 713ba9dd7f..66798d50c5 100644 --- a/src/server/api/endpoints/messaging/history.ts +++ b/src/server/api/endpoints/messaging/history.ts @@ -4,12 +4,20 @@ import Mute from '../../../../models/mute'; import { pack } from '../../../../models/messaging-message'; import { ILocalUser } from '../../../../models/user'; -/** - * Show messaging history - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: 'Messagingの履歴を取得します。', + en: 'Show messaging history.' + }, + + requireCredential: true, + + kind: 'messaging-read' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); const mute = await Mute.find({ diff --git a/src/server/api/endpoints/messaging/messages.ts b/src/server/api/endpoints/messaging/messages.ts index 3eb20ec06b..ae26419bc6 100644 --- a/src/server/api/endpoints/messaging/messages.ts +++ b/src/server/api/endpoints/messaging/messages.ts @@ -1,13 +1,21 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Message from '../../../../models/messaging-message'; import User, { ILocalUser } from '../../../../models/user'; import { pack } from '../../../../models/messaging-message'; import read from '../../common/read-messaging-message'; -/** - * Get messages - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '指定したユーザーとのMessagingのメッセージ一覧を取得します。', + en: 'Get messages of messaging.' + }, + + requireCredential: true, + + kind: 'messaging-read' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'userId' parameter const [recipientId, recipientIdErr] = $.type(ID).get(params.userId); if (recipientIdErr) return rej('invalid userId param'); @@ -16,29 +24,29 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) const recipient = await User.findOne({ _id: recipientId }, { - fields: { - _id: true - } - }); + fields: { + _id: true + } + }); if (recipient === null) { return rej('user not found'); } // Get 'markAsRead' parameter - const [markAsRead = true, markAsReadErr] = $.bool.optional().get(params.markAsRead); + const [markAsRead = true, markAsReadErr] = $.bool.optional.get(params.markAsRead); if (markAsReadErr) return rej('invalid markAsRead param'); // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId); + const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); if (sinceIdErr) return rej('invalid sinceId param'); // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId); + const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); if (untilIdErr) return rej('invalid untilId param'); // Check if both of sinceId and untilId is specified @@ -88,7 +96,7 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) return; } - // Mark as read all + // Mark all as read if (markAsRead) { read(user._id, recipient._id, messages); } diff --git a/src/server/api/endpoints/messaging/messages/create.ts b/src/server/api/endpoints/messaging/messages/create.ts index b3e4f6a8cd..9b897b45e7 100644 --- a/src/server/api/endpoints/messaging/messages/create.ts +++ b/src/server/api/endpoints/messaging/messages/create.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import Message from '../../../../../models/messaging-message'; import { isValidText } from '../../../../../models/messaging-message'; import History from '../../../../../models/messaging-history'; @@ -6,15 +6,23 @@ import User, { ILocalUser } from '../../../../../models/user'; import Mute from '../../../../../models/mute'; import DriveFile from '../../../../../models/drive-file'; import { pack } from '../../../../../models/messaging-message'; -import publishUserStream from '../../../../../publishers/stream'; -import { publishMessagingStream, publishMessagingIndexStream } from '../../../../../publishers/stream'; -import pushSw from '../../../../../publishers/push-sw'; +import publishUserStream from '../../../../../stream'; +import { publishMessagingStream, publishMessagingIndexStream } from '../../../../../stream'; +import pushSw from '../../../../../push-sw'; import config from '../../../../../config'; -/** - * Create a message - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '指定したユーザーへMessagingのメッセージを送信します。', + en: 'Create a message of messaging.' + }, + + requireCredential: true, + + kind: 'messaging-write' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'userId' parameter const [recipientId, recipientIdErr] = $.type(ID).get(params.userId); if (recipientIdErr) return rej('invalid userId param'); @@ -38,11 +46,11 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) } // Get 'text' parameter - const [text, textErr] = $.str.optional().pipe(isValidText).get(params.text); + const [text, textErr] = $.str.optional.pipe(isValidText).get(params.text); if (textErr) return rej('invalid text'); // Get 'fileId' parameter - const [fileId, fileIdErr] = $.type(ID).optional().get(params.fileId); + const [fileId, fileIdErr] = $.type(ID).optional.get(params.fileId); if (fileIdErr) return rej('invalid fileId param'); let file = null; @@ -116,7 +124,7 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) }, 3000); // Register to search database - if (message.text && config.elasticsearch.enable) { + if (message.text && config.elasticsearch) { const es = require('../../../db/elasticsearch'); es.index({ diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index ce460d0b8b..c2d93997a7 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -9,36 +9,9 @@ const pkg = require('../../../../package.json'); const client = require('../../../../built/client/meta.json'); /** - * @swagger - * /meta: - * note: - * summary: Show the misskey's information - * responses: - * 200: - * description: Success - * schema: - * type: object - * properties: - * maintainer: - * description: maintainer's name - * type: string - * commit: - * description: latest commit's hash - * type: string - * secure: - * description: whether the server supports secure protocols - * type: boolean - * - * default: - * description: Failed - * schema: - * $ref: "#/definitions/Error" - */ - -/** * Show core info */ -module.exports = (params: any) => new Promise(async (res, rej) => { +export default () => new Promise(async (res, rej) => { const meta: any = (await Meta.findOne()) || {}; res({ diff --git a/src/server/api/endpoints/mute/create.ts b/src/server/api/endpoints/mute/create.ts index 415745e2c3..bd70cd62ef 100644 --- a/src/server/api/endpoints/mute/create.ts +++ b/src/server/api/endpoints/mute/create.ts @@ -1,11 +1,19 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import User, { ILocalUser } from '../../../../models/user'; import Mute from '../../../../models/mute'; -/** - * Mute a user - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: 'ユーザーをミュートします。', + en: 'Mute a user' + }, + + requireCredential: true, + + kind: 'account/write' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { const muter = user; // Get 'userId' parameter diff --git a/src/server/api/endpoints/mute/delete.ts b/src/server/api/endpoints/mute/delete.ts index 2d1d286585..3187c46f83 100644 --- a/src/server/api/endpoints/mute/delete.ts +++ b/src/server/api/endpoints/mute/delete.ts @@ -1,11 +1,19 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import User, { ILocalUser } from '../../../../models/user'; import Mute from '../../../../models/mute'; -/** - * Unmute a user - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: 'ユーザーのミュートを解除します。', + en: 'Unmute a user' + }, + + requireCredential: true, + + kind: 'account/write' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { const muter = user; // Get 'userId' parameter diff --git a/src/server/api/endpoints/mute/list.ts b/src/server/api/endpoints/mute/list.ts index 8b0171be33..e297605338 100644 --- a/src/server/api/endpoints/mute/list.ts +++ b/src/server/api/endpoints/mute/list.ts @@ -1,22 +1,30 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Mute from '../../../../models/mute'; import { pack, ILocalUser } from '../../../../models/user'; import { getFriendIds } from '../../common/get-friends'; -/** - * Get muted users of a user - */ -module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: 'ミュートしているユーザー一覧を取得します。', + en: 'Get muted users.' + }, + + requireCredential: true, + + kind: 'account/read' +}; + +export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'iknow' parameter - const [iknow = false, iknowErr] = $.bool.optional().get(params.iknow); + const [iknow = false, iknowErr] = $.bool.optional.get(params.iknow); if (iknowErr) return rej('invalid iknow param'); // Get 'limit' parameter - const [limit = 30, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 30, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'cursor' parameter - const [cursor = null, cursorErr] = $.type(ID).optional().get(params.cursor); + const [cursor = null, cursorErr] = $.type(ID).optional.get(params.cursor); if (cursorErr) return rej('invalid cursor param'); // Construct query diff --git a/src/server/api/endpoints/my/apps.ts b/src/server/api/endpoints/my/apps.ts index 7687afd0c7..35185db41d 100644 --- a/src/server/api/endpoints/my/apps.ts +++ b/src/server/api/endpoints/my/apps.ts @@ -2,16 +2,22 @@ import $ from 'cafy'; import App, { pack } from '../../../../models/app'; import { ILocalUser } from '../../../../models/user'; -/** - * Get my apps - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '自分のアプリケーション一覧を取得します。', + en: 'Get my apps' + }, + + requireCredential: true +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset); + const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); if (offsetErr) return rej('invalid offset param'); const query = { diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts index 5554e53aa4..029bc1a95e 100644 --- a/src/server/api/endpoints/notes.ts +++ b/src/server/api/endpoints/notes.ts @@ -1,47 +1,47 @@ /** * Module dependencies */ -import $ from 'cafy'; import ID from '../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../misc/cafy-id'; import Note, { pack } from '../../../models/note'; /** * Get all notes */ -module.exports = (params: any) => new Promise(async (res, rej) => { +export default (params: any) => new Promise(async (res, rej) => { // Get 'local' parameter - const [local, localErr] = $.bool.optional().get(params.local); + const [local, localErr] = $.bool.optional.get(params.local); if (localErr) return rej('invalid local param'); // Get 'reply' parameter - const [reply, replyErr] = $.bool.optional().get(params.reply); + const [reply, replyErr] = $.bool.optional.get(params.reply); if (replyErr) return rej('invalid reply param'); // Get 'renote' parameter - const [renote, renoteErr] = $.bool.optional().get(params.renote); + const [renote, renoteErr] = $.bool.optional.get(params.renote); if (renoteErr) return rej('invalid renote param'); // Get 'media' parameter - const [media, mediaErr] = $.bool.optional().get(params.media); + const [media, mediaErr] = $.bool.optional.get(params.media); if (mediaErr) return rej('invalid media param'); // Get 'poll' parameter - const [poll, pollErr] = $.bool.optional().get(params.poll); + const [poll, pollErr] = $.bool.optional.get(params.poll); if (pollErr) return rej('invalid poll param'); // Get 'bot' parameter - //const [bot, botErr] = $.bool.optional().get(params.bot); + //const [bot, botErr] = $.bool.optional.get(params.bot); //if (botErr) return rej('invalid bot param'); // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId); + const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); if (sinceIdErr) return rej('invalid sinceId param'); // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId); + const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); if (untilIdErr) return rej('invalid untilId param'); // Check if both of sinceId and untilId is specified diff --git a/src/server/api/endpoints/notes/conversation.ts b/src/server/api/endpoints/notes/conversation.ts index b2bc6a2e72..2782d14155 100644 --- a/src/server/api/endpoints/notes/conversation.ts +++ b/src/server/api/endpoints/notes/conversation.ts @@ -1,21 +1,21 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note, { pack, INote } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; /** * Show conversation of a note */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset); + const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); if (offsetErr) return rej('invalid offset param'); // Lookup note diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 64f3b5ce26..66d018618c 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -1,68 +1,135 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +const ms = require('ms'); import Note, { INote, isValidText, isValidCw, pack } from '../../../../models/note'; import User, { ILocalUser, IUser } from '../../../../models/user'; -import DriveFile from '../../../../models/drive-file'; +import DriveFile, { IDriveFile } from '../../../../models/drive-file'; import create from '../../../../services/note/create'; import { IApp } from '../../../../models/app'; +import getParams from '../../get-params'; -/** - * Create a note - */ -module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { - // Get 'visibility' parameter - const [visibility = 'public', visibilityErr] = $.str.optional().or(['public', 'home', 'followers', 'specified', 'private']).get(params.visibility); - if (visibilityErr) return rej('invalid visibility'); +export const meta = { + desc: { + ja: '投稿します。' + }, - // Get 'visibleUserIds' parameter - const [visibleUserIds, visibleUserIdsErr] = $.arr($.type(ID)).optional().unique().min(1).get(params.visibleUserIds); - if (visibleUserIdsErr) return rej('invalid visibleUserIds'); + requireCredential: true, - let visibleUsers: IUser[] = []; - if (visibleUserIds !== undefined) { - visibleUsers = await Promise.all(visibleUserIds.map(id => User.findOne({ - _id: id - }))); - } + limit: { + duration: ms('1hour'), + max: 300, + minInterval: ms('1second') + }, - // Get 'text' parameter - const [text = null, textErr] = $.str.optional().nullable().pipe(isValidText).get(params.text); - if (textErr) return rej('invalid text'); + kind: 'note-write', - // Get 'cw' parameter - const [cw, cwErr] = $.str.optional().nullable().pipe(isValidCw).get(params.cw); - if (cwErr) return rej('invalid cw'); + params: { + visibility: $.str.optional.or(['public', 'home', 'followers', 'specified', 'private']).note({ + default: 'public', + desc: { + ja: '投稿の公開範囲' + } + }), - // Get 'viaMobile' parameter - const [viaMobile = false, viaMobileErr] = $.bool.optional().get(params.viaMobile); - if (viaMobileErr) return rej('invalid viaMobile'); + visibleUserIds: $.arr($.type(ID)).optional.unique().min(1).note({ + desc: { + ja: '(投稿の公開範囲が specified の場合)投稿を閲覧できるユーザー' + } + }), - // Get 'tags' parameter - const [tags = [], tagsErr] = $.arr($.str.range(1, 32)).optional().unique().get(params.tags); - if (tagsErr) return rej('invalid tags'); + text: $.str.optional.nullable.pipe(isValidText).note({ + default: null, + desc: { + ja: '投稿内容' + } + }), + + cw: $.str.optional.nullable.pipe(isValidCw).note({ + desc: { + ja: 'コンテンツの警告。このパラメータを指定すると設定したテキストで投稿のコンテンツを隠す事が出来ます。' + } + }), + + viaMobile: $.bool.optional.note({ + default: false, + desc: { + ja: 'モバイルデバイスからの投稿か否か。' + } + }), + + geo: $.obj({ + coordinates: $.arr().length(2) + .item(0, $.num.range(-180, 180)) + .item(1, $.num.range(-90, 90)), + altitude: $.num.nullable, + accuracy: $.num.nullable, + altitudeAccuracy: $.num.nullable, + heading: $.num.nullable.range(0, 360), + speed: $.num.nullable + }).optional.nullable.strict().note({ + desc: { + ja: '位置情報' + }, + ref: 'geo' + }), - // Get 'geo' parameter - const [geo, geoErr] = $.obj.optional().nullable().strict() - .have('coordinates', $.arr().length(2) - .item(0, $.num.range(-180, 180)) - .item(1, $.num.range(-90, 90))) - .have('altitude', $.num.nullable()) - .have('accuracy', $.num.nullable()) - .have('altitudeAccuracy', $.num.nullable()) - .have('heading', $.num.nullable().range(0, 360)) - .have('speed', $.num.nullable()) - .get(params.geo); - if (geoErr) return rej('invalid geo'); + mediaIds: $.arr($.type(ID)).optional.unique().range(1, 4).note({ + desc: { + ja: '添付するメディア' + } + }), - // Get 'mediaIds' parameter - const [mediaIds, mediaIdsErr] = $.arr($.type(ID)).optional().unique().range(1, 4).get(params.mediaIds); - if (mediaIdsErr) return rej('invalid mediaIds'); + renoteId: $.type(ID).optional.note({ + desc: { + ja: 'Renote対象' + } + }), - let files = []; - if (mediaIds !== undefined) { + poll: $.obj({ + choices: $.arr($.str) + .unique() + .range(2, 10) + .each(c => c.length > 0 && c.length < 50) + }).optional.strict().note({ + desc: { + ja: 'アンケート' + }, + ref: 'poll' + }) + }, + + res: { + type: 'object', + props: { + createdNote: { + type: 'entity(Note)', + desc: { + ja: '作成した投稿' + } + } + } + } +}; + +/** + * Create a note + */ +export default (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + + let visibleUsers: IUser[] = []; + if (ps.visibleUserIds !== undefined) { + visibleUsers = await Promise.all(ps.visibleUserIds.map(id => User.findOne({ + _id: id + }))); + } + + let files: IDriveFile[] = []; + if (ps.mediaIds !== undefined) { // Fetch files // forEach だと途中でエラーなどがあっても return できないので // 敢えて for を使っています。 - for (const mediaId of mediaIds) { + for (const mediaId of ps.mediaIds) { // Fetch file // SELECT _id const entity = await DriveFile.findOne({ @@ -80,15 +147,11 @@ module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async files = null; } - // Get 'renoteId' parameter - const [renoteId, renoteIdErr] = $.type(ID).optional().get(params.renoteId); - if (renoteIdErr) return rej('invalid renoteId'); - let renote: INote = null; - if (renoteId !== undefined) { + if (ps.renoteId !== undefined) { // Fetch renote to note renote = await Note.findOne({ - _id: renoteId + _id: ps.renoteId }); if (renote == null) { @@ -99,7 +162,7 @@ module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async } // Get 'replyId' parameter - const [replyId, replyIdErr] = $.type(ID).optional().get(params.replyId); + const [replyId, replyIdErr] = $.type(ID).optional.get(params.replyId); if (replyIdErr) return rej('invalid replyId'); let reply: INote = null; @@ -119,17 +182,8 @@ module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async } } - // Get 'poll' parameter - const [poll, pollErr] = $.obj.optional().strict() - .have('choices', $.arr($.str) - .unique() - .range(2, 10) - .each(c => c.length > 0 && c.length < 50)) - .get(params.poll); - if (pollErr) return rej('invalid poll'); - - if (poll) { - (poll as any).choices = (poll as any).choices.map((choice: string, i: number) => ({ + if (ps.poll) { + (ps.poll as any).choices = (ps.poll as any).choices.map((choice: string, i: number) => ({ id: i, // IDを付与 text: choice.trim(), votes: 0 @@ -137,7 +191,7 @@ module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async } // テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー - if ((text === undefined || text === null) && files === null && renote === null && poll === undefined) { + if ((ps.text === undefined || ps.text === null) && files === null && renote === null && ps.poll === undefined) { return rej('text, mediaIds, renoteId or poll is required'); } @@ -145,17 +199,16 @@ module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async const note = await create(user, { createdAt: new Date(), media: files, - poll, - text, + poll: ps.poll, + text: ps.text, reply, renote, - cw, - tags, + cw: ps.cw, app, - viaMobile, - visibility, + viaMobile: ps.viaMobile, + visibility: ps.visibility, visibleUsers, - geo + geo: ps.geo }); const noteObj = await pack(note, user); diff --git a/src/server/api/endpoints/notes/delete.ts b/src/server/api/endpoints/notes/delete.ts index 70bcdbaab9..22c6101e14 100644 --- a/src/server/api/endpoints/notes/delete.ts +++ b/src/server/api/endpoints/notes/delete.ts @@ -1,12 +1,20 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import deleteNote from '../../../../services/note/delete'; import { ILocalUser } from '../../../../models/user'; -/** - * Delete a note - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '指定した投稿を削除します。', + en: 'Delete a note.' + }, + + requireCredential: true, + + kind: 'note-write' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); diff --git a/src/server/api/endpoints/notes/favorites/create.ts b/src/server/api/endpoints/notes/favorites/create.ts index 23f7ac5f8d..87f6cf1f08 100644 --- a/src/server/api/endpoints/notes/favorites/create.ts +++ b/src/server/api/endpoints/notes/favorites/create.ts @@ -1,12 +1,20 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import Favorite from '../../../../../models/favorite'; import Note from '../../../../../models/note'; import { ILocalUser } from '../../../../../models/user'; -/** - * Favorite a note - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '指定した投稿をお気に入りに登録します。', + en: 'Favorite a note.' + }, + + requireCredential: true, + + kind: 'favorite-write' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); diff --git a/src/server/api/endpoints/notes/favorites/delete.ts b/src/server/api/endpoints/notes/favorites/delete.ts index 7d2d2b4cb5..3906fe99bb 100644 --- a/src/server/api/endpoints/notes/favorites/delete.ts +++ b/src/server/api/endpoints/notes/favorites/delete.ts @@ -1,12 +1,20 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import Favorite from '../../../../../models/favorite'; import Note from '../../../../../models/note'; import { ILocalUser } from '../../../../../models/user'; -/** - * Unfavorite a note - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '指定した投稿のお気に入りを解除します。', + en: 'Unfavorite a note.' + }, + + requireCredential: true, + + kind: 'favorite-write' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts index 24ffdbcba7..8f7233e308 100644 --- a/src/server/api/endpoints/notes/global-timeline.ts +++ b/src/server/api/endpoints/notes/global-timeline.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { pack } from '../../../../models/note'; @@ -7,25 +7,25 @@ import { ILocalUser } from '../../../../models/user'; /** * Get timeline of global */ -module.exports = async (params: any, user: ILocalUser) => { +export default async (params: any, user: ILocalUser) => { // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) throw 'invalid limit param'; // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId); + const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); if (sinceIdErr) throw 'invalid sinceId param'; // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId); + const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); if (untilIdErr) throw 'invalid untilId param'; // Get 'sinceDate' parameter - const [sinceDate, sinceDateErr] = $.num.optional().get(params.sinceDate); + const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate); if (sinceDateErr) throw 'invalid sinceDate param'; // Get 'untilDate' parameter - const [untilDate, untilDateErr] = $.num.optional().get(params.untilDate); + const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate); if (untilDateErr) throw 'invalid untilDate param'; // Check if only one of sinceId, untilId, sinceDate, untilDate specified @@ -34,7 +34,7 @@ module.exports = async (params: any, user: ILocalUser) => { } // Get 'mediaOnly' parameter - const [mediaOnly, mediaOnlyErr] = $.bool.optional().get(params.mediaOnly); + const [mediaOnly, mediaOnlyErr] = $.bool.optional.get(params.mediaOnly); if (mediaOnlyErr) throw 'invalid mediaOnly param'; // ミュートしているユーザーを取得 @@ -49,7 +49,9 @@ module.exports = async (params: any, user: ILocalUser) => { const query = { // public only - visibility: 'public' + visibility: 'public', + + replyId: null } as any; if (mutedUserIds && mutedUserIds.length > 0) { diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts new file mode 100644 index 0000000000..036f84b54b --- /dev/null +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -0,0 +1,219 @@ +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import Note from '../../../../models/note'; +import Mute from '../../../../models/mute'; +import { getFriends } from '../../common/get-friends'; +import { pack } from '../../../../models/note'; +import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; + +export const meta = { + name: 'notes/hybrid-timeline', + + desc: { + ja: 'ハイブリッドタイムラインを取得します。' + }, + + params: { + limit: $.num.optional.range(1, 100).note({ + default: 10, + desc: { + ja: '最大数' + } + }), + + sinceId: $.type(ID).optional.note({ + desc: { + ja: '指定すると、この投稿を基点としてより新しい投稿を取得します' + } + }), + + untilId: $.type(ID).optional.note({ + desc: { + ja: '指定すると、この投稿を基点としてより古い投稿を取得します' + } + }), + + sinceDate: $.num.optional.note({ + desc: { + ja: '指定した時間を基点としてより新しい投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' + } + }), + + untilDate: $.num.optional.note({ + desc: { + ja: '指定した時間を基点としてより古い投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' + } + }), + + includeMyRenotes: $.bool.optional.note({ + default: true, + desc: { + ja: '自分の行ったRenoteを含めるかどうか' + } + }), + + includeRenotedMyNotes: $.bool.optional.note({ + default: true, + desc: { + ja: 'Renoteされた自分の投稿を含めるかどうか' + } + }), + + mediaOnly: $.bool.optional.note({ + desc: { + ja: 'true にすると、メディアが添付された投稿だけ取得します' + } + }), + } +}; + +/** + * Get hybrid timeline of myself + */ +export default async (params: any, user: ILocalUser) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; + + // Check if only one of sinceId, untilId, sinceDate, untilDate specified + if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) { + throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; + } + + const [followings, mutedUserIds] = await Promise.all([ + // フォローを取得 + // Fetch following + getFriends(user._id, true, false), + + // ミュートしているユーザーを取得 + Mute.find({ + muterId: user._id + }).then(ms => ms.map(m => m.muteeId)) + ]); + + //#region Construct query + const sort = { + _id: -1 + }; + + const followQuery = followings.map(f => f.stalk ? { + userId: f.id + } : { + userId: f.id, + + // ストーキングしてないならリプライは含めない(ただし投稿者自身の投稿へのリプライ、自分の投稿へのリプライ、自分のリプライは含める) + $or: [{ + // リプライでない + replyId: null + }, { // または + // リプライだが返信先が投稿者自身の投稿 + $expr: { + $eq: ['$_reply.userId', '$userId'] + } + }, { // または + // リプライだが返信先が自分(フォロワー)の投稿 + '_reply.userId': user._id + }, { // または + // 自分(フォロワー)が送信したリプライ + userId: user._id + }] + }); + + const query = { + $and: [{ + $or: [{ + // フォローしている人の投稿 + $or: followQuery + }, { + // public only + visibility: 'public', + + // local + '_user.host': null + }], + + // mute + userId: { + $nin: mutedUserIds + }, + '_reply.userId': { + $nin: mutedUserIds + }, + '_renote.userId': { + $nin: mutedUserIds + }, + }] + } as any; + + // MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。 + // つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。 + // for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws + + if (ps.includeMyRenotes === false) { + query.$and.push({ + $or: [{ + userId: { $ne: user._id } + }, { + renoteId: null + }, { + text: { $ne: null } + }, { + mediaIds: { $ne: [] } + }, { + poll: { $ne: null } + }] + }); + } + + if (ps.includeRenotedMyNotes === false) { + query.$and.push({ + $or: [{ + '_renote.userId': { $ne: user._id } + }, { + renoteId: null + }, { + text: { $ne: null } + }, { + mediaIds: { $ne: [] } + }, { + poll: { $ne: null } + }] + }); + } + + if (ps.mediaOnly) { + query.$and.push({ + mediaIds: { $exists: true, $ne: [] } + }); + } + + if (ps.sinceId) { + sort._id = 1; + query._id = { + $gt: ps.sinceId + }; + } else if (ps.untilId) { + query._id = { + $lt: ps.untilId + }; + } else if (ps.sinceDate) { + sort._id = 1; + query.createdAt = { + $gt: new Date(ps.sinceDate) + }; + } else if (ps.untilDate) { + query.createdAt = { + $lt: new Date(ps.untilDate) + }; + } + //#endregion + + // Issue query + const timeline = await Note + .find(query, { + limit: ps.limit, + sort: sort + }); + + // Serialize + return await Promise.all(timeline.map(note => pack(note, user))); +}; diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index 48490638d2..bbcc6303ca 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { pack } from '../../../../models/note'; @@ -7,25 +7,25 @@ import { ILocalUser } from '../../../../models/user'; /** * Get timeline of local */ -module.exports = async (params: any, user: ILocalUser) => { +export default async (params: any, user: ILocalUser) => { // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) throw 'invalid limit param'; // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId); + const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); if (sinceIdErr) throw 'invalid sinceId param'; // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId); + const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); if (untilIdErr) throw 'invalid untilId param'; // Get 'sinceDate' parameter - const [sinceDate, sinceDateErr] = $.num.optional().get(params.sinceDate); + const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate); if (sinceDateErr) throw 'invalid sinceDate param'; // Get 'untilDate' parameter - const [untilDate, untilDateErr] = $.num.optional().get(params.untilDate); + const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate); if (untilDateErr) throw 'invalid untilDate param'; // Check if only one of sinceId, untilId, sinceDate, untilDate specified @@ -34,7 +34,7 @@ module.exports = async (params: any, user: ILocalUser) => { } // Get 'mediaOnly' parameter - const [mediaOnly, mediaOnlyErr] = $.bool.optional().get(params.mediaOnly); + const [mediaOnly, mediaOnlyErr] = $.bool.optional.get(params.mediaOnly); if (mediaOnlyErr) throw 'invalid mediaOnly param'; // ミュートしているユーザーを取得 diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index 45511603af..db91230a81 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -1,28 +1,34 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import { getFriendIds } from '../../common/get-friends'; import { pack } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; -/** - * Get mentions of myself - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '自分に言及している投稿の一覧を取得します。', + en: 'Get mentions of myself.' + }, + + requireCredential: true +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'following' parameter const [following = false, followingError] = - $.bool.optional().get(params.following); + $.bool.optional.get(params.following); if (followingError) return rej('invalid following param'); // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId); + const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); if (sinceIdErr) return rej('invalid sinceId param'); // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId); + const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); if (untilIdErr) return rej('invalid untilId param'); // Check if both of sinceId and untilId is specified diff --git a/src/server/api/endpoints/notes/polls/recommendation.ts b/src/server/api/endpoints/notes/polls/recommendation.ts index 640140c3d1..a0469d1870 100644 --- a/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/src/server/api/endpoints/notes/polls/recommendation.ts @@ -3,16 +3,22 @@ import Vote from '../../../../../models/poll-vote'; import Note, { pack } from '../../../../../models/note'; import { ILocalUser } from '../../../../../models/user'; -/** - * Get recommended polls - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: 'おすすめのアンケート一覧を取得します。', + en: 'Get recommended polls.' + }, + + requireCredential: true, +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset); + const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); if (offsetErr) return rej('invalid offset param'); // Get votes diff --git a/src/server/api/endpoints/notes/polls/vote.ts b/src/server/api/endpoints/notes/polls/vote.ts index 72ac6bb202..568c187f8a 100644 --- a/src/server/api/endpoints/notes/polls/vote.ts +++ b/src/server/api/endpoints/notes/polls/vote.ts @@ -1,16 +1,24 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import Vote from '../../../../../models/poll-vote'; import Note from '../../../../../models/note'; import Watching from '../../../../../models/note-watching'; import watch from '../../../../../services/note/watch'; -import { publishNoteStream } from '../../../../../publishers/stream'; -import notify from '../../../../../publishers/notify'; +import { publishNoteStream } from '../../../../../stream'; +import notify from '../../../../../notify'; import { ILocalUser } from '../../../../../models/user'; -/** - * Vote poll of a note - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '指定した投稿のアンケートに投票します。', + en: 'Vote poll of a note.' + }, + + requireCredential: true, + + kind: 'vote-write' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); diff --git a/src/server/api/endpoints/notes/reactions.ts b/src/server/api/endpoints/notes/reactions.ts index d3b2d43432..8921c55916 100644 --- a/src/server/api/endpoints/notes/reactions.ts +++ b/src/server/api/endpoints/notes/reactions.ts @@ -1,26 +1,32 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Reaction, { pack } from '../../../../models/note-reaction'; import { ILocalUser } from '../../../../models/user'; -/** - * Show reactions of a note - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '指定した投稿のリアクション一覧を取得します。', + en: 'Show reactions of a note.' + }, + + requireCredential: true +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset); + const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); if (offsetErr) return rej('invalid offset param'); // Get 'sort' parameter - const [sort = 'desc', sortError] = $.str.optional().or('desc asc').get(params.sort); + const [sort = 'desc', sortError] = $.str.optional.or('desc asc').get(params.sort); if (sortError) return rej('invalid sort param'); // Lookup note @@ -46,6 +52,5 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) }); // Serialize - res(await Promise.all(reactions.map(async reaction => - await pack(reaction, user)))); + res(await Promise.all(reactions.map(reaction => pack(reaction, user)))); }); diff --git a/src/server/api/endpoints/notes/reactions/create.ts b/src/server/api/endpoints/notes/reactions/create.ts index 33e457e308..65e24e7c06 100644 --- a/src/server/api/endpoints/notes/reactions/create.ts +++ b/src/server/api/endpoints/notes/reactions/create.ts @@ -1,24 +1,42 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import Note from '../../../../../models/note'; import create from '../../../../../services/note/reaction/create'; import { validateReaction } from '../../../../../models/note-reaction'; import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; -/** - * React to a note - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - // Get 'noteId' parameter - const [noteId, noteIdErr] = $.type(ID).get(params.noteId); - if (noteIdErr) return rej('invalid noteId param'); +export const meta = { + desc: { + ja: '指定した投稿にリアクションします。', + en: 'React to a note.' + }, - // Get 'reaction' parameter - const [reaction, reactionErr] = $.str.pipe(validateReaction.ok).get(params.reaction); - if (reactionErr) return rej('invalid reaction param'); + requireCredential: true, + + kind: 'reaction-write', + + params: { + noteId: $.type(ID).note({ + desc: { + ja: '対象の投稿' + } + }), + + reaction: $.str.pipe(validateReaction.ok).note({ + desc: { + ja: 'リアクションの種類' + } + }) + } +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Fetch reactee const note = await Note.findOne({ - _id: noteId + _id: ps.noteId }); if (note === null) { @@ -26,7 +44,7 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) } try { - await create(user, note, reaction); + await create(user, note, ps.reaction); } catch (e) { rej(e); } diff --git a/src/server/api/endpoints/notes/reactions/delete.ts b/src/server/api/endpoints/notes/reactions/delete.ts index 1f2d662511..62af0407bc 100644 --- a/src/server/api/endpoints/notes/reactions/delete.ts +++ b/src/server/api/endpoints/notes/reactions/delete.ts @@ -1,12 +1,20 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import Reaction from '../../../../../models/note-reaction'; import Note from '../../../../../models/note'; import { ILocalUser } from '../../../../../models/user'; -/** - * Unreact to a note - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '指定した投稿へのリアクションを取り消します。', + en: 'Unreact to a note.' + }, + + requireCredential: true, + + kind: 'reaction-write' +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); diff --git a/src/server/api/endpoints/notes/replies.ts b/src/server/api/endpoints/notes/replies.ts index 4aaf1d322b..44c80afc4a 100644 --- a/src/server/api/endpoints/notes/replies.ts +++ b/src/server/api/endpoints/notes/replies.ts @@ -1,21 +1,21 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note, { pack } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; /** * Get replies of a note */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset); + const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); if (offsetErr) return rej('invalid offset param'); // Lookup note diff --git a/src/server/api/endpoints/notes/reposts.ts b/src/server/api/endpoints/notes/reposts.ts index ea3f174e1a..05e68302ba 100644 --- a/src/server/api/endpoints/notes/reposts.ts +++ b/src/server/api/endpoints/notes/reposts.ts @@ -1,25 +1,25 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note, { pack } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; /** * Show a renotes of a note */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId); + const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); if (sinceIdErr) return rej('invalid sinceId param'); // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId); + const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); if (untilIdErr) return rej('invalid untilId param'); // Check if both of sinceId and untilId is specified diff --git a/src/server/api/endpoints/notes/search.ts b/src/server/api/endpoints/notes/search.ts new file mode 100644 index 0000000000..9124899ad8 --- /dev/null +++ b/src/server/api/endpoints/notes/search.ts @@ -0,0 +1,65 @@ +import $ from 'cafy'; +import * as mongo from 'mongodb'; +import Note from '../../../../models/note'; +import { ILocalUser } from '../../../../models/user'; +import { pack } from '../../../../models/note'; +import es from '../../../../db/elasticsearch'; + +export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { + // Get 'query' parameter + const [query, queryError] = $.str.get(params.query); + if (queryError) return rej('invalid query param'); + + // Get 'offset' parameter + const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); + if (offsetErr) return rej('invalid offset param'); + + // Get 'limit' parameter + const [limit = 10, limitErr] = $.num.optional.range(1, 30).get(params.limit); + if (limitErr) return rej('invalid limit param'); + + if (es == null) return rej('searching not available'); + + es.search({ + index: 'misskey', + type: 'note', + body: { + size: limit, + from: offset, + query: { + simple_query_string: { + fields: ['text'], + query: query, + default_operator: 'and' + } + }, + sort: [ + { _doc: 'desc' } + ] + } + }, async (error, response) => { + if (error) { + console.error(error); + return res(500); + } + + if (response.hits.total === 0) { + return res([]); + } + + const hits = response.hits.hits.map(hit => new mongo.ObjectID(hit._id)); + + // Fetch found notes + const notes = await Note.find({ + _id: { + $in: hits + } + }, { + sort: { + _id: -1 + } + }); + + res(await Promise.all(notes.map(note => pack(note, me)))); + }); +}); diff --git a/src/server/api/endpoints/notes/search_by_tag.ts b/src/server/api/endpoints/notes/search_by_tag.ts index 9be7cfffb6..e092275fe8 100644 --- a/src/server/api/endpoints/notes/search_by_tag.ts +++ b/src/server/api/endpoints/notes/search_by_tag.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import User, { ILocalUser } from '../../../../models/user'; import Mute from '../../../../models/mute'; @@ -8,65 +8,65 @@ import { pack } from '../../../../models/note'; /** * Search notes by tag */ -module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'tag' parameter const [tag, tagError] = $.str.get(params.tag); if (tagError) return rej('invalid tag param'); // Get 'includeUserIds' parameter - const [includeUserIds = [], includeUserIdsErr] = $.arr($.type(ID)).optional().get(params.includeUserIds); + const [includeUserIds = [], includeUserIdsErr] = $.arr($.type(ID)).optional.get(params.includeUserIds); if (includeUserIdsErr) return rej('invalid includeUserIds param'); // Get 'excludeUserIds' parameter - const [excludeUserIds = [], excludeUserIdsErr] = $.arr($.type(ID)).optional().get(params.excludeUserIds); + const [excludeUserIds = [], excludeUserIdsErr] = $.arr($.type(ID)).optional.get(params.excludeUserIds); if (excludeUserIdsErr) return rej('invalid excludeUserIds param'); // Get 'includeUserUsernames' parameter - const [includeUserUsernames = [], includeUserUsernamesErr] = $.arr($.str).optional().get(params.includeUserUsernames); + const [includeUserUsernames = [], includeUserUsernamesErr] = $.arr($.str).optional.get(params.includeUserUsernames); if (includeUserUsernamesErr) return rej('invalid includeUserUsernames param'); // Get 'excludeUserUsernames' parameter - const [excludeUserUsernames = [], excludeUserUsernamesErr] = $.arr($.str).optional().get(params.excludeUserUsernames); + const [excludeUserUsernames = [], excludeUserUsernamesErr] = $.arr($.str).optional.get(params.excludeUserUsernames); if (excludeUserUsernamesErr) return rej('invalid excludeUserUsernames param'); // Get 'following' parameter - const [following = null, followingErr] = $.bool.optional().nullable().get(params.following); + const [following = null, followingErr] = $.bool.optional.nullable.get(params.following); if (followingErr) return rej('invalid following param'); // Get 'mute' parameter - const [mute = 'mute_all', muteErr] = $.str.optional().get(params.mute); + const [mute = 'mute_all', muteErr] = $.str.optional.get(params.mute); if (muteErr) return rej('invalid mute param'); // Get 'reply' parameter - const [reply = null, replyErr] = $.bool.optional().nullable().get(params.reply); + const [reply = null, replyErr] = $.bool.optional.nullable.get(params.reply); if (replyErr) return rej('invalid reply param'); // Get 'renote' parameter - const [renote = null, renoteErr] = $.bool.optional().nullable().get(params.renote); + const [renote = null, renoteErr] = $.bool.optional.nullable.get(params.renote); if (renoteErr) return rej('invalid renote param'); // Get 'media' parameter - const [media = null, mediaErr] = $.bool.optional().nullable().get(params.media); + const [media = null, mediaErr] = $.bool.optional.nullable.get(params.media); if (mediaErr) return rej('invalid media param'); // Get 'poll' parameter - const [poll = null, pollErr] = $.bool.optional().nullable().get(params.poll); + const [poll = null, pollErr] = $.bool.optional.nullable.get(params.poll); if (pollErr) return rej('invalid poll param'); // Get 'sinceDate' parameter - const [sinceDate, sinceDateErr] = $.num.optional().get(params.sinceDate); + const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate); if (sinceDateErr) throw 'invalid sinceDate param'; // Get 'untilDate' parameter - const [untilDate, untilDateErr] = $.num.optional().get(params.untilDate); + const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate); if (untilDateErr) throw 'invalid untilDate param'; // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset); + const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); if (offsetErr) return rej('invalid offset param'); // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 30).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 30).get(params.limit); if (limitErr) return rej('invalid limit param'); if (includeUserUsernames != null) { diff --git a/src/server/api/endpoints/notes/show.ts b/src/server/api/endpoints/notes/show.ts index 1ba7145996..3f94eeede5 100644 --- a/src/server/api/endpoints/notes/show.ts +++ b/src/server/api/endpoints/notes/show.ts @@ -1,11 +1,11 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note, { pack } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; /** * Show a note */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index 18c0acd379..faa8ccf3ca 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -1,50 +1,83 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { getFriends } from '../../common/get-friends'; import { pack } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; -/** - * Get timeline of myself - */ -module.exports = async (params: any, user: ILocalUser) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); - if (limitErr) throw 'invalid limit param'; +export const meta = { + desc: { + ja: 'タイムラインを取得します。' + }, - // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId); - if (sinceIdErr) throw 'invalid sinceId param'; + requireCredential: true, - // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId); - if (untilIdErr) throw 'invalid untilId param'; + params: { + limit: $.num.optional.range(1, 100).note({ + default: 10, + desc: { + ja: '最大数' + } + }), - // Get 'sinceDate' parameter - const [sinceDate, sinceDateErr] = $.num.optional().get(params.sinceDate); - if (sinceDateErr) throw 'invalid sinceDate param'; + sinceId: $.type(ID).optional.note({ + desc: { + ja: '指定すると、この投稿を基点としてより新しい投稿を取得します' + } + }), - // Get 'untilDate' parameter - const [untilDate, untilDateErr] = $.num.optional().get(params.untilDate); - if (untilDateErr) throw 'invalid untilDate param'; + untilId: $.type(ID).optional.note({ + desc: { + ja: '指定すると、この投稿を基点としてより古い投稿を取得します' + } + }), - // Check if only one of sinceId, untilId, sinceDate, untilDate specified - if ([sinceId, untilId, sinceDate, untilDate].filter(x => x != null).length > 1) { - throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; - } + sinceDate: $.num.optional.note({ + desc: { + ja: '指定した時間を基点としてより新しい投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' + } + }), - // Get 'includeMyRenotes' parameter - const [includeMyRenotes = true, includeMyRenotesErr] = $.bool.optional().get(params.includeMyRenotes); - if (includeMyRenotesErr) throw 'invalid includeMyRenotes param'; + untilDate: $.num.optional.note({ + desc: { + ja: '指定した時間を基点としてより古い投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' + } + }), - // Get 'includeRenotedMyNotes' parameter - const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional().get(params.includeRenotedMyNotes); - if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param'; + includeMyRenotes: $.bool.optional.note({ + default: true, + desc: { + ja: '自分の行ったRenoteを含めるかどうか' + } + }), - // Get 'mediaOnly' parameter - const [mediaOnly, mediaOnlyErr] = $.bool.optional().get(params.mediaOnly); - if (mediaOnlyErr) throw 'invalid mediaOnly param'; + includeRenotedMyNotes: $.bool.optional.note({ + default: true, + desc: { + ja: 'Renoteされた自分の投稿を含めるかどうか' + } + }), + + mediaOnly: $.bool.optional.note({ + desc: { + ja: 'true にすると、メディアが添付された投稿だけ取得します' + } + }), + } +}; + +/** + * Get timeline of myself + */ +export default async (params: any, user: ILocalUser) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; + + // Check if only one of sinceId, untilId, sinceDate, untilDate specified + if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) { + throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; + } const [followings, mutedUserIds] = await Promise.all([ // フォローを取得 @@ -107,7 +140,7 @@ module.exports = async (params: any, user: ILocalUser) => { // つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。 // for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws - if (includeMyRenotes === false) { + if (ps.includeMyRenotes === false) { query.$and.push({ $or: [{ userId: { $ne: user._id } @@ -123,7 +156,7 @@ module.exports = async (params: any, user: ILocalUser) => { }); } - if (includeRenotedMyNotes === false) { + if (ps.includeRenotedMyNotes === false) { query.$and.push({ $or: [{ '_renote.userId': { $ne: user._id } @@ -139,29 +172,29 @@ module.exports = async (params: any, user: ILocalUser) => { }); } - if (mediaOnly) { + if (ps.mediaOnly) { query.$and.push({ mediaIds: { $exists: true, $ne: [] } }); } - if (sinceId) { + if (ps.sinceId) { sort._id = 1; query._id = { - $gt: sinceId + $gt: ps.sinceId }; - } else if (untilId) { + } else if (ps.untilId) { query._id = { - $lt: untilId + $lt: ps.untilId }; - } else if (sinceDate) { + } else if (ps.sinceDate) { sort._id = 1; query.createdAt = { - $gt: new Date(sinceDate) + $gt: new Date(ps.sinceDate) }; - } else if (untilDate) { + } else if (ps.untilDate) { query.createdAt = { - $lt: new Date(untilDate) + $lt: new Date(ps.untilDate) }; } //#endregion @@ -169,7 +202,7 @@ module.exports = async (params: any, user: ILocalUser) => { // Issue query const timeline = await Note .find(query, { - limit: limit, + limit: ps.limit, sort: sort }); diff --git a/src/server/api/endpoints/notes/trend.ts b/src/server/api/endpoints/notes/trend.ts index 9c0a1bb112..1cbbfacadc 100644 --- a/src/server/api/endpoints/notes/trend.ts +++ b/src/server/api/endpoints/notes/trend.ts @@ -3,32 +3,38 @@ import $ from 'cafy'; import Note, { pack } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; -/** - * Get trend notes - */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '人気の投稿の一覧を取得します。', + en: 'Get trend notes.' + }, + + requireCredential: true +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset); + const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); if (offsetErr) return rej('invalid offset param'); // Get 'reply' parameter - const [reply, replyErr] = $.bool.optional().get(params.reply); + const [reply, replyErr] = $.bool.optional.get(params.reply); if (replyErr) return rej('invalid reply param'); // Get 'renote' parameter - const [renote, renoteErr] = $.bool.optional().get(params.renote); + const [renote, renoteErr] = $.bool.optional.get(params.renote); if (renoteErr) return rej('invalid renote param'); // Get 'media' parameter - const [media, mediaErr] = $.bool.optional().get(params.media); + const [media, mediaErr] = $.bool.optional.get(params.media); if (mediaErr) return rej('invalid media param'); // Get 'poll' parameter - const [poll, pollErr] = $.bool.optional().get(params.poll); + const [poll, pollErr] = $.bool.optional.get(params.poll); if (pollErr) return rej('invalid poll param'); const query = { diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts index 8aa800b712..5837a9a301 100644 --- a/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/src/server/api/endpoints/notes/user-list-timeline.ts @@ -1,32 +1,38 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { pack } from '../../../../models/note'; import UserList from '../../../../models/user-list'; import { ILocalUser } from '../../../../models/user'; -/** - * Get timeline of a user list - */ -module.exports = async (params: any, user: ILocalUser) => { +export const meta = { + desc: { + ja: '指定したユーザーリストのタイムラインを取得します。', + en: 'Get timeline of a user list.' + }, + + requireCredential: true +}; + +export default async (params: any, user: ILocalUser) => { // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) throw 'invalid limit param'; // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId); + const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); if (sinceIdErr) throw 'invalid sinceId param'; // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId); + const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); if (untilIdErr) throw 'invalid untilId param'; // Get 'sinceDate' parameter - const [sinceDate, sinceDateErr] = $.num.optional().get(params.sinceDate); + const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate); if (sinceDateErr) throw 'invalid sinceDate param'; // Get 'untilDate' parameter - const [untilDate, untilDateErr] = $.num.optional().get(params.untilDate); + const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate); if (untilDateErr) throw 'invalid untilDate param'; // Check if only one of sinceId, untilId, sinceDate, untilDate specified @@ -35,15 +41,15 @@ module.exports = async (params: any, user: ILocalUser) => { } // Get 'includeMyRenotes' parameter - const [includeMyRenotes = true, includeMyRenotesErr] = $.bool.optional().get(params.includeMyRenotes); + const [includeMyRenotes = true, includeMyRenotesErr] = $.bool.optional.get(params.includeMyRenotes); if (includeMyRenotesErr) throw 'invalid includeMyRenotes param'; // Get 'includeRenotedMyNotes' parameter - const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional().get(params.includeRenotedMyNotes); + const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional.get(params.includeRenotedMyNotes); if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param'; // Get 'mediaOnly' parameter - const [mediaOnly, mediaOnlyErr] = $.bool.optional().get(params.mediaOnly); + const [mediaOnly, mediaOnlyErr] = $.bool.optional.get(params.mediaOnly); if (mediaOnlyErr) throw 'invalid mediaOnly param'; // Get 'listId' parameter diff --git a/src/server/api/endpoints/notifications/mark_as_read_all.ts b/src/server/api/endpoints/notifications/mark_all_as_read.ts index faaaf65a2d..91319d0553 100644 --- a/src/server/api/endpoints/notifications/mark_as_read_all.ts +++ b/src/server/api/endpoints/notifications/mark_all_as_read.ts @@ -1,22 +1,33 @@ import Notification from '../../../../models/notification'; -import event from '../../../../publishers/stream'; +import event from '../../../../stream'; import User, { ILocalUser } from '../../../../models/user'; +export const meta = { + desc: { + ja: '全ての通知を既読にします。', + en: 'Mark all notifications as read.' + }, + + requireCredential: true, + + kind: 'notification-write' +}; + /** - * Mark as read all notifications + * Mark all notifications as read */ -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Update documents await Notification.update({ notifieeId: user._id, isRead: false }, { - $set: { - isRead: true - } - }, { - multi: true - }); + $set: { + isRead: true + } + }, { + multi: true + }); // Response res(); diff --git a/src/server/api/endpoints/reversi/invitations.ts b/src/server/api/endpoints/reversi/invitations.ts deleted file mode 100644 index d7727071ae..0000000000 --- a/src/server/api/endpoints/reversi/invitations.ts +++ /dev/null @@ -1,16 +0,0 @@ -import Matching, { pack as packMatching } from '../../../../models/reversi-matching'; -import { ILocalUser } from '../../../../models/user'; - -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - // Find session - const invitations = await Matching.find({ - childId: user._id - }, { - sort: { - _id: -1 - } - }); - - // Reponse - res(Promise.all(invitations.map(async (i) => await packMatching(i, user)))); -}); diff --git a/src/server/api/endpoints/reversi/match/cancel.ts b/src/server/api/endpoints/reversi/match/cancel.ts deleted file mode 100644 index 1c9c799dbe..0000000000 --- a/src/server/api/endpoints/reversi/match/cancel.ts +++ /dev/null @@ -1,10 +0,0 @@ -import Matching from '../../../../../models/reversi-matching'; -import { ILocalUser } from '../../../../../models/user'; - -module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - await Matching.remove({ - parentId: user._id - }); - - res(); -}); diff --git a/src/server/api/endpoints/stats.ts b/src/server/api/endpoints/stats.ts index 74ab6ba945..fc195da22d 100644 --- a/src/server/api/endpoints/stats.ts +++ b/src/server/api/endpoints/stats.ts @@ -3,8 +3,8 @@ import Meta from '../../../models/meta'; /** * Get the misskey's statistics */ -module.exports = () => new Promise(async (res, rej) => { +export default () => new Promise(async (res, rej) => { const meta = await Meta.findOne(); - res(meta.stats); + res(meta ? meta.stats : {}); }); diff --git a/src/server/api/endpoints/sw/register.ts b/src/server/api/endpoints/sw/register.ts index f04a77fa4d..3414600048 100644 --- a/src/server/api/endpoints/sw/register.ts +++ b/src/server/api/endpoints/sw/register.ts @@ -2,10 +2,14 @@ import $ from 'cafy'; import Subscription from '../../../../models/sw-subscription'; import { ILocalUser } from '../../../../models/user'; +export const meta = { + requireCredential: true +}; + /** * subscribe service worker */ -module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'endpoint' parameter const [endpoint, endpointErr] = $.str.get(params.endpoint); if (endpointErr) return rej('invalid endpoint param'); diff --git a/src/server/api/endpoints/username/available.ts b/src/server/api/endpoints/username/available.ts index aad3adc514..ff12b797ed 100644 --- a/src/server/api/endpoints/username/available.ts +++ b/src/server/api/endpoints/username/available.ts @@ -5,7 +5,7 @@ import { validateUsername } from '../../../../models/user'; /** * Check available username */ -module.exports = async (params: any) => new Promise(async (res, rej) => { +export default async (params: any) => new Promise(async (res, rej) => { // Get 'username' parameter const [username, usernameError] = $.str.pipe(validateUsername).get(params.username); if (usernameError) return rej('invalid username param'); diff --git a/src/server/api/endpoints/users.ts b/src/server/api/endpoints/users.ts index b8df6f3ecf..d7e85d3cbe 100644 --- a/src/server/api/endpoints/users.ts +++ b/src/server/api/endpoints/users.ts @@ -4,17 +4,17 @@ import User, { pack, ILocalUser } from '../../../models/user'; /** * Lists all users */ -module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset); + const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); if (offsetErr) return rej('invalid offset param'); // Get 'sort' parameter - const [sort, sortError] = $.str.optional().or('+follower|-follower').get(params.sort); + const [sort, sortError] = $.str.optional.or('+follower|-follower').get(params.sort); if (sortError) return rej('invalid sort param'); // Construct query diff --git a/src/server/api/endpoints/users/followers.ts b/src/server/api/endpoints/users/followers.ts index 53133ee969..9411873573 100644 --- a/src/server/api/endpoints/users/followers.ts +++ b/src/server/api/endpoints/users/followers.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import User, { ILocalUser } from '../../../../models/user'; import Following from '../../../../models/following'; import { pack } from '../../../../models/user'; @@ -7,21 +7,21 @@ import { getFriendIds } from '../../common/get-friends'; /** * Get followers of a user */ -module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'userId' parameter const [userId, userIdErr] = $.type(ID).get(params.userId); if (userIdErr) return rej('invalid userId param'); // Get 'iknow' parameter - const [iknow = false, iknowErr] = $.bool.optional().get(params.iknow); + const [iknow = false, iknowErr] = $.bool.optional.get(params.iknow); if (iknowErr) return rej('invalid iknow param'); // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'cursor' parameter - const [cursor = null, cursorErr] = $.type(ID).optional().get(params.cursor); + const [cursor = null, cursorErr] = $.type(ID).optional.get(params.cursor); if (cursorErr) return rej('invalid cursor param'); // Lookup user diff --git a/src/server/api/endpoints/users/following.ts b/src/server/api/endpoints/users/following.ts index 2061200198..7a64d15d7b 100644 --- a/src/server/api/endpoints/users/following.ts +++ b/src/server/api/endpoints/users/following.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import User, { ILocalUser } from '../../../../models/user'; import Following from '../../../../models/following'; import { pack } from '../../../../models/user'; @@ -7,21 +7,21 @@ import { getFriendIds } from '../../common/get-friends'; /** * Get following users of a user */ -module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'userId' parameter const [userId, userIdErr] = $.type(ID).get(params.userId); if (userIdErr) return rej('invalid userId param'); // Get 'iknow' parameter - const [iknow = false, iknowErr] = $.bool.optional().get(params.iknow); + const [iknow = false, iknowErr] = $.bool.optional.get(params.iknow); if (iknowErr) return rej('invalid iknow param'); // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'cursor' parameter - const [cursor = null, cursorErr] = $.type(ID).optional().get(params.cursor); + const [cursor = null, cursorErr] = $.type(ID).optional.get(params.cursor); if (cursorErr) return rej('invalid cursor param'); // Lookup user diff --git a/src/server/api/endpoints/users/get_frequently_replied_users.ts b/src/server/api/endpoints/users/get_frequently_replied_users.ts index ba8779d334..42b6ce20d6 100644 --- a/src/server/api/endpoints/users/get_frequently_replied_users.ts +++ b/src/server/api/endpoints/users/get_frequently_replied_users.ts @@ -1,14 +1,14 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import User, { pack, ILocalUser } from '../../../../models/user'; -module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'userId' parameter const [userId, userIdErr] = $.type(ID).get(params.userId); if (userIdErr) return rej('invalid userId param'); // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Lookup user diff --git a/src/server/api/endpoints/users/lists/create.ts b/src/server/api/endpoints/users/lists/create.ts index cdd1a0d813..d7dc2a5f70 100644 --- a/src/server/api/endpoints/users/lists/create.ts +++ b/src/server/api/endpoints/users/lists/create.ts @@ -2,10 +2,18 @@ import $ from 'cafy'; import UserList, { pack } from '../../../../../models/user-list'; import { ILocalUser } from '../../../../../models/user'; -/** - * Create a user list - */ -module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: 'ユーザーリストを作成します。', + en: 'Create a user list' + }, + + requireCredential: true, + + kind: 'account-write' +}; + +export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'title' parameter const [title, titleErr] = $.str.range(1, 100).get(params.title); if (titleErr) return rej('invalid title param'); diff --git a/src/server/api/endpoints/users/lists/list.ts b/src/server/api/endpoints/users/lists/list.ts index bf8391d863..31fef26bdc 100644 --- a/src/server/api/endpoints/users/lists/list.ts +++ b/src/server/api/endpoints/users/lists/list.ts @@ -1,10 +1,17 @@ import UserList, { pack } from '../../../../../models/user-list'; import { ILocalUser } from '../../../../../models/user'; -/** - * Add a user to a user list - */ -module.exports = async (params: any, me: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '自分の作成したユーザーリスト一覧を取得します。' + }, + + requireCredential: true, + + kind: 'account-read' +}; + +export default async (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Fetch lists const userLists = await UserList.find({ userId: me._id, diff --git a/src/server/api/endpoints/users/lists/push.ts b/src/server/api/endpoints/users/lists/push.ts index d8a3b3c9aa..bd4e201bde 100644 --- a/src/server/api/endpoints/users/lists/push.ts +++ b/src/server/api/endpoints/users/lists/push.ts @@ -1,15 +1,26 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import UserList from '../../../../../models/user-list'; import User, { pack as packUser, isRemoteUser, getGhost, ILocalUser } from '../../../../../models/user'; -import { publishUserListStream } from '../../../../../publishers/stream'; +import { publishUserListStream } from '../../../../../stream'; import ap from '../../../../../remote/activitypub/renderer'; import renderFollow from '../../../../../remote/activitypub/renderer/follow'; import { deliver } from '../../../../../queue'; +export const meta = { + desc: { + ja: '指定したユーザーリストに指定したユーザーを追加します。', + en: 'Add a user to a user list.' + }, + + requireCredential: true, + + kind: 'account-write' +}; + /** * Add a user to a user list */ -module.exports = async (params: any, me: ILocalUser) => new Promise(async (res, rej) => { +export default async (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'listId' parameter const [listId, listIdErr] = $.type(ID).get(params.listId); if (listIdErr) return rej('invalid listId param'); diff --git a/src/server/api/endpoints/users/lists/show.ts b/src/server/api/endpoints/users/lists/show.ts index e4ae239613..2fd142a609 100644 --- a/src/server/api/endpoints/users/lists/show.ts +++ b/src/server/api/endpoints/users/lists/show.ts @@ -1,11 +1,19 @@ -import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import UserList, { pack } from '../../../../../models/user-list'; import { ILocalUser } from '../../../../../models/user'; -/** - * Show a user list - */ -module.exports = async (params: any, me: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: '指定したユーザーリストの情報を取得します。', + en: 'Show a user list.' + }, + + requireCredential: true, + + kind: 'account-read' +}; + +export default async (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'listId' parameter const [listId, listIdErr] = $.type(ID).get(params.listId); if (listIdErr) return rej('invalid listId param'); diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index 222a8d950a..c60050d3cd 100644 --- a/src/server/api/endpoints/users/notes.ts +++ b/src/server/api/endpoints/users/notes.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import getHostLower from '../../common/get-host-lower'; import Note, { pack } from '../../../../models/note'; import User, { ILocalUser } from '../../../../models/user'; @@ -6,13 +6,13 @@ import User, { ILocalUser } from '../../../../models/user'; /** * Get notes of a user */ -module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'userId' parameter - const [userId, userIdErr] = $.type(ID).optional().get(params.userId); + const [userId, userIdErr] = $.type(ID).optional.get(params.userId); if (userIdErr) return rej('invalid userId param'); // Get 'username' parameter - const [username, usernameErr] = $.str.optional().get(params.username); + const [username, usernameErr] = $.str.optional.get(params.username); if (usernameErr) return rej('invalid username param'); if (userId === undefined && username === undefined) { @@ -20,7 +20,7 @@ module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) = } // Get 'host' parameter - const [host, hostErr] = $.str.optional().get(params.host); + const [host, hostErr] = $.str.optional.get(params.host); if (hostErr) return rej('invalid host param'); if (userId === undefined && host === undefined) { @@ -28,31 +28,31 @@ module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) = } // Get 'includeReplies' parameter - const [includeReplies = true, includeRepliesErr] = $.bool.optional().get(params.includeReplies); + const [includeReplies = true, includeRepliesErr] = $.bool.optional.get(params.includeReplies); if (includeRepliesErr) return rej('invalid includeReplies param'); // Get 'withMedia' parameter - const [withMedia = false, withMediaErr] = $.bool.optional().get(params.withMedia); + const [withMedia = false, withMediaErr] = $.bool.optional.get(params.withMedia); if (withMediaErr) return rej('invalid withMedia param'); // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId); + const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); if (sinceIdErr) return rej('invalid sinceId param'); // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId); + const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); if (untilIdErr) return rej('invalid untilId param'); // Get 'sinceDate' parameter - const [sinceDate, sinceDateErr] = $.num.optional().get(params.sinceDate); + const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate); if (sinceDateErr) throw 'invalid sinceDate param'; // Get 'untilDate' parameter - const [untilDate, untilDateErr] = $.num.optional().get(params.untilDate); + const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate); if (untilDateErr) throw 'invalid untilDate param'; // Check if only one of sinceId, untilId, sinceDate, untilDate specified diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts index 1d0d889f11..13377e6fff 100644 --- a/src/server/api/endpoints/users/recommendation.ts +++ b/src/server/api/endpoints/users/recommendation.ts @@ -4,16 +4,23 @@ import User, { pack, ILocalUser } from '../../../../models/user'; import { getFriendIds } from '../../common/get-friends'; import Mute from '../../../../models/mute'; -/** - * Get recommended users - */ -module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { +export const meta = { + desc: { + ja: 'おすすめのユーザー一覧を取得します。' + }, + + requireCredential: true, + + kind: 'account-read' +}; + +export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset); + const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); if (offsetErr) return rej('invalid offset param'); // ID list of the user itself and other users who the user follows diff --git a/src/server/api/endpoints/users/search.ts b/src/server/api/endpoints/users/search.ts index e29c8d32f1..d443d35b47 100644 --- a/src/server/api/endpoints/users/search.ts +++ b/src/server/api/endpoints/users/search.ts @@ -5,13 +5,13 @@ const escapeRegexp = require('escape-regexp'); /** * Search a user */ -module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'query' parameter const [query, queryError] = $.str.pipe(x => x != '').get(params.query); if (queryError) return rej('invalid query param'); // Get 'max' parameter - const [max = 10, maxErr] = $.num.optional().range(1, 30).get(params.max); + const [max = 10, maxErr] = $.num.optional.range(1, 30).get(params.max); if (maxErr) return rej('invalid max param'); const escapedQuery = escapeRegexp(query); diff --git a/src/server/api/endpoints/users/search_by_username.ts b/src/server/api/endpoints/users/search_by_username.ts index 937f9af589..bfab378389 100644 --- a/src/server/api/endpoints/users/search_by_username.ts +++ b/src/server/api/endpoints/users/search_by_username.ts @@ -1,41 +1,68 @@ import $ from 'cafy'; import User, { pack, ILocalUser } from '../../../../models/user'; +const escapeRegexp = require('escape-regexp'); /** * Search a user by username */ -module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'query' parameter const [query, queryError] = $.str.get(params.query); if (queryError) return rej('invalid query param'); // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset); + const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); if (offsetErr) return rej('invalid offset param'); // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); let users = await User .find({ host: null, - usernameLower: new RegExp(query.toLowerCase()) + usernameLower: new RegExp('^' + escapeRegexp(query.toLowerCase())) }, { limit: limit, skip: offset }); if (users.length < limit) { - const remoteUsers = await User + const otherUsers = await User .find({ host: { $ne: null }, - usernameLower: new RegExp(query.toLowerCase()) + usernameLower: new RegExp('^' + escapeRegexp(query.toLowerCase())) }, { limit: limit - users.length }); - users = users.concat(remoteUsers); + users = users.concat(otherUsers); + } + + if (users.length < limit) { + const otherUsers = await User + .find({ + _id: { $nin: users.map(u => u._id) }, + host: null, + usernameLower: new RegExp(escapeRegexp(query.toLowerCase())) + }, { + limit: limit - users.length + }); + + users = users.concat(otherUsers); + } + + if (users.length < limit) { + const otherUsers = await User + .find({ + _id: { $nin: users.map(u => u._id) }, + host: { $ne: null }, + usernameLower: new RegExp(escapeRegexp(query.toLowerCase())) + }, { + limit: limit - users.length + }); + + users = users.concat(otherUsers); } // Serialize diff --git a/src/server/api/endpoints/users/show.ts b/src/server/api/endpoints/users/show.ts index bf7e2a2312..8ec0eb8dd9 100644 --- a/src/server/api/endpoints/users/show.ts +++ b/src/server/api/endpoints/users/show.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../cafy-id'; +import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import User, { pack, ILocalUser } from '../../../../models/user'; import resolveRemoteUser from '../../../../remote/resolve-user'; @@ -7,23 +7,23 @@ const cursorOption = { fields: { data: false } }; /** * Show user(s) */ -module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { +export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { let user; // Get 'userId' parameter - const [userId, userIdErr] = $.type(ID).optional().get(params.userId); + const [userId, userIdErr] = $.type(ID).optional.get(params.userId); if (userIdErr) return rej('invalid userId param'); // Get 'userIds' parameter - const [userIds, userIdsErr] = $.arr($.type(ID)).optional().get(params.userIds); + const [userIds, userIdsErr] = $.arr($.type(ID)).optional.get(params.userIds); if (userIdsErr) return rej('invalid userIds param'); // Get 'username' parameter - const [username, usernameErr] = $.str.optional().get(params.username); + const [username, usernameErr] = $.str.optional.get(params.username); if (usernameErr) return rej('invalid username param'); // Get 'host' parameter - const [host, hostErr] = $.str.optional().nullable().get(params.host); + const [host, hostErr] = $.str.optional.nullable.get(params.host); if (hostErr) return rej('invalid host param'); if (userIds) { diff --git a/src/server/api/get-params.ts b/src/server/api/get-params.ts new file mode 100644 index 0000000000..e495e3ef3e --- /dev/null +++ b/src/server/api/get-params.ts @@ -0,0 +1,27 @@ +import { Context } from 'cafy'; + +type Defs = { + params: { [key: string]: Context<any> } +}; + +export default function <T extends Defs>(defs: T, params: any): [{ + [P in keyof T['params']]: ReturnType<T['params'][P]['get']>[0]; +}, Error] { + const x: any = {}; + let err: Error = null; + Object.keys(defs.params).some(k => { + const [v, e] = defs.params[k].get(params[k]); + if (e) { + err = e; + return true; + } else { + if (v === undefined && defs.params[k].data.default) { + x[k] = defs.params[k].data.default; + } else { + x[k] = v; + } + return false; + } + }); + return [x, err]; +} diff --git a/src/server/api/index.ts b/src/server/api/index.ts index 004c21b821..3ec7a28df9 100644 --- a/src/server/api/index.ts +++ b/src/server/api/index.ts @@ -35,7 +35,7 @@ const router = new Router(); /** * Register endpoint handlers */ -endpoints.forEach(endpoint => endpoint.withFile +endpoints.forEach(endpoint => endpoint.meta.requireFile ? router.post(`/${endpoint.name}`, upload.single('file'), handler.bind(null, endpoint)) : router.post(`/${endpoint.name}`, handler.bind(null, endpoint)) ); diff --git a/src/server/api/limitter.ts b/src/server/api/limitter.ts index b84e16ecde..20a18a7098 100644 --- a/src/server/api/limitter.ts +++ b/src/server/api/limitter.ts @@ -1,14 +1,14 @@ import * as Limiter from 'ratelimiter'; import * as debug from 'debug'; import limiterDB from '../../db/redis'; -import { Endpoint } from './endpoints'; -import getAcct from '../../acct/render'; +import { IEndpoint } from './endpoints'; +import getAcct from '../../misc/acct/render'; import { IUser } from '../../models/user'; const log = debug('misskey:limitter'); -export default (endpoint: Endpoint, user: IUser) => new Promise((ok, reject) => { - const limitation = endpoint.limit; +export default (endpoint: IEndpoint, user: IUser) => new Promise((ok, reject) => { + const limitation = endpoint.meta.limit; const key = limitation.hasOwnProperty('key') ? limitation.key diff --git a/src/server/api/private/signin.ts b/src/server/api/private/signin.ts index 5450c7ad27..9719329ddb 100644 --- a/src/server/api/private/signin.ts +++ b/src/server/api/private/signin.ts @@ -3,7 +3,7 @@ import * as bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; import User, { ILocalUser } from '../../../models/user'; import Signin, { pack } from '../../../models/signin'; -import event from '../../../publishers/stream'; +import event from '../../../stream'; import signin from '../common/signin'; import config from '../../../config'; @@ -11,9 +11,10 @@ export default async (ctx: Koa.Context) => { ctx.set('Access-Control-Allow-Origin', config.url); ctx.set('Access-Control-Allow-Credentials', 'true'); - const username = ctx.request.body['username']; - const password = ctx.request.body['password']; - const token = ctx.request.body['token']; + const body = ctx.request.body as any; + const username = body['username']; + const password = body['password']; + const token = body['token']; if (typeof username != 'string') { ctx.status = 400; @@ -35,11 +36,11 @@ export default async (ctx: Koa.Context) => { usernameLower: username.toLowerCase(), host: null }, { - fields: { - data: false, - profile: false - } - }) as ILocalUser; + fields: { + data: false, + profile: false + } + }) as ILocalUser; if (user === null) { ctx.throw(404, { diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index cb47d400b0..563b6ca845 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -7,15 +7,19 @@ import generateUserToken from '../common/generate-native-user-token'; import config from '../../../config'; import Meta from '../../../models/meta'; -recaptcha.init({ - secret_key: config.recaptcha.secret_key -}); +if (config.recaptcha) { + recaptcha.init({ + secret_key: config.recaptcha.secret_key + }); +} export default async (ctx: Koa.Context) => { + const body = ctx.request.body as any; + // Verify recaptcha // ただしテスト時はこの機構は障害となるため無効にする - if (process.env.NODE_ENV !== 'test') { - const success = await recaptcha(ctx.request.body['g-recaptcha-response']); + if (process.env.NODE_ENV !== 'test' && config.recaptcha != null) { + const success = await recaptcha(body['g-recaptcha-response']); if (!success) { ctx.throw(400, 'recaptcha-failed'); @@ -23,8 +27,8 @@ export default async (ctx: Koa.Context) => { } } - const username = ctx.request.body['username']; - const password = ctx.request.body['password']; + const username = body['username']; + const password = body['password']; // Validate username if (!validateUsername(username)) { @@ -44,8 +48,8 @@ export default async (ctx: Koa.Context) => { usernameLower: username.toLowerCase(), host: null }, { - limit: 1 - }); + limit: 1 + }); // Check username already used if (usernameExist !== 0) { @@ -70,14 +74,12 @@ export default async (ctx: Koa.Context) => { followingCount: 0, name: null, notesCount: 0, - driveCapacity: 1024 * 1024 * 128, // 128MiB username: username, usernameLower: username.toLowerCase(), host: null, keypair: generateKeypair(), token: secret, email: null, - links: null, password: hash, profile: { bio: null, diff --git a/src/server/api/service/twitter.ts b/src/server/api/service/twitter.ts index 8c35509cce..080f5879a3 100644 --- a/src/server/api/service/twitter.ts +++ b/src/server/api/service/twitter.ts @@ -4,7 +4,7 @@ import * as uuid from 'uuid'; import autwh from 'autwh'; import redis from '../../../db/redis'; import User, { pack, ILocalUser } from '../../../models/user'; -import event from '../../../publishers/stream'; +import event from '../../../stream'; import config from '../../../config'; import signin from '../common/signin'; diff --git a/src/server/api/stream/reversi-game.ts b/src/server/api/stream/games/reversi-game.ts index ea8a9741d2..da949e90ff 100644 --- a/src/server/api/stream/reversi-game.ts +++ b/src/server/api/stream/games/reversi-game.ts @@ -1,10 +1,10 @@ import * as websocket from 'websocket'; import * as redis from 'redis'; import * as CRC32 from 'crc-32'; -import ReversiGame, { pack } from '../../../models/reversi-game'; -import { publishReversiGameStream } from '../../../publishers/stream'; -import Reversi from '../../../reversi/core'; -import * as maps from '../../../reversi/maps'; +import ReversiGame, { pack } from '../../../../models/games/reversi/game'; +import { publishReversiGameStream } from '../../../../stream'; +import Reversi from '../../../../games/reversi/core'; +import * as maps from '../../../../games/reversi/maps'; import { ParsedUrlQuery } from 'querystring'; export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user?: any): void { @@ -87,8 +87,8 @@ export default function(request: websocket.request, connection: websocket.connec const set = game.user1Id.equals(user._id) ? { form1: form } : { - form2: form - }; + form2: form + }; await ReversiGame.update({ _id: gameId }, { $set: set @@ -117,8 +117,8 @@ export default function(request: websocket.request, connection: websocket.connec const set = game.user1Id.equals(user._id) ? { form2: form } : { - form1: form - }; + form1: form + }; await ReversiGame.update({ _id: gameId }, { $set: set @@ -193,7 +193,7 @@ export default function(request: websocket.request, connection: websocket.connec function getRandomMap() { const mapCount = Object.entries(maps).length; const rnd = Math.floor(Math.random() * mapCount); - return Object.entries(maps).find((x, i) => i == rnd)[1].data; + return Object.values(maps)[rnd].data; } const map = freshGame.settings.map != null ? freshGame.settings.map : getRandomMap(); @@ -227,11 +227,11 @@ export default function(request: websocket.request, connection: websocket.connec await ReversiGame.update({ _id: gameId }, { - $set: { - isEnded: true, - winnerId: winner - } - }); + $set: { + isEnded: true, + winnerId: winner + } + }); publishReversiGameStream(gameId, 'ended', { winnerId: winner, @@ -293,15 +293,15 @@ export default function(request: websocket.request, connection: websocket.connec await ReversiGame.update({ _id: gameId }, { - $set: { - crc32, - isEnded: o.isEnded, - winnerId: winner - }, - $push: { - logs: log - } - }); + $set: { + crc32, + isEnded: o.isEnded, + winnerId: winner + }, + $push: { + logs: log + } + }); publishReversiGameStream(gameId, 'set', Object.assign(log, { next: o.turn diff --git a/src/server/api/stream/reversi.ts b/src/server/api/stream/games/reversi.ts index 35c6167364..3f23466520 100644 --- a/src/server/api/stream/reversi.ts +++ b/src/server/api/stream/games/reversi.ts @@ -1,8 +1,8 @@ import * as mongo from 'mongodb'; import * as websocket from 'websocket'; import * as redis from 'redis'; -import Matching, { pack } from '../../../models/reversi-matching'; -import publishUserStream from '../../../publishers/stream'; +import Matching, { pack } from '../../../../models/games/reversi/matching'; +import publishUserStream from '../../../../stream'; export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void { // Subscribe reversi stream diff --git a/src/server/api/stream/hybrid-timeline.ts b/src/server/api/stream/hybrid-timeline.ts new file mode 100644 index 0000000000..513af9c1d4 --- /dev/null +++ b/src/server/api/stream/hybrid-timeline.ts @@ -0,0 +1,47 @@ +import * as websocket from 'websocket'; +import * as redis from 'redis'; + +import { IUser } from '../../../models/user'; +import Mute from '../../../models/mute'; +import { pack } from '../../../models/note'; + +export default async function( + request: websocket.request, + connection: websocket.connection, + subscriber: redis.RedisClient, + user: IUser +) { + // Subscribe stream + subscriber.subscribe('misskey:hybrid-timeline', `misskey:hybrid-timeline:${user._id}`); + + const mute = await Mute.find({ muterId: user._id }); + const mutedUserIds = mute.map(m => m.muteeId.toString()); + + subscriber.on('message', async (_, data) => { + const note = JSON.parse(data); + + //#region 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する + if (mutedUserIds.indexOf(note.userId) != -1) { + return; + } + if (note.reply != null && mutedUserIds.indexOf(note.reply.userId) != -1) { + return; + } + if (note.renote != null && mutedUserIds.indexOf(note.renote.userId) != -1) { + return; + } + //#endregion + + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await pack(note.renoteId, user, { + detail: true + }); + } + + connection.send(JSON.stringify({ + type: 'note', + body: note + })); + }); +} diff --git a/src/server/api/stream/local-timeline.ts b/src/server/api/stream/local-timeline.ts index 8f6a445be0..32718810dc 100644 --- a/src/server/api/stream/local-timeline.ts +++ b/src/server/api/stream/local-timeline.ts @@ -12,7 +12,7 @@ export default async function( user: IUser ) { // Subscribe stream - subscriber.subscribe(`misskey:local-timeline`); + subscriber.subscribe('misskey:local-timeline'); const mute = await Mute.find({ muterId: user._id }); const mutedUserIds = mute.map(m => m.muteeId.toString()); diff --git a/src/server/api/streaming.ts b/src/server/api/streaming.ts index 81adff0b58..afa0de2ce1 100644 --- a/src/server/api/streaming.ts +++ b/src/server/api/streaming.ts @@ -5,13 +5,14 @@ import config from '../../config'; import homeStream from './stream/home'; import localTimelineStream from './stream/local-timeline'; +import hybridTimelineStream from './stream/hybrid-timeline'; import globalTimelineStream from './stream/global-timeline'; import userListStream from './stream/user-list'; import driveStream from './stream/drive'; import messagingStream from './stream/messaging'; import messagingIndexStream from './stream/messaging-index'; -import reversiGameStream from './stream/reversi-game'; -import reversiStream from './stream/reversi'; +import reversiGameStream from './stream/games/reversi-game'; +import reversiStream from './stream/games/reversi'; import serverStatsStream from './stream/server-stats'; import notesStatsStream from './stream/notes-stats'; import { ParsedUrlQuery } from 'querystring'; @@ -50,7 +51,7 @@ module.exports = (server: http.Server) => { const q = request.resourceURL.query as ParsedUrlQuery; const [user, app] = await authenticate(q.i as string); - if (request.resourceURL.pathname === '/reversi-game') { + if (request.resourceURL.pathname === '/games/reversi-game') { reversiGameStream(request, connection, subscriber, user); return; } @@ -64,12 +65,13 @@ module.exports = (server: http.Server) => { const channel: any = request.resourceURL.pathname === '/' ? homeStream : request.resourceURL.pathname === '/local-timeline' ? localTimelineStream : + request.resourceURL.pathname === '/hybrid-timeline' ? hybridTimelineStream : request.resourceURL.pathname === '/global-timeline' ? globalTimelineStream : request.resourceURL.pathname === '/user-list' ? userListStream : request.resourceURL.pathname === '/drive' ? driveStream : request.resourceURL.pathname === '/messaging' ? messagingStream : request.resourceURL.pathname === '/messaging-index' ? messagingIndexStream : - request.resourceURL.pathname === '/reversi' ? reversiStream : + request.resourceURL.pathname === '/games/reversi' ? reversiStream : null; if (channel !== null) { diff --git a/src/server/file/assets/avatar.jpg b/src/server/file/assets/avatar.jpg Binary files differindex 3c803f568e..be0c3ca829 100644 --- a/src/server/file/assets/avatar.jpg +++ b/src/server/file/assets/avatar.jpg diff --git a/src/server/file/assets/bad-egg.png b/src/server/file/assets/bad-egg.png Binary files differindex a7c5930bd4..e96ba0dcc1 100644 --- a/src/server/file/assets/bad-egg.png +++ b/src/server/file/assets/bad-egg.png diff --git a/src/server/file/assets/cache-expired.png b/src/server/file/assets/cache-expired.png Binary files differindex ea681af0a0..5d988c502b 100644 --- a/src/server/file/assets/cache-expired.png +++ b/src/server/file/assets/cache-expired.png diff --git a/src/server/file/assets/not-an-image.png b/src/server/file/assets/not-an-image.png Binary files differindex bf98b293f7..39e4aa0892 100644 --- a/src/server/file/assets/not-an-image.png +++ b/src/server/file/assets/not-an-image.png diff --git a/src/server/file/assets/thumbnail-not-available.png b/src/server/file/assets/thumbnail-not-available.png Binary files differindex f960ce4d00..07cad9919c 100644 --- a/src/server/file/assets/thumbnail-not-available.png +++ b/src/server/file/assets/thumbnail-not-available.png diff --git a/src/server/file/assets/tombstone.png b/src/server/file/assets/tombstone.png Binary files differindex 86224e3182..83159d6b3c 100644 --- a/src/server/file/assets/tombstone.png +++ b/src/server/file/assets/tombstone.png diff --git a/src/server/file/send-drive-file.ts b/src/server/file/send-drive-file.ts index e04400317f..1a76b0e41f 100644 --- a/src/server/file/send-drive-file.ts +++ b/src/server/file/send-drive-file.ts @@ -37,7 +37,7 @@ export default async function(ctx: Koa.Context) { return; } - if (file.metadata.isMetaOnly) { + if (file.metadata.withoutChunks) { ctx.status = 204; return; } diff --git a/src/server/web/docs.ts b/src/server/web/docs.ts index e65cc87b12..f0181c4b94 100644 --- a/src/server/web/docs.ts +++ b/src/server/web/docs.ts @@ -2,26 +2,236 @@ * Docs */ +import * as fs from 'fs'; +import * as path from 'path'; +import * as showdown from 'showdown'; +import 'showdown-highlightjs-extension'; import ms = require('ms'); import * as Router from 'koa-router'; import * as send from 'koa-send'; +import { Context, ObjectContext } from 'cafy'; +import * as glob from 'glob'; +import * as yaml from 'js-yaml'; +import config from '../../config'; +import I18n from '../../misc/i18n'; +import { licenseHtml } from '../../misc/license'; +const constants = require('../../const.json'); +import endpoints from '../api/endpoints'; -const docs = `${__dirname}/../../client/docs/`; +async function genVars(lang: string): Promise<{ [key: string]: any }> { + const vars = {} as { [key: string]: any }; + + vars['lang'] = lang; + + const cwd = path.resolve(__dirname + '/../../../') + '/'; + + vars['endpoints'] = endpoints; + + const entities = glob.sync('src/docs/api/entities/**/*.yaml', { cwd }); + vars['entities'] = entities.map(x => { + const _x = yaml.safeLoad(fs.readFileSync(cwd + x, 'utf-8')) as any; + return _x.name; + }); + + const docs = glob.sync(`src/docs/**/*.${lang}.md`, { cwd }); + vars['docs'] = {}; + docs.forEach(x => { + const [, name] = x.match(/docs\/(.+?)\.(.+?)\.md$/); + if (vars['docs'][name] == null) { + vars['docs'][name] = { + name, + title: {} + }; + } + vars['docs'][name]['title'][lang] = fs.readFileSync(cwd + x, 'utf-8').match(/^# (.+?)\r?\n/)[1]; + }); + + vars['kebab'] = (string: string) => string.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/\s+/g, '-').toLowerCase(); + + vars['config'] = config; + + vars['copyright'] = constants.copyright; + + vars['license'] = licenseHtml; + + const i18n = new I18n(lang); + vars['i18n'] = (key: string) => i18n.get(null, key); + + return vars; +} + +// WIP type +const parseParamDefinition = (key: string, param: Context) => { + return Object.assign({ + name: key, + type: param.getType() + }, param.data); +}; + +const parsePropDefinition = (key: string, prop: any) => { + const id = prop.type.match(/^id\((.+?)\)|^id/); + const entity = prop.type.match(/^entity\((.+?)\)/); + const isObject = /^object/.test(prop.type); + const isDate = /^date/.test(prop.type); + const isArray = /\[\]$/.test(prop.type); + if (id) { + prop.kind = 'id'; + prop.type = 'string'; + prop.entity = id[1]; + if (isArray) { + prop.type += '[]'; + } + } + if (entity) { + prop.kind = 'entity'; + prop.type = 'object'; + prop.entity = entity[1]; + if (isArray) { + prop.type += '[]'; + } + } + if (isObject) { + prop.kind = 'object'; + if (prop.props) { + prop.hasDef = true; + } + } + if (isDate) { + prop.kind = 'date'; + prop.type = 'string'; + if (isArray) { + prop.type += '[]'; + } + } + + if (prop.optional) { + prop.type += '?'; + } + + prop.name = key; + + return prop; +}; + +const sortParams = (params: Array<{ name: string }>) => { + return params; +}; + +// WIP type +const extractParamDefRef = (params: Context[]) => { + let defs: any[] = []; + + params.forEach(param => { + if (param.data && param.data.ref) { + const props = (param as ObjectContext<any>).props; + defs.push({ + name: param.data.ref, + params: sortParams(Object.keys(props).map(k => parseParamDefinition(k, props[k]))) + }); + + const childDefs = extractParamDefRef(Object.keys(props).map(k => props[k])); + + defs = defs.concat(childDefs); + } + }); + + return sortParams(defs); +}; + +const extractPropDefRef = (props: any[]) => { + let defs: any[] = []; + + Object.entries(props).forEach(([k, v]) => { + if (v.props) { + defs.push({ + name: k, + props: sortParams(Object.entries(v.props).map(([k, v]) => parsePropDefinition(k, v))) + }); + + const childDefs = extractPropDefRef(v.props); + + defs = defs.concat(childDefs); + } + }); + + return sortParams(defs); +}; const router = new Router(); router.get('/assets/*', async ctx => { await send(ctx, ctx.params[0], { - root: docs + '/assets/', + root: `${__dirname}/../../docs/assets/`, maxage: ms('7 days'), immutable: true }); }); -router.get('*', async ctx => { - await send(ctx, `${ctx.params[0]}.html`, { - root: docs +router.get('/*/api/endpoints/*', async ctx => { + const lang = ctx.params[0]; + const name = ctx.params[1]; + const ep = endpoints.find(e => e.name === name); + + const vars = { + title: name, + endpoint: ep.meta, + url: { + host: config.api_url, + path: name + }, + // @ts-ignore + params: ep.meta.params ? sortParams(Object.entries(ep.meta.params).map(([k, v]) => parseParamDefinition(k, v))) : null, + paramDefs: ep.meta.params ? extractParamDefRef(Object.values(ep.meta.params)) : null, + res: ep.meta.res, + resProps: ep.meta.res && ep.meta.res.props ? sortParams(Object.entries(ep.meta.res.props).map(([k, v]) => parsePropDefinition(k, v))) : null, + resDefs: null as any, //extractPropDefRef(Object.entries(ep.res.props).map(([k, v]) => parsePropDefinition(k, v))) + src: `https://github.com/syuilo/misskey/tree/master/src/server/api/endpoints/${name}.ts` + }; + + await ctx.render('../../../../src/docs/api/endpoints/view', Object.assign(await genVars(lang), vars)); +}); + +router.get('/*/api/entities/*', async ctx => { + const lang = ctx.params[0]; + const entity = ctx.params[1]; + + const x = yaml.safeLoad(fs.readFileSync(path.resolve(__dirname + '/../../../src/docs/api/entities/' + entity + '.yaml'), 'utf-8')) as any; + + await ctx.render('../../../../src/docs/api/entities/view', Object.assign(await genVars(lang), { + name: x.name, + desc: x.desc, + props: sortParams(Object.entries(x.props).map(([k, v]) => parsePropDefinition(k, v))), + propDefs: extractPropDefRef(x.props) + })); +}); + +router.get('/*/*', async ctx => { + const lang = ctx.params[0]; + const doc = ctx.params[1]; + + showdown.extension('urlExtension', () => ({ + type: 'output', + regex: /%URL%/g, + replace: config.url + })); + + showdown.extension('apiUrlExtension', () => ({ + type: 'output', + regex: /%API_URL%/g, + replace: config.api_url + })); + + const conv = new showdown.Converter({ + tables: true, + extensions: ['urlExtension', 'apiUrlExtension', 'highlightjs'] }); + const md = fs.readFileSync(`${__dirname}/../../../src/docs/${doc}.${lang}.md`, 'utf8'); + + await ctx.render('../../../../src/docs/article', Object.assign({ + html: conv.makeHtml(md), + title: md.match(/^# (.+?)\r?\n/)[1], + src: `https://github.com/syuilo/misskey/tree/master/src/docs/${doc}.${lang}.md` + }, await genVars(lang))); }); export default router; diff --git a/src/server/web/index.ts b/src/server/web/index.ts index 4400fc1024..7291f8a0a5 100644 --- a/src/server/web/index.ts +++ b/src/server/web/index.ts @@ -11,11 +11,11 @@ import * as views from 'koa-views'; import docs from './docs'; import User from '../../models/user'; -import parseAcct from '../../acct/parse'; -import { fa } from '../../build/fa'; +import parseAcct from '../../misc/acct/parse'; +import { fa } from '../../misc/fa'; import config from '../../config'; import Note, { pack as packNote } from '../../models/note'; -import getNoteSummary from '../../renderers/get-note-summary'; +import getNoteSummary from '../../misc/get-note-summary'; const consts = require('../../const.json'); const client = `${__dirname}/../../client/`; diff --git a/src/server/webfinger.ts b/src/server/webfinger.ts index ce0cb82fe2..6c2afae79c 100644 --- a/src/server/webfinger.ts +++ b/src/server/webfinger.ts @@ -2,7 +2,7 @@ import * as mongo from 'mongodb'; import * as Router from 'koa-router'; import config from '../config'; -import parseAcct from '../acct/parse'; +import parseAcct from '../misc/acct/parse'; import User, { IUser } from '../models/user'; // Init router |