Use PostgreSQL instead of MongoDB (#4572)
* wip * Update note.ts * Update timeline.ts * Update core.ts * wip * Update generate-visibility-query.ts * wip * wip * wip * wip * wip * Update global-timeline.ts * wip * wip * wip * Update vote.ts * wip * wip * Update create.ts * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Update files.ts * wip * wip * Update CONTRIBUTING.md * wip * wip * wip * wip * wip * wip * wip * wip * Update read-notification.ts * wip * wip * wip * wip * wip * wip * wip * Update cancel.ts * wip * wip * wip * Update show.ts * wip * wip * Update gen-id.ts * Update create.ts * Update id.ts * wip * wip * wip * wip * wip * wip * wip * Docker: Update files about Docker (#4599) * Docker: Use cache if files used by `yarn install` was not updated This patch reduces the number of times to installing node_modules. For example, `yarn install` step will be skipped when only ".config/default.yml" is updated. * Docker: Migrate MongoDB to Postgresql Misskey uses Postgresql as a database instead of Mongodb since version 11. * Docker: Uncomment about data persistence This patch will save a lot of databases. * wip * wip * wip * Update activitypub.ts * wip * wip * wip * Update logs.ts * wip * Update drive-file.ts * Update register.ts * wip * wip * Update mentions.ts * wip * wip * wip * Update recommendation.ts * wip * Update index.ts * wip * Update recommendation.ts * Doc: Update docker.ja.md and docker.en.md (#1) (#4608) Update how to set up misskey. * wip * ✌️ * wip * Update note.ts * Update postgre.ts * wip * wip * wip * wip * Update add-file.ts * wip * wip * wip * Clean up * Update logs.ts * wip * 🍕 * wip * Ad notes * wip * Update api-visibility.ts * Update note.ts * Update add-file.ts * tests * tests * Update postgre.ts * Update utils.ts * wip * wip * Refactor * wip * Refactor * wip * wip * Update show-users.ts * Update update-instance.ts * wip * Update feed.ts * Update outbox.ts * Update outbox.ts * Update user.ts * wip * Update list.ts * Update update-hashtag.ts * wip * Update update-hashtag.ts * Refactor * Update update.ts * wip * wip * ✌️ * clean up * docs * Update push.ts * wip * Update api.ts * wip * ✌️ * Update make-pagination-query.ts * ✌️ * Delete hashtags.ts * Update instances.ts * Update instances.ts * Update create.ts * Update search.ts * Update reversi-game.ts * Update signup.ts * Update user.ts * id * Update example.yml * 🎨 * objectid * fix * reversi * reversi * Fix bug of chart engine * Add test of chart engine * Improve test * Better testing * Improve chart engine * Refactor * Add test of chart engine * Refactor * Add chart test * Fix bug * コミットし忘れ * Refactoring * ✌️ * Add tests * Add test * Extarct note tests * Refactor * 存在しないユーザーにメンションできなくなっていた問題を修正 * Fix bug * Update update-meta.ts * Fix bug * Update mention.vue * Fix bug * Update meta.ts * Update CONTRIBUTING.md * Fix bug * Fix bug * Fix bug * Clean up * Clean up * Update notification.ts * Clean up * Add mute tests * Add test * Refactor * Add test * Fix test * Refactor * Refactor * Add tests * Update utils.ts * Update utils.ts * Fix test * Update package.json * Update update.ts * Update manifest.ts * Fix bug * Fix bug * Add test * 🎨 * Update endpoint permissions * Updaye permisison * Update person.ts #4299 * データベースと同期しないように * Fix bug * Fix bug * Update reversi-game.ts * Use a feature of Node v11.7.0 to extract a public key (#4644) * wip * wip * ✌️ * Refactoring #1540 * test * test * test * test * test * test * test * Fix bug * Fix test * 🍣 * wip * #4471 * Add test for #4335 * Refactor * Fix test * Add tests * 🕓 * Fix bug * Add test * Add test * rename * Fix bug
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../misc/cafy-id';
|
||||
import Note, { packMany } from '../../../../models/note';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { getFriends } from '../../common/get-friends';
|
||||
import { getHideUserIds } from '../../common/get-hide-users';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { Brackets } from 'typeorm';
|
||||
import { Notes } from '../../../../models';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -18,7 +20,6 @@ export const meta = {
|
||||
params: {
|
||||
noteId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '対象の投稿のID',
|
||||
'en-US': 'Target note ID'
|
||||
@@ -32,12 +33,10 @@ export const meta = {
|
||||
|
||||
sinceId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
},
|
||||
|
||||
untilId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -50,83 +49,24 @@ export const meta = {
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
const [followings, hideUserIds] = await Promise.all([
|
||||
// フォローを取得
|
||||
// Fetch following
|
||||
user ? getFriends(user._id) : [],
|
||||
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
|
||||
.andWhere(new Brackets(qb => { qb
|
||||
.where(`note.replyId = :noteId`, { noteId: ps.noteId })
|
||||
.orWhere(new Brackets(qb => { qb
|
||||
.where(`note.renoteId = :noteId`, { noteId: ps.noteId })
|
||||
.andWhere(new Brackets(qb => { qb
|
||||
.where(`note.text IS NOT NULL`)
|
||||
.orWhere(`note.fileIds != '{}'`)
|
||||
.orWhere(`note.hasPoll = TRUE`);
|
||||
}));
|
||||
}));
|
||||
}))
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
// 隠すユーザーを取得
|
||||
getHideUserIds(user)
|
||||
]);
|
||||
if (user) generateVisibilityQuery(query, user);
|
||||
if (user) generateMuteQuery(query, user);
|
||||
|
||||
const visibleQuery = user == null ? [{
|
||||
visibility: { $in: [ 'public', 'home' ] }
|
||||
}] : [{
|
||||
visibility: { $in: [ 'public', 'home' ] }
|
||||
}, {
|
||||
// myself (for followers/specified/private)
|
||||
userId: user._id
|
||||
}, {
|
||||
// to me (for specified)
|
||||
visibleUserIds: { $in: [ user._id ] }
|
||||
}, {
|
||||
visibility: 'followers',
|
||||
$or: [{
|
||||
// フォロワーの投稿
|
||||
userId: { $in: followings.map(f => f.id) },
|
||||
}, {
|
||||
// 自分の投稿へのリプライ
|
||||
'_reply.userId': user._id,
|
||||
}, {
|
||||
// 自分へのメンションが含まれている
|
||||
mentions: { $in: [ user._id ] }
|
||||
}]
|
||||
}];
|
||||
const notes = await query.take(ps.limit).getMany();
|
||||
|
||||
const q = {
|
||||
$and: [{
|
||||
$or: [{
|
||||
replyId: ps.noteId,
|
||||
}, {
|
||||
renoteId: ps.noteId,
|
||||
$or: [{
|
||||
text: { $ne: null }
|
||||
}, {
|
||||
fileIds: { $ne: [] }
|
||||
}, {
|
||||
poll: { $ne: null }
|
||||
}]
|
||||
}]
|
||||
}, {
|
||||
$or: visibleQuery
|
||||
}]
|
||||
} as any;
|
||||
|
||||
if (hideUserIds && hideUserIds.length > 0) {
|
||||
q['userId'] = {
|
||||
$nin: hideUserIds
|
||||
};
|
||||
}
|
||||
|
||||
const sort = {
|
||||
_id: -1
|
||||
};
|
||||
|
||||
if (ps.sinceId) {
|
||||
sort._id = 1;
|
||||
q._id = {
|
||||
$gt: ps.sinceId
|
||||
};
|
||||
} else if (ps.untilId) {
|
||||
q._id = {
|
||||
$lt: ps.untilId
|
||||
};
|
||||
}
|
||||
|
||||
const notes = await Note.find(q, {
|
||||
limit: ps.limit,
|
||||
sort: sort
|
||||
});
|
||||
|
||||
return await packMany(notes, user);
|
||||
return await Notes.packMany(notes, user);
|
||||
});
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../misc/cafy-id';
|
||||
import Note, { packMany, INote } from '../../../../models/note';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { ApiError } from '../../error';
|
||||
import { getNote } from '../../common/getters';
|
||||
import { Note } from '../../../../models/entities/note';
|
||||
import { Notes } from '../../../../models';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -18,7 +19,6 @@ export const meta = {
|
||||
params: {
|
||||
noteId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '対象の投稿のID',
|
||||
'en-US': 'Target note ID'
|
||||
@@ -58,12 +58,12 @@ export default define(meta, async (ps, user) => {
|
||||
throw e;
|
||||
});
|
||||
|
||||
const conversation: INote[] = [];
|
||||
const conversation: Note[] = [];
|
||||
let i = 0;
|
||||
|
||||
async function get(id: any) {
|
||||
i++;
|
||||
const p = await Note.findOne({ _id: id });
|
||||
const p = await Notes.findOne(id);
|
||||
|
||||
if (i > ps.offset) {
|
||||
conversation.push(p);
|
||||
@@ -82,5 +82,5 @@ export default define(meta, async (ps, user) => {
|
||||
await get(note.replyId);
|
||||
}
|
||||
|
||||
return await packMany(conversation, user);
|
||||
return await Notes.packMany(conversation, user);
|
||||
});
|
||||
|
@@ -1,14 +1,15 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform, transformMany } from '../../../../misc/cafy-id';
|
||||
import * as ms from 'ms';
|
||||
import { length } from 'stringz';
|
||||
import Note, { INote, isValidCw, pack } from '../../../../models/note';
|
||||
import User, { IUser } from '../../../../models/user';
|
||||
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';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import { User } from '../../../../models/entities/user';
|
||||
import { Users, DriveFiles, Notes } from '../../../../models';
|
||||
import { DriveFile } from '../../../../models/entities/drive-file';
|
||||
import { Note } from '../../../../models/entities/note';
|
||||
|
||||
let maxNoteTextLength = 1000;
|
||||
|
||||
@@ -34,7 +35,7 @@ export const meta = {
|
||||
max: 300
|
||||
},
|
||||
|
||||
kind: 'note-write',
|
||||
kind: 'write:notes',
|
||||
|
||||
params: {
|
||||
visibility: {
|
||||
@@ -47,7 +48,6 @@ export const meta = {
|
||||
|
||||
visibleUserIds: {
|
||||
validator: $.optional.arr($.type(ID)).unique().min(0),
|
||||
transform: transformMany,
|
||||
desc: {
|
||||
'ja-JP': '(投稿の公開範囲が specified の場合)投稿を閲覧できるユーザー'
|
||||
}
|
||||
@@ -64,7 +64,7 @@ export const meta = {
|
||||
},
|
||||
|
||||
cw: {
|
||||
validator: $.optional.nullable.str.pipe(isValidCw),
|
||||
validator: $.optional.nullable.str.pipe(Notes.validateCw),
|
||||
desc: {
|
||||
'ja-JP': 'コンテンツの警告。このパラメータを指定すると設定したテキストで投稿のコンテンツを隠す事が出来ます。'
|
||||
}
|
||||
@@ -129,7 +129,6 @@ export const meta = {
|
||||
|
||||
fileIds: {
|
||||
validator: $.optional.arr($.type(ID)).unique().range(1, 4),
|
||||
transform: transformMany,
|
||||
desc: {
|
||||
'ja-JP': '添付するファイル'
|
||||
}
|
||||
@@ -137,7 +136,6 @@ export const meta = {
|
||||
|
||||
mediaIds: {
|
||||
validator: $.optional.arr($.type(ID)).unique().range(1, 4),
|
||||
transform: transformMany,
|
||||
deprecated: true,
|
||||
desc: {
|
||||
'ja-JP': '添付するファイル (このパラメータは廃止予定です。代わりに fileIds を使ってください。)'
|
||||
@@ -146,7 +144,6 @@ export const meta = {
|
||||
|
||||
replyId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '返信対象'
|
||||
}
|
||||
@@ -154,7 +151,6 @@ export const meta = {
|
||||
|
||||
renoteId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': 'Renote対象'
|
||||
}
|
||||
@@ -227,32 +223,28 @@ export const meta = {
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user, app) => {
|
||||
let visibleUsers: IUser[] = [];
|
||||
let visibleUsers: User[] = [];
|
||||
if (ps.visibleUserIds) {
|
||||
visibleUsers = await Promise.all(ps.visibleUserIds.map(id => User.findOne({
|
||||
_id: id
|
||||
})));
|
||||
visibleUsers = await Promise.all(ps.visibleUserIds.map(id => Users.findOne(id)));
|
||||
}
|
||||
|
||||
let files: IDriveFile[] = [];
|
||||
let files: DriveFile[] = [];
|
||||
const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null;
|
||||
if (fileIds != null) {
|
||||
files = await Promise.all(fileIds.map(fileId => {
|
||||
return DriveFile.findOne({
|
||||
_id: fileId,
|
||||
'metadata.userId': user._id
|
||||
return DriveFiles.findOne({
|
||||
id: fileId,
|
||||
userId: user.id
|
||||
});
|
||||
}));
|
||||
|
||||
files = files.filter(file => file != null);
|
||||
}
|
||||
|
||||
let renote: INote = null;
|
||||
let renote: Note = null;
|
||||
if (ps.renoteId != null) {
|
||||
// Fetch renote to note
|
||||
renote = await Note.findOne({
|
||||
_id: ps.renoteId
|
||||
});
|
||||
renote = await Notes.findOne(ps.renoteId);
|
||||
|
||||
if (renote == null) {
|
||||
throw new ApiError(meta.errors.noSuchRenoteTarget);
|
||||
@@ -261,14 +253,12 @@ export default define(meta, async (ps, user, app) => {
|
||||
}
|
||||
}
|
||||
|
||||
let reply: INote = null;
|
||||
let reply: Note = null;
|
||||
if (ps.replyId != null) {
|
||||
// Fetch reply
|
||||
reply = await Note.findOne({
|
||||
_id: ps.replyId
|
||||
});
|
||||
reply = await Notes.findOne(ps.replyId);
|
||||
|
||||
if (reply === null) {
|
||||
if (reply == null) {
|
||||
throw new ApiError(meta.errors.noSuchReplyTarget);
|
||||
}
|
||||
|
||||
@@ -279,12 +269,6 @@ export default define(meta, async (ps, user, app) => {
|
||||
}
|
||||
|
||||
if (ps.poll) {
|
||||
(ps.poll as any).choices = (ps.poll as any).choices.map((choice: string, i: number) => ({
|
||||
id: i, // IDを付与
|
||||
text: choice.trim(),
|
||||
votes: 0
|
||||
}));
|
||||
|
||||
if (typeof ps.poll.expiresAt === 'number') {
|
||||
if (ps.poll.expiresAt < Date.now())
|
||||
throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll);
|
||||
@@ -298,11 +282,6 @@ export default define(meta, async (ps, user, app) => {
|
||||
throw new ApiError(meta.errors.contentRequired);
|
||||
}
|
||||
|
||||
// 後方互換性のため
|
||||
if (ps.visibility == 'private') {
|
||||
ps.visibility = 'specified';
|
||||
}
|
||||
|
||||
// 投稿を作成
|
||||
const note = await create(user, {
|
||||
createdAt: new Date(),
|
||||
@@ -311,7 +290,7 @@ export default define(meta, async (ps, user, app) => {
|
||||
choices: ps.poll.choices,
|
||||
multiple: ps.poll.multiple || false,
|
||||
expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null
|
||||
} : undefined,
|
||||
} : null,
|
||||
text: ps.text,
|
||||
reply,
|
||||
renote,
|
||||
@@ -321,13 +300,13 @@ export default define(meta, async (ps, user, app) => {
|
||||
localOnly: ps.localOnly,
|
||||
visibility: ps.visibility,
|
||||
visibleUsers,
|
||||
apMentions: ps.noExtractMentions ? [] : undefined,
|
||||
apHashtags: ps.noExtractHashtags ? [] : undefined,
|
||||
apEmojis: ps.noExtractEmojis ? [] : undefined,
|
||||
apMentions: ps.noExtractMentions ? [] : null,
|
||||
apHashtags: ps.noExtractHashtags ? [] : null,
|
||||
apEmojis: ps.noExtractEmojis ? [] : null,
|
||||
geo: ps.geo
|
||||
});
|
||||
|
||||
return {
|
||||
createdNote: await pack(note, user)
|
||||
createdNote: await Notes.pack(note, user)
|
||||
};
|
||||
});
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../misc/cafy-id';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import deleteNote from '../../../../services/note/delete';
|
||||
import User from '../../../../models/user';
|
||||
import define from '../../define';
|
||||
import * as ms from 'ms';
|
||||
import { getNote } from '../../common/getters';
|
||||
import { ApiError } from '../../error';
|
||||
import { Users } from '../../../../models';
|
||||
|
||||
export const meta = {
|
||||
stability: 'stable',
|
||||
@@ -19,7 +19,7 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'note-write',
|
||||
kind: 'write:notes',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
@@ -30,7 +30,6 @@ export const meta = {
|
||||
params: {
|
||||
noteId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '対象の投稿のID',
|
||||
'en-US': 'Target note ID.'
|
||||
@@ -59,9 +58,10 @@ export default define(meta, async (ps, user) => {
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (!user.isAdmin && !user.isModerator && !note.userId.equals(user._id)) {
|
||||
if (!user.isAdmin && !user.isModerator && (note.userId !== user.id)) {
|
||||
throw new ApiError(meta.errors.accessDenied);
|
||||
}
|
||||
|
||||
await deleteNote(await User.findOne({ _id: note.userId }), note);
|
||||
// この操作を行うのが投稿者とは限らない(例えばモデレーター)ため
|
||||
await deleteNote(await Users.findOne(note.userId), note);
|
||||
});
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../../misc/cafy-id';
|
||||
import Favorite from '../../../../../models/favorite';
|
||||
import { ID } from '../../../../../misc/cafy-id';
|
||||
import define from '../../../define';
|
||||
import { ApiError } from '../../../error';
|
||||
import { getNote } from '../../../common/getters';
|
||||
import { NoteFavorites } from '../../../../../models';
|
||||
import { genId } from '../../../../../misc/gen-id';
|
||||
|
||||
export const meta = {
|
||||
stability: 'stable',
|
||||
@@ -22,7 +23,6 @@ export const meta = {
|
||||
params: {
|
||||
noteId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '対象の投稿のID',
|
||||
'en-US': 'Target note ID.'
|
||||
@@ -53,21 +53,20 @@ export default define(meta, async (ps, user) => {
|
||||
});
|
||||
|
||||
// if already favorited
|
||||
const exist = await Favorite.findOne({
|
||||
noteId: note._id,
|
||||
userId: user._id
|
||||
const exist = await NoteFavorites.findOne({
|
||||
noteId: note.id,
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
if (exist !== null) {
|
||||
if (exist != null) {
|
||||
throw new ApiError(meta.errors.alreadyFavorited);
|
||||
}
|
||||
|
||||
// Create favorite
|
||||
await Favorite.insert({
|
||||
await NoteFavorites.save({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
noteId: note._id,
|
||||
userId: user._id
|
||||
noteId: note.id,
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
return;
|
||||
});
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../../misc/cafy-id';
|
||||
import Favorite from '../../../../../models/favorite';
|
||||
import { ID } from '../../../../../misc/cafy-id';
|
||||
import define from '../../../define';
|
||||
import { ApiError } from '../../../error';
|
||||
import { getNote } from '../../../common/getters';
|
||||
import { NoteFavorites } from '../../../../../models';
|
||||
|
||||
export const meta = {
|
||||
stability: 'stable',
|
||||
@@ -22,7 +22,6 @@ export const meta = {
|
||||
params: {
|
||||
noteId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '対象の投稿のID',
|
||||
'en-US': 'Target note ID.'
|
||||
@@ -53,19 +52,15 @@ export default define(meta, async (ps, user) => {
|
||||
});
|
||||
|
||||
// if already favorited
|
||||
const exist = await Favorite.findOne({
|
||||
noteId: note._id,
|
||||
userId: user._id
|
||||
const exist = await NoteFavorites.findOne({
|
||||
noteId: note.id,
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
if (exist === null) {
|
||||
if (exist == null) {
|
||||
throw new ApiError(meta.errors.notFavorited);
|
||||
}
|
||||
|
||||
// Delete favorite
|
||||
await Favorite.remove({
|
||||
_id: exist._id
|
||||
});
|
||||
|
||||
return;
|
||||
await NoteFavorites.delete(exist.id);
|
||||
});
|
||||
|
@@ -1,8 +1,7 @@
|
||||
import $ from 'cafy';
|
||||
import Note from '../../../../models/note';
|
||||
import { packMany } from '../../../../models/note';
|
||||
import define from '../../define';
|
||||
import { getHideUserIds } from '../../common/get-hide-users';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { Notes } from '../../../../models';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -35,25 +34,14 @@ export const meta = {
|
||||
export default define(meta, async (ps, user) => {
|
||||
const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで
|
||||
|
||||
const hideUserIds = await getHideUserIds(user);
|
||||
const query = Notes.createQueryBuilder('note')
|
||||
.andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) })
|
||||
.andWhere(`note.visibility = 'public'`)
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
const notes = await Note.find({
|
||||
createdAt: {
|
||||
$gt: new Date(Date.now() - day)
|
||||
},
|
||||
deletedAt: null,
|
||||
visibility: 'public',
|
||||
'_user.host': null,
|
||||
...(hideUserIds && hideUserIds.length > 0 ? { userId: { $nin: hideUserIds } } : {})
|
||||
}, {
|
||||
limit: ps.limit,
|
||||
sort: {
|
||||
score: -1
|
||||
},
|
||||
hint: {
|
||||
score: -1
|
||||
}
|
||||
});
|
||||
if (user) generateMuteQuery(query, user);
|
||||
|
||||
return await packMany(notes, user);
|
||||
const notes = await query.orderBy('note.score', 'DESC').take(ps.limit).getMany();
|
||||
|
||||
return await Notes.packMany(notes, user);
|
||||
});
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../misc/cafy-id';
|
||||
import Note from '../../../../models/note';
|
||||
import { packMany } from '../../../../models/note';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import fetchMeta from '../../../../misc/fetch-meta';
|
||||
import { getHideUserIds } from '../../common/get-hide-users';
|
||||
import { ApiError } from '../../error';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { Notes } from '../../../../models';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { activeUsersChart } from '../../../../services/chart';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -22,14 +23,6 @@ export const meta = {
|
||||
}
|
||||
},
|
||||
|
||||
mediaOnly: {
|
||||
validator: $.optional.bool,
|
||||
deprecated: true,
|
||||
desc: {
|
||||
'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
|
||||
}
|
||||
},
|
||||
|
||||
limit: {
|
||||
validator: $.optional.num.range(1, 100),
|
||||
default: 10
|
||||
@@ -37,12 +30,10 @@ export const meta = {
|
||||
|
||||
sinceId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
},
|
||||
|
||||
untilId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
},
|
||||
|
||||
sinceDate: {
|
||||
@@ -71,6 +62,7 @@ export const meta = {
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
// TODO どっかにキャッシュ
|
||||
const m = await fetchMeta();
|
||||
if (m.disableGlobalTimeline) {
|
||||
if (user == null || (!user.isAdmin && !user.isModerator)) {
|
||||
@@ -78,68 +70,25 @@ export default define(meta, async (ps, user) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 隠すユーザーを取得
|
||||
const hideUserIds = await getHideUserIds(user);
|
||||
|
||||
//#region Construct query
|
||||
const sort = {
|
||||
_id: -1
|
||||
};
|
||||
const query = makePaginationQuery(Notes.createQueryBuilder('note'),
|
||||
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('note.visibility = \'public\'')
|
||||
.andWhere('note.replyId IS NULL')
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
const query = {
|
||||
deletedAt: null,
|
||||
if (user) generateMuteQuery(query, user);
|
||||
|
||||
// public only
|
||||
visibility: 'public',
|
||||
|
||||
replyId: null
|
||||
} as any;
|
||||
|
||||
if (hideUserIds && hideUserIds.length > 0) {
|
||||
query.userId = {
|
||||
$nin: hideUserIds
|
||||
};
|
||||
|
||||
query['_reply.userId'] = {
|
||||
$nin: hideUserIds
|
||||
};
|
||||
|
||||
query['_renote.userId'] = {
|
||||
$nin: hideUserIds
|
||||
};
|
||||
}
|
||||
|
||||
const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
|
||||
|
||||
if (withFiles) {
|
||||
query.fileIds = { $exists: true, $ne: [] };
|
||||
}
|
||||
|
||||
if (ps.sinceId) {
|
||||
sort._id = 1;
|
||||
query._id = {
|
||||
$gt: ps.sinceId
|
||||
};
|
||||
} else if (ps.untilId) {
|
||||
query._id = {
|
||||
$lt: ps.untilId
|
||||
};
|
||||
} else if (ps.sinceDate) {
|
||||
sort._id = 1;
|
||||
query.createdAt = {
|
||||
$gt: new Date(ps.sinceDate)
|
||||
};
|
||||
} else if (ps.untilDate) {
|
||||
query.createdAt = {
|
||||
$lt: new Date(ps.untilDate)
|
||||
};
|
||||
if (ps.withFiles) {
|
||||
query.andWhere('note.fileIds != \'{}\'');
|
||||
}
|
||||
//#endregion
|
||||
|
||||
const timeline = await Note.find(query, {
|
||||
limit: ps.limit,
|
||||
sort: sort
|
||||
});
|
||||
const timeline = await query.take(ps.limit).getMany();
|
||||
|
||||
return await packMany(timeline, user);
|
||||
if (user) {
|
||||
activeUsersChart.update(user);
|
||||
}
|
||||
|
||||
return await Notes.packMany(timeline, user);
|
||||
});
|
||||
|
@@ -1,12 +1,14 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../misc/cafy-id';
|
||||
import Note from '../../../../models/note';
|
||||
import { packMany } from '../../../../models/note';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import fetchMeta from '../../../../misc/fetch-meta';
|
||||
import activeUsersChart from '../../../../services/chart/active-users';
|
||||
import { getHideUserIds } from '../../common/get-hide-users';
|
||||
import { ApiError } from '../../error';
|
||||
import { Notes } from '../../../../models';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { activeUsersChart } from '../../../../services/chart';
|
||||
import { Brackets } from 'typeorm';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -23,14 +25,6 @@ export const meta = {
|
||||
}
|
||||
},
|
||||
|
||||
mediaOnly: {
|
||||
validator: $.optional.bool,
|
||||
deprecated: true,
|
||||
desc: {
|
||||
'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
|
||||
}
|
||||
},
|
||||
|
||||
fileType: {
|
||||
validator: $.optional.arr($.str),
|
||||
desc: {
|
||||
@@ -53,12 +47,10 @@ export const meta = {
|
||||
|
||||
sinceId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
},
|
||||
|
||||
untilId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
},
|
||||
|
||||
sinceDate: {
|
||||
@@ -87,6 +79,7 @@ export const meta = {
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
// TODO どっかにキャッシュ
|
||||
const m = await fetchMeta();
|
||||
if (m.disableLocalTimeline) {
|
||||
if (user == null || (!user.isAdmin && !user.isModerator)) {
|
||||
@@ -94,90 +87,44 @@ export default define(meta, async (ps, user) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 隠すユーザーを取得
|
||||
const hideUserIds = await getHideUserIds(user);
|
||||
|
||||
//#region Construct query
|
||||
const sort = {
|
||||
_id: -1
|
||||
};
|
||||
const query = makePaginationQuery(Notes.createQueryBuilder('note'),
|
||||
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)')
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
const query = {
|
||||
deletedAt: null,
|
||||
if (user) generateVisibilityQuery(query, user);
|
||||
if (user) generateMuteQuery(query, user);
|
||||
|
||||
// public only
|
||||
visibility: 'public',
|
||||
|
||||
// リプライでない
|
||||
//replyId: null,
|
||||
|
||||
// local
|
||||
'_user.host': null
|
||||
} as any;
|
||||
|
||||
if (hideUserIds && hideUserIds.length > 0) {
|
||||
query.userId = {
|
||||
$nin: hideUserIds
|
||||
};
|
||||
|
||||
query['_reply.userId'] = {
|
||||
$nin: hideUserIds
|
||||
};
|
||||
|
||||
query['_renote.userId'] = {
|
||||
$nin: hideUserIds
|
||||
};
|
||||
}
|
||||
|
||||
const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
|
||||
|
||||
if (withFiles) {
|
||||
query.fileIds = { $exists: true, $ne: [] };
|
||||
if (ps.withFiles) {
|
||||
query.andWhere('note.fileIds != \'{}\'');
|
||||
}
|
||||
|
||||
if (ps.fileType) {
|
||||
query.fileIds = { $exists: true, $ne: [] };
|
||||
|
||||
query['_files.contentType'] = {
|
||||
$in: ps.fileType
|
||||
};
|
||||
query.andWhere('note.fileIds != \'{}\'');
|
||||
query.andWhere(new Brackets(qb => {
|
||||
for (const type of ps.fileType) {
|
||||
const i = ps.fileType.indexOf(type);
|
||||
qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type });
|
||||
}
|
||||
}));
|
||||
|
||||
if (ps.excludeNsfw) {
|
||||
query['_files.metadata.isSensitive'] = {
|
||||
// v11 TODO
|
||||
/*
|
||||
query['_files.isSensitive'] = {
|
||||
$ne: true
|
||||
};
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
if (ps.sinceId) {
|
||||
sort._id = 1;
|
||||
query._id = {
|
||||
$gt: ps.sinceId
|
||||
};
|
||||
} else if (ps.untilId) {
|
||||
query._id = {
|
||||
$lt: ps.untilId
|
||||
};
|
||||
} else if (ps.sinceDate) {
|
||||
sort._id = 1;
|
||||
query.createdAt = {
|
||||
$gt: new Date(ps.sinceDate)
|
||||
};
|
||||
} else if (ps.untilDate) {
|
||||
query.createdAt = {
|
||||
$lt: new Date(ps.untilDate)
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
|
||||
const timeline = await Note.find(query, {
|
||||
limit: ps.limit,
|
||||
sort: sort
|
||||
});
|
||||
const timeline = await query.take(ps.limit).getMany();
|
||||
|
||||
if (user) {
|
||||
activeUsersChart.update(user);
|
||||
}
|
||||
|
||||
return await packMany(timeline, user);
|
||||
return await Notes.packMany(timeline, user);
|
||||
});
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../misc/cafy-id';
|
||||
import Note from '../../../../models/note';
|
||||
import { getFriendIds, getFriends } from '../../common/get-friends';
|
||||
import { packMany } from '../../../../models/note';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import read from '../../../../services/note/read';
|
||||
import { getHideUserIds } from '../../common/get-hide-users';
|
||||
import { Notes, Followings } from '../../../../models';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { Brackets } from 'typeorm';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -30,12 +31,10 @@ export const meta = {
|
||||
|
||||
sinceId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
},
|
||||
|
||||
untilId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
},
|
||||
|
||||
visibility: {
|
||||
@@ -52,97 +51,34 @@ export const meta = {
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
// フォローを取得
|
||||
const followings = await getFriends(user._id);
|
||||
const followingQuery = Followings.createQueryBuilder('following')
|
||||
.select('following.followeeId')
|
||||
.where('following.followerId = :followerId', { followerId: user.id });
|
||||
|
||||
const visibleQuery = [{
|
||||
visibility: { $in: [ 'public', 'home' ] }
|
||||
}, {
|
||||
// myself (for followers/specified/private)
|
||||
userId: user._id
|
||||
}, {
|
||||
// to me (for specified)
|
||||
visibleUserIds: { $in: [ user._id ] }
|
||||
}, {
|
||||
visibility: 'followers',
|
||||
$or: [{
|
||||
// フォロワーの投稿
|
||||
userId: { $in: followings.map(f => f.id) },
|
||||
}, {
|
||||
// 自分の投稿へのリプライ
|
||||
'_reply.userId': user._id,
|
||||
}, {
|
||||
// 自分へのメンションが含まれている
|
||||
mentions: { $in: [ user._id ] }
|
||||
}]
|
||||
}];
|
||||
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
|
||||
.andWhere(new Brackets(qb => { qb
|
||||
.where(`:meId = ANY(note.mentions)`, { meId: user.id })
|
||||
.orWhere(`:meId = ANY(note.visibleUserIds)`, { meId: user.id });
|
||||
}))
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
const query = {
|
||||
$and: [{
|
||||
deletedAt: null,
|
||||
}, {
|
||||
$or: visibleQuery,
|
||||
}],
|
||||
|
||||
$or: [{
|
||||
mentions: user._id
|
||||
}, {
|
||||
visibleUserIds: user._id
|
||||
}]
|
||||
} as any;
|
||||
|
||||
// 隠すユーザーを取得
|
||||
const hideUserIds = await getHideUserIds(user);
|
||||
|
||||
if (hideUserIds && hideUserIds.length > 0) {
|
||||
query.userId = {
|
||||
$nin: hideUserIds
|
||||
};
|
||||
|
||||
query['_reply.userId'] = {
|
||||
$nin: hideUserIds
|
||||
};
|
||||
|
||||
query['_renote.userId'] = {
|
||||
$nin: hideUserIds
|
||||
};
|
||||
}
|
||||
|
||||
const sort = {
|
||||
_id: -1
|
||||
};
|
||||
generateVisibilityQuery(query, user);
|
||||
generateMuteQuery(query, user);
|
||||
|
||||
if (ps.visibility) {
|
||||
query.visibility = ps.visibility;
|
||||
query.andWhere('note.visibility = :visibility', { visibility: ps.visibility });
|
||||
}
|
||||
|
||||
if (ps.following) {
|
||||
const followingIds = await getFriendIds(user._id);
|
||||
|
||||
query.userId = {
|
||||
$in: followingIds
|
||||
};
|
||||
query.andWhere(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: user.id });
|
||||
query.setParameters(followingQuery.getParameters());
|
||||
}
|
||||
|
||||
if (ps.sinceId) {
|
||||
sort._id = 1;
|
||||
query._id = {
|
||||
$gt: ps.sinceId
|
||||
};
|
||||
} else if (ps.untilId) {
|
||||
query._id = {
|
||||
$lt: ps.untilId
|
||||
};
|
||||
}
|
||||
|
||||
const mentions = await Note.find(query, {
|
||||
limit: ps.limit,
|
||||
sort: sort
|
||||
});
|
||||
const mentions = await query.take(ps.limit).getMany();
|
||||
|
||||
for (const note of mentions) {
|
||||
read(user._id, note._id);
|
||||
read(user.id, note.id);
|
||||
}
|
||||
|
||||
return await packMany(mentions, user);
|
||||
return await Notes.packMany(mentions, user);
|
||||
});
|
||||
|
@@ -1,8 +1,7 @@
|
||||
import $ from 'cafy';
|
||||
import Vote from '../../../../../models/poll-vote';
|
||||
import Note, { pack } from '../../../../../models/note';
|
||||
import define from '../../../define';
|
||||
import { getHideUserIds } from '../../../common/get-hide-users';
|
||||
import { Polls, Mutings, Notes, PollVotes } from '../../../../../models';
|
||||
import { Brackets, In } from 'typeorm';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -28,51 +27,46 @@ export const meta = {
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
// Get votes
|
||||
const votes = await Vote.find({
|
||||
userId: user._id
|
||||
}, {
|
||||
fields: {
|
||||
_id: false,
|
||||
noteId: true
|
||||
}
|
||||
const query = Polls.createQueryBuilder('poll')
|
||||
.where('poll.userHost IS NULL')
|
||||
.andWhere(`poll.userId != :meId`, { meId: user.id })
|
||||
.andWhere(`poll.noteVisibility = 'public'`)
|
||||
.andWhere(new Brackets(qb => { qb
|
||||
.where('poll.expiresAt IS NULL')
|
||||
.orWhere('poll.expiresAt > :now', { now: new Date() });
|
||||
}));
|
||||
|
||||
//#region exclude arleady voted polls
|
||||
const votedQuery = PollVotes.createQueryBuilder('vote')
|
||||
.select('vote.noteId')
|
||||
.where('vote.userId = :meId', { meId: user.id });
|
||||
|
||||
query
|
||||
.andWhere(`poll.noteId NOT IN (${ votedQuery.getQuery() })`);
|
||||
|
||||
query.setParameters(votedQuery.getParameters());
|
||||
//#endregion
|
||||
|
||||
//#region mute
|
||||
const mutingQuery = Mutings.createQueryBuilder('muting')
|
||||
.select('muting.muteeId')
|
||||
.where('muting.muterId = :muterId', { muterId: user.id });
|
||||
|
||||
query
|
||||
.andWhere(`poll.userId NOT IN (${ mutingQuery.getQuery() })`);
|
||||
|
||||
query.setParameters(mutingQuery.getParameters());
|
||||
//#endregion
|
||||
|
||||
const polls = await query.take(ps.limit).skip(ps.offset).getMany();
|
||||
|
||||
if (polls.length === 0) return [];
|
||||
|
||||
const notes = await Notes.find({
|
||||
id: In(polls.map(poll => poll.noteId))
|
||||
});
|
||||
|
||||
const nin = votes && votes.length != 0 ? votes.map(v => v.noteId) : [];
|
||||
|
||||
// 隠すユーザーを取得
|
||||
const hideUserIds = await getHideUserIds(user);
|
||||
|
||||
const notes = await Note.find({
|
||||
'_user.host': null,
|
||||
_id: {
|
||||
$nin: nin
|
||||
},
|
||||
userId: {
|
||||
$ne: user._id,
|
||||
$nin: hideUserIds
|
||||
},
|
||||
visibility: 'public',
|
||||
poll: {
|
||||
$exists: true,
|
||||
$ne: null
|
||||
},
|
||||
$or: [{
|
||||
'poll.expiresAt': null
|
||||
}, {
|
||||
'poll.expiresAt': {
|
||||
$gt: new Date()
|
||||
}
|
||||
}],
|
||||
}, {
|
||||
limit: ps.limit,
|
||||
skip: ps.offset,
|
||||
sort: {
|
||||
_id: -1
|
||||
}
|
||||
});
|
||||
|
||||
return await Promise.all(notes.map(note => pack(note, user, {
|
||||
return await Notes.packMany(notes, user, {
|
||||
detail: true
|
||||
})));
|
||||
});
|
||||
});
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../../misc/cafy-id';
|
||||
import Vote from '../../../../../models/poll-vote';
|
||||
import Note from '../../../../../models/note';
|
||||
import Watching from '../../../../../models/note-watching';
|
||||
import { ID } from '../../../../../misc/cafy-id';
|
||||
import watch from '../../../../../services/note/watch';
|
||||
import { publishNoteStream } from '../../../../../services/stream';
|
||||
import notify from '../../../../../services/create-notification';
|
||||
import { createNotification } from '../../../../../services/create-notification';
|
||||
import define from '../../../define';
|
||||
import User, { IRemoteUser } from '../../../../../models/user';
|
||||
import { ApiError } from '../../../error';
|
||||
import { getNote } from '../../../common/getters';
|
||||
import { deliver } from '../../../../../queue';
|
||||
import { renderActivity } from '../../../../../remote/activitypub/renderer';
|
||||
import renderVote from '../../../../../remote/activitypub/renderer/vote';
|
||||
import { deliverQuestionUpdate } from '../../../../../services/note/polls/update';
|
||||
import { PollVotes, NoteWatchings, Users, Polls } from '../../../../../models';
|
||||
import { Not } from 'typeorm';
|
||||
import { IRemoteUser } from '../../../../../models/entities/user';
|
||||
import { genId } from '../../../../../misc/gen-id';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -30,7 +30,6 @@ export const meta = {
|
||||
params: {
|
||||
noteId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '対象の投稿のID',
|
||||
'en-US': 'Target note ID'
|
||||
@@ -84,26 +83,28 @@ export default define(meta, async (ps, user) => {
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (note.poll == null) {
|
||||
if (!note.hasPoll) {
|
||||
throw new ApiError(meta.errors.noPoll);
|
||||
}
|
||||
|
||||
if (note.poll.expiresAt && note.poll.expiresAt < createdAt) {
|
||||
const poll = await Polls.findOne({ noteId: note.id });
|
||||
|
||||
if (poll.expiresAt && poll.expiresAt < createdAt) {
|
||||
throw new ApiError(meta.errors.alreadyExpired);
|
||||
}
|
||||
|
||||
if (!note.poll.choices.some(x => x.id == ps.choice)) {
|
||||
if (poll.choices[ps.choice] == null) {
|
||||
throw new ApiError(meta.errors.invalidChoice);
|
||||
}
|
||||
|
||||
// if already voted
|
||||
const exist = await Vote.find({
|
||||
noteId: note._id,
|
||||
userId: user._id
|
||||
const exist = await PollVotes.find({
|
||||
noteId: note.id,
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
if (exist.length) {
|
||||
if (note.poll.multiple) {
|
||||
if (poll.multiple) {
|
||||
if (exist.some(x => x.choice == ps.choice))
|
||||
throw new ApiError(meta.errors.alreadyVoted);
|
||||
} else {
|
||||
@@ -112,69 +113,54 @@ export default define(meta, async (ps, user) => {
|
||||
}
|
||||
|
||||
// Create vote
|
||||
const vote = await Vote.insert({
|
||||
const vote = await PollVotes.save({
|
||||
id: genId(),
|
||||
createdAt,
|
||||
noteId: note._id,
|
||||
userId: user._id,
|
||||
noteId: note.id,
|
||||
userId: user.id,
|
||||
choice: ps.choice
|
||||
});
|
||||
|
||||
const inc: any = {};
|
||||
inc[`poll.choices.${note.poll.choices.findIndex(c => c.id == ps.choice)}.votes`] = 1;
|
||||
|
||||
// Increment votes count
|
||||
await Note.update({ _id: note._id }, {
|
||||
$inc: inc
|
||||
});
|
||||
const index = ps.choice + 1; // In SQL, array index is 1 based
|
||||
await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE id = '${poll.id}'`);
|
||||
|
||||
publishNoteStream(note._id, 'pollVoted', {
|
||||
publishNoteStream(note.id, 'pollVoted', {
|
||||
choice: ps.choice,
|
||||
userId: user._id.toHexString()
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
// Notify
|
||||
notify(note.userId, user._id, 'poll_vote', {
|
||||
noteId: note._id,
|
||||
createNotification(note.userId, user.id, 'pollVote', {
|
||||
noteId: note.id,
|
||||
choice: ps.choice
|
||||
});
|
||||
|
||||
// Fetch watchers
|
||||
Watching
|
||||
.find({
|
||||
noteId: note._id,
|
||||
userId: { $ne: user._id },
|
||||
// 削除されたドキュメントは除く
|
||||
deletedAt: { $exists: false }
|
||||
}, {
|
||||
fields: {
|
||||
userId: true
|
||||
}
|
||||
})
|
||||
.then(watchers => {
|
||||
for (const watcher of watchers) {
|
||||
notify(watcher.userId, user._id, 'poll_vote', {
|
||||
noteId: note._id,
|
||||
choice: ps.choice
|
||||
});
|
||||
}
|
||||
});
|
||||
NoteWatchings.find({
|
||||
noteId: note.id,
|
||||
userId: Not(user.id),
|
||||
}).then(watchers => {
|
||||
for (const watcher of watchers) {
|
||||
createNotification(watcher.userId, user.id, 'pollVote', {
|
||||
noteId: note.id,
|
||||
choice: ps.choice
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// この投稿をWatchする
|
||||
if (user.settings.autoWatch !== false) {
|
||||
watch(user._id, note);
|
||||
if (user.autoWatch !== false) {
|
||||
watch(user.id, note);
|
||||
}
|
||||
|
||||
// リモート投票の場合リプライ送信
|
||||
if (note._user.host != null) {
|
||||
const pollOwner: IRemoteUser = await User.findOne({
|
||||
_id: note.userId
|
||||
});
|
||||
if (note.userHost != null) {
|
||||
const pollOwner: IRemoteUser = await Users.findOne(note.userId);
|
||||
|
||||
deliver(user, renderActivity(await renderVote(user, vote, note, pollOwner)), pollOwner.inbox);
|
||||
deliver(user, renderActivity(await renderVote(user, vote, note, poll, pollOwner)), pollOwner.inbox);
|
||||
}
|
||||
|
||||
// リモートフォロワーにUpdate配信
|
||||
deliverQuestionUpdate(note._id);
|
||||
|
||||
return;
|
||||
deliverQuestionUpdate(note.id);
|
||||
});
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../misc/cafy-id';
|
||||
import NoteReaction, { pack } from '../../../../models/note-reaction';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { getNote } from '../../common/getters';
|
||||
import { ApiError } from '../../error';
|
||||
import { NoteReactions } from '../../../../models';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -18,7 +18,6 @@ export const meta = {
|
||||
params: {
|
||||
noteId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '対象の投稿のID',
|
||||
'en-US': 'The ID of the target note'
|
||||
@@ -37,12 +36,10 @@ export const meta = {
|
||||
|
||||
sinceId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
},
|
||||
|
||||
untilId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -69,29 +66,17 @@ export default define(meta, async (ps, user) => {
|
||||
});
|
||||
|
||||
const query = {
|
||||
noteId: note._id
|
||||
} as any;
|
||||
|
||||
const sort = {
|
||||
_id: -1
|
||||
noteId: note.id
|
||||
};
|
||||
|
||||
if (ps.sinceId) {
|
||||
sort._id = 1;
|
||||
query._id = {
|
||||
$gt: ps.sinceId
|
||||
};
|
||||
} else if (ps.untilId) {
|
||||
query._id = {
|
||||
$lt: ps.untilId
|
||||
};
|
||||
}
|
||||
|
||||
const reactions = await NoteReaction.find(query, {
|
||||
limit: ps.limit,
|
||||
const reactions = await NoteReactions.find({
|
||||
where: query,
|
||||
take: ps.limit,
|
||||
skip: ps.offset,
|
||||
sort: sort
|
||||
order: {
|
||||
id: -1
|
||||
}
|
||||
});
|
||||
|
||||
return await Promise.all(reactions.map(reaction => pack(reaction, user)));
|
||||
return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, user)));
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../../misc/cafy-id';
|
||||
import { ID } from '../../../../../misc/cafy-id';
|
||||
import createReaction from '../../../../../services/note/reaction/create';
|
||||
import define from '../../../define';
|
||||
import { getNote } from '../../../common/getters';
|
||||
@@ -17,12 +17,11 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'reaction-write',
|
||||
kind: 'write:reactions',
|
||||
|
||||
params: {
|
||||
noteId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '対象の投稿'
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../../misc/cafy-id';
|
||||
import { ID } from '../../../../../misc/cafy-id';
|
||||
import define from '../../../define';
|
||||
import * as ms from 'ms';
|
||||
import deleteReaction from '../../../../../services/note/reaction/delete';
|
||||
@@ -16,7 +16,7 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'reaction-write',
|
||||
kind: 'write:reactions',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
@@ -27,7 +27,6 @@ export const meta = {
|
||||
params: {
|
||||
noteId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '対象の投稿のID',
|
||||
'en-US': 'Target note ID'
|
||||
|
@@ -1,9 +1,12 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../misc/cafy-id';
|
||||
import Note, { packMany } from '../../../../models/note';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { getNote } from '../../common/getters';
|
||||
import { ApiError } from '../../error';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { Notes } from '../../../../models';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -18,7 +21,6 @@ export const meta = {
|
||||
params: {
|
||||
noteId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '対象の投稿のID',
|
||||
'en-US': 'Target note ID'
|
||||
@@ -32,12 +34,10 @@ export const meta = {
|
||||
|
||||
sinceId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
},
|
||||
|
||||
untilId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -63,29 +63,14 @@ export default define(meta, async (ps, user) => {
|
||||
throw e;
|
||||
});
|
||||
|
||||
const sort = {
|
||||
_id: -1
|
||||
};
|
||||
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
|
||||
.andWhere(`note.renoteId = :renoteId`, { renoteId: note.id })
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
const query = {
|
||||
renoteId: note._id
|
||||
} as any;
|
||||
if (user) generateVisibilityQuery(query, user);
|
||||
if (user) generateMuteQuery(query, user);
|
||||
|
||||
if (ps.sinceId) {
|
||||
sort._id = 1;
|
||||
query._id = {
|
||||
$gt: ps.sinceId
|
||||
};
|
||||
} else if (ps.untilId) {
|
||||
query._id = {
|
||||
$lt: ps.untilId
|
||||
};
|
||||
}
|
||||
const renotes = await query.take(ps.limit).getMany();
|
||||
|
||||
const renotes = await Note.find(query, {
|
||||
limit: ps.limit,
|
||||
sort: sort
|
||||
});
|
||||
|
||||
return await packMany(renotes, user);
|
||||
return await Notes.packMany(renotes, user);
|
||||
});
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../misc/cafy-id';
|
||||
import Note, { packMany } from '../../../../models/note';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { getFriends } from '../../common/get-friends';
|
||||
import { getHideUserIds } from '../../common/get-hide-users';
|
||||
import { Notes } from '../../../../models';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -18,22 +19,30 @@ export const meta = {
|
||||
params: {
|
||||
noteId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '対象の投稿のID',
|
||||
'en-US': 'Target note ID'
|
||||
}
|
||||
},
|
||||
|
||||
sinceId: {
|
||||
validator: $.optional.type(ID),
|
||||
desc: {
|
||||
'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します'
|
||||
}
|
||||
},
|
||||
|
||||
untilId: {
|
||||
validator: $.optional.type(ID),
|
||||
desc: {
|
||||
'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します'
|
||||
}
|
||||
},
|
||||
|
||||
limit: {
|
||||
validator: $.optional.num.range(1, 100),
|
||||
default: 10
|
||||
},
|
||||
|
||||
offset: {
|
||||
validator: $.optional.num.min(0),
|
||||
default: 0
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
@@ -45,54 +54,14 @@ export const meta = {
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
const [followings, hideUserIds] = await Promise.all([
|
||||
// フォローを取得
|
||||
// Fetch following
|
||||
user ? getFriends(user._id) : [],
|
||||
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
|
||||
.andWhere('note.replyId = :replyId', { replyId: ps.noteId })
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
// 隠すユーザーを取得
|
||||
getHideUserIds(user)
|
||||
]);
|
||||
if (user) generateVisibilityQuery(query, user);
|
||||
if (user) generateMuteQuery(query, user);
|
||||
|
||||
const visibleQuery = user == null ? [{
|
||||
visibility: { $in: [ 'public', 'home' ] }
|
||||
}] : [{
|
||||
visibility: { $in: [ 'public', 'home' ] }
|
||||
}, {
|
||||
// myself (for followers/specified/private)
|
||||
userId: user._id
|
||||
}, {
|
||||
// to me (for specified)
|
||||
visibleUserIds: { $in: [ user._id ] }
|
||||
}, {
|
||||
visibility: 'followers',
|
||||
$or: [{
|
||||
// フォロワーの投稿
|
||||
userId: { $in: followings.map(f => f.id) },
|
||||
}, {
|
||||
// 自分の投稿へのリプライ
|
||||
'_reply.userId': user._id,
|
||||
}, {
|
||||
// 自分へのメンションが含まれている
|
||||
mentions: { $in: [ user._id ] }
|
||||
}]
|
||||
}];
|
||||
const timeline = await query.take(ps.limit).getMany();
|
||||
|
||||
const q = {
|
||||
replyId: ps.noteId,
|
||||
$or: visibleQuery
|
||||
} as any;
|
||||
|
||||
if (hideUserIds && hideUserIds.length > 0) {
|
||||
q['userId'] = {
|
||||
$nin: hideUserIds
|
||||
};
|
||||
}
|
||||
|
||||
const notes = await Note.find(q, {
|
||||
limit: ps.limit,
|
||||
skip: ps.offset
|
||||
});
|
||||
|
||||
return await packMany(notes, user);
|
||||
return await Notes.packMany(timeline, user);
|
||||
});
|
||||
|
@@ -1,10 +1,11 @@
|
||||
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 { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { getHideUserIds } from '../../common/get-hide-users';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { Notes } from '../../../../models';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { Brackets } from 'typeorm';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -28,16 +29,6 @@ export const meta = {
|
||||
}
|
||||
},
|
||||
|
||||
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,
|
||||
@@ -61,15 +52,6 @@ export const meta = {
|
||||
}
|
||||
},
|
||||
|
||||
media: {
|
||||
validator: $.optional.nullable.bool,
|
||||
default: null as any,
|
||||
deprecated: true,
|
||||
desc: {
|
||||
'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
|
||||
}
|
||||
},
|
||||
|
||||
poll: {
|
||||
validator: $.optional.nullable.bool,
|
||||
default: null as any,
|
||||
@@ -78,25 +60,18 @@ export const meta = {
|
||||
}
|
||||
},
|
||||
|
||||
untilId: {
|
||||
sinceId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します'
|
||||
'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します'
|
||||
}
|
||||
},
|
||||
|
||||
sinceDate: {
|
||||
validator: $.optional.num,
|
||||
},
|
||||
|
||||
untilDate: {
|
||||
validator: $.optional.num,
|
||||
},
|
||||
|
||||
offset: {
|
||||
validator: $.optional.num.min(0),
|
||||
default: 0
|
||||
untilId: {
|
||||
validator: $.optional.type(ID),
|
||||
desc: {
|
||||
'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します'
|
||||
}
|
||||
},
|
||||
|
||||
limit: {
|
||||
@@ -114,226 +89,58 @@ export const meta = {
|
||||
};
|
||||
|
||||
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 query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
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
|
||||
};
|
||||
if (me) generateVisibilityQuery(query, me);
|
||||
if (me) generateMuteQuery(query, me);
|
||||
|
||||
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 (ps.tag) {
|
||||
query.andWhere(':tag = ANY(note.tags)', { tag: ps.tag });
|
||||
} else {
|
||||
let i = 0;
|
||||
query.andWhere(new Brackets(qb => {
|
||||
for (const tags of ps.query) {
|
||||
qb.orWhere(new Brackets(qb => {
|
||||
for (const tag of tags) {
|
||||
qb.andWhere(`:tag${i} = ANY(note.tags)`, { [`tag${i}`]: tag });
|
||||
i++;
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
});
|
||||
query.andWhere('note.replyId IS NOT NULL');
|
||||
} else {
|
||||
push({
|
||||
$or: [{
|
||||
replyId: {
|
||||
$exists: false
|
||||
}
|
||||
}, {
|
||||
replyId: null
|
||||
}]
|
||||
});
|
||||
query.andWhere('note.replyId IS NULL');
|
||||
}
|
||||
}
|
||||
|
||||
if (ps.renote != null) {
|
||||
if (ps.renote) {
|
||||
push({
|
||||
renoteId: {
|
||||
$exists: true,
|
||||
$ne: null
|
||||
}
|
||||
});
|
||||
query.andWhere('note.renoteId IS NOT NULL');
|
||||
} else {
|
||||
push({
|
||||
$or: [{
|
||||
renoteId: {
|
||||
$exists: false
|
||||
}
|
||||
}, {
|
||||
renoteId: null
|
||||
}]
|
||||
});
|
||||
query.andWhere('note.renoteId IS NULL');
|
||||
}
|
||||
}
|
||||
|
||||
const withFiles = ps.withFiles != null ? ps.withFiles : ps.media;
|
||||
|
||||
if (withFiles) {
|
||||
push({
|
||||
fileIds: { $exists: true, $ne: [] }
|
||||
});
|
||||
if (ps.withFiles) {
|
||||
query.andWhere('note.fileIds != \'{}\'');
|
||||
}
|
||||
|
||||
if (ps.poll != null) {
|
||||
if (ps.poll) {
|
||||
push({
|
||||
poll: {
|
||||
$exists: true,
|
||||
$ne: null
|
||||
}
|
||||
});
|
||||
query.andWhere('note.hasPoll = TRUE');
|
||||
} else {
|
||||
push({
|
||||
$or: [{
|
||||
poll: {
|
||||
$exists: false
|
||||
}
|
||||
}, {
|
||||
poll: null
|
||||
}]
|
||||
});
|
||||
query.andWhere('note.hasPoll = FALSE');
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
const notes = await query.take(ps.limit).getMany();
|
||||
|
||||
return await packMany(notes, me);
|
||||
return await Notes.packMany(notes, me);
|
||||
});
|
||||
|
@@ -1,10 +1,9 @@
|
||||
import $ from 'cafy';
|
||||
import * as mongo from 'mongodb';
|
||||
import Note from '../../../../models/note';
|
||||
import { packMany } from '../../../../models/note';
|
||||
import es from '../../../../db/elasticsearch';
|
||||
import define from '../../define';
|
||||
import { ApiError } from '../../error';
|
||||
import { Notes } from '../../../../models';
|
||||
import { In } from 'typeorm';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -74,18 +73,19 @@ export default define(meta, async (ps, me) => {
|
||||
return [];
|
||||
}
|
||||
|
||||
const hits = response.hits.hits.map(hit => new mongo.ObjectID(hit._id));
|
||||
const hits = response.hits.hits.map((hit: any) => hit.id);
|
||||
|
||||
if (hits.length === 0) return [];
|
||||
|
||||
// Fetch found notes
|
||||
const notes = await Note.find({
|
||||
_id: {
|
||||
$in: hits
|
||||
}
|
||||
}, {
|
||||
sort: {
|
||||
_id: -1
|
||||
const notes = await Notes.find({
|
||||
where: {
|
||||
id: In(hits)
|
||||
},
|
||||
order: {
|
||||
id: -1
|
||||
}
|
||||
});
|
||||
|
||||
return await packMany(notes, me);
|
||||
return await Notes.packMany(notes, me);
|
||||
});
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../misc/cafy-id';
|
||||
import { pack } from '../../../../models/note';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { getNote } from '../../common/getters';
|
||||
import { ApiError } from '../../error';
|
||||
import { Notes } from '../../../../models';
|
||||
|
||||
export const meta = {
|
||||
stability: 'stable',
|
||||
@@ -20,7 +20,6 @@ export const meta = {
|
||||
params: {
|
||||
noteId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '対象の投稿のID',
|
||||
'en-US': 'Target note ID.'
|
||||
@@ -47,7 +46,7 @@ export default define(meta, async (ps, user) => {
|
||||
throw e;
|
||||
});
|
||||
|
||||
return await pack(note, user, {
|
||||
return await Notes.pack(note, user, {
|
||||
detail: true
|
||||
});
|
||||
});
|
||||
|
@@ -1,17 +1,18 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../misc/cafy-id';
|
||||
import Note from '../../../../models/note';
|
||||
import { getFriends } from '../../common/get-friends';
|
||||
import { packMany } from '../../../../models/note';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import fetchMeta from '../../../../misc/fetch-meta';
|
||||
import activeUsersChart from '../../../../services/chart/active-users';
|
||||
import { getHideUserIds } from '../../common/get-hide-users';
|
||||
import { ApiError } from '../../error';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { Followings, Notes } from '../../../../models';
|
||||
import { Brackets } from 'typeorm';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { activeUsersChart } from '../../../../services/chart';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': 'ハイブリッドタイムラインを取得します。'
|
||||
'ja-JP': 'ソーシャルタイムラインを取得します。'
|
||||
},
|
||||
|
||||
tags: ['notes'],
|
||||
@@ -27,17 +28,15 @@ export const meta = {
|
||||
|
||||
sinceId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します'
|
||||
'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します'
|
||||
}
|
||||
},
|
||||
|
||||
untilId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します'
|
||||
'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -85,14 +84,6 @@ export const meta = {
|
||||
'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します'
|
||||
}
|
||||
},
|
||||
|
||||
mediaOnly: {
|
||||
validator: $.optional.bool,
|
||||
deprecated: true,
|
||||
desc: {
|
||||
'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
@@ -112,94 +103,30 @@ export const meta = {
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
// TODO どっかにキャッシュ
|
||||
const m = await fetchMeta();
|
||||
if (m.disableLocalTimeline && !user.isAdmin && !user.isModerator) {
|
||||
throw new ApiError(meta.errors.stlDisabled);
|
||||
}
|
||||
|
||||
const [followings, hideUserIds] = await Promise.all([
|
||||
// フォローを取得
|
||||
// Fetch following
|
||||
getFriends(user._id, true, false),
|
||||
|
||||
// 隠すユーザーを取得
|
||||
getHideUserIds(user)
|
||||
]);
|
||||
|
||||
//#region Construct query
|
||||
const sort = {
|
||||
_id: -1
|
||||
};
|
||||
const followingQuery = Followings.createQueryBuilder('following')
|
||||
.select('following.followeeId')
|
||||
.where('following.followerId = :followerId', { followerId: user.id });
|
||||
|
||||
const followQuery = followings.map(f => ({
|
||||
userId: f.id,
|
||||
const query = makePaginationQuery(Notes.createQueryBuilder('note'),
|
||||
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere(new Brackets(qb => {
|
||||
qb.where(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: user.id })
|
||||
.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
|
||||
}))
|
||||
.leftJoinAndSelect('note.user', 'user')
|
||||
.setParameters(followingQuery.getParameters());
|
||||
|
||||
/*// リプライは含めない(ただし投稿者自身の投稿へのリプライ、自分の投稿へのリプライ、自分のリプライは含める)
|
||||
$or: [{
|
||||
// リプライでない
|
||||
replyId: null
|
||||
}, { // または
|
||||
// リプライだが返信先が投稿者自身の投稿
|
||||
$expr: {
|
||||
$eq: ['$_reply.userId', '$userId']
|
||||
}
|
||||
}, { // または
|
||||
// リプライだが返信先が自分(フォロワー)の投稿
|
||||
'_reply.userId': user._id
|
||||
}, { // または
|
||||
// 自分(フォロワー)が送信したリプライ
|
||||
userId: user._id
|
||||
}]*/
|
||||
}));
|
||||
|
||||
const visibleQuery = user == null ? [{
|
||||
visibility: { $in: ['public', 'home'] }
|
||||
}] : [{
|
||||
visibility: { $in: ['public', 'home', 'followers'] }
|
||||
}, {
|
||||
// myself (for specified/private)
|
||||
userId: user._id
|
||||
}, {
|
||||
// to me (for specified)
|
||||
visibleUserIds: { $in: [ user._id ] }
|
||||
}];
|
||||
|
||||
const query = {
|
||||
$and: [{
|
||||
deletedAt: null,
|
||||
|
||||
$or: [{
|
||||
$and: [{
|
||||
// フォローしている人の投稿
|
||||
$or: followQuery
|
||||
}, {
|
||||
// visible for me
|
||||
$or: visibleQuery
|
||||
}]
|
||||
}, {
|
||||
// public only
|
||||
visibility: 'public',
|
||||
|
||||
// リプライでない
|
||||
//replyId: null,
|
||||
|
||||
// local
|
||||
'_user.host': null
|
||||
}],
|
||||
|
||||
// hide
|
||||
userId: {
|
||||
$nin: hideUserIds
|
||||
},
|
||||
'_reply.userId': {
|
||||
$nin: hideUserIds
|
||||
},
|
||||
'_renote.userId': {
|
||||
$nin: hideUserIds
|
||||
},
|
||||
}]
|
||||
} as any;
|
||||
generateVisibilityQuery(query, user);
|
||||
generateMuteQuery(query, user);
|
||||
|
||||
/* TODO
|
||||
// MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。
|
||||
// つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。
|
||||
// for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws
|
||||
@@ -207,7 +134,7 @@ export default define(meta, async (ps, user) => {
|
||||
if (ps.includeMyRenotes === false) {
|
||||
query.$and.push({
|
||||
$or: [{
|
||||
userId: { $ne: user._id }
|
||||
userId: { $ne: user.id }
|
||||
}, {
|
||||
renoteId: null
|
||||
}, {
|
||||
@@ -223,7 +150,7 @@ export default define(meta, async (ps, user) => {
|
||||
if (ps.includeRenotedMyNotes === false) {
|
||||
query.$and.push({
|
||||
$or: [{
|
||||
'_renote.userId': { $ne: user._id }
|
||||
'_renote.userId': { $ne: user.id }
|
||||
}, {
|
||||
renoteId: null
|
||||
}, {
|
||||
@@ -251,40 +178,18 @@ export default define(meta, async (ps, user) => {
|
||||
}]
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
if (ps.withFiles || ps.mediaOnly) {
|
||||
query.$and.push({
|
||||
fileIds: { $exists: true, $ne: [] }
|
||||
});
|
||||
}
|
||||
|
||||
if (ps.sinceId) {
|
||||
sort._id = 1;
|
||||
query._id = {
|
||||
$gt: ps.sinceId
|
||||
};
|
||||
} else if (ps.untilId) {
|
||||
query._id = {
|
||||
$lt: ps.untilId
|
||||
};
|
||||
} else if (ps.sinceDate) {
|
||||
sort._id = 1;
|
||||
query.createdAt = {
|
||||
$gt: new Date(ps.sinceDate)
|
||||
};
|
||||
} else if (ps.untilDate) {
|
||||
query.createdAt = {
|
||||
$lt: new Date(ps.untilDate)
|
||||
};
|
||||
if (ps.withFiles) {
|
||||
query.andWhere('note.fileIds != \'{}\'');
|
||||
}
|
||||
//#endregion
|
||||
|
||||
const timeline = await Note.find(query, {
|
||||
limit: ps.limit,
|
||||
sort: sort
|
||||
});
|
||||
const timeline = await query.take(ps.limit).getMany();
|
||||
|
||||
activeUsersChart.update(user);
|
||||
if (user) {
|
||||
activeUsersChart.update(user);
|
||||
}
|
||||
|
||||
return await packMany(timeline, user);
|
||||
return await Notes.packMany(timeline, user);
|
||||
});
|
@@ -1,8 +1,7 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../misc/cafy-id';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import Favorite from '../../../../models/favorite';
|
||||
import NoteWatching from '../../../../models/note-watching';
|
||||
import { NoteFavorites, NoteWatchings } from '../../../../models';
|
||||
|
||||
export const meta = {
|
||||
stability: 'stable',
|
||||
@@ -19,7 +18,6 @@ export const meta = {
|
||||
params: {
|
||||
noteId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '対象の投稿のID',
|
||||
'en-US': 'Target note ID.'
|
||||
@@ -30,17 +28,19 @@ export const meta = {
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
const [favorite, watching] = await Promise.all([
|
||||
Favorite.count({
|
||||
userId: user._id,
|
||||
NoteFavorites.count({
|
||||
where: {
|
||||
userId: user.id,
|
||||
noteId: ps.noteId
|
||||
}, {
|
||||
limit: 1
|
||||
},
|
||||
take: 1
|
||||
}),
|
||||
NoteWatching.count({
|
||||
userId: user._id,
|
||||
NoteWatchings.count({
|
||||
where: {
|
||||
userId: user.id,
|
||||
noteId: ps.noteId
|
||||
}, {
|
||||
limit: 1
|
||||
},
|
||||
take: 1
|
||||
})
|
||||
]);
|
||||
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../misc/cafy-id';
|
||||
import Note from '../../../../models/note';
|
||||
import { getFriends } from '../../common/get-friends';
|
||||
import { packMany } from '../../../../models/note';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import activeUsersChart from '../../../../services/chart/active-users';
|
||||
import { getHideUserIds } from '../../common/get-hide-users';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { Notes, Followings } from '../../../../models';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { activeUsersChart } from '../../../../services/chart';
|
||||
import { Brackets } from 'typeorm';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -28,17 +29,15 @@ export const meta = {
|
||||
|
||||
sinceId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します'
|
||||
'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します'
|
||||
}
|
||||
},
|
||||
|
||||
untilId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します'
|
||||
'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -86,14 +85,6 @@ export const meta = {
|
||||
'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します'
|
||||
}
|
||||
},
|
||||
|
||||
mediaOnly: {
|
||||
validator: $.optional.bool,
|
||||
deprecated: true,
|
||||
desc: {
|
||||
'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
@@ -105,78 +96,24 @@ export const meta = {
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
const [followings, hideUserIds] = await Promise.all([
|
||||
// フォローを取得
|
||||
// Fetch following
|
||||
getFriends(user._id),
|
||||
|
||||
// 隠すユーザーを取得
|
||||
getHideUserIds(user)
|
||||
]);
|
||||
|
||||
//#region Construct query
|
||||
const sort = {
|
||||
_id: -1
|
||||
};
|
||||
const followingQuery = Followings.createQueryBuilder('following')
|
||||
.select('following.followeeId')
|
||||
.where('following.followerId = :followerId', { followerId: user.id });
|
||||
|
||||
const followQuery = followings.map(f => ({
|
||||
userId: f.id,
|
||||
const query = makePaginationQuery(Notes.createQueryBuilder('note'),
|
||||
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere(new Brackets(qb => { qb
|
||||
.where(`note.userId IN (${ followingQuery.getQuery() })`)
|
||||
.orWhere('note.userId = :meId', { meId: user.id });
|
||||
}))
|
||||
.leftJoinAndSelect('note.user', 'user')
|
||||
.setParameters(followingQuery.getParameters());
|
||||
|
||||
/*// リプライは含めない(ただし投稿者自身の投稿へのリプライ、自分の投稿へのリプライ、自分のリプライは含める)
|
||||
$or: [{
|
||||
// リプライでない
|
||||
replyId: null
|
||||
}, { // または
|
||||
// リプライだが返信先が投稿者自身の投稿
|
||||
$expr: {
|
||||
$eq: ['$_reply.userId', '$userId']
|
||||
}
|
||||
}, { // または
|
||||
// リプライだが返信先が自分(フォロワー)の投稿
|
||||
'_reply.userId': user._id
|
||||
}, { // または
|
||||
// 自分(フォロワー)が送信したリプライ
|
||||
userId: user._id
|
||||
}]*/
|
||||
}));
|
||||
|
||||
const visibleQuery = user == null ? [{
|
||||
visibility: { $in: [ 'public', 'home' ] }
|
||||
}] : [{
|
||||
visibility: { $in: [ 'public', 'home', 'followers' ] }
|
||||
}, {
|
||||
// myself (for specified/private)
|
||||
userId: user._id
|
||||
}, {
|
||||
// to me (for specified)
|
||||
visibleUserIds: { $in: [ user._id ] }
|
||||
}];
|
||||
|
||||
const query = {
|
||||
$and: [{
|
||||
deletedAt: null,
|
||||
|
||||
$and: [{
|
||||
// フォローしている人の投稿
|
||||
$or: followQuery
|
||||
}, {
|
||||
// visible for me
|
||||
$or: visibleQuery
|
||||
}],
|
||||
|
||||
// mute
|
||||
userId: {
|
||||
$nin: hideUserIds
|
||||
},
|
||||
'_reply.userId': {
|
||||
$nin: hideUserIds
|
||||
},
|
||||
'_renote.userId': {
|
||||
$nin: hideUserIds
|
||||
},
|
||||
}]
|
||||
} as any;
|
||||
generateVisibilityQuery(query, user);
|
||||
generateMuteQuery(query, user);
|
||||
|
||||
/* v11 TODO
|
||||
// MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。
|
||||
// つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。
|
||||
// for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws
|
||||
@@ -184,7 +121,7 @@ export default define(meta, async (ps, user) => {
|
||||
if (ps.includeMyRenotes === false) {
|
||||
query.$and.push({
|
||||
$or: [{
|
||||
userId: { $ne: user._id }
|
||||
userId: { $ne: user.id }
|
||||
}, {
|
||||
renoteId: null
|
||||
}, {
|
||||
@@ -200,7 +137,7 @@ export default define(meta, async (ps, user) => {
|
||||
if (ps.includeRenotedMyNotes === false) {
|
||||
query.$and.push({
|
||||
$or: [{
|
||||
'_renote.userId': { $ne: user._id }
|
||||
'_renote.userId': { $ne: user.id }
|
||||
}, {
|
||||
renoteId: null
|
||||
}, {
|
||||
@@ -227,43 +164,16 @@ export default define(meta, async (ps, user) => {
|
||||
poll: { $ne: null }
|
||||
}]
|
||||
});
|
||||
}
|
||||
}*/
|
||||
|
||||
const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
|
||||
|
||||
if (withFiles) {
|
||||
query.$and.push({
|
||||
fileIds: { $exists: true, $ne: [] }
|
||||
});
|
||||
}
|
||||
|
||||
if (ps.sinceId) {
|
||||
sort._id = 1;
|
||||
query._id = {
|
||||
$gt: ps.sinceId
|
||||
};
|
||||
} else if (ps.untilId) {
|
||||
query._id = {
|
||||
$lt: ps.untilId
|
||||
};
|
||||
} else if (ps.sinceDate) {
|
||||
sort._id = 1;
|
||||
query.createdAt = {
|
||||
$gt: new Date(ps.sinceDate)
|
||||
};
|
||||
} else if (ps.untilDate) {
|
||||
query.createdAt = {
|
||||
$lt: new Date(ps.untilDate)
|
||||
};
|
||||
if (ps.withFiles) {
|
||||
query.andWhere('note.fileIds != \'{}\'');
|
||||
}
|
||||
//#endregion
|
||||
|
||||
const timeline = await Note.find(query, {
|
||||
limit: ps.limit,
|
||||
sort: sort
|
||||
});
|
||||
const timeline = await query.take(ps.limit).getMany();
|
||||
|
||||
activeUsersChart.update(user);
|
||||
|
||||
return await packMany(timeline, user);
|
||||
return await Notes.packMany(timeline, user);
|
||||
});
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../misc/cafy-id';
|
||||
import Note from '../../../../models/note';
|
||||
import { packMany } from '../../../../models/note';
|
||||
import UserList from '../../../../models/user-list';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { getFriends } from '../../common/get-friends';
|
||||
import { getHideUserIds } from '../../common/get-hide-users';
|
||||
import { ApiError } from '../../error';
|
||||
import { UserLists, UserListJoinings, Notes } from '../../../../models';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { activeUsersChart } from '../../../../services/chart';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -21,7 +20,6 @@ export const meta = {
|
||||
params: {
|
||||
listId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': 'リストのID'
|
||||
}
|
||||
@@ -37,17 +35,15 @@ export const meta = {
|
||||
|
||||
sinceId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します'
|
||||
'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します'
|
||||
}
|
||||
},
|
||||
|
||||
untilId: {
|
||||
validator: $.optional.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します'
|
||||
'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -95,14 +91,6 @@ export const meta = {
|
||||
'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します'
|
||||
}
|
||||
},
|
||||
|
||||
mediaOnly: {
|
||||
validator: $.optional.bool,
|
||||
deprecated: true,
|
||||
desc: {
|
||||
'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
@@ -122,94 +110,28 @@ export const meta = {
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
const [list, followings, hideUserIds] = await Promise.all([
|
||||
// リストを取得
|
||||
// Fetch the list
|
||||
UserList.findOne({
|
||||
_id: ps.listId,
|
||||
userId: user._id
|
||||
}),
|
||||
|
||||
// フォローを取得
|
||||
// Fetch following
|
||||
getFriends(user._id, true, false),
|
||||
|
||||
// 隠すユーザーを取得
|
||||
getHideUserIds(user)
|
||||
]);
|
||||
const list = await UserLists.findOne({
|
||||
id: ps.listId,
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
if (list == null) {
|
||||
throw new ApiError(meta.errors.noSuchList);
|
||||
}
|
||||
|
||||
if (list.userIds.length == 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
//#region Construct query
|
||||
const sort = {
|
||||
_id: -1
|
||||
};
|
||||
const listQuery = UserListJoinings.createQueryBuilder('joining')
|
||||
.select('joining.userId')
|
||||
.where('joining.userListId = :userListId', { userListId: list.id });
|
||||
|
||||
const listQuery = list.userIds.map(u => ({
|
||||
userId: u,
|
||||
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
|
||||
.andWhere(`note.userId IN (${ listQuery.getQuery() })`)
|
||||
.leftJoinAndSelect('note.user', 'user')
|
||||
.setParameters(listQuery.getParameters());
|
||||
|
||||
/*// リプライは含めない(ただし投稿者自身の投稿へのリプライ、自分の投稿へのリプライ、自分のリプライは含める)
|
||||
$or: [{
|
||||
// リプライでない
|
||||
replyId: null
|
||||
}, { // または
|
||||
// リプライだが返信先が投稿者自身の投稿
|
||||
$expr: {
|
||||
$eq: ['$_reply.userId', '$userId']
|
||||
}
|
||||
}, { // または
|
||||
// リプライだが返信先が自分(フォロワー)の投稿
|
||||
'_reply.userId': user._id
|
||||
}, { // または
|
||||
// 自分(フォロワー)が送信したリプライ
|
||||
userId: user._id
|
||||
}]*/
|
||||
}));
|
||||
|
||||
const visibleQuery = [{
|
||||
visibility: { $in: ['public', 'home'] }
|
||||
}, {
|
||||
// myself (for specified/private)
|
||||
userId: user._id
|
||||
}, {
|
||||
// to me (for specified)
|
||||
visibleUserIds: { $in: [user._id] }
|
||||
}, {
|
||||
visibility: 'followers',
|
||||
userId: { $in: followings.map(f => f.id) }
|
||||
}];
|
||||
|
||||
const query = {
|
||||
$and: [{
|
||||
deletedAt: null,
|
||||
|
||||
$and: [{
|
||||
// リストに入っている人のタイムラインへの投稿
|
||||
$or: listQuery
|
||||
}, {
|
||||
// visible for me
|
||||
$or: visibleQuery
|
||||
}],
|
||||
|
||||
// mute
|
||||
userId: {
|
||||
$nin: hideUserIds
|
||||
},
|
||||
'_reply.userId': {
|
||||
$nin: hideUserIds
|
||||
},
|
||||
'_renote.userId': {
|
||||
$nin: hideUserIds
|
||||
},
|
||||
}]
|
||||
} as any;
|
||||
generateVisibilityQuery(query, user);
|
||||
|
||||
/* TODO
|
||||
// MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。
|
||||
// つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。
|
||||
// for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws
|
||||
@@ -217,7 +139,7 @@ export default define(meta, async (ps, user) => {
|
||||
if (ps.includeMyRenotes === false) {
|
||||
query.$and.push({
|
||||
$or: [{
|
||||
userId: { $ne: user._id }
|
||||
userId: { $ne: user.id }
|
||||
}, {
|
||||
renoteId: null
|
||||
}, {
|
||||
@@ -233,7 +155,7 @@ export default define(meta, async (ps, user) => {
|
||||
if (ps.includeRenotedMyNotes === false) {
|
||||
query.$and.push({
|
||||
$or: [{
|
||||
'_renote.userId': { $ne: user._id }
|
||||
'_renote.userId': { $ne: user.id }
|
||||
}, {
|
||||
renoteId: null
|
||||
}, {
|
||||
@@ -260,41 +182,16 @@ export default define(meta, async (ps, user) => {
|
||||
poll: { $ne: null }
|
||||
}]
|
||||
});
|
||||
}
|
||||
}*/
|
||||
|
||||
const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
|
||||
|
||||
if (withFiles) {
|
||||
query.$and.push({
|
||||
fileIds: { $exists: true, $ne: [] }
|
||||
});
|
||||
}
|
||||
|
||||
if (ps.sinceId) {
|
||||
sort._id = 1;
|
||||
query._id = {
|
||||
$gt: ps.sinceId
|
||||
};
|
||||
} else if (ps.untilId) {
|
||||
query._id = {
|
||||
$lt: ps.untilId
|
||||
};
|
||||
} else if (ps.sinceDate) {
|
||||
sort._id = 1;
|
||||
query.createdAt = {
|
||||
$gt: new Date(ps.sinceDate)
|
||||
};
|
||||
} else if (ps.untilDate) {
|
||||
query.createdAt = {
|
||||
$lt: new Date(ps.untilDate)
|
||||
};
|
||||
if (ps.withFiles) {
|
||||
query.andWhere('note.fileIds != \'{}\'');
|
||||
}
|
||||
//#endregion
|
||||
|
||||
const timeline = await Note.find(query, {
|
||||
limit: ps.limit,
|
||||
sort: sort
|
||||
});
|
||||
const timeline = await query.take(ps.limit).getMany();
|
||||
|
||||
return await packMany(timeline, user);
|
||||
activeUsersChart.update(user);
|
||||
|
||||
return await Notes.packMany(timeline, user);
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../../misc/cafy-id';
|
||||
import { ID } from '../../../../../misc/cafy-id';
|
||||
import define from '../../../define';
|
||||
import watch from '../../../../../services/note/watch';
|
||||
import { getNote } from '../../../common/getters';
|
||||
@@ -17,12 +17,11 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'account-write',
|
||||
kind: 'write:account',
|
||||
|
||||
params: {
|
||||
noteId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '対象の投稿のID',
|
||||
'en-US': 'Target note ID.'
|
||||
@@ -45,5 +44,5 @@ export default define(meta, async (ps, user) => {
|
||||
throw e;
|
||||
});
|
||||
|
||||
await watch(user._id, note);
|
||||
await watch(user.id, note);
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../../misc/cafy-id';
|
||||
import { ID } from '../../../../../misc/cafy-id';
|
||||
import define from '../../../define';
|
||||
import unwatch from '../../../../../services/note/unwatch';
|
||||
import { getNote } from '../../../common/getters';
|
||||
@@ -17,12 +17,11 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'account-write',
|
||||
kind: 'write:account',
|
||||
|
||||
params: {
|
||||
noteId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '対象の投稿のID',
|
||||
'en-US': 'Target note ID.'
|
||||
@@ -45,5 +44,5 @@ export default define(meta, async (ps, user) => {
|
||||
throw e;
|
||||
});
|
||||
|
||||
await unwatch(user._id, note);
|
||||
await unwatch(user.id, note);
|
||||
});
|
||||
|
Reference in New Issue
Block a user