getUserUriをUserEntityServiceに

This commit is contained in:
tamaina 2023-04-22 13:37:09 +00:00
parent 70d34aa05c
commit 5115ff1466
16 changed files with 107 additions and 84 deletions

View file

@ -4,7 +4,7 @@ import { IsNull, In, MoreThan, Not } from 'typeorm';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { LocalUser } from '@/models/entities/User.js'; import type { LocalUser, RemoteUser } from '@/models/entities/User.js';
import type { BlockingsRepository, FollowingsRepository, InstancesRepository, Muting, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/index.js'; import type { BlockingsRepository, FollowingsRepository, InstancesRepository, Muting, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/index.js';
import type { RelationshipJobData, ThinUser } from '@/queue/types.js'; import type { RelationshipJobData, ThinUser } from '@/queue/types.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
@ -69,8 +69,8 @@ export class AccountMoveService {
* After delivering Move activity, its local followers unfollow the old account and then follow the new one. * After delivering Move activity, its local followers unfollow the old account and then follow the new one.
*/ */
@bindThis @bindThis
public async moveFromLocal(src: LocalUser, dst: User): Promise<unknown> { public async moveFromLocal(src: LocalUser, dst: LocalUser | RemoteUser): Promise<unknown> {
const dstUri = this.getUserUri(dst); const dstUri = this.userEntityService.getUserUri(dst);
// add movedToUri to indicate that the user has moved // add movedToUri to indicate that the user has moved
const update = {} as Partial<User>; const update = {} as Partial<User>;
@ -253,12 +253,6 @@ export class AccountMoveService {
} }
} }
@bindThis
public getUserUri(user: User): string {
return this.userEntityService.isRemoteUser(user)
? user.uri : `${this.config.url}/users/${user.id}`;
}
@bindThis @bindThis
private async adjustFollowingCounts(localFollowerIds: string[], oldAccount: User): Promise<void> { private async adjustFollowingCounts(localFollowerIds: string[], oldAccount: User): Promise<void> {
if (localFollowerIds.length === 0) return; if (localFollowerIds.length === 0) return;

View file

@ -4,7 +4,7 @@ import chalk from 'chalk';
import { IsNull } from 'typeorm'; import { IsNull } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { UsersRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js';
import type { RemoteUser, User } from '@/models/entities/User.js'; import type { LocalUser, RemoteUser } from '@/models/entities/User.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
@ -33,7 +33,7 @@ export class RemoteUserResolveService {
} }
@bindThis @bindThis
public async resolveUser(username: string, host: string | null): Promise<User> { public async resolveUser(username: string, host: string | null): Promise<LocalUser | RemoteUser> {
const usernameLower = username.toLowerCase(); const usernameLower = username.toLowerCase();
if (host == null) { if (host == null) {
@ -44,7 +44,7 @@ export class RemoteUserResolveService {
} else { } else {
return u; return u;
} }
}); }) as LocalUser;
} }
host = this.utilityService.toPuny(host); host = this.utilityService.toPuny(host);
@ -57,7 +57,7 @@ export class RemoteUserResolveService {
} else { } else {
return u; return u;
} }
}); }) as LocalUser;
} }
const user = await this.usersRepository.findOneBy({ usernameLower, host }) as RemoteUser | null; const user = await this.usersRepository.findOneBy({ usernameLower, host }) as RemoteUser | null;
@ -109,7 +109,7 @@ export class RemoteUserResolveService {
if (u == null) { if (u == null) {
throw new Error('user not found'); throw new Error('user not found');
} else { } else {
return u; return u as LocalUser | RemoteUser;
} }
}); });
} }

View file

@ -1,6 +1,6 @@
import { Inject, Injectable, OnModuleInit, forwardRef } from '@nestjs/common'; import { Inject, Injectable, OnModuleInit, forwardRef } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core'; import { ModuleRef } from '@nestjs/core';
import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js'; import type { LocalUser, PartialLocalUser, PartialRemoteUser, RemoteUser, User } from '@/models/entities/User.js';
import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js';
import { QueueService } from '@/core/QueueService.js'; import { QueueService } from '@/core/QueueService.js';
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js'; import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
@ -90,7 +90,7 @@ export class UserFollowingService implements OnModuleInit {
const [follower, followee] = await Promise.all([ const [follower, followee] = await Promise.all([
this.usersRepository.findOneByOrFail({ id: _follower.id }), this.usersRepository.findOneByOrFail({ id: _follower.id }),
this.usersRepository.findOneByOrFail({ id: _followee.id }), this.usersRepository.findOneByOrFail({ id: _followee.id }),
]); ]) as [LocalUser | RemoteUser, LocalUser | RemoteUser];
// check blocking // check blocking
const [blocking, blocked] = await Promise.all([ const [blocking, blocked] = await Promise.all([
@ -152,7 +152,7 @@ export class UserFollowingService implements OnModuleInit {
} }
if (movedFollower.alsoKnownAs) { if (movedFollower.alsoKnownAs) {
const newUri = this.userEntityService.isRemoteUser(movedFollower) ? movedFollower.uri : `${this.config.url}/users/${movedFollower.id}`; const newUri = this.userEntityService.getUserUri(movedFollower);
for (const oldUri of movedFollower.alsoKnownAs) { for (const oldUri of movedFollower.alsoKnownAs) {
try { try {
@ -374,13 +374,13 @@ export class UserFollowingService implements OnModuleInit {
} }
if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower, followee), follower)); const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower as PartialLocalUser, followee as PartialRemoteUser), follower));
this.queueService.deliver(follower, content, followee.inbox, false); this.queueService.deliver(follower, content, followee.inbox, false);
} }
if (this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower)) { if (this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower)) {
// local user has null host // local user has null host
const content = this.apRendererService.addContext(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower, followee), followee)); const content = this.apRendererService.addContext(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower as PartialRemoteUser, followee as PartialLocalUser), followee));
this.queueService.deliver(followee, content, follower.inbox, false); this.queueService.deliver(followee, content, follower.inbox, false);
} }
} }
@ -510,7 +510,7 @@ export class UserFollowingService implements OnModuleInit {
} }
if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
const content = this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee, requestId ?? `${this.config.url}/follows/${followRequest.id}`)); const content = this.apRendererService.addContext(this.apRendererService.renderFollow(follower as PartialLocalUser, followee as PartialRemoteUser, requestId ?? `${this.config.url}/follows/${followRequest.id}`));
this.queueService.deliver(follower, content, followee.inbox, false); this.queueService.deliver(follower, content, followee.inbox, false);
} }
} }
@ -525,7 +525,7 @@ export class UserFollowingService implements OnModuleInit {
}, },
): Promise<void> { ): Promise<void> {
if (this.userEntityService.isRemoteUser(followee)) { if (this.userEntityService.isRemoteUser(followee)) {
const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower, followee), follower)); const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower as PartialLocalUser | PartialRemoteUser, followee as PartialRemoteUser), follower));
if (this.userEntityService.isLocalUser(follower)) { // 本来このチェックは不要だけどTSに怒られるので if (this.userEntityService.isLocalUser(follower)) { // 本来このチェックは不要だけどTSに怒られるので
this.queueService.deliver(follower, content, followee.inbox, false); this.queueService.deliver(follower, content, followee.inbox, false);
@ -570,7 +570,7 @@ export class UserFollowingService implements OnModuleInit {
await this.insertFollowingDoc(followee, follower); await this.insertFollowingDoc(followee, follower);
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, request.requestId!), followee)); const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee as PartialLocalUser, request.requestId!), followee));
this.queueService.deliver(followee, content, follower.inbox, false); this.queueService.deliver(followee, content, follower.inbox, false);
} }

View file

@ -35,7 +35,7 @@ export class UserSuspendService {
if (this.userEntityService.isLocalUser(user)) { if (this.userEntityService.isLocalUser(user)) {
// 知り得る全SharedInboxにDelete配信 // 知り得る全SharedInboxにDelete配信
const content = this.apRendererService.addContext(this.apRendererService.renderDelete(`${this.config.url}/users/${user.id}`, user)); const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user));
const queue: string[] = []; const queue: string[] = [];
@ -65,7 +65,7 @@ export class UserSuspendService {
if (this.userEntityService.isLocalUser(user)) { if (this.userEntityService.isLocalUser(user)) {
// 知り得る全SharedInboxにUndo Delete配信 // 知り得る全SharedInboxにUndo Delete配信
const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderDelete(`${this.config.url}/users/${user.id}`, user), user)); const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user), user));
const queue: string[] = []; const queue: string[] = [];

View file

@ -8,7 +8,7 @@ import type { UserPublickey } from '@/models/entities/UserPublickey.js';
import { CacheService } from '@/core/CacheService.js'; import { CacheService } from '@/core/CacheService.js';
import type { Note } from '@/models/entities/Note.js'; import type { Note } from '@/models/entities/Note.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { RemoteUser, User } from '@/models/entities/User.js'; import { LocalUser, RemoteUser } from '@/models/entities/User.js';
import { getApId } from './type.js'; import { getApId } from './type.js';
import { ApPersonService } from './models/ApPersonService.js'; import { ApPersonService } from './models/ApPersonService.js';
import type { IObject } from './type.js'; import type { IObject } from './type.js';
@ -101,7 +101,7 @@ export class ApDbResolverService {
* AP Person => Misskey User in DB * AP Person => Misskey User in DB
*/ */
@bindThis @bindThis
public async getUserFromApId(value: string | IObject): Promise<User | null> { public async getUserFromApId(value: string | IObject): Promise<LocalUser | RemoteUser | null> {
const parsed = this.parseUri(value); const parsed = this.parseUri(value);
if (parsed.local) { if (parsed.local) {
@ -109,11 +109,11 @@ export class ApDbResolverService {
return await this.cacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({ return await this.cacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({
id: parsed.id, id: parsed.id,
}).then(x => x ?? undefined)) ?? null; }).then(x => x ?? undefined)) as LocalUser | undefined ?? null;
} else { } else {
return await this.cacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({ return await this.cacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({
uri: parsed.uri, uri: parsed.uri,
})); })) as RemoteUser | null;
} }
} }

View file

@ -757,7 +757,7 @@ export class ApInboxService {
if (!newAccount.alsoKnownAs?.includes(oldAccount.uri)) { if (!newAccount.alsoKnownAs?.includes(oldAccount.uri)) {
isValidMove = false; isValidMove = false;
} }
} else if (!newAccount.alsoKnownAs?.includes(this.accountMoveService.getUserUri(oldAccount))) { } else if (!newAccount.alsoKnownAs?.includes(this.userEntityService.getUserUri(oldAccount))) {
isValidMove = false; isValidMove = false;
} }
if (newAccount.movedToUri) { if (newAccount.movedToUri) {

View file

@ -5,7 +5,7 @@ import { v4 as uuid } from 'uuid';
import * as mfm from 'mfm-js'; import * as mfm from 'mfm-js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js'; import type { PartialLocalUser, LocalUser, PartialRemoteUser, RemoteUser, User } from '@/models/entities/User.js';
import type { IMentionedRemoteUsers, Note } from '@/models/entities/Note.js'; import type { IMentionedRemoteUsers, Note } from '@/models/entities/Note.js';
import type { Blocking } from '@/models/entities/Blocking.js'; import type { Blocking } from '@/models/entities/Blocking.js';
import type { Relay } from '@/models/entities/Relay.js'; import type { Relay } from '@/models/entities/Relay.js';
@ -66,7 +66,7 @@ export class ApRendererService {
public renderAccept(object: any, user: { id: User['id']; host: null }): IAccept { public renderAccept(object: any, user: { id: User['id']; host: null }): IAccept {
return { return {
type: 'Accept', type: 'Accept',
actor: `${this.config.url}/users/${user.id}`, actor: this.userEntityService.genLocalUserUri(user.id),
object, object,
}; };
} }
@ -75,7 +75,7 @@ export class ApRendererService {
public renderAdd(user: LocalUser, target: any, object: any): IAdd { public renderAdd(user: LocalUser, target: any, object: any): IAdd {
return { return {
type: 'Add', type: 'Add',
actor: `${this.config.url}/users/${user.id}`, actor: this.userEntityService.genLocalUserUri(user.id),
target, target,
object, object,
}; };
@ -83,7 +83,7 @@ export class ApRendererService {
@bindThis @bindThis
public renderAnnounce(object: any, note: Note): IAnnounce { public renderAnnounce(object: any, note: Note): IAnnounce {
const attributedTo = `${this.config.url}/users/${note.userId}`; const attributedTo = this.userEntityService.genLocalUserUri(note.userId);
let to: string[] = []; let to: string[] = [];
let cc: string[] = []; let cc: string[] = [];
@ -103,7 +103,7 @@ export class ApRendererService {
return { return {
id: `${this.config.url}/notes/${note.id}/activity`, id: `${this.config.url}/notes/${note.id}/activity`,
actor: `${this.config.url}/users/${note.userId}`, actor: this.userEntityService.genLocalUserUri(note.userId),
type: 'Announce', type: 'Announce',
published: note.createdAt.toISOString(), published: note.createdAt.toISOString(),
to, to,
@ -126,7 +126,7 @@ export class ApRendererService {
return { return {
type: 'Block', type: 'Block',
id: `${this.config.url}/blocks/${block.id}`, id: `${this.config.url}/blocks/${block.id}`,
actor: `${this.config.url}/users/${block.blockerId}`, actor: this.userEntityService.genLocalUserUri(block.blockerId),
object: block.blockee.uri, object: block.blockee.uri,
}; };
} }
@ -135,7 +135,7 @@ export class ApRendererService {
public renderCreate(object: IObject, note: Note): ICreate { public renderCreate(object: IObject, note: Note): ICreate {
const activity = { const activity = {
id: `${this.config.url}/notes/${note.id}/activity`, id: `${this.config.url}/notes/${note.id}/activity`,
actor: `${this.config.url}/users/${note.userId}`, actor: this.userEntityService.genLocalUserUri(note.userId),
type: 'Create', type: 'Create',
published: note.createdAt.toISOString(), published: note.createdAt.toISOString(),
object, object,
@ -151,7 +151,7 @@ export class ApRendererService {
public renderDelete(object: IObject | string, user: { id: User['id']; host: null }): IDelete { public renderDelete(object: IObject | string, user: { id: User['id']; host: null }): IDelete {
return { return {
type: 'Delete', type: 'Delete',
actor: `${this.config.url}/users/${user.id}`, actor: this.userEntityService.genLocalUserUri(user.id),
object, object,
published: new Date().toISOString(), published: new Date().toISOString(),
}; };
@ -188,7 +188,7 @@ export class ApRendererService {
public renderFlag(user: LocalUser, object: IObject | string, content: string): IFlag { public renderFlag(user: LocalUser, object: IObject | string, content: string): IFlag {
return { return {
type: 'Flag', type: 'Flag',
actor: `${this.config.url}/users/${user.id}`, actor: this.userEntityService.genLocalUserUri(user.id),
content, content,
object, object,
}; };
@ -199,7 +199,7 @@ export class ApRendererService {
return { return {
id: `${this.config.url}/activities/follow-relay/${relay.id}`, id: `${this.config.url}/activities/follow-relay/${relay.id}`,
type: 'Follow', type: 'Follow',
actor: `${this.config.url}/users/${relayActor.id}`, actor: this.userEntityService.genLocalUserUri(relayActor.id),
object: 'https://www.w3.org/ns/activitystreams#Public', object: 'https://www.w3.org/ns/activitystreams#Public',
}; };
} }
@ -210,21 +210,21 @@ export class ApRendererService {
*/ */
@bindThis @bindThis
public async renderFollowUser(id: User['id']) { public async renderFollowUser(id: User['id']) {
const user = await this.usersRepository.findOneByOrFail({ id: id }); const user = await this.usersRepository.findOneByOrFail({ id: id }) as PartialLocalUser | PartialRemoteUser;
return this.userEntityService.isLocalUser(user) ? `${this.config.url}/users/${user.id}` : user.uri; return this.userEntityService.getUserUri(user);
} }
@bindThis @bindThis
public renderFollow( public renderFollow(
follower: { id: User['id']; host: User['host']; uri: User['host'] }, follower: PartialLocalUser | PartialRemoteUser,
followee: { id: User['id']; host: User['host']; uri: User['host'] }, followee: PartialLocalUser | PartialRemoteUser,
requestId?: string, requestId?: string,
): IFollow { ): IFollow {
return { return {
id: requestId ?? `${this.config.url}/follows/${follower.id}/${followee.id}`, id: requestId ?? `${this.config.url}/follows/${follower.id}/${followee.id}`,
type: 'Follow', type: 'Follow',
actor: this.userEntityService.isLocalUser(follower) ? `${this.config.url}/users/${follower.id}` : follower.uri!, actor: this.userEntityService.getUserUri(follower)!,
object: this.userEntityService.isLocalUser(followee) ? `${this.config.url}/users/${followee.id}` : followee.uri!, object: this.userEntityService.getUserUri(followee)!,
}; };
} }
@ -252,7 +252,7 @@ export class ApRendererService {
return { return {
id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`, id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`,
type: 'Key', type: 'Key',
owner: `${this.config.url}/users/${user.id}`, owner: this.userEntityService.genLocalUserUri(user.id),
publicKeyPem: createPublicKey(key.publicKey).export({ publicKeyPem: createPublicKey(key.publicKey).export({
type: 'spki', type: 'spki',
format: 'pem', format: 'pem',
@ -284,21 +284,21 @@ export class ApRendererService {
} }
@bindThis @bindThis
public renderMention(mention: User): IApMention { public renderMention(mention: PartialLocalUser | PartialRemoteUser): IApMention {
return { return {
type: 'Mention', type: 'Mention',
href: this.userEntityService.isRemoteUser(mention) ? mention.uri! : `${this.config.url}/users/${(mention as LocalUser).id}`, href: this.userEntityService.getUserUri(mention)!,
name: this.userEntityService.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as LocalUser).username}`, name: this.userEntityService.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as LocalUser).username}`,
}; };
} }
@bindThis @bindThis
public renderMove( public renderMove(
src: { id: User['id']; host: User['host']; uri: User['host'] }, src: PartialLocalUser | PartialRemoteUser,
dst: { id: User['id']; host: User['host']; uri: User['host'] }, dst: PartialLocalUser | PartialRemoteUser,
): IMove { ): IMove {
const actor = this.userEntityService.isLocalUser(src) ? `${this.config.url}/users/${src.id}` : src.uri!; const actor = this.userEntityService.getUserUri(src)!;
const target = this.userEntityService.isLocalUser(dst) ? `${this.config.url}/users/${dst.id}` : dst.uri!; const target = this.userEntityService.getUserUri(dst)!;
return { return {
id: `${this.config.url}/moves/${src.id}/${dst.id}`, id: `${this.config.url}/moves/${src.id}/${dst.id}`,
actor, actor,
@ -351,7 +351,7 @@ export class ApRendererService {
} }
} }
const attributedTo = `${this.config.url}/users/${note.userId}`; const attributedTo = this.userEntityService.genLocalUserUri(note.userId);
const mentions = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri); const mentions = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri);
@ -376,7 +376,7 @@ export class ApRendererService {
}) : []; }) : [];
const hashtagTags = (note.tags ?? []).map(tag => this.renderHashtag(tag)); const hashtagTags = (note.tags ?? []).map(tag => this.renderHashtag(tag));
const mentionTags = mentionedUsers.map(u => this.renderMention(u)); const mentionTags = mentionedUsers.map(u => this.renderMention(u as LocalUser | RemoteUser));
const files = await getPromisedFiles(note.fileIds); const files = await getPromisedFiles(note.fileIds);
@ -450,7 +450,7 @@ export class ApRendererService {
@bindThis @bindThis
public async renderPerson(user: LocalUser) { public async renderPerson(user: LocalUser) {
const id = `${this.config.url}/users/${user.id}`; const id = this.userEntityService.genLocalUserUri(user.id);
const isSystem = !!user.username.match(/\./); const isSystem = !!user.username.match(/\./);
const [avatar, banner, profile] = await Promise.all([ const [avatar, banner, profile] = await Promise.all([
@ -538,7 +538,7 @@ export class ApRendererService {
return { return {
type: 'Question', type: 'Question',
id: `${this.config.url}/questions/${note.id}`, id: `${this.config.url}/questions/${note.id}`,
actor: `${this.config.url}/users/${user.id}`, actor: this.userEntityService.genLocalUserUri(user.id),
content: note.text ?? '', content: note.text ?? '',
[poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({
name: text, name: text,
@ -555,7 +555,7 @@ export class ApRendererService {
public renderReject(object: any, user: { id: User['id'] }): IReject { public renderReject(object: any, user: { id: User['id'] }): IReject {
return { return {
type: 'Reject', type: 'Reject',
actor: `${this.config.url}/users/${user.id}`, actor: this.userEntityService.genLocalUserUri(user.id),
object, object,
}; };
} }
@ -564,7 +564,7 @@ export class ApRendererService {
public renderRemove(user: { id: User['id'] }, target: any, object: any): IRemove { public renderRemove(user: { id: User['id'] }, target: any, object: any): IRemove {
return { return {
type: 'Remove', type: 'Remove',
actor: `${this.config.url}/users/${user.id}`, actor: this.userEntityService.genLocalUserUri(user.id),
target, target,
object, object,
}; };
@ -585,7 +585,7 @@ export class ApRendererService {
return { return {
type: 'Undo', type: 'Undo',
...(id ? { id } : {}), ...(id ? { id } : {}),
actor: `${this.config.url}/users/${user.id}`, actor: this.userEntityService.genLocalUserUri(user.id),
object, object,
published: new Date().toISOString(), published: new Date().toISOString(),
}; };
@ -595,7 +595,7 @@ export class ApRendererService {
public renderUpdate(object: any, user: { id: User['id'] }): IUpdate { public renderUpdate(object: any, user: { id: User['id'] }): IUpdate {
return { return {
id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`, id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`,
actor: `${this.config.url}/users/${user.id}`, actor: this.userEntityService.genLocalUserUri(user.id),
type: 'Update', type: 'Update',
to: ['https://www.w3.org/ns/activitystreams#Public'], to: ['https://www.w3.org/ns/activitystreams#Public'],
object, object,
@ -607,14 +607,14 @@ export class ApRendererService {
public renderVote(user: { id: User['id'] }, vote: PollVote, note: Note, poll: Poll, pollOwner: RemoteUser): ICreate { public renderVote(user: { id: User['id'] }, vote: PollVote, note: Note, poll: Poll, pollOwner: RemoteUser): ICreate {
return { return {
id: `${this.config.url}/users/${user.id}#votes/${vote.id}/activity`, id: `${this.config.url}/users/${user.id}#votes/${vote.id}/activity`,
actor: `${this.config.url}/users/${user.id}`, actor: this.userEntityService.genLocalUserUri(user.id),
type: 'Create', type: 'Create',
to: [pollOwner.uri], to: [pollOwner.uri],
published: new Date().toISOString(), published: new Date().toISOString(),
object: { object: {
id: `${this.config.url}/users/${user.id}#votes/${vote.id}`, id: `${this.config.url}/users/${user.id}#votes/${vote.id}`,
type: 'Note', type: 'Note',
attributedTo: `${this.config.url}/users/${user.id}`, attributedTo: this.userEntityService.genLocalUserUri(user.id),
to: [pollOwner.uri], to: [pollOwner.uri],
inReplyTo: note.uri, inReplyTo: note.uri,
name: poll.choices[vote.choice], name: poll.choices[vote.choice],

View file

@ -1,5 +1,5 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import type { LocalUser } from '@/models/entities/User.js'; import type { LocalUser, RemoteUser } from '@/models/entities/User.js';
import { InstanceActorService } from '@/core/InstanceActorService.js'; import { InstanceActorService } from '@/core/InstanceActorService.js';
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js'; import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
@ -151,7 +151,7 @@ export class Resolver {
return Promise.all( return Promise.all(
[parsed.id, parsed.rest].map(id => this.usersRepository.findOneByOrFail({ id })), [parsed.id, parsed.rest].map(id => this.usersRepository.findOneByOrFail({ id })),
) )
.then(([follower, followee]) => this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee, url))); .then(([follower, followee]) => this.apRendererService.addContext(this.apRendererService.renderFollow(follower as LocalUser | RemoteUser, followee as LocalUser | RemoteUser, url)));
default: default:
throw new Error(`resolveLocal: type ${parsed.type} unhandled`); throw new Error(`resolveLocal: type ${parsed.type} unhandled`);
} }

View file

@ -5,7 +5,7 @@ import { ModuleRef } from '@nestjs/core';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { BlockingsRepository, MutingsRepository, FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; import type { BlockingsRepository, MutingsRepository, FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { RemoteUser } from '@/models/entities/User.js'; import type { LocalUser, RemoteUser } from '@/models/entities/User.js';
import { User } from '@/models/entities/User.js'; import { User } from '@/models/entities/User.js';
import { truncate } from '@/misc/truncate.js'; import { truncate } from '@/misc/truncate.js';
import type { CacheService } from '@/core/CacheService.js'; import type { CacheService } from '@/core/CacheService.js';
@ -210,22 +210,22 @@ export class ApPersonService implements OnModuleInit {
* Misskeyに対象のPersonが登録されていればそれを返しnullを返します * Misskeyに対象のPersonが登録されていればそれを返しnullを返します
*/ */
@bindThis @bindThis
public async fetchPerson(uri: string): Promise<User | null> { public async fetchPerson(uri: string): Promise<LocalUser | RemoteUser | 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 = this.cacheService.uriPersonCache.get(uri); const cached = this.cacheService.uriPersonCache.get(uri) as LocalUser | RemoteUser | null;
if (cached) return cached; if (cached) return cached;
// URIがこのサーバーを指しているならデータベースからフェッチ // URIがこのサーバーを指しているならデータベースからフェッチ
if (uri.startsWith(`${this.config.url}/`)) { if (uri.startsWith(`${this.config.url}/`)) {
const id = uri.split('/').pop(); const id = uri.split('/').pop();
const u = await this.usersRepository.findOneBy({ id }); const u = await this.usersRepository.findOneBy({ id }) as LocalUser;
if (u) this.cacheService.uriPersonCache.set(uri, u); if (u) this.cacheService.uriPersonCache.set(uri, u);
return u; return u;
} }
//#region このサーバーに既に登録されていたらそれを返す //#region このサーバーに既に登録されていたらそれを返す
const exist = await this.usersRepository.findOneBy({ uri }); const exist = await this.usersRepository.findOneBy({ uri }) as LocalUser | RemoteUser;
if (exist) { if (exist) {
this.cacheService.uriPersonCache.set(uri, exist); this.cacheService.uriPersonCache.set(uri, exist);
@ -240,7 +240,7 @@ export class ApPersonService implements OnModuleInit {
* Personを作成します * Personを作成します
*/ */
@bindThis @bindThis
public async createPerson(uri: string, resolver?: Resolver): Promise<User> { public async createPerson(uri: string, resolver?: Resolver): Promise<RemoteUser> {
if (typeof uri !== 'string') throw new Error('uri is not string'); if (typeof uri !== 'string') throw new Error('uri is not string');
if (uri.startsWith(this.config.url)) { if (uri.startsWith(this.config.url)) {
@ -553,7 +553,7 @@ export class ApPersonService implements OnModuleInit {
* Misskeyに登録しそれを返します * Misskeyに登録しそれを返します
*/ */
@bindThis @bindThis
public async resolvePerson(uri: string, resolver?: Resolver): Promise<User> { public async resolvePerson(uri: string, resolver?: Resolver): Promise<LocalUser | RemoteUser> {
if (typeof uri !== 'string') throw new Error('uri is not string'); if (typeof uri !== 'string') throw new Error('uri is not string');
//#region このサーバーに既に登録されていたらそれを返す //#region このサーバーに既に登録されていたらそれを返す

View file

@ -9,8 +9,7 @@ import type { Packed } from '@/misc/json-schema.js';
import type { Promiseable } from '@/misc/prelude/await-all.js'; import type { Promiseable } from '@/misc/prelude/await-all.js';
import { awaitAll } from '@/misc/prelude/await-all.js'; import { awaitAll } from '@/misc/prelude/await-all.js';
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js'; import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js';
import type { Instance } from '@/models/entities/Instance.js'; import type { LocalUser, PartialLocalUser, PartialRemoteUser, RemoteUser, User } from '@/models/entities/User.js';
import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js'; import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js';
import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, AnnouncementsRepository, PagesRepository, UserProfile, RenoteMutingsRepository, UserMemoRepository } from '@/models/index.js'; import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, AnnouncementsRepository, PagesRepository, UserProfile, RenoteMutingsRepository, UserMemoRepository } from '@/models/index.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -35,13 +34,13 @@ type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends bo
const ajv = new Ajv(); const ajv = new Ajv();
function isLocalUser(user: User): user is LocalUser; function isLocalUser(user: User): user is LocalUser;
function isLocalUser<T extends { host: User['host'] }>(user: T): user is T & { host: null; }; function isLocalUser<T extends { host: User['host'] }>(user: T): user is (T & { host: null; });
function isLocalUser(user: User | { host: User['host'] }): boolean { function isLocalUser(user: User | { host: User['host'] }): boolean {
return user.host == null; return user.host == null;
} }
function isRemoteUser(user: User): user is RemoteUser; function isRemoteUser(user: User): user is RemoteUser;
function isRemoteUser<T extends { host: User['host'] }>(user: T): user is T & { host: string; }; function isRemoteUser<T extends { host: User['host'] }>(user: T): user is (T & { host: string; });
function isRemoteUser(user: User | { host: User['host'] }): boolean { function isRemoteUser(user: User | { host: User['host'] }): boolean {
return !isLocalUser(user); return !isLocalUser(user);
} }
@ -280,6 +279,17 @@ export class UserEntityService implements OnModuleInit {
return `${this.config.url}/identicon/${user.username.toLowerCase()}@${user.host ?? this.config.host}`; return `${this.config.url}/identicon/${user.username.toLowerCase()}@${user.host ?? this.config.host}`;
} }
@bindThis
public getUserUri(user: LocalUser | PartialLocalUser | RemoteUser | PartialRemoteUser): string {
return this.isRemoteUser(user)
? user.uri : this.genLocalUserUri(user.id);
}
@bindThis
public genLocalUserUri(userId: string): string {
return `${this.config.url}/users/${userId}`;
}
public async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>( public async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>(
src: User['id'] | User, src: User['id'] | User,
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,

View file

@ -253,11 +253,23 @@ export type LocalUser = User & {
uri: null; uri: null;
} }
export type PartialLocalUser = Partial<User> & {
id: User['id'];
host: null;
uri: null;
}
export type RemoteUser = User & { export type RemoteUser = User & {
host: string; host: string;
uri: string; uri: string;
} }
export type PartialRemoteUser = Partial<User> & {
id: User['id'];
host: string;
uri: string;
}
export const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const; export const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const;
export const passwordSchema = { type: 'string', minLength: 1 } as const; export const passwordSchema = { type: 'string', minLength: 1 } as const;
export const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; export const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;

View file

@ -10,6 +10,7 @@ import { QueueLoggerService } from '../QueueLoggerService.js';
import { RelationshipJobData } from '../types.js'; import { RelationshipJobData } from '../types.js';
import type { UsersRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { LocalUser, RemoteUser } from '@/models/entities/User.js';
@Injectable() @Injectable()
export class RelationshipProcessorService { export class RelationshipProcessorService {
@ -39,7 +40,7 @@ export class RelationshipProcessorService {
const [follower, followee] = await Promise.all([ const [follower, followee] = await Promise.all([
this.usersRepository.findOneByOrFail({ id: job.data.from.id }), this.usersRepository.findOneByOrFail({ id: job.data.from.id }),
this.usersRepository.findOneByOrFail({ id: job.data.to.id }), this.usersRepository.findOneByOrFail({ id: job.data.to.id }),
]); ]) as [LocalUser | RemoteUser, LocalUser | RemoteUser];
await this.userFollowingService.unfollow(follower, followee, job.data.silent); await this.userFollowingService.unfollow(follower, followee, job.data.silent);
return 'ok'; return 'ok';
} }

View file

@ -11,7 +11,7 @@ import * as url from '@/misc/prelude/url.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { QueueService } from '@/core/QueueService.js'; import { QueueService } from '@/core/QueueService.js';
import type { LocalUser, User } from '@/models/entities/User.js'; import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
import { UserKeypairService } from '@/core/UserKeypairService.js'; import { UserKeypairService } from '@/core/UserKeypairService.js';
import type { Following } from '@/models/entities/Following.js'; import type { Following } from '@/models/entities/Following.js';
import { countIf } from '@/misc/prelude/array.js'; import { countIf } from '@/misc/prelude/array.js';
@ -630,7 +630,7 @@ export class ActivityPubServerService {
id: request.params.followee, id: request.params.followee,
host: Not(IsNull()), host: Not(IsNull()),
}), }),
]); ]) as [LocalUser | RemoteUser | null, LocalUser | RemoteUser | null];
if (follower == null || followee == null) { if (follower == null || followee == null) {
reply.code(404); reply.code(404);
@ -665,7 +665,7 @@ export class ActivityPubServerService {
id: followRequest.followeeId, id: followRequest.followeeId,
host: Not(IsNull()), host: Not(IsNull()),
}), }),
]); ]) as [LocalUser | RemoteUser | null, LocalUser | RemoteUser | null];
if (follower == null || followee == null) { if (follower == null || followee == null) {
reply.code(404); reply.code(404);

View file

@ -8,6 +8,7 @@ import { escapeAttribute, escapeValue } from '@/misc/prelude/xml.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import * as Acct from '@/misc/acct.js'; import * as Acct from '@/misc/acct.js';
import { NodeinfoServerService } from './NodeinfoServerService.js'; import { NodeinfoServerService } from './NodeinfoServerService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import type { FindOptionsWhere } from 'typeorm'; import type { FindOptionsWhere } from 'typeorm';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
@ -23,6 +24,7 @@ export class WellKnownServerService {
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
private nodeinfoServerService: NodeinfoServerService, private nodeinfoServerService: NodeinfoServerService,
private userEntityService: UserEntityService,
) { ) {
//this.createServer = this.createServer.bind(this); //this.createServer = this.createServer.bind(this);
} }
@ -130,7 +132,7 @@ fastify.get('/.well-known/change-password', async (request, reply) => {
const self = { const self = {
rel: 'self', rel: 'self',
type: 'application/activity+json', type: 'application/activity+json',
href: `${this.config.url}/users/${user.id}`, href: this.userEntityService.genLocalUserUri(user.id),
}; };
const profilePage = { const profilePage = {
rel: 'http://webfinger.net/rel/profile-page', rel: 'http://webfinger.net/rel/profile-page',

View file

@ -7,11 +7,14 @@ import { DI } from '@/di-symbols.js';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import { ApiError } from '@/server/api/error.js'; import { ApiError } from '@/server/api/error.js';
import { LocalUser, RemoteUser } from '@/models/entities/User.js';
import { AccountMoveService } from '@/core/AccountMoveService.js'; import { AccountMoveService } from '@/core/AccountMoveService.js';
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
import { ApiLoggerService } from '@/server/api/ApiLoggerService.js'; import { ApiLoggerService } from '@/server/api/ApiLoggerService.js';
import { GetterService } from '@/server/api/GetterService.js'; import { GetterService } from '@/server/api/GetterService.js';
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import * as Acct from '@/misc/acct.js'; import * as Acct from '@/misc/acct.js';
@ -81,6 +84,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private accountMoveService: AccountMoveService, private accountMoveService: AccountMoveService,
private getterService: GetterService, private getterService: GetterService,
private apPersonService: ApPersonService, private apPersonService: ApPersonService,
private userEntityService: UserEntityService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
// check parameter // check parameter
@ -97,8 +101,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
this.apiLoggerService.logger.warn(`failed to resolve remote user: ${e}`); this.apiLoggerService.logger.warn(`failed to resolve remote user: ${e}`);
throw new ApiError(meta.errors.noSuchUser); throw new ApiError(meta.errors.noSuchUser);
}); });
const destination = await this.getterService.getUser(moveTo.id); const destination = await this.getterService.getUser(moveTo.id) as LocalUser | RemoteUser;
const newUri = this.accountMoveService.getUserUri(destination); const newUri = this.userEntityService.getUserUri(destination);
// update local db // update local db
await this.apPersonService.updatePerson(newUri); await this.apPersonService.updatePerson(newUri);
@ -106,7 +110,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
moveTo = await this.apPersonService.resolvePerson(newUri); moveTo = await this.apPersonService.resolvePerson(newUri);
// make sure that the user has indicated the old account as an alias // make sure that the user has indicated the old account as an alias
const fromUrl = `${this.config.url}/users/${me.id}`; const fromUrl = this.userEntityService.genLocalUserUri(me.id);
let allowed = false; let allowed = false;
if (moveTo.alsoKnownAs) { if (moveTo.alsoKnownAs) {
for (const knownAs of moveTo.alsoKnownAs) { for (const knownAs of moveTo.alsoKnownAs) {

View file

@ -314,7 +314,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
}); });
if (knownAs.id === _user.id) throw new ApiError(meta.errors.forbiddenToSetYourself); if (knownAs.id === _user.id) throw new ApiError(meta.errors.forbiddenToSetYourself);
const toUrl = this.accountMoveService.getUserUri(knownAs); const toUrl = this.userEntityService.getUserUri(knownAs);
if (!toUrl) throw new ApiError(meta.errors.uriNull); if (!toUrl) throw new ApiError(meta.errors.uriNull);
newAlsoKnownAs.add(toUrl); newAlsoKnownAs.add(toUrl);