From 3da7221eecd6b1e3a0694c2b1ed1078f9bda1b12 Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Sun, 16 Oct 2022 17:46:11 -0400 Subject: [PATCH 1/6] backend: mark elasticsearch as optional --- packages/backend/src/config/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts index 7c804d175..ff49fb04e 100644 --- a/packages/backend/src/config/types.ts +++ b/packages/backend/src/config/types.ts @@ -24,7 +24,7 @@ export type Source = { db?: number; prefix?: string; }; - elasticsearch: { + elasticsearch?: { host: string; port: number; ssl?: boolean; From d83c1c3851addddc8d7ef4093b94e45cfcfa4f91 Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Sun, 16 Oct 2022 17:49:41 -0400 Subject: [PATCH 2/6] backend: use named exports for services/note --- packages/backend/src/remote/activitypub/kernel/like.ts | 4 ++-- packages/backend/src/remote/activitypub/kernel/undo/like.ts | 2 +- packages/backend/src/remote/activitypub/models/note.ts | 2 +- packages/backend/src/server/api/endpoints/antennas/notes.ts | 2 +- .../backend/src/server/api/endpoints/i/notifications.ts | 4 ++-- packages/backend/src/server/api/endpoints/notes/mentions.ts | 4 ++-- .../src/server/api/endpoints/notes/reactions/create.ts | 2 +- .../src/server/api/endpoints/notes/reactions/delete.ts | 2 +- .../src/server/api/endpoints/notes/thread-muting/create.ts | 2 +- .../src/server/api/endpoints/notes/watching/create.ts | 2 +- .../src/server/api/endpoints/notes/watching/delete.ts | 2 +- packages/backend/src/server/api/stream/index.ts | 2 +- packages/backend/src/services/note/polls/vote.ts | 2 +- packages/backend/src/services/note/reaction/create.ts | 6 +++--- packages/backend/src/services/note/reaction/delete.ts | 4 ++-- packages/backend/src/services/note/read.ts | 4 ++-- packages/backend/src/services/note/unwatch.ts | 4 ++-- packages/backend/src/services/note/watch.ts | 4 ++-- 18 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/backend/src/remote/activitypub/kernel/like.ts b/packages/backend/src/remote/activitypub/kernel/like.ts index b9faa38d1..76272eea7 100644 --- a/packages/backend/src/remote/activitypub/kernel/like.ts +++ b/packages/backend/src/remote/activitypub/kernel/like.ts @@ -1,5 +1,5 @@ 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 { 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); - 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') { return 'skip: already reacted'; } else { diff --git a/packages/backend/src/remote/activitypub/kernel/undo/like.ts b/packages/backend/src/remote/activitypub/kernel/undo/like.ts index a7f7e6bc6..6c7b8d18b 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/like.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/like.ts @@ -1,5 +1,5 @@ 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 { fetchNote } from '@/remote/activitypub/models/note.js'; diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index e5f8344ff..55989206e 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -4,7 +4,7 @@ import config from '@/config/index.js'; import post from '@/services/note/create.js'; import { CacheableRemoteUser } from '@/models/entities/user.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 { deliverQuestionUpdate } from '@/services/note/polls/update.js'; import { extractDbHost, toPuny } from '@/misc/convert-host.js'; diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index f86b6c7d8..0231fcfd6 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -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 { makePaginationQuery } from '../../common/make-pagination-query.js'; import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index 237d76da8..4a2ccdc79 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -1,7 +1,7 @@ import { Brackets } from 'typeorm'; import { notificationTypes } from 'foundkey-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 define from '../../define.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!); if (notes.length > 0) { - read(user.id, notes); + readNote(user.id, notes); } return await Notifications.packMany(notifications, user.id); diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index f86465ef6..10d329c9d 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; 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 define from '../../define.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(); - read(user.id, mentions); + readNote(user.id, mentions); return await Notes.packMany(mentions, user); }); diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts index b5c0c9d17..5d27ab8fb 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts @@ -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 { getNote } from '../../../common/getters.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts index e1bb63197..a5c50e4c2 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts @@ -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 define from '../../../define.js'; import { getNote } from '../../../common/getters.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts index d7599dc30..efcafe306 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts @@ -1,7 +1,7 @@ import { noteNotificationTypes } from 'foundkey-js'; import { Notes, NoteThreadMutings, NoteWatchings } from '@/models/index.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 { getNote } from '../../../common/getters.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/watching/create.ts b/packages/backend/src/server/api/endpoints/notes/watching/create.ts index 6025799fa..07c0517a1 100644 --- a/packages/backend/src/server/api/endpoints/notes/watching/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/watching/create.ts @@ -1,4 +1,4 @@ -import watch from '@/services/note/watch.js'; +import { watch } from '@/services/note/watch.js'; import define from '../../../define.js'; import { getNote } from '../../../common/getters.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts index 7021c7970..5949c9cd1 100644 --- a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts @@ -1,4 +1,4 @@ -import unwatch from '@/services/note/unwatch.js'; +import { unwatch } from '@/services/note/unwatch.js'; import define from '../../../define.js'; import { getNote } from '../../../common/getters.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index be67aa226..f3337fbfe 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -1,6 +1,6 @@ import { EventEmitter } from 'events'; 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 { Channel as ChannelModel } from '@/models/entities/channel.js'; import { Followings, Mutings, RenoteMutings, UserProfiles, ChannelFollowings, Blockings } from '@/models/index.js'; diff --git a/packages/backend/src/services/note/polls/vote.ts b/packages/backend/src/services/note/polls/vote.ts index 3382e10bd..5e27159e8 100644 --- a/packages/backend/src/services/note/polls/vote.ts +++ b/packages/backend/src/services/note/polls/vote.ts @@ -6,7 +6,7 @@ import { PollVotes, NoteWatchings, Polls, Blockings, NoteThreadMutings } from '@ import { genId } from '@/misc/gen-id.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 { const poll = await Polls.findOneBy({ noteId: note.id }); if (poll == null) throw new Error('poll not found'); diff --git a/packages/backend/src/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts index 5cc4f331c..df930c15c 100644 --- a/packages/backend/src/services/note/reaction/create.ts +++ b/packages/backend/src/services/note/reaction/create.ts @@ -13,9 +13,9 @@ import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js import { NoteReaction } from '@/models/entities/note-reaction.js'; import { IdentifiableError } from '@/misc/identifiable-error.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 { // Check blocking if (note.userId !== user.id) { const block = await Blockings.findOneBy({ @@ -148,4 +148,4 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, dm.execute(); } //#endregion -}; +} diff --git a/packages/backend/src/services/note/reaction/delete.ts b/packages/backend/src/services/note/reaction/delete.ts index a7cbcb1c1..3fc85a3d1 100644 --- a/packages/backend/src/services/note/reaction/delete.ts +++ b/packages/backend/src/services/note/reaction/delete.ts @@ -9,7 +9,7 @@ import { Note } from '@/models/entities/note.js'; import { NoteReactions, Users, Notes } from '@/models/index.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 { // if already unreacted const exist = await NoteReactions.findOneBy({ noteId: note.id, @@ -55,4 +55,4 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note) dm.execute(); } //#endregion -}; +} diff --git a/packages/backend/src/services/note/read.ts b/packages/backend/src/services/note/read.ts index 0b678893c..6899eccc5 100644 --- a/packages/backend/src/services/note/read.ts +++ b/packages/backend/src/services/note/read.ts @@ -12,14 +12,14 @@ import { Packed } from '@/misc/schema.js'; /** * Mark notes as read */ -export default async function( +export async function readNote( userId: User['id'], notes: (Note | Packed<'Note'>)[], info?: { following: Set; followingChannels: Set; }, -) { +): Promise { const following = info?.following ? info.following : new Set((await Followings.find({ where: { followerId: userId, diff --git a/packages/backend/src/services/note/unwatch.ts b/packages/backend/src/services/note/unwatch.ts index 3964b2ba5..2f779586d 100644 --- a/packages/backend/src/services/note/unwatch.ts +++ b/packages/backend/src/services/note/unwatch.ts @@ -2,9 +2,9 @@ import { User } from '@/models/entities/user.js'; import { NoteWatchings } from '@/models/index.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 { await NoteWatchings.delete({ noteId: note.id, userId: me, }); -}; +} diff --git a/packages/backend/src/services/note/watch.ts b/packages/backend/src/services/note/watch.ts index 2210c44a7..e7217a2c0 100644 --- a/packages/backend/src/services/note/watch.ts +++ b/packages/backend/src/services/note/watch.ts @@ -4,7 +4,7 @@ import { NoteWatchings } from '@/models/index.js'; import { genId } from '@/misc/gen-id.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 { // 自分の投稿はwatchできない if (me === note.userId) { return; @@ -17,4 +17,4 @@ export default async (me: User['id'], note: Note) => { userId: me, noteUserId: note.userId, } as NoteWatching); -}; +} From bfba54524d79b9fed586caeec02021fed2d9dd39 Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Sun, 16 Oct 2022 17:50:25 -0400 Subject: [PATCH 3/6] backend: fix various type lints in services/note `createdAt` in `insertNote` now will default to the current date. Also refactor poll insert: Instead of testing hasPoll, just do a null check on data.poll since it's a more reliable indicator for whether a poll exists (and also tsc won't complain about data.poll being possibly null). --- packages/backend/src/services/note/create.ts | 74 ++++++++++---------- packages/backend/src/services/note/delete.ts | 10 +-- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index 1b987b5cc..4bcbe5f9a 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -35,6 +35,7 @@ import { webhookDeliver } from '@/queue/index.js'; import { Cache } from '@/misc/cache.js'; import { UserProfile } from '@/models/entities/user-profile.js'; import { getActiveWebhooks } from '@/misc/webhook-cache.js'; +import { IActivity } from '@/remote/activitypub/type.js'; import { updateHashtags } from '../update-hashtag.js'; import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js'; import { createNotification } from '../create-notification.js'; @@ -59,8 +60,8 @@ class NotificationManager { 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; const exist = this.queue.find(x => x.target === notifiee); @@ -78,7 +79,7 @@ class NotificationManager { } } - public async deliver() { + public async deliver(): Promise { for (const x of this.queue) { // check if the sender or thread are muted const userMuted = await Mutings.findOneBy({ @@ -119,7 +120,7 @@ type Option = { poll?: IPoll | null; localOnly?: boolean | null; cw?: string | null; - visibility?: string; + visibility?: 'home' | 'public' | 'followers' | 'specified'; visibleUsers?: MinimumUser[] | null; channel?: Channel | null; apMentions?: MinimumUser[] | null; @@ -130,7 +131,7 @@ type Option = { 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(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 => new Promise(async (res, rej) => { // チャンネル外にリプライしたら対象のスコープに合わせる // (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで) if (data.reply && data.channel && data.reply.channelId !== data.channel.id) { @@ -214,7 +215,7 @@ export default async (user: { id: User['id']; username: User['username']; host: 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)) { - mentionedUsers.push(await Users.findOneByOrFail({ id: data.reply!.userId })); + mentionedUsers.push(await Users.findOneByOrFail({ id: data.reply.userId })); } 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)) { - data.visibleUsers.push(await Users.findOneByOrFail({ 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 })); } } @@ -477,7 +478,7 @@ export default async (user: { id: User['id']; username: User['username']; host: index(note); }); -async function renderNoteOrRenoteActivity(data: Option, note: Note) { +async function renderNoteOrRenoteActivity(data: Option, note: Note): Promise { if (data.localOnly) return null; 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); } -function incRenoteCount(renote: Note) { +function incRenoteCount(renote: Note): void { Notes.createQueryBuilder().update() .set({ renoteCount: () => '"renoteCount" + 1', @@ -497,10 +498,12 @@ function incRenoteCount(renote: Note) { .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 { + const createdAt = data.createdAt ?? new Date(); + const insert = new Note({ - id: genId(data.createdAt!), - createdAt: data.createdAt!, + id: genId(createdAt), + createdAt, fileIds: data.files ? data.files.map(file => file.id) : [], replyId: data.reply ? data.reply.id : null, renoteId: data.renote ? data.renote.id : null, @@ -517,8 +520,8 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O tags: tags.map(tag => normalizeForSearch(tag)), emojis, userId: user.id, - localOnly: data.localOnly!, - visibility: data.visibility as any, + localOnly: data.localOnly ?? false, + visibility: data.visibility, visibleUserIds: data.visibility === 'specified' ? data.visibleUsers ? data.visibleUsers.map(u => u.id) @@ -543,29 +546,26 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O insert.mentions = mentionedUsers.map(u => u.id); } - // 投稿を作成 + // Create a post try { - if (insert.hasPoll) { - // Start transaction - await db.transaction(async transactionalEntityManager => { - await transactionalEntityManager.insert(Note, insert); + // Start transaction + await db.transaction(async transactionalEntityManager => { + await transactionalEntityManager.insert(Note, insert); + if (data.poll != null) { const poll = new Poll({ noteId: insert.id, - choices: data.poll!.choices, - expiresAt: data.poll!.expiresAt, - multiple: data.poll!.multiple, - votes: new Array(data.poll!.choices.length).fill(0), + choices: data.poll.choices, + expiresAt: data.poll.expiresAt, + multiple: data.poll.multiple, + votes: new Array(data.poll.choices.length).fill(0), noteVisibility: insert.visibility, userId: user.id, userHost: user.host, }); - await transactionalEntityManager.insert(Poll, poll); - }); - } else { - await Notes.insert(insert); - } + } + }); return insert; } catch (e) { @@ -582,10 +582,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; - es!.index({ + es.index({ index: config.elasticsearch.index || 'misskey_note', id: note.id.toString(), body: { @@ -596,7 +596,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 { const watchers = await NoteWatchings.findBy({ noteId: renote.id, userId: Not(user.id), @@ -607,7 +607,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 { const watchers = await NoteWatchings.findBy({ noteId: reply.id, userId: Not(user.id), @@ -618,7 +618,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 { for (const u of mentionedUsers.filter(u => Users.isLocalUser(u))) { const threadMuted = await NoteThreadMutings.findOneBy({ userId: u.id, @@ -653,11 +653,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); } -function incNotesCountOfUser(user: { id: User['id']; }) { +function incNotesCountOfUser(user: { id: User['id']; }): void { Users.createQueryBuilder().update() .set({ updatedAt: new Date(), @@ -668,7 +668,7 @@ function incNotesCountOfUser(user: { id: User['id']; }) { } async function extractMentionedUsers(user: { host: User['host']; }, tokens: mfm.MfmNode[]): Promise { - if (tokens == null) return []; + if (tokens.length === 0) return []; const mentions = extractMentions(tokens); diff --git a/packages/backend/src/services/note/delete.ts b/packages/backend/src/services/note/delete.ts index 1fd1d2900..c36e6c46b 100644 --- a/packages/backend/src/services/note/delete.ts +++ b/packages/backend/src/services/note/delete.ts @@ -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 renderDelete from '@/remote/activitypub/renderer/delete.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) { let renote: Note | null = null; - // if deletd note is renote + // if deleted note is renote if (isPureRenote(note)) { 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 { } async function getMentionedRemoteUsers(note: Note): Promise { - const where = [] as any[]; + const where: FindOptionsWhere[] = []; // mention / reply / dm if (note.mentions.length > 0) { From aa1e4d0fbc174cb5efd0e63b2c666071e42d7fc1 Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Mon, 17 Oct 2022 14:43:29 -0400 Subject: [PATCH 4/6] change null assertion ternaries to use optional chaining --- packages/backend/src/services/note/create.ts | 48 +++++++++----------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index 4bcbe5f9a..36534e02b 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -142,9 +142,9 @@ export default async (user: { id: User['id']; username: User['username']; host: } } - // チャンネル内にリプライしたら対象のスコープに合わせる - // (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで) - if (data.reply && (data.channel == null) && data.reply.channelId) { + // 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?.channelId && (data.channel == null)) { data.channel = await Channels.findOneBy({ id: data.reply.channelId }); } @@ -197,10 +197,10 @@ export default async (user: { id: User['id']; username: User['username']; host: // Parse MFM if needed if (!tags || !emojis || !mentionedUsers) { - const tokens = data.text ? mfm.parse(data.text)! : []; - const cwTokens = data.cw ? mfm.parse(data.cw)! : []; - const choiceTokens = data.poll && data.poll.choices - ? concat(data.poll.choices.map(choice => mfm.parse(choice)!)) + const tokens = data.text ? mfm.parse(data.text) : []; + const cwTokens = data.cw ? mfm.parse(data.cw) : []; + const choiceTokens = data.poll?.choices + ? concat(data.poll.choices.map(choice => mfm.parse(choice))) : []; const combinedTokens = tokens.concat(cwTokens).concat(choiceTokens); @@ -214,7 +214,7 @@ export default async (user: { id: User['id']; username: User['username']; host: 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 })); } @@ -465,13 +465,13 @@ export default async (user: { id: User['id']; username: User['username']; host: Notes.countBy({ userId: user.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 @@ -504,15 +504,11 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O const insert = new Note({ id: genId(createdAt), createdAt, - fileIds: data.files ? data.files.map(file => file.id) : [], - replyId: data.reply ? data.reply.id : null, - renoteId: data.renote ? data.renote.id : null, - channelId: data.channel ? data.channel.id : null, - threadId: data.reply - ? data.reply.threadId - ? data.reply.threadId - : data.reply.id - : null, + fileIds: data.files?.map(file => file.id) ?? [], + replyId: data.reply?.id ?? null, + renoteId: data.renote?.id ?? null, + channelId: data.channel?.id ?? null, + threadId: data.reply?.threadId ?? data.reply?.id ?? null, name: data.name, text: data.text, hasPoll: data.poll != null, @@ -523,12 +519,10 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O localOnly: data.localOnly ?? false, visibility: data.visibility, 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 replyUserId: data.reply?.userId, From 923c93da1228458dd65be47483c198a1a9191bcf Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Mon, 17 Oct 2022 16:46:42 -0400 Subject: [PATCH 5/6] use await for notes.countBy --- packages/backend/src/services/note/create.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index 36534e02b..1d8db6ac7 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -462,7 +462,7 @@ export default async (user: { id: User['id']; username: User['username']; host: lastNotedAt: new Date(), }); - Notes.countBy({ + const count = await Notes.countBy({ userId: user.id, channelId: data.channel.id, }); From 43644494d35e56d9a1d13f4dbb688d831464e99c Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Mon, 17 Oct 2022 16:49:41 -0400 Subject: [PATCH 6/6] translate remaining comments --- packages/backend/src/services/note/create.ts | 38 ++++++++++---------- packages/backend/src/services/note/watch.ts | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index 1d8db6ac7..2cae03241 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -67,7 +67,7 @@ class NotificationManager { const exist = this.queue.find(x => x.target === notifiee); if (exist) { - // 「メンションされているかつ返信されている」場合は、メンションとしての通知ではなく返信としての通知にする + // If you have been "mentioned and replied to," make the notification as a reply, not as a mention. if (reason !== 'mention') { exist.reason = reason; } @@ -132,8 +132,8 @@ type Option = { }; export default async (user: { id: User['id']; username: User['username']; host: User['host']; isSilenced: User['isSilenced']; createdAt: User['createdAt']; }, data: Option, silent = false): Promise => new Promise(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.channelId) { data.channel = await Channels.findOneBy({ id: data.reply.channelId }); @@ -155,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.localOnly = true; - // サイレンス + // silence if (user.isSilenced && data.visibility === 'public' && data.channel == null) { 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) { 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') { data.visibility = 'home'; } - // Renote対象がfollowersならfollowersにする + // If the target of Renote is followers, make it followers. if (data.renote && data.renote.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) { data.localOnly = true; } - // ローカルのみにリプライしたらローカルのみにする + // If you reply to local only, make it local only. if (data.reply && data.reply.localOnly && data.channel == null) { data.localOnly = true; } @@ -236,7 +236,7 @@ export default async (user: { id: User['id']; username: User['username']; host: res(note); - // 統計を更新 + // Update Statistics notesChart.update(note, true); perUserNotesChart.update(user, note, true); @@ -248,7 +248,7 @@ export default async (user: { id: User['id']; username: User['username']; host: }); } - // ハッシュタグ更新 + // Hashtag Update if (data.visibility === 'public' || data.visibility === 'home') { updateHashtags(user, tags); } @@ -302,7 +302,7 @@ export default async (user: { id: User['id']; username: User['username']; host: 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)) { incRenoteCount(data.renote); } @@ -320,12 +320,12 @@ export default async (user: { id: User['id']; username: User['username']; host: if (!silent) { if (Users.isLocalUser(user)) activeUsersChart.write(user); - // 未読通知を作成 + // Create unread notifications if (data.visibility === 'specified') { if (data.visibleUsers == null) throw new Error('invalid param'); for (const u of data.visibleUsers) { - // ローカルユーザーのみ + // Local users only if (!Users.isLocalUser(u)) continue; insertNoteUnread(u.id, note, { @@ -335,7 +335,7 @@ export default async (user: { id: User['id']; username: User['username']; host: } } else { for (const u of mentionedUsers) { - // ローカルユーザーのみ + // Local users only if (!Users.isLocalUser(u)) continue; insertNoteUnread(u.id, note, { @@ -424,24 +424,24 @@ export default async (user: { id: User['id']; username: User['username']; host: const noteActivity = await renderNoteOrRenoteActivity(data, note); const dm = new DeliverManager(user, noteActivity); - // メンションされたリモートユーザーに配送 + // Delivered to remote users who have been mentioned for (const u of mentionedUsers.filter(u => Users.isRemoteUser(u))) { 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) { const u = await Users.findOneBy({ id: data.reply.userId }); 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) { const u = await Users.findOneBy({ id: data.renote.userId }); if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u); } - // フォロワーに配送 + // Deliver to followers if (['public', 'home', 'followers'].includes(note.visibility)) { dm.addFollowersRecipe(); } diff --git a/packages/backend/src/services/note/watch.ts b/packages/backend/src/services/note/watch.ts index e7217a2c0..634870c75 100644 --- a/packages/backend/src/services/note/watch.ts +++ b/packages/backend/src/services/note/watch.ts @@ -5,7 +5,7 @@ import { genId } from '@/misc/gen-id.js'; import { NoteWatching } from '@/models/entities/note-watching.js'; export async function watch(me: User['id'], note: Note): Promise { - // 自分の投稿はwatchできない + // User can't watch their own posts. if (me === note.userId) { return; }