Improve error handling of API (#4345)

* wip

* wip

* wip

* Update attached_notes.ts

* wip

* Refactor

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update call.ts

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* ✌️

* Fix
This commit is contained in:
syuilo
2019-02-22 11:46:58 +09:00
committed by GitHub
parent fc52e95ad0
commit 2756f553c6
181 changed files with 2010 additions and 1322 deletions

View File

@@ -2,6 +2,8 @@ import $ from 'cafy';
import ID, { transform } from '../../../../misc/cafy-id';
import Note, { packMany, INote } from '../../../../models/note';
import define from '../../define';
import { ApiError } from '../../error';
import { getValiedNote } from '../../common/getters';
export const meta = {
desc: {
@@ -30,19 +32,23 @@ export const meta = {
validator: $.optional.num.min(0),
default: 0
},
},
errors: {
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: 'e1035875-9551-45ec-afa8-1ded1fcb53c8'
}
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Lookup note
const note = await Note.findOne({
_id: ps.noteId
export default define(meta, async (ps, user) => {
const note = await getValiedNote(ps.noteId).catch(e => {
if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw e;
});
if (note === null) {
return rej('note not found');
}
const conversation: INote[] = [];
let i = 0;
@@ -67,5 +73,5 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
await get(note.replyId);
}
res(await packMany(conversation, user));
}));
return await packMany(conversation, user);
});

View File

@@ -8,6 +8,7 @@ import DriveFile, { IDriveFile } from '../../../../models/drive-file';
import create from '../../../../services/note/create';
import define from '../../define';
import fetchMeta from '../../../../misc/fetch-meta';
import { ApiError } from '../../error';
let maxNoteTextLength = 1000;
@@ -180,10 +181,42 @@ export const meta = {
}
}
}
},
errors: {
noSuchRenoteTarget: {
message: 'No such renote target.',
code: 'NO_SUCH_RENOTE_TARGET',
id: 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4'
},
cannotReRenote: {
message: 'You can not Renote a pure Renote.',
code: 'CANNOT_RENOTE_TO_A_PURE_RENOTE',
id: 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a'
},
noSuchReplyTarget: {
message: 'No such reply target.',
code: 'NO_SUCH_REPLY_TARGET',
id: '749ee0f6-d3da-459a-bf02-282e2da4292c'
},
cannotReplyToPureRenote: {
message: 'You can not reply to a pure Renote.',
code: 'CANNOT_REPLY_TO_A_PURE_RENOTE',
id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15'
},
contentRequired: {
message: 'Content required. You need to set text, fileIds, renoteId or poll.',
code: 'CONTENT_REQUIRED',
id: '6f57e42b-c348-439b-bc45-993995cc515a'
},
}
};
export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user, app) => {
let visibleUsers: IUser[] = [];
if (ps.visibleUserIds) {
visibleUsers = await Promise.all(ps.visibleUserIds.map(id => User.findOne({
@@ -212,9 +245,9 @@ export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
});
if (renote == null) {
return rej('renoteee is not found');
throw new ApiError(meta.errors.noSuchRenoteTarget);
} else if (renote.renoteId && !renote.text && !renote.fileIds) {
return rej('cannot renote to renote');
throw new ApiError(meta.errors.cannotReRenote);
}
}
@@ -226,12 +259,12 @@ export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
});
if (reply === null) {
return rej('in reply to note is not found');
throw new ApiError(meta.errors.noSuchReplyTarget);
}
// 返信対象が引用でないRenoteだったらエラー
if (reply.renoteId && !reply.text && !reply.fileIds) {
return rej('cannot reply to renote');
throw new ApiError(meta.errors.cannotReplyToPureRenote);
}
}
@@ -245,7 +278,7 @@ export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
// テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー
if (!(ps.text || files.length || renote || ps.poll)) {
return rej('text, fileIds, renoteId or poll is required');
throw new ApiError(meta.errors.contentRequired);
}
// 後方互換性のため
@@ -254,7 +287,7 @@ export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
}
// 投稿を作成
create(user, {
const note = await create(user, {
createdAt: new Date(),
files: files,
poll: ps.poll,
@@ -271,14 +304,9 @@ export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
apHashtags: ps.noExtractHashtags ? [] : undefined,
apEmojis: ps.noExtractEmojis ? [] : undefined,
geo: ps.geo
})
.then(note => pack(note, user))
.then(noteObj => {
res({
createdNote: noteObj
});
})
.catch(e => {
rej(e);
});
}));
return {
createdNote: await pack(note, user)
};
});

View File

@@ -1,10 +1,11 @@
import $ from 'cafy';
import ID, { transform } from '../../../../misc/cafy-id';
import Note from '../../../../models/note';
import deleteNote from '../../../../services/note/delete';
import User from '../../../../models/user';
import define from '../../define';
import * as ms from 'ms';
import { getValiedNote } from '../../common/getters';
import { ApiError } from '../../error';
export const meta = {
stability: 'stable',
@@ -33,24 +34,32 @@ export const meta = {
'en-US': 'Target note ID.'
}
}
},
errors: {
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: '490be23f-8c1f-4796-819f-94cb4f9d1630'
},
accessDenied: {
message: 'Access denied.',
code: 'ACCESS_DENIED',
id: 'fe8d7103-0ea8-4ec3-814d-f8b401dc69e9'
}
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Fetch note
const note = await Note.findOne({
_id: ps.noteId
export default define(meta, async (ps, user) => {
const note = await getValiedNote(ps.noteId).catch(e => {
if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw e;
});
if (note === null) {
return rej('note not found');
}
if (!user.isAdmin && !user.isModerator && !note.userId.equals(user._id)) {
return rej('access denied');
throw new ApiError(meta.errors.accessDenied);
}
await deleteNote(await User.findOne({ _id: note.userId }), note);
res();
}));
});

View File

@@ -3,6 +3,7 @@ import ID, { transform } from '../../../../../misc/cafy-id';
import Favorite from '../../../../../models/favorite';
import Note from '../../../../../models/note';
import define from '../../../define';
import { ApiError } from '../../../error';
export const meta = {
stability: 'stable',
@@ -25,17 +26,31 @@ export const meta = {
'en-US': 'Target note ID.'
}
}
},
errors: {
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: '6dd26674-e060-4816-909a-45ba3f4da458'
},
alreadyFavorited: {
message: 'The note has already been marked as a favorite.',
code: 'ALREADY_FAVORITED',
id: 'a402c12b-34dd-41d2-97d8-4d2ffd96a1a6'
},
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
// Get favoritee
const note = await Note.findOne({
_id: ps.noteId
});
if (note === null) {
return rej('note not found');
throw new ApiError(meta.errors.noSuchNote);
}
// if already favorited
@@ -45,7 +60,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
});
if (exist !== null) {
return rej('already favorited');
throw new ApiError(meta.errors.alreadyFavorited);
}
// Create favorite
@@ -55,6 +70,5 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
userId: user._id
});
// Send response
res();
}));
return;
});

View File

@@ -3,6 +3,7 @@ import ID, { transform } from '../../../../../misc/cafy-id';
import Favorite from '../../../../../models/favorite';
import Note from '../../../../../models/note';
import define from '../../../define';
import { ApiError } from '../../../error';
export const meta = {
stability: 'stable',
@@ -25,17 +26,31 @@ export const meta = {
'en-US': 'Target note ID.'
}
}
},
errors: {
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: '80848a2c-398f-4343-baa9-df1d57696c56'
},
notFavorited: {
message: 'You have not marked that note a favorite.',
code: 'NOT_FAVORITED',
id: 'b625fc69-635e-45e9-86f4-dbefbef35af5'
},
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
// Get favoritee
const note = await Note.findOne({
_id: ps.noteId
});
if (note === null) {
return rej('note not found');
throw new ApiError(meta.errors.noSuchNote);
}
// if already favorited
@@ -45,7 +60,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
});
if (exist === null) {
return rej('already not favorited');
throw new ApiError(meta.errors.notFavorited);
}
// Delete favorite
@@ -53,6 +68,5 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
_id: exist._id
});
// Send response
res();
}));
return;
});

View File

@@ -23,29 +23,28 @@ export const meta = {
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
const day = 1000 * 60 * 60 * 24 * 2;
const hideUserIds = await getHideUserIds(user);
const notes = await Note
.find({
createdAt: {
$gt: new Date(Date.now() - day)
},
deletedAt: null,
visibility: { $in: ['public', 'home'] },
'_user.host': null,
...(hideUserIds && hideUserIds.length > 0 ? { userId: { $nin: hideUserIds } } : {})
}, {
limit: ps.limit,
sort: {
score: -1
},
hint: {
score: -1
}
});
const notes = await Note.find({
createdAt: {
$gt: new Date(Date.now() - day)
},
deletedAt: null,
visibility: { $in: ['public', 'home'] },
'_user.host': null,
...(hideUserIds && hideUserIds.length > 0 ? { userId: { $nin: hideUserIds } } : {})
}, {
limit: ps.limit,
sort: {
score: -1
},
hint: {
score: -1
}
});
res(await packMany(notes, user));
}));
return await packMany(notes, user);
});

View File

@@ -3,9 +3,9 @@ import ID, { transform } from '../../../../misc/cafy-id';
import Note from '../../../../models/note';
import { packMany } from '../../../../models/note';
import define from '../../define';
import { countIf } from '../../../../prelude/array';
import fetchMeta from '../../../../misc/fetch-meta';
import { getHideUserIds } from '../../common/get-hide-users';
import { ApiError } from '../../error';
export const meta = {
desc: {
@@ -49,22 +49,25 @@ export const meta = {
untilDate: {
validator: $.optional.num
},
},
errors: {
gtlDisabled: {
message: 'Global timeline has been disabled.',
code: 'GTL_DISABLED',
id: '0332fc13-6ab2-4427-ae80-a9fadffd1a6b'
},
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
const meta = await fetchMeta();
if (meta.disableGlobalTimeline) {
export default define(meta, async (ps, user) => {
const m = await fetchMeta();
if (m.disableGlobalTimeline) {
if (user == null || (!user.isAdmin && !user.isModerator)) {
return rej('global timeline disabled');
throw new ApiError(meta.errors.gtlDisabled);
}
}
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) {
return rej('only one of sinceId, untilId, sinceDate, untilDate can be specified');
}
// 隠すユーザーを取得
const hideUserIds = await getHideUserIds(user);
@@ -123,11 +126,10 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
}
//#endregion
const timeline = await Note
.find(query, {
limit: ps.limit,
sort: sort
});
const timeline = await Note.find(query, {
limit: ps.limit,
sort: sort
});
res(await packMany(timeline, user));
}));
return await packMany(timeline, user);
});

View File

@@ -4,10 +4,10 @@ import Note from '../../../../models/note';
import { getFriends } from '../../common/get-friends';
import { packMany } from '../../../../models/note';
import define from '../../define';
import { countIf } from '../../../../prelude/array';
import fetchMeta from '../../../../misc/fetch-meta';
import activeUsersChart from '../../../../services/chart/active-users';
import { getHideUserIds } from '../../common/get-hide-users';
import { ApiError } from '../../error';
export const meta = {
desc: {
@@ -90,18 +90,21 @@ export const meta = {
'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
}
},
},
errors: {
stlDisabled: {
message: 'Social timeline has been disabled.',
code: 'STL_DISABLED',
id: '620763f4-f621-4533-ab33-0577a1a3c342'
},
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
const meta = await fetchMeta();
if (meta.disableLocalTimeline && !user.isAdmin && !user.isModerator) {
return rej('local timeline disabled');
}
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) {
return rej('only one of sinceId, untilId, sinceDate, untilDate can be specified');
export default define(meta, async (ps, user) => {
const m = await fetchMeta();
if (m.disableLocalTimeline && !user.isAdmin && !user.isModerator) {
throw new ApiError(meta.errors.stlDisabled);
}
const [followings, hideUserIds] = await Promise.all([
@@ -266,13 +269,12 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
}
//#endregion
const timeline = await Note
.find(query, {
limit: ps.limit,
sort: sort
});
res(await packMany(timeline, user));
const timeline = await Note.find(query, {
limit: ps.limit,
sort: sort
});
activeUsersChart.update(user);
}));
return await packMany(timeline, user);
});

View File

@@ -3,10 +3,10 @@ import ID, { transform } from '../../../../misc/cafy-id';
import Note from '../../../../models/note';
import { packMany } from '../../../../models/note';
import define from '../../define';
import { countIf } from '../../../../prelude/array';
import fetchMeta from '../../../../misc/fetch-meta';
import activeUsersChart from '../../../../services/chart/active-users';
import { getHideUserIds } from '../../common/get-hide-users';
import { ApiError } from '../../error';
export const meta = {
desc: {
@@ -65,22 +65,25 @@ export const meta = {
untilDate: {
validator: $.optional.num,
},
},
errors: {
ltlDisabled: {
message: 'Local timeline has been disabled.',
code: 'LTL_DISABLED',
id: '45a6eb02-7695-4393-b023-dd3be9aaaefd'
},
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
const meta = await fetchMeta();
if (meta.disableLocalTimeline) {
export default define(meta, async (ps, user) => {
const m = await fetchMeta();
if (m.disableLocalTimeline) {
if (user == null || (!user.isAdmin && !user.isModerator)) {
return rej('local timeline disabled');
throw new ApiError(meta.errors.ltlDisabled);
}
}
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) {
return rej('only one of sinceId, untilId, sinceDate, untilDate can be specified');
}
// 隠すユーザーを取得
const hideUserIds = await getHideUserIds(user);
@@ -157,15 +160,14 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
}
//#endregion
const timeline = await Note
.find(query, {
limit: ps.limit,
sort: sort
});
res(await packMany(timeline, user));
const timeline = await Note.find(query, {
limit: ps.limit,
sort: sort
});
if (user) {
activeUsersChart.update(user);
}
}));
return await packMany(timeline, user);
});

View File

@@ -42,12 +42,7 @@ export const meta = {
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Check if both of sinceId and untilId is specified
if (ps.sinceId && ps.untilId) {
return rej('cannot set sinceId and untilId');
}
export default define(meta, async (ps, user) => {
// フォローを取得
const followings = await getFriends(user._id);
@@ -131,15 +126,14 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
};
}
const mentions = await Note
.find(query, {
limit: ps.limit,
sort: sort
});
res(await packMany(mentions, user));
const mentions = await Note.find(query, {
limit: ps.limit,
sort: sort
});
for (const note of mentions) {
read(user._id, note._id);
}
}));
return await packMany(mentions, user);
});

View File

@@ -25,7 +25,7 @@ export const meta = {
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
// Get votes
const votes = await Vote.find({
userId: user._id
@@ -41,29 +41,28 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// 隠すユーザーを取得
const hideUserIds = await getHideUserIds(user);
const notes = await Note
.find({
'_user.host': null,
_id: {
$nin: nin
},
userId: {
$ne: user._id,
$nin: hideUserIds
},
poll: {
$exists: true,
$ne: null
}
}, {
limit: ps.limit,
skip: ps.offset,
sort: {
_id: -1
}
});
const notes = await Note.find({
'_user.host': null,
_id: {
$nin: nin
},
userId: {
$ne: user._id,
$nin: hideUserIds
},
poll: {
$exists: true,
$ne: null
}
}, {
limit: ps.limit,
skip: ps.offset,
sort: {
_id: -1
}
});
res(await Promise.all(notes.map(note => pack(note, user, {
return await Promise.all(notes.map(note => pack(note, user, {
detail: true
}))));
}));
})));
});

View File

@@ -9,6 +9,7 @@ import notify from '../../../../../services/create-notification';
import define from '../../../define';
import createNote from '../../../../../services/note/create';
import User from '../../../../../models/user';
import { ApiError } from '../../../error';
export const meta = {
desc: {
@@ -33,24 +34,52 @@ export const meta = {
choice: {
validator: $.num
},
},
errors: {
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: 'ecafbd2e-c283-4d6d-aecb-1a0a33b75396'
},
noPoll: {
message: 'The note does not attach a poll.',
code: 'NO_POLL',
id: '5f979967-52d9-4314-a911-1c673727f92f'
},
invalidChoice: {
message: 'Choice ID is invalid.',
code: 'INVALID_CHOICE',
id: 'e0cc9a04-f2e8-41e4-a5f1-4127293260cc'
},
alreadyVoted: {
message: 'You have already voted.',
code: 'ALREADY_VOTED',
id: '0963fc77-efac-419b-9424-b391608dc6d8'
},
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
// Get votee
const note = await Note.findOne({
_id: ps.noteId
});
if (note === null) {
return rej('note not found');
throw new ApiError(meta.errors.noSuchNote);
}
if (note.poll == null) {
return rej('poll not found');
throw new ApiError(meta.errors.noPoll);
}
if (!note.poll.choices.some(x => x.id == ps.choice)) return rej('invalid choice param');
if (!note.poll.choices.some(x => x.id == ps.choice)) {
throw new ApiError(meta.errors.invalidChoice);
}
// if already voted
const exist = await Vote.findOne({
@@ -59,7 +88,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
});
if (exist !== null) {
return rej('already voted');
throw new ApiError(meta.errors.alreadyVoted);
}
// Create vote
@@ -70,9 +99,6 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
choice: ps.choice
});
// Send response
res();
const inc: any = {};
inc[`poll.choices.${note.poll.choices.findIndex(c => c.id == ps.choice)}.votes`] = 1;
@@ -132,4 +158,6 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
visibleUsers: [ pollOwner ],
});
}
}));
return;
});

View File

@@ -1,8 +1,9 @@
import $ from 'cafy';
import ID, { transform } from '../../../../misc/cafy-id';
import Note from '../../../../models/note';
import Reaction, { pack } from '../../../../models/note-reaction';
import define from '../../define';
import { getValiedNote } from '../../common/getters';
import { ApiError } from '../../error';
export const meta = {
desc: {
@@ -41,24 +42,23 @@ export const meta = {
validator: $.optional.type(ID),
transform: transform,
},
},
errors: {
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: '263fff3d-d0e1-4af4-bea7-8408059b451a'
}
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Check if both of sinceId and untilId is specified
if (ps.sinceId && ps.untilId) {
return rej('cannot set sinceId and untilId');
}
// Lookup note
const note = await Note.findOne({
_id: ps.noteId
export default define(meta, async (ps, user) => {
const note = await getValiedNote(ps.noteId).catch(e => {
if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw e;
});
if (note === null) {
return rej('note not found');
}
const query = {
noteId: note._id
} as any;
@@ -78,13 +78,11 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
};
}
const reactions = await Reaction
.find(query, {
limit: ps.limit,
skip: ps.offset,
sort: sort
});
const reactions = await Reaction.find(query, {
limit: ps.limit,
skip: ps.offset,
sort: sort
});
// Serialize
res(await Promise.all(reactions.map(reaction => pack(reaction, user))));
}));
return await Promise.all(reactions.map(reaction => pack(reaction, user)));
});

View File

@@ -1,11 +1,10 @@
import * as mongo from 'mongodb';
import $ from 'cafy';
import ID, { transform } from '../../../../../misc/cafy-id';
import createReaction from '../../../../../services/note/reaction/create';
import { validateReaction } from '../../../../../models/note-reaction';
import define from '../../../define';
import { IUser } from '../../../../../models/user';
import { getValiedNote } from '../../../common/getters';
import { ApiError } from '../../../error';
export const meta = {
stability: 'stable',
@@ -34,15 +33,38 @@ export const meta = {
'ja-JP': 'リアクションの種類'
}
}
},
errors: {
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: '033d0620-5bfe-4027-965d-980b0c85a3ea'
},
isMyNote: {
message: 'You can not react to your own notes.',
code: 'IS_MY_NOTE',
id: '7eeb9714-b047-43b5-b559-7b1b72810f53'
},
alreadyReacted: {
message: 'You are already reacting to that note.',
code: 'ALREADY_REACTED',
id: '71efcf98-86d6-4e2b-b2ad-9d032369366b'
}
}
};
export default define(meta, (ps, user) => new Promise((res, rej) => {
createReactionById(user, ps.noteId, ps.reaction)
.then(r => res(r)).catch(e => rej(e));
}));
async function createReactionById(user: IUser, noteId: mongo.ObjectID, reaction: string) {
const note = await getValiedNote(noteId);
await createReaction(user, note, reaction);
}
export default define(meta, async (ps, user) => {
const note = await getValiedNote(ps.noteId).catch(e => {
if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw e;
});
await createReaction(user, note, ps.reaction).catch(e => {
if (e.id === '2d8e7297-1873-4c00-8404-792c68d7bef0') throw new ApiError(meta.errors.isMyNote);
if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError(meta.errors.alreadyReacted);
throw e;
});
return;
});

View File

@@ -1,11 +1,10 @@
import * as mongo from 'mongodb';
import $ from 'cafy';
import ID, { transform } from '../../../../../misc/cafy-id';
import define from '../../../define';
import * as ms from 'ms';
import deleteReaction from '../../../../../services/note/reaction/delete';
import { IUser } from '../../../../../models/user';
import { getValiedNote } from '../../../common/getters';
import { ApiError } from '../../../error';
export const meta = {
desc: {
@@ -32,15 +31,30 @@ export const meta = {
'en-US': 'Target note ID'
}
},
},
errors: {
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: '764d9fce-f9f2-4a0e-92b1-6ceac9a7ad37'
},
notReacted: {
message: 'You are not reacting to that note.',
code: 'NOT_REACTED',
id: '92f4426d-4196-4125-aa5b-02943e2ec8fc'
},
}
};
export default define(meta, (ps, user) => new Promise((res, rej) => {
deleteReactionById(user, ps.noteId)
.then(r => res(r)).catch(e => rej(e));
}));
async function deleteReactionById(user: IUser, noteId: mongo.ObjectID) {
const note = await getValiedNote(noteId);
await deleteReaction(user, note);
}
export default define(meta, async (ps, user) => {
const note = await getValiedNote(ps.noteId).catch(e => {
if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw e;
});
await deleteReaction(user, note).catch(e => {
if (e.id === '60527ec9-b4cb-4a88-a6bd-32d3ad26817d') throw new ApiError(meta.errors.notReacted);
throw e;
});
});

View File

@@ -2,6 +2,8 @@ import $ from 'cafy';
import ID, { transform } from '../../../../misc/cafy-id';
import Note, { packMany } from '../../../../models/note';
import define from '../../define';
import { getValiedNote } from '../../common/getters';
import { ApiError } from '../../error';
export const meta = {
desc: {
@@ -35,24 +37,23 @@ export const meta = {
validator: $.optional.type(ID),
transform: transform,
}
},
errors: {
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: '12908022-2e21-46cd-ba6a-3edaf6093f46'
}
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Check if both of sinceId and untilId is specified
if (ps.sinceId && ps.untilId) {
return rej('cannot set sinceId and untilId');
}
// Lookup note
const note = await Note.findOne({
_id: ps.noteId
export default define(meta, async (ps, user) => {
const note = await getValiedNote(ps.noteId).catch(e => {
if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw e;
});
if (note === null) {
return rej('note not found');
}
const sort = {
_id: -1
};
@@ -72,11 +73,10 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
};
}
const renotes = await Note
.find(query, {
limit: ps.limit,
sort: sort
});
const renotes = await Note.find(query, {
limit: ps.limit,
sort: sort
});
res(await packMany(renotes, user));
}));
return await packMany(renotes, user);
});

View File

@@ -35,7 +35,7 @@ export const meta = {
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
const [followings, hideUserIds] = await Promise.all([
// フォローを取得
// Fetch following
@@ -85,5 +85,5 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
skip: ps.offset
});
res(await packMany(notes, user));
}));
return await packMany(notes, user);
});

View File

@@ -4,7 +4,7 @@ import Note from '../../../../models/note';
import { packMany } from '../../../../models/note';
import es from '../../../../db/elasticsearch';
import define from '../../define';
import { apiLogger } from '../../logger';
import { ApiError } from '../../error';
export const meta = {
desc: {
@@ -28,13 +28,21 @@ export const meta = {
validator: $.optional.num.min(0),
default: 0
}
},
errors: {
searchingNotAvailable: {
message: 'Searching not available.',
code: 'SEARCHING_NOT_AVAILABLE',
id: '7ee9c119-16a1-479f-a6fd-6fab00ed946f'
}
}
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
if (es == null) return rej('searching not available');
export default define(meta, async (ps, me) => {
if (es == null) throw new ApiError(meta.errors.searchingNotAvailable);
es.search({
const response = await es.search({
index: 'misskey',
type: 'note',
body: {
@@ -51,29 +59,24 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
{ _doc: 'desc' }
]
}
}, async (error, response) => {
if (error) {
apiLogger.error(error);
return res(500);
}
if (response.hits.total === 0) {
return res([]);
}
const hits = response.hits.hits.map(hit => new mongo.ObjectID(hit._id));
// Fetch found notes
const notes = await Note.find({
_id: {
$in: hits
}
}, {
sort: {
_id: -1
}
});
res(await packMany(notes, me));
});
}));
if (response.hits.total === 0) {
return [];
}
const hits = response.hits.hits.map(hit => new mongo.ObjectID(hit._id));
// Fetch found notes
const notes = await Note.find({
_id: {
$in: hits
}
}, {
sort: {
_id: -1
}
});
return await packMany(notes, me);
});

View File

@@ -103,7 +103,7 @@ export const meta = {
}
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
export default define(meta, async (ps, me) => {
const visibleQuery = me == null ? [{
visibility: { $in: [ 'public', 'home' ] }
}] : [{
@@ -317,15 +317,13 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
}
// Search notes
const notes = await Note
.find(q, {
sort: {
_id: -1
},
limit: ps.limit,
skip: ps.offset
});
const notes = await Note.find(q, {
sort: {
_id: -1
},
limit: ps.limit,
skip: ps.offset
});
// Serialize
res(await packMany(notes, me));
}));
return await packMany(notes, me);
});

View File

@@ -1,7 +1,9 @@
import $ from 'cafy';
import ID, { transform } from '../../../../misc/cafy-id';
import Note, { pack } from '../../../../models/note';
import { pack } from '../../../../models/note';
import define from '../../define';
import { getValiedNote } from '../../common/getters';
import { ApiError } from '../../error';
export const meta = {
stability: 'stable',
@@ -22,21 +24,24 @@ export const meta = {
'en-US': 'Target note ID.'
}
}
},
errors: {
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: '24fcbfc6-2e37-42b6-8388-c29b3861a08d'
}
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Get note
const note = await Note.findOne({
_id: ps.noteId
export default define(meta, async (ps, user) => {
const note = await getValiedNote(ps.noteId).catch(e => {
if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw e;
});
if (note === null) {
return rej('note not found');
}
// Serialize
res(await pack(note, user, {
return await pack(note, user, {
detail: true
}));
}));
});
});

View File

@@ -26,7 +26,7 @@ export const meta = {
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
const [favorite, watching] = await Promise.all([
Favorite.count({
userId: user._id,
@@ -42,8 +42,8 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
})
]);
res({
return {
isFavorited: favorite !== 0,
isWatching: watching !== 0
});
}));
};
});

View File

@@ -4,7 +4,6 @@ import Note from '../../../../models/note';
import { getFriends } from '../../common/get-friends';
import { packMany } from '../../../../models/note';
import define from '../../define';
import { countIf } from '../../../../prelude/array';
import activeUsersChart from '../../../../services/chart/active-users';
import { getHideUserIds } from '../../common/get-hide-users';
@@ -95,13 +94,7 @@ export const meta = {
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) {
rej('only one of sinceId, untilId, sinceDate, untilDate can be specified');
return;
}
export default define(meta, async (ps, user) => {
const [followings, hideUserIds] = await Promise.all([
// フォローを取得
// Fetch following
@@ -255,15 +248,12 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
}
//#endregion
// Issue query
const timeline = await Note
.find(query, {
limit: ps.limit,
sort: sort
});
// Serialize
res(await packMany(timeline, user));
const timeline = await Note.find(query, {
limit: ps.limit,
sort: sort
});
activeUsersChart.update(user);
}));
return await packMany(timeline, user);
});

View File

@@ -6,6 +6,7 @@ import UserList from '../../../../models/user-list';
import define from '../../define';
import { getFriends } from '../../common/get-friends';
import { getHideUserIds } from '../../common/get-hide-users';
import { ApiError } from '../../error';
export const meta = {
desc: {
@@ -99,10 +100,18 @@ export const meta = {
'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
}
},
},
errors: {
noSuchList: {
message: 'No such list.',
code: 'NO_SUCH_LIST',
id: '8fb1fbd5-e476-4c37-9fb0-43d55b63a2ff'
}
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
const [list, followings, hideUserIds] = await Promise.all([
// リストを取得
// Fetch the list
@@ -120,13 +129,11 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
]);
if (list == null) {
rej('list not found');
return;
throw new ApiError(meta.errors.noSuchList);
}
if (list.userIds.length == 0) {
res([]);
return;
return [];
}
//#region Construct query
@@ -274,13 +281,10 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
}
//#endregion
// Issue query
const timeline = await Note
.find(query, {
limit: ps.limit,
sort: sort
});
const timeline = await Note.find(query, {
limit: ps.limit,
sort: sort
});
// Serialize
res(await packMany(timeline, user));
}));
return await packMany(timeline, user);
});

View File

@@ -1,8 +1,9 @@
import $ from 'cafy';
import ID, { transform } from '../../../../../misc/cafy-id';
import Note from '../../../../../models/note';
import define from '../../../define';
import watch from '../../../../../services/note/watch';
import { getValiedNote } from '../../../common/getters';
import { ApiError } from '../../../error';
export const meta = {
stability: 'stable',
@@ -25,21 +26,22 @@ export const meta = {
'en-US': 'Target note ID.'
}
}
},
errors: {
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: 'ea0e37a6-90a3-4f58-ba6b-c328ca206fc7'
}
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Get note
const note = await Note.findOne({
_id: ps.noteId
export default define(meta, async (ps, user) => {
const note = await getValiedNote(ps.noteId).catch(e => {
if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw e;
});
if (note === null) {
return rej('note not found');
}
await watch(user._id, note);
// Send response
res();
}));
});

View File

@@ -1,8 +1,9 @@
import $ from 'cafy';
import ID, { transform } from '../../../../../misc/cafy-id';
import Note from '../../../../../models/note';
import define from '../../../define';
import unwatch from '../../../../../services/note/unwatch';
import { getValiedNote } from '../../../common/getters';
import { ApiError } from '../../../error';
export const meta = {
stability: 'stable',
@@ -25,21 +26,22 @@ export const meta = {
'en-US': 'Target note ID.'
}
}
},
errors: {
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: '09b3695c-f72c-4731-a428-7cff825fc82e'
}
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Get note
const note = await Note.findOne({
_id: ps.noteId
export default define(meta, async (ps, user) => {
const note = await getValiedNote(ps.noteId).catch(e => {
if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw e;
});
if (note === null) {
return rej('note not found');
}
await unwatch(user._id, note);
// Send response
res();
}));
});