perf(server): refactor and performance improvements
This commit is contained in:
parent
22b56ac65c
commit
ac8c66f5ab
71 changed files with 289 additions and 189 deletions
|
@ -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']) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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`)
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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}`);
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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}`);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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`;
|
||||
};
|
||||
|
|
|
@ -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}`);
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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`);
|
||||
}
|
||||
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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))) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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}`);
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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`;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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`;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
// リモートサーバーからフェッチしてきて登録
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 このサーバーに既に登録されていたらそれを返す
|
||||
|
|
|
@ -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!);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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, () => {});
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
});
|
||||
|
|
|
@ -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 });
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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));
|
||||
|
|
44
packages/backend/src/services/user-cache.ts
Normal file
44
packages/backend/src/services/user-cache.ts
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in a new issue