Merge branch 'develop' into merge/2025-03-24
# Conflicts: # packages/backend/src/core/activitypub/models/ApPersonService.ts
This commit is contained in:
commit
ea2a3be70f
135 changed files with 2259 additions and 16763 deletions
|
|
@ -179,7 +179,7 @@ export class MfmService {
|
|||
break;
|
||||
}
|
||||
|
||||
// this is here only to catch upstream changes!
|
||||
// this is here only to catch upstream changes!
|
||||
case 'ruby--': {
|
||||
let ruby: [string, string][] = [];
|
||||
for (const child of node.childNodes) {
|
||||
|
|
@ -585,9 +585,10 @@ export class MfmService {
|
|||
}
|
||||
|
||||
// the toMastoApiHtml function was taken from Iceshrimp and written by zotan and modified by marie to work with the current MK version
|
||||
// additionally modified by hazelnoot to remove async
|
||||
|
||||
@bindThis
|
||||
public async toMastoApiHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = [], inline = false, quoteUri: string | null = null) {
|
||||
public toMastoApiHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = [], inline = false, quoteUri: string | null = null) {
|
||||
if (nodes == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -598,50 +599,50 @@ export class MfmService {
|
|||
|
||||
const body = doc.createElement('p');
|
||||
|
||||
async function appendChildren(children: mfm.MfmNode[], targetElement: any): Promise<void> {
|
||||
function appendChildren(children: mfm.MfmNode[], targetElement: any): void {
|
||||
if (children) {
|
||||
for (const child of await Promise.all(children.map(async (x) => await (handlers as any)[x.type](x)))) targetElement.appendChild(child);
|
||||
for (const child of children.map((x) => (handlers as any)[x.type](x))) targetElement.appendChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
const handlers: {
|
||||
[K in mfm.MfmNode['type']]: (node: mfm.NodeType<K>) => any;
|
||||
} = {
|
||||
async bold(node) {
|
||||
bold(node) {
|
||||
const el = doc.createElement('span');
|
||||
el.textContent = '**';
|
||||
await appendChildren(node.children, el);
|
||||
appendChildren(node.children, el);
|
||||
el.textContent += '**';
|
||||
return el;
|
||||
},
|
||||
|
||||
async small(node) {
|
||||
small(node) {
|
||||
const el = doc.createElement('small');
|
||||
await appendChildren(node.children, el);
|
||||
appendChildren(node.children, el);
|
||||
return el;
|
||||
},
|
||||
|
||||
async strike(node) {
|
||||
strike(node) {
|
||||
const el = doc.createElement('span');
|
||||
el.textContent = '~~';
|
||||
await appendChildren(node.children, el);
|
||||
appendChildren(node.children, el);
|
||||
el.textContent += '~~';
|
||||
return el;
|
||||
},
|
||||
|
||||
async italic(node) {
|
||||
italic(node) {
|
||||
const el = doc.createElement('span');
|
||||
el.textContent = '*';
|
||||
await appendChildren(node.children, el);
|
||||
appendChildren(node.children, el);
|
||||
el.textContent += '*';
|
||||
return el;
|
||||
},
|
||||
|
||||
async fn(node) {
|
||||
fn(node) {
|
||||
switch (node.props.name) {
|
||||
case 'group': { // hack for ruby
|
||||
const el = doc.createElement('span');
|
||||
await appendChildren(node.children, el);
|
||||
appendChildren(node.children, el);
|
||||
return el;
|
||||
}
|
||||
case 'ruby': {
|
||||
|
|
@ -667,7 +668,7 @@ export class MfmService {
|
|||
|
||||
if (!rt) {
|
||||
const el = doc.createElement('span');
|
||||
await appendChildren(node.children, el);
|
||||
appendChildren(node.children, el);
|
||||
return el;
|
||||
}
|
||||
|
||||
|
|
@ -680,7 +681,7 @@ export class MfmService {
|
|||
const rpEndEl = doc.createElement('rp');
|
||||
rpEndEl.appendChild(doc.createTextNode(')'));
|
||||
|
||||
await appendChildren(node.children.slice(0, node.children.length - 1), rubyEl);
|
||||
appendChildren(node.children.slice(0, node.children.length - 1), rubyEl);
|
||||
rtEl.appendChild(doc.createTextNode(text.trim()));
|
||||
rubyEl.appendChild(rpStartEl);
|
||||
rubyEl.appendChild(rtEl);
|
||||
|
|
@ -692,7 +693,7 @@ export class MfmService {
|
|||
default: {
|
||||
const el = doc.createElement('span');
|
||||
el.textContent = '*';
|
||||
await appendChildren(node.children, el);
|
||||
appendChildren(node.children, el);
|
||||
el.textContent += '*';
|
||||
return el;
|
||||
}
|
||||
|
|
@ -715,9 +716,9 @@ export class MfmService {
|
|||
return pre;
|
||||
},
|
||||
|
||||
async center(node) {
|
||||
center(node) {
|
||||
const el = doc.createElement('div');
|
||||
await appendChildren(node.children, el);
|
||||
appendChildren(node.children, el);
|
||||
return el;
|
||||
},
|
||||
|
||||
|
|
@ -756,16 +757,16 @@ export class MfmService {
|
|||
return el;
|
||||
},
|
||||
|
||||
async link(node) {
|
||||
link(node) {
|
||||
const a = doc.createElement('a');
|
||||
a.setAttribute('rel', 'nofollow noopener noreferrer');
|
||||
a.setAttribute('target', '_blank');
|
||||
a.setAttribute('href', node.props.url);
|
||||
await appendChildren(node.children, a);
|
||||
appendChildren(node.children, a);
|
||||
return a;
|
||||
},
|
||||
|
||||
async mention(node) {
|
||||
mention(node) {
|
||||
const { username, host, acct } = node.props;
|
||||
const resolved = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
|
||||
|
||||
|
|
@ -788,9 +789,9 @@ export class MfmService {
|
|||
return el;
|
||||
},
|
||||
|
||||
async quote(node) {
|
||||
quote(node) {
|
||||
const el = doc.createElement('blockquote');
|
||||
await appendChildren(node.children, el);
|
||||
appendChildren(node.children, el);
|
||||
return el;
|
||||
},
|
||||
|
||||
|
|
@ -823,14 +824,14 @@ export class MfmService {
|
|||
return a;
|
||||
},
|
||||
|
||||
async plain(node) {
|
||||
plain(node) {
|
||||
const el = doc.createElement('span');
|
||||
await appendChildren(node.children, el);
|
||||
appendChildren(node.children, el);
|
||||
return el;
|
||||
},
|
||||
};
|
||||
|
||||
await appendChildren(nodes, body);
|
||||
appendChildren(nodes, body);
|
||||
|
||||
if (quoteUri !== null) {
|
||||
const a = doc.createElement('a');
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ import { UtilityService } from '@/core/UtilityService.js';
|
|||
import type { MiNote } from '@/models/Note.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
|
||||
import { ApLoggerService } from '@/core/activitypub/ApLoggerService.js';
|
||||
import { getApId } from './type.js';
|
||||
import { ApPersonService } from './models/ApPersonService.js';
|
||||
import type { IObject } from './type.js';
|
||||
import { ApLoggerService } from '@/core/activitypub/ApLoggerService.js';
|
||||
|
||||
export type UriParseResult = {
|
||||
/** wether the URI was generated by us */
|
||||
|
|
@ -37,9 +37,6 @@ export type UriParseResult = {
|
|||
|
||||
@Injectable()
|
||||
export class ApDbResolverService implements OnApplicationShutdown {
|
||||
private publicKeyCache: MemoryKVCache<MiUserPublickey | null>;
|
||||
private publicKeyByUserIdCache: MemoryKVCache<MiUserPublickey | null>;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
|
@ -58,8 +55,7 @@ export class ApDbResolverService implements OnApplicationShutdown {
|
|||
private apLoggerService: ApLoggerService,
|
||||
private utilityService: UtilityService,
|
||||
) {
|
||||
this.publicKeyCache = new MemoryKVCache<MiUserPublickey | null>(1000 * 60 * 60 * 12); // 12h
|
||||
this.publicKeyByUserIdCache = new MemoryKVCache<MiUserPublickey | null>(1000 * 60 * 60 * 12); // 12h
|
||||
// Caches moved to ApPersonService to avoid circular dependency
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
@ -131,15 +127,7 @@ export class ApDbResolverService implements OnApplicationShutdown {
|
|||
user: MiRemoteUser;
|
||||
key: MiUserPublickey;
|
||||
} | null> {
|
||||
const key = await this.publicKeyCache.fetch(keyId, async () => {
|
||||
const key = await this.userPublickeysRepository.findOneBy({
|
||||
keyId,
|
||||
});
|
||||
|
||||
if (key == null) return null;
|
||||
|
||||
return key;
|
||||
}, key => key != null);
|
||||
const key = await this.apPersonService.findPublicKeyByKeyId(keyId);
|
||||
|
||||
if (key == null) return null;
|
||||
|
||||
|
|
@ -164,11 +152,7 @@ export class ApDbResolverService implements OnApplicationShutdown {
|
|||
const user = await this.apPersonService.resolvePerson(uri) as MiRemoteUser;
|
||||
if (user.isDeleted) return null;
|
||||
|
||||
const key = await this.publicKeyByUserIdCache.fetch(
|
||||
user.id,
|
||||
() => this.userPublickeysRepository.findOneBy({ userId: user.id }),
|
||||
v => v != null,
|
||||
);
|
||||
const key = await this.apPersonService.findPublicKeyByUserId(user.id);
|
||||
|
||||
return {
|
||||
user,
|
||||
|
|
@ -184,21 +168,19 @@ export class ApDbResolverService implements OnApplicationShutdown {
|
|||
this.apLoggerService.logger.debug('Re-fetching public key for user', { userId: user.id, uri: user.uri });
|
||||
await this.apPersonService.updatePerson(user.uri);
|
||||
|
||||
const key = await this.userPublickeysRepository.findOneBy({ userId: user.id });
|
||||
this.publicKeyByUserIdCache.set(user.id, key);
|
||||
const key = await this.apPersonService.findPublicKeyByUserId(user.id);
|
||||
|
||||
if (key) {
|
||||
this.apLoggerService.logger.info('Re-fetched public key for user', { userId: user.id, uri: user.uri });
|
||||
} else {
|
||||
this.apLoggerService.logger.warn('Failed to re-fetch key for user', { userId: user.id, uri: user.uri });
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public dispose(): void {
|
||||
this.publicKeyCache.dispose();
|
||||
this.publicKeyByUserIdCache.dispose();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import promiseLimit from 'promise-limit';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
|
|
@ -41,6 +41,7 @@ import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.j
|
|||
import type { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||
import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js';
|
||||
import { AppLockService } from '@/core/AppLockService.js';
|
||||
import { MemoryKVCache } from '@/misc/cache.js';
|
||||
import { getApId, getApType, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js';
|
||||
import { extractApHashtags } from './tag.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
|
|
@ -58,7 +59,11 @@ const summaryLength = 2048;
|
|||
type Field = Record<'name' | 'value', string>;
|
||||
|
||||
@Injectable()
|
||||
export class ApPersonService implements OnModuleInit {
|
||||
export class ApPersonService implements OnModuleInit, OnApplicationShutdown {
|
||||
// Moved from ApDbResolverService
|
||||
private readonly publicKeyByKeyIdCache = new MemoryKVCache<MiUserPublickey | null>(1000 * 60 * 60 * 12); // 12h
|
||||
private readonly publicKeyByUserIdCache = new MemoryKVCache<MiUserPublickey | null>(1000 * 60 * 60 * 12); // 12h
|
||||
|
||||
private utilityService: UtilityService;
|
||||
private userEntityService: UserEntityService;
|
||||
private driveFileEntityService: DriveFileEntityService;
|
||||
|
|
@ -134,6 +139,10 @@ export class ApPersonService implements OnModuleInit {
|
|||
this.logger = this.apLoggerService.logger;
|
||||
}
|
||||
|
||||
onApplicationShutdown(): void {
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and convert to actor object
|
||||
* @param x Fetched object
|
||||
|
|
@ -362,6 +371,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
|
||||
// Create user
|
||||
let user: MiRemoteUser | null = null;
|
||||
let publicKey: MiUserPublickey | null = null;
|
||||
|
||||
//#region カスタム絵文字取得
|
||||
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host)
|
||||
|
|
@ -442,7 +452,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
}));
|
||||
|
||||
if (person.publicKey) {
|
||||
await transactionalEntityManager.save(new MiUserPublickey({
|
||||
publicKey = await transactionalEntityManager.save(new MiUserPublickey({
|
||||
userId: user.id,
|
||||
keyId: person.publicKey.id,
|
||||
keyPem: person.publicKey.publicKeyPem.trim(),
|
||||
|
|
@ -457,6 +467,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
if (u == null) throw new UnrecoverableError(`already registered a user with conflicting data: ${uri}`);
|
||||
|
||||
user = u as MiRemoteUser;
|
||||
publicKey = await this.userPublickeysRepository.findOneBy({ userId: user.id });
|
||||
} else {
|
||||
this.logger.error(e instanceof Error ? e : new Error(e as string));
|
||||
throw e;
|
||||
|
|
@ -468,6 +479,11 @@ export class ApPersonService implements OnModuleInit {
|
|||
// Register to the cache
|
||||
this.cacheService.uriPersonCache.set(user.uri, user);
|
||||
|
||||
// Register public key to the cache.
|
||||
// Value may be null, which indicates that the user has no defined key. (optimization)
|
||||
this.publicKeyByUserIdCache.set(user.id, publicKey);
|
||||
if (publicKey) this.publicKeyByKeyIdCache.set(publicKey.keyId, publicKey);
|
||||
|
||||
// Register host
|
||||
if (this.meta.enableStatsForFederatedInstances) {
|
||||
this.federatedInstanceService.fetchOrRegister(host).then(i => {
|
||||
|
|
@ -620,10 +636,27 @@ export class ApPersonService implements OnModuleInit {
|
|||
}
|
||||
|
||||
if (person.publicKey) {
|
||||
await this.userPublickeysRepository.update({ userId: exist.id }, {
|
||||
const publicKey = new MiUserPublickey({
|
||||
userId: exist.id,
|
||||
keyId: person.publicKey.id,
|
||||
keyPem: person.publicKey.publicKeyPem,
|
||||
});
|
||||
|
||||
// Create or update key
|
||||
await this.userPublickeysRepository.save(publicKey);
|
||||
|
||||
this.publicKeyByKeyIdCache.set(person.publicKey.id, publicKey);
|
||||
this.publicKeyByUserIdCache.set(exist.id, publicKey);
|
||||
} else {
|
||||
const existingPublicKey = await this.userPublickeysRepository.findOneBy({ userId: exist.id });
|
||||
if (existingPublicKey) {
|
||||
// Delete key
|
||||
await this.userPublickeysRepository.delete({ userId: exist.id });
|
||||
this.publicKeyByKeyIdCache.delete(existingPublicKey.keyId);
|
||||
}
|
||||
|
||||
// Null indicates that the user has no key. (optimization)
|
||||
this.publicKeyByUserIdCache.set(exist.id, null);
|
||||
}
|
||||
|
||||
let _description: string | null = null;
|
||||
|
|
@ -854,4 +887,38 @@ export class ApPersonService implements OnModuleInit {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async findPublicKeyByUserId(userId: string): Promise<MiUserPublickey | null> {
|
||||
const publicKey = this.publicKeyByUserIdCache.get(userId) ?? await this.userPublickeysRepository.findOneBy({ userId });
|
||||
|
||||
// This can technically keep a key cached "forever" if it's used enough, but that's ok.
|
||||
// We can never have stale data because the publicKey caches are coherent. (cache updates whenever data changes)
|
||||
if (publicKey) {
|
||||
this.publicKeyByUserIdCache.set(publicKey.userId, publicKey);
|
||||
this.publicKeyByKeyIdCache.set(publicKey.keyId, publicKey);
|
||||
}
|
||||
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async findPublicKeyByKeyId(keyId: string): Promise<MiUserPublickey | null> {
|
||||
const publicKey = this.publicKeyByKeyIdCache.get(keyId) ?? await this.userPublickeysRepository.findOneBy({ keyId });
|
||||
|
||||
// This can technically keep a key cached "forever" if it's used enough, but that's ok.
|
||||
// We can never have stale data because the publicKey caches are coherent. (cache updates whenever data changes)
|
||||
if (publicKey) {
|
||||
this.publicKeyByUserIdCache.set(publicKey.userId, publicKey);
|
||||
this.publicKeyByKeyIdCache.set(publicKey.keyId, publicKey);
|
||||
}
|
||||
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public dispose(): void {
|
||||
this.publicKeyByUserIdCache.dispose();
|
||||
this.publicKeyByKeyIdCache.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue