From 931bdc6aace5e7aa71ffdfb470e208ead78a2a53 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 2 Nov 2018 03:32:24 +0900 Subject: [PATCH] Refactoring, Clean up and bug fixes --- package-lock.json | 10 +- package.json | 2 +- src/misc/cafy-id.ts | 28 ++-- src/models/user.ts | 2 +- src/server/activitypub/followers.ts | 4 +- src/server/activitypub/following.ts | 4 +- src/server/activitypub/outbox.ts | 6 +- src/server/api/endpoints.ts | 15 +- .../api/endpoints/admin/suspend-user.ts | 8 +- .../api/endpoints/admin/unsuspend-user.ts | 8 +- .../api/endpoints/admin/unverify-user.ts | 8 +- src/server/api/endpoints/admin/update-meta.ts | 30 ++-- src/server/api/endpoints/admin/verify-user.ts | 8 +- .../api/endpoints/aggregation/hashtags.ts | 66 -------- .../endpoints/aggregation/users/activity.ts | 110 -------------- .../api/endpoints/aggregation/users/post.ts | 104 ------------- .../endpoints/aggregation/users/reaction.ts | 74 --------- src/server/api/endpoints/ap/show.ts | 5 +- src/server/api/endpoints/app/show.ts | 24 +-- src/server/api/endpoints/blocking/create.ts | 8 +- src/server/api/endpoints/blocking/delete.ts | 8 +- src/server/api/endpoints/blocking/list.ts | 19 ++- src/server/api/endpoints/charts/drive.ts | 10 +- src/server/api/endpoints/charts/federation.ts | 10 +- src/server/api/endpoints/charts/hashtag.ts | 15 +- src/server/api/endpoints/charts/network.ts | 10 +- src/server/api/endpoints/charts/notes.ts | 10 +- src/server/api/endpoints/charts/user/drive.ts | 18 ++- .../api/endpoints/charts/user/following.ts | 18 ++- src/server/api/endpoints/charts/user/notes.ts | 18 ++- .../api/endpoints/charts/user/reactions.ts | 18 ++- src/server/api/endpoints/charts/users.ts | 10 +- src/server/api/endpoints/drive/files.ts | 74 +++++---- .../endpoints/drive/files/attached_notes.ts | 8 +- .../endpoints/drive/files/check_existence.ts | 5 +- .../api/endpoints/drive/files/create.ts | 20 ++- .../api/endpoints/drive/files/delete.ts | 8 +- src/server/api/endpoints/drive/files/find.ts | 36 +++-- src/server/api/endpoints/drive/files/show.ts | 8 +- .../api/endpoints/drive/files/update.ts | 30 ++-- .../endpoints/drive/files/upload_from_url.ts | 33 ++-- src/server/api/endpoints/drive/folders.ts | 65 ++++---- .../api/endpoints/drive/folders/create.ts | 13 +- .../api/endpoints/drive/folders/delete.ts | 8 +- .../api/endpoints/drive/folders/find.ts | 33 ++-- .../api/endpoints/drive/folders/show.ts | 8 +- .../api/endpoints/drive/folders/update.ts | 19 ++- src/server/api/endpoints/drive/stream.ts | 62 ++++---- src/server/api/endpoints/following/create.ts | 8 +- src/server/api/endpoints/following/delete.ts | 8 +- .../endpoints/following/requests/accept.ts | 20 ++- .../endpoints/following/requests/cancel.ts | 20 ++- .../api/endpoints/following/requests/list.ts | 2 +- .../endpoints/following/requests/reject.ts | 20 ++- src/server/api/endpoints/following/stalk.ts | 22 ++- src/server/api/endpoints/following/unstalk.ts | 22 ++- .../api/endpoints/games/reversi/games.ts | 57 ++++--- .../api/endpoints/games/reversi/games/show.ts | 19 ++- .../games/reversi/games/surrender.ts | 8 +- .../api/endpoints/games/reversi/match.ts | 23 ++- src/server/api/endpoints/hashtags/search.ts | 15 +- src/server/api/endpoints/i/favorites.ts | 51 ++++--- src/server/api/endpoints/i/notifications.ts | 84 ++++++----- src/server/api/endpoints/i/pin.ts | 8 +- src/server/api/endpoints/i/signin_history.ts | 52 ++++--- src/server/api/endpoints/i/unpin.ts | 8 +- src/server/api/endpoints/i/update.ts | 70 +++++---- .../api/endpoints/messaging/messages.ts | 86 ++++++----- .../endpoints/messaging/messages/create.ts | 46 +++--- .../api/endpoints/messaging/messages/read.ts | 8 +- src/server/api/endpoints/mute/create.ts | 24 +-- src/server/api/endpoints/mute/delete.ts | 24 +-- src/server/api/endpoints/mute/list.ts | 19 ++- src/server/api/endpoints/notes.ts | 47 +++--- .../api/endpoints/notes/conversation.ts | 52 ++++--- src/server/api/endpoints/notes/create.ts | 111 ++++++++------ src/server/api/endpoints/notes/delete.ts | 8 +- .../api/endpoints/notes/favorites/create.ts | 8 +- .../api/endpoints/notes/favorites/delete.ts | 8 +- src/server/api/endpoints/notes/featured.ts | 5 +- .../api/endpoints/notes/global-timeline.ts | 35 +++-- .../api/endpoints/notes/hybrid-timeline.ts | 54 ++++--- .../api/endpoints/notes/local-timeline.ts | 45 ++++-- src/server/api/endpoints/notes/mentions.ts | 29 ++-- src/server/api/endpoints/notes/polls/vote.ts | 40 ++--- src/server/api/endpoints/notes/reactions.ts | 30 ++-- .../api/endpoints/notes/reactions/create.ts | 13 +- .../api/endpoints/notes/reactions/delete.ts | 20 ++- src/server/api/endpoints/notes/renotes.ts | 81 ++++++++++ src/server/api/endpoints/notes/replies.ts | 50 +++--- src/server/api/endpoints/notes/reposts.ts | 66 -------- .../api/endpoints/notes/search_by_tag.ts | 142 ++++++------------ src/server/api/endpoints/notes/show.ts | 8 +- src/server/api/endpoints/notes/timeline.ts | 54 ++++--- .../api/endpoints/notes/user-list-timeline.ts | 60 +++++--- src/server/api/endpoints/users/followers.ts | 69 +++++---- src/server/api/endpoints/users/following.ts | 69 +++++---- .../users/get_frequently_replied_users.ts | 33 ++-- .../api/endpoints/users/lists/delete.ts | 8 +- src/server/api/endpoints/users/lists/push.ts | 33 ++-- src/server/api/endpoints/users/lists/show.ts | 19 ++- .../api/endpoints/users/lists/update.ts | 14 +- src/server/api/endpoints/users/notes.ts | 82 ++++++---- src/server/api/endpoints/users/relation.ts | 8 +- src/server/api/endpoints/users/search.ts | 20 ++- src/server/api/endpoints/users/show.ts | 75 +++++---- src/server/api/get-params.ts | 21 ++- src/server/web/docs.ts | 2 +- 108 files changed, 1722 insertions(+), 1539 deletions(-) delete mode 100644 src/server/api/endpoints/aggregation/hashtags.ts delete mode 100644 src/server/api/endpoints/aggregation/users/activity.ts delete mode 100644 src/server/api/endpoints/aggregation/users/post.ts delete mode 100644 src/server/api/endpoints/aggregation/users/reaction.ts create mode 100644 src/server/api/endpoints/notes/renotes.ts delete mode 100644 src/server/api/endpoints/notes/reposts.ts diff --git a/package-lock.json b/package-lock.json index 7257e735e..35a2f9b09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "10.36.0", + "version": "10.37.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2250,9 +2250,9 @@ } }, "cafy": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/cafy/-/cafy-11.3.0.tgz", - "integrity": "sha512-7kqqF4I6seSNSAWihRfnM78wP/OwaZMrCNIUzu0+TC1pDGfF2uoVfMsAJ1oV1jZsZ2L2qlUSvo9zhSEIouS/xQ==" + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/cafy/-/cafy-12.0.0.tgz", + "integrity": "sha512-HGsunRfyqFyG1/oh+Szw8GtVpj4pwehyqmp8sTO1QwDF3htjDP+vVBWzg7iOU2Y3Cm+h+UiEpf6DJ0p57RNmAg==" }, "caller-path": { "version": "0.1.0", @@ -15830,7 +15830,7 @@ }, "fast-deep-equal": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" }, "ignore": { diff --git a/package.json b/package.json index 4d03b7316..217124a42 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "bcryptjs": "2.4.3", "bee-queue": "1.2.2", "bootstrap-vue": "2.0.0-rc.11", - "cafy": "11.3.0", + "cafy": "12.0.0", "chai": "4.2.0", "chai-http": "4.2.0", "chalk": "2.4.1", diff --git a/src/misc/cafy-id.ts b/src/misc/cafy-id.ts index 3880f0bd0..621f7e794 100644 --- a/src/misc/cafy-id.ts +++ b/src/misc/cafy-id.ts @@ -4,23 +4,31 @@ import isObjectId from './is-objectid'; export const isAnId = (x: any) => mongo.ObjectID.isValid(x); export const isNotAnId = (x: any) => !isAnId(x); +export const transform = (x: string | mongo.ObjectID): mongo.ObjectID => { + if (x == null) return null; + + if (isAnId(x) && !isObjectId(x)) { + return new mongo.ObjectID(x); + } else { + return x as mongo.ObjectID; + } +}; +export const transformMany = (xs: (string | mongo.ObjectID)[]): mongo.ObjectID[] => { + if (xs == null) return null; + + return xs.map(x => transform(x)); +}; + +export type ObjectId = mongo.ObjectID; /** * ID */ -export default class ID extends Context { +export default class ID extends Context { constructor() { super(); - this.transform = v => { - if (isAnId(v) && !isObjectId(v)) { - return new mongo.ObjectID(v); - } else { - return v; - } - }; - - this.push(v => { + this.push((v: any) => { if (!isObjectId(v) && isNotAnId(v)) { return new Error('must-be-an-id'); } diff --git a/src/models/user.ts b/src/models/user.ts index f629ddbd2..1e5b6ad74 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -189,7 +189,7 @@ export async function getRelation(me: mongo.ObjectId, target: mongo.ObjectId) { return { isFollowing: following1 !== null, - isStalking: following1 && following1.stalk, + isStalking: following1 ? following1.stalk : false, hasPendingFollowRequestFromYou: followReq1 !== null, hasPendingFollowRequestToYou: followReq2 !== null, isFollowed: following2 !== null, diff --git a/src/server/activitypub/followers.ts b/src/server/activitypub/followers.ts index fcc75fc5b..5c809424c 100644 --- a/src/server/activitypub/followers.ts +++ b/src/server/activitypub/followers.ts @@ -1,7 +1,7 @@ import * as mongo from 'mongodb'; import * as Router from 'koa-router'; import config from '../../config'; -import $ from 'cafy'; import ID from '../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../misc/cafy-id'; import User from '../../models/user'; import Following from '../../models/following'; import pack from '../../remote/activitypub/renderer'; @@ -49,7 +49,7 @@ export default async (ctx: Router.IRouterContext) => { // カーソルが指定されている場合 if (cursor) { query._id = { - $lt: cursor + $lt: transform(cursor) }; } diff --git a/src/server/activitypub/following.ts b/src/server/activitypub/following.ts index 2c739ff07..a46bb9c7f 100644 --- a/src/server/activitypub/following.ts +++ b/src/server/activitypub/following.ts @@ -1,7 +1,7 @@ import * as mongo from 'mongodb'; import * as Router from 'koa-router'; import config from '../../config'; -import $ from 'cafy'; import ID from '../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../misc/cafy-id'; import User from '../../models/user'; import Following from '../../models/following'; import pack from '../../remote/activitypub/renderer'; @@ -49,7 +49,7 @@ export default async (ctx: Router.IRouterContext) => { // カーソルが指定されている場合 if (cursor) { query._id = { - $lt: cursor + $lt: transform(cursor) }; } diff --git a/src/server/activitypub/outbox.ts b/src/server/activitypub/outbox.ts index aeb6f25dd..24d4e3730 100644 --- a/src/server/activitypub/outbox.ts +++ b/src/server/activitypub/outbox.ts @@ -1,7 +1,7 @@ import * as mongo from 'mongodb'; import * as Router from 'koa-router'; import config from '../../config'; -import $ from 'cafy'; import ID from '../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../misc/cafy-id'; import User from '../../models/user'; import pack from '../../remote/activitypub/renderer'; import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection'; @@ -61,11 +61,11 @@ export default async (ctx: Router.IRouterContext) => { if (sinceId) { sort._id = 1; query._id = { - $gt: sinceId + $gt: transform(sinceId) }; } else if (untilId) { query._id = { - $lt: untilId + $lt: transform(untilId) }; } //#endregion diff --git a/src/server/api/endpoints.ts b/src/server/api/endpoints.ts index 6e5ca90c6..e764ac2e9 100644 --- a/src/server/api/endpoints.ts +++ b/src/server/api/endpoints.ts @@ -1,12 +1,21 @@ +import { Context } from 'cafy'; import * as path from 'path'; import * as glob from 'glob'; export interface IEndpointMeta { - stability?: 'deprecated' | 'experimental' | 'stable'; + stability?: string; //'deprecated' | 'experimental' | 'stable'; - desc?: any; + desc?: { [key: string]: string }; - params?: any; + params?: { + [key: string]: { + validator: Context; + transform?: any; + default?: any; + desc?: { [key: string]: string }; + ref?: string; + }; + }; res?: any; diff --git a/src/server/api/endpoints/admin/suspend-user.ts b/src/server/api/endpoints/admin/suspend-user.ts index 32c2416fb..6d8b28932 100644 --- a/src/server/api/endpoints/admin/suspend-user.ts +++ b/src/server/api/endpoints/admin/suspend-user.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID from '../../../../misc/cafy-id'; +import ID, { transform } from '../../../../misc/cafy-id'; import getParams from '../../get-params'; import User from '../../../../models/user'; @@ -13,12 +13,14 @@ export const meta = { requireAdmin: true, params: { - userId: $.type(ID).note({ + userId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to suspend' } - }), + }, } }; diff --git a/src/server/api/endpoints/admin/unsuspend-user.ts b/src/server/api/endpoints/admin/unsuspend-user.ts index 879c23ab1..5c736122b 100644 --- a/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/src/server/api/endpoints/admin/unsuspend-user.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID from '../../../../misc/cafy-id'; +import ID, { transform } from '../../../../misc/cafy-id'; import getParams from '../../get-params'; import User from '../../../../models/user'; @@ -13,12 +13,14 @@ export const meta = { requireAdmin: true, params: { - userId: $.type(ID).note({ + userId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to unsuspend' } - }), + }, } }; diff --git a/src/server/api/endpoints/admin/unverify-user.ts b/src/server/api/endpoints/admin/unverify-user.ts index 178049fa1..fc55bd747 100644 --- a/src/server/api/endpoints/admin/unverify-user.ts +++ b/src/server/api/endpoints/admin/unverify-user.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID from '../../../../misc/cafy-id'; +import ID, { transform } from '../../../../misc/cafy-id'; import getParams from '../../get-params'; import User from '../../../../models/user'; @@ -13,12 +13,14 @@ export const meta = { requireAdmin: true, params: { - userId: $.type(ID).note({ + userId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to unverify' } - }), + }, } }; diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts index f45efd7fe..aed53e12c 100644 --- a/src/server/api/endpoints/admin/update-meta.ts +++ b/src/server/api/endpoints/admin/update-meta.ts @@ -11,41 +11,47 @@ export const meta = { requireAdmin: true, params: { - broadcasts: $.arr($.obj()).optional.nullable.note({ + broadcasts: { + validator: $.arr($.obj()).optional.nullable, desc: { 'ja-JP': 'ブロードキャスト' } - }), + }, - emojis: $.arr($.obj()).optional.note({ + emojis: { + validator: $.arr($.obj()).optional, desc: { 'ja-JP': 'カスタム絵文字定義' } - }), + }, - disableRegistration: $.bool.optional.nullable.note({ + disableRegistration: { + validator: $.bool.optional.nullable, desc: { 'ja-JP': '招待制か否か' } - }), + }, - disableLocalTimeline: $.bool.optional.nullable.note({ + disableLocalTimeline: { + validator: $.bool.optional.nullable, desc: { 'ja-JP': 'ローカルタイムライン(とソーシャルタイムライン)を無効にするか否か' } - }), + }, - hidedTags: $.arr($.str).optional.nullable.note({ + hidedTags: { + validator: $.arr($.str).optional.nullable, desc: { 'ja-JP': '統計などで無視するハッシュタグ' } - }), + }, - bannerUrl: $.str.optional.nullable.note({ + bannerUrl: { + validator: $.str.optional.nullable, desc: { 'ja-JP': 'インスタンスのバナー画像URL' } - }), + }, } }; diff --git a/src/server/api/endpoints/admin/verify-user.ts b/src/server/api/endpoints/admin/verify-user.ts index dd07684de..b8c0bbaa8 100644 --- a/src/server/api/endpoints/admin/verify-user.ts +++ b/src/server/api/endpoints/admin/verify-user.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID from '../../../../misc/cafy-id'; +import ID, { transform } from '../../../../misc/cafy-id'; import getParams from '../../get-params'; import User from '../../../../models/user'; @@ -13,12 +13,14 @@ export const meta = { requireAdmin: true, params: { - userId: $.type(ID).note({ + userId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to verify' } - }), + }, } }; diff --git a/src/server/api/endpoints/aggregation/hashtags.ts b/src/server/api/endpoints/aggregation/hashtags.ts deleted file mode 100644 index ffeafb253..000000000 --- a/src/server/api/endpoints/aggregation/hashtags.ts +++ /dev/null @@ -1,66 +0,0 @@ -import Note from '../../../../models/note'; -import Meta from '../../../../models/meta'; - -export default () => new Promise(async (res, rej) => { - const meta = await Meta.findOne({}); - const hidedTags = meta ? (meta.hidedTags || []).map(t => t.toLowerCase()) : []; - - const span = 1000 * 60 * 60 * 24 * 7; // 1週間 - - //#region 1. 指定期間の内に投稿されたハッシュタグ(とユーザーのペア)を集計 - const data = await Note.aggregate([{ - $match: { - createdAt: { - $gt: new Date(Date.now() - span) - }, - tagsLower: { - $exists: true, - $ne: [] - } - } - }, { - $unwind: '$tagsLower' - }, { - $group: { - _id: { tag: '$tagsLower', userId: '$userId' } - } - }]) as Array<{ - _id: { - tag: string; - userId: any; - } - }>; - //#endregion - - if (data.length == 0) { - return res([]); - } - - let tags: Array<{ - name: string; - count: number; - }> = []; - - // カウント - data.map(x => x._id).forEach(x => { - // ブラックリストに登録されているタグなら弾く - if (hidedTags.includes(x.tag)) return; - - const i = tags.findIndex(tag => tag.name == x.tag); - if (i != -1) { - tags[i].count++; - } else { - tags.push({ - name: x.tag, - count: 1 - }); - } - }); - - // タグを人気順に並べ替え - tags = tags.sort((a, b) => b.count - a.count); - - tags = tags.slice(0, 30); - - res(tags); -}); diff --git a/src/server/api/endpoints/aggregation/users/activity.ts b/src/server/api/endpoints/aggregation/users/activity.ts deleted file mode 100644 index 0ec3f0db7..000000000 --- a/src/server/api/endpoints/aggregation/users/activity.ts +++ /dev/null @@ -1,110 +0,0 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; -import User from '../../../../../models/user'; -import Note from '../../../../../models/note'; - -// TODO: likeやfollowも集計 - -/** - * Aggregate activity of a user - */ -export default (params: any) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 365, limitErr] = $.num.optional.range(1, 365).get(params.limit); - if (limitErr) return rej('invalid limit param'); - - // Get 'userId' parameter - const [userId, userIdErr] = $.type(ID).get(params.userId); - if (userIdErr) return rej('invalid userId param'); - - // Lookup user - const user = await User.findOne({ - _id: userId - }, { - fields: { - _id: true - } - }); - - if (user === null) { - return rej('user not found'); - } - - const datas = await Note - .aggregate([ - { $match: { userId: user._id } }, - { $project: { - renoteId: '$renoteId', - replyId: '$replyId', - createdAt: { $add: ['$createdAt', 9 * 60 * 60 * 1000] } // Convert into JST - }}, - { $project: { - date: { - year: { $year: '$createdAt' }, - month: { $month: '$createdAt' }, - day: { $dayOfMonth: '$createdAt' } - }, - type: { - $cond: { - if: { $ne: ['$renoteId', null] }, - then: 'renote', - else: { - $cond: { - if: { $ne: ['$replyId', null] }, - then: 'reply', - else: 'note' - } - } - } - }} - }, - { $group: { _id: { - date: '$date', - type: '$type' - }, count: { $sum: 1 } } }, - { $group: { - _id: '$_id.date', - data: { $addToSet: { - type: '$_id.type', - count: '$count' - }} - } } - ]); - - datas.forEach((data: any) => { - data.date = data._id; - delete data._id; - - data.notes = (data.data.filter((x: any) => x.type == 'note')[0] || { count: 0 }).count; - data.renotes = (data.data.filter((x: any) => x.type == 'renote')[0] || { count: 0 }).count; - data.replies = (data.data.filter((x: any) => x.type == 'reply')[0] || { count: 0 }).count; - - delete data.data; - }); - - const graph = []; - - for (let i = 0; i < limit; i++) { - const day = new Date(new Date().setDate(new Date().getDate() - i)); - - const data = datas.filter((d: any) => - d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate() - )[0]; - - if (data) { - graph.push(data); - } else { - graph.push({ - date: { - year: day.getFullYear(), - month: day.getMonth() + 1, // In JavaScript, month is zero-based. - day: day.getDate() - }, - notes: 0, - renotes: 0, - replies: 0 - }); - } - } - - res(graph); -}); diff --git a/src/server/api/endpoints/aggregation/users/post.ts b/src/server/api/endpoints/aggregation/users/post.ts deleted file mode 100644 index 090f6d2f0..000000000 --- a/src/server/api/endpoints/aggregation/users/post.ts +++ /dev/null @@ -1,104 +0,0 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; -import User from '../../../../../models/user'; -import Note from '../../../../../models/note'; - -/** - * Aggregate note of a user - */ -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'); - - // Lookup user - const user = await User.findOne({ - _id: userId - }, { - fields: { - _id: true - } - }); - - if (user === null) { - return rej('user not found'); - } - - const datas = await Note - .aggregate([ - { $match: { userId: user._id } }, - { $project: { - renoteId: '$renoteId', - replyId: '$replyId', - createdAt: { $add: ['$createdAt', 9 * 60 * 60 * 1000] } // Convert into JST - }}, - { $project: { - date: { - year: { $year: '$createdAt' }, - month: { $month: '$createdAt' }, - day: { $dayOfMonth: '$createdAt' } - }, - type: { - $cond: { - if: { $ne: ['$renoteId', null] }, - then: 'renote', - else: { - $cond: { - if: { $ne: ['$replyId', null] }, - then: 'reply', - else: 'note' - } - } - } - }} - }, - { $group: { _id: { - date: '$date', - type: '$type' - }, count: { $sum: 1 } } }, - { $group: { - _id: '$_id.date', - data: { $addToSet: { - type: '$_id.type', - count: '$count' - }} - } } - ]); - - datas.forEach((data: any) => { - data.date = data._id; - delete data._id; - - data.notes = (data.data.filter((x: any) => x.type == 'note')[0] || { count: 0 }).count; - data.renotes = (data.data.filter((x: any) => x.type == 'renote')[0] || { count: 0 }).count; - data.replies = (data.data.filter((x: any) => x.type == 'reply')[0] || { count: 0 }).count; - - delete data.data; - }); - - const graph = []; - - for (let i = 0; i < 30; i++) { - const day = new Date(new Date().setDate(new Date().getDate() - i)); - - const data = datas.filter((d: any) => - d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate() - )[0]; - - if (data) { - graph.push(data); - } else { - graph.push({ - date: { - year: day.getFullYear(), - month: day.getMonth() + 1, // In JavaScript, month is zero-based. - day: day.getDate() - }, - notes: 0, - renotes: 0, - replies: 0 - }); - } - } - - res(graph); -}); diff --git a/src/server/api/endpoints/aggregation/users/reaction.ts b/src/server/api/endpoints/aggregation/users/reaction.ts deleted file mode 100644 index ce9e15096..000000000 --- a/src/server/api/endpoints/aggregation/users/reaction.ts +++ /dev/null @@ -1,74 +0,0 @@ -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 - */ -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'); - - // Lookup user - const user = await User.findOne({ - _id: userId - }, { - fields: { - _id: true - } - }); - - if (user === null) { - return rej('user not found'); - } - - const datas = await Reaction - .aggregate([ - { $match: { userId: user._id } }, - { $project: { - createdAt: { $add: ['$createdAt', 9 * 60 * 60 * 1000] } // Convert into JST - }}, - { $project: { - date: { - year: { $year: '$createdAt' }, - month: { $month: '$createdAt' }, - day: { $dayOfMonth: '$createdAt' } - } - }}, - { $group: { - _id: '$date', - count: { $sum: 1 } - }} - ]); - - datas.forEach((data: any) => { - data.date = data._id; - delete data._id; - }); - - const graph = []; - - for (let i = 0; i < 30; i++) { - const day = new Date(new Date().setDate(new Date().getDate() - i)); - - const data = datas.filter((d: any) => - d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate() - )[0]; - - if (data) { - graph.push(data); - } else { - graph.push({ - date: { - year: day.getFullYear(), - month: day.getMonth() + 1, // In JavaScript, month is zero-based. - day: day.getDate() - }, - count: 0 - }); - } - } - - res(graph); -}); diff --git a/src/server/api/endpoints/ap/show.ts b/src/server/api/endpoints/ap/show.ts index 490b2f1ad..c5286583d 100644 --- a/src/server/api/endpoints/ap/show.ts +++ b/src/server/api/endpoints/ap/show.ts @@ -16,11 +16,12 @@ export const meta = { requireCredential: false, params: { - uri: $.str.note({ + uri: { + validator: $.str, desc: { 'ja-JP': 'ActivityPubオブジェクトのURI' } - }), + }, }, }; diff --git a/src/server/api/endpoints/app/show.ts b/src/server/api/endpoints/app/show.ts index 8cc2abdc2..0d73985b4 100644 --- a/src/server/api/endpoints/app/show.ts +++ b/src/server/api/endpoints/app/show.ts @@ -1,19 +1,25 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import App, { pack, IApp } from '../../../../models/app'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; + +export const meta = { + params: { + appId: { + validator: $.type(ID), + transform: transform + }, + } +}; -/** - * Show an app - */ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + const isSecure = user != null && app == null; - // Get 'appId' parameter - const [appId, appIdErr] = $.type(ID).get(params.appId); - if (appIdErr) return rej('invalid appId param'); - // Lookup app - const ap = await App.findOne({ _id: appId }); + const ap = await App.findOne({ _id: ps.appId }); if (ap === null) { return rej('app not found'); diff --git a/src/server/api/endpoints/blocking/create.ts b/src/server/api/endpoints/blocking/create.ts index c3a4d24ae..7ec01b423 100644 --- a/src/server/api/endpoints/blocking/create.ts +++ b/src/server/api/endpoints/blocking/create.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; const ms = require('ms'); import User, { pack, ILocalUser } from '../../../../models/user'; import Blocking from '../../../../models/blocking'; @@ -23,12 +23,14 @@ export const meta = { kind: 'following-write', params: { - userId: $.type(ID).note({ + userId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' } - }) + } } }; diff --git a/src/server/api/endpoints/blocking/delete.ts b/src/server/api/endpoints/blocking/delete.ts index e712d54d3..adf8d8c50 100644 --- a/src/server/api/endpoints/blocking/delete.ts +++ b/src/server/api/endpoints/blocking/delete.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; const ms = require('ms'); import User, { pack, ILocalUser } from '../../../../models/user'; import Blocking from '../../../../models/blocking'; @@ -23,12 +23,14 @@ export const meta = { kind: 'following-write', params: { - userId: $.type(ID).note({ + userId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' } - }) + } } }; diff --git a/src/server/api/endpoints/blocking/list.ts b/src/server/api/endpoints/blocking/list.ts index a0bef38b5..52f55805d 100644 --- a/src/server/api/endpoints/blocking/list.ts +++ b/src/server/api/endpoints/blocking/list.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Blocking, { packMany } from '../../../../models/blocking'; import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; @@ -14,15 +14,20 @@ export const meta = { kind: 'following-read', params: { - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 30 - }), + }, - sinceId: $.type(ID).optional.note({ - }), + sinceId: { + validator: $.type(ID).optional, + transform: transform, + }, - untilId: $.type(ID).optional.note({ - }), + untilId: { + validator: $.type(ID).optional, + transform: transform, + }, } }; diff --git a/src/server/api/endpoints/charts/drive.ts b/src/server/api/endpoints/charts/drive.ts index dc1833184..220008133 100644 --- a/src/server/api/endpoints/charts/drive.ts +++ b/src/server/api/endpoints/charts/drive.ts @@ -8,18 +8,20 @@ export const meta = { }, params: { - span: $.str.or(['day', 'hour']).note({ + span: { + validator: $.str.or(['day', 'hour']), desc: { 'ja-JP': '集計のスパン (day または hour)' } - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 30, desc: { 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' } - }), + }, } }; diff --git a/src/server/api/endpoints/charts/federation.ts b/src/server/api/endpoints/charts/federation.ts index 5b24783c6..ba19aae04 100644 --- a/src/server/api/endpoints/charts/federation.ts +++ b/src/server/api/endpoints/charts/federation.ts @@ -8,18 +8,20 @@ export const meta = { }, params: { - span: $.str.or(['day', 'hour']).note({ + span: { + validator: $.str.or(['day', 'hour']), desc: { 'ja-JP': '集計のスパン (day または hour)' } - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 30, desc: { 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' } - }), + }, } }; diff --git a/src/server/api/endpoints/charts/hashtag.ts b/src/server/api/endpoints/charts/hashtag.ts index bcd48dc48..2ef6a0606 100644 --- a/src/server/api/endpoints/charts/hashtag.ts +++ b/src/server/api/endpoints/charts/hashtag.ts @@ -8,24 +8,27 @@ export const meta = { }, params: { - span: $.str.or(['day', 'hour']).note({ + span: { + validator: $.str.or(['day', 'hour']), desc: { 'ja-JP': '集計のスパン (day または hour)' } - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 30, desc: { 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' } - }), + }, - tag: $.str.note({ + tag: { + validator: $.str, desc: { 'ja-JP': '対象のハッシュタグ' } - }), + }, } }; diff --git a/src/server/api/endpoints/charts/network.ts b/src/server/api/endpoints/charts/network.ts index d5b079199..249504f2e 100644 --- a/src/server/api/endpoints/charts/network.ts +++ b/src/server/api/endpoints/charts/network.ts @@ -8,18 +8,20 @@ export const meta = { }, params: { - span: $.str.or(['day', 'hour']).note({ + span: { + validator: $.str.or(['day', 'hour']), desc: { 'ja-JP': '集計のスパン (day または hour)' } - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 30, desc: { 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' } - }), + }, } }; diff --git a/src/server/api/endpoints/charts/notes.ts b/src/server/api/endpoints/charts/notes.ts index 573b01246..0f04a4bd9 100644 --- a/src/server/api/endpoints/charts/notes.ts +++ b/src/server/api/endpoints/charts/notes.ts @@ -8,18 +8,20 @@ export const meta = { }, params: { - span: $.str.or(['day', 'hour']).note({ + span: { + validator: $.str.or(['day', 'hour']), desc: { 'ja-JP': '集計のスパン (day または hour)' } - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 30, desc: { 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' } - }), + }, } }; diff --git a/src/server/api/endpoints/charts/user/drive.ts b/src/server/api/endpoints/charts/user/drive.ts index 2626c36c9..beb6b5bea 100644 --- a/src/server/api/endpoints/charts/user/drive.ts +++ b/src/server/api/endpoints/charts/user/drive.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import getParams from '../../../get-params'; import perUserDriveChart from '../../../../../chart/per-user-drive'; -import ID from '../../../../../misc/cafy-id'; +import ID, { transform } from '../../../../../misc/cafy-id'; export const meta = { desc: { @@ -9,25 +9,29 @@ export const meta = { }, params: { - span: $.str.or(['day', 'hour']).note({ + span: { + validator: $.str.or(['day', 'hour']), desc: { 'ja-JP': '集計のスパン (day または hour)' } - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 30, desc: { 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' } - }), + }, - userId: $.type(ID).note({ + userId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' } - }) + } } }; diff --git a/src/server/api/endpoints/charts/user/following.ts b/src/server/api/endpoints/charts/user/following.ts index 57c15cdcf..e0aebf425 100644 --- a/src/server/api/endpoints/charts/user/following.ts +++ b/src/server/api/endpoints/charts/user/following.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import getParams from '../../../get-params'; import perUserFollowingChart from '../../../../../chart/per-user-following'; -import ID from '../../../../../misc/cafy-id'; +import ID, { transform } from '../../../../../misc/cafy-id'; export const meta = { desc: { @@ -9,25 +9,29 @@ export const meta = { }, params: { - span: $.str.or(['day', 'hour']).note({ + span: { + validator: $.str.or(['day', 'hour']), desc: { 'ja-JP': '集計のスパン (day または hour)' } - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 30, desc: { 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' } - }), + }, - userId: $.type(ID).note({ + userId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' } - }) + } } }; diff --git a/src/server/api/endpoints/charts/user/notes.ts b/src/server/api/endpoints/charts/user/notes.ts index 66051fa7c..251de4d39 100644 --- a/src/server/api/endpoints/charts/user/notes.ts +++ b/src/server/api/endpoints/charts/user/notes.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import getParams from '../../../get-params'; import perUserNotesChart from '../../../../../chart/per-user-notes'; -import ID from '../../../../../misc/cafy-id'; +import ID, { transform } from '../../../../../misc/cafy-id'; export const meta = { desc: { @@ -9,25 +9,29 @@ export const meta = { }, params: { - span: $.str.or(['day', 'hour']).note({ + span: { + validator: $.str.or(['day', 'hour']), desc: { 'ja-JP': '集計のスパン (day または hour)' } - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 30, desc: { 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' } - }), + }, - userId: $.type(ID).note({ + userId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' } - }) + } } }; diff --git a/src/server/api/endpoints/charts/user/reactions.ts b/src/server/api/endpoints/charts/user/reactions.ts index 60cdaa70b..f81240070 100644 --- a/src/server/api/endpoints/charts/user/reactions.ts +++ b/src/server/api/endpoints/charts/user/reactions.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import getParams from '../../../get-params'; import perUserReactionsChart from '../../../../../chart/per-user-reactions'; -import ID from '../../../../../misc/cafy-id'; +import ID, { transform } from '../../../../../misc/cafy-id'; export const meta = { desc: { @@ -9,25 +9,29 @@ export const meta = { }, params: { - span: $.str.or(['day', 'hour']).note({ + span: { + validator: $.str.or(['day', 'hour']), desc: { 'ja-JP': '集計のスパン (day または hour)' } - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 30, desc: { 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' } - }), + }, - userId: $.type(ID).note({ + userId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' } - }) + } } }; diff --git a/src/server/api/endpoints/charts/users.ts b/src/server/api/endpoints/charts/users.ts index 595bb63f0..9c23235b2 100644 --- a/src/server/api/endpoints/charts/users.ts +++ b/src/server/api/endpoints/charts/users.ts @@ -8,18 +8,20 @@ export const meta = { }, params: { - span: $.str.or(['day', 'hour']).note({ + span: { + validator: $.str.or(['day', 'hour']), desc: { 'ja-JP': '集計のスパン (day または hour)' } - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 30, desc: { 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' } - }), + }, } }; diff --git a/src/server/api/endpoints/drive/files.ts b/src/server/api/endpoints/drive/files.ts index de0bde086..ae4e2249f 100644 --- a/src/server/api/endpoints/drive/files.ts +++ b/src/server/api/endpoints/drive/files.ts @@ -1,6 +1,7 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import DriveFile, { packMany } from '../../../../models/drive-file'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; export const meta = { desc: { @@ -10,68 +11,75 @@ export const meta = { requireCredential: true, - kind: 'drive-read' + kind: 'drive-read', + + params: { + limit: { + validator: $.num.optional.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.type(ID).optional, + transform: transform, + }, + + untilId: { + validator: $.type(ID).optional, + transform: transform, + }, + + folderId: { + validator: $.type(ID).optional.nullable, + default: null as any, + transform: transform, + }, + + type: { + validator: $.str.optional.match(/^[a-zA-Z\/\-\*]+$/) + } + } }; export default 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'; - - // Get 'sinceId' parameter - 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); - if (untilIdErr) throw 'invalid untilId param'; + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; // Check if both of sinceId and untilId is specified - if (sinceId && untilId) { + if (ps.sinceId && ps.untilId) { throw 'cannot set sinceId and untilId'; } - // Get 'folderId' parameter - 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); - if (typeErr) throw 'invalid type param'; - - // Construct query const sort = { _id: -1 }; const query = { 'metadata.userId': user._id, - 'metadata.folderId': folderId, + 'metadata.folderId': ps.folderId, 'metadata.deletedAt': { $exists: false } } as any; - 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 }; } - if (type) { - query.contentType = new RegExp(`^${type.replace(/\*/g, '.+?')}$`); + if (ps.type) { + query.contentType = new RegExp(`^${ps.type.replace(/\*/g, '.+?')}$`); } - // Issue query const files = await DriveFile .find(query, { - limit: limit, + limit: ps.limit, sort: sort }); - // Serialize return await packMany(files); }; diff --git a/src/server/api/endpoints/drive/files/attached_notes.ts b/src/server/api/endpoints/drive/files/attached_notes.ts index 1187169c6..ad9a2370b 100644 --- a/src/server/api/endpoints/drive/files/attached_notes.ts +++ b/src/server/api/endpoints/drive/files/attached_notes.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import DriveFile from '../../../../../models/drive-file'; import { ILocalUser } from '../../../../../models/user'; import getParams from '../../../get-params'; @@ -17,12 +17,14 @@ export const meta = { kind: 'drive-read', params: { - fileId: $.type(ID).note({ + fileId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のファイルID', 'en-US': 'Target file ID' } - }) + } } }; diff --git a/src/server/api/endpoints/drive/files/check_existence.ts b/src/server/api/endpoints/drive/files/check_existence.ts index a02470165..407c7d553 100644 --- a/src/server/api/endpoints/drive/files/check_existence.ts +++ b/src/server/api/endpoints/drive/files/check_existence.ts @@ -13,11 +13,12 @@ export const meta = { kind: 'drive-read', params: { - md5: $.str.note({ + md5: { + validator: $.str, desc: { 'ja-JP': 'ファイルのMD5ハッシュ' } - }) + } } }; diff --git a/src/server/api/endpoints/drive/files/create.ts b/src/server/api/endpoints/drive/files/create.ts index 2fa4c6584..2653eba65 100644 --- a/src/server/api/endpoints/drive/files/create.ts +++ b/src/server/api/endpoints/drive/files/create.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; const ms = require('ms'); -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import { validateFileName, pack } from '../../../../../models/drive-file'; import create from '../../../../../services/drive/add-file'; import { ILocalUser } from '../../../../../models/user'; @@ -24,27 +24,31 @@ export const meta = { kind: 'drive-write', params: { - folderId: $.type(ID).optional.nullable.note({ - default: null, + folderId: { + validator: $.type(ID).optional.nullable, + transform: transform, + default: null as any, desc: { 'ja-JP': 'フォルダID' } - }), + }, - isSensitive: $.bool.optional.note({ + isSensitive: { + validator: $.bool.optional, default: false, desc: { 'ja-JP': 'このメディアが「閲覧注意」(NSFW)かどうか', 'en-US': 'Whether this media is NSFW' } - }), + }, - force: $.bool.optional.note({ + force: { + validator: $.bool.optional, default: false, desc: { 'ja-JP': 'true にすると、同じハッシュを持つファイルが既にアップロードされていても強制的にファイルを作成します。', } - }) + } } }; diff --git a/src/server/api/endpoints/drive/files/delete.ts b/src/server/api/endpoints/drive/files/delete.ts index fc6849e57..af7e19265 100644 --- a/src/server/api/endpoints/drive/files/delete.ts +++ b/src/server/api/endpoints/drive/files/delete.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import DriveFile from '../../../../../models/drive-file'; import del from '../../../../../services/drive/delete-file'; import { publishDriveStream } from '../../../../../stream'; @@ -18,12 +18,14 @@ export const meta = { kind: 'drive-write', params: { - fileId: $.type(ID).note({ + fileId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のファイルID', 'en-US': 'Target file ID' } - }) + } } }; diff --git a/src/server/api/endpoints/drive/files/find.ts b/src/server/api/endpoints/drive/files/find.ts index aa44ee688..0ac110c11 100644 --- a/src/server/api/endpoints/drive/files/find.ts +++ b/src/server/api/endpoints/drive/files/find.ts @@ -1,31 +1,39 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import DriveFile, { pack } from '../../../../../models/drive-file'; import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; export const meta = { requireCredential: true, - kind: 'drive-read' + kind: 'drive-read', + + params: { + name: { + validator: $.str + }, + + folderId: { + validator: $.type(ID).optional.nullable, + transform: transform, + default: null as any, + desc: { + 'ja-JP': 'フォルダID' + } + }, + } }; 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'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); - // Get 'folderId' parameter - const [folderId = null, folderIdErr] = $.type(ID).optional.nullable.get(params.folderId); - if (folderIdErr) return rej('invalid folderId param'); - - // Issue query const files = await DriveFile .find({ filename: name, 'metadata.userId': user._id, - 'metadata.folderId': folderId + 'metadata.folderId': ps.folderId }); - // Serialize - res(await Promise.all(files.map(async file => - await pack(file)))); + res(await Promise.all(files.map(file => pack(file)))); }); diff --git a/src/server/api/endpoints/drive/files/show.ts b/src/server/api/endpoints/drive/files/show.ts index 49d6027ad..ce0812c50 100644 --- a/src/server/api/endpoints/drive/files/show.ts +++ b/src/server/api/endpoints/drive/files/show.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import DriveFile, { pack } from '../../../../../models/drive-file'; import { ILocalUser } from '../../../../../models/user'; import getParams from '../../../get-params'; @@ -16,12 +16,14 @@ export const meta = { kind: 'drive-read', params: { - fileId: $.type(ID).note({ + fileId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のファイルID', 'en-US': 'Target file ID' } - }) + } } }; diff --git a/src/server/api/endpoints/drive/files/update.ts b/src/server/api/endpoints/drive/files/update.ts index 915cf4ceb..7c335e5bc 100644 --- a/src/server/api/endpoints/drive/files/update.ts +++ b/src/server/api/endpoints/drive/files/update.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import DriveFolder from '../../../../../models/drive-folder'; import DriveFile, { validateFileName, pack } from '../../../../../models/drive-file'; import { publishDriveStream } from '../../../../../stream'; @@ -17,34 +17,40 @@ export const meta = { kind: 'drive-write', params: { - fileId: $.type(ID).note({ + fileId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のファイルID' } - }), + }, - folderId: $.type(ID).optional.nullable.note({ - default: undefined, + folderId: { + validator: $.type(ID).optional.nullable, + transform: transform, + default: undefined as any, desc: { 'ja-JP': 'フォルダID' } - }), + }, - name: $.str.optional.pipe(validateFileName).note({ - default: undefined, + name: { + validator: $.str.optional.pipe(validateFileName), + default: undefined as any, desc: { 'ja-JP': 'ファイル名', 'en-US': 'Name of the file' } - }), + }, - isSensitive: $.bool.optional.note({ - default: undefined, + isSensitive: { + validator: $.bool.optional, + default: undefined as any, desc: { 'ja-JP': 'このメディアが「閲覧注意」(NSFW)かどうか', 'en-US': 'Whether this media is NSFW' } - }) + } } }; 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 783646feb..3d1972527 100644 --- a/src/server/api/endpoints/drive/files/upload_from_url.ts +++ b/src/server/api/endpoints/drive/files/upload_from_url.ts @@ -1,8 +1,9 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } 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'; +import getParams from '../../../get-params'; export const meta = { desc: { @@ -16,21 +17,25 @@ export const meta = { requireCredential: true, - kind: 'drive-write' + kind: 'drive-write', + + params: { + url: { + // TODO: Validate this url + validator: $.str, + }, + + folderId: { + validator: $.type(ID).optional.nullable, + default: null as any as any, + transform: transform + }, + } }; -/** - * Create a file from a URL - */ export default async (params: any, user: ILocalUser): Promise => { - // Get 'url' parameter - // TODO: Validate this url - const [url, urlErr] = $.str.get(params.url); - if (urlErr) throw 'invalid url param'; + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; - // Get 'folderId' parameter - const [folderId = null, folderIdErr] = $.type(ID).optional.nullable.get(params.folderId); - if (folderIdErr) throw 'invalid folderId param'; - - return pack(await uploadFromUrl(url, user, folderId)); + return pack(await uploadFromUrl(ps.url, user, ps.folderId)); }; diff --git a/src/server/api/endpoints/drive/folders.ts b/src/server/api/endpoints/drive/folders.ts index 19c2ef7ac..95700ee26 100644 --- a/src/server/api/endpoints/drive/folders.ts +++ b/src/server/api/endpoints/drive/folders.ts @@ -1,6 +1,7 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import DriveFolder, { pack } from '../../../../models/drive-folder'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; export const meta = { desc: { @@ -10,58 +11,64 @@ export const meta = { requireCredential: true, - kind: 'drive-read' + kind: 'drive-read', + + params: { + limit: { + validator: $.num.optional.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.type(ID).optional, + transform: transform, + }, + + untilId: { + validator: $.type(ID).optional, + transform: transform, + }, + + folderId: { + validator: $.type(ID).optional.nullable, + default: null as any, + transform: transform, + } + } }; 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); - if (limitErr) return rej('invalid limit param'); - - // Get 'sinceId' parameter - 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); - if (untilIdErr) return rej('invalid untilId param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Check if both of sinceId and untilId is specified - if (sinceId && untilId) { + if (ps.sinceId && ps.untilId) { return rej('cannot set sinceId and untilId'); } - // Get 'folderId' parameter - const [folderId = null, folderIdErr] = $.type(ID).optional.nullable.get(params.folderId); - if (folderIdErr) return rej('invalid folderId param'); - - // Construct query const sort = { _id: -1 }; const query = { userId: user._id, - parentId: folderId + parentId: ps.folderId } as any; - 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 }; } - // Issue query const folders = await DriveFolder .find(query, { - limit: limit, + limit: ps.limit, sort: sort }); - // Serialize - res(await Promise.all(folders.map(async folder => - await pack(folder)))); + res(await Promise.all(folders.map(folder => pack(folder)))); }); diff --git a/src/server/api/endpoints/drive/folders/create.ts b/src/server/api/endpoints/drive/folders/create.ts index cca25b059..b51fb0264 100644 --- a/src/server/api/endpoints/drive/folders/create.ts +++ b/src/server/api/endpoints/drive/folders/create.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import DriveFolder, { isValidFolderName, pack } from '../../../../../models/drive-folder'; import { publishDriveStream } from '../../../../../stream'; import { ILocalUser } from '../../../../../models/user'; @@ -17,20 +17,23 @@ export const meta = { kind: 'drive-write', params: { - name: $.str.optional.pipe(isValidFolderName).note({ + name: { + validator: $.str.optional.pipe(isValidFolderName), default: 'Untitled', desc: { 'ja-JP': 'フォルダ名', 'en-US': 'Folder name' } - }), + }, - parentId: $.type(ID).optional.nullable.note({ + parentId: { + validator: $.type(ID).optional.nullable, + transform: transform, desc: { 'ja-JP': '親フォルダID', 'en-US': 'Parent folder ID' } - }) + } } }; diff --git a/src/server/api/endpoints/drive/folders/delete.ts b/src/server/api/endpoints/drive/folders/delete.ts index 41f910878..304666bdc 100644 --- a/src/server/api/endpoints/drive/folders/delete.ts +++ b/src/server/api/endpoints/drive/folders/delete.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import DriveFolder from '../../../../../models/drive-folder'; import { ILocalUser } from '../../../../../models/user'; import getParams from '../../../get-params'; @@ -18,12 +18,14 @@ export const meta = { kind: 'drive-write', params: { - folderId: $.type(ID).note({ + folderId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のフォルダID', 'en-US': 'Target folder ID' } - }) + } } }; diff --git a/src/server/api/endpoints/drive/folders/find.ts b/src/server/api/endpoints/drive/folders/find.ts index ec3c1d2e3..3d24d1868 100644 --- a/src/server/api/endpoints/drive/folders/find.ts +++ b/src/server/api/endpoints/drive/folders/find.ts @@ -1,30 +1,39 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import DriveFolder, { pack } from '../../../../../models/drive-folder'; import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; export const meta = { requireCredential: true, - kind: 'drive-read' + kind: 'drive-read', + + params: { + name: { + validator: $.str + }, + + parentId: { + validator: $.type(ID).optional.nullable, + transform: transform, + default: null as any, + desc: { + 'ja-JP': 'フォルダID' + } + }, + } }; 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'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); - // Get 'parentId' parameter - const [parentId = null, parentIdErr] = $.type(ID).optional.nullable.get(params.parentId); - if (parentIdErr) return rej('invalid parentId param'); - - // Issue query const folders = await DriveFolder .find({ name: name, userId: user._id, - parentId: parentId + parentId: ps.parentId }); - // Serialize res(await Promise.all(folders.map(folder => pack(folder)))); }); diff --git a/src/server/api/endpoints/drive/folders/show.ts b/src/server/api/endpoints/drive/folders/show.ts index f01c75d95..b7d8f0a29 100644 --- a/src/server/api/endpoints/drive/folders/show.ts +++ b/src/server/api/endpoints/drive/folders/show.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import DriveFolder, { pack } from '../../../../../models/drive-folder'; import { ILocalUser } from '../../../../../models/user'; import getParams from '../../../get-params'; @@ -16,12 +16,14 @@ export const meta = { kind: 'drive-read', params: { - folderId: $.type(ID).note({ + folderId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のフォルダID', 'en-US': 'Target folder ID' } - }) + } } }; diff --git a/src/server/api/endpoints/drive/folders/update.ts b/src/server/api/endpoints/drive/folders/update.ts index b041a1592..53bb14bfe 100644 --- a/src/server/api/endpoints/drive/folders/update.ts +++ b/src/server/api/endpoints/drive/folders/update.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import DriveFolder, { isValidFolderName, pack } from '../../../../../models/drive-folder'; import { publishDriveStream } from '../../../../../stream'; import { ILocalUser } from '../../../../../models/user'; @@ -17,26 +17,31 @@ export const meta = { kind: 'drive-write', params: { - folderId: $.type(ID).note({ + folderId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のフォルダID', 'en-US': 'Target folder ID' } - }), + }, - name: $.str.optional.pipe(isValidFolderName).note({ + name: { + validator: $.str.optional.pipe(isValidFolderName), desc: { 'ja-JP': 'フォルダ名', 'en-US': 'Folder name' } - }), + }, - parentId: $.type(ID).optional.nullable.note({ + parentId: { + validator: $.type(ID).optional.nullable, + transform: transform, desc: { 'ja-JP': '親フォルダID', 'en-US': 'Parent folder ID' } - }) + } } }; diff --git a/src/server/api/endpoints/drive/stream.ts b/src/server/api/endpoints/drive/stream.ts index 3ac7dd023..ecf405fe6 100644 --- a/src/server/api/endpoints/drive/stream.ts +++ b/src/server/api/endpoints/drive/stream.ts @@ -1,36 +1,44 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import DriveFile, { packMany } from '../../../../models/drive-file'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; export const meta = { requireCredential: true, - kind: 'drive-read' + kind: 'drive-read', + + params: { + limit: { + validator: $.num.optional.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.type(ID).optional, + transform: transform, + }, + + untilId: { + validator: $.type(ID).optional, + transform: transform, + }, + + type: { + validator: $.str.optional.match(/^[a-zA-Z\/\-\*]+$/) + } + } }; 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); - if (limitErr) return rej('invalid limit param'); - - // Get 'sinceId' parameter - 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); - if (untilIdErr) return rej('invalid untilId param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Check if both of sinceId and untilId is specified - if (sinceId && untilId) { + if (ps.sinceId && ps.untilId) { return rej('cannot set sinceId and untilId'); } - // Get 'type' parameter - const [type, typeErr] = $.str.optional.match(/^[a-zA-Z\/\-\*]+$/).get(params.type); - if (typeErr) return rej('invalid type param'); - - // Construct query const sort = { _id: -1 }; @@ -40,28 +48,26 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = 'metadata.deletedAt': { $exists: false } } as any; - 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 }; } - if (type) { - query.contentType = new RegExp(`^${type.replace(/\*/g, '.+?')}$`); + if (ps.type) { + query.contentType = new RegExp(`^${ps.type.replace(/\*/g, '.+?')}$`); } - // Issue query const files = await DriveFile .find(query, { - limit: limit, + limit: ps.limit, sort: sort }); - // Serialize res(await packMany(files)); }); diff --git a/src/server/api/endpoints/following/create.ts b/src/server/api/endpoints/following/create.ts index 028a2aa82..7795bbcc6 100644 --- a/src/server/api/endpoints/following/create.ts +++ b/src/server/api/endpoints/following/create.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; const ms = require('ms'); import User, { pack, ILocalUser } from '../../../../models/user'; import Following from '../../../../models/following'; @@ -23,12 +23,14 @@ export const meta = { kind: 'following-write', params: { - userId: $.type(ID).note({ + userId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' } - }) + } } }; diff --git a/src/server/api/endpoints/following/delete.ts b/src/server/api/endpoints/following/delete.ts index 0489c1e04..ae0edd34e 100644 --- a/src/server/api/endpoints/following/delete.ts +++ b/src/server/api/endpoints/following/delete.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; const ms = require('ms'); import User, { pack, ILocalUser } from '../../../../models/user'; import Following from '../../../../models/following'; @@ -23,12 +23,14 @@ export const meta = { kind: 'following-write', params: { - userId: $.type(ID).note({ + userId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' } - }) + } } }; diff --git a/src/server/api/endpoints/following/requests/accept.ts b/src/server/api/endpoints/following/requests/accept.ts index f6a7dcf12..1172f463d 100644 --- a/src/server/api/endpoints/following/requests/accept.ts +++ b/src/server/api/endpoints/following/requests/accept.ts @@ -1,6 +1,7 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import acceptFollowRequest from '../../../../../services/following/requests/accept'; import User, { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; export const meta = { desc: { @@ -10,17 +11,23 @@ export const meta = { requireCredential: true, - kind: 'following-write' + kind: 'following-write', + + params: { + userId: { + validator: $.type(ID), + transform: transform, + } + } }; 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'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Fetch follower const follower = await User.findOne({ - _id: followerId + _id: ps.userId }); if (follower === null) { @@ -29,6 +36,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = await acceptFollowRequest(user, follower); - // Send response res(); }); diff --git a/src/server/api/endpoints/following/requests/cancel.ts b/src/server/api/endpoints/following/requests/cancel.ts index 3da4f4734..77bfcfe15 100644 --- a/src/server/api/endpoints/following/requests/cancel.ts +++ b/src/server/api/endpoints/following/requests/cancel.ts @@ -1,6 +1,7 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import cancelFollowRequest from '../../../../../services/following/requests/cancel'; import User, { pack, ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; export const meta = { desc: { @@ -10,17 +11,23 @@ export const meta = { requireCredential: true, - kind: 'following-write' + kind: 'following-write', + + params: { + userId: { + validator: $.type(ID), + transform: transform, + } + } }; 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'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Fetch followee const followee = await User.findOne({ - _id: followeeId + _id: ps.userId }); if (followee === null) { @@ -33,6 +40,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = return rej(e); } - // Send response res(await pack(followee._id, user)); }); diff --git a/src/server/api/endpoints/following/requests/list.ts b/src/server/api/endpoints/following/requests/list.ts index 11a387cf1..6d42d06db 100644 --- a/src/server/api/endpoints/following/requests/list.ts +++ b/src/server/api/endpoints/following/requests/list.ts @@ -1,4 +1,4 @@ -//import $ from 'cafy'; import ID from '../../../../../cafy-id'; +//import $ from 'cafy'; import ID, { transform } from '../../../../../cafy-id'; import FollowRequest, { pack } from '../../../../../models/follow-request'; import { ILocalUser } from '../../../../../models/user'; diff --git a/src/server/api/endpoints/following/requests/reject.ts b/src/server/api/endpoints/following/requests/reject.ts index 98febe9e9..45ebb7807 100644 --- a/src/server/api/endpoints/following/requests/reject.ts +++ b/src/server/api/endpoints/following/requests/reject.ts @@ -1,6 +1,7 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import rejectFollowRequest from '../../../../../services/following/requests/reject'; import User, { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; export const meta = { desc: { @@ -10,17 +11,23 @@ export const meta = { requireCredential: true, - kind: 'following-write' + kind: 'following-write', + + params: { + userId: { + validator: $.type(ID), + transform: transform, + } + } }; 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'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Fetch follower const follower = await User.findOne({ - _id: followerId + _id: ps.userId }); if (follower === null) { @@ -29,6 +36,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = await rejectFollowRequest(user, follower); - // Send response res(); }); diff --git a/src/server/api/endpoints/following/stalk.ts b/src/server/api/endpoints/following/stalk.ts index d44cea2cc..434bc52b5 100644 --- a/src/server/api/endpoints/following/stalk.ts +++ b/src/server/api/endpoints/following/stalk.ts @@ -1,6 +1,7 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Following from '../../../../models/following'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; export const meta = { desc: { @@ -10,20 +11,26 @@ export const meta = { requireCredential: true, - kind: 'following-write' + kind: 'following-write', + + params: { + userId: { + validator: $.type(ID), + transform: transform, + } + } }; export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - const follower = user; + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); - // Get 'userId' parameter - const [userId, userIdErr] = $.type(ID).get(params.userId); - if (userIdErr) return rej('invalid userId param'); + const follower = user; // Fetch following const following = await Following.findOne({ followerId: follower._id, - followeeId: userId + followeeId: ps.userId }); if (following === null) { @@ -37,7 +44,6 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = } }); - // Send response res(); // TODO: イベント diff --git a/src/server/api/endpoints/following/unstalk.ts b/src/server/api/endpoints/following/unstalk.ts index 8b66f0727..00e91357e 100644 --- a/src/server/api/endpoints/following/unstalk.ts +++ b/src/server/api/endpoints/following/unstalk.ts @@ -1,6 +1,7 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Following from '../../../../models/following'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; export const meta = { desc: { @@ -10,20 +11,26 @@ export const meta = { requireCredential: true, - kind: 'following-write' + kind: 'following-write', + + params: { + userId: { + validator: $.type(ID), + transform: transform, + } + } }; export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - const follower = user; + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); - // Get 'userId' parameter - const [userId, userIdErr] = $.type(ID).get(params.userId); - if (userIdErr) return rej('invalid userId param'); + const follower = user; // Fetch following const following = await Following.findOne({ followerId: follower._id, - followeeId: userId + followeeId: ps.userId }); if (following === null) { @@ -37,7 +44,6 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = } }); - // Send response res(); // TODO: イベント diff --git a/src/server/api/endpoints/games/reversi/games.ts b/src/server/api/endpoints/games/reversi/games.ts index 2838940b5..9c7a7c4d7 100644 --- a/src/server/api/endpoints/games/reversi/games.ts +++ b/src/server/api/endpoints/games/reversi/games.ts @@ -1,33 +1,42 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import ReversiGame, { pack } from '../../../../../models/games/reversi/game'; import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; export const meta = { + params: { + limit: { + validator: $.num.optional.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.type(ID).optional, + transform: transform, + }, + + untilId: { + validator: $.type(ID).optional, + transform: transform, + }, + + my: { + validator: $.bool.optional, + default: false + } + } }; export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - // Get 'my' parameter - 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); - if (limitErr) return rej('invalid limit param'); - - // Get 'sinceId' parameter - 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); - if (untilIdErr) return rej('invalid untilId param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Check if both of sinceId and untilId is specified - if (sinceId && untilId) { + if (ps.sinceId && ps.untilId) { return rej('cannot set sinceId and untilId'); } - const q: any = my ? { + const q: any = ps.my ? { isStarted: true, $or: [{ user1Id: user._id @@ -42,21 +51,21 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = _id: -1 }; - if (sinceId) { + if (ps.sinceId) { sort._id = 1; q._id = { - $gt: sinceId + $gt: ps.sinceId }; - } else if (untilId) { + } else if (ps.untilId) { q._id = { - $lt: untilId + $lt: ps.untilId }; } // Fetch games const games = await ReversiGame.find(q, { - sort, - limit + sort: sort, + limit: ps.limit }); // Reponse diff --git a/src/server/api/endpoints/games/reversi/games/show.ts b/src/server/api/endpoints/games/reversi/games/show.ts index 8d7cd987a..a2c84ae82 100644 --- a/src/server/api/endpoints/games/reversi/games/show.ts +++ b/src/server/api/endpoints/games/reversi/games/show.ts @@ -1,14 +1,23 @@ -import $ from 'cafy'; import ID from '../../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../../misc/cafy-id'; import ReversiGame, { pack } from '../../../../../../models/games/reversi/game'; import Reversi from '../../../../../../games/reversi/core'; import { ILocalUser } from '../../../../../../models/user'; +import getParams from '../../../../get-params'; + +export const meta = { + params: { + gameId: { + validator: $.type(ID), + transform: transform, + }, + } +}; 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'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); - const game = await ReversiGame.findOne({ _id: gameId }); + const game = await ReversiGame.findOne({ _id: ps.gameId }); if (game == null) { return rej('game not found'); diff --git a/src/server/api/endpoints/games/reversi/games/surrender.ts b/src/server/api/endpoints/games/reversi/games/surrender.ts index 8ca014367..2860c154f 100644 --- a/src/server/api/endpoints/games/reversi/games/surrender.ts +++ b/src/server/api/endpoints/games/reversi/games/surrender.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../../misc/cafy-id'; import ReversiGame, { pack } from '../../../../../../models/games/reversi/game'; import { ILocalUser } from '../../../../../../models/user'; import getParams from '../../../../get-params'; @@ -12,11 +12,13 @@ export const meta = { requireCredential: true, params: { - gameId: $.type(ID).note({ + gameId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '投了したい対局' } - }) + } } }; diff --git a/src/server/api/endpoints/games/reversi/match.ts b/src/server/api/endpoints/games/reversi/match.ts index d7483a0bf..f43650aee 100644 --- a/src/server/api/endpoints/games/reversi/match.ts +++ b/src/server/api/endpoints/games/reversi/match.ts @@ -1,27 +1,34 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } 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 { publishMainStream, publishReversiStream } from '../../../../../stream'; import { eighteight } from '../../../../../games/reversi/maps'; +import getParams from '../../../get-params'; export const meta = { - requireCredential: true + requireCredential: true, + + params: { + userId: { + validator: $.type(ID), + transform: transform, + }, + } }; 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'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Myself - if (childId.equals(user._id)) { + if (ps.userId.equals(user._id)) { return rej('invalid userId param'); } // Find session const exist = await Matching.findOne({ - parentId: childId, + parentId: ps.userId, childId: user._id }); @@ -63,7 +70,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = } else { // Fetch child const child = await User.findOne({ - _id: childId + _id: ps.userId }, { fields: { _id: true diff --git a/src/server/api/endpoints/hashtags/search.ts b/src/server/api/endpoints/hashtags/search.ts index 69cdc62ab..35ae2a83b 100644 --- a/src/server/api/endpoints/hashtags/search.ts +++ b/src/server/api/endpoints/hashtags/search.ts @@ -11,25 +11,28 @@ export const meta = { requireCredential: false, params: { - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 10, desc: { 'ja-JP': '最大数' } - }), + }, - query: $.str.note({ + query: { + validator: $.str, desc: { 'ja-JP': 'クエリ' } - }), + }, - offset: $.num.optional.min(0).note({ + offset: { + validator: $.num.optional.min(0), default: 0, desc: { 'ja-JP': 'オフセット' } - }) + } } }; diff --git a/src/server/api/endpoints/i/favorites.ts b/src/server/api/endpoints/i/favorites.ts index e7cf8a71a..847ac64d9 100644 --- a/src/server/api/endpoints/i/favorites.ts +++ b/src/server/api/endpoints/i/favorites.ts @@ -1,6 +1,7 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Favorite, { packMany } from '../../../../models/favorite'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; export const meta = { desc: { @@ -10,24 +11,32 @@ export const meta = { requireCredential: true, - kind: 'favorites-read' + kind: 'favorites-read', + + params: { + limit: { + validator: $.num.optional.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.type(ID).optional, + transform: transform, + }, + + untilId: { + validator: $.type(ID).optional, + transform: transform, + } + } }; 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); - if (limitErr) return rej('invalid limit param'); - - // Get 'sinceId' parameter - 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); - if (untilIdErr) return rej('invalid untilId param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Check if both of sinceId and untilId is specified - if (sinceId && untilId) { + if (ps.sinceId && ps.untilId) { return rej('cannot set sinceId and untilId'); } @@ -39,21 +48,23 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = _id: -1 }; - 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 }; } // Get favorites const favorites = await Favorite - .find(query, { limit, sort }); + .find(query, { + limit: ps.limit, + sort: sort + }); - // Serialize res(await packMany(favorites, user)); }); diff --git a/src/server/api/endpoints/i/notifications.ts b/src/server/api/endpoints/i/notifications.ts index 5cc836e36..d16ba63bd 100644 --- a/src/server/api/endpoints/i/notifications.ts +++ b/src/server/api/endpoints/i/notifications.ts @@ -1,38 +1,56 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Notification from '../../../../models/notification'; import Mute from '../../../../models/mute'; import { packMany } from '../../../../models/notification'; import { getFriendIds } from '../../common/get-friends'; import read from '../../common/read-notification'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; + +export const meta = { + desc: { + 'ja-JP': '通知一覧を取得します。', + 'en-US': 'Get notifications.' + }, + + requireCredential: true, + + kind: 'account-read', + + params: { + limit: { + validator: $.num.optional.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.type(ID).optional, + transform: transform, + }, + + untilId: { + validator: $.type(ID).optional, + transform: transform, + }, + + following: { + validator: $.bool.optional, + default: false + }, + + markAsRead: { + validator: $.bool.optional, + default: true + } + } +}; -/** - * Get notifications - */ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - // Get 'following' parameter - const [following = false, followingError] = - $.bool.optional.get(params.following); - if (followingError) return rej('invalid following param'); - - // Get 'markAsRead' parameter - 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); - if (limitErr) return rej('invalid limit param'); - - // Get 'sinceId' parameter - 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); - if (untilIdErr) return rej('invalid untilId param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Check if both of sinceId and untilId is specified - if (sinceId && untilId) { + if (ps.sinceId && ps.untilId) { return rej('cannot set sinceId and untilId'); } @@ -53,7 +71,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = _id: -1 }; - if (following) { + if (ps.following) { // ID list of the user itself and other users who the user follows const followingIds = await getFriendIds(user._id); @@ -64,29 +82,27 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = }); } - 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 }; } - // Issue query const notifications = await Notification .find(query, { - limit: limit, + limit: ps.limit, sort: sort }); - // Serialize res(await packMany(notifications)); // Mark all as read - if (notifications.length > 0 && markAsRead) { + if (notifications.length > 0 && ps.markAsRead) { read(user._id, notifications); } }); diff --git a/src/server/api/endpoints/i/pin.ts b/src/server/api/endpoints/i/pin.ts index 44c7fe77b..4341906a5 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 '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import { ILocalUser } from '../../../../models/user'; import { pack } from '../../../../models/user'; import { addPinned } from '../../../../services/i/pin'; @@ -16,12 +16,14 @@ export const meta = { kind: 'account-write', params: { - noteId: $.type(ID).note({ + noteId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID' } - }) + } } }; diff --git a/src/server/api/endpoints/i/signin_history.ts b/src/server/api/endpoints/i/signin_history.ts index 5a3c122f3..df1cd34c8 100644 --- a/src/server/api/endpoints/i/signin_history.ts +++ b/src/server/api/endpoints/i/signin_history.ts @@ -1,27 +1,37 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Signin, { pack } from '../../../../models/signin'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; export const meta = { requireCredential: true, - secure: true + + secure: true, + + params: { + limit: { + validator: $.num.optional.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.type(ID).optional, + transform: transform, + }, + + untilId: { + validator: $.type(ID).optional, + transform: transform, + } + } }; 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); - if (limitErr) return rej('invalid limit param'); - - // Get 'sinceId' parameter - 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); - if (untilIdErr) return rej('invalid untilId param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Check if both of sinceId and untilId is specified - if (sinceId && untilId) { + if (ps.sinceId && ps.untilId) { return rej('cannot set sinceId and untilId'); } @@ -33,25 +43,23 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = _id: -1 }; - 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 }; } - // Issue query const history = await Signin .find(query, { - limit: limit, + limit: ps.limit, sort: sort }); // Serialize - res(await Promise.all(history.map(async record => - await pack(record)))); + res(await Promise.all(history.map(record => pack(record)))); }); diff --git a/src/server/api/endpoints/i/unpin.ts b/src/server/api/endpoints/i/unpin.ts index 6c20e2771..26ff8ccda 100644 --- a/src/server/api/endpoints/i/unpin.ts +++ b/src/server/api/endpoints/i/unpin.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import { ILocalUser } from '../../../../models/user'; import { pack } from '../../../../models/user'; import { removePinned } from '../../../../services/i/pin'; @@ -16,12 +16,14 @@ export const meta = { kind: 'account-write', params: { - noteId: $.type(ID).note({ + noteId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID' } - }) + } } }; diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 04f132c55..93d444809 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import User, { isValidName, isValidDescription, isValidLocation, isValidBirthday, pack, ILocalUser } from '../../../../models/user'; import { publishMainStream } from '../../../../stream'; import DriveFile from '../../../../models/drive-file'; @@ -19,83 +19,99 @@ export const meta = { kind: 'account-write', params: { - name: $.str.optional.nullable.pipe(isValidName).note({ + name: { + validator: $.str.optional.nullable.pipe(isValidName), desc: { 'ja-JP': '名前(ハンドルネームやニックネーム)' } - }), + }, - description: $.str.optional.nullable.pipe(isValidDescription).note({ + description: { + validator: $.str.optional.nullable.pipe(isValidDescription), desc: { 'ja-JP': 'アカウントの説明や自己紹介' } - }), + }, - location: $.str.optional.nullable.pipe(isValidLocation).note({ + location: { + validator: $.str.optional.nullable.pipe(isValidLocation), desc: { 'ja-JP': '住んでいる地域、所在' } - }), + }, - birthday: $.str.optional.nullable.pipe(isValidBirthday).note({ + birthday: { + validator: $.str.optional.nullable.pipe(isValidBirthday), desc: { 'ja-JP': '誕生日 (YYYY-MM-DD形式)' } - }), + }, - avatarId: $.type(ID).optional.nullable.note({ + avatarId: { + validator: $.type(ID).optional.nullable, + transform: transform, desc: { 'ja-JP': 'アイコンに設定する画像のドライブファイルID' } - }), + }, - bannerId: $.type(ID).optional.nullable.note({ + bannerId: { + validator: $.type(ID).optional.nullable, + transform: transform, desc: { 'ja-JP': 'バナーに設定する画像のドライブファイルID' } - }), + }, - wallpaperId: $.type(ID).optional.nullable.note({ + wallpaperId: { + validator: $.type(ID).optional.nullable, + transform: transform, desc: { 'ja-JP': '壁紙に設定する画像のドライブファイルID' } - }), + }, - isLocked: $.bool.optional.note({ + isLocked: { + validator: $.bool.optional, desc: { 'ja-JP': '鍵アカウントか否か' } - }), + }, - carefulBot: $.bool.optional.note({ + carefulBot: { + validator: $.bool.optional, desc: { 'ja-JP': 'Botからのフォローを承認制にするか' } - }), + }, - isBot: $.bool.optional.note({ + isBot: { + validator: $.bool.optional, desc: { 'ja-JP': 'Botか否か' } - }), + }, - isCat: $.bool.optional.note({ + isCat: { + validator: $.bool.optional, desc: { 'ja-JP': '猫か否か' } - }), + }, - autoWatch: $.bool.optional.note({ + autoWatch: { + validator: $.bool.optional, desc: { 'ja-JP': '投稿の自動ウォッチをするか否か' } - }), + }, - alwaysMarkNsfw: $.bool.optional.note({ + alwaysMarkNsfw: { + validator: $.bool.optional, desc: { 'ja-JP': 'アップロードするメディアをデフォルトで「閲覧注意」として設定するか' } - }), + }, } }; diff --git a/src/server/api/endpoints/messaging/messages.ts b/src/server/api/endpoints/messaging/messages.ts index dec0638ee..43d96e2ee 100644 --- a/src/server/api/endpoints/messaging/messages.ts +++ b/src/server/api/endpoints/messaging/messages.ts @@ -1,8 +1,9 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } 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'; +import getParams from '../../get-params'; export const meta = { desc: { @@ -12,17 +13,48 @@ export const meta = { requireCredential: true, - kind: 'messaging-read' + kind: 'messaging-read', + + params: { + userId: { + validator: $.type(ID), + transform: transform, + }, + + limit: { + validator: $.num.optional.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.type(ID).optional, + transform: transform, + }, + + untilId: { + validator: $.type(ID).optional, + transform: transform, + }, + + markAsRead: { + validator: $.bool.optional, + default: true + } + } }; 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'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + + // Check if both of sinceId and untilId is specified + if (ps.sinceId && ps.untilId) { + return rej('cannot set sinceId and untilId'); + } // Fetch recipient const recipient = await User.findOne({ - _id: recipientId + _id: ps.userId }, { fields: { _id: true @@ -33,27 +65,6 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = return rej('user not found'); } - // Get 'markAsRead' parameter - 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); - if (limitErr) return rej('invalid limit param'); - - // Get 'sinceId' parameter - 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); - if (untilIdErr) return rej('invalid untilId param'); - - // Check if both of sinceId and untilId is specified - if (sinceId && untilId) { - return rej('cannot set sinceId and untilId'); - } - const query = { $or: [{ userId: user._id, @@ -68,36 +79,33 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = _id: -1 }; - 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 }; } - // Issue query const messages = await Message .find(query, { - limit: limit, + limit: ps.limit, sort: sort }); - // Serialize - res(await Promise.all(messages.map(async message => - await pack(message, user, { - populateRecipient: false - })))); + res(await Promise.all(messages.map(message => pack(message, user, { + populateRecipient: false + })))); if (messages.length === 0) { return; } // Mark all as read - if (markAsRead) { + if (ps.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 cb115cf98..ff44e192d 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 '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import Message from '../../../../../models/messaging-message'; import { isValidText } from '../../../../../models/messaging-message'; import History from '../../../../../models/messaging-history'; @@ -9,6 +9,7 @@ import { pack } from '../../../../../models/messaging-message'; import { publishMainStream } from '../../../../../stream'; import { publishMessagingStream, publishMessagingIndexStream } from '../../../../../stream'; import pushSw from '../../../../../push-sw'; +import getParams from '../../../get-params'; export const meta = { desc: { @@ -18,22 +19,37 @@ export const meta = { requireCredential: true, - kind: 'messaging-write' + kind: 'messaging-write', + + params: { + userId: { + validator: $.type(ID), + transform: transform, + }, + + text: { + validator: $.str.optional.pipe(isValidText) + }, + + fileId: { + validator: $.type(ID).optional, + transform: transform, + } + } }; 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'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Myself - if (recipientId.equals(user._id)) { + if (ps.userId.equals(user._id)) { return rej('cannot send message to myself'); } // Fetch recipient const recipient = await User.findOne({ - _id: recipientId + _id: ps.userId }, { fields: { _id: true @@ -44,18 +60,10 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = return rej('user not found'); } - // Get 'text' parameter - 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); - if (fileIdErr) return rej('invalid fileId param'); - let file = null; - if (fileId !== undefined) { + if (ps.fileId != null) { file = await DriveFile.findOne({ - _id: fileId, + _id: ps.fileId, 'metadata.userId': user._id }); @@ -65,7 +73,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = } // テキストが無いかつ添付ファイルも無かったらエラー - if (text === undefined && file === null) { + if (ps.text == null && file == null) { return rej('text or file is required'); } @@ -74,7 +82,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = createdAt: new Date(), fileId: file ? file._id : undefined, recipientId: recipient._id, - text: text ? text.trim() : undefined, + text: ps.text ? ps.text.trim() : undefined, userId: user._id, isRead: false }); diff --git a/src/server/api/endpoints/messaging/messages/read.ts b/src/server/api/endpoints/messaging/messages/read.ts index 1c0bdf523..122034fdf 100644 --- a/src/server/api/endpoints/messaging/messages/read.ts +++ b/src/server/api/endpoints/messaging/messages/read.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import Message from '../../../../../models/messaging-message'; import { ILocalUser } from '../../../../../models/user'; import read from '../../../common/read-messaging-message'; @@ -15,12 +15,14 @@ export const meta = { kind: 'messaging-write', params: { - messageId: $.type(ID).note({ + messageId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '既読にするメッセージのID', 'en-US': 'The ID of a message that you want to mark as read' } - }) + } } }; diff --git a/src/server/api/endpoints/mute/create.ts b/src/server/api/endpoints/mute/create.ts index 5b2e7a8d7..442d3f51d 100644 --- a/src/server/api/endpoints/mute/create.ts +++ b/src/server/api/endpoints/mute/create.ts @@ -1,6 +1,7 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import User, { ILocalUser } from '../../../../models/user'; import Mute from '../../../../models/mute'; +import getParams from '../../get-params'; export const meta = { desc: { @@ -10,24 +11,30 @@ export const meta = { requireCredential: true, - kind: 'account/write' + kind: 'account/write', + + params: { + userId: { + validator: $.type(ID), + transform: transform, + }, + } }; export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + const muter = user; - // Get 'userId' parameter - const [userId, userIdErr] = $.type(ID).get(params.userId); - if (userIdErr) return rej('invalid userId param'); - // 自分自身 - if (user._id.equals(userId)) { + if (user._id.equals(ps.userId)) { return rej('mutee is yourself'); } // Get mutee const mutee = await User.findOne({ - _id: userId + _id: ps.userId }, { fields: { data: false, @@ -56,6 +63,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = muteeId: mutee._id, }); - // Send response res(); }); diff --git a/src/server/api/endpoints/mute/delete.ts b/src/server/api/endpoints/mute/delete.ts index e8ed75a84..c1d4f3542 100644 --- a/src/server/api/endpoints/mute/delete.ts +++ b/src/server/api/endpoints/mute/delete.ts @@ -1,6 +1,7 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import User, { ILocalUser } from '../../../../models/user'; import Mute from '../../../../models/mute'; +import getParams from '../../get-params'; export const meta = { desc: { @@ -10,24 +11,30 @@ export const meta = { requireCredential: true, - kind: 'account/write' + kind: 'account/write', + + params: { + userId: { + validator: $.type(ID), + transform: transform, + }, + } }; export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + const muter = user; - // Get 'userId' parameter - const [userId, userIdErr] = $.type(ID).get(params.userId); - if (userIdErr) return rej('invalid userId param'); - // Check if the mutee is yourself - if (user._id.equals(userId)) { + if (user._id.equals(ps.userId)) { return rej('mutee is yourself'); } // Get mutee const mutee = await User.findOne({ - _id: userId + _id: ps.userId }, { fields: { data: false, @@ -54,6 +61,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = _id: exist._id }); - // Send response res(); }); diff --git a/src/server/api/endpoints/mute/list.ts b/src/server/api/endpoints/mute/list.ts index 465387762..e33e70977 100644 --- a/src/server/api/endpoints/mute/list.ts +++ b/src/server/api/endpoints/mute/list.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Mute, { packMany } from '../../../../models/mute'; import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; @@ -14,15 +14,20 @@ export const meta = { kind: 'account/read', params: { - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 30 - }), + }, - sinceId: $.type(ID).optional.note({ - }), + sinceId: { + validator: $.type(ID).optional, + transform: transform, + }, - untilId: $.type(ID).optional.note({ - }), + untilId: { + validator: $.type(ID).optional, + transform: transform, + }, } }; diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts index 4f5a21124..83776c59d 100644 --- a/src/server/api/endpoints/notes.ts +++ b/src/server/api/endpoints/notes.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../misc/cafy-id'; import Note, { packMany } from '../../../models/note'; import getParams from '../get-params'; @@ -8,49 +8,62 @@ export const meta = { }, params: { - local: $.bool.optional.note({ + local: { + validator: $.bool.optional, desc: { 'ja-JP': 'ローカルの投稿に限定するか否か' } - }), + }, - reply: $.bool.optional.note({ + reply: { + validator: $.bool.optional, desc: { 'ja-JP': '返信に限定するか否か' } - }), + }, - renote: $.bool.optional.note({ + renote: { + validator: $.bool.optional, desc: { 'ja-JP': 'Renoteに限定するか否か' } - }), + }, - withFiles: $.bool.optional.note({ + withFiles: { + validator: $.bool.optional, desc: { 'ja-JP': 'ファイルが添付された投稿に限定するか否か' } - }), + }, - media: $.bool.optional.note({ + media: { + validator: $.bool.optional, desc: { 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' } - }), + }, - poll: $.bool.optional.note({ + poll: { + validator: $.bool.optional, desc: { 'ja-JP': 'アンケートが添付された投稿に限定するか否か' } - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 10 - }), + }, - sinceId: $.type(ID).optional.note({}), + sinceId: { + validator: $.type(ID).optional, + transform: transform, + }, - untilId: $.type(ID).optional.note({}), + untilId: { + validator: $.type(ID).optional, + transform: transform, + }, } }; diff --git a/src/server/api/endpoints/notes/conversation.ts b/src/server/api/endpoints/notes/conversation.ts index 0c23f9e5f..c7be6d6e3 100644 --- a/src/server/api/endpoints/notes/conversation.ts +++ b/src/server/api/endpoints/notes/conversation.ts @@ -1,26 +1,41 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Note, { packMany, INote } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; + +export const meta = { + desc: { + 'ja-JP': '指定した投稿の文脈を取得します。', + 'en-US': 'Show conversation of a note.' + }, + + requireCredential: false, + + params: { + noteId: { + validator: $.type(ID), + transform: transform, + }, + + limit: { + validator: $.num.optional.range(1, 100), + default: 10 + }, + + offset: { + validator: $.num.optional.min(0), + default: 0 + }, + } +}; -/** - * Show conversation of a note - */ 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); - if (limitErr) return rej('invalid limit param'); - - // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); - if (offsetErr) return rej('invalid offset param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Lookup note const note = await Note.findOne({ - _id: noteId + _id: ps.noteId }); if (note === null) { @@ -34,11 +49,11 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = i++; const p = await Note.findOne({ _id: id }); - if (i > offset) { + if (i > ps.offset) { conversation.push(p); } - if (conversation.length == limit) { + if (conversation.length == ps.limit) { return; } @@ -51,6 +66,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = await get(note.replyId); } - // Serialize res(await packMany(conversation, user)); }); diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 432561da3..9eff8c27b 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform, transformMany } from '../../../../misc/cafy-id'; const ms = require('ms'); import Note, { INote, isValidText, isValidCw, pack } from '../../../../models/note'; import User, { ILocalUser, IUser } from '../../../../models/user'; @@ -24,84 +24,106 @@ export const meta = { kind: 'note-write', params: { - visibility: $.str.optional.or(['public', 'home', 'followers', 'specified', 'private']).note({ + visibility: { + validator: $.str.optional.or(['public', 'home', 'followers', 'specified', 'private']), default: 'public', desc: { 'ja-JP': '投稿の公開範囲' } - }), + }, - visibleUserIds: $.arr($.type(ID)).optional.unique().min(1).note({ + visibleUserIds: { + validator: $.arr($.type(ID)).optional.unique().min(1), + transform: transformMany, desc: { 'ja-JP': '(投稿の公開範囲が specified の場合)投稿を閲覧できるユーザー' } - }), + }, - text: $.str.optional.nullable.pipe(isValidText).note({ - default: null, + text: { + validator: $.str.optional.nullable.pipe(isValidText), + default: null as any, desc: { 'ja-JP': '投稿内容' } - }), + }, - cw: $.str.optional.nullable.pipe(isValidCw).note({ + cw: { + validator: $.str.optional.nullable.pipe(isValidCw), desc: { 'ja-JP': 'コンテンツの警告。このパラメータを指定すると設定したテキストで投稿のコンテンツを隠す事が出来ます。' } - }), + }, - viaMobile: $.bool.optional.note({ + viaMobile: { + validator: $.bool.optional, default: false, desc: { 'ja-JP': 'モバイルデバイスからの投稿か否か。' } - }), + }, - 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({ + geo: { + validator: $.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(), desc: { 'ja-JP': '位置情報' }, ref: 'geo' - }), + }, - fileIds: $.arr($.type(ID)).optional.unique().range(1, 4).note({ + fileIds: { + validator: $.arr($.type(ID)).optional.unique().range(1, 4), + transform: transformMany, desc: { 'ja-JP': '添付するファイル' } - }), + }, - mediaIds: $.arr($.type(ID)).optional.unique().range(1, 4).note({ + mediaIds: { + validator: $.arr($.type(ID)).optional.unique().range(1, 4), + transform: transformMany, desc: { 'ja-JP': '添付するファイル (このパラメータは廃止予定です。代わりに fileIds を使ってください。)' } - }), + }, - renoteId: $.type(ID).optional.note({ + replyId: { + validator: $.type(ID).optional, + transform: transform, + desc: { + 'ja-JP': '返信対象' + } + }, + + renoteId: { + validator: $.type(ID).optional, + transform: transform, desc: { 'ja-JP': 'Renote対象' } - }), + }, - poll: $.obj({ - choices: $.arr($.str) - .unique() - .range(2, 10) - .each(c => c.length > 0 && c.length < 50) - }).optional.strict().note({ + poll: { + validator: $.obj({ + choices: $.arr($.str) + .unique() + .range(2, 10) + .each(c => c.length > 0 && c.length < 50) + }).optional.strict(), desc: { 'ja-JP': 'アンケート' }, ref: 'poll' - }) + } }, res: { @@ -117,15 +139,12 @@ export const meta = { } }; -/** - * 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) { + if (ps.visibleUserIds) { visibleUsers = await Promise.all(ps.visibleUserIds.map(id => User.findOne({ _id: id }))); @@ -145,7 +164,7 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( } let renote: INote = null; - if (ps.renoteId !== undefined) { + if (ps.renoteId != null) { // Fetch renote to note renote = await Note.findOne({ _id: ps.renoteId @@ -158,15 +177,11 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( } } - // Get 'replyId' parameter - const [replyId, replyIdErr] = $.type(ID).optional.get(params.replyId); - if (replyIdErr) return rej('invalid replyId'); - let reply: INote = null; - if (replyId !== undefined) { + if (ps.replyId != null) { // Fetch reply reply = await Note.findOne({ - _id: replyId + _id: ps.replyId }); if (reply === null) { @@ -188,7 +203,7 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( } // テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー - if ((ps.text === undefined || ps.text === null) && files === null && renote === null && ps.poll === undefined) { + if ((ps.text == null) && files === null && renote === null && ps.poll == null) { return rej('text, fileIds, renoteId or poll is required'); } diff --git a/src/server/api/endpoints/notes/delete.ts b/src/server/api/endpoints/notes/delete.ts index 160d5c4cf..580cb344e 100644 --- a/src/server/api/endpoints/notes/delete.ts +++ b/src/server/api/endpoints/notes/delete.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import deleteNote from '../../../../services/note/delete'; import User, { ILocalUser } from '../../../../models/user'; @@ -17,12 +17,14 @@ export const meta = { kind: 'note-write', params: { - noteId: $.type(ID).note({ + noteId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' } - }) + } } }; diff --git a/src/server/api/endpoints/notes/favorites/create.ts b/src/server/api/endpoints/notes/favorites/create.ts index 76673e248..e489c7776 100644 --- a/src/server/api/endpoints/notes/favorites/create.ts +++ b/src/server/api/endpoints/notes/favorites/create.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import Favorite from '../../../../../models/favorite'; import Note from '../../../../../models/note'; import { ILocalUser } from '../../../../../models/user'; @@ -17,12 +17,14 @@ export const meta = { kind: 'favorite-write', params: { - noteId: $.type(ID).note({ + noteId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' } - }) + } } }; diff --git a/src/server/api/endpoints/notes/favorites/delete.ts b/src/server/api/endpoints/notes/favorites/delete.ts index 70e1ca8cc..ac1b35d19 100644 --- a/src/server/api/endpoints/notes/favorites/delete.ts +++ b/src/server/api/endpoints/notes/favorites/delete.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import Favorite from '../../../../../models/favorite'; import Note from '../../../../../models/note'; import { ILocalUser } from '../../../../../models/user'; @@ -17,12 +17,14 @@ export const meta = { kind: 'favorite-write', params: { - noteId: $.type(ID).note({ + noteId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' } - }) + } } }; diff --git a/src/server/api/endpoints/notes/featured.ts b/src/server/api/endpoints/notes/featured.ts index 363170ead..c03130882 100644 --- a/src/server/api/endpoints/notes/featured.ts +++ b/src/server/api/endpoints/notes/featured.ts @@ -13,12 +13,13 @@ export const meta = { requireCredential: false, params: { - limit: $.num.optional.range(1, 30).note({ + limit: { + validator: $.num.optional.range(1, 30), default: 10, desc: { 'ja-JP': '最大数' } - }) + } } }; diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts index 8a6c84894..fa7a76f12 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 '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { packMany } from '../../../../models/note'; @@ -12,29 +12,42 @@ export const meta = { }, params: { - withFiles: $.bool.optional.note({ + withFiles: { + validator: $.bool.optional, desc: { 'ja-JP': 'ファイルが添付された投稿に限定するか否か' } - }), + }, - mediaOnly: $.bool.optional.note({ + mediaOnly: { + validator: $.bool.optional, desc: { 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' } - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 10 - }), + }, - sinceId: $.type(ID).optional.note({}), + sinceId: { + validator: $.type(ID).optional, + transform: transform, + }, - untilId: $.type(ID).optional.note({}), + untilId: { + validator: $.type(ID).optional, + transform: transform, + }, - sinceDate: $.num.optional.note({}), + sinceDate: { + validator: $.num.optional + }, - untilDate: $.num.optional.note({}), + untilDate: { + validator: $.num.optional + }, } }; diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index b2ea9c60a..000814482 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { getFriends } from '../../common/get-friends'; @@ -13,69 +13,81 @@ export const meta = { }, params: { - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 10, desc: { 'ja-JP': '最大数' } - }), + }, - sinceId: $.type(ID).optional.note({ + sinceId: { + validator: $.type(ID).optional, + transform: transform, desc: { 'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します' } - }), + }, - untilId: $.type(ID).optional.note({ + untilId: { + validator: $.type(ID).optional, + transform: transform, desc: { 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' } - }), + }, - sinceDate: $.num.optional.note({ + sinceDate: { + validator: $.num.optional, desc: { 'ja-JP': '指定した時間を基点としてより新しい投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' } - }), + }, - untilDate: $.num.optional.note({ + untilDate: { + validator: $.num.optional, desc: { 'ja-JP': '指定した時間を基点としてより古い投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' } - }), + }, - includeMyRenotes: $.bool.optional.note({ + includeMyRenotes: { + validator: $.bool.optional, default: true, desc: { 'ja-JP': '自分の行ったRenoteを含めるかどうか' } - }), + }, - includeRenotedMyNotes: $.bool.optional.note({ + includeRenotedMyNotes: { + validator: $.bool.optional, default: true, desc: { 'ja-JP': 'Renoteされた自分の投稿を含めるかどうか' } - }), + }, - includeLocalRenotes: $.bool.optional.note({ + includeLocalRenotes: { + validator: $.bool.optional, default: true, desc: { 'ja-JP': 'Renoteされたローカルの投稿を含めるかどうか' } - }), + }, - withFiles: $.bool.optional.note({ + withFiles: { + validator: $.bool.optional, desc: { 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' } - }), + }, - mediaOnly: $.bool.optional.note({ + mediaOnly: { + validator: $.bool.optional, desc: { 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' } - }), + }, } }; diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index 510564129..78ddf0062 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 '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { packMany } from '../../../../models/note'; @@ -12,42 +12,57 @@ export const meta = { }, params: { - withFiles: $.bool.optional.note({ + withFiles: { + validator: $.bool.optional, desc: { 'ja-JP': 'ファイルが添付された投稿に限定するか否か' } - }), + }, - mediaOnly: $.bool.optional.note({ + mediaOnly: { + validator: $.bool.optional, desc: { 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' } - }), + }, - fileType: $.arr($.str).optional.note({ + fileType: { + validator: $.arr($.str).optional, desc: { 'ja-JP': '指定された種類のファイルが添付された投稿のみを取得します' } - }), + }, - excludeNsfw: $.bool.optional.note({ + excludeNsfw: { + validator: $.bool.optional, default: false, desc: { 'ja-JP': 'true にすると、NSFW指定されたファイルを除外します(fileTypeが指定されている場合のみ有効)' } - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 10 - }), + }, - sinceId: $.type(ID).optional.note({}), + sinceId: { + validator: $.type(ID).optional, + transform: transform, + }, - untilId: $.type(ID).optional.note({}), + untilId: { + validator: $.type(ID).optional, + transform: transform, + }, - sinceDate: $.num.optional.note({}), + sinceDate: { + validator: $.num.optional, + }, - untilDate: $.num.optional.note({}), + untilDate: { + validator: $.num.optional, + }, } }; diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index c66421284..3fc397707 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import { getFriendIds } from '../../common/get-friends'; import { packMany } from '../../../../models/note'; @@ -15,22 +15,29 @@ export const meta = { requireCredential: true, params: { - following: $.bool.optional.note({ + following: { + validator: $.bool.optional, default: false - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 10 - }), + }, - sinceId: $.type(ID).optional.note({ - }), + sinceId: { + validator: $.type(ID).optional, + transform: transform, + }, - untilId: $.type(ID).optional.note({ - }), + untilId: { + validator: $.type(ID).optional, + transform: transform, + }, - visibility: $.str.optional.note({ - }), + visibility: { + validator: $.str.optional, + }, } }; diff --git a/src/server/api/endpoints/notes/polls/vote.ts b/src/server/api/endpoints/notes/polls/vote.ts index 3b78d62fd..32dcefec7 100644 --- a/src/server/api/endpoints/notes/polls/vote.ts +++ b/src/server/api/endpoints/notes/polls/vote.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import Vote from '../../../../../models/poll-vote'; import Note from '../../../../../models/note'; import Watching from '../../../../../models/note-watching'; @@ -6,6 +6,7 @@ import watch from '../../../../../services/note/watch'; import { publishNoteStream } from '../../../../../stream'; import notify from '../../../../../notify'; import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; export const meta = { desc: { @@ -15,17 +16,27 @@ export const meta = { requireCredential: true, - kind: 'vote-write' + kind: 'vote-write', + + params: { + noteId: { + validator: $.type(ID), + transform: transform, + }, + + choice: { + validator: $.num + }, + } }; 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'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Get votee const note = await Note.findOne({ - _id: noteId + _id: ps.noteId }); if (note === null) { @@ -36,12 +47,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = return rej('poll not found'); } - // Get 'choice' parameter - const [choice, choiceError] = - $.num - .pipe(c => note.poll.choices.some(x => x.id == c)) - .get(params.choice); - if (choiceError) return rej('invalid choice param'); + if (!note.poll.choices.some(x => x.id == ps.choice)) return rej('invalid choice param'); // if already voted const exist = await Vote.findOne({ @@ -58,14 +64,14 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = createdAt: new Date(), noteId: note._id, userId: user._id, - choice: choice + choice: ps.choice }); // Send response res(); const inc: any = {}; - inc[`poll.choices.${note.poll.choices.findIndex(c => c.id == choice)}.votes`] = 1; + inc[`poll.choices.${note.poll.choices.findIndex(c => c.id == ps.choice)}.votes`] = 1; // Increment votes count await Note.update({ _id: note._id }, { @@ -73,14 +79,14 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = }); publishNoteStream(note._id, 'pollVoted', { - choice: choice, + choice: ps.choice, userId: user._id.toHexString() }); // Notify notify(note.userId, user._id, 'poll_vote', { noteId: note._id, - choice: choice + choice: ps.choice }); // Fetch watchers @@ -99,7 +105,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = watchers.forEach(watcher => { notify(watcher.userId, user._id, 'poll_vote', { noteId: note._id, - choice: choice + choice: ps.choice }); }); }); diff --git a/src/server/api/endpoints/notes/reactions.ts b/src/server/api/endpoints/notes/reactions.ts index 5f23068cd..1ab5afaba 100644 --- a/src/server/api/endpoints/notes/reactions.ts +++ b/src/server/api/endpoints/notes/reactions.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Reaction, { pack } from '../../../../models/note-reaction'; import { ILocalUser } from '../../../../models/user'; @@ -13,26 +13,34 @@ export const meta = { requireCredential: false, params: { - noteId: $.type(ID).note({ + noteId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'The ID of the target note' } - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 10 - }), + }, - offset: $.num.optional.note({ + offset: { + validator: $.num.optional, default: 0 - }), + }, - sinceId: $.type(ID).optional.note({ - }), + sinceId: { + validator: $.type(ID).optional, + transform: transform, + }, - untilId: $.type(ID).optional.note({ - }), + untilId: { + validator: $.type(ID).optional, + transform: transform, + }, } }; diff --git a/src/server/api/endpoints/notes/reactions/create.ts b/src/server/api/endpoints/notes/reactions/create.ts index aa9ab0738..f2b06473a 100644 --- a/src/server/api/endpoints/notes/reactions/create.ts +++ b/src/server/api/endpoints/notes/reactions/create.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import Note from '../../../../../models/note'; import create from '../../../../../services/note/reaction/create'; import { validateReaction } from '../../../../../models/note-reaction'; @@ -18,17 +18,20 @@ export const meta = { kind: 'reaction-write', params: { - noteId: $.type(ID).note({ + noteId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象の投稿' } - }), + }, - reaction: $.str.pipe(validateReaction.ok).note({ + reaction: { + validator: $.str.pipe(validateReaction.ok), desc: { 'ja-JP': 'リアクションの種類' } - }) + } } }; diff --git a/src/server/api/endpoints/notes/reactions/delete.ts b/src/server/api/endpoints/notes/reactions/delete.ts index 598eb6536..2a2577dfe 100644 --- a/src/server/api/endpoints/notes/reactions/delete.ts +++ b/src/server/api/endpoints/notes/reactions/delete.ts @@ -1,7 +1,8 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import Reaction from '../../../../../models/note-reaction'; import Note from '../../../../../models/note'; import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; export const meta = { desc: { @@ -11,17 +12,23 @@ export const meta = { requireCredential: true, - kind: 'reaction-write' + kind: 'reaction-write', + + params: { + noteId: { + validator: $.type(ID), + transform: transform, + }, + } }; 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'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Fetch unreactee const note = await Note.findOne({ - _id: noteId + _id: ps.noteId }); if (note === null) { @@ -48,7 +55,6 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = } }); - // Send response res(); const dec: any = {}; diff --git a/src/server/api/endpoints/notes/renotes.ts b/src/server/api/endpoints/notes/renotes.ts new file mode 100644 index 000000000..ff926806e --- /dev/null +++ b/src/server/api/endpoints/notes/renotes.ts @@ -0,0 +1,81 @@ +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; +import Note, { packMany } from '../../../../models/note'; +import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; + +export const meta = { + desc: { + 'ja-JP': '指定した投稿のRenote一覧を取得します。', + 'en-US': 'Show a renotes of a note.' + }, + + requireCredential: false, + + params: { + noteId: { + validator: $.type(ID), + transform: transform, + }, + + limit: { + validator: $.num.optional.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.type(ID).optional, + transform: transform, + }, + + untilId: { + validator: $.type(ID).optional, + transform: transform, + } + } +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + + // Check if both of sinceId and untilId is specified + if (ps.sinceId && ps.untilId) { + return rej('cannot set sinceId and untilId'); + } + + // Lookup note + const note = await Note.findOne({ + _id: ps.noteId + }); + + if (note === null) { + return rej('note not found'); + } + + const sort = { + _id: -1 + }; + + const query = { + renoteId: note._id + } as any; + + if (ps.sinceId) { + sort._id = 1; + query._id = { + $gt: ps.sinceId + }; + } else if (ps.untilId) { + query._id = { + $lt: ps.untilId + }; + } + + const renotes = await Note + .find(query, { + limit: ps.limit, + sort: sort + }); + + res(await packMany(renotes, user)); +}); diff --git a/src/server/api/endpoints/notes/replies.ts b/src/server/api/endpoints/notes/replies.ts index b2f8f94f6..86a75c1ca 100644 --- a/src/server/api/endpoints/notes/replies.ts +++ b/src/server/api/endpoints/notes/replies.ts @@ -1,34 +1,48 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Note, { packMany } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; + +export const meta = { + desc: { + 'ja-JP': '指定した投稿への返信を取得します。', + 'en-US': 'Get replies of a note.' + }, + + requireCredential: false, + + params: { + noteId: { + validator: $.type(ID), + transform: transform, + }, + + limit: { + validator: $.num.optional.range(1, 100), + default: 10 + }, + + offset: { + validator: $.num.optional.min(0), + default: 0 + }, + } +}; -/** - * Get replies of a note - */ 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); - if (limitErr) return rej('invalid limit param'); - - // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); - if (offsetErr) return rej('invalid offset param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Lookup note const note = await Note.findOne({ - _id: noteId + _id: ps.noteId }); if (note === null) { return rej('note not found'); } - const ids = (note._replyIds || []).slice(offset, offset + limit); + const ids = (note._replyIds || []).slice(ps.offset, ps.offset + ps.limit); - // Serialize res(await packMany(ids, user)); }); diff --git a/src/server/api/endpoints/notes/reposts.ts b/src/server/api/endpoints/notes/reposts.ts deleted file mode 100644 index 2c6e1a499..000000000 --- a/src/server/api/endpoints/notes/reposts.ts +++ /dev/null @@ -1,66 +0,0 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; -import Note, { packMany } from '../../../../models/note'; -import { ILocalUser } from '../../../../models/user'; - -/** - * Show a renotes of a note - */ -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); - if (limitErr) return rej('invalid limit param'); - - // Get 'sinceId' parameter - 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); - if (untilIdErr) return rej('invalid untilId param'); - - // Check if both of sinceId and untilId is specified - if (sinceId && untilId) { - return rej('cannot set sinceId and untilId'); - } - - // Lookup note - const note = await Note.findOne({ - _id: noteId - }); - - if (note === null) { - return rej('note not found'); - } - - // Construct query - const sort = { - _id: -1 - }; - const query = { - renoteId: note._id - } as any; - if (sinceId) { - sort._id = 1; - query._id = { - $gt: sinceId - }; - } else if (untilId) { - query._id = { - $lt: untilId - }; - } - - // Issue query - const renotes = await Note - .find(query, { - limit: limit, - sort: sort - }); - - // Serialize - res(await packMany(renotes, user)); -}); diff --git a/src/server/api/endpoints/notes/search_by_tag.ts b/src/server/api/endpoints/notes/search_by_tag.ts index e7fa15f76..99a6dce83 100644 --- a/src/server/api/endpoints/notes/search_by_tag.ts +++ b/src/server/api/endpoints/notes/search_by_tag.ts @@ -1,11 +1,10 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; -import User, { ILocalUser } from '../../../../models/user'; +import { ILocalUser } from '../../../../models/user'; import Mute from '../../../../models/mute'; import { getFriendIds } from '../../common/get-friends'; import { packMany } from '../../../../models/note'; import getParams from '../../get-params'; -import { erase } from '../../../../prelude/array'; export const meta = { desc: { @@ -13,99 +12,94 @@ export const meta = { }, params: { - tag: $.str.optional.note({ + tag: { + validator: $.str.optional, desc: { 'ja-JP': 'タグ' } - }), + }, - query: $.arr($.arr($.str)).optional.note({ + query: { + validator: $.arr($.arr($.str)).optional, desc: { 'ja-JP': 'クエリ' } - }), + }, - includeUserIds: $.arr($.type(ID)).optional.note({ - default: [] - }), + following: { + validator: $.bool.optional.nullable, + default: null as any + }, - excludeUserIds: $.arr($.type(ID)).optional.note({ - default: [] - }), - - includeUserUsernames: $.arr($.str).optional.note({ - default: [] - }), - - excludeUserUsernames: $.arr($.str).optional.note({ - default: [] - }), - - following: $.bool.optional.nullable.note({ - default: null - }), - - mute: $.str.optional.note({ + mute: { + validator: $.str.optional, default: 'mute_all' - }), - - reply: $.bool.optional.nullable.note({ - default: null, + }, + reply: { + validator: $.bool.optional.nullable, + default: null as any, desc: { 'ja-JP': '返信に限定するか否か' } - }), - - renote: $.bool.optional.nullable.note({ - default: null, + }, + renote: { + validator: $.bool.optional.nullable, + default: null as any, desc: { 'ja-JP': 'Renoteに限定するか否か' } - }), + }, - withFiles: $.bool.optional.note({ + withFiles: { + validator: $.bool.optional, desc: { 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' } - }), - - media: $.bool.optional.nullable.note({ - default: null, + }, + media: { + validator: $.bool.optional.nullable, + default: null as any, desc: { 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' } - }), - - poll: $.bool.optional.nullable.note({ - default: null, + }, + poll: { + validator: $.bool.optional.nullable, + default: null as any, desc: { 'ja-JP': 'アンケートが添付された投稿に限定するか否か' } - }), + }, - untilId: $.type(ID).optional.note({ + untilId: { + validator: $.type(ID).optional, + transform: transform, desc: { 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' } - }), + }, - sinceDate: $.num.optional.note({ - }), + sinceDate: { + validator: $.num.optional, + }, - untilDate: $.num.optional.note({ - }), + untilDate: { + validator: $.num.optional, + }, - offset: $.num.optional.min(0).note({ + offset: { + validator: $.num.optional.min(0), default: 0 - }), + }, - limit: $.num.optional.range(1, 30).note({ + limit: { + validator: $.num.optional.range(1, 30), default: 10 - }), + }, } }; @@ -113,28 +107,6 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => const [ps, psErr] = getParams(meta, params); if (psErr) return rej(psErr); - if (ps.includeUserUsernames != null) { - const ids = erase(null, await Promise.all(ps.includeUserUsernames.map(async (username) => { - const _user = await User.findOne({ - usernameLower: username.toLowerCase() - }); - return _user ? _user._id : null; - }))); - - ids.forEach(id => ps.includeUserIds.push(id)); - } - - if (ps.excludeUserUsernames != null) { - const ids = erase(null, await Promise.all(ps.excludeUserUsernames.map(async (username) => { - const _user = await User.findOne({ - usernameLower: username.toLowerCase() - }); - return _user ? _user._id : null; - }))); - - ids.forEach(id => ps.excludeUserIds.push(id)); - } - const q: any = { $and: [ps.tag ? { tagsLower: ps.tag.toLowerCase() @@ -150,20 +122,6 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => const push = (x: any) => q.$and.push(x); - if (ps.includeUserIds && ps.includeUserIds.length != 0) { - push({ - userId: { - $in: ps.includeUserIds - } - }); - } else if (ps.excludeUserIds && ps.excludeUserIds.length != 0) { - push({ - userId: { - $nin: ps.excludeUserIds - } - }); - } - if (ps.following != null && me != null) { const ids = await getFriendIds(me._id, false); push({ diff --git a/src/server/api/endpoints/notes/show.ts b/src/server/api/endpoints/notes/show.ts index e84a948c9..8b426c005 100644 --- a/src/server/api/endpoints/notes/show.ts +++ b/src/server/api/endpoints/notes/show.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Note, { pack } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; @@ -14,12 +14,14 @@ export const meta = { requireCredential: false, params: { - noteId: $.type(ID).note({ + noteId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' } - }) + } } }; diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index 31a497840..8834645fc 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { getFriends } from '../../common/get-friends'; @@ -16,69 +16,81 @@ export const meta = { requireCredential: true, params: { - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 10, desc: { 'ja-JP': '最大数' } - }), + }, - sinceId: $.type(ID).optional.note({ + sinceId: { + validator: $.type(ID).optional, + transform: transform, desc: { 'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します' } - }), + }, - untilId: $.type(ID).optional.note({ + untilId: { + validator: $.type(ID).optional, + transform: transform, desc: { 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' } - }), + }, - sinceDate: $.num.optional.note({ + sinceDate: { + validator: $.num.optional, desc: { 'ja-JP': '指定した時間を基点としてより新しい投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' } - }), + }, - untilDate: $.num.optional.note({ + untilDate: { + validator: $.num.optional, desc: { 'ja-JP': '指定した時間を基点としてより古い投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' } - }), + }, - includeMyRenotes: $.bool.optional.note({ + includeMyRenotes: { + validator: $.bool.optional, default: true, desc: { 'ja-JP': '自分の行ったRenoteを含めるかどうか' } - }), + }, - includeRenotedMyNotes: $.bool.optional.note({ + includeRenotedMyNotes: { + validator: $.bool.optional, default: true, desc: { 'ja-JP': 'Renoteされた自分の投稿を含めるかどうか' } - }), + }, - includeLocalRenotes: $.bool.optional.note({ + includeLocalRenotes: { + validator: $.bool.optional, default: true, desc: { 'ja-JP': 'Renoteされたローカルの投稿を含めるかどうか' } - }), + }, - withFiles: $.bool.optional.note({ + withFiles: { + validator: $.bool.optional, desc: { 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' } - }), + }, - mediaOnly: $.bool.optional.note({ + mediaOnly: { + validator: $.bool.optional, desc: { 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' } - }), + }, } }; diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts index 7dddc4834..3bdcc6c12 100644 --- a/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/src/server/api/endpoints/notes/user-list-timeline.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { packMany } from '../../../../models/note'; @@ -15,75 +15,89 @@ export const meta = { requireCredential: true, params: { - listId: $.type(ID).note({ + listId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': 'リストのID' } - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 10, desc: { 'ja-JP': '最大数' } - }), + }, - sinceId: $.type(ID).optional.note({ + sinceId: { + validator: $.type(ID).optional, + transform: transform, desc: { 'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します' } - }), + }, - untilId: $.type(ID).optional.note({ + untilId: { + validator: $.type(ID).optional, + transform: transform, desc: { 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' } - }), + }, - sinceDate: $.num.optional.note({ + sinceDate: { + validator: $.num.optional, desc: { 'ja-JP': '指定した時間を基点としてより新しい投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' } - }), + }, - untilDate: $.num.optional.note({ + untilDate: { + validator: $.num.optional, desc: { 'ja-JP': '指定した時間を基点としてより古い投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' } - }), + }, - includeMyRenotes: $.bool.optional.note({ + includeMyRenotes: { + validator: $.bool.optional, default: true, desc: { 'ja-JP': '自分の行ったRenoteを含めるかどうか' } - }), + }, - includeRenotedMyNotes: $.bool.optional.note({ + includeRenotedMyNotes: { + validator: $.bool.optional, default: true, desc: { 'ja-JP': 'Renoteされた自分の投稿を含めるかどうか' } - }), + }, - includeLocalRenotes: $.bool.optional.note({ + includeLocalRenotes: { + validator: $.bool.optional, default: true, desc: { 'ja-JP': 'Renoteされたローカルの投稿を含めるかどうか' } - }), + }, - withFiles: $.bool.optional.note({ + withFiles: { + validator: $.bool.optional, desc: { 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' } - }), + }, - mediaOnly: $.bool.optional.note({ + mediaOnly: { + validator: $.bool.optional, desc: { 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' } - }), + }, } }; diff --git a/src/server/api/endpoints/users/followers.ts b/src/server/api/endpoints/users/followers.ts index 7fe3ca994..71081835c 100644 --- a/src/server/api/endpoints/users/followers.ts +++ b/src/server/api/endpoints/users/followers.ts @@ -1,32 +1,49 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import User, { ILocalUser } from '../../../../models/user'; import Following from '../../../../models/following'; import { pack } from '../../../../models/user'; import { getFriendIds } from '../../common/get-friends'; +import getParams from '../../get-params'; + +export const meta = { + desc: { + 'ja-JP': '指定したユーザーのフォロワー一覧を取得します。', + 'en-US': 'Get followers of a user.' + }, + + requireCredential: false, + + params: { + userId: { + validator: $.type(ID), + transform: transform, + }, + + limit: { + validator: $.num.optional.range(1, 100), + default: 10 + }, + + cursor: { + validator: $.type(ID).optional, + default: null as any, + transform: transform, + }, + + iknow: { + validator: $.bool.optional, + default: false, + } + } +}; -/** - * Get followers of a user - */ 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); - if (iknowErr) return rej('invalid iknow param'); - - // Get 'limit' parameter - 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); - if (cursorErr) return rej('invalid cursor param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Lookup user const user = await User.findOne({ - _id: userId + _id: ps.userId }, { fields: { _id: true @@ -43,7 +60,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } as any; // ログインしていてかつ iknow フラグがあるとき - if (me && iknow) { + if (me && ps.iknow) { // Get my friends const myFriends = await getFriendIds(me._id); @@ -53,29 +70,27 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } // カーソルが指定されている場合 - if (cursor) { + if (ps.cursor) { query._id = { - $lt: cursor + $lt: ps.cursor }; } // Get followers const following = await Following .find(query, { - limit: limit + 1, + limit: ps.limit + 1, sort: { _id: -1 } }); // 「次のページ」があるかどうか - const inStock = following.length === limit + 1; + const inStock = following.length === ps.limit + 1; if (inStock) { following.pop(); } - // Serialize const users = await Promise.all(following.map(f => pack(f.followerId, me, { detail: true }))); - // Response res({ users: users, next: inStock ? following[following.length - 1]._id : null, diff --git a/src/server/api/endpoints/users/following.ts b/src/server/api/endpoints/users/following.ts index 0e564fd1b..778ef54a2 100644 --- a/src/server/api/endpoints/users/following.ts +++ b/src/server/api/endpoints/users/following.ts @@ -1,32 +1,49 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import User, { ILocalUser } from '../../../../models/user'; import Following from '../../../../models/following'; import { pack } from '../../../../models/user'; import { getFriendIds } from '../../common/get-friends'; +import getParams from '../../get-params'; + +export const meta = { + desc: { + 'ja-JP': '指定したユーザーのフォロー一覧を取得します。', + 'en-US': 'Get following users of a user.' + }, + + requireCredential: false, + + params: { + userId: { + validator: $.type(ID), + transform: transform, + }, + + limit: { + validator: $.num.optional.range(1, 100), + default: 10 + }, + + cursor: { + validator: $.type(ID).optional, + default: null as any, + transform: transform, + }, + + iknow: { + validator: $.bool.optional, + default: false, + } + } +}; -/** - * Get following users of a user - */ 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); - if (iknowErr) return rej('invalid iknow param'); - - // Get 'limit' parameter - 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); - if (cursorErr) return rej('invalid cursor param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Lookup user const user = await User.findOne({ - _id: userId + _id: ps.userId }, { fields: { _id: true @@ -43,7 +60,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } as any; // ログインしていてかつ iknow フラグがあるとき - if (me && iknow) { + if (me && ps.iknow) { // Get my friends const myFriends = await getFriendIds(me._id); @@ -53,29 +70,27 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } // カーソルが指定されている場合 - if (cursor) { + if (ps.cursor) { query._id = { - $lt: cursor + $lt: ps.cursor }; } // Get followers const following = await Following .find(query, { - limit: limit + 1, + limit: ps.limit + 1, sort: { _id: -1 } }); // 「次のページ」があるかどうか - const inStock = following.length === limit + 1; + const inStock = following.length === ps.limit + 1; if (inStock) { following.pop(); } - // Serialize const users = await Promise.all(following.map(f => pack(f.followeeId, me, { detail: true }))); - // Response res({ users: users, next: inStock ? following[following.length - 1]._id : null, 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 42b6ce20d..b0fd25958 100644 --- a/src/server/api/endpoints/users/get_frequently_replied_users.ts +++ b/src/server/api/endpoints/users/get_frequently_replied_users.ts @@ -1,19 +1,31 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import User, { pack, ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; + +export const meta = { + requireCredential: false, + + params: { + userId: { + validator: $.type(ID), + transform: transform, + }, + + limit: { + validator: $.num.optional.range(1, 100), + default: 10 + }, + } +}; 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); - if (limitErr) return rej('invalid limit param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Lookup user const user = await User.findOne({ - _id: userId + _id: ps.userId }, { fields: { _id: true @@ -83,7 +95,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]); // Extract top replied users - const topRepliedUsers = repliedUsersSorted.slice(0, limit); + const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit); // Make replies object (includes weights) const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({ @@ -91,6 +103,5 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => weight: repliedUsers[user] / peak }))); - // Response res(repliesObj); }); diff --git a/src/server/api/endpoints/users/lists/delete.ts b/src/server/api/endpoints/users/lists/delete.ts index c56963aab..1d4513a82 100644 --- a/src/server/api/endpoints/users/lists/delete.ts +++ b/src/server/api/endpoints/users/lists/delete.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID from '../../../../../misc/cafy-id'; +import ID, { transform } from '../../../../../misc/cafy-id'; import UserList from '../../../../../models/user-list'; import { ILocalUser } from '../../../../../models/user'; import getParams from '../../../get-params'; @@ -15,12 +15,14 @@ export const meta = { kind: 'account-write', params: { - listId: $.type(ID).note({ + listId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象となるユーザーリストのID', 'en-US': 'ID of target user list' } - }) + } } }; diff --git a/src/server/api/endpoints/users/lists/push.ts b/src/server/api/endpoints/users/lists/push.ts index 2d68ec745..8208e627e 100644 --- a/src/server/api/endpoints/users/lists/push.ts +++ b/src/server/api/endpoints/users/lists/push.ts @@ -1,10 +1,11 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import UserList from '../../../../../models/user-list'; import User, { pack as packUser, isRemoteUser, getGhost, ILocalUser } from '../../../../../models/user'; import { publishUserListStream } from '../../../../../stream'; import ap from '../../../../../remote/activitypub/renderer'; import renderFollow from '../../../../../remote/activitypub/renderer/follow'; import { deliver } from '../../../../../queue'; +import getParams from '../../../get-params'; export const meta = { desc: { @@ -14,20 +15,28 @@ export const meta = { requireCredential: true, - kind: 'account-write' + kind: 'account-write', + + params: { + listId: { + validator: $.type(ID), + transform: transform, + }, + + userId: { + validator: $.type(ID), + transform: transform, + }, + } }; -/** - * Add a user to a user list - */ 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'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Fetch the list const userList = await UserList.findOne({ - _id: listId, + _id: ps.listId, userId: me._id, }); @@ -35,13 +44,9 @@ export default async (params: any, me: ILocalUser) => new Promise(async (res, re return rej('list not found'); } - // Get 'userId' parameter - const [userId, userIdErr] = $.type(ID).get(params.userId); - if (userIdErr) return rej('invalid userId param'); - // Fetch the user const user = await User.findOne({ - _id: userId + _id: ps.userId }); if (user == null) { diff --git a/src/server/api/endpoints/users/lists/show.ts b/src/server/api/endpoints/users/lists/show.ts index a2dd00c6e..585833a2f 100644 --- a/src/server/api/endpoints/users/lists/show.ts +++ b/src/server/api/endpoints/users/lists/show.ts @@ -1,6 +1,7 @@ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id'; import UserList, { pack } from '../../../../../models/user-list'; import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; export const meta = { desc: { @@ -10,17 +11,23 @@ export const meta = { requireCredential: true, - kind: 'account-read' + kind: 'account-read', + + params: { + listId: { + validator: $.type(ID), + transform: transform, + }, + } }; 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'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Fetch the list const userList = await UserList.findOne({ - _id: listId, + _id: ps.listId, userId: me._id, }); diff --git a/src/server/api/endpoints/users/lists/update.ts b/src/server/api/endpoints/users/lists/update.ts index e39f66fb5..fb1a37b2f 100644 --- a/src/server/api/endpoints/users/lists/update.ts +++ b/src/server/api/endpoints/users/lists/update.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID from '../../../../../misc/cafy-id'; +import ID, { transform } from '../../../../../misc/cafy-id'; import UserList, { pack } from '../../../../../models/user-list'; import { ILocalUser } from '../../../../../models/user'; import getParams from '../../../get-params'; @@ -15,18 +15,22 @@ export const meta = { kind: 'account-write', params: { - listId: $.type(ID).note({ + listId: { + validator: $.type(ID), + transform: transform, desc: { 'ja-JP': '対象となるユーザーリストのID', 'en-US': 'ID of target user list' } - }), - title: $.str.range(1, 100).note({ + }, + + title: { + validator: $.str.range(1, 100), desc: { 'ja-JP': 'このユーザーリストの名前', 'en-US': 'name of this user list' } - }) + } } }; diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index 6934f7c4b..ac6601f64 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 '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id'; import getHostLower from '../../common/get-host-lower'; import Note, { packMany } from '../../../../models/note'; import User, { ILocalUser } from '../../../../models/user'; @@ -11,100 +11,118 @@ export const meta = { }, params: { - userId: $.type(ID).optional.note({ + userId: { + validator: $.type(ID).optional, + transform: transform, desc: { 'ja-JP': 'ユーザーID' } - }), + }, - username: $.str.optional.note({ + username: { + validator: $.str.optional, desc: { 'ja-JP': 'ユーザー名' } - }), + }, - host: $.str.optional.note({ - }), + host: { + validator: $.str.optional, + }, - includeReplies: $.bool.optional.note({ + includeReplies: { + validator: $.bool.optional, default: true, desc: { 'ja-JP': 'リプライを含めるか否か' } - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 10, desc: { 'ja-JP': '最大数' } - }), + }, - sinceId: $.type(ID).optional.note({ + sinceId: { + validator: $.type(ID).optional, + transform: transform, desc: { 'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します' } - }), + }, - untilId: $.type(ID).optional.note({ + untilId: { + validator: $.type(ID).optional, + transform: transform, desc: { 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' } - }), + }, - sinceDate: $.num.optional.note({ + sinceDate: { + validator: $.num.optional, desc: { 'ja-JP': '指定した時間を基点としてより新しい投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' } - }), + }, - untilDate: $.num.optional.note({ + untilDate: { + validator: $.num.optional, desc: { 'ja-JP': '指定した時間を基点としてより古い投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' } - }), + }, - includeMyRenotes: $.bool.optional.note({ + includeMyRenotes: { + validator: $.bool.optional, default: true, desc: { 'ja-JP': '自分の行ったRenoteを含めるかどうか' } - }), + }, - includeRenotedMyNotes: $.bool.optional.note({ + includeRenotedMyNotes: { + validator: $.bool.optional, default: true, desc: { 'ja-JP': 'Renoteされた自分の投稿を含めるかどうか' } - }), + }, - includeLocalRenotes: $.bool.optional.note({ + includeLocalRenotes: { + validator: $.bool.optional, default: true, desc: { 'ja-JP': 'Renoteされたローカルの投稿を含めるかどうか' } - }), + }, - withFiles: $.bool.optional.note({ + withFiles: { + validator: $.bool.optional, default: false, desc: { 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' } - }), + }, - mediaOnly: $.bool.optional.note({ + mediaOnly: { + validator: $.bool.optional, default: false, desc: { 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' } - }), + }, - fileType: $.arr($.str).optional.note({ + fileType: { + validator: $.arr($.str).optional, desc: { 'ja-JP': '指定された種類のファイルが添付された投稿のみを取得します' } - }), + }, } }; @@ -121,7 +139,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } - const q = ps.userId !== undefined + const q = ps.userId != null ? { _id: ps.userId } : { usernameLower: ps.username.toLowerCase(), host: getHostLower(ps.host) } ; diff --git a/src/server/api/endpoints/users/relation.ts b/src/server/api/endpoints/users/relation.ts index 5c7da4834..19643ceed 100644 --- a/src/server/api/endpoints/users/relation.ts +++ b/src/server/api/endpoints/users/relation.ts @@ -1,4 +1,4 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform, ObjectId } from '../../../../misc/cafy-id'; import { ILocalUser, getRelation } from '../../../../models/user'; import getParams from '../../get-params'; @@ -10,11 +10,13 @@ export const meta = { requireCredential: true, params: { - userId: $.or($.type(ID), $.arr($.type(ID)).unique()).note({ + userId: { + validator: $.or($.type(ID), $.arr($.type(ID)).unique()), + transform: (v: any): ObjectId | ObjectId[] => Array.isArray(v) ? v.map(x => transform(x)) : transform(v), desc: { 'ja-JP': 'ユーザーID (配列でも可)' } - }) + } } }; diff --git a/src/server/api/endpoints/users/search.ts b/src/server/api/endpoints/users/search.ts index 307a8f689..a2077b589 100644 --- a/src/server/api/endpoints/users/search.ts +++ b/src/server/api/endpoints/users/search.ts @@ -11,32 +11,36 @@ export const meta = { requireCredential: false, params: { - query: $.str.note({ + query: { + validator: $.str, desc: { 'ja-JP': 'クエリ' } - }), + }, - offset: $.num.optional.min(0).note({ + offset: { + validator: $.num.optional.min(0), default: 0, desc: { 'ja-JP': 'オフセット' } - }), + }, - limit: $.num.optional.range(1, 100).note({ + limit: { + validator: $.num.optional.range(1, 100), default: 10, desc: { 'ja-JP': '取得する数' } - }), + }, - localOnly: $.bool.optional.note({ + localOnly: { + validator: $.bool.optional, default: false, desc: { 'ja-JP': 'ローカルユーザーのみ検索対象にするか否か' } - }), + }, }, }; diff --git a/src/server/api/endpoints/users/show.ts b/src/server/api/endpoints/users/show.ts index dd09bd5b9..c6f01593d 100644 --- a/src/server/api/endpoints/users/show.ts +++ b/src/server/api/endpoints/users/show.ts @@ -1,35 +1,54 @@ -import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; +import $ from 'cafy'; import ID, { transform, transformMany } from '../../../../misc/cafy-id'; import User, { pack, ILocalUser, isRemoteUser } from '../../../../models/user'; import resolveRemoteUser from '../../../../remote/resolve-user'; +import getParams from '../../get-params'; const cursorOption = { fields: { data: false } }; -/** - * Show user(s) - */ +export const meta = { + desc: { + 'ja-JP': '指定したユーザーの情報を取得します。' + }, + + requireCredential: false, + + params: { + userId: { + validator: $.type(ID).optional, + transform: transform, + desc: { + 'ja-JP': 'ユーザーID' + } + }, + + userIds: { + validator: $.arr($.type(ID)).optional.unique(), + transform: transformMany, + desc: { + 'ja-JP': 'ユーザーID (配列)' + } + }, + + username: { + validator: $.str.optional + }, + + host: { + validator: $.str.optional.nullable + } + } +}; + export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + let user; - // Get 'userId' parameter - 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); - if (userIdsErr) return rej('invalid userIds param'); - - // Get 'username' parameter - 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); - if (hostErr) return rej('invalid host param'); - - if (userIds) { + if (ps.userIds) { const users = await User.find({ _id: { - $in: userIds + $in: ps.userIds } }); @@ -38,17 +57,17 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => })))); } else { // Lookup user - if (typeof host === 'string') { + if (typeof ps.host === 'string') { try { - user = await resolveRemoteUser(username, host, cursorOption); + user = await resolveRemoteUser(ps.username, ps.host, cursorOption); } catch (e) { console.warn(`failed to resolve remote user: ${e}`); return rej('failed to resolve remote user'); } } else { - const q: any = userId !== undefined - ? { _id: userId } - : { usernameLower: username.toLowerCase(), host: null }; + const q: any = ps.userId != null + ? { _id: ps.userId } + : { usernameLower: ps.username.toLowerCase(), host: null }; user = await User.findOne(q, cursorOption); @@ -64,7 +83,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => if (isRemoteUser(user)) { if (user.updatedAt == null || Date.now() - user.updatedAt.getTime() > 1000 * 60 * 60 * 24) { - resolveRemoteUser(username, host, { }, true); + resolveRemoteUser(ps.username, ps.host, { }, true); } } } diff --git a/src/server/api/get-params.ts b/src/server/api/get-params.ts index 878f223cc..60d038990 100644 --- a/src/server/api/get-params.ts +++ b/src/server/api/get-params.ts @@ -1,27 +1,26 @@ -import { Context } from 'cafy'; +import { IEndpointMeta } from './endpoints'; -type Defs = { - params: { [key: string]: Context } -}; - -export default function (defs: T, params: any): [{ - [P in keyof T['params']]: ReturnType[0]; +export default function (defs: T, params: any): [{ + [P in keyof T['params']]: T['params'][P]['transform'] extends Function + ? ReturnType + : ReturnType[0]; }, Error] { const x: any = {}; let err: Error = null; - Object.keys(defs.params).some(k => { - const [v, e] = defs.params[k].get(params[k]); + Object.entries(defs.params).some(([k, def]) => { + const [v, e] = def.validator.get(params[k]); if (e) { err = new Error(e.message); err.name = 'INVALID_PARAM'; (err as any).param = k; return true; } else { - if (v === undefined && defs.params[k].data.default) { - x[k] = defs.params[k].data.default; + if (v === undefined && def.default) { + x[k] = def.default; } else { x[k] = v; } + if (def.transform) x[k] = def.transform(x[k]); return false; } }); diff --git a/src/server/web/docs.ts b/src/server/web/docs.ts index 14fb39e9b..003022bcf 100644 --- a/src/server/web/docs.ts +++ b/src/server/web/docs.ts @@ -181,7 +181,7 @@ router.get('/*/api/endpoints/*', async ctx => { }, // @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, + paramDefs: ep.meta.params ? extractParamDefRef(Object.values(ep.meta.params).map(x => x.validator)) : 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)))