Merge pull request 'backend: Fix various lints in services/note' (#206) from backend-services-note into main

Reviewed-on: FoundKeyGang/FoundKey#206
This commit is contained in:
Norm 2022-10-21 19:29:41 +00:00
commit c36cca30cb
21 changed files with 113 additions and 117 deletions

View file

@ -24,7 +24,7 @@ export type Source = {
db?: number; db?: number;
prefix?: string; prefix?: string;
}; };
elasticsearch: { elasticsearch?: {
host: string; host: string;
port: number; port: number;
ssl?: boolean; ssl?: boolean;

View file

@ -1,5 +1,5 @@
import { CacheableRemoteUser } from '@/models/entities/user.js'; import { CacheableRemoteUser } from '@/models/entities/user.js';
import create from '@/services/note/reaction/create.js'; import { createReaction } from '@/services/note/reaction/create.js';
import { ILike, getApId } from '../type.js'; import { ILike, getApId } from '../type.js';
import { fetchNote, extractEmojis } from '../models/note.js'; import { fetchNote, extractEmojis } from '../models/note.js';
@ -11,7 +11,7 @@ export default async (actor: CacheableRemoteUser, activity: ILike) => {
await extractEmojis(activity.tag || [], actor.host).catch(() => null); await extractEmojis(activity.tag || [], actor.host).catch(() => null);
return await create(actor, note, activity._misskey_reaction || activity.content || activity.name).catch(e => { return await createReaction(actor, note, activity._misskey_reaction || activity.content || activity.name).catch(e => {
if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') { if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') {
return 'skip: already reacted'; return 'skip: already reacted';
} else { } else {

View file

@ -1,5 +1,5 @@
import { CacheableRemoteUser } from '@/models/entities/user.js'; import { CacheableRemoteUser } from '@/models/entities/user.js';
import deleteReaction from '@/services/note/reaction/delete.js'; import { deleteReaction } from '@/services/note/reaction/delete.js';
import { ILike, getApId } from '@/remote/activitypub/type.js'; import { ILike, getApId } from '@/remote/activitypub/type.js';
import { fetchNote } from '@/remote/activitypub/models/note.js'; import { fetchNote } from '@/remote/activitypub/models/note.js';

View file

@ -4,7 +4,7 @@ import config from '@/config/index.js';
import post from '@/services/note/create.js'; import post from '@/services/note/create.js';
import { CacheableRemoteUser } from '@/models/entities/user.js'; import { CacheableRemoteUser } from '@/models/entities/user.js';
import { unique, toArray, toSingle } from '@/prelude/array.js'; import { unique, toArray, toSingle } from '@/prelude/array.js';
import vote from '@/services/note/polls/vote.js'; import { vote } from '@/services/note/polls/vote.js';
import { DriveFile } from '@/models/entities/drive-file.js'; import { DriveFile } from '@/models/entities/drive-file.js';
import { deliverQuestionUpdate } from '@/services/note/polls/update.js'; import { deliverQuestionUpdate } from '@/services/note/polls/update.js';
import { extractDbHost, toPuny } from '@/misc/convert-host.js'; import { extractDbHost, toPuny } from '@/misc/convert-host.js';

View file

@ -1,4 +1,4 @@
import readNote from '@/services/note/read.js'; import { readNote } from '@/services/note/read.js';
import { Antennas, Notes, AntennaNotes } from '@/models/index.js'; import { Antennas, Notes, AntennaNotes } from '@/models/index.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';

View file

@ -1,7 +1,7 @@
import { Brackets } from 'typeorm'; import { Brackets } from 'typeorm';
import { notificationTypes } from 'foundkey-js'; import { notificationTypes } from 'foundkey-js';
import { Notifications, Followings, Mutings, Users, UserProfiles } from '@/models/index.js'; import { Notifications, Followings, Mutings, Users, UserProfiles } from '@/models/index.js';
import read from '@/services/note/read.js'; import { readNote } from '@/services/note/read.js';
import { readNotification } from '../../common/read-notification.js'; import { readNotification } from '../../common/read-notification.js';
import define from '../../define.js'; import define from '../../define.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js';
@ -137,7 +137,7 @@ export default define(meta, paramDef, async (ps, user) => {
const notes = notifications.filter(notification => ['mention', 'reply', 'quote'].includes(notification.type)).map(notification => notification.note!); const notes = notifications.filter(notification => ['mention', 'reply', 'quote'].includes(notification.type)).map(notification => notification.note!);
if (notes.length > 0) { if (notes.length > 0) {
read(user.id, notes); readNote(user.id, notes);
} }
return await Notifications.packMany(notifications, user.id); return await Notifications.packMany(notifications, user.id);

View file

@ -1,6 +1,6 @@
import { Brackets } from 'typeorm'; import { Brackets } from 'typeorm';
import { noteVisibilities } from 'foundkey-js'; import { noteVisibilities } from 'foundkey-js';
import read from '@/services/note/read.js'; import { readNote } from '@/services/note/read.js';
import { Notes, Followings } from '@/models/index.js'; import { Notes, Followings } from '@/models/index.js';
import define from '../../define.js'; import define from '../../define.js';
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
@ -79,7 +79,7 @@ export default define(meta, paramDef, async (ps, user) => {
const mentions = await query.take(ps.limit).getMany(); const mentions = await query.take(ps.limit).getMany();
read(user.id, mentions); readNote(user.id, mentions);
return await Notes.packMany(mentions, user); return await Notes.packMany(mentions, user);
}); });

View file

@ -1,4 +1,4 @@
import createReaction from '@/services/note/reaction/create.js'; import { createReaction } from '@/services/note/reaction/create.js';
import define from '../../../define.js'; import define from '../../../define.js';
import { getNote } from '../../../common/getters.js'; import { getNote } from '../../../common/getters.js';
import { ApiError } from '../../../error.js'; import { ApiError } from '../../../error.js';

View file

@ -1,4 +1,4 @@
import deleteReaction from '@/services/note/reaction/delete.js'; import { deleteReaction } from '@/services/note/reaction/delete.js';
import { SECOND, HOUR } from '@/const.js'; import { SECOND, HOUR } from '@/const.js';
import define from '../../../define.js'; import define from '../../../define.js';
import { getNote } from '../../../common/getters.js'; import { getNote } from '../../../common/getters.js';

View file

@ -1,7 +1,7 @@
import { noteNotificationTypes } from 'foundkey-js'; import { noteNotificationTypes } from 'foundkey-js';
import { Notes, NoteThreadMutings, NoteWatchings } from '@/models/index.js'; import { Notes, NoteThreadMutings, NoteWatchings } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js'; import { genId } from '@/misc/gen-id.js';
import readNote from '@/services/note/read.js'; import { readNote } from '@/services/note/read.js';
import define from '../../../define.js'; import define from '../../../define.js';
import { getNote } from '../../../common/getters.js'; import { getNote } from '../../../common/getters.js';
import { ApiError } from '../../../error.js'; import { ApiError } from '../../../error.js';

View file

@ -1,4 +1,4 @@
import watch from '@/services/note/watch.js'; import { watch } from '@/services/note/watch.js';
import define from '../../../define.js'; import define from '../../../define.js';
import { getNote } from '../../../common/getters.js'; import { getNote } from '../../../common/getters.js';
import { ApiError } from '../../../error.js'; import { ApiError } from '../../../error.js';

View file

@ -1,4 +1,4 @@
import unwatch from '@/services/note/unwatch.js'; import { unwatch } from '@/services/note/unwatch.js';
import define from '../../../define.js'; import define from '../../../define.js';
import { getNote } from '../../../common/getters.js'; import { getNote } from '../../../common/getters.js';
import { ApiError } from '../../../error.js'; import { ApiError } from '../../../error.js';

View file

@ -1,6 +1,6 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import * as websocket from 'websocket'; import * as websocket from 'websocket';
import readNote from '@/services/note/read.js'; import { readNote } from '@/services/note/read.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
import { Channel as ChannelModel } from '@/models/entities/channel.js'; import { Channel as ChannelModel } from '@/models/entities/channel.js';
import { Followings, Mutings, RenoteMutings, UserProfiles, ChannelFollowings, Blockings } from '@/models/index.js'; import { Followings, Mutings, RenoteMutings, UserProfiles, ChannelFollowings, Blockings } from '@/models/index.js';

View file

@ -35,6 +35,7 @@ import { webhookDeliver } from '@/queue/index.js';
import { Cache } from '@/misc/cache.js'; import { Cache } from '@/misc/cache.js';
import { UserProfile } from '@/models/entities/user-profile.js'; import { UserProfile } from '@/models/entities/user-profile.js';
import { getActiveWebhooks } from '@/misc/webhook-cache.js'; import { getActiveWebhooks } from '@/misc/webhook-cache.js';
import { IActivity } from '@/remote/activitypub/type.js';
import { updateHashtags } from '../update-hashtag.js'; import { updateHashtags } from '../update-hashtag.js';
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js'; import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js';
import { createNotification } from '../create-notification.js'; import { createNotification } from '../create-notification.js';
@ -59,14 +60,14 @@ class NotificationManager {
this.queue = []; this.queue = [];
} }
public push(notifiee: ILocalUser['id'], reason: NotificationType) { public push(notifiee: ILocalUser['id'], reason: NotificationType): void {
// 自分自身へは通知しない // No notification to yourself.
if (this.notifier.id === notifiee) return; if (this.notifier.id === notifiee) return;
const exist = this.queue.find(x => x.target === notifiee); const exist = this.queue.find(x => x.target === notifiee);
if (exist) { if (exist) {
// 「メンションされているかつ返信されている」場合は、メンションとしての通知ではなく返信としての通知にする // If you have been "mentioned and replied to," make the notification as a reply, not as a mention.
if (reason !== 'mention') { if (reason !== 'mention') {
exist.reason = reason; exist.reason = reason;
} }
@ -78,7 +79,7 @@ class NotificationManager {
} }
} }
public async deliver() { public async deliver(): Promise<void> {
for (const x of this.queue) { for (const x of this.queue) {
// check if the sender or thread are muted // check if the sender or thread are muted
const userMuted = await Mutings.findOneBy({ const userMuted = await Mutings.findOneBy({
@ -119,7 +120,7 @@ type Option = {
poll?: IPoll | null; poll?: IPoll | null;
localOnly?: boolean | null; localOnly?: boolean | null;
cw?: string | null; cw?: string | null;
visibility?: string; visibility?: 'home' | 'public' | 'followers' | 'specified';
visibleUsers?: MinimumUser[] | null; visibleUsers?: MinimumUser[] | null;
channel?: Channel | null; channel?: Channel | null;
apMentions?: MinimumUser[] | null; apMentions?: MinimumUser[] | null;
@ -130,9 +131,9 @@ type Option = {
app?: App | null; app?: App | null;
}; };
export default async (user: { id: User['id']; username: User['username']; host: User['host']; isSilenced: User['isSilenced']; createdAt: User['createdAt']; }, data: Option, silent = false) => new Promise<Note>(async (res, rej) => { export default async (user: { id: User['id']; username: User['username']; host: User['host']; isSilenced: User['isSilenced']; createdAt: User['createdAt']; }, data: Option, silent = false): Promise<Note> => new Promise<Note>(async (res, rej) => {
// チャンネル外にリプライしたら対象のスコープに合わせる // If you reply outside the channel, adjust to the scope of the target
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで) // (I think this could be done client-side, but server-side for now)
if (data.reply && data.channel && data.reply.channelId !== data.channel.id) { if (data.reply && data.channel && data.reply.channelId !== data.channel.id) {
if (data.reply.channelId) { if (data.reply.channelId) {
data.channel = await Channels.findOneBy({ id: data.reply.channelId }); data.channel = await Channels.findOneBy({ id: data.reply.channelId });
@ -141,9 +142,9 @@ export default async (user: { id: User['id']; username: User['username']; host:
} }
} }
// チャンネル内にリプライしたら対象のスコープに合わせる // When you reply to a channel, adjust the scope to that of the target.
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで) // (I think this could be done client-side, but server-side for now)
if (data.reply && (data.channel == null) && data.reply.channelId) { if (data.reply?.channelId && (data.channel == null)) {
data.channel = await Channels.findOneBy({ id: data.reply.channelId }); data.channel = await Channels.findOneBy({ id: data.reply.channelId });
} }
@ -154,32 +155,32 @@ export default async (user: { id: User['id']; username: User['username']; host:
if (data.channel != null) data.visibleUsers = []; if (data.channel != null) data.visibleUsers = [];
if (data.channel != null) data.localOnly = true; if (data.channel != null) data.localOnly = true;
// サイレンス // silence
if (user.isSilenced && data.visibility === 'public' && data.channel == null) { if (user.isSilenced && data.visibility === 'public' && data.channel == null) {
data.visibility = 'home'; data.visibility = 'home';
} }
// Renote対象が「ホームまたは全体」以外の公開範囲ならreject // Reject if the target of the renote is not Home or Public.
if (data.renote && data.renote.visibility !== 'public' && data.renote.visibility !== 'home' && data.renote.userId !== user.id) { if (data.renote && data.renote.visibility !== 'public' && data.renote.visibility !== 'home' && data.renote.userId !== user.id) {
return rej('Renote target is not public or home'); return rej('Renote target is not public or home');
} }
// Renote対象がpublicではないならhomeにする // If the target of the renote is not public, make it home.
if (data.renote && data.renote.visibility !== 'public' && data.visibility === 'public') { if (data.renote && data.renote.visibility !== 'public' && data.visibility === 'public') {
data.visibility = 'home'; data.visibility = 'home';
} }
// Renote対象がfollowersならfollowersにする // If the target of Renote is followers, make it followers.
if (data.renote && data.renote.visibility === 'followers') { if (data.renote && data.renote.visibility === 'followers') {
data.visibility = 'followers'; data.visibility = 'followers';
} }
// ローカルのみをRenoteしたらローカルのみにする // Ff the original note is local-only, make the renote also local-only.
if (data.renote && data.renote.localOnly && data.channel == null) { if (data.renote && data.renote.localOnly && data.channel == null) {
data.localOnly = true; data.localOnly = true;
} }
// ローカルのみにリプライしたらローカルのみにする // If you reply to local only, make it local only.
if (data.reply && data.reply.localOnly && data.channel == null) { if (data.reply && data.reply.localOnly && data.channel == null) {
data.localOnly = true; data.localOnly = true;
} }
@ -196,10 +197,10 @@ export default async (user: { id: User['id']; username: User['username']; host:
// Parse MFM if needed // Parse MFM if needed
if (!tags || !emojis || !mentionedUsers) { if (!tags || !emojis || !mentionedUsers) {
const tokens = data.text ? mfm.parse(data.text)! : []; const tokens = data.text ? mfm.parse(data.text) : [];
const cwTokens = data.cw ? mfm.parse(data.cw)! : []; const cwTokens = data.cw ? mfm.parse(data.cw) : [];
const choiceTokens = data.poll && data.poll.choices const choiceTokens = data.poll?.choices
? concat(data.poll.choices.map(choice => mfm.parse(choice)!)) ? concat(data.poll.choices.map(choice => mfm.parse(choice)))
: []; : [];
const combinedTokens = tokens.concat(cwTokens).concat(choiceTokens); const combinedTokens = tokens.concat(cwTokens).concat(choiceTokens);
@ -213,8 +214,8 @@ export default async (user: { id: User['id']; username: User['username']; host:
tags = tags.filter(tag => Array.from(tag || '').length <= 128).splice(0, 32); tags = tags.filter(tag => Array.from(tag || '').length <= 128).splice(0, 32);
if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply!.userId)) { if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply?.userId)) {
mentionedUsers.push(await Users.findOneByOrFail({ id: data.reply!.userId })); mentionedUsers.push(await Users.findOneByOrFail({ id: data.reply.userId }));
} }
if (data.visibility === 'specified') { if (data.visibility === 'specified') {
@ -226,8 +227,8 @@ export default async (user: { id: User['id']; username: User['username']; host:
} }
} }
if (data.reply && !data.visibleUsers.some(x => x.id === data.reply!.userId)) { if (data.reply && !data.visibleUsers.some(x => x.id === data.reply?.userId)) {
data.visibleUsers.push(await Users.findOneByOrFail({ id: data.reply!.userId })); data.visibleUsers.push(await Users.findOneByOrFail({ id: data.reply.userId }));
} }
} }
@ -235,7 +236,7 @@ export default async (user: { id: User['id']; username: User['username']; host:
res(note); res(note);
// 統計を更新 // Update Statistics
notesChart.update(note, true); notesChart.update(note, true);
perUserNotesChart.update(user, note, true); perUserNotesChart.update(user, note, true);
@ -247,7 +248,7 @@ export default async (user: { id: User['id']; username: User['username']; host:
}); });
} }
// ハッシュタグ更新 // Hashtag Update
if (data.visibility === 'public' || data.visibility === 'home') { if (data.visibility === 'public' || data.visibility === 'home') {
updateHashtags(user, tags); updateHashtags(user, tags);
} }
@ -301,7 +302,7 @@ export default async (user: { id: User['id']; username: User['username']; host:
saveReply(data.reply, note); saveReply(data.reply, note);
} }
// この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき // When there is no re-note of the specified note by the specified user except for this post
if (data.renote && (await countSameRenotes(user.id, data.renote.id, note.id) === 0)) { if (data.renote && (await countSameRenotes(user.id, data.renote.id, note.id) === 0)) {
incRenoteCount(data.renote); incRenoteCount(data.renote);
} }
@ -319,12 +320,12 @@ export default async (user: { id: User['id']; username: User['username']; host:
if (!silent) { if (!silent) {
if (Users.isLocalUser(user)) activeUsersChart.write(user); if (Users.isLocalUser(user)) activeUsersChart.write(user);
// 未読通知を作成 // Create unread notifications
if (data.visibility === 'specified') { if (data.visibility === 'specified') {
if (data.visibleUsers == null) throw new Error('invalid param'); if (data.visibleUsers == null) throw new Error('invalid param');
for (const u of data.visibleUsers) { for (const u of data.visibleUsers) {
// ローカルユーザーのみ // Local users only
if (!Users.isLocalUser(u)) continue; if (!Users.isLocalUser(u)) continue;
insertNoteUnread(u.id, note, { insertNoteUnread(u.id, note, {
@ -334,7 +335,7 @@ export default async (user: { id: User['id']; username: User['username']; host:
} }
} else { } else {
for (const u of mentionedUsers) { for (const u of mentionedUsers) {
// ローカルユーザーのみ // Local users only
if (!Users.isLocalUser(u)) continue; if (!Users.isLocalUser(u)) continue;
insertNoteUnread(u.id, note, { insertNoteUnread(u.id, note, {
@ -423,24 +424,24 @@ export default async (user: { id: User['id']; username: User['username']; host:
const noteActivity = await renderNoteOrRenoteActivity(data, note); const noteActivity = await renderNoteOrRenoteActivity(data, note);
const dm = new DeliverManager(user, noteActivity); const dm = new DeliverManager(user, noteActivity);
// メンションされたリモートユーザーに配送 // Delivered to remote users who have been mentioned
for (const u of mentionedUsers.filter(u => Users.isRemoteUser(u))) { for (const u of mentionedUsers.filter(u => Users.isRemoteUser(u))) {
dm.addDirectRecipe(u as IRemoteUser); dm.addDirectRecipe(u as IRemoteUser);
} }
// 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送 // If the post is a reply and the poster is a local user and the poster of the post to which you are replying is a remote user, deliver
if (data.reply && data.reply.userHost !== null) { if (data.reply && data.reply.userHost !== null) {
const u = await Users.findOneBy({ id: data.reply.userId }); const u = await Users.findOneBy({ id: data.reply.userId });
if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u); if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u);
} }
// 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送 // If the post is a Renote and the poster is a local user and the poster of the original Renote post is a remote user, deliver
if (data.renote && data.renote.userHost !== null) { if (data.renote && data.renote.userHost !== null) {
const u = await Users.findOneBy({ id: data.renote.userId }); const u = await Users.findOneBy({ id: data.renote.userId });
if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u); if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u);
} }
// フォロワーに配送 // Deliver to followers
if (['public', 'home', 'followers'].includes(note.visibility)) { if (['public', 'home', 'followers'].includes(note.visibility)) {
dm.addFollowersRecipe(); dm.addFollowersRecipe();
} }
@ -461,23 +462,23 @@ export default async (user: { id: User['id']; username: User['username']; host:
lastNotedAt: new Date(), lastNotedAt: new Date(),
}); });
Notes.countBy({ const count = await Notes.countBy({
userId: user.id, userId: user.id,
channelId: data.channel.id, channelId: data.channel.id,
}).then(count => {
// この処理が行われるのはノート作成後なので、ノートが一つしかなかったら最初の投稿だと判断できる
// TODO: とはいえノートを削除して何回も投稿すればその分だけインクリメントされる雑さもあるのでどうにかしたい
if (count === 1) {
Channels.increment({ id: data.channel!.id }, 'usersCount', 1);
}
}); });
// This process takes place after the note is created, so if there is only one note, you can determine that it is the first submission.
// TODO: but there's also the messiness of deleting a note and posting it multiple times, which is incremented by the number of times it's posted, so I'd like to do something about that.
if (count === 1) {
Channels.increment({ id: data.channel.id }, 'usersCount', 1);
}
} }
// Register to search database // Register to search database
index(note); index(note);
}); });
async function renderNoteOrRenoteActivity(data: Option, note: Note) { async function renderNoteOrRenoteActivity(data: Option, note: Note): Promise<IActivity | null> {
if (data.localOnly) return null; if (data.localOnly) return null;
const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0) const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0)
@ -487,7 +488,7 @@ async function renderNoteOrRenoteActivity(data: Option, note: Note) {
return renderActivity(content); return renderActivity(content);
} }
function incRenoteCount(renote: Note) { function incRenoteCount(renote: Note): void {
Notes.createQueryBuilder().update() Notes.createQueryBuilder().update()
.set({ .set({
renoteCount: () => '"renoteCount" + 1', renoteCount: () => '"renoteCount" + 1',
@ -497,19 +498,17 @@ function incRenoteCount(renote: Note) {
.execute(); .execute();
} }
async function insertNote(user: { id: User['id']; host: User['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) { async function insertNote(user: { id: User['id']; host: User['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]): Promise<Note> {
const createdAt = data.createdAt ?? new Date();
const insert = new Note({ const insert = new Note({
id: genId(data.createdAt!), id: genId(createdAt),
createdAt: data.createdAt!, createdAt,
fileIds: data.files ? data.files.map(file => file.id) : [], fileIds: data.files?.map(file => file.id) ?? [],
replyId: data.reply ? data.reply.id : null, replyId: data.reply?.id ?? null,
renoteId: data.renote ? data.renote.id : null, renoteId: data.renote?.id ?? null,
channelId: data.channel ? data.channel.id : null, channelId: data.channel?.id ?? null,
threadId: data.reply threadId: data.reply?.threadId ?? data.reply?.id ?? null,
? data.reply.threadId
? data.reply.threadId
: data.reply.id
: null,
name: data.name, name: data.name,
text: data.text, text: data.text,
hasPoll: data.poll != null, hasPoll: data.poll != null,
@ -517,15 +516,13 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O
tags: tags.map(tag => normalizeForSearch(tag)), tags: tags.map(tag => normalizeForSearch(tag)),
emojis, emojis,
userId: user.id, userId: user.id,
localOnly: data.localOnly!, localOnly: data.localOnly ?? false,
visibility: data.visibility as any, visibility: data.visibility,
visibleUserIds: data.visibility === 'specified' visibleUserIds: data.visibility === 'specified'
? data.visibleUsers ? data.visibleUsers?.map(u => u.id) ?? []
? data.visibleUsers.map(u => u.id)
: []
: [], : [],
attachedFileTypes: data.files ? data.files.map(file => file.type) : [], attachedFileTypes: data.files?.map(file => file.type) ?? [],
// denormalized data below // denormalized data below
replyUserId: data.reply?.userId, replyUserId: data.reply?.userId,
@ -543,29 +540,26 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O
insert.mentions = mentionedUsers.map(u => u.id); insert.mentions = mentionedUsers.map(u => u.id);
} }
// 投稿を作成 // Create a post
try { try {
if (insert.hasPoll) { // Start transaction
// Start transaction await db.transaction(async transactionalEntityManager => {
await db.transaction(async transactionalEntityManager => { await transactionalEntityManager.insert(Note, insert);
await transactionalEntityManager.insert(Note, insert);
if (data.poll != null) {
const poll = new Poll({ const poll = new Poll({
noteId: insert.id, noteId: insert.id,
choices: data.poll!.choices, choices: data.poll.choices,
expiresAt: data.poll!.expiresAt, expiresAt: data.poll.expiresAt,
multiple: data.poll!.multiple, multiple: data.poll.multiple,
votes: new Array(data.poll!.choices.length).fill(0), votes: new Array(data.poll.choices.length).fill(0),
noteVisibility: insert.visibility, noteVisibility: insert.visibility,
userId: user.id, userId: user.id,
userHost: user.host, userHost: user.host,
}); });
await transactionalEntityManager.insert(Poll, poll); await transactionalEntityManager.insert(Poll, poll);
}); }
} else { });
await Notes.insert(insert);
}
return insert; return insert;
} catch (e) { } catch (e) {
@ -582,10 +576,10 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O
} }
} }
function index(note: Note) { function index(note: Note): void {
if (note.text == null || config.elasticsearch == null) return; if (note.text == null || config.elasticsearch == null) return;
es!.index({ es.index({
index: config.elasticsearch.index || 'misskey_note', index: config.elasticsearch.index || 'misskey_note',
id: note.id.toString(), id: note.id.toString(),
body: { body: {
@ -596,7 +590,7 @@ function index(note: Note) {
}); });
} }
async function notifyToWatchersOfRenotee(renote: Note, user: { id: User['id']; }, nm: NotificationManager, type: NotificationType) { async function notifyToWatchersOfRenotee(renote: Note, user: { id: User['id']; }, nm: NotificationManager, type: NotificationType): Promise<void> {
const watchers = await NoteWatchings.findBy({ const watchers = await NoteWatchings.findBy({
noteId: renote.id, noteId: renote.id,
userId: Not(user.id), userId: Not(user.id),
@ -607,7 +601,7 @@ async function notifyToWatchersOfRenotee(renote: Note, user: { id: User['id']; }
} }
} }
async function notifyToWatchersOfReplyee(reply: Note, user: { id: User['id']; }, nm: NotificationManager) { async function notifyToWatchersOfReplyee(reply: Note, user: { id: User['id']; }, nm: NotificationManager): Promise<void> {
const watchers = await NoteWatchings.findBy({ const watchers = await NoteWatchings.findBy({
noteId: reply.id, noteId: reply.id,
userId: Not(user.id), userId: Not(user.id),
@ -618,7 +612,7 @@ async function notifyToWatchersOfReplyee(reply: Note, user: { id: User['id']; },
} }
} }
async function createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, nm: NotificationManager) { async function createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, nm: NotificationManager): Promise<void> {
for (const u of mentionedUsers.filter(u => Users.isLocalUser(u))) { for (const u of mentionedUsers.filter(u => Users.isLocalUser(u))) {
const threadMuted = await NoteThreadMutings.findOneBy({ const threadMuted = await NoteThreadMutings.findOneBy({
userId: u.id, userId: u.id,
@ -653,11 +647,11 @@ async function createMentionedEvents(mentionedUsers: MinimumUser[], note: Note,
} }
} }
function saveReply(reply: Note, note: Note) { function saveReply(reply: Note, note: Note): void {
Notes.increment({ id: reply.id }, 'repliesCount', 1); Notes.increment({ id: reply.id }, 'repliesCount', 1);
} }
function incNotesCountOfUser(user: { id: User['id']; }) { function incNotesCountOfUser(user: { id: User['id']; }): void {
Users.createQueryBuilder().update() Users.createQueryBuilder().update()
.set({ .set({
updatedAt: new Date(), updatedAt: new Date(),
@ -668,7 +662,7 @@ function incNotesCountOfUser(user: { id: User['id']; }) {
} }
async function extractMentionedUsers(user: { host: User['host']; }, tokens: mfm.MfmNode[]): Promise<User[]> { async function extractMentionedUsers(user: { host: User['host']; }, tokens: mfm.MfmNode[]): Promise<User[]> {
if (tokens == null) return []; if (tokens.length === 0) return [];
const mentions = extractMentions(tokens); const mentions = extractMentions(tokens);

View file

@ -1,4 +1,4 @@
import { Brackets, In, IsNull, Not } from 'typeorm'; import { Brackets, FindOptionsWhere, In, IsNull, Not } from 'typeorm';
import { publishNoteStream } from '@/services/stream.js'; import { publishNoteStream } from '@/services/stream.js';
import renderDelete from '@/remote/activitypub/renderer/delete.js'; import renderDelete from '@/remote/activitypub/renderer/delete.js';
import renderAnnounce from '@/remote/activitypub/renderer/announce.js'; import renderAnnounce from '@/remote/activitypub/renderer/announce.js';
@ -41,10 +41,12 @@ export default async function(user: { id: User['id']; uri: User['uri']; host: Us
if (Users.isLocalUser(user) && !note.localOnly) { if (Users.isLocalUser(user) && !note.localOnly) {
let renote: Note | null = null; let renote: Note | null = null;
// if deletd note is renote // if deleted note is renote
if (isPureRenote(note)) { if (isPureRenote(note)) {
renote = await Notes.findOneBy({ renote = await Notes.findOneBy({
id: note.renoteId, // isPureRenote checks if note.renoteId is null already, so renoteId should be non-null.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: note.renoteId!,
}); });
} }
@ -106,7 +108,7 @@ async function findCascadingNotes(note: Note): Promise<Note[]> {
} }
async function getMentionedRemoteUsers(note: Note): Promise<IRemoteUser[]> { async function getMentionedRemoteUsers(note: Note): Promise<IRemoteUser[]> {
const where = [] as any[]; const where: FindOptionsWhere<User>[] = [];
// mention / reply / dm // mention / reply / dm
if (note.mentions.length > 0) { if (note.mentions.length > 0) {

View file

@ -6,7 +6,7 @@ import { PollVotes, NoteWatchings, Polls, Blockings, NoteThreadMutings } from '@
import { genId } from '@/misc/gen-id.js'; import { genId } from '@/misc/gen-id.js';
import { createNotification } from '@/services/create-notification.js'; import { createNotification } from '@/services/create-notification.js';
export default async function(user: CacheableUser, note: Note, choice: number) { export async function vote(user: CacheableUser, note: Note, choice: number): Promise<void> {
const poll = await Polls.findOneBy({ noteId: note.id }); const poll = await Polls.findOneBy({ noteId: note.id });
if (poll == null) throw new Error('poll not found'); if (poll == null) throw new Error('poll not found');

View file

@ -13,9 +13,9 @@ import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js
import { NoteReaction } from '@/models/entities/note-reaction.js'; import { NoteReaction } from '@/models/entities/note-reaction.js';
import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js';
import { createNotification } from '@/services/create-notification.js'; import { createNotification } from '@/services/create-notification.js';
import deleteReaction from './delete.js'; import { deleteReaction } from './delete.js';
export default async (user: { id: User['id']; host: User['host']; }, note: Note, reaction?: string) => { export async function createReaction(user: { id: User['id']; host: User['host']; }, note: Note, reaction?: string): Promise<void> {
// Check blocking // Check blocking
if (note.userId !== user.id) { if (note.userId !== user.id) {
const block = await Blockings.findOneBy({ const block = await Blockings.findOneBy({
@ -148,4 +148,4 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note,
dm.execute(); dm.execute();
} }
//#endregion //#endregion
}; }

View file

@ -9,7 +9,7 @@ import { Note } from '@/models/entities/note.js';
import { NoteReactions, Users, Notes } from '@/models/index.js'; import { NoteReactions, Users, Notes } from '@/models/index.js';
import { decodeReaction } from '@/misc/reaction-lib.js'; import { decodeReaction } from '@/misc/reaction-lib.js';
export default async (user: { id: User['id']; host: User['host']; }, note: Note) => { export async function deleteReaction(user: { id: User['id']; host: User['host']; }, note: Note): Promise<void> {
// if already unreacted // if already unreacted
const exist = await NoteReactions.findOneBy({ const exist = await NoteReactions.findOneBy({
noteId: note.id, noteId: note.id,
@ -55,4 +55,4 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note)
dm.execute(); dm.execute();
} }
//#endregion //#endregion
}; }

View file

@ -12,14 +12,14 @@ import { Packed } from '@/misc/schema.js';
/** /**
* Mark notes as read * Mark notes as read
*/ */
export default async function( export async function readNote(
userId: User['id'], userId: User['id'],
notes: (Note | Packed<'Note'>)[], notes: (Note | Packed<'Note'>)[],
info?: { info?: {
following: Set<User['id']>; following: Set<User['id']>;
followingChannels: Set<Channel['id']>; followingChannels: Set<Channel['id']>;
}, },
) { ): Promise<void> {
const following = info?.following ? info.following : new Set<string>((await Followings.find({ const following = info?.following ? info.following : new Set<string>((await Followings.find({
where: { where: {
followerId: userId, followerId: userId,

View file

@ -2,9 +2,9 @@ import { User } from '@/models/entities/user.js';
import { NoteWatchings } from '@/models/index.js'; import { NoteWatchings } from '@/models/index.js';
import { Note } from '@/models/entities/note.js'; import { Note } from '@/models/entities/note.js';
export default async (me: User['id'], note: Note) => { export async function unwatch(me: User['id'], note: Note): Promise<void> {
await NoteWatchings.delete({ await NoteWatchings.delete({
noteId: note.id, noteId: note.id,
userId: me, userId: me,
}); });
}; }

View file

@ -4,8 +4,8 @@ import { NoteWatchings } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js'; import { genId } from '@/misc/gen-id.js';
import { NoteWatching } from '@/models/entities/note-watching.js'; import { NoteWatching } from '@/models/entities/note-watching.js';
export default async (me: User['id'], note: Note) => { export async function watch(me: User['id'], note: Note): Promise<void> {
// 自分の投稿はwatchできない // User can't watch their own posts.
if (me === note.userId) { if (me === note.userId) {
return; return;
} }
@ -17,4 +17,4 @@ export default async (me: User['id'], note: Note) => {
userId: me, userId: me,
noteUserId: note.userId, noteUserId: note.userId,
} as NoteWatching); } as NoteWatching);
}; }