diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index e9966b77859786391348f930997985eb29d20ba9..9ce5c3e8b05cd1441f5926efded2ece92737c18a 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -1,5 +1,5 @@ export class Cache<T> { - private cache: Map<string | null, { date: number; value: T; }>; + public cache: Map<string | null, { date: number; value: T; }>; private lifetime: number; constructor(lifetime: Cache<never>['lifetime']) { diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts index 9d5db10eb34f536e63aaac4d2b4c5361d1ec55a8..c76824c977e3b25fb352f654202d830d7808e1d3 100644 --- a/packages/backend/src/models/entities/user.ts +++ b/packages/backend/src/models/entities/user.ts @@ -234,3 +234,9 @@ export interface ILocalUser extends User { export interface IRemoteUser extends User { host: string; } + +export type CacheableLocalUser = ILocalUser; + +export type CacheableRemoteUser = IRemoteUser; + +export type CacheableUser = CacheableLocalUser | CacheableRemoteUser; diff --git a/packages/backend/src/queue/processors/inbox.ts b/packages/backend/src/queue/processors/inbox.ts index 1b3f94b700c9f2315a8d5331aa3b051cc495f947..4fbfdb234f4025a79015b42824e9bfd9f35feac6 100644 --- a/packages/backend/src/queue/processors/inbox.ts +++ b/packages/backend/src/queue/processors/inbox.ts @@ -15,6 +15,8 @@ import DbResolver from '@/remote/activitypub/db-resolver.js'; import { resolvePerson } from '@/remote/activitypub/models/person.js'; import { LdSignature } from '@/remote/activitypub/misc/ld-signature.js'; import { StatusError } from '@/misc/fetch.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { UserPublickey } from '@/models/entities/user-publickey.js'; const logger = new Logger('inbox'); @@ -42,11 +44,13 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => { return `Old keyId is no longer supported. ${keyIdLower}`; } - // TDOO: ã‚ャッシュ const dbResolver = new DbResolver(); // HTTP-Signature keyIdã‚’å…ƒã«DBã‹ã‚‰å–å¾— - let authUser = await dbResolver.getAuthUserFromKeyId(signature.keyId); + let authUser: { + user: CacheableRemoteUser; + key: UserPublickey | null; + } | null = await dbResolver.getAuthUserFromKeyId(signature.keyId); // keyIdã§ã‚ã‹ã‚‰ãªã‘ã‚Œã°ã€activity.actorã‚’å…ƒã«DBã‹ã‚‰å–å¾— || activity.actorã‚’å…ƒã«ãƒªãƒ¢ãƒ¼ãƒˆã‹ã‚‰å–å¾— if (authUser == null) { diff --git a/packages/backend/src/remote/activitypub/audience.ts b/packages/backend/src/remote/activitypub/audience.ts index ba69b11e856b3f5a2c0dafaa2f8e80321be241b8..846ccf9c004778de5b9d713ef9a7fde58530ff58 100644 --- a/packages/backend/src/remote/activitypub/audience.ts +++ b/packages/backend/src/remote/activitypub/audience.ts @@ -3,26 +3,26 @@ import Resolver from './resolver.js'; import { resolvePerson } from './models/person.js'; import { unique, concat } from '@/prelude/array.js'; import promiseLimit from 'promise-limit'; -import { User, IRemoteUser } from '@/models/entities/user.js'; +import { User, CacheableRemoteUser, CacheableUser } from '@/models/entities/user.js'; type Visibility = 'public' | 'home' | 'followers' | 'specified'; type AudienceInfo = { visibility: Visibility, - mentionedUsers: User[], - visibleUsers: User[], + mentionedUsers: CacheableUser[], + visibleUsers: CacheableUser[], }; -export async function parseAudience(actor: IRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise<AudienceInfo> { +export async function parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise<AudienceInfo> { const toGroups = groupingAudience(getApIds(to), actor); const ccGroups = groupingAudience(getApIds(cc), actor); const others = unique(concat([toGroups.other, ccGroups.other])); - const limit = promiseLimit<User | null>(2); + const limit = promiseLimit<CacheableUser | null>(2); const mentionedUsers = (await Promise.all( others.map(id => limit(() => resolvePerson(id, resolver).catch(() => null))) - )).filter((x): x is User => x != null); + )).filter((x): x is CacheableUser => x != null); if (toGroups.public.length > 0) { return { @@ -55,7 +55,7 @@ export async function parseAudience(actor: IRemoteUser, to?: ApObject, cc?: ApOb }; } -function groupingAudience(ids: string[], actor: IRemoteUser) { +function groupingAudience(ids: string[], actor: CacheableRemoteUser) { const groups = { public: [] as string[], followers: [] as string[], @@ -85,7 +85,7 @@ function isPublic(id: string) { ].includes(id); } -function isFollowers(id: string, actor: IRemoteUser) { +function isFollowers(id: string, actor: CacheableRemoteUser) { return ( id === (actor.followersUri || `${actor.uri}/followers`) ); diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts index 3f16c5f56d4aa38d216f5d26244f017cf8e037fb..3e7d2655ff12272ea973fc660276bbb9184846cb 100644 --- a/packages/backend/src/remote/activitypub/db-resolver.ts +++ b/packages/backend/src/remote/activitypub/db-resolver.ts @@ -1,12 +1,17 @@ +import escapeRegexp from 'escape-regexp'; import config from '@/config/index.js'; import { Note } from '@/models/entities/note.js'; -import { User, IRemoteUser } from '@/models/entities/user.js'; +import { User, IRemoteUser, CacheableRemoteUser } from '@/models/entities/user.js'; import { UserPublickey } from '@/models/entities/user-publickey.js'; import { MessagingMessage } from '@/models/entities/messaging-message.js'; import { Notes, Users, UserPublickeys, MessagingMessages } from '@/models/index.js'; import { IObject, getApId } from './type.js'; import { resolvePerson } from './models/person.js'; -import escapeRegexp from 'escape-regexp'; +import { Cache } from '@/misc/cache.js'; +import { userByIdCache } from '@/services/user-cache.js'; + +const publicKeyCache = new Cache<UserPublickey | null>(Infinity); +const publicKeyByUserIdCache = new Cache<UserPublickey | null>(Infinity); export default class DbResolver { constructor() { @@ -75,17 +80,24 @@ export default class DbResolver { /** * AP KeyId => Misskey User and Key */ - public async getAuthUserFromKeyId(keyId: string): Promise<AuthUser | null> { - const key = await UserPublickeys.findOne({ - keyId, - }, { - relations: ['user'], - }); + public async getAuthUserFromKeyId(keyId: string): Promise<{ + user: CacheableRemoteUser; + key: UserPublickey; + } | null> { + const key = await publicKeyCache.fetch(keyId, async () => { + const key = await UserPublickeys.findOne({ + keyId, + }); + + if (key == null) return null; + + return key; + }, key => key != null); if (key == null) return null; return { - user: key.user as IRemoteUser, + user: await userByIdCache.fetch(key.userId, () => Users.findOneOrFail(key.userId)) as CacheableRemoteUser, key, }; } @@ -93,12 +105,15 @@ export default class DbResolver { /** * AP Actor id => Misskey User and Key */ - public async getAuthUserFromApId(uri: string): Promise<AuthUser | null> { - const user = await resolvePerson(uri) as IRemoteUser; + public async getAuthUserFromApId(uri: string): Promise<{ + user: CacheableRemoteUser; + key: UserPublickey | null; + } | null> { + const user = await resolvePerson(uri) as CacheableRemoteUser; if (user == null) return null; - const key = await UserPublickeys.findOne(user.id); + const key = await publicKeyByUserIdCache.fetch(user.id, () => UserPublickeys.findOne(user.id).then(x => x || null), v => v != null); // TODO: typeorm 3.0 ã«ã—ãŸã‚‰.then(x => x || null)ã¯æ¶ˆã›ã‚‹ return { user, @@ -125,11 +140,6 @@ export default class DbResolver { } } -export type AuthUser = { - user: IRemoteUser; - key?: UserPublickey; -}; - type UriParseResult = { /** id in DB (local object only) */ id?: string; diff --git a/packages/backend/src/remote/activitypub/deliver-manager.ts b/packages/backend/src/remote/activitypub/deliver-manager.ts index 9c4e3418ff52d8414fc817a31a750c57f521669e..6cbd57af5d01ed52e3eea1f19dccaa5fc358a081 100644 --- a/packages/backend/src/remote/activitypub/deliver-manager.ts +++ b/packages/backend/src/remote/activitypub/deliver-manager.ts @@ -112,7 +112,7 @@ export default class DeliverManager { * @param activity Activity * @param from Followee */ -export async function deliverToFollowers(actor: ILocalUser, activity: any) { +export async function deliverToFollowers(actor: { id: ILocalUser['id']; host: null; }, activity: any) { const manager = new DeliverManager(actor, activity); manager.addFollowersRecipe(); await manager.execute(); @@ -123,7 +123,7 @@ export async function deliverToFollowers(actor: ILocalUser, activity: any) { * @param activity Activity * @param to Target user */ -export async function deliverToUser(actor: ILocalUser, activity: any, to: IRemoteUser) { +export async function deliverToUser(actor: { id: ILocalUser['id']; host: null; }, activity: any, to: IRemoteUser) { const manager = new DeliverManager(actor, activity); manager.addDirectRecipe(to); await manager.execute(); diff --git a/packages/backend/src/remote/activitypub/kernel/accept/follow.ts b/packages/backend/src/remote/activitypub/kernel/accept/follow.ts index 393516addf6474ec9b9dba64aaab401487aff5f4..4350ef1333a3980ea23dfb81959987438adf0da0 100644 --- a/packages/backend/src/remote/activitypub/kernel/accept/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/accept/follow.ts @@ -1,10 +1,10 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import accept from '@/services/following/requests/accept.js'; import { IFollow } from '../../type.js'; import DbResolver from '../../db-resolver.js'; import { relayAccepted } from '@/services/relay.js'; -export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => { +export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => { // ※ activityã¯ã“ã£ã¡ã‹ã‚‰æŠ•ã’ãŸãƒ•ã‚©ãƒãƒ¼ãƒªã‚¯ã‚¨ã‚¹ãƒˆãªã®ã§ã€activity.actorã¯å˜åœ¨ã™ã‚‹ãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã§ã‚ã‚‹å¿…è¦ãŒã‚ã‚‹ const dbResolver = new DbResolver(); diff --git a/packages/backend/src/remote/activitypub/kernel/accept/index.ts b/packages/backend/src/remote/activitypub/kernel/accept/index.ts index 354bd4f6e11e7cc92df387f22e360ace1d94cba5..78ef75ade39264d98d2a4842e42d67ce23a583dc 100644 --- a/packages/backend/src/remote/activitypub/kernel/accept/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/accept/index.ts @@ -1,12 +1,12 @@ import Resolver from '../../resolver.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import acceptFollow from './follow.js'; import { IAccept, isFollow, getApType } from '../../type.js'; import { apLogger } from '../../logger.js'; const logger = apLogger; -export default async (actor: IRemoteUser, activity: IAccept): Promise<string> => { +export default async (actor: CacheableRemoteUser, activity: IAccept): Promise<string> => { const uri = activity.id || activity; logger.info(`Accept: ${uri}`); diff --git a/packages/backend/src/remote/activitypub/kernel/add/index.ts b/packages/backend/src/remote/activitypub/kernel/add/index.ts index 9a2fac1e749442d2d842e0d010059ad2a00580f4..c813414f93bdc27ed3520486729f8d0c4ee31f42 100644 --- a/packages/backend/src/remote/activitypub/kernel/add/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/add/index.ts @@ -1,9 +1,9 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IAdd } from '../../type.js'; import { resolveNote } from '../../models/note.js'; import { addPinned } from '@/services/i/pin.js'; -export default async (actor: IRemoteUser, activity: IAdd): Promise<void> => { +export default async (actor: CacheableRemoteUser, activity: IAdd): Promise<void> => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } diff --git a/packages/backend/src/remote/activitypub/kernel/announce/index.ts b/packages/backend/src/remote/activitypub/kernel/announce/index.ts index 7e2e73bdd53c16a8d5539461aeff2a34a236ac09..ae7e507c99fc8b676e654461f02c84ba4038e858 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/index.ts @@ -1,12 +1,12 @@ import Resolver from '../../resolver.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import announceNote from './note.js'; import { IAnnounce, getApId } from '../../type.js'; import { apLogger } from '../../logger.js'; const logger = apLogger; -export default async (actor: IRemoteUser, activity: IAnnounce): Promise<void> => { +export default async (actor: CacheableRemoteUser, activity: IAnnounce): Promise<void> => { const uri = getApId(activity); logger.info(`Announce: ${uri}`); diff --git a/packages/backend/src/remote/activitypub/kernel/announce/note.ts b/packages/backend/src/remote/activitypub/kernel/announce/note.ts index f6068fac7989812cc0a8ea3d47d2d1dc3161dfbf..680749f4d86b36ddb971d748ca67edf949cdab1c 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/note.ts @@ -1,6 +1,6 @@ import Resolver from '../../resolver.js'; import post from '@/services/note/create.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IAnnounce, getApId } from '../../type.js'; import { fetchNote, resolveNote } from '../../models/note.js'; import { apLogger } from '../../logger.js'; @@ -15,10 +15,9 @@ const logger = apLogger; /** * アナウンスアクティビティをæŒãã¾ã™ */ -export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> { +export default async function(resolver: Resolver, actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> { const uri = getApId(activity); - // アナウンサーãŒå‡çµã•ã‚Œã¦ã„ãŸã‚‰ã‚¹ã‚ップ if (actor.isSuspended) { return; } diff --git a/packages/backend/src/remote/activitypub/kernel/block/index.ts b/packages/backend/src/remote/activitypub/kernel/block/index.ts index 9e4f1b316e6393704b2ba8b5db5ac2d16658b725..69037fec172002236dd9cbfabc8e1b5201292529 100644 --- a/packages/backend/src/remote/activitypub/kernel/block/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/block/index.ts @@ -1,9 +1,10 @@ import { IBlock } from '../../type.js'; import block from '@/services/blocking/create.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import DbResolver from '../../db-resolver.js'; +import { Users } from '@/models/index.js'; -export default async (actor: IRemoteUser, activity: IBlock): Promise<string> => { +export default async (actor: CacheableRemoteUser, activity: IBlock): Promise<string> => { // ※ activity.objectã«ãƒ–ãƒãƒƒã‚¯å¯¾è±¡ãŒã‚ã‚Šã€ãã‚Œã¯å˜åœ¨ã™ã‚‹ãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ã¯ãš const dbResolver = new DbResolver(); @@ -17,6 +18,6 @@ export default async (actor: IRemoteUser, activity: IBlock): Promise<string> => return `skip: ブãƒãƒƒã‚¯ã—よã†ã¨ã—ã¦ã„るユーザーã¯ãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã§ã¯ã‚ã‚Šã¾ã›ã‚“`; } - await block(actor, blockee); + await block(await Users.findOneOrFail(actor.id), blockee); return `ok`; }; diff --git a/packages/backend/src/remote/activitypub/kernel/create/index.ts b/packages/backend/src/remote/activitypub/kernel/create/index.ts index 1187b95ac61169b8b3d10c3b028e7e5e78b86338..c253f9f66779701ce8fa04cf5eed3b940d5a38f7 100644 --- a/packages/backend/src/remote/activitypub/kernel/create/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/create/index.ts @@ -1,5 +1,5 @@ import Resolver from '../../resolver.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import createNote from './note.js'; import { ICreate, getApId, isPost, getApType } from '../../type.js'; import { apLogger } from '../../logger.js'; @@ -7,7 +7,7 @@ import { toArray, concat, unique } from '@/prelude/array.js'; const logger = apLogger; -export default async (actor: IRemoteUser, activity: ICreate): Promise<void> => { +export default async (actor: CacheableRemoteUser, activity: ICreate): Promise<void> => { const uri = getApId(activity); logger.info(`Create: ${uri}`); diff --git a/packages/backend/src/remote/activitypub/kernel/create/note.ts b/packages/backend/src/remote/activitypub/kernel/create/note.ts index b5c47990aaf646349911c251d0931e22ab0317f4..f8dabe06e29224b6555e2e35a96b7b63f2573270 100644 --- a/packages/backend/src/remote/activitypub/kernel/create/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/create/note.ts @@ -1,5 +1,5 @@ import Resolver from '../../resolver.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { createNote, fetchNote } from '../../models/note.js'; import { getApId, IObject, ICreate } from '../../type.js'; import { getApLock } from '@/misc/app-lock.js'; @@ -9,7 +9,7 @@ import { StatusError } from '@/misc/fetch.js'; /** * 投稿作æˆã‚¢ã‚¯ãƒ†ã‚£ãƒ“ティをæŒãã¾ã™ */ -export default async function(resolver: Resolver, actor: IRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> { +export default async function(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> { const uri = getApId(note); if (typeof note === 'object') { diff --git a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts index 2f75841e524bfcfc16455dcb625e0fa7b44ee113..53dabb11912f9cc775569bfa9d1db8d9ab8f3cc3 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts @@ -1,18 +1,19 @@ import { apLogger } from '../../logger.js'; import { createDeleteAccountJob } from '@/queue/index.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { Users } from '@/models/index.js'; const logger = apLogger; -export async function deleteActor(actor: IRemoteUser, uri: string): Promise<string> { +export async function deleteActor(actor: CacheableRemoteUser, uri: string): Promise<string> { logger.info(`Deleting the Actor: ${uri}`); if (actor.uri !== uri) { return `skip: delete actor ${actor.uri} !== ${uri}`; } - if (actor.isDeleted) { + const user = await Users.findOneOrFail(actor.id); + if (user.isDeleted) { logger.info(`skip: already deleted`); } diff --git a/packages/backend/src/remote/activitypub/kernel/delete/index.ts b/packages/backend/src/remote/activitypub/kernel/delete/index.ts index b6d5e96d031af67655bebab2358a5cf08d369d4d..4c06a9de0bfe7004d90ae96ad9cbe65f37e85c3d 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/index.ts @@ -1,5 +1,5 @@ import deleteNote from './note.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IDelete, getApId, isTombstone, IObject, validPost, validActor } from '../../type.js'; import { toSingle } from '@/prelude/array.js'; import { deleteActor } from './actor.js'; @@ -7,7 +7,7 @@ import { deleteActor } from './actor.js'; /** * 削除アクティビティをæŒãã¾ã™ */ -export default async (actor: IRemoteUser, activity: IDelete): Promise<string> => { +export default async (actor: CacheableRemoteUser, activity: IDelete): Promise<string> => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } diff --git a/packages/backend/src/remote/activitypub/kernel/delete/note.ts b/packages/backend/src/remote/activitypub/kernel/delete/note.ts index ad5e1a2edcd80c29a20f9bd914b99a6ca441667a..1f44c35562afbc204ba51fa6c09ed4f9c8e7633f 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/note.ts @@ -1,4 +1,4 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import deleteNode from '@/services/note/delete.js'; import { apLogger } from '../../logger.js'; import DbResolver from '../../db-resolver.js'; @@ -7,7 +7,7 @@ import { deleteMessage } from '@/services/messages/delete.js'; const logger = apLogger; -export default async function(actor: IRemoteUser, uri: string): Promise<string> { +export default async function(actor: CacheableRemoteUser, uri: string): Promise<string> { logger.info(`Deleting the Note: ${uri}`); const unlock = await getApLock(uri); diff --git a/packages/backend/src/remote/activitypub/kernel/flag/index.ts b/packages/backend/src/remote/activitypub/kernel/flag/index.ts index e80e632786d0305147628bdef7187a23d38ee52e..45c0a6c711192fa5c0296709ac155eca44961443 100644 --- a/packages/backend/src/remote/activitypub/kernel/flag/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/flag/index.ts @@ -1,11 +1,11 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import config from '@/config/index.js'; import { IFlag, getApIds } from '../../type.js'; import { AbuseUserReports, Users } from '@/models/index.js'; import { In } from 'typeorm'; import { genId } from '@/misc/gen-id.js'; -export default async (actor: IRemoteUser, activity: IFlag): Promise<string> => { +export default async (actor: CacheableRemoteUser, activity: IFlag): Promise<string> => { // object㯠`(User|Note) | (User|Note)[]` ã ã‘ã©ã€å…¨ãƒ‘ターンDBスã‚ーマã¨å¯¾å¿œã•ã›ã‚‰ã‚Œãªã„ã®ã§ // 対象ユーザーã¯ä¸€ç•ªæœ€åˆã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ ã¨ã—㦠ã‚ã¨ã¯ã‚³ãƒ¡ãƒ³ãƒˆã¨ã—ã¦æ ¼ç´ã™ã‚‹ const uris = getApIds(activity.object); diff --git a/packages/backend/src/remote/activitypub/kernel/follow.ts b/packages/backend/src/remote/activitypub/kernel/follow.ts index 49c1a7ee01fe3fb86e959a405714ff7ab09d1ee8..a9e92fa2296f1715a9b0d8a573659a6c58608bb1 100644 --- a/packages/backend/src/remote/activitypub/kernel/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/follow.ts @@ -1,9 +1,9 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import follow from '@/services/following/create.js'; import { IFollow } from '../type.js'; import DbResolver from '../db-resolver.js'; -export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => { +export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => { const dbResolver = new DbResolver(); const followee = await dbResolver.getUserFromApId(activity.object); diff --git a/packages/backend/src/remote/activitypub/kernel/index.ts b/packages/backend/src/remote/activitypub/kernel/index.ts index 6aea8e57cfa7c155a2595d1a2a36ef2f3e935395..254a1216059d4080d070f6a0079d81178643ae6f 100644 --- a/packages/backend/src/remote/activitypub/kernel/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/index.ts @@ -1,5 +1,5 @@ import { IObject, isCreate, isDelete, isUpdate, isRead, isFollow, isAccept, isReject, isAdd, isRemove, isAnnounce, isLike, isUndo, isBlock, isCollectionOrOrderedCollection, isCollection, isFlag } from '../type.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import create from './create/index.js'; import performDeleteActivity from './delete/index.js'; import performUpdateActivity from './update/index.js'; @@ -17,8 +17,9 @@ import flag from './flag/index.js'; import { apLogger } from '../logger.js'; import Resolver from '../resolver.js'; import { toArray } from '@/prelude/array.js'; +import { Users } from '@/models/index.js'; -export async function performActivity(actor: IRemoteUser, activity: IObject) { +export async function performActivity(actor: CacheableRemoteUser, activity: IObject) { if (isCollectionOrOrderedCollection(activity)) { const resolver = new Resolver(); for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) { @@ -36,7 +37,7 @@ export async function performActivity(actor: IRemoteUser, activity: IObject) { } } -async function performOneActivity(actor: IRemoteUser, activity: IObject): Promise<void> { +async function performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise<void> { if (actor.isSuspended) return; if (isCreate(activity)) { diff --git a/packages/backend/src/remote/activitypub/kernel/like.ts b/packages/backend/src/remote/activitypub/kernel/like.ts index 715cc379b9309b0e01dbe201b1c3eb0990e5dd46..2b65ff7383e49f860445f15f29162b0834678063 100644 --- a/packages/backend/src/remote/activitypub/kernel/like.ts +++ b/packages/backend/src/remote/activitypub/kernel/like.ts @@ -1,9 +1,9 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { ILike, getApId } from '../type.js'; import create from '@/services/note/reaction/create.js'; import { fetchNote, extractEmojis } from '../models/note.js'; -export default async (actor: IRemoteUser, activity: ILike) => { +export default async (actor: CacheableRemoteUser, activity: ILike) => { const targetUri = getApId(activity.object); const note = await fetchNote(targetUri); diff --git a/packages/backend/src/remote/activitypub/kernel/read.ts b/packages/backend/src/remote/activitypub/kernel/read.ts index 93cc36ec46312a134227577b9a97481ce1d1f5ff..333466e22fbc5cc0d1ea3abcf7b69702d4dd757c 100644 --- a/packages/backend/src/remote/activitypub/kernel/read.ts +++ b/packages/backend/src/remote/activitypub/kernel/read.ts @@ -1,10 +1,10 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IRead, getApId } from '../type.js'; import { isSelfHost, extractDbHost } from '@/misc/convert-host.js'; import { MessagingMessages } from '@/models/index.js'; import { readUserMessagingMessage } from '../../../server/api/common/read-messaging-message.js'; -export const performReadActivity = async (actor: IRemoteUser, activity: IRead): Promise<string> => { +export const performReadActivity = async (actor: CacheableRemoteUser, activity: IRead): Promise<string> => { const id = await getApId(activity.object); if (!isSelfHost(extractDbHost(id))) { diff --git a/packages/backend/src/remote/activitypub/kernel/reject/follow.ts b/packages/backend/src/remote/activitypub/kernel/reject/follow.ts index 72751e83c05c4f796670fdcae7023b9f22d5225e..824ac69d70897f7a6a06cdf05b3bad8b87bef68f 100644 --- a/packages/backend/src/remote/activitypub/kernel/reject/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/reject/follow.ts @@ -1,11 +1,11 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { remoteReject } from '@/services/following/reject.js'; import { IFollow } from '../../type.js'; import DbResolver from '../../db-resolver.js'; import { relayRejected } from '@/services/relay.js'; import { Users } from '@/models/index.js'; -export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => { +export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => { // ※ activityã¯ã“ã£ã¡ã‹ã‚‰æŠ•ã’ãŸãƒ•ã‚©ãƒãƒ¼ãƒªã‚¯ã‚¨ã‚¹ãƒˆãªã®ã§ã€activity.actorã¯å˜åœ¨ã™ã‚‹ãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã§ã‚ã‚‹å¿…è¦ãŒã‚ã‚‹ const dbResolver = new DbResolver(); diff --git a/packages/backend/src/remote/activitypub/kernel/reject/index.ts b/packages/backend/src/remote/activitypub/kernel/reject/index.ts index ed86a4aa2f958daf08f805a0d6f0ba116e31069c..00f08842f41b6e99b64669100d6e235693fa1ff6 100644 --- a/packages/backend/src/remote/activitypub/kernel/reject/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/reject/index.ts @@ -1,12 +1,12 @@ import Resolver from '../../resolver.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import rejectFollow from './follow.js'; import { IReject, isFollow, getApType } from '../../type.js'; import { apLogger } from '../../logger.js'; const logger = apLogger; -export default async (actor: IRemoteUser, activity: IReject): Promise<string> => { +export default async (actor: CacheableRemoteUser, activity: IReject): Promise<string> => { const uri = activity.id || activity; logger.info(`Reject: ${uri}`); diff --git a/packages/backend/src/remote/activitypub/kernel/remove/index.ts b/packages/backend/src/remote/activitypub/kernel/remove/index.ts index 7d7b3386c0a8294e556dfb83abc014736ade8066..11a994a83bc95379dd1b7d3a82952bfdff073e22 100644 --- a/packages/backend/src/remote/activitypub/kernel/remove/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/remove/index.ts @@ -1,9 +1,9 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IRemove } from '../../type.js'; import { resolveNote } from '../../models/note.js'; import { removePinned } from '@/services/i/pin.js'; -export default async (actor: IRemoteUser, activity: IRemove): Promise<void> => { +export default async (actor: CacheableRemoteUser, activity: IRemove): Promise<void> => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } diff --git a/packages/backend/src/remote/activitypub/kernel/undo/accept.ts b/packages/backend/src/remote/activitypub/kernel/undo/accept.ts index 2383eea5bd362cec342120ae4381b7f050881eb4..e2f77ca79223a2d6d7053dfd03c733fbee496b3d 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/accept.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/accept.ts @@ -1,11 +1,11 @@ import unfollow from '@/services/following/delete.js'; import cancelRequest from '@/services/following/requests/cancel.js'; import {IAccept} from '../../type.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { Followings } from '@/models/index.js'; import DbResolver from '../../db-resolver.js'; -export default async (actor: IRemoteUser, activity: IAccept): Promise<string> => { +export default async (actor: CacheableRemoteUser, activity: IAccept): Promise<string> => { const dbResolver = new DbResolver(); const follower = await dbResolver.getUserFromApId(activity.object); diff --git a/packages/backend/src/remote/activitypub/kernel/undo/announce.ts b/packages/backend/src/remote/activitypub/kernel/undo/announce.ts index 822c1e49485c884c97b9a0d32f0a895394047fc8..f31aca1d8d44b88f0f9c8136a3a2fdaf33f24e16 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/announce.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/announce.ts @@ -1,9 +1,9 @@ import { Notes } from '@/models/index.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IAnnounce, getApId } from '../../type.js'; import deleteNote from '@/services/note/delete.js'; -export const undoAnnounce = async (actor: IRemoteUser, activity: IAnnounce): Promise<string> => { +export const undoAnnounce = async (actor: CacheableRemoteUser, activity: IAnnounce): Promise<string> => { const uri = getApId(activity); const note = await Notes.findOne({ diff --git a/packages/backend/src/remote/activitypub/kernel/undo/block.ts b/packages/backend/src/remote/activitypub/kernel/undo/block.ts index 844b067e2b1781b2df8767ad1139395d225b0647..a4bb5e604f80e2678b96ad90b9469b0a71259486 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/block.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/block.ts @@ -1,9 +1,10 @@ import { IBlock } from '../../type.js'; import unblock from '@/services/blocking/delete.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import DbResolver from '../../db-resolver.js'; +import { Users } from '@/models/index.js'; -export default async (actor: IRemoteUser, activity: IBlock): Promise<string> => { +export default async (actor: CacheableRemoteUser, activity: IBlock): Promise<string> => { const dbResolver = new DbResolver(); const blockee = await dbResolver.getUserFromApId(activity.object); @@ -15,6 +16,6 @@ export default async (actor: IRemoteUser, activity: IBlock): Promise<string> => return `skip: ブãƒãƒƒã‚¯è§£é™¤ã—よã†ã¨ã—ã¦ã„るユーザーã¯ãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã§ã¯ã‚ã‚Šã¾ã›ã‚“`; } - await unblock(actor, blockee); + await unblock(await Users.findOneOrFail(actor.id), blockee); return `ok`; }; diff --git a/packages/backend/src/remote/activitypub/kernel/undo/follow.ts b/packages/backend/src/remote/activitypub/kernel/undo/follow.ts index 6715adcf7665833bb5e29184fc794c9748797540..f501cc8cc6c68547c0d64ac0b433971235475e41 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/follow.ts @@ -1,11 +1,11 @@ import unfollow from '@/services/following/delete.js'; import cancelRequest from '@/services/following/requests/cancel.js'; import { IFollow } from '../../type.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { FollowRequests, Followings } from '@/models/index.js'; import DbResolver from '../../db-resolver.js'; -export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => { +export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => { const dbResolver = new DbResolver(); const followee = await dbResolver.getUserFromApId(activity.object); diff --git a/packages/backend/src/remote/activitypub/kernel/undo/index.ts b/packages/backend/src/remote/activitypub/kernel/undo/index.ts index 05937c685585000076cee2e5aba69d8a097b8e5f..27d433eb331c9cff6733f09941f5a707184ad799 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/index.ts @@ -1,5 +1,5 @@ -import { IRemoteUser } from '@/models/entities/user.js'; -import {IUndo, isFollow, isBlock, isLike, isAnnounce, getApType, isAccept} from '../../type.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IUndo, isFollow, isBlock, isLike, isAnnounce, getApType, isAccept } from '../../type.js'; import unfollow from './follow.js'; import unblock from './block.js'; import undoLike from './like.js'; @@ -10,7 +10,7 @@ import { apLogger } from '../../logger.js'; const logger = apLogger; -export default async (actor: IRemoteUser, activity: IUndo): Promise<string> => { +export default async (actor: CacheableRemoteUser, activity: IUndo): Promise<string> => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } diff --git a/packages/backend/src/remote/activitypub/kernel/undo/like.ts b/packages/backend/src/remote/activitypub/kernel/undo/like.ts index 08ac63035114da3832a4cb01cf86097870dd46f7..01aeba1fb75003622bab5b7b82d8705d15b187cd 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/like.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/like.ts @@ -1,4 +1,4 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { ILike, getApId } from '../../type.js'; import deleteReaction from '@/services/note/reaction/delete.js'; import { fetchNote } from '../../models/note.js'; @@ -6,7 +6,7 @@ import { fetchNote } from '../../models/note.js'; /** * Process Undo.Like activity */ -export default async (actor: IRemoteUser, activity: ILike) => { +export default async (actor: CacheableRemoteUser, activity: ILike) => { const targetUri = getApId(activity.object); const note = await fetchNote(targetUri); diff --git a/packages/backend/src/remote/activitypub/kernel/update/index.ts b/packages/backend/src/remote/activitypub/kernel/update/index.ts index 7888c698e3fab7e3a9c749efe61bf67ec5e7222a..9e8a81bb39fd226690f4f65fcf68ec1188d78f11 100644 --- a/packages/backend/src/remote/activitypub/kernel/update/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/update/index.ts @@ -1,4 +1,4 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { getApType, IUpdate, isActor } from '../../type.js'; import { apLogger } from '../../logger.js'; import { updateQuestion } from '../../models/question.js'; @@ -8,7 +8,7 @@ import { updatePerson } from '../../models/person.js'; /** * UpdateアクティビティをæŒãã¾ã™ */ -export default async (actor: IRemoteUser, activity: IUpdate): Promise<string> => { +export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise<string> => { if ('actor' in activity && actor.uri !== activity.actor) { return `skip: invalid actor`; } diff --git a/packages/backend/src/remote/activitypub/models/image.ts b/packages/backend/src/remote/activitypub/models/image.ts index b5e9181d3031e4393f892ea0fd527559af65b678..33316dcd988e8fc958e0c8f8b599be180ca0a853 100644 --- a/packages/backend/src/remote/activitypub/models/image.ts +++ b/packages/backend/src/remote/activitypub/models/image.ts @@ -1,10 +1,10 @@ import { uploadFromUrl } from '@/services/drive/upload-from-url.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser, IRemoteUser } from '@/models/entities/user.js'; import Resolver from '../resolver.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { apLogger } from '../logger.js'; import { DriveFile } from '@/models/entities/drive-file.js'; -import { DriveFiles } from '@/models/index.js'; +import { DriveFiles, Users } from '@/models/index.js'; import { truncate } from '@/misc/truncate.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; @@ -13,7 +13,7 @@ const logger = apLogger; /** * Imageを作æˆã—ã¾ã™ã€‚ */ -export async function createImage(actor: IRemoteUser, value: any): Promise<DriveFile> { +export async function createImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> { // 投稿者ãŒå‡çµã•ã‚Œã¦ã„ãŸã‚‰ã‚¹ã‚ップ if (actor.isSuspended) { throw new Error('actor has been suspended'); @@ -60,7 +60,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<Drive * Misskeyã«å¯¾è±¡ã®ImageãŒç™»éŒ²ã•ã‚Œã¦ã„ã‚Œã°ãれを返ã—ã€ãã†ã§ãªã‘れ㰠* リモートサーãƒãƒ¼ã‹ã‚‰ãƒ•ã‚§ãƒƒãƒã—ã¦Misskeyã«ç™»éŒ²ã—ãれを返ã—ã¾ã™ã€‚ */ -export async function resolveImage(actor: IRemoteUser, value: any): Promise<DriveFile> { +export async function resolveImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> { // TODO // リモートサーãƒãƒ¼ã‹ã‚‰ãƒ•ã‚§ãƒƒãƒã—ã¦ãã¦ç™»éŒ² diff --git a/packages/backend/src/remote/activitypub/models/mention.ts b/packages/backend/src/remote/activitypub/models/mention.ts index c5b0ea53ceee248a2f4337c7f0dcf4e9e154e3e9..a160092969cba453294ae3b9311f6752d18c2d04 100644 --- a/packages/backend/src/remote/activitypub/models/mention.ts +++ b/packages/backend/src/remote/activitypub/models/mention.ts @@ -3,17 +3,17 @@ import { IObject, isMention, IApMention } from '../type.js'; import { resolvePerson } from './person.js'; import promiseLimit from 'promise-limit'; import Resolver from '../resolver.js'; -import { User } from '@/models/entities/user.js'; +import { CacheableUser, User } from '@/models/entities/user.js'; export async function extractApMentions(tags: IObject | IObject[] | null | undefined) { const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string)); const resolver = new Resolver(); - const limit = promiseLimit<User | null>(2); + const limit = promiseLimit<CacheableUser | null>(2); const mentionedUsers = (await Promise.all( hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null))) - )).filter((x): x is User => x != null); + )).filter((x): x is CacheableUser => x != null); return mentionedUsers; } diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index 0bcc89fbbbe7f9178b66b6d41cc6a7c351e597a4..bdcec6b2571fc20d03b0bacbebd131b29df33dcf 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -5,7 +5,7 @@ import Resolver from '../resolver.js'; import post from '@/services/note/create.js'; import { resolvePerson, updatePerson } from './person.js'; import { resolveImage } from './image.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser, IRemoteUser } from '@/models/entities/user.js'; import { htmlToMfm } from '../misc/html-to-mfm.js'; import { extractApHashtags } from './tag.js'; import { unique, toArray, toSingle } from '@/prelude/array.js'; @@ -15,7 +15,7 @@ import { apLogger } from '../logger.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'; -import { Emojis, Polls, MessagingMessages } from '@/models/index.js'; +import { Emojis, Polls, MessagingMessages, Users } from '@/models/index.js'; import { Note } from '@/models/entities/note.js'; import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji, getApType } from '../type.js'; import { Emoji } from '@/models/entities/emoji.js'; @@ -90,7 +90,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s logger.info(`Creating the Note: ${note.id}`); // 投稿者をフェッム- const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as IRemoteUser; + const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser; // 投稿者ãŒå‡çµã•ã‚Œã¦ã„ãŸã‚‰ã‚¹ã‚ップ if (actor.isSuspended) { @@ -230,11 +230,6 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s const poll = await extractPollFromQuestion(note, resolver).catch(() => undefined); - // ユーザーã®æƒ…å ±ãŒå¤ã‹ã£ãŸã‚‰ã¤ã„ã§ã«æ›´æ–°ã—ã¦ãŠã - if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { - if (actor.uri) updatePerson(actor.uri); - } - if (isTalk) { for (const recipient of visibleUsers) { await createMessage(actor, recipient, undefined, text || undefined, (files && files.length > 0) ? files[0] : null, object.id); diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index 659d3ac9a2dd9b0bf76b2183bec101e5bc02c4f5..de64a4305bd5a49e70e13519d2941cc0793181fb 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -15,7 +15,7 @@ import { apLogger } from '../logger.js'; import { Note } from '@/models/entities/note.js'; import { updateUsertags } from '@/services/update-hashtag.js'; import { Users, Instances, DriveFiles, Followings, UserProfiles, UserPublickeys } from '@/models/index.js'; -import { User, IRemoteUser } from '@/models/entities/user.js'; +import { User, IRemoteUser, CacheableUser } from '@/models/entities/user.js'; import { Emoji } from '@/models/entities/emoji.js'; import { UserNotePining } from '@/models/entities/user-note-pining.js'; import { genId } from '@/misc/gen-id.js'; @@ -30,6 +30,8 @@ import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { truncate } from '@/misc/truncate.js'; import { StatusError } from '@/misc/fetch.js'; +import { uriPersonCache } from '@/services/user-cache.js'; +import { publishInternalEvent } from '@/services/stream.js'; const logger = apLogger; @@ -91,19 +93,25 @@ function validateActor(x: IObject, uri: string): IActor { * * Misskeyã«å¯¾è±¡ã®PersonãŒç™»éŒ²ã•ã‚Œã¦ã„ã‚Œã°ãれを返ã—ã¾ã™ã€‚ */ -export async function fetchPerson(uri: string, resolver?: Resolver): Promise<User | null> { +export async function fetchPerson(uri: string, resolver?: Resolver): Promise<CacheableUser | null> { if (typeof uri !== 'string') throw new Error('uri is not string'); + const cached = uriPersonCache.get(uri); + if (cached) return cached; + // URIãŒã“ã®ã‚µãƒ¼ãƒãƒ¼ã‚’指ã—ã¦ã„ã‚‹ãªã‚‰ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã‹ã‚‰ãƒ•ã‚§ãƒƒãƒ if (uri.startsWith(config.url + '/')) { const id = uri.split('/').pop(); - return await Users.findOne(id).then(x => x || null); + const u = await Users.findOne(id).then(x => x || null); // TODO: typeorm 3.0 ã«ã—ãŸã‚‰ .then(x => x || null) を消㙠+ if (u) uriPersonCache.set(uri, u); + return u; } //#region ã“ã®ã‚µãƒ¼ãƒãƒ¼ã«æ—¢ã«ç™»éŒ²ã•ã‚Œã¦ã„ãŸã‚‰ãれを返㙠const exist = await Users.findOne({ uri }); if (exist) { + uriPersonCache.set(uri, exist); return exist; } //#endregion @@ -352,6 +360,8 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint location: person['vcard:Address'] || null, }); + publishInternalEvent('remoteUserUpdated', { id: exist.id }); + // ãƒãƒƒã‚·ãƒ¥ã‚¿ã‚°æ›´æ–° updateUsertags(exist, tags); @@ -371,7 +381,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint * Misskeyã«å¯¾è±¡ã®PersonãŒç™»éŒ²ã•ã‚Œã¦ã„ã‚Œã°ãれを返ã—ã€ãã†ã§ãªã‘れ㰠* リモートサーãƒãƒ¼ã‹ã‚‰ãƒ•ã‚§ãƒƒãƒã—ã¦Misskeyã«ç™»éŒ²ã—ãれを返ã—ã¾ã™ã€‚ */ -export async function resolvePerson(uri: string, resolver?: Resolver): Promise<User> { +export async function resolvePerson(uri: string, resolver?: Resolver): Promise<CacheableUser> { if (typeof uri !== 'string') throw new Error('uri is not string'); //#region ã“ã®ã‚µãƒ¼ãƒãƒ¼ã«æ—¢ã«ç™»éŒ²ã•ã‚Œã¦ã„ãŸã‚‰ãれを返㙠diff --git a/packages/backend/src/remote/activitypub/perform.ts b/packages/backend/src/remote/activitypub/perform.ts index 3e1881558655b281c1733ddd6c06b5199f2e1648..a3c10ba94562f5d4caeefd3c93e76a01b3959065 100644 --- a/packages/backend/src/remote/activitypub/perform.ts +++ b/packages/backend/src/remote/activitypub/perform.ts @@ -1,7 +1,17 @@ import { IObject } from './type.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { performActivity } from './kernel/index.js'; +import { updatePerson } from './models/person.js'; -export default async (actor: IRemoteUser, activity: IObject): Promise<void> => { +export default async (actor: CacheableRemoteUser, activity: IObject): Promise<void> => { await performActivity(actor, activity); + + // ã¤ã„ã§ã«ãƒªãƒ¢ãƒ¼ãƒˆãƒ¦ãƒ¼ã‚¶ãƒ¼ã®æƒ…å ±ãŒå¤ã‹ã£ãŸã‚‰æ›´æ–°ã—ã¦ãŠã + if (actor.uri) { + if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { + setImmediate(() => { + updatePerson(actor.uri!); + }); + } + } }; diff --git a/packages/backend/src/server/activitypub/cache.ts b/packages/backend/src/server/activitypub/cache.ts deleted file mode 100644 index eb20d00787a75df43ff41f875adba6fa0f6f2238..0000000000000000000000000000000000000000 --- a/packages/backend/src/server/activitypub/cache.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Cache } from "@/misc/cache.js"; -import { User } from "@/models/entities/user.js"; - -export const userCache = new Cache<User | null>(1000 * 60 * 30); diff --git a/packages/backend/src/server/activitypub/followers.ts b/packages/backend/src/server/activitypub/followers.ts index fdae9dd9283bf5daa956770eeb39c3f391c17a52..f2bdb48bf04f90169e204ab34bb07aa376b9fe68 100644 --- a/packages/backend/src/server/activitypub/followers.ts +++ b/packages/backend/src/server/activitypub/followers.ts @@ -10,7 +10,6 @@ import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js'; import { setResponseType } from '../activitypub.js'; import { Users, Followings, UserProfiles } from '@/models/index.js'; import { LessThan } from 'typeorm'; -import { userCache } from './cache.js'; export default async (ctx: Router.RouterContext) => { const userId = ctx.params.user; @@ -28,11 +27,10 @@ export default async (ctx: Router.RouterContext) => { return; } - // TODO: typeorm 3.0ã«ã—ãŸã‚‰ .then(x => x || null) ã¯æ¶ˆã›ã‚‹ - const user = await userCache.fetch(userId, () => Users.findOne({ + const user = await Users.findOne({ id: userId, host: null, - }).then(x => x || null)); + }); if (user == null) { ctx.status = 404; diff --git a/packages/backend/src/server/activitypub/following.ts b/packages/backend/src/server/activitypub/following.ts index eb1b7a9d8c3417b048c92ab00b6099a42aecf580..16b2b051d3f48bb798749534bd0ac7f76f330a3f 100644 --- a/packages/backend/src/server/activitypub/following.ts +++ b/packages/backend/src/server/activitypub/following.ts @@ -11,7 +11,6 @@ import { setResponseType } from '../activitypub.js'; import { Users, Followings, UserProfiles } from '@/models/index.js'; import { LessThan, FindConditions } from 'typeorm'; import { Following } from '@/models/entities/following.js'; -import { userCache } from './cache.js'; export default async (ctx: Router.RouterContext) => { const userId = ctx.params.user; @@ -29,11 +28,10 @@ export default async (ctx: Router.RouterContext) => { return; } - // TODO: typeorm 3.0ã«ã—ãŸã‚‰ .then(x => x || null) ã¯æ¶ˆã›ã‚‹ - const user = await userCache.fetch(userId, () => Users.findOne({ + const user = await Users.findOne({ id: userId, host: null, - }).then(x => x || null)); + }); if (user == null) { ctx.status = 404; diff --git a/packages/backend/src/server/activitypub/outbox.ts b/packages/backend/src/server/activitypub/outbox.ts index db2a18efcd26a1998669de84cb39179922d22c54..450fdea96313a9249896cbeae86556031ba62499 100644 --- a/packages/backend/src/server/activitypub/outbox.ts +++ b/packages/backend/src/server/activitypub/outbox.ts @@ -15,7 +15,6 @@ import { Users, Notes } from '@/models/index.js'; import { makePaginationQuery } from '../api/common/make-pagination-query.js'; import { Brackets } from 'typeorm'; import { Note } from '@/models/entities/note.js'; -import { userCache } from './cache.js'; export default async (ctx: Router.RouterContext) => { const userId = ctx.params.user; @@ -36,11 +35,10 @@ export default async (ctx: Router.RouterContext) => { return; } - // TODO: typeorm 3.0ã«ã—ãŸã‚‰ .then(x => x || null) ã¯æ¶ˆã›ã‚‹ - const user = await userCache.fetch(userId, () => Users.findOne({ + const user = await Users.findOne({ id: userId, host: null, - }).then(x => x || null)); + }); if (user == null) { ctx.status = 404; diff --git a/packages/backend/src/server/api/authenticate.ts b/packages/backend/src/server/api/authenticate.ts index 7fdf14666e30db4c793f892b18d6ec3036d023aa..8fb397ca5f4336788b3917424e1f4c4530db82bf 100644 --- a/packages/backend/src/server/api/authenticate.ts +++ b/packages/backend/src/server/api/authenticate.ts @@ -1,7 +1,12 @@ import isNativeToken from './common/is-native-token.js'; -import { User } from '@/models/entities/user.js'; +import { CacheableLocalUser, ILocalUser } from '@/models/entities/user.js'; import { Users, AccessTokens, Apps } from '@/models/index.js'; import { AccessToken } from '@/models/entities/access-token.js'; +import { Cache } from '@/misc/cache.js'; +import { App } from '@/models/entities/app.js'; +import { localUserByIdCache, localUserByNativeTokenCache } from '@/services/user-cache.js'; + +const appCache = new Cache<App>(Infinity); export class AuthenticationError extends Error { constructor(message: string) { @@ -10,15 +15,15 @@ export class AuthenticationError extends Error { } } -export default async (token: string | null): Promise<[User | null | undefined, AccessToken | null | undefined]> => { +export default async (token: string | null): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> => { if (token == null) { return [null, null]; } if (isNativeToken(token)) { - // Fetch user - const user = await Users - .findOne({ token }); + // TODO: typeorm 3.0ã«ã—ãŸã‚‰ .then(x => x || null) ã¯æ¶ˆã›ã‚‹ + const user = await localUserByNativeTokenCache.fetch(token, + () => Users.findOne({ token }).then(x => x || null) as Promise<ILocalUser | null>); if (user == null) { throw new AuthenticationError('user not found'); @@ -42,14 +47,14 @@ export default async (token: string | null): Promise<[User | null | undefined, A lastUsedAt: new Date(), }); - const user = await Users - .findOne({ + const user = await localUserByIdCache.fetch(accessToken.userId, + () => Users.findOne({ id: accessToken.userId, // findOne(accessToken.userId) ã®ã‚ˆã†ã«æ›¸ã‹ãªã„ã®ã¯å¾Œæ–¹äº’æ›æ€§ã®ãŸã‚ - }); + }) as Promise<ILocalUser>); if (accessToken.appId) { - const app = await Apps - .findOneOrFail(accessToken.appId); + const app = await appCache.fetch(accessToken.appId, + () => Apps.findOneOrFail(accessToken.appId!)); return [user, { id: accessToken.id, diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts index 5c5ef66019e203dc259d905215ca3f10a8168621..9a85e4565b6227bd27c1248c20676f6be5845577 100644 --- a/packages/backend/src/server/api/call.ts +++ b/packages/backend/src/server/api/call.ts @@ -1,7 +1,7 @@ import Koa from 'koa'; import { performance } from 'perf_hooks'; import { limiter } from './limiter.js'; -import { User } from '@/models/entities/user.js'; +import { CacheableLocalUser, User } from '@/models/entities/user.js'; import endpoints, { IEndpoint } from './endpoints.js'; import { ApiError } from './error.js'; import { apiLogger } from './logger.js'; @@ -13,7 +13,7 @@ const accessDenied = { id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e', }; -export default async (endpoint: string, user: User | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => { +export default async (endpoint: string, user: CacheableLocalUser | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => { const isSecure = user != null && token == null; const ep = endpoints.find(e => e.name === endpoint); diff --git a/packages/backend/src/server/api/define.ts b/packages/backend/src/server/api/define.ts index 4e6d041a293cc4b5986e09602aa3393468918c3d..15298943415ff3e7b8f55abdb5b1c19783b34ae6 100644 --- a/packages/backend/src/server/api/define.ts +++ b/packages/backend/src/server/api/define.ts @@ -1,30 +1,16 @@ import * as fs from 'node:fs'; import Ajv from 'ajv'; -import { ILocalUser } from '@/models/entities/user.js'; +import { CacheableLocalUser, ILocalUser } from '@/models/entities/user.js'; import { IEndpointMeta } from './endpoints.js'; import { ApiError } from './error.js'; import { Schema, SchemaType } from '@/misc/schema.js'; import { AccessToken } from '@/models/entities/access-token.js'; -type SimpleUserInfo = { - id: ILocalUser['id']; - createdAt: ILocalUser['createdAt']; - host: ILocalUser['host']; - username: ILocalUser['username']; - uri: ILocalUser['uri']; - inbox: ILocalUser['inbox']; - sharedInbox: ILocalUser['sharedInbox']; - isAdmin: ILocalUser['isAdmin']; - isModerator: ILocalUser['isModerator']; - isSilenced: ILocalUser['isSilenced']; - showTimelineReplies: ILocalUser['showTimelineReplies']; -}; - export type Response = Record<string, any> | void; // TODO: paramsã®åž‹ã‚’T['params']ã®ã‚¹ã‚ーマ定義ã‹ã‚‰æŽ¨è«–ã™ã‚‹ type executor<T extends IEndpointMeta, Ps extends Schema> = - (params: SchemaType<Ps>, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any, cleanup?: () => any) => + (params: SchemaType<Ps>, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, cleanup?: () => any) => Promise<T['res'] extends undefined ? Response : SchemaType<NonNullable<T['res']>>>; const ajv = new Ajv({ @@ -34,11 +20,11 @@ const ajv = new Ajv({ ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/); export default function <T extends IEndpointMeta, Ps extends Schema>(meta: T, paramDef: Ps, cb: executor<T, Ps>) - : (params: any, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any) => Promise<any> { + : (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any) => Promise<any> { const validate = ajv.compile(paramDef); - return (params: any, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any) => { + return (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any) => { function cleanup() { fs.unlink(file.path, () => {}); } diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts index 4206e3a3c2328806604be1d2ceda2f2878937a5c..60bf0ff09cd383aab56b4c8f04f518a42df4031a 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts @@ -1,5 +1,6 @@ import define from '../../../define.js'; import { Users } from '@/models/index.js'; +import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['admin'], @@ -31,4 +32,6 @@ export default define(meta, paramDef, async (ps) => { await Users.update(user.id, { isModerator: true, }); + + publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: true }); }); diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts index 143119bfe4e525e9e972ec6805f02a434698a390..cf9d7c14a87360504512ce2ad1b1f3d6321fa780 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts @@ -27,4 +27,6 @@ export default define(meta, paramDef, async (ps) => { await Users.update(user.id, { isModerator: false, }); + + publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: false }); }); diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index a435dcc288671d0bc0c9e9500d0f95e1adc73d6b..564b8a02614e0d4634105a76f4acf84479dd85ec 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -29,7 +29,8 @@ export default define(meta, paramDef, async (ps, me) => { throw new Error('user not found'); } - if ((me.isModerator && !me.isAdmin) && user.isAdmin) { + const _me = await Users.findOneOrFail(me.id); + if ((_me.isModerator && !_me.isAdmin) && user.isAdmin) { throw new Error('cannot show info of admin'); } diff --git a/packages/backend/src/server/api/endpoints/admin/silence-user.ts b/packages/backend/src/server/api/endpoints/admin/silence-user.ts index 4a74c3fb00f7e34af0b400a61b2a9ad5522efaf3..4cbed1cf9cc86961adf754be534b6a418d04362d 100644 --- a/packages/backend/src/server/api/endpoints/admin/silence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/silence-user.ts @@ -1,6 +1,7 @@ import define from '../../define.js'; import { Users } from '@/models/index.js'; import { insertModerationLog } from '@/services/insert-moderation-log.js'; +import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['admin'], @@ -33,6 +34,8 @@ export default define(meta, paramDef, async (ps, me) => { isSilenced: true, }); + publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: true }); + insertModerationLog(me, 'silence', { targetId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts index 4e6366aa1865764abdfd51a84ac5490eb2196278..6c6628811794504c97fe1bb9fce4ded8fd45f5c3 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts @@ -1,6 +1,7 @@ import define from '../../define.js'; import { Users } from '@/models/index.js'; import { insertModerationLog } from '@/services/insert-moderation-log.js'; +import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['admin'], @@ -29,6 +30,8 @@ export default define(meta, paramDef, async (ps, me) => { isSilenced: false, }); + publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: false }); + insertModerationLog(me, 'unsilence', { targetId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts index 5f565a63fb941adcc1e4eae501d3a4e169dc0c9d..eac8eabfc09b8ceb04e67833240d48b7306450cc 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts @@ -2,7 +2,7 @@ import { deleteFile } from '@/services/drive/delete-file.js'; import { publishDriveStream } from '@/services/stream.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; -import { DriveFiles } from '@/models/index.js'; +import { DriveFiles, Users } from '@/models/index.js'; export const meta = { tags: ['drive'], @@ -42,7 +42,7 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchFile); } - if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { + if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts index 181365c7e6146ecdfbc77f82d3b798ffbe08406f..16b313cabf45e135943c1a1269c4e8b2a55630f0 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts @@ -1,7 +1,7 @@ import define from '../../../define.js'; import { ApiError } from '../../../error.js'; import { DriveFile } from '@/models/entities/drive-file.js'; -import { DriveFiles } from '@/models/index.js'; +import { DriveFiles, Users } from '@/models/index.js'; export const meta = { tags: ['drive'], @@ -70,7 +70,7 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchFile); } - if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { + if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index ab8e4aeeb2945efa0798276a0a6f11056338f971..30d7847b5bf6495cf1830e76b1d2cd5883cd1a24 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -1,7 +1,7 @@ import { publishDriveStream } from '@/services/stream.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; -import { DriveFiles, DriveFolders } from '@/models/index.js'; +import { DriveFiles, DriveFolders, Users } from '@/models/index.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; export const meta = { @@ -64,7 +64,7 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchFile); } - if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { + if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts index 771c98b2129b41c3aa6a5b4df406da5df074e5c1..ae23d2482e39015fc49685cc55c7891dc8e182d6 100644 --- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts +++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts @@ -1,5 +1,5 @@ import bcrypt from 'bcryptjs'; -import { publishMainStream, publishUserEvent } from '@/services/stream.js'; +import { publishInternalEvent, publishMainStream, publishUserEvent } from '@/services/stream.js'; import generateUserToken from '../../common/generate-native-user-token.js'; import define from '../../define.js'; import { Users, UserProfiles } from '@/models/index.js'; @@ -20,6 +20,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { + const freshUser = await Users.findOneOrFail(user.id); + const oldToken = freshUser.token; + const profile = await UserProfiles.findOneOrFail(user.id); // Compare password @@ -29,14 +32,14 @@ export default define(meta, paramDef, async (ps, user) => { throw new Error('incorrect password'); } - // Generate secret - const secret = generateUserToken(); + const newToken = generateUserToken(); await Users.update(user.id, { - token: secret, + token: newToken, }); // Publish event + publishInternalEvent('userTokenRegenerated', { id: user.id, oldToken, newToken }); publishMainStream(user.id, 'myTokenRegenerated'); // Terminate streaming diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index 22ff2275caafeb1744e85dcacd71c443729b2f8f..a1ab06d4614a6f7662bce9c87e740aeeccced945 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -48,7 +48,7 @@ export default define(meta, paramDef, async (ps, user) => { throw e; }); - if (!user.isAdmin && !user.isModerator && (note.userId !== user.id)) { + if ((!user.isAdmin && !user.isModerator) && (note.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 26aaa0919c1ce74ebad3f720222483bffe807654..09a819466506bea96e3a2a543ad3ef71eb66dbf8 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -2,7 +2,7 @@ import define from '../../define.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { ApiError } from '../../error.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { Notes } from '@/models/index.js'; +import { Notes, Users } from '@/models/index.js'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query.js'; import { activeUsersChart } from '@/services/chart/index.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 9bcb64b6565d92c29af8eeaa92e97e7a7b50c2c6..7c9c122963bc90783690477cee0b38df3bf67d82 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -2,7 +2,7 @@ import define from '../../define.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { ApiError } from '../../error.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { Followings, Notes } from '@/models/index.js'; +import { Followings, Notes, Users } from '@/models/index.js'; import { Brackets } from 'typeorm'; import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; @@ -56,7 +56,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { const m = await fetchMeta(); - if (m.disableLocalTimeline && !user.isAdmin && !user.isModerator) { + if (m.disableLocalTimeline && (!user.isAdmin && !user.isModerator)) { throw new ApiError(meta.errors.stlDisabled); } diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index 12fc88b1fd4b35018f5aaf57fdf53c385f81b993..bb0bbe2a2014bd967a792853091db47defecb62a 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -1,7 +1,7 @@ import define from '../../define.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { ApiError } from '../../error.js'; -import { Notes } from '@/models/index.js'; +import { Notes, Users } from '@/models/index.js'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query.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/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index 70db12fb14bd7cf568fc301ba52d1708bf11a37c..3555424fa664a2e1c225c3f23336c0d41d59aec6 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -1,5 +1,4 @@ import define from '../../define.js'; -import { getNote } from '../../common/getters.js'; import { ApiError } from '../../error.js'; import { NoteReactions } from '@/models/index.js'; import { DeepPartial } from 'typeorm'; diff --git a/packages/backend/src/server/api/limiter.ts b/packages/backend/src/server/api/limiter.ts index 7e6b93b39fd89f6c9f3cfae91f5a176a6f1e3a92..e74db8466e6239696a84ba3f2b26691d7b9d0be9 100644 --- a/packages/backend/src/server/api/limiter.ts +++ b/packages/backend/src/server/api/limiter.ts @@ -2,12 +2,12 @@ import Limiter from 'ratelimiter'; import { redisClient } from '../../db/redis.js'; import { IEndpoint } from './endpoints.js'; import * as Acct from '@/misc/acct.js'; -import { User } from '@/models/entities/user.js'; +import { CacheableLocalUser, User } from '@/models/entities/user.js'; import Logger from '@/services/logger.js'; const logger = new Logger('limiter'); -export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user: User) => new Promise<void>((ok, reject) => { +export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user: CacheableLocalUser) => new Promise<void>((ok, reject) => { const limitation = endpoint.meta.limit; const key = Object.prototype.hasOwnProperty.call(limitation, 'key') diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index 90cf59038d4ba6e334b39f335576244c63ef19cb..bea863eb7cd9f011cacaad926b244804cf8a6095 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -18,6 +18,11 @@ import { Packed } from '@/misc/schema.js'; //#region Stream type-body definitions export interface InternalStreamTypes { + userChangeSuspendedState: { id: User['id']; isSuspended: User['isSuspended']; }; + userChangeSilencedState: { id: User['id']; isSilenced: User['isSilenced']; }; + userChangeModeratorState: { id: User['id']; isModerator: User['isModerator']; }; + userTokenRegenerated: { id: User['id']; oldToken: User['token']; newToken: User['token']; }; + remoteUserUpdated: { id: User['id']; }; antennaCreated: Antenna; antennaDeleted: Antenna; antennaUpdated: Antenna; diff --git a/packages/backend/src/services/following/reject.ts b/packages/backend/src/services/following/reject.ts index 3b0cb2ba88ff4de17da072d9ad743a730ade9a2d..a108d1c1c57b3aae57a3c7c43e21c72922d41af7 100644 --- a/packages/backend/src/services/following/reject.ts +++ b/packages/backend/src/services/following/reject.ts @@ -7,8 +7,17 @@ import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js'; import { Users, FollowRequests, Followings } from '@/models/index.js'; import { decrementFollowing } from './delete.js'; -type Local = ILocalUser | { id: User['id']; host: User['host']; uri: User['host'] }; -type Remote = IRemoteUser; +type Local = ILocalUser | { + id: ILocalUser['id']; + host: ILocalUser['host']; + uri: ILocalUser['uri'] +}; +type Remote = IRemoteUser | { + id: IRemoteUser['id']; + host: IRemoteUser['host']; + uri: IRemoteUser['uri']; + inbox: IRemoteUser['inbox']; +}; type Both = Local | Remote; /** diff --git a/packages/backend/src/services/messages/create.ts b/packages/backend/src/services/messages/create.ts index c3908b255289c4da764fe2097036035d063cc338..17479a85f93a32dc6bc54d321cb84e8927e0b5aa 100644 --- a/packages/backend/src/services/messages/create.ts +++ b/packages/backend/src/services/messages/create.ts @@ -1,4 +1,4 @@ -import { User } from '@/models/entities/user.js'; +import { CacheableUser, User } from '@/models/entities/user.js'; import { UserGroup } from '@/models/entities/user-group.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { MessagingMessages, UserGroupJoinings, Mutings, Users } from '@/models/index.js'; @@ -13,7 +13,7 @@ import renderCreate from '@/remote/activitypub/renderer/create.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import { deliver } from '@/queue/index.js'; -export async function createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: User | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) { +export async function createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: CacheableUser | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) { const message = { id: genId(), createdAt: new Date(), diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index b295534cd24b455cc4aa7ffd4092444edb678254..f4b0d52045a75bda6dbb6d7f9eab236c1f9b0f34 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -38,8 +38,6 @@ import { endedPollNotificationQueue } from '@/queue/queues.js'; import { Cache } from '@/misc/cache.js'; import { UserProfile } from '@/models/entities/user-profile.js'; -const usersCache = new Cache<MinimumUser>(Infinity); - const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5); type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; @@ -212,7 +210,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 usersCache.fetch(data.reply.userId, () => Users.findOneOrFail(data.reply!.userId))); + mentionedUsers.push(await Users.findOneOrFail(data.reply!.userId)); } if (data.visibility === 'specified') { @@ -225,7 +223,7 @@ 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 usersCache.fetch(data.reply.userId, () => Users.findOneOrFail(data.reply!.userId))); + data.visibleUsers.push(await Users.findOneOrFail(data.reply!.userId)); } } diff --git a/packages/backend/src/services/note/delete.ts b/packages/backend/src/services/note/delete.ts index 356dc39727c4d4a5439527b9cabb0dd42473d3c3..1caac2b88f56a22450eb69c7762ea7e3c5d3adfc 100644 --- a/packages/backend/src/services/note/delete.ts +++ b/packages/backend/src/services/note/delete.ts @@ -20,7 +20,7 @@ import { Brackets, In } from 'typeorm'; * @param user 投稿者 * @param note 投稿 */ -export default async function(user: User, note: Note, quiet = false) { +export default async function(user: { id: User['id']; uri: User['uri']; host: User['host']; }, note: Note, quiet = false) { const deletedAt = new Date(); // ã“ã®æŠ•ç¨¿ã‚’除ã指定ã—ãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã«ã‚ˆã‚‹æŒ‡å®šã—ãŸãƒŽãƒ¼ãƒˆã®ãƒªãƒŽãƒ¼ãƒˆãŒå˜åœ¨ã—ãªã„ã¨ã @@ -131,7 +131,7 @@ async function getMentionedRemoteUsers(note: Note) { }) as IRemoteUser[]; } -async function deliverToConcerned(user: ILocalUser, note: Note, content: any) { +async function deliverToConcerned(user: { id: ILocalUser['id']; host: null; }, note: Note, content: any) { deliverToFollowers(user, content); deliverToRelays(user, content); const remoteUsers = await getMentionedRemoteUsers(note); diff --git a/packages/backend/src/services/note/polls/vote.ts b/packages/backend/src/services/note/polls/vote.ts index 9b83b1953f4b3b49afb7b9d3f47d9937c3f17f8f..c758e3857227a3cb1df5cb848fecb4f99d0d0f9d 100644 --- a/packages/backend/src/services/note/polls/vote.ts +++ b/packages/backend/src/services/note/polls/vote.ts @@ -1,12 +1,12 @@ import { publishNoteStream } from '@/services/stream.js'; -import { User } from '@/models/entities/user.js'; +import { CacheableUser, User } from '@/models/entities/user.js'; import { Note } from '@/models/entities/note.js'; import { PollVotes, NoteWatchings, Polls, Blockings } from '@/models/index.js'; import { Not } from 'typeorm'; import { genId } from '@/misc/gen-id.js'; import { createNotification } from '../../create-notification.js'; -export default async function(user: User, note: Note, choice: number) { +export default async function(user: CacheableUser, note: Note, choice: number) { const poll = await Polls.findOne(note.id); if (poll == null) throw new Error('poll not found'); diff --git a/packages/backend/src/services/relay.ts b/packages/backend/src/services/relay.ts index 6f0da503fc5dbad14a392cd8d46e6f70c8d7d9d2..ef04e1edf46322ed28b8e062dd8f4e6e0f4186e0 100644 --- a/packages/backend/src/services/relay.ts +++ b/packages/backend/src/services/relay.ts @@ -6,9 +6,13 @@ import { deliver } from '@/queue/index.js'; import { ILocalUser, User } from '@/models/entities/user.js'; import { Users, Relays } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; +import { Cache } from '@/misc/cache.js'; +import { Relay } from '@/models/entities/relay.js'; const ACTOR_USERNAME = 'relay.actor' as const; +const relaysCache = new Cache<Relay[]>(1000 * 60 * 10); + export async function getRelayActor(): Promise<ILocalUser> { const user = await Users.findOne({ host: null, @@ -78,9 +82,9 @@ export async function relayRejected(id: string) { export async function deliverToRelays(user: { id: User['id']; host: null; }, activity: any) { if (activity == null) return; - const relays = await Relays.find({ + const relays = await relaysCache.fetch(null, () => Relays.find({ status: 'accepted', - }); + })); if (relays.length === 0) return; const copy = JSON.parse(JSON.stringify(activity)); diff --git a/packages/backend/src/services/suspend-user.ts b/packages/backend/src/services/suspend-user.ts index 033311a3cca37f136caa03bc1787ac299b5f9ef2..57999763cc92087b2996c28327066995152469e7 100644 --- a/packages/backend/src/services/suspend-user.ts +++ b/packages/backend/src/services/suspend-user.ts @@ -5,8 +5,11 @@ import config from '@/config/index.js'; import { User } from '@/models/entities/user.js'; import { Users, Followings } from '@/models/index.js'; import { Not, IsNull } from 'typeorm'; +import { publishInternalEvent } from './stream'; export async function doPostSuspend(user: { id: User['id']; host: User['host'] }) { + publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); + if (Users.isLocalUser(user)) { // 知り得る全SharedInboxã«Deleteé…ä¿¡ const content = renderActivity(renderDelete(`${config.url}/users/${user.id}`, user)); diff --git a/packages/backend/src/services/unsuspend-user.ts b/packages/backend/src/services/unsuspend-user.ts index 3be081d0eda5500cf4a4357a7a1d1eb2fe40004d..b1f0284ac3aa8c66b20199e7ef20dbce8296e9af 100644 --- a/packages/backend/src/services/unsuspend-user.ts +++ b/packages/backend/src/services/unsuspend-user.ts @@ -6,8 +6,11 @@ import config from '@/config/index.js'; import { User } from '@/models/entities/user.js'; import { Users, Followings } from '@/models/index.js'; import { Not, IsNull } from 'typeorm'; +import { publishInternalEvent } from './stream'; export async function doPostUnsuspend(user: User) { + publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false }); + if (Users.isLocalUser(user)) { // 知り得る全SharedInboxã«Undo Deleteé…ä¿¡ const content = renderActivity(renderUndo(renderDelete(`${config.url}/users/${user.id}`, user), user)); diff --git a/packages/backend/src/services/user-cache.ts b/packages/backend/src/services/user-cache.ts new file mode 100644 index 0000000000000000000000000000000000000000..4cf3526b7021168d66c29783563add6b843153f6 --- /dev/null +++ b/packages/backend/src/services/user-cache.ts @@ -0,0 +1,44 @@ +import { CacheableLocalUser, CacheableUser, ILocalUser, User } from '@/models/entities/user.js'; +import { Users } from '@/models/index.js'; +import { Cache } from '@/misc/cache.js'; +import { subsdcriber } from '@/db/redis.js'; + +export const userByIdCache = new Cache<CacheableUser>(Infinity); +export const localUserByNativeTokenCache = new Cache<CacheableLocalUser | null>(Infinity); +export const localUserByIdCache = new Cache<CacheableLocalUser>(Infinity); +export const uriPersonCache = new Cache<CacheableUser | null>(Infinity); + +subsdcriber.on('message', async (_, data) => { + const obj = JSON.parse(data); + + if (obj.channel === 'internal') { + const { type, body } = obj.message; + switch (type) { + case 'userChangeSuspendedState': + case 'userChangeSilencedState': + case 'userChangeModeratorState': + case 'remoteUserUpdated': { + const user = await Users.findOneOrFail(body.id); + userByIdCache.set(user.id, user); + for (const [k, v] of uriPersonCache.cache.entries()) { + if (v.value?.id === user.id) { + uriPersonCache.set(k, user); + } + } + if (Users.isLocalUser(user)) { + localUserByNativeTokenCache.set(user.token, user); + localUserByIdCache.set(user.id, user); + } + break; + } + case 'userTokenRegenerated': { + const user = await Users.findOneOrFail(body.id) as ILocalUser; + localUserByNativeTokenCache.delete(body.oldToken); + localUserByNativeTokenCache.set(body.newToken, user); + break; + } + default: + break; + } + } +}); diff --git a/packages/client/src/pages/settings/profile.vue b/packages/client/src/pages/settings/profile.vue index 8ed29d5c24fde24cc61268973904f8f8c6459e59..e991d725b6c412b98c04a6dbca316ad1c880fa21 100644 --- a/packages/client/src/pages/settings/profile.vue +++ b/packages/client/src/pages/settings/profile.vue @@ -54,7 +54,7 @@ </FormSlot> <FormSwitch v-model="profile.isCat" class="_formBlock">{{ i18n.ts.flagAsCat }}<template #caption>{{ i18n.ts.flagAsCatDescription }}</template></FormSwitch> - <FormSwitch v-model="profile.showTimelineReplies" class="_formBlock">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }}</template></FormSwitch> + <FormSwitch v-model="profile.showTimelineReplies" class="_formBlock">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}</template></FormSwitch> <FormSwitch v-model="profile.isBot" class="_formBlock">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></FormSwitch> <FormSwitch v-model="profile.alwaysMarkNsfw" class="_formBlock">{{ i18n.ts.alwaysMarkSensitive }}</FormSwitch> diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue index 4bdc82f60100d7d1ce828e3f9c606cbc479a86d4..516ab4d440a37349b629c8211f9dbfe4df6f824b 100644 --- a/packages/client/src/pages/user-info.vue +++ b/packages/client/src/pages/user-info.vue @@ -25,6 +25,7 @@ <FormSwitch v-if="user.host == null && $i.isAdmin && (moderator || !user.isAdmin)" v-model="moderator" class="_formBlock" @update:modelValue="toggleModerator">{{ $ts.moderator }}</FormSwitch> <FormSwitch v-model="silenced" class="_formBlock" @update:modelValue="toggleSilence">{{ $ts.silence }}</FormSwitch> <FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.suspend }}</FormSwitch> + {{ $ts.reflectMayTakeTime }} <FormButton v-if="user.host == null && iAmModerator" class="_formBlock" @click="resetPassword"><i class="fas fa-key"></i> {{ $ts.resetPassword }}</FormButton> </FormSection>