perf(server): refactor and performance improvements
This commit is contained in:
parent
22b56ac65c
commit
ac8c66f5ab
@ -1,5 +1,5 @@
|
|||||||
export class Cache<T> {
|
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;
|
private lifetime: number;
|
||||||
|
|
||||||
constructor(lifetime: Cache<never>['lifetime']) {
|
constructor(lifetime: Cache<never>['lifetime']) {
|
||||||
|
@ -234,3 +234,9 @@ export interface ILocalUser extends User {
|
|||||||
export interface IRemoteUser extends User {
|
export interface IRemoteUser extends User {
|
||||||
host: string;
|
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 { resolvePerson } from '@/remote/activitypub/models/person.js';
|
||||||
import { LdSignature } from '@/remote/activitypub/misc/ld-signature.js';
|
import { LdSignature } from '@/remote/activitypub/misc/ld-signature.js';
|
||||||
import { StatusError } from '@/misc/fetch.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');
|
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}`;
|
return `Old keyId is no longer supported. ${keyIdLower}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TDOO: キャッシュ
|
|
||||||
const dbResolver = new DbResolver();
|
const dbResolver = new DbResolver();
|
||||||
|
|
||||||
// HTTP-Signature keyIdを元にDBから取得
|
// 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を元にリモートから取得
|
// keyIdでわからなければ、activity.actorを元にDBから取得 || activity.actorを元にリモートから取得
|
||||||
if (authUser == null) {
|
if (authUser == null) {
|
||||||
|
@ -3,26 +3,26 @@ import Resolver from './resolver.js';
|
|||||||
import { resolvePerson } from './models/person.js';
|
import { resolvePerson } from './models/person.js';
|
||||||
import { unique, concat } from '@/prelude/array.js';
|
import { unique, concat } from '@/prelude/array.js';
|
||||||
import promiseLimit from 'promise-limit';
|
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 Visibility = 'public' | 'home' | 'followers' | 'specified';
|
||||||
|
|
||||||
type AudienceInfo = {
|
type AudienceInfo = {
|
||||||
visibility: Visibility,
|
visibility: Visibility,
|
||||||
mentionedUsers: User[],
|
mentionedUsers: CacheableUser[],
|
||||||
visibleUsers: User[],
|
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 toGroups = groupingAudience(getApIds(to), actor);
|
||||||
const ccGroups = groupingAudience(getApIds(cc), actor);
|
const ccGroups = groupingAudience(getApIds(cc), actor);
|
||||||
|
|
||||||
const others = unique(concat([toGroups.other, ccGroups.other]));
|
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(
|
const mentionedUsers = (await Promise.all(
|
||||||
others.map(id => limit(() => resolvePerson(id, resolver).catch(() => null)))
|
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) {
|
if (toGroups.public.length > 0) {
|
||||||
return {
|
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 = {
|
const groups = {
|
||||||
public: [] as string[],
|
public: [] as string[],
|
||||||
followers: [] as string[],
|
followers: [] as string[],
|
||||||
@ -85,7 +85,7 @@ function isPublic(id: string) {
|
|||||||
].includes(id);
|
].includes(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isFollowers(id: string, actor: IRemoteUser) {
|
function isFollowers(id: string, actor: CacheableRemoteUser) {
|
||||||
return (
|
return (
|
||||||
id === (actor.followersUri || `${actor.uri}/followers`)
|
id === (actor.followersUri || `${actor.uri}/followers`)
|
||||||
);
|
);
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
|
import escapeRegexp from 'escape-regexp';
|
||||||
import config from '@/config/index.js';
|
import config from '@/config/index.js';
|
||||||
import { Note } from '@/models/entities/note.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 { UserPublickey } from '@/models/entities/user-publickey.js';
|
||||||
import { MessagingMessage } from '@/models/entities/messaging-message.js';
|
import { MessagingMessage } from '@/models/entities/messaging-message.js';
|
||||||
import { Notes, Users, UserPublickeys, MessagingMessages } from '@/models/index.js';
|
import { Notes, Users, UserPublickeys, MessagingMessages } from '@/models/index.js';
|
||||||
import { IObject, getApId } from './type.js';
|
import { IObject, getApId } from './type.js';
|
||||||
import { resolvePerson } from './models/person.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 {
|
export default class DbResolver {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -75,17 +80,24 @@ export default class DbResolver {
|
|||||||
/**
|
/**
|
||||||
* AP KeyId => Misskey User and Key
|
* AP KeyId => Misskey User and Key
|
||||||
*/
|
*/
|
||||||
public async getAuthUserFromKeyId(keyId: string): Promise<AuthUser | null> {
|
public async getAuthUserFromKeyId(keyId: string): Promise<{
|
||||||
|
user: CacheableRemoteUser;
|
||||||
|
key: UserPublickey;
|
||||||
|
} | null> {
|
||||||
|
const key = await publicKeyCache.fetch(keyId, async () => {
|
||||||
const key = await UserPublickeys.findOne({
|
const key = await UserPublickeys.findOne({
|
||||||
keyId,
|
keyId,
|
||||||
}, {
|
|
||||||
relations: ['user'],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (key == null) return null;
|
if (key == null) return null;
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}, key => key != null);
|
||||||
|
|
||||||
|
if (key == null) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user: key.user as IRemoteUser,
|
user: await userByIdCache.fetch(key.userId, () => Users.findOneOrFail(key.userId)) as CacheableRemoteUser,
|
||||||
key,
|
key,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -93,12 +105,15 @@ export default class DbResolver {
|
|||||||
/**
|
/**
|
||||||
* AP Actor id => Misskey User and Key
|
* AP Actor id => Misskey User and Key
|
||||||
*/
|
*/
|
||||||
public async getAuthUserFromApId(uri: string): Promise<AuthUser | null> {
|
public async getAuthUserFromApId(uri: string): Promise<{
|
||||||
const user = await resolvePerson(uri) as IRemoteUser;
|
user: CacheableRemoteUser;
|
||||||
|
key: UserPublickey | null;
|
||||||
|
} | null> {
|
||||||
|
const user = await resolvePerson(uri) as CacheableRemoteUser;
|
||||||
|
|
||||||
if (user == null) return null;
|
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 {
|
return {
|
||||||
user,
|
user,
|
||||||
@ -125,11 +140,6 @@ export default class DbResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AuthUser = {
|
|
||||||
user: IRemoteUser;
|
|
||||||
key?: UserPublickey;
|
|
||||||
};
|
|
||||||
|
|
||||||
type UriParseResult = {
|
type UriParseResult = {
|
||||||
/** id in DB (local object only) */
|
/** id in DB (local object only) */
|
||||||
id?: string;
|
id?: string;
|
||||||
|
@ -112,7 +112,7 @@ export default class DeliverManager {
|
|||||||
* @param activity Activity
|
* @param activity Activity
|
||||||
* @param from Followee
|
* @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);
|
const manager = new DeliverManager(actor, activity);
|
||||||
manager.addFollowersRecipe();
|
manager.addFollowersRecipe();
|
||||||
await manager.execute();
|
await manager.execute();
|
||||||
@ -123,7 +123,7 @@ export async function deliverToFollowers(actor: ILocalUser, activity: any) {
|
|||||||
* @param activity Activity
|
* @param activity Activity
|
||||||
* @param to Target user
|
* @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);
|
const manager = new DeliverManager(actor, activity);
|
||||||
manager.addDirectRecipe(to);
|
manager.addDirectRecipe(to);
|
||||||
await manager.execute();
|
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 accept from '@/services/following/requests/accept.js';
|
||||||
import { IFollow } from '../../type.js';
|
import { IFollow } from '../../type.js';
|
||||||
import DbResolver from '../../db-resolver.js';
|
import DbResolver from '../../db-resolver.js';
|
||||||
import { relayAccepted } from '@/services/relay.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は存在するローカルユーザーである必要がある
|
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
|
||||||
|
|
||||||
const dbResolver = new DbResolver();
|
const dbResolver = new DbResolver();
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import Resolver from '../../resolver.js';
|
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 acceptFollow from './follow.js';
|
||||||
import { IAccept, isFollow, getApType } from '../../type.js';
|
import { IAccept, isFollow, getApType } from '../../type.js';
|
||||||
import { apLogger } from '../../logger.js';
|
import { apLogger } from '../../logger.js';
|
||||||
|
|
||||||
const logger = apLogger;
|
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;
|
const uri = activity.id || activity;
|
||||||
|
|
||||||
logger.info(`Accept: ${uri}`);
|
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 { IAdd } from '../../type.js';
|
||||||
import { resolveNote } from '../../models/note.js';
|
import { resolveNote } from '../../models/note.js';
|
||||||
import { addPinned } from '@/services/i/pin.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) {
|
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||||
throw new Error('invalid actor');
|
throw new Error('invalid actor');
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import Resolver from '../../resolver.js';
|
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 announceNote from './note.js';
|
||||||
import { IAnnounce, getApId } from '../../type.js';
|
import { IAnnounce, getApId } from '../../type.js';
|
||||||
import { apLogger } from '../../logger.js';
|
import { apLogger } from '../../logger.js';
|
||||||
|
|
||||||
const logger = apLogger;
|
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);
|
const uri = getApId(activity);
|
||||||
|
|
||||||
logger.info(`Announce: ${uri}`);
|
logger.info(`Announce: ${uri}`);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Resolver from '../../resolver.js';
|
import Resolver from '../../resolver.js';
|
||||||
import post from '@/services/note/create.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 { IAnnounce, getApId } from '../../type.js';
|
||||||
import { fetchNote, resolveNote } from '../../models/note.js';
|
import { fetchNote, resolveNote } from '../../models/note.js';
|
||||||
import { apLogger } from '../../logger.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);
|
const uri = getApId(activity);
|
||||||
|
|
||||||
// アナウンサーが凍結されていたらスキップ
|
|
||||||
if (actor.isSuspended) {
|
if (actor.isSuspended) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { IBlock } from '../../type.js';
|
import { IBlock } from '../../type.js';
|
||||||
import block from '@/services/blocking/create.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 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にブロック対象があり、それは存在するローカルユーザーのはず
|
// ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず
|
||||||
|
|
||||||
const dbResolver = new DbResolver();
|
const dbResolver = new DbResolver();
|
||||||
@ -17,6 +18,6 @@ export default async (actor: IRemoteUser, activity: IBlock): Promise<string> =>
|
|||||||
return `skip: ブロックしようとしているユーザーはローカルユーザーではありません`;
|
return `skip: ブロックしようとしているユーザーはローカルユーザーではありません`;
|
||||||
}
|
}
|
||||||
|
|
||||||
await block(actor, blockee);
|
await block(await Users.findOneOrFail(actor.id), blockee);
|
||||||
return `ok`;
|
return `ok`;
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Resolver from '../../resolver.js';
|
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 createNote from './note.js';
|
||||||
import { ICreate, getApId, isPost, getApType } from '../../type.js';
|
import { ICreate, getApId, isPost, getApType } from '../../type.js';
|
||||||
import { apLogger } from '../../logger.js';
|
import { apLogger } from '../../logger.js';
|
||||||
@ -7,7 +7,7 @@ import { toArray, concat, unique } from '@/prelude/array.js';
|
|||||||
|
|
||||||
const logger = apLogger;
|
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);
|
const uri = getApId(activity);
|
||||||
|
|
||||||
logger.info(`Create: ${uri}`);
|
logger.info(`Create: ${uri}`);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Resolver from '../../resolver.js';
|
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 { createNote, fetchNote } from '../../models/note.js';
|
||||||
import { getApId, IObject, ICreate } from '../../type.js';
|
import { getApId, IObject, ICreate } from '../../type.js';
|
||||||
import { getApLock } from '@/misc/app-lock.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);
|
const uri = getApId(note);
|
||||||
|
|
||||||
if (typeof note === 'object') {
|
if (typeof note === 'object') {
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
import { apLogger } from '../../logger.js';
|
import { apLogger } from '../../logger.js';
|
||||||
import { createDeleteAccountJob } from '@/queue/index.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';
|
import { Users } from '@/models/index.js';
|
||||||
|
|
||||||
const logger = apLogger;
|
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}`);
|
logger.info(`Deleting the Actor: ${uri}`);
|
||||||
|
|
||||||
if (actor.uri !== uri) {
|
if (actor.uri !== uri) {
|
||||||
return `skip: delete actor ${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`);
|
logger.info(`skip: already deleted`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import deleteNote from './note.js';
|
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 { IDelete, getApId, isTombstone, IObject, validPost, validActor } from '../../type.js';
|
||||||
import { toSingle } from '@/prelude/array.js';
|
import { toSingle } from '@/prelude/array.js';
|
||||||
import { deleteActor } from './actor.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) {
|
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||||
throw new Error('invalid 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 deleteNode from '@/services/note/delete.js';
|
||||||
import { apLogger } from '../../logger.js';
|
import { apLogger } from '../../logger.js';
|
||||||
import DbResolver from '../../db-resolver.js';
|
import DbResolver from '../../db-resolver.js';
|
||||||
@ -7,7 +7,7 @@ import { deleteMessage } from '@/services/messages/delete.js';
|
|||||||
|
|
||||||
const logger = apLogger;
|
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}`);
|
logger.info(`Deleting the Note: ${uri}`);
|
||||||
|
|
||||||
const unlock = await getApLock(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 config from '@/config/index.js';
|
||||||
import { IFlag, getApIds } from '../../type.js';
|
import { IFlag, getApIds } from '../../type.js';
|
||||||
import { AbuseUserReports, Users } from '@/models/index.js';
|
import { AbuseUserReports, Users } from '@/models/index.js';
|
||||||
import { In } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
import { genId } from '@/misc/gen-id.js';
|
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スキーマと対応させられないので
|
// objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので
|
||||||
// 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する
|
// 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する
|
||||||
const uris = getApIds(activity.object);
|
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 follow from '@/services/following/create.js';
|
||||||
import { IFollow } from '../type.js';
|
import { IFollow } from '../type.js';
|
||||||
import DbResolver from '../db-resolver.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 dbResolver = new DbResolver();
|
||||||
const followee = await dbResolver.getUserFromApId(activity.object);
|
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 { 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 create from './create/index.js';
|
||||||
import performDeleteActivity from './delete/index.js';
|
import performDeleteActivity from './delete/index.js';
|
||||||
import performUpdateActivity from './update/index.js';
|
import performUpdateActivity from './update/index.js';
|
||||||
@ -17,8 +17,9 @@ import flag from './flag/index.js';
|
|||||||
import { apLogger } from '../logger.js';
|
import { apLogger } from '../logger.js';
|
||||||
import Resolver from '../resolver.js';
|
import Resolver from '../resolver.js';
|
||||||
import { toArray } from '@/prelude/array.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)) {
|
if (isCollectionOrOrderedCollection(activity)) {
|
||||||
const resolver = new Resolver();
|
const resolver = new Resolver();
|
||||||
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
|
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 (actor.isSuspended) return;
|
||||||
|
|
||||||
if (isCreate(activity)) {
|
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 { ILike, getApId } from '../type.js';
|
||||||
import create from '@/services/note/reaction/create.js';
|
import create from '@/services/note/reaction/create.js';
|
||||||
import { fetchNote, extractEmojis } from '../models/note.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 targetUri = getApId(activity.object);
|
||||||
|
|
||||||
const note = await fetchNote(targetUri);
|
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 { IRead, getApId } from '../type.js';
|
||||||
import { isSelfHost, extractDbHost } from '@/misc/convert-host.js';
|
import { isSelfHost, extractDbHost } from '@/misc/convert-host.js';
|
||||||
import { MessagingMessages } from '@/models/index.js';
|
import { MessagingMessages } from '@/models/index.js';
|
||||||
import { readUserMessagingMessage } from '../../../server/api/common/read-messaging-message.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);
|
const id = await getApId(activity.object);
|
||||||
|
|
||||||
if (!isSelfHost(extractDbHost(id))) {
|
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 { remoteReject } from '@/services/following/reject.js';
|
||||||
import { IFollow } from '../../type.js';
|
import { IFollow } from '../../type.js';
|
||||||
import DbResolver from '../../db-resolver.js';
|
import DbResolver from '../../db-resolver.js';
|
||||||
import { relayRejected } from '@/services/relay.js';
|
import { relayRejected } from '@/services/relay.js';
|
||||||
import { Users } from '@/models/index.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は存在するローカルユーザーである必要がある
|
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
|
||||||
|
|
||||||
const dbResolver = new DbResolver();
|
const dbResolver = new DbResolver();
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import Resolver from '../../resolver.js';
|
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 rejectFollow from './follow.js';
|
||||||
import { IReject, isFollow, getApType } from '../../type.js';
|
import { IReject, isFollow, getApType } from '../../type.js';
|
||||||
import { apLogger } from '../../logger.js';
|
import { apLogger } from '../../logger.js';
|
||||||
|
|
||||||
const logger = apLogger;
|
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;
|
const uri = activity.id || activity;
|
||||||
|
|
||||||
logger.info(`Reject: ${uri}`);
|
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 { IRemove } from '../../type.js';
|
||||||
import { resolveNote } from '../../models/note.js';
|
import { resolveNote } from '../../models/note.js';
|
||||||
import { removePinned } from '@/services/i/pin.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) {
|
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||||
throw new Error('invalid actor');
|
throw new Error('invalid actor');
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import unfollow from '@/services/following/delete.js';
|
import unfollow from '@/services/following/delete.js';
|
||||||
import cancelRequest from '@/services/following/requests/cancel.js';
|
import cancelRequest from '@/services/following/requests/cancel.js';
|
||||||
import {IAccept} from '../../type.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 { Followings } from '@/models/index.js';
|
||||||
import DbResolver from '../../db-resolver.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 dbResolver = new DbResolver();
|
||||||
|
|
||||||
const follower = await dbResolver.getUserFromApId(activity.object);
|
const follower = await dbResolver.getUserFromApId(activity.object);
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Notes } from '@/models/index.js';
|
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 { IAnnounce, getApId } from '../../type.js';
|
||||||
import deleteNote from '@/services/note/delete.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 uri = getApId(activity);
|
||||||
|
|
||||||
const note = await Notes.findOne({
|
const note = await Notes.findOne({
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { IBlock } from '../../type.js';
|
import { IBlock } from '../../type.js';
|
||||||
import unblock from '@/services/blocking/delete.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 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 dbResolver = new DbResolver();
|
||||||
const blockee = await dbResolver.getUserFromApId(activity.object);
|
const blockee = await dbResolver.getUserFromApId(activity.object);
|
||||||
|
|
||||||
@ -15,6 +16,6 @@ export default async (actor: IRemoteUser, activity: IBlock): Promise<string> =>
|
|||||||
return `skip: ブロック解除しようとしているユーザーはローカルユーザーではありません`;
|
return `skip: ブロック解除しようとしているユーザーはローカルユーザーではありません`;
|
||||||
}
|
}
|
||||||
|
|
||||||
await unblock(actor, blockee);
|
await unblock(await Users.findOneOrFail(actor.id), blockee);
|
||||||
return `ok`;
|
return `ok`;
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import unfollow from '@/services/following/delete.js';
|
import unfollow from '@/services/following/delete.js';
|
||||||
import cancelRequest from '@/services/following/requests/cancel.js';
|
import cancelRequest from '@/services/following/requests/cancel.js';
|
||||||
import { IFollow } from '../../type.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 { FollowRequests, Followings } from '@/models/index.js';
|
||||||
import DbResolver from '../../db-resolver.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 dbResolver = new DbResolver();
|
||||||
|
|
||||||
const followee = await dbResolver.getUserFromApId(activity.object);
|
const followee = await dbResolver.getUserFromApId(activity.object);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { IRemoteUser } from '@/models/entities/user.js';
|
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||||
import {IUndo, isFollow, isBlock, isLike, isAnnounce, getApType, isAccept} from '../../type.js';
|
import { IUndo, isFollow, isBlock, isLike, isAnnounce, getApType, isAccept } from '../../type.js';
|
||||||
import unfollow from './follow.js';
|
import unfollow from './follow.js';
|
||||||
import unblock from './block.js';
|
import unblock from './block.js';
|
||||||
import undoLike from './like.js';
|
import undoLike from './like.js';
|
||||||
@ -10,7 +10,7 @@ import { apLogger } from '../../logger.js';
|
|||||||
|
|
||||||
const logger = apLogger;
|
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) {
|
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||||
throw new Error('invalid 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 { ILike, getApId } from '../../type.js';
|
||||||
import deleteReaction from '@/services/note/reaction/delete.js';
|
import deleteReaction from '@/services/note/reaction/delete.js';
|
||||||
import { fetchNote } from '../../models/note.js';
|
import { fetchNote } from '../../models/note.js';
|
||||||
@ -6,7 +6,7 @@ import { fetchNote } from '../../models/note.js';
|
|||||||
/**
|
/**
|
||||||
* Process Undo.Like activity
|
* Process Undo.Like activity
|
||||||
*/
|
*/
|
||||||
export default async (actor: IRemoteUser, activity: ILike) => {
|
export default async (actor: CacheableRemoteUser, activity: ILike) => {
|
||||||
const targetUri = getApId(activity.object);
|
const targetUri = getApId(activity.object);
|
||||||
|
|
||||||
const note = await fetchNote(targetUri);
|
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 { getApType, IUpdate, isActor } from '../../type.js';
|
||||||
import { apLogger } from '../../logger.js';
|
import { apLogger } from '../../logger.js';
|
||||||
import { updateQuestion } from '../../models/question.js';
|
import { updateQuestion } from '../../models/question.js';
|
||||||
@ -8,7 +8,7 @@ import { updatePerson } from '../../models/person.js';
|
|||||||
/**
|
/**
|
||||||
* Updateアクティビティを捌きます
|
* 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) {
|
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||||
return `skip: invalid actor`;
|
return `skip: invalid actor`;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { uploadFromUrl } from '@/services/drive/upload-from-url.js';
|
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 Resolver from '../resolver.js';
|
||||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
import { apLogger } from '../logger.js';
|
import { apLogger } from '../logger.js';
|
||||||
import { DriveFile } from '@/models/entities/drive-file.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 { truncate } from '@/misc/truncate.js';
|
||||||
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js';
|
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js';
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ const logger = apLogger;
|
|||||||
/**
|
/**
|
||||||
* Imageを作成します。
|
* Imageを作成します。
|
||||||
*/
|
*/
|
||||||
export async function createImage(actor: IRemoteUser, value: any): Promise<DriveFile> {
|
export async function createImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> {
|
||||||
// 投稿者が凍結されていたらスキップ
|
// 投稿者が凍結されていたらスキップ
|
||||||
if (actor.isSuspended) {
|
if (actor.isSuspended) {
|
||||||
throw new Error('actor has been suspended');
|
throw new Error('actor has been suspended');
|
||||||
@ -60,7 +60,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<Drive
|
|||||||
* Misskeyに対象のImageが登録されていればそれを返し、そうでなければ
|
* Misskeyに対象のImageが登録されていればそれを返し、そうでなければ
|
||||||
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
||||||
*/
|
*/
|
||||||
export async function resolveImage(actor: IRemoteUser, value: any): Promise<DriveFile> {
|
export async function resolveImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> {
|
||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
// リモートサーバーからフェッチしてきて登録
|
// リモートサーバーからフェッチしてきて登録
|
||||||
|
@ -3,17 +3,17 @@ import { IObject, isMention, IApMention } from '../type.js';
|
|||||||
import { resolvePerson } from './person.js';
|
import { resolvePerson } from './person.js';
|
||||||
import promiseLimit from 'promise-limit';
|
import promiseLimit from 'promise-limit';
|
||||||
import Resolver from '../resolver.js';
|
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) {
|
export async function extractApMentions(tags: IObject | IObject[] | null | undefined) {
|
||||||
const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string));
|
const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string));
|
||||||
|
|
||||||
const resolver = new Resolver();
|
const resolver = new Resolver();
|
||||||
|
|
||||||
const limit = promiseLimit<User | null>(2);
|
const limit = promiseLimit<CacheableUser | null>(2);
|
||||||
const mentionedUsers = (await Promise.all(
|
const mentionedUsers = (await Promise.all(
|
||||||
hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null)))
|
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;
|
return mentionedUsers;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import Resolver from '../resolver.js';
|
|||||||
import post from '@/services/note/create.js';
|
import post from '@/services/note/create.js';
|
||||||
import { resolvePerson, updatePerson } from './person.js';
|
import { resolvePerson, updatePerson } from './person.js';
|
||||||
import { resolveImage } from './image.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 { htmlToMfm } from '../misc/html-to-mfm.js';
|
||||||
import { extractApHashtags } from './tag.js';
|
import { extractApHashtags } from './tag.js';
|
||||||
import { unique, toArray, toSingle } from '@/prelude/array.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 { DriveFile } from '@/models/entities/drive-file.js';
|
||||||
import { deliverQuestionUpdate } from '@/services/note/polls/update.js';
|
import { deliverQuestionUpdate } from '@/services/note/polls/update.js';
|
||||||
import { extractDbHost, toPuny } from '@/misc/convert-host.js';
|
import { extractDbHost, toPuny } from '@/misc/convert-host.js';
|
||||||
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 { Note } from '@/models/entities/note.js';
|
||||||
import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji, getApType } from '../type.js';
|
import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji, getApType } from '../type.js';
|
||||||
import { Emoji } from '@/models/entities/emoji.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}`);
|
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) {
|
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);
|
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) {
|
if (isTalk) {
|
||||||
for (const recipient of visibleUsers) {
|
for (const recipient of visibleUsers) {
|
||||||
await createMessage(actor, recipient, undefined, text || undefined, (files && files.length > 0) ? files[0] : null, object.id);
|
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 { Note } from '@/models/entities/note.js';
|
||||||
import { updateUsertags } from '@/services/update-hashtag.js';
|
import { updateUsertags } from '@/services/update-hashtag.js';
|
||||||
import { Users, Instances, DriveFiles, Followings, UserProfiles, UserPublickeys } from '@/models/index.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 { Emoji } from '@/models/entities/emoji.js';
|
||||||
import { UserNotePining } from '@/models/entities/user-note-pining.js';
|
import { UserNotePining } from '@/models/entities/user-note-pining.js';
|
||||||
import { genId } from '@/misc/gen-id.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 { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||||
import { truncate } from '@/misc/truncate.js';
|
import { truncate } from '@/misc/truncate.js';
|
||||||
import { StatusError } from '@/misc/fetch.js';
|
import { StatusError } from '@/misc/fetch.js';
|
||||||
|
import { uriPersonCache } from '@/services/user-cache.js';
|
||||||
|
import { publishInternalEvent } from '@/services/stream.js';
|
||||||
|
|
||||||
const logger = apLogger;
|
const logger = apLogger;
|
||||||
|
|
||||||
@ -91,19 +93,25 @@ function validateActor(x: IObject, uri: string): IActor {
|
|||||||
*
|
*
|
||||||
* Misskeyに対象のPersonが登録されていればそれを返します。
|
* 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');
|
if (typeof uri !== 'string') throw new Error('uri is not string');
|
||||||
|
|
||||||
|
const cached = uriPersonCache.get(uri);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
// URIがこのサーバーを指しているならデータベースからフェッチ
|
// URIがこのサーバーを指しているならデータベースからフェッチ
|
||||||
if (uri.startsWith(config.url + '/')) {
|
if (uri.startsWith(config.url + '/')) {
|
||||||
const id = uri.split('/').pop();
|
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 このサーバーに既に登録されていたらそれを返す
|
//#region このサーバーに既に登録されていたらそれを返す
|
||||||
const exist = await Users.findOne({ uri });
|
const exist = await Users.findOne({ uri });
|
||||||
|
|
||||||
if (exist) {
|
if (exist) {
|
||||||
|
uriPersonCache.set(uri, exist);
|
||||||
return exist;
|
return exist;
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
@ -352,6 +360,8 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
|
|||||||
location: person['vcard:Address'] || null,
|
location: person['vcard:Address'] || null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
publishInternalEvent('remoteUserUpdated', { id: exist.id });
|
||||||
|
|
||||||
// ハッシュタグ更新
|
// ハッシュタグ更新
|
||||||
updateUsertags(exist, tags);
|
updateUsertags(exist, tags);
|
||||||
|
|
||||||
@ -371,7 +381,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
|
|||||||
* Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ
|
* Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ
|
||||||
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
* リモートサーバーからフェッチして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');
|
if (typeof uri !== 'string') throw new Error('uri is not string');
|
||||||
|
|
||||||
//#region このサーバーに既に登録されていたらそれを返す
|
//#region このサーバーに既に登録されていたらそれを返す
|
||||||
|
@ -1,7 +1,17 @@
|
|||||||
import { IObject } from './type.js';
|
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 { 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);
|
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 { setResponseType } from '../activitypub.js';
|
||||||
import { Users, Followings, UserProfiles } from '@/models/index.js';
|
import { Users, Followings, UserProfiles } from '@/models/index.js';
|
||||||
import { LessThan } from 'typeorm';
|
import { LessThan } from 'typeorm';
|
||||||
import { userCache } from './cache.js';
|
|
||||||
|
|
||||||
export default async (ctx: Router.RouterContext) => {
|
export default async (ctx: Router.RouterContext) => {
|
||||||
const userId = ctx.params.user;
|
const userId = ctx.params.user;
|
||||||
@ -28,11 +27,10 @@ export default async (ctx: Router.RouterContext) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: typeorm 3.0にしたら .then(x => x || null) は消せる
|
const user = await Users.findOne({
|
||||||
const user = await userCache.fetch(userId, () => Users.findOne({
|
|
||||||
id: userId,
|
id: userId,
|
||||||
host: null,
|
host: null,
|
||||||
}).then(x => x || null));
|
});
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
ctx.status = 404;
|
ctx.status = 404;
|
||||||
|
@ -11,7 +11,6 @@ import { setResponseType } from '../activitypub.js';
|
|||||||
import { Users, Followings, UserProfiles } from '@/models/index.js';
|
import { Users, Followings, UserProfiles } from '@/models/index.js';
|
||||||
import { LessThan, FindConditions } from 'typeorm';
|
import { LessThan, FindConditions } from 'typeorm';
|
||||||
import { Following } from '@/models/entities/following.js';
|
import { Following } from '@/models/entities/following.js';
|
||||||
import { userCache } from './cache.js';
|
|
||||||
|
|
||||||
export default async (ctx: Router.RouterContext) => {
|
export default async (ctx: Router.RouterContext) => {
|
||||||
const userId = ctx.params.user;
|
const userId = ctx.params.user;
|
||||||
@ -29,11 +28,10 @@ export default async (ctx: Router.RouterContext) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: typeorm 3.0にしたら .then(x => x || null) は消せる
|
const user = await Users.findOne({
|
||||||
const user = await userCache.fetch(userId, () => Users.findOne({
|
|
||||||
id: userId,
|
id: userId,
|
||||||
host: null,
|
host: null,
|
||||||
}).then(x => x || null));
|
});
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
ctx.status = 404;
|
ctx.status = 404;
|
||||||
|
@ -15,7 +15,6 @@ import { Users, Notes } from '@/models/index.js';
|
|||||||
import { makePaginationQuery } from '../api/common/make-pagination-query.js';
|
import { makePaginationQuery } from '../api/common/make-pagination-query.js';
|
||||||
import { Brackets } from 'typeorm';
|
import { Brackets } from 'typeorm';
|
||||||
import { Note } from '@/models/entities/note.js';
|
import { Note } from '@/models/entities/note.js';
|
||||||
import { userCache } from './cache.js';
|
|
||||||
|
|
||||||
export default async (ctx: Router.RouterContext) => {
|
export default async (ctx: Router.RouterContext) => {
|
||||||
const userId = ctx.params.user;
|
const userId = ctx.params.user;
|
||||||
@ -36,11 +35,10 @@ export default async (ctx: Router.RouterContext) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: typeorm 3.0にしたら .then(x => x || null) は消せる
|
const user = await Users.findOne({
|
||||||
const user = await userCache.fetch(userId, () => Users.findOne({
|
|
||||||
id: userId,
|
id: userId,
|
||||||
host: null,
|
host: null,
|
||||||
}).then(x => x || null));
|
});
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
ctx.status = 404;
|
ctx.status = 404;
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import isNativeToken from './common/is-native-token.js';
|
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 { Users, AccessTokens, Apps } from '@/models/index.js';
|
||||||
import { AccessToken } from '@/models/entities/access-token.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 {
|
export class AuthenticationError extends Error {
|
||||||
constructor(message: string) {
|
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) {
|
if (token == null) {
|
||||||
return [null, null];
|
return [null, null];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNativeToken(token)) {
|
if (isNativeToken(token)) {
|
||||||
// Fetch user
|
// TODO: typeorm 3.0にしたら .then(x => x || null) は消せる
|
||||||
const user = await Users
|
const user = await localUserByNativeTokenCache.fetch(token,
|
||||||
.findOne({ token });
|
() => Users.findOne({ token }).then(x => x || null) as Promise<ILocalUser | null>);
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new AuthenticationError('user not found');
|
throw new AuthenticationError('user not found');
|
||||||
@ -42,14 +47,14 @@ export default async (token: string | null): Promise<[User | null | undefined, A
|
|||||||
lastUsedAt: new Date(),
|
lastUsedAt: new Date(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = await Users
|
const user = await localUserByIdCache.fetch(accessToken.userId,
|
||||||
.findOne({
|
() => Users.findOne({
|
||||||
id: accessToken.userId, // findOne(accessToken.userId) のように書かないのは後方互換性のため
|
id: accessToken.userId, // findOne(accessToken.userId) のように書かないのは後方互換性のため
|
||||||
});
|
}) as Promise<ILocalUser>);
|
||||||
|
|
||||||
if (accessToken.appId) {
|
if (accessToken.appId) {
|
||||||
const app = await Apps
|
const app = await appCache.fetch(accessToken.appId,
|
||||||
.findOneOrFail(accessToken.appId);
|
() => Apps.findOneOrFail(accessToken.appId!));
|
||||||
|
|
||||||
return [user, {
|
return [user, {
|
||||||
id: accessToken.id,
|
id: accessToken.id,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Koa from 'koa';
|
import Koa from 'koa';
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
import { limiter } from './limiter.js';
|
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 endpoints, { IEndpoint } from './endpoints.js';
|
||||||
import { ApiError } from './error.js';
|
import { ApiError } from './error.js';
|
||||||
import { apiLogger } from './logger.js';
|
import { apiLogger } from './logger.js';
|
||||||
@ -13,7 +13,7 @@ const accessDenied = {
|
|||||||
id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e',
|
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 isSecure = user != null && token == null;
|
||||||
|
|
||||||
const ep = endpoints.find(e => e.name === endpoint);
|
const ep = endpoints.find(e => e.name === endpoint);
|
||||||
|
@ -1,30 +1,16 @@
|
|||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
import Ajv from 'ajv';
|
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 { IEndpointMeta } from './endpoints.js';
|
||||||
import { ApiError } from './error.js';
|
import { ApiError } from './error.js';
|
||||||
import { Schema, SchemaType } from '@/misc/schema.js';
|
import { Schema, SchemaType } from '@/misc/schema.js';
|
||||||
import { AccessToken } from '@/models/entities/access-token.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;
|
export type Response = Record<string, any> | void;
|
||||||
|
|
||||||
// TODO: paramsの型をT['params']のスキーマ定義から推論する
|
// TODO: paramsの型をT['params']のスキーマ定義から推論する
|
||||||
type executor<T extends IEndpointMeta, Ps extends Schema> =
|
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']>>>;
|
Promise<T['res'] extends undefined ? Response : SchemaType<NonNullable<T['res']>>>;
|
||||||
|
|
||||||
const ajv = new Ajv({
|
const ajv = new Ajv({
|
||||||
@ -34,11 +20,11 @@ const ajv = new Ajv({
|
|||||||
ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/);
|
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>)
|
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);
|
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() {
|
function cleanup() {
|
||||||
fs.unlink(file.path, () => {});
|
fs.unlink(file.path, () => {});
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import define from '../../../define.js';
|
import define from '../../../define.js';
|
||||||
import { Users } from '@/models/index.js';
|
import { Users } from '@/models/index.js';
|
||||||
|
import { publishInternalEvent } from '@/services/stream.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['admin'],
|
tags: ['admin'],
|
||||||
@ -31,4 +32,6 @@ export default define(meta, paramDef, async (ps) => {
|
|||||||
await Users.update(user.id, {
|
await Users.update(user.id, {
|
||||||
isModerator: true,
|
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, {
|
await Users.update(user.id, {
|
||||||
isModerator: false,
|
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');
|
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');
|
throw new Error('cannot show info of admin');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
import { Users } from '@/models/index.js';
|
import { Users } from '@/models/index.js';
|
||||||
import { insertModerationLog } from '@/services/insert-moderation-log.js';
|
import { insertModerationLog } from '@/services/insert-moderation-log.js';
|
||||||
|
import { publishInternalEvent } from '@/services/stream.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['admin'],
|
tags: ['admin'],
|
||||||
@ -33,6 +34,8 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||||||
isSilenced: true,
|
isSilenced: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: true });
|
||||||
|
|
||||||
insertModerationLog(me, 'silence', {
|
insertModerationLog(me, 'silence', {
|
||||||
targetId: user.id,
|
targetId: user.id,
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
import { Users } from '@/models/index.js';
|
import { Users } from '@/models/index.js';
|
||||||
import { insertModerationLog } from '@/services/insert-moderation-log.js';
|
import { insertModerationLog } from '@/services/insert-moderation-log.js';
|
||||||
|
import { publishInternalEvent } from '@/services/stream.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['admin'],
|
tags: ['admin'],
|
||||||
@ -29,6 +30,8 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||||||
isSilenced: false,
|
isSilenced: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: false });
|
||||||
|
|
||||||
insertModerationLog(me, 'unsilence', {
|
insertModerationLog(me, 'unsilence', {
|
||||||
targetId: user.id,
|
targetId: user.id,
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,7 @@ import { deleteFile } from '@/services/drive/delete-file.js';
|
|||||||
import { publishDriveStream } from '@/services/stream.js';
|
import { publishDriveStream } from '@/services/stream.js';
|
||||||
import define from '../../../define.js';
|
import define from '../../../define.js';
|
||||||
import { ApiError } from '../../../error.js';
|
import { ApiError } from '../../../error.js';
|
||||||
import { DriveFiles } from '@/models/index.js';
|
import { DriveFiles, Users } from '@/models/index.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['drive'],
|
tags: ['drive'],
|
||||||
@ -42,7 +42,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
|||||||
throw new ApiError(meta.errors.noSuchFile);
|
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);
|
throw new ApiError(meta.errors.accessDenied);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import define from '../../../define.js';
|
import define from '../../../define.js';
|
||||||
import { ApiError } from '../../../error.js';
|
import { ApiError } from '../../../error.js';
|
||||||
import { DriveFile } from '@/models/entities/drive-file.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 = {
|
export const meta = {
|
||||||
tags: ['drive'],
|
tags: ['drive'],
|
||||||
@ -70,7 +70,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
|||||||
throw new ApiError(meta.errors.noSuchFile);
|
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);
|
throw new ApiError(meta.errors.accessDenied);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { publishDriveStream } from '@/services/stream.js';
|
import { publishDriveStream } from '@/services/stream.js';
|
||||||
import define from '../../../define.js';
|
import define from '../../../define.js';
|
||||||
import { ApiError } from '../../../error.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';
|
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
@ -64,7 +64,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
|||||||
throw new ApiError(meta.errors.noSuchFile);
|
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);
|
throw new ApiError(meta.errors.accessDenied);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import bcrypt from 'bcryptjs';
|
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 generateUserToken from '../../common/generate-native-user-token.js';
|
||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
import { Users, UserProfiles } from '@/models/index.js';
|
import { Users, UserProfiles } from '@/models/index.js';
|
||||||
@ -20,6 +20,9 @@ export const paramDef = {
|
|||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
// eslint-disable-next-line import/no-default-export
|
||||||
export default define(meta, paramDef, async (ps, user) => {
|
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);
|
const profile = await UserProfiles.findOneOrFail(user.id);
|
||||||
|
|
||||||
// Compare password
|
// Compare password
|
||||||
@ -29,14 +32,14 @@ export default define(meta, paramDef, async (ps, user) => {
|
|||||||
throw new Error('incorrect password');
|
throw new Error('incorrect password');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate secret
|
const newToken = generateUserToken();
|
||||||
const secret = generateUserToken();
|
|
||||||
|
|
||||||
await Users.update(user.id, {
|
await Users.update(user.id, {
|
||||||
token: secret,
|
token: newToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Publish event
|
// Publish event
|
||||||
|
publishInternalEvent('userTokenRegenerated', { id: user.id, oldToken, newToken });
|
||||||
publishMainStream(user.id, 'myTokenRegenerated');
|
publishMainStream(user.id, 'myTokenRegenerated');
|
||||||
|
|
||||||
// Terminate streaming
|
// Terminate streaming
|
||||||
|
@ -48,7 +48,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
|||||||
throw e;
|
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);
|
throw new ApiError(meta.errors.accessDenied);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import define from '../../define.js';
|
|||||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query.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 { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
||||||
import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query.js';
|
import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query.js';
|
||||||
import { activeUsersChart } from '@/services/chart/index.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 { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query.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 { Brackets } from 'typeorm';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
||||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-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
|
// eslint-disable-next-line import/no-default-export
|
||||||
export default define(meta, paramDef, async (ps, user) => {
|
export default define(meta, paramDef, async (ps, user) => {
|
||||||
const m = await fetchMeta();
|
const m = await fetchMeta();
|
||||||
if (m.disableLocalTimeline && !user.isAdmin && !user.isModerator) {
|
if (m.disableLocalTimeline && (!user.isAdmin && !user.isModerator)) {
|
||||||
throw new ApiError(meta.errors.stlDisabled);
|
throw new ApiError(meta.errors.stlDisabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
import { ApiError } from '../../error.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 { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
import { getNote } from '../../common/getters.js';
|
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
import { NoteReactions } from '@/models/index.js';
|
import { NoteReactions } from '@/models/index.js';
|
||||||
import { DeepPartial } from 'typeorm';
|
import { DeepPartial } from 'typeorm';
|
||||||
|
@ -2,12 +2,12 @@ import Limiter from 'ratelimiter';
|
|||||||
import { redisClient } from '../../db/redis.js';
|
import { redisClient } from '../../db/redis.js';
|
||||||
import { IEndpoint } from './endpoints.js';
|
import { IEndpoint } from './endpoints.js';
|
||||||
import * as Acct from '@/misc/acct.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';
|
import Logger from '@/services/logger.js';
|
||||||
|
|
||||||
const logger = new Logger('limiter');
|
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 limitation = endpoint.meta.limit;
|
||||||
|
|
||||||
const key = Object.prototype.hasOwnProperty.call(limitation, 'key')
|
const key = Object.prototype.hasOwnProperty.call(limitation, 'key')
|
||||||
|
@ -18,6 +18,11 @@ import { Packed } from '@/misc/schema.js';
|
|||||||
|
|
||||||
//#region Stream type-body definitions
|
//#region Stream type-body definitions
|
||||||
export interface InternalStreamTypes {
|
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;
|
antennaCreated: Antenna;
|
||||||
antennaDeleted: Antenna;
|
antennaDeleted: Antenna;
|
||||||
antennaUpdated: 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 { Users, FollowRequests, Followings } from '@/models/index.js';
|
||||||
import { decrementFollowing } from './delete.js';
|
import { decrementFollowing } from './delete.js';
|
||||||
|
|
||||||
type Local = ILocalUser | { id: User['id']; host: User['host']; uri: User['host'] };
|
type Local = ILocalUser | {
|
||||||
type Remote = IRemoteUser;
|
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;
|
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 { UserGroup } from '@/models/entities/user-group.js';
|
||||||
import { DriveFile } from '@/models/entities/drive-file.js';
|
import { DriveFile } from '@/models/entities/drive-file.js';
|
||||||
import { MessagingMessages, UserGroupJoinings, Mutings, Users } from '@/models/index.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 { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
||||||
import { deliver } from '@/queue/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 = {
|
const message = {
|
||||||
id: genId(),
|
id: genId(),
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
|
@ -38,8 +38,6 @@ import { endedPollNotificationQueue } from '@/queue/queues.js';
|
|||||||
import { Cache } from '@/misc/cache.js';
|
import { Cache } from '@/misc/cache.js';
|
||||||
import { UserProfile } from '@/models/entities/user-profile.js';
|
import { UserProfile } from '@/models/entities/user-profile.js';
|
||||||
|
|
||||||
const usersCache = new Cache<MinimumUser>(Infinity);
|
|
||||||
|
|
||||||
const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
|
const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
|
||||||
|
|
||||||
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
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);
|
tags = tags.filter(tag => Array.from(tag || '').length <= 128).splice(0, 32);
|
||||||
|
|
||||||
if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply!.userId)) {
|
if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply!.userId)) {
|
||||||
mentionedUsers.push(await usersCache.fetch(data.reply.userId, () => Users.findOneOrFail(data.reply!.userId)));
|
mentionedUsers.push(await Users.findOneOrFail(data.reply!.userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.visibility === 'specified') {
|
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)) {
|
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 user 投稿者
|
||||||
* @param note 投稿
|
* @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();
|
const deletedAt = new Date();
|
||||||
|
|
||||||
// この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき
|
// この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき
|
||||||
@ -131,7 +131,7 @@ async function getMentionedRemoteUsers(note: Note) {
|
|||||||
}) as IRemoteUser[];
|
}) 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);
|
deliverToFollowers(user, content);
|
||||||
deliverToRelays(user, content);
|
deliverToRelays(user, content);
|
||||||
const remoteUsers = await getMentionedRemoteUsers(note);
|
const remoteUsers = await getMentionedRemoteUsers(note);
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { publishNoteStream } from '@/services/stream.js';
|
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 { Note } from '@/models/entities/note.js';
|
||||||
import { PollVotes, NoteWatchings, Polls, Blockings } from '@/models/index.js';
|
import { PollVotes, NoteWatchings, Polls, Blockings } from '@/models/index.js';
|
||||||
import { Not } from 'typeorm';
|
import { Not } from 'typeorm';
|
||||||
import { genId } from '@/misc/gen-id.js';
|
import { genId } from '@/misc/gen-id.js';
|
||||||
import { createNotification } from '../../create-notification.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);
|
const poll = await Polls.findOne(note.id);
|
||||||
|
|
||||||
if (poll == null) throw new Error('poll not found');
|
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 { ILocalUser, User } from '@/models/entities/user.js';
|
||||||
import { Users, Relays } from '@/models/index.js';
|
import { Users, Relays } from '@/models/index.js';
|
||||||
import { genId } from '@/misc/gen-id.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 ACTOR_USERNAME = 'relay.actor' as const;
|
||||||
|
|
||||||
|
const relaysCache = new Cache<Relay[]>(1000 * 60 * 10);
|
||||||
|
|
||||||
export async function getRelayActor(): Promise<ILocalUser> {
|
export async function getRelayActor(): Promise<ILocalUser> {
|
||||||
const user = await Users.findOne({
|
const user = await Users.findOne({
|
||||||
host: null,
|
host: null,
|
||||||
@ -78,9 +82,9 @@ export async function relayRejected(id: string) {
|
|||||||
export async function deliverToRelays(user: { id: User['id']; host: null; }, activity: any) {
|
export async function deliverToRelays(user: { id: User['id']; host: null; }, activity: any) {
|
||||||
if (activity == null) return;
|
if (activity == null) return;
|
||||||
|
|
||||||
const relays = await Relays.find({
|
const relays = await relaysCache.fetch(null, () => Relays.find({
|
||||||
status: 'accepted',
|
status: 'accepted',
|
||||||
});
|
}));
|
||||||
if (relays.length === 0) return;
|
if (relays.length === 0) return;
|
||||||
|
|
||||||
const copy = JSON.parse(JSON.stringify(activity));
|
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 { User } from '@/models/entities/user.js';
|
||||||
import { Users, Followings } from '@/models/index.js';
|
import { Users, Followings } from '@/models/index.js';
|
||||||
import { Not, IsNull } from 'typeorm';
|
import { Not, IsNull } from 'typeorm';
|
||||||
|
import { publishInternalEvent } from './stream';
|
||||||
|
|
||||||
export async function doPostSuspend(user: { id: User['id']; host: User['host'] }) {
|
export async function doPostSuspend(user: { id: User['id']; host: User['host'] }) {
|
||||||
|
publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true });
|
||||||
|
|
||||||
if (Users.isLocalUser(user)) {
|
if (Users.isLocalUser(user)) {
|
||||||
// 知り得る全SharedInboxにDelete配信
|
// 知り得る全SharedInboxにDelete配信
|
||||||
const content = renderActivity(renderDelete(`${config.url}/users/${user.id}`, user));
|
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 { User } from '@/models/entities/user.js';
|
||||||
import { Users, Followings } from '@/models/index.js';
|
import { Users, Followings } from '@/models/index.js';
|
||||||
import { Not, IsNull } from 'typeorm';
|
import { Not, IsNull } from 'typeorm';
|
||||||
|
import { publishInternalEvent } from './stream';
|
||||||
|
|
||||||
export async function doPostUnsuspend(user: User) {
|
export async function doPostUnsuspend(user: User) {
|
||||||
|
publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false });
|
||||||
|
|
||||||
if (Users.isLocalUser(user)) {
|
if (Users.isLocalUser(user)) {
|
||||||
// 知り得る全SharedInboxにUndo Delete配信
|
// 知り得る全SharedInboxにUndo Delete配信
|
||||||
const content = renderActivity(renderUndo(renderDelete(`${config.url}/users/${user.id}`, user), user));
|
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>
|
</FormSlot>
|
||||||
|
|
||||||
<FormSwitch v-model="profile.isCat" class="_formBlock">{{ i18n.ts.flagAsCat }}<template #caption>{{ i18n.ts.flagAsCatDescription }}</template></FormSwitch>
|
<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.isBot" class="_formBlock">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></FormSwitch>
|
||||||
|
|
||||||
<FormSwitch v-model="profile.alwaysMarkNsfw" class="_formBlock">{{ i18n.ts.alwaysMarkSensitive }}</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-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="silenced" class="_formBlock" @update:modelValue="toggleSilence">{{ $ts.silence }}</FormSwitch>
|
||||||
<FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.suspend }}</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>
|
<FormButton v-if="user.host == null && iAmModerator" class="_formBlock" @click="resetPassword"><i class="fas fa-key"></i> {{ $ts.resetPassword }}</FormButton>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user