From f4ae9391248d9ba366210be215681537ce9ecb49 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 24 Feb 2019 17:57:49 +0900 Subject: ハイフンに統一 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/endpoints/drive/files/attached-notes.ts | 58 ++++ .../api/endpoints/drive/files/attached_notes.ts | 58 ---- .../api/endpoints/drive/files/check-existence.ts | 35 +++ .../api/endpoints/drive/files/check_existence.ts | 35 --- .../api/endpoints/drive/files/upload-from-url.ts | 57 ++++ .../api/endpoints/drive/files/upload_from_url.ts | 57 ---- src/server/api/endpoints/i/authorized-apps.ts | 45 +++ src/server/api/endpoints/i/authorized_apps.ts | 45 --- src/server/api/endpoints/i/change-password.ts | 41 +++ src/server/api/endpoints/i/change_password.ts | 41 --- .../api/endpoints/i/read-all-messaging-messages.ts | 44 +++ .../api/endpoints/i/read-all-unread-notes.ts | 40 +++ .../api/endpoints/i/read_all_messaging_messages.ts | 44 --- .../api/endpoints/i/read_all_unread_notes.ts | 40 --- src/server/api/endpoints/i/regenerate-token.ts | 41 +++ src/server/api/endpoints/i/regenerate_token.ts | 41 --- src/server/api/endpoints/i/signin-history.ts | 56 ++++ src/server/api/endpoints/i/signin_history.ts | 56 ---- .../api/endpoints/i/update-client-setting.ts | 37 +++ src/server/api/endpoints/i/update-email.ts | 100 ++++++ src/server/api/endpoints/i/update-home.ts | 33 ++ src/server/api/endpoints/i/update-mobile-home.ts | 32 ++ src/server/api/endpoints/i/update-widget.ts | 88 ++++++ .../api/endpoints/i/update_client_setting.ts | 37 --- src/server/api/endpoints/i/update_email.ts | 100 ------ src/server/api/endpoints/i/update_home.ts | 33 -- src/server/api/endpoints/i/update_mobile_home.ts | 32 -- src/server/api/endpoints/i/update_widget.ts | 88 ------ src/server/api/endpoints/notes/search-by-tag.ts | 338 +++++++++++++++++++++ src/server/api/endpoints/notes/search_by_tag.ts | 338 --------------------- .../endpoints/notifications/mark-all-as-read.ts | 41 +++ .../endpoints/notifications/mark_all_as_read.ts | 41 --- .../users/get-frequently-replied-users.ts | 115 +++++++ .../users/get_frequently_replied_users.ts | 115 ------- src/server/api/index.ts | 4 + 35 files changed, 1205 insertions(+), 1201 deletions(-) create mode 100644 src/server/api/endpoints/drive/files/attached-notes.ts delete mode 100644 src/server/api/endpoints/drive/files/attached_notes.ts create mode 100644 src/server/api/endpoints/drive/files/check-existence.ts delete mode 100644 src/server/api/endpoints/drive/files/check_existence.ts create mode 100644 src/server/api/endpoints/drive/files/upload-from-url.ts delete mode 100644 src/server/api/endpoints/drive/files/upload_from_url.ts create mode 100644 src/server/api/endpoints/i/authorized-apps.ts delete mode 100644 src/server/api/endpoints/i/authorized_apps.ts create mode 100644 src/server/api/endpoints/i/change-password.ts delete mode 100644 src/server/api/endpoints/i/change_password.ts create mode 100644 src/server/api/endpoints/i/read-all-messaging-messages.ts create mode 100644 src/server/api/endpoints/i/read-all-unread-notes.ts delete mode 100644 src/server/api/endpoints/i/read_all_messaging_messages.ts delete mode 100644 src/server/api/endpoints/i/read_all_unread_notes.ts create mode 100644 src/server/api/endpoints/i/regenerate-token.ts delete mode 100644 src/server/api/endpoints/i/regenerate_token.ts create mode 100644 src/server/api/endpoints/i/signin-history.ts delete mode 100644 src/server/api/endpoints/i/signin_history.ts create mode 100644 src/server/api/endpoints/i/update-client-setting.ts create mode 100644 src/server/api/endpoints/i/update-email.ts create mode 100644 src/server/api/endpoints/i/update-home.ts create mode 100644 src/server/api/endpoints/i/update-mobile-home.ts create mode 100644 src/server/api/endpoints/i/update-widget.ts delete mode 100644 src/server/api/endpoints/i/update_client_setting.ts delete mode 100644 src/server/api/endpoints/i/update_email.ts delete mode 100644 src/server/api/endpoints/i/update_home.ts delete mode 100644 src/server/api/endpoints/i/update_mobile_home.ts delete mode 100644 src/server/api/endpoints/i/update_widget.ts create mode 100644 src/server/api/endpoints/notes/search-by-tag.ts delete mode 100644 src/server/api/endpoints/notes/search_by_tag.ts create mode 100644 src/server/api/endpoints/notifications/mark-all-as-read.ts delete mode 100644 src/server/api/endpoints/notifications/mark_all_as_read.ts create mode 100644 src/server/api/endpoints/users/get-frequently-replied-users.ts delete mode 100644 src/server/api/endpoints/users/get_frequently_replied_users.ts (limited to 'src/server/api') diff --git a/src/server/api/endpoints/drive/files/attached-notes.ts b/src/server/api/endpoints/drive/files/attached-notes.ts new file mode 100644 index 0000000000..c1d828b05a --- /dev/null +++ b/src/server/api/endpoints/drive/files/attached-notes.ts @@ -0,0 +1,58 @@ +import $ from 'cafy'; +import ID, { transform } from '../../../../../misc/cafy-id'; +import DriveFile from '../../../../../models/drive-file'; +import define from '../../../define'; +import { packMany } from '../../../../../models/note'; +import { ApiError } from '../../../error'; + +export const meta = { + stability: 'stable', + + desc: { + 'ja-JP': '指定したドライブのファイルが添付されている投稿一覧を取得します。', + 'en-US': 'Get the notes that specified file of drive attached.' + }, + + tags: ['drive', 'notes'], + + requireCredential: true, + + kind: 'drive-read', + + params: { + fileId: { + validator: $.type(ID), + transform: transform, + desc: { + 'ja-JP': '対象のファイルID', + 'en-US': 'Target file ID' + } + } + }, + + errors: { + noSuchFile: { + message: 'No such file.', + code: 'NO_SUCH_FILE', + id: 'c118ece3-2e4b-4296-99d1-51756e32d232', + } + } +}; + +export default define(meta, async (ps, user) => { + // Fetch file + const file = await DriveFile + .findOne({ + _id: ps.fileId, + 'metadata.userId': user._id, + 'metadata.deletedAt': { $exists: false } + }); + + if (file === null) { + throw new ApiError(meta.errors.noSuchFile); + } + + return await packMany(file.metadata.attachedNoteIds || [], user, { + detail: true + }); +}); diff --git a/src/server/api/endpoints/drive/files/attached_notes.ts b/src/server/api/endpoints/drive/files/attached_notes.ts deleted file mode 100644 index c1d828b05a..0000000000 --- a/src/server/api/endpoints/drive/files/attached_notes.ts +++ /dev/null @@ -1,58 +0,0 @@ -import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFile from '../../../../../models/drive-file'; -import define from '../../../define'; -import { packMany } from '../../../../../models/note'; -import { ApiError } from '../../../error'; - -export const meta = { - stability: 'stable', - - desc: { - 'ja-JP': '指定したドライブのファイルが添付されている投稿一覧を取得します。', - 'en-US': 'Get the notes that specified file of drive attached.' - }, - - tags: ['drive', 'notes'], - - requireCredential: true, - - kind: 'drive-read', - - params: { - fileId: { - validator: $.type(ID), - transform: transform, - desc: { - 'ja-JP': '対象のファイルID', - 'en-US': 'Target file ID' - } - } - }, - - errors: { - noSuchFile: { - message: 'No such file.', - code: 'NO_SUCH_FILE', - id: 'c118ece3-2e4b-4296-99d1-51756e32d232', - } - } -}; - -export default define(meta, async (ps, user) => { - // Fetch file - const file = await DriveFile - .findOne({ - _id: ps.fileId, - 'metadata.userId': user._id, - 'metadata.deletedAt': { $exists: false } - }); - - if (file === null) { - throw new ApiError(meta.errors.noSuchFile); - } - - return await packMany(file.metadata.attachedNoteIds || [], user, { - detail: true - }); -}); diff --git a/src/server/api/endpoints/drive/files/check-existence.ts b/src/server/api/endpoints/drive/files/check-existence.ts new file mode 100644 index 0000000000..12680d5419 --- /dev/null +++ b/src/server/api/endpoints/drive/files/check-existence.ts @@ -0,0 +1,35 @@ +import $ from 'cafy'; +import DriveFile, { pack } from '../../../../../models/drive-file'; +import define from '../../../define'; + +export const meta = { + desc: { + 'ja-JP': '与えられたMD5ハッシュ値を持つファイルがドライブに存在するかどうかを返します。', + 'en-US': 'Returns whether the file with the given MD5 hash exists in the user\'s drive.' + }, + + tags: ['drive'], + + requireCredential: true, + + kind: 'drive-read', + + params: { + md5: { + validator: $.str, + desc: { + 'ja-JP': 'ファイルのMD5ハッシュ' + } + } + } +}; + +export default define(meta, async (ps, user) => { + const file = await DriveFile.findOne({ + md5: ps.md5, + 'metadata.userId': user._id, + 'metadata.deletedAt': { $exists: false } + }); + + return { file: file ? await pack(file, { self: true }) : null }; +}); diff --git a/src/server/api/endpoints/drive/files/check_existence.ts b/src/server/api/endpoints/drive/files/check_existence.ts deleted file mode 100644 index 12680d5419..0000000000 --- a/src/server/api/endpoints/drive/files/check_existence.ts +++ /dev/null @@ -1,35 +0,0 @@ -import $ from 'cafy'; -import DriveFile, { pack } from '../../../../../models/drive-file'; -import define from '../../../define'; - -export const meta = { - desc: { - 'ja-JP': '与えられたMD5ハッシュ値を持つファイルがドライブに存在するかどうかを返します。', - 'en-US': 'Returns whether the file with the given MD5 hash exists in the user\'s drive.' - }, - - tags: ['drive'], - - requireCredential: true, - - kind: 'drive-read', - - params: { - md5: { - validator: $.str, - desc: { - 'ja-JP': 'ファイルのMD5ハッシュ' - } - } - } -}; - -export default define(meta, async (ps, user) => { - const file = await DriveFile.findOne({ - md5: ps.md5, - 'metadata.userId': user._id, - 'metadata.deletedAt': { $exists: false } - }); - - return { file: file ? await pack(file, { self: true }) : null }; -}); diff --git a/src/server/api/endpoints/drive/files/upload-from-url.ts b/src/server/api/endpoints/drive/files/upload-from-url.ts new file mode 100644 index 0000000000..93a9fa62fa --- /dev/null +++ b/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -0,0 +1,57 @@ +import $ from 'cafy'; +import ID, { transform } from '../../../../../misc/cafy-id'; +import * as ms from 'ms'; +import { pack } from '../../../../../models/drive-file'; +import uploadFromUrl from '../../../../../services/drive/upload-from-url'; +import define from '../../../define'; + +export const meta = { + desc: { + 'ja-JP': 'ドライブに指定されたURLに存在するファイルをアップロードします。' + }, + + tags: ['drive'], + + limit: { + duration: ms('1hour'), + max: 60 + }, + + requireCredential: true, + + kind: 'drive-write', + + params: { + url: { + // TODO: Validate this url + validator: $.str, + }, + + folderId: { + validator: $.optional.nullable.type(ID), + default: null as any, + transform: transform + }, + + isSensitive: { + validator: $.optional.bool, + default: false, + desc: { + 'ja-JP': 'このメディアが「閲覧注意」(NSFW)かどうか', + 'en-US': 'Whether this media is NSFW' + } + }, + + force: { + validator: $.optional.bool, + default: false, + desc: { + 'ja-JP': 'true にすると、同じハッシュを持つファイルが既にアップロードされていても強制的にファイルを作成します。', + } + } + } +}; + +export default define(meta, async (ps, user) => { + return await pack(await uploadFromUrl(ps.url, user, ps.folderId, null, ps.isSensitive, ps.force), { self: true }); +}); diff --git a/src/server/api/endpoints/drive/files/upload_from_url.ts b/src/server/api/endpoints/drive/files/upload_from_url.ts deleted file mode 100644 index 93a9fa62fa..0000000000 --- a/src/server/api/endpoints/drive/files/upload_from_url.ts +++ /dev/null @@ -1,57 +0,0 @@ -import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import * as ms from 'ms'; -import { pack } from '../../../../../models/drive-file'; -import uploadFromUrl from '../../../../../services/drive/upload-from-url'; -import define from '../../../define'; - -export const meta = { - desc: { - 'ja-JP': 'ドライブに指定されたURLに存在するファイルをアップロードします。' - }, - - tags: ['drive'], - - limit: { - duration: ms('1hour'), - max: 60 - }, - - requireCredential: true, - - kind: 'drive-write', - - params: { - url: { - // TODO: Validate this url - validator: $.str, - }, - - folderId: { - validator: $.optional.nullable.type(ID), - default: null as any, - transform: transform - }, - - isSensitive: { - validator: $.optional.bool, - default: false, - desc: { - 'ja-JP': 'このメディアが「閲覧注意」(NSFW)かどうか', - 'en-US': 'Whether this media is NSFW' - } - }, - - force: { - validator: $.optional.bool, - default: false, - desc: { - 'ja-JP': 'true にすると、同じハッシュを持つファイルが既にアップロードされていても強制的にファイルを作成します。', - } - } - } -}; - -export default define(meta, async (ps, user) => { - return await pack(await uploadFromUrl(ps.url, user, ps.folderId, null, ps.isSensitive, ps.force), { self: true }); -}); diff --git a/src/server/api/endpoints/i/authorized-apps.ts b/src/server/api/endpoints/i/authorized-apps.ts new file mode 100644 index 0000000000..cb8be9ed97 --- /dev/null +++ b/src/server/api/endpoints/i/authorized-apps.ts @@ -0,0 +1,45 @@ +import $ from 'cafy'; +import AccessToken from '../../../../models/access-token'; +import { pack } from '../../../../models/app'; +import define from '../../define'; + +export const meta = { + requireCredential: true, + + secure: true, + + params: { + limit: { + validator: $.optional.num.range(1, 100), + default: 10, + }, + + offset: { + validator: $.optional.num.min(0), + default: 0, + }, + + sort: { + validator: $.optional.str.or('desc|asc'), + default: 'desc', + } + } +}; + +export default define(meta, async (ps, user) => { + // Get tokens + const tokens = await AccessToken + .find({ + userId: user._id + }, { + limit: ps.limit, + skip: ps.offset, + sort: { + _id: ps.sort == 'asc' ? 1 : -1 + } + }); + + return await Promise.all(tokens.map(token => pack(token.appId, user, { + detail: true + }))); +}); diff --git a/src/server/api/endpoints/i/authorized_apps.ts b/src/server/api/endpoints/i/authorized_apps.ts deleted file mode 100644 index cb8be9ed97..0000000000 --- a/src/server/api/endpoints/i/authorized_apps.ts +++ /dev/null @@ -1,45 +0,0 @@ -import $ from 'cafy'; -import AccessToken from '../../../../models/access-token'; -import { pack } from '../../../../models/app'; -import define from '../../define'; - -export const meta = { - requireCredential: true, - - secure: true, - - params: { - limit: { - validator: $.optional.num.range(1, 100), - default: 10, - }, - - offset: { - validator: $.optional.num.min(0), - default: 0, - }, - - sort: { - validator: $.optional.str.or('desc|asc'), - default: 'desc', - } - } -}; - -export default define(meta, async (ps, user) => { - // Get tokens - const tokens = await AccessToken - .find({ - userId: user._id - }, { - limit: ps.limit, - skip: ps.offset, - sort: { - _id: ps.sort == 'asc' ? 1 : -1 - } - }); - - return await Promise.all(tokens.map(token => pack(token.appId, user, { - detail: true - }))); -}); diff --git a/src/server/api/endpoints/i/change-password.ts b/src/server/api/endpoints/i/change-password.ts new file mode 100644 index 0000000000..8ab286b4bf --- /dev/null +++ b/src/server/api/endpoints/i/change-password.ts @@ -0,0 +1,41 @@ +import $ from 'cafy'; +import * as bcrypt from 'bcryptjs'; +import User from '../../../../models/user'; +import define from '../../define'; + +export const meta = { + requireCredential: true, + + secure: true, + + params: { + currentPassword: { + validator: $.str + }, + + newPassword: { + validator: $.str + } + } +}; + +export default define(meta, async (ps, user) => { + // Compare password + const same = await bcrypt.compare(ps.currentPassword, user.password); + + if (!same) { + throw new Error('incorrect password'); + } + + // Generate hash of password + const salt = await bcrypt.genSalt(8); + const hash = await bcrypt.hash(ps.newPassword, salt); + + await User.update(user._id, { + $set: { + 'password': hash + } + }); + + return; +}); diff --git a/src/server/api/endpoints/i/change_password.ts b/src/server/api/endpoints/i/change_password.ts deleted file mode 100644 index 8ab286b4bf..0000000000 --- a/src/server/api/endpoints/i/change_password.ts +++ /dev/null @@ -1,41 +0,0 @@ -import $ from 'cafy'; -import * as bcrypt from 'bcryptjs'; -import User from '../../../../models/user'; -import define from '../../define'; - -export const meta = { - requireCredential: true, - - secure: true, - - params: { - currentPassword: { - validator: $.str - }, - - newPassword: { - validator: $.str - } - } -}; - -export default define(meta, async (ps, user) => { - // Compare password - const same = await bcrypt.compare(ps.currentPassword, user.password); - - if (!same) { - throw new Error('incorrect password'); - } - - // Generate hash of password - const salt = await bcrypt.genSalt(8); - const hash = await bcrypt.hash(ps.newPassword, salt); - - await User.update(user._id, { - $set: { - 'password': hash - } - }); - - return; -}); diff --git a/src/server/api/endpoints/i/read-all-messaging-messages.ts b/src/server/api/endpoints/i/read-all-messaging-messages.ts new file mode 100644 index 0000000000..bbbfa0d7b3 --- /dev/null +++ b/src/server/api/endpoints/i/read-all-messaging-messages.ts @@ -0,0 +1,44 @@ +import User from '../../../../models/user'; +import { publishMainStream } from '../../../../services/stream'; +import Message from '../../../../models/messaging-message'; +import define from '../../define'; + +export const meta = { + desc: { + 'ja-JP': 'トークメッセージをすべて既読にします。', + 'en-US': 'Mark all talk messages as read.' + }, + + tags: ['account', 'messaging'], + + requireCredential: true, + + kind: 'account-write', + + params: { + } +}; + +export default define(meta, async (ps, user) => { + // Update documents + await Message.update({ + recipientId: user._id, + isRead: false + }, { + $set: { + isRead: true + } + }, { + multi: true + }); + + User.update({ _id: user._id }, { + $set: { + hasUnreadMessagingMessage: false + } + }); + + publishMainStream(user._id, 'readAllMessagingMessages'); + + return; +}); diff --git a/src/server/api/endpoints/i/read-all-unread-notes.ts b/src/server/api/endpoints/i/read-all-unread-notes.ts new file mode 100644 index 0000000000..742c2d9908 --- /dev/null +++ b/src/server/api/endpoints/i/read-all-unread-notes.ts @@ -0,0 +1,40 @@ +import User from '../../../../models/user'; +import { publishMainStream } from '../../../../services/stream'; +import NoteUnread from '../../../../models/note-unread'; +import define from '../../define'; + +export const meta = { + desc: { + 'ja-JP': '未読の投稿をすべて既読にします。', + 'en-US': 'Mark all messages as read.' + }, + + tags: ['account'], + + requireCredential: true, + + kind: 'account-write', + + params: { + } +}; + +export default define(meta, async (ps, user) => { + // Remove documents + await NoteUnread.remove({ + userId: user._id + }); + + User.update({ _id: user._id }, { + $set: { + hasUnreadMentions: false, + hasUnreadSpecifiedNotes: false + } + }); + + // 全て既読になったイベントを発行 + publishMainStream(user._id, 'readAllUnreadMentions'); + publishMainStream(user._id, 'readAllUnreadSpecifiedNotes'); + + return; +}); diff --git a/src/server/api/endpoints/i/read_all_messaging_messages.ts b/src/server/api/endpoints/i/read_all_messaging_messages.ts deleted file mode 100644 index bbbfa0d7b3..0000000000 --- a/src/server/api/endpoints/i/read_all_messaging_messages.ts +++ /dev/null @@ -1,44 +0,0 @@ -import User from '../../../../models/user'; -import { publishMainStream } from '../../../../services/stream'; -import Message from '../../../../models/messaging-message'; -import define from '../../define'; - -export const meta = { - desc: { - 'ja-JP': 'トークメッセージをすべて既読にします。', - 'en-US': 'Mark all talk messages as read.' - }, - - tags: ['account', 'messaging'], - - requireCredential: true, - - kind: 'account-write', - - params: { - } -}; - -export default define(meta, async (ps, user) => { - // Update documents - await Message.update({ - recipientId: user._id, - isRead: false - }, { - $set: { - isRead: true - } - }, { - multi: true - }); - - User.update({ _id: user._id }, { - $set: { - hasUnreadMessagingMessage: false - } - }); - - publishMainStream(user._id, 'readAllMessagingMessages'); - - return; -}); diff --git a/src/server/api/endpoints/i/read_all_unread_notes.ts b/src/server/api/endpoints/i/read_all_unread_notes.ts deleted file mode 100644 index 742c2d9908..0000000000 --- a/src/server/api/endpoints/i/read_all_unread_notes.ts +++ /dev/null @@ -1,40 +0,0 @@ -import User from '../../../../models/user'; -import { publishMainStream } from '../../../../services/stream'; -import NoteUnread from '../../../../models/note-unread'; -import define from '../../define'; - -export const meta = { - desc: { - 'ja-JP': '未読の投稿をすべて既読にします。', - 'en-US': 'Mark all messages as read.' - }, - - tags: ['account'], - - requireCredential: true, - - kind: 'account-write', - - params: { - } -}; - -export default define(meta, async (ps, user) => { - // Remove documents - await NoteUnread.remove({ - userId: user._id - }); - - User.update({ _id: user._id }, { - $set: { - hasUnreadMentions: false, - hasUnreadSpecifiedNotes: false - } - }); - - // 全て既読になったイベントを発行 - publishMainStream(user._id, 'readAllUnreadMentions'); - publishMainStream(user._id, 'readAllUnreadSpecifiedNotes'); - - return; -}); diff --git a/src/server/api/endpoints/i/regenerate-token.ts b/src/server/api/endpoints/i/regenerate-token.ts new file mode 100644 index 0000000000..ad10b99b36 --- /dev/null +++ b/src/server/api/endpoints/i/regenerate-token.ts @@ -0,0 +1,41 @@ +import $ from 'cafy'; +import * as bcrypt from 'bcryptjs'; +import User from '../../../../models/user'; +import { publishMainStream } from '../../../../services/stream'; +import generateUserToken from '../../common/generate-native-user-token'; +import define from '../../define'; + +export const meta = { + requireCredential: true, + + secure: true, + + params: { + password: { + validator: $.str + } + } +}; + +export default define(meta, async (ps, user) => { + // Compare password + const same = await bcrypt.compare(ps.password, user.password); + + if (!same) { + throw new Error('incorrect password'); + } + + // Generate secret + const secret = generateUserToken(); + + await User.update(user._id, { + $set: { + 'token': secret + } + }); + + // Publish event + publishMainStream(user._id, 'myTokenRegenerated'); + + return; +}); diff --git a/src/server/api/endpoints/i/regenerate_token.ts b/src/server/api/endpoints/i/regenerate_token.ts deleted file mode 100644 index ad10b99b36..0000000000 --- a/src/server/api/endpoints/i/regenerate_token.ts +++ /dev/null @@ -1,41 +0,0 @@ -import $ from 'cafy'; -import * as bcrypt from 'bcryptjs'; -import User from '../../../../models/user'; -import { publishMainStream } from '../../../../services/stream'; -import generateUserToken from '../../common/generate-native-user-token'; -import define from '../../define'; - -export const meta = { - requireCredential: true, - - secure: true, - - params: { - password: { - validator: $.str - } - } -}; - -export default define(meta, async (ps, user) => { - // Compare password - const same = await bcrypt.compare(ps.password, user.password); - - if (!same) { - throw new Error('incorrect password'); - } - - // Generate secret - const secret = generateUserToken(); - - await User.update(user._id, { - $set: { - 'token': secret - } - }); - - // Publish event - publishMainStream(user._id, 'myTokenRegenerated'); - - return; -}); diff --git a/src/server/api/endpoints/i/signin-history.ts b/src/server/api/endpoints/i/signin-history.ts new file mode 100644 index 0000000000..87160a9f91 --- /dev/null +++ b/src/server/api/endpoints/i/signin-history.ts @@ -0,0 +1,56 @@ +import $ from 'cafy'; +import ID, { transform } from '../../../../misc/cafy-id'; +import Signin, { pack } from '../../../../models/signin'; +import define from '../../define'; + +export const meta = { + requireCredential: true, + + secure: true, + + params: { + limit: { + validator: $.optional.num.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.optional.type(ID), + transform: transform, + }, + + untilId: { + validator: $.optional.type(ID), + transform: transform, + } + } +}; + +export default define(meta, async (ps, user) => { + const query = { + userId: user._id + } as any; + + const sort = { + _id: -1 + }; + + if (ps.sinceId) { + sort._id = 1; + query._id = { + $gt: ps.sinceId + }; + } else if (ps.untilId) { + query._id = { + $lt: ps.untilId + }; + } + + const history = await Signin + .find(query, { + limit: ps.limit, + sort: sort + }); + + return await Promise.all(history.map(record => pack(record))); +}); diff --git a/src/server/api/endpoints/i/signin_history.ts b/src/server/api/endpoints/i/signin_history.ts deleted file mode 100644 index 87160a9f91..0000000000 --- a/src/server/api/endpoints/i/signin_history.ts +++ /dev/null @@ -1,56 +0,0 @@ -import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Signin, { pack } from '../../../../models/signin'; -import define from '../../define'; - -export const meta = { - requireCredential: true, - - secure: true, - - params: { - limit: { - validator: $.optional.num.range(1, 100), - default: 10 - }, - - sinceId: { - validator: $.optional.type(ID), - transform: transform, - }, - - untilId: { - validator: $.optional.type(ID), - transform: transform, - } - } -}; - -export default define(meta, async (ps, user) => { - const query = { - userId: user._id - } as any; - - const sort = { - _id: -1 - }; - - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } - - const history = await Signin - .find(query, { - limit: ps.limit, - sort: sort - }); - - return await Promise.all(history.map(record => pack(record))); -}); diff --git a/src/server/api/endpoints/i/update-client-setting.ts b/src/server/api/endpoints/i/update-client-setting.ts new file mode 100644 index 0000000000..79cd04e169 --- /dev/null +++ b/src/server/api/endpoints/i/update-client-setting.ts @@ -0,0 +1,37 @@ +import $ from 'cafy'; +import User from '../../../../models/user'; +import { publishMainStream } from '../../../../services/stream'; +import define from '../../define'; + +export const meta = { + requireCredential: true, + + secure: true, + + params: { + name: { + validator: $.str + }, + + value: { + validator: $.nullable.any + } + } +}; + +export default define(meta, async (ps, user) => { + const x: any = {}; + x[`clientSettings.${ps.name}`] = ps.value; + + await User.update(user._id, { + $set: x + }); + + // Publish event + publishMainStream(user._id, 'clientSettingUpdated', { + key: ps.name, + value: ps.value + }); + + return; +}); diff --git a/src/server/api/endpoints/i/update-email.ts b/src/server/api/endpoints/i/update-email.ts new file mode 100644 index 0000000000..c90462d850 --- /dev/null +++ b/src/server/api/endpoints/i/update-email.ts @@ -0,0 +1,100 @@ +import $ from 'cafy'; +import User, { pack } from '../../../../models/user'; +import { publishMainStream } from '../../../../services/stream'; +import define from '../../define'; +import * as nodemailer from 'nodemailer'; +import fetchMeta from '../../../../misc/fetch-meta'; +import rndstr from 'rndstr'; +import config from '../../../../config'; +import * as ms from 'ms'; +import * as bcrypt from 'bcryptjs'; +import { apiLogger } from '../../logger'; + +export const meta = { + requireCredential: true, + + secure: true, + + limit: { + duration: ms('1hour'), + max: 3 + }, + + params: { + password: { + validator: $.str + }, + + email: { + validator: $.optional.nullable.str + }, + } +}; + +export default define(meta, async (ps, user) => { + // Compare password + const same = await bcrypt.compare(ps.password, user.password); + + if (!same) { + throw new Error('incorrect password'); + } + + await User.update(user._id, { + $set: { + email: ps.email, + emailVerified: false, + emailVerifyCode: null + } + }); + + const iObj = await pack(user._id, user, { + detail: true, + includeSecrets: true + }); + + // Publish meUpdated event + publishMainStream(user._id, 'meUpdated', iObj); + + if (ps.email != null) { + const code = rndstr('a-z0-9', 16); + + await User.update(user._id, { + $set: { + emailVerifyCode: code + } + }); + + const meta = await fetchMeta(); + + const enableAuth = meta.smtpUser != null && meta.smtpUser !== ''; + + const transporter = nodemailer.createTransport({ + host: meta.smtpHost, + port: meta.smtpPort, + secure: meta.smtpSecure, + ignoreTLS: !enableAuth, + auth: enableAuth ? { + user: meta.smtpUser, + pass: meta.smtpPass + } : undefined + }); + + const link = `${config.url}/verify-email/${code}`; + + transporter.sendMail({ + from: meta.email, + to: ps.email, + subject: meta.name, + text: `To verify email, please click this link: ${link}` + }, (error, info) => { + if (error) { + apiLogger.error(error); + return; + } + + apiLogger.info('Message sent: %s', info.messageId); + }); + } + + return iObj; +}); diff --git a/src/server/api/endpoints/i/update-home.ts b/src/server/api/endpoints/i/update-home.ts new file mode 100644 index 0000000000..e2c319887f --- /dev/null +++ b/src/server/api/endpoints/i/update-home.ts @@ -0,0 +1,33 @@ +import $ from 'cafy'; +import User from '../../../../models/user'; +import { publishMainStream } from '../../../../services/stream'; +import define from '../../define'; + +export const meta = { + requireCredential: true, + + secure: true, + + params: { + home: { + validator: $.arr($.obj({ + name: $.str, + id: $.str, + place: $.str, + data: $.obj() + }).strict()) + } + } +}; + +export default define(meta, async (ps, user) => { + await User.update(user._id, { + $set: { + 'clientSettings.home': ps.home + } + }); + + publishMainStream(user._id, 'homeUpdated', ps.home); + + return; +}); diff --git a/src/server/api/endpoints/i/update-mobile-home.ts b/src/server/api/endpoints/i/update-mobile-home.ts new file mode 100644 index 0000000000..642e2b3e09 --- /dev/null +++ b/src/server/api/endpoints/i/update-mobile-home.ts @@ -0,0 +1,32 @@ +import $ from 'cafy'; +import User from '../../../../models/user'; +import { publishMainStream } from '../../../../services/stream'; +import define from '../../define'; + +export const meta = { + requireCredential: true, + + secure: true, + + params: { + home: { + validator: $.arr($.obj({ + name: $.str, + id: $.str, + data: $.obj() + }).strict()) + } + } +}; + +export default define(meta, async (ps, user) => { + await User.update(user._id, { + $set: { + 'clientSettings.mobileHome': ps.home + } + }); + + publishMainStream(user._id, 'mobileHomeUpdated', ps.home); + + return; +}); diff --git a/src/server/api/endpoints/i/update-widget.ts b/src/server/api/endpoints/i/update-widget.ts new file mode 100644 index 0000000000..67d342278d --- /dev/null +++ b/src/server/api/endpoints/i/update-widget.ts @@ -0,0 +1,88 @@ +import $ from 'cafy'; +import User from '../../../../models/user'; +import { publishMainStream } from '../../../../services/stream'; +import define from '../../define'; + +export const meta = { + requireCredential: true, + + secure: true, + + params: { + id: { + validator: $.str + }, + + data: { + validator: $.obj() + } + } +}; + +export default define(meta, async (ps, user) => { + if (ps.id == null && ps.data == null) throw new Error('you need to set id and data params if home param unset'); + + let widget; + + //#region Desktop home + if (widget == null && user.clientSettings.home) { + const desktopHome = user.clientSettings.home; + widget = desktopHome.find((w: any) => w.id == ps.id); + if (widget) { + widget.data = ps.data; + + await User.update(user._id, { + $set: { + 'clientSettings.home': desktopHome + } + }); + } + } + //#endregion + + //#region Mobile home + if (widget == null && user.clientSettings.mobileHome) { + const mobileHome = user.clientSettings.mobileHome; + widget = mobileHome.find((w: any) => w.id == ps.id); + if (widget) { + widget.data = ps.data; + + await User.update(user._id, { + $set: { + 'clientSettings.mobileHome': mobileHome + } + }); + } + } + //#endregion + + //#region Deck + if (widget == null && user.clientSettings.deck && user.clientSettings.deck.columns) { + const deck = user.clientSettings.deck; + for (const c of deck.columns.filter((c: any) => c.type == 'widgets')) { + for (const w of c.widgets.filter((w: any) => w.id == ps.id)) { + widget = w; + } + } + if (widget) { + widget.data = ps.data; + + await User.update(user._id, { + $set: { + 'clientSettings.deck': deck + } + }); + } + } + //#endregion + + if (widget) { + publishMainStream(user._id, 'widgetUpdated', { + id: ps.id, data: ps.data + }); + + return; + } else { + throw new Error('widget not found'); + } +}); diff --git a/src/server/api/endpoints/i/update_client_setting.ts b/src/server/api/endpoints/i/update_client_setting.ts deleted file mode 100644 index 79cd04e169..0000000000 --- a/src/server/api/endpoints/i/update_client_setting.ts +++ /dev/null @@ -1,37 +0,0 @@ -import $ from 'cafy'; -import User from '../../../../models/user'; -import { publishMainStream } from '../../../../services/stream'; -import define from '../../define'; - -export const meta = { - requireCredential: true, - - secure: true, - - params: { - name: { - validator: $.str - }, - - value: { - validator: $.nullable.any - } - } -}; - -export default define(meta, async (ps, user) => { - const x: any = {}; - x[`clientSettings.${ps.name}`] = ps.value; - - await User.update(user._id, { - $set: x - }); - - // Publish event - publishMainStream(user._id, 'clientSettingUpdated', { - key: ps.name, - value: ps.value - }); - - return; -}); diff --git a/src/server/api/endpoints/i/update_email.ts b/src/server/api/endpoints/i/update_email.ts deleted file mode 100644 index c90462d850..0000000000 --- a/src/server/api/endpoints/i/update_email.ts +++ /dev/null @@ -1,100 +0,0 @@ -import $ from 'cafy'; -import User, { pack } from '../../../../models/user'; -import { publishMainStream } from '../../../../services/stream'; -import define from '../../define'; -import * as nodemailer from 'nodemailer'; -import fetchMeta from '../../../../misc/fetch-meta'; -import rndstr from 'rndstr'; -import config from '../../../../config'; -import * as ms from 'ms'; -import * as bcrypt from 'bcryptjs'; -import { apiLogger } from '../../logger'; - -export const meta = { - requireCredential: true, - - secure: true, - - limit: { - duration: ms('1hour'), - max: 3 - }, - - params: { - password: { - validator: $.str - }, - - email: { - validator: $.optional.nullable.str - }, - } -}; - -export default define(meta, async (ps, user) => { - // Compare password - const same = await bcrypt.compare(ps.password, user.password); - - if (!same) { - throw new Error('incorrect password'); - } - - await User.update(user._id, { - $set: { - email: ps.email, - emailVerified: false, - emailVerifyCode: null - } - }); - - const iObj = await pack(user._id, user, { - detail: true, - includeSecrets: true - }); - - // Publish meUpdated event - publishMainStream(user._id, 'meUpdated', iObj); - - if (ps.email != null) { - const code = rndstr('a-z0-9', 16); - - await User.update(user._id, { - $set: { - emailVerifyCode: code - } - }); - - const meta = await fetchMeta(); - - const enableAuth = meta.smtpUser != null && meta.smtpUser !== ''; - - const transporter = nodemailer.createTransport({ - host: meta.smtpHost, - port: meta.smtpPort, - secure: meta.smtpSecure, - ignoreTLS: !enableAuth, - auth: enableAuth ? { - user: meta.smtpUser, - pass: meta.smtpPass - } : undefined - }); - - const link = `${config.url}/verify-email/${code}`; - - transporter.sendMail({ - from: meta.email, - to: ps.email, - subject: meta.name, - text: `To verify email, please click this link: ${link}` - }, (error, info) => { - if (error) { - apiLogger.error(error); - return; - } - - apiLogger.info('Message sent: %s', info.messageId); - }); - } - - return iObj; -}); diff --git a/src/server/api/endpoints/i/update_home.ts b/src/server/api/endpoints/i/update_home.ts deleted file mode 100644 index e2c319887f..0000000000 --- a/src/server/api/endpoints/i/update_home.ts +++ /dev/null @@ -1,33 +0,0 @@ -import $ from 'cafy'; -import User from '../../../../models/user'; -import { publishMainStream } from '../../../../services/stream'; -import define from '../../define'; - -export const meta = { - requireCredential: true, - - secure: true, - - params: { - home: { - validator: $.arr($.obj({ - name: $.str, - id: $.str, - place: $.str, - data: $.obj() - }).strict()) - } - } -}; - -export default define(meta, async (ps, user) => { - await User.update(user._id, { - $set: { - 'clientSettings.home': ps.home - } - }); - - publishMainStream(user._id, 'homeUpdated', ps.home); - - return; -}); diff --git a/src/server/api/endpoints/i/update_mobile_home.ts b/src/server/api/endpoints/i/update_mobile_home.ts deleted file mode 100644 index 642e2b3e09..0000000000 --- a/src/server/api/endpoints/i/update_mobile_home.ts +++ /dev/null @@ -1,32 +0,0 @@ -import $ from 'cafy'; -import User from '../../../../models/user'; -import { publishMainStream } from '../../../../services/stream'; -import define from '../../define'; - -export const meta = { - requireCredential: true, - - secure: true, - - params: { - home: { - validator: $.arr($.obj({ - name: $.str, - id: $.str, - data: $.obj() - }).strict()) - } - } -}; - -export default define(meta, async (ps, user) => { - await User.update(user._id, { - $set: { - 'clientSettings.mobileHome': ps.home - } - }); - - publishMainStream(user._id, 'mobileHomeUpdated', ps.home); - - return; -}); diff --git a/src/server/api/endpoints/i/update_widget.ts b/src/server/api/endpoints/i/update_widget.ts deleted file mode 100644 index 67d342278d..0000000000 --- a/src/server/api/endpoints/i/update_widget.ts +++ /dev/null @@ -1,88 +0,0 @@ -import $ from 'cafy'; -import User from '../../../../models/user'; -import { publishMainStream } from '../../../../services/stream'; -import define from '../../define'; - -export const meta = { - requireCredential: true, - - secure: true, - - params: { - id: { - validator: $.str - }, - - data: { - validator: $.obj() - } - } -}; - -export default define(meta, async (ps, user) => { - if (ps.id == null && ps.data == null) throw new Error('you need to set id and data params if home param unset'); - - let widget; - - //#region Desktop home - if (widget == null && user.clientSettings.home) { - const desktopHome = user.clientSettings.home; - widget = desktopHome.find((w: any) => w.id == ps.id); - if (widget) { - widget.data = ps.data; - - await User.update(user._id, { - $set: { - 'clientSettings.home': desktopHome - } - }); - } - } - //#endregion - - //#region Mobile home - if (widget == null && user.clientSettings.mobileHome) { - const mobileHome = user.clientSettings.mobileHome; - widget = mobileHome.find((w: any) => w.id == ps.id); - if (widget) { - widget.data = ps.data; - - await User.update(user._id, { - $set: { - 'clientSettings.mobileHome': mobileHome - } - }); - } - } - //#endregion - - //#region Deck - if (widget == null && user.clientSettings.deck && user.clientSettings.deck.columns) { - const deck = user.clientSettings.deck; - for (const c of deck.columns.filter((c: any) => c.type == 'widgets')) { - for (const w of c.widgets.filter((w: any) => w.id == ps.id)) { - widget = w; - } - } - if (widget) { - widget.data = ps.data; - - await User.update(user._id, { - $set: { - 'clientSettings.deck': deck - } - }); - } - } - //#endregion - - if (widget) { - publishMainStream(user._id, 'widgetUpdated', { - id: ps.id, data: ps.data - }); - - return; - } else { - throw new Error('widget not found'); - } -}); diff --git a/src/server/api/endpoints/notes/search-by-tag.ts b/src/server/api/endpoints/notes/search-by-tag.ts new file mode 100644 index 0000000000..0030e2e37a --- /dev/null +++ b/src/server/api/endpoints/notes/search-by-tag.ts @@ -0,0 +1,338 @@ +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'; +import define from '../../define'; +import { getHideUserIds } from '../../common/get-hide-users'; + +export const meta = { + desc: { + 'ja-JP': '指定されたタグが付けられた投稿を取得します。' + }, + + tags: ['notes', 'hashtags'], + + params: { + tag: { + validator: $.optional.str, + desc: { + 'ja-JP': 'タグ' + } + }, + + query: { + validator: $.optional.arr($.arr($.str)), + desc: { + 'ja-JP': 'クエリ' + } + }, + + following: { + validator: $.optional.nullable.bool, + default: null as any + }, + + mute: { + validator: $.optional.str, + default: 'mute_all' + }, + + reply: { + validator: $.optional.nullable.bool, + default: null as any, + desc: { + 'ja-JP': '返信に限定するか否か' + } + }, + + renote: { + validator: $.optional.nullable.bool, + default: null as any, + desc: { + 'ja-JP': 'Renoteに限定するか否か' + } + }, + + withFiles: { + validator: $.optional.bool, + desc: { + 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' + } + }, + + media: { + validator: $.optional.nullable.bool, + default: null as any, + desc: { + 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' + } + }, + + poll: { + validator: $.optional.nullable.bool, + default: null as any, + desc: { + 'ja-JP': 'アンケートが添付された投稿に限定するか否か' + } + }, + + untilId: { + validator: $.optional.type(ID), + transform: transform, + desc: { + 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' + } + }, + + sinceDate: { + validator: $.optional.num, + }, + + untilDate: { + validator: $.optional.num, + }, + + offset: { + validator: $.optional.num.min(0), + default: 0 + }, + + limit: { + validator: $.optional.num.range(1, 30), + default: 10 + }, + }, + + res: { + type: 'array', + items: { + type: 'Note', + }, + }, +}; + +export default define(meta, async (ps, me) => { + const visibleQuery = me == null ? [{ + visibility: { $in: [ 'public', 'home' ] } + }] : [{ + visibility: { $in: [ 'public', 'home' ] } + }, { + // myself (for specified/private) + userId: me._id + }, { + // to me (for specified) + visibleUserIds: { $in: [ me._id ] } + }]; + + const q: any = { + $and: [ps.tag ? { + tagsLower: ps.tag.toLowerCase() + } : { + $or: ps.query.map(tags => ({ + $and: tags.map(t => ({ + tagsLower: t.toLowerCase() + })) + })) + }], + deletedAt: { $exists: false }, + $or: visibleQuery + }; + + const push = (x: any) => q.$and.push(x); + + if (ps.following != null && me != null) { + const ids = await getFriendIds(me._id, false); + push({ + userId: ps.following ? { + $in: ids + } : { + $nin: ids.concat(me._id) + } + }); + } + + if (me != null) { + const hideUserIds = await getHideUserIds(me); + + switch (ps.mute) { + case 'mute_all': + push({ + userId: { + $nin: hideUserIds + }, + '_reply.userId': { + $nin: hideUserIds + }, + '_renote.userId': { + $nin: hideUserIds + } + }); + break; + case 'mute_related': + push({ + '_reply.userId': { + $nin: hideUserIds + }, + '_renote.userId': { + $nin: hideUserIds + } + }); + break; + case 'mute_direct': + push({ + userId: { + $nin: hideUserIds + } + }); + break; + case 'direct_only': + push({ + userId: { + $in: hideUserIds + } + }); + break; + case 'related_only': + push({ + $or: [{ + '_reply.userId': { + $in: hideUserIds + } + }, { + '_renote.userId': { + $in: hideUserIds + } + }] + }); + break; + case 'all_only': + push({ + $or: [{ + userId: { + $in: hideUserIds + } + }, { + '_reply.userId': { + $in: hideUserIds + } + }, { + '_renote.userId': { + $in: hideUserIds + } + }] + }); + break; + } + } + + if (ps.reply != null) { + if (ps.reply) { + push({ + replyId: { + $exists: true, + $ne: null + } + }); + } else { + push({ + $or: [{ + replyId: { + $exists: false + } + }, { + replyId: null + }] + }); + } + } + + if (ps.renote != null) { + if (ps.renote) { + push({ + renoteId: { + $exists: true, + $ne: null + } + }); + } else { + push({ + $or: [{ + renoteId: { + $exists: false + } + }, { + renoteId: null + }] + }); + } + } + + const withFiles = ps.withFiles != null ? ps.withFiles : ps.media; + + if (withFiles) { + push({ + fileIds: { $exists: true, $ne: [] } + }); + } + + if (ps.poll != null) { + if (ps.poll) { + push({ + poll: { + $exists: true, + $ne: null + } + }); + } else { + push({ + $or: [{ + poll: { + $exists: false + } + }, { + poll: null + }] + }); + } + } + + if (ps.untilId) { + push({ + _id: { + $lt: ps.untilId + } + }); + } + + if (ps.sinceDate) { + push({ + createdAt: { + $gt: new Date(ps.sinceDate) + } + }); + } + + if (ps.untilDate) { + push({ + createdAt: { + $lt: new Date(ps.untilDate) + } + }); + } + + if (q.$and.length == 0) { + delete q.$and; + } + + // Search notes + const notes = await Note.find(q, { + sort: { + _id: -1 + }, + limit: ps.limit, + skip: ps.offset + }); + + return await packMany(notes, me); +}); diff --git a/src/server/api/endpoints/notes/search_by_tag.ts b/src/server/api/endpoints/notes/search_by_tag.ts deleted file mode 100644 index 0030e2e37a..0000000000 --- a/src/server/api/endpoints/notes/search_by_tag.ts +++ /dev/null @@ -1,338 +0,0 @@ -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'; -import define from '../../define'; -import { getHideUserIds } from '../../common/get-hide-users'; - -export const meta = { - desc: { - 'ja-JP': '指定されたタグが付けられた投稿を取得します。' - }, - - tags: ['notes', 'hashtags'], - - params: { - tag: { - validator: $.optional.str, - desc: { - 'ja-JP': 'タグ' - } - }, - - query: { - validator: $.optional.arr($.arr($.str)), - desc: { - 'ja-JP': 'クエリ' - } - }, - - following: { - validator: $.optional.nullable.bool, - default: null as any - }, - - mute: { - validator: $.optional.str, - default: 'mute_all' - }, - - reply: { - validator: $.optional.nullable.bool, - default: null as any, - desc: { - 'ja-JP': '返信に限定するか否か' - } - }, - - renote: { - validator: $.optional.nullable.bool, - default: null as any, - desc: { - 'ja-JP': 'Renoteに限定するか否か' - } - }, - - withFiles: { - validator: $.optional.bool, - desc: { - 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' - } - }, - - media: { - validator: $.optional.nullable.bool, - default: null as any, - desc: { - 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' - } - }, - - poll: { - validator: $.optional.nullable.bool, - default: null as any, - desc: { - 'ja-JP': 'アンケートが添付された投稿に限定するか否か' - } - }, - - untilId: { - validator: $.optional.type(ID), - transform: transform, - desc: { - 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' - } - }, - - sinceDate: { - validator: $.optional.num, - }, - - untilDate: { - validator: $.optional.num, - }, - - offset: { - validator: $.optional.num.min(0), - default: 0 - }, - - limit: { - validator: $.optional.num.range(1, 30), - default: 10 - }, - }, - - res: { - type: 'array', - items: { - type: 'Note', - }, - }, -}; - -export default define(meta, async (ps, me) => { - const visibleQuery = me == null ? [{ - visibility: { $in: [ 'public', 'home' ] } - }] : [{ - visibility: { $in: [ 'public', 'home' ] } - }, { - // myself (for specified/private) - userId: me._id - }, { - // to me (for specified) - visibleUserIds: { $in: [ me._id ] } - }]; - - const q: any = { - $and: [ps.tag ? { - tagsLower: ps.tag.toLowerCase() - } : { - $or: ps.query.map(tags => ({ - $and: tags.map(t => ({ - tagsLower: t.toLowerCase() - })) - })) - }], - deletedAt: { $exists: false }, - $or: visibleQuery - }; - - const push = (x: any) => q.$and.push(x); - - if (ps.following != null && me != null) { - const ids = await getFriendIds(me._id, false); - push({ - userId: ps.following ? { - $in: ids - } : { - $nin: ids.concat(me._id) - } - }); - } - - if (me != null) { - const hideUserIds = await getHideUserIds(me); - - switch (ps.mute) { - case 'mute_all': - push({ - userId: { - $nin: hideUserIds - }, - '_reply.userId': { - $nin: hideUserIds - }, - '_renote.userId': { - $nin: hideUserIds - } - }); - break; - case 'mute_related': - push({ - '_reply.userId': { - $nin: hideUserIds - }, - '_renote.userId': { - $nin: hideUserIds - } - }); - break; - case 'mute_direct': - push({ - userId: { - $nin: hideUserIds - } - }); - break; - case 'direct_only': - push({ - userId: { - $in: hideUserIds - } - }); - break; - case 'related_only': - push({ - $or: [{ - '_reply.userId': { - $in: hideUserIds - } - }, { - '_renote.userId': { - $in: hideUserIds - } - }] - }); - break; - case 'all_only': - push({ - $or: [{ - userId: { - $in: hideUserIds - } - }, { - '_reply.userId': { - $in: hideUserIds - } - }, { - '_renote.userId': { - $in: hideUserIds - } - }] - }); - break; - } - } - - if (ps.reply != null) { - if (ps.reply) { - push({ - replyId: { - $exists: true, - $ne: null - } - }); - } else { - push({ - $or: [{ - replyId: { - $exists: false - } - }, { - replyId: null - }] - }); - } - } - - if (ps.renote != null) { - if (ps.renote) { - push({ - renoteId: { - $exists: true, - $ne: null - } - }); - } else { - push({ - $or: [{ - renoteId: { - $exists: false - } - }, { - renoteId: null - }] - }); - } - } - - const withFiles = ps.withFiles != null ? ps.withFiles : ps.media; - - if (withFiles) { - push({ - fileIds: { $exists: true, $ne: [] } - }); - } - - if (ps.poll != null) { - if (ps.poll) { - push({ - poll: { - $exists: true, - $ne: null - } - }); - } else { - push({ - $or: [{ - poll: { - $exists: false - } - }, { - poll: null - }] - }); - } - } - - if (ps.untilId) { - push({ - _id: { - $lt: ps.untilId - } - }); - } - - if (ps.sinceDate) { - push({ - createdAt: { - $gt: new Date(ps.sinceDate) - } - }); - } - - if (ps.untilDate) { - push({ - createdAt: { - $lt: new Date(ps.untilDate) - } - }); - } - - if (q.$and.length == 0) { - delete q.$and; - } - - // Search notes - const notes = await Note.find(q, { - sort: { - _id: -1 - }, - limit: ps.limit, - skip: ps.offset - }); - - return await packMany(notes, me); -}); diff --git a/src/server/api/endpoints/notifications/mark-all-as-read.ts b/src/server/api/endpoints/notifications/mark-all-as-read.ts new file mode 100644 index 0000000000..e5df648285 --- /dev/null +++ b/src/server/api/endpoints/notifications/mark-all-as-read.ts @@ -0,0 +1,41 @@ +import Notification from '../../../../models/notification'; +import { publishMainStream } from '../../../../services/stream'; +import User from '../../../../models/user'; +import define from '../../define'; + +export const meta = { + desc: { + 'ja-JP': '全ての通知を既読にします。', + 'en-US': 'Mark all notifications as read.' + }, + + tags: ['notifications', 'account'], + + requireCredential: true, + + kind: 'notification-write' +}; + +export default define(meta, async (ps, user) => { + // Update documents + await Notification.update({ + notifieeId: user._id, + isRead: false + }, { + $set: { + isRead: true + } + }, { + multi: true + }); + + // Update flag + User.update({ _id: user._id }, { + $set: { + hasUnreadNotification: false + } + }); + + // 全ての通知を読みましたよというイベントを発行 + publishMainStream(user._id, 'readAllNotifications'); +}); diff --git a/src/server/api/endpoints/notifications/mark_all_as_read.ts b/src/server/api/endpoints/notifications/mark_all_as_read.ts deleted file mode 100644 index e5df648285..0000000000 --- a/src/server/api/endpoints/notifications/mark_all_as_read.ts +++ /dev/null @@ -1,41 +0,0 @@ -import Notification from '../../../../models/notification'; -import { publishMainStream } from '../../../../services/stream'; -import User from '../../../../models/user'; -import define from '../../define'; - -export const meta = { - desc: { - 'ja-JP': '全ての通知を既読にします。', - 'en-US': 'Mark all notifications as read.' - }, - - tags: ['notifications', 'account'], - - requireCredential: true, - - kind: 'notification-write' -}; - -export default define(meta, async (ps, user) => { - // Update documents - await Notification.update({ - notifieeId: user._id, - isRead: false - }, { - $set: { - isRead: true - } - }, { - multi: true - }); - - // Update flag - User.update({ _id: user._id }, { - $set: { - hasUnreadNotification: false - } - }); - - // 全ての通知を読みましたよというイベントを発行 - publishMainStream(user._id, 'readAllNotifications'); -}); diff --git a/src/server/api/endpoints/users/get-frequently-replied-users.ts b/src/server/api/endpoints/users/get-frequently-replied-users.ts new file mode 100644 index 0000000000..6f9c746acd --- /dev/null +++ b/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -0,0 +1,115 @@ +import $ from 'cafy'; +import ID, { transform } from '../../../../misc/cafy-id'; +import Note from '../../../../models/note'; +import { pack } from '../../../../models/user'; +import define from '../../define'; +import { maximum } from '../../../../prelude/array'; +import { getHideUserIds } from '../../common/get-hide-users'; +import { ApiError } from '../../error'; +import { getUser } from '../../common/getters'; + +export const meta = { + tags: ['users'], + + requireCredential: false, + + params: { + userId: { + validator: $.type(ID), + transform: transform, + desc: { + 'ja-JP': '対象のユーザーのID', + 'en-US': 'Target user ID' + } + }, + + limit: { + validator: $.optional.num.range(1, 100), + default: 10 + }, + }, + + errors: { + noSuchUser: { + message: 'No such user.', + code: 'NO_SUCH_USER', + id: 'e6965129-7b2a-40a4-bae2-cd84cd434822' + } + } +}; + +export default define(meta, async (ps, me) => { + // Lookup user + const user = await getUser(ps.userId).catch(e => { + if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw e; + }); + + // Fetch recent notes + const recentNotes = await Note.find({ + userId: user._id, + replyId: { + $exists: true, + $ne: null + } + }, { + sort: { + _id: -1 + }, + limit: 1000, + fields: { + _id: false, + replyId: true + } + }); + + // 投稿が少なかったら中断 + if (recentNotes.length === 0) { + return []; + } + + const hideUserIds = await getHideUserIds(me); + hideUserIds.push(user._id); + + const replyTargetNotes = await Note.find({ + _id: { + $in: recentNotes.map(p => p.replyId) + }, + userId: { + $nin: hideUserIds + } + }, { + fields: { + _id: false, + userId: true + } + }); + + const repliedUsers: any = {}; + + // Extract replies from recent notes + for (const userId of replyTargetNotes.map(x => x.userId.toString())) { + if (repliedUsers[userId]) { + repliedUsers[userId]++; + } else { + repliedUsers[userId] = 1; + } + } + + // Calc peak + const peak = maximum(Object.values(repliedUsers)); + + // Sort replies by frequency + const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]); + + // Extract top replied users + const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit); + + // Make replies object (includes weights) + const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({ + user: await pack(user, me, { detail: true }), + weight: repliedUsers[user] / peak + }))); + + return repliesObj; +}); diff --git a/src/server/api/endpoints/users/get_frequently_replied_users.ts b/src/server/api/endpoints/users/get_frequently_replied_users.ts deleted file mode 100644 index 6f9c746acd..0000000000 --- a/src/server/api/endpoints/users/get_frequently_replied_users.ts +++ /dev/null @@ -1,115 +0,0 @@ -import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note from '../../../../models/note'; -import { pack } from '../../../../models/user'; -import define from '../../define'; -import { maximum } from '../../../../prelude/array'; -import { getHideUserIds } from '../../common/get-hide-users'; -import { ApiError } from '../../error'; -import { getUser } from '../../common/getters'; - -export const meta = { - tags: ['users'], - - requireCredential: false, - - params: { - userId: { - validator: $.type(ID), - transform: transform, - desc: { - 'ja-JP': '対象のユーザーのID', - 'en-US': 'Target user ID' - } - }, - - limit: { - validator: $.optional.num.range(1, 100), - default: 10 - }, - }, - - errors: { - noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: 'e6965129-7b2a-40a4-bae2-cd84cd434822' - } - } -}; - -export default define(meta, async (ps, me) => { - // Lookup user - const user = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // Fetch recent notes - const recentNotes = await Note.find({ - userId: user._id, - replyId: { - $exists: true, - $ne: null - } - }, { - sort: { - _id: -1 - }, - limit: 1000, - fields: { - _id: false, - replyId: true - } - }); - - // 投稿が少なかったら中断 - if (recentNotes.length === 0) { - return []; - } - - const hideUserIds = await getHideUserIds(me); - hideUserIds.push(user._id); - - const replyTargetNotes = await Note.find({ - _id: { - $in: recentNotes.map(p => p.replyId) - }, - userId: { - $nin: hideUserIds - } - }, { - fields: { - _id: false, - userId: true - } - }); - - const repliedUsers: any = {}; - - // Extract replies from recent notes - for (const userId of replyTargetNotes.map(x => x.userId.toString())) { - if (repliedUsers[userId]) { - repliedUsers[userId]++; - } else { - repliedUsers[userId] = 1; - } - } - - // Calc peak - const peak = maximum(Object.values(repliedUsers)); - - // Sort replies by frequency - const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]); - - // Extract top replied users - const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit); - - // Make replies object (includes weights) - const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({ - user: await pack(user, me, { detail: true }), - weight: repliedUsers[user] / peak - }))); - - return repliesObj; -}); diff --git a/src/server/api/index.ts b/src/server/api/index.ts index 6c523a9d49..fac57ca06e 100644 --- a/src/server/api/index.ts +++ b/src/server/api/index.ts @@ -51,6 +51,10 @@ for (const endpoint of endpoints) { if (endpoint.meta.requireFile) { router.post(`/${endpoint.name}`, upload.single('file'), handler.bind(null, endpoint)); } else { + if (endpoint.name.includes('-')) { + // 後方互換性のため + router.post(`/${endpoint.name.replace(/\-/g, '_')}`, handler.bind(null, endpoint)); + } router.post(`/${endpoint.name}`, handler.bind(null, endpoint)); } } -- cgit v1.2.3-freya