From 36d712699206f0e197d589a55097b1544db2f62f Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 01:31:13 -0400 Subject: implement InstanceEntityService.fetchInstancesByHost --- .../src/core/entities/InstanceEntityService.ts | 29 +++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index a2ee4b0505..4ca4ff650b 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -4,6 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; +import { In } from 'typeorm'; import type { Packed } from '@/misc/json-schema.js'; import type { MiInstance } from '@/models/Instance.js'; import { bindThis } from '@/decorators.js'; @@ -11,7 +12,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { RoleService } from '@/core/RoleService.js'; import { MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; -import { MiMeta } from '@/models/_.js'; +import type { InstancesRepository, MiMeta } from '@/models/_.js'; @Injectable() export class InstanceEntityService { @@ -19,6 +20,9 @@ export class InstanceEntityService { @Inject(DI.meta) private meta: MiMeta, + @Inject(DI.instancesRepository) + private readonly instancesRepository: InstancesRepository, + private roleService: RoleService, private utilityService: UtilityService, @@ -73,5 +77,28 @@ export class InstanceEntityService { ) { return Promise.all(instances.map(x => this.pack(x, me))); } + + @bindThis + public async fetchInstancesByHost(instances: (MiInstance | MiInstance['host'])[]): Promise { + const result: MiInstance[] = []; + + const toFetch: string[] = []; + for (const instance of instances) { + if (typeof(instance) === 'string') { + toFetch.push(instance); + } else { + result.push(instance); + } + } + + if (toFetch.length > 0) { + const fetched = await this.instancesRepository.findBy({ + host: In(toFetch), + }); + result.push(...fetched); + } + + return result; + } } -- cgit v1.2.3-freya From cfbf2c9c8eb374791d163962b761248b11dc557e Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 01:32:45 -0400 Subject: pack users with "me" context in AbuseUserReportEntityService --- .../src/core/entities/AbuseUserReportEntityService.ts | 13 ++++++++----- .../src/server/api/endpoints/admin/abuse-user-reports.ts | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts index 70ead890ab..a6c00d89b9 100644 --- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts +++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { AbuseUserReportsRepository } from '@/models/_.js'; +import type { AbuseUserReportsRepository, MiUser } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js'; import { bindThis } from '@/decorators.js'; @@ -32,9 +32,11 @@ export class AbuseUserReportEntityService { packedTargetUser?: Packed<'UserDetailedNotMe'>, packedAssignee?: Packed<'UserDetailedNotMe'>, }, + me?: MiUser | null, ) { const report = typeof src === 'object' ? src : await this.abuseUserReportsRepository.findOneByOrFail({ id: src }); + // noinspection ES6MissingAwait return await awaitAll({ id: report.id, createdAt: this.idService.parse(report.id).date.toISOString(), @@ -43,10 +45,10 @@ export class AbuseUserReportEntityService { reporterId: report.reporterId, targetUserId: report.targetUserId, assigneeId: report.assigneeId, - reporter: hint?.packedReporter ?? this.userEntityService.pack(report.reporter ?? report.reporterId, null, { + reporter: hint?.packedReporter ?? this.userEntityService.pack(report.reporter ?? report.reporterId, me, { schema: 'UserDetailedNotMe', }), - targetUser: hint?.packedTargetUser ?? this.userEntityService.pack(report.targetUser ?? report.targetUserId, null, { + targetUser: hint?.packedTargetUser ?? this.userEntityService.pack(report.targetUser ?? report.targetUserId, me, { schema: 'UserDetailedNotMe', }), assignee: report.assigneeId ? hint?.packedAssignee ?? this.userEntityService.pack(report.assignee ?? report.assigneeId, null, { @@ -61,13 +63,14 @@ export class AbuseUserReportEntityService { @bindThis public async packMany( reports: MiAbuseUserReport[], + me?: MiUser | null, ) { const _reporters = reports.map(({ reporter, reporterId }) => reporter ?? reporterId); const _targetUsers = reports.map(({ targetUser, targetUserId }) => targetUser ?? targetUserId); const _assignees = reports.map(({ assignee, assigneeId }) => assignee ?? assigneeId).filter(x => x != null); const _userMap = await this.userEntityService.packMany( [..._reporters, ..._targetUsers, ..._assignees], - null, + me, { schema: 'UserDetailedNotMe' }, ).then(users => new Map(users.map(u => [u.id, u]))); return Promise.all( @@ -75,7 +78,7 @@ export class AbuseUserReportEntityService { const packedReporter = _userMap.get(report.reporterId); const packedTargetUser = _userMap.get(report.targetUserId); const packedAssignee = report.assigneeId != null ? _userMap.get(report.assigneeId) : undefined; - return this.pack(report, { packedReporter, packedTargetUser, packedAssignee }); + return this.pack(report, { packedReporter, packedTargetUser, packedAssignee }, me); }), ); } diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index 0dbfaae054..ad0bbb5647 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -134,7 +134,7 @@ export default class extends Endpoint { // eslint- const reports = await query.limit(ps.limit).getMany(); - return await this.abuseUserReportEntityService.packMany(reports); + return await this.abuseUserReportEntityService.packMany(reports, me); }); } } -- cgit v1.2.3-freya From 71f60d519b6f8e655737136484658ea0f1d00507 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 01:33:55 -0400 Subject: add targetInstance to abuse report schema --- .../core/entities/AbuseUserReportEntityService.ts | 27 +++++++++++++++++++--- packages/backend/src/models/AbuseUserReport.ts | 21 +++++++++++++++++ .../api/endpoints/admin/abuse-user-reports.ts | 5 ++++ packages/misskey-js/src/autogen/types.ts | 1 + 4 files changed, 51 insertions(+), 3 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts index a6c00d89b9..c1d877aa12 100644 --- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts +++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts @@ -5,13 +5,14 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { AbuseUserReportsRepository, MiUser } from '@/models/_.js'; +import type { AbuseUserReportsRepository, InstancesRepository, MiInstance, MiUser } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; import type { Packed } from '@/misc/json-schema.js'; import { UserEntityService } from './UserEntityService.js'; +import { InstanceEntityService } from './InstanceEntityService.js'; @Injectable() export class AbuseUserReportEntityService { @@ -19,6 +20,10 @@ export class AbuseUserReportEntityService { @Inject(DI.abuseUserReportsRepository) private abuseUserReportsRepository: AbuseUserReportsRepository, + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, + + private readonly instanceEntityService: InstanceEntityService, private userEntityService: UserEntityService, private idService: IdService, ) { @@ -30,6 +35,7 @@ export class AbuseUserReportEntityService { hint?: { packedReporter?: Packed<'UserDetailedNotMe'>, packedTargetUser?: Packed<'UserDetailedNotMe'>, + packedTargetInstance?: Packed<'FederationInstance'>, packedAssignee?: Packed<'UserDetailedNotMe'>, }, me?: MiUser | null, @@ -51,7 +57,16 @@ export class AbuseUserReportEntityService { targetUser: hint?.packedTargetUser ?? this.userEntityService.pack(report.targetUser ?? report.targetUserId, me, { schema: 'UserDetailedNotMe', }), - assignee: report.assigneeId ? hint?.packedAssignee ?? this.userEntityService.pack(report.assignee ?? report.assigneeId, null, { + // return hint, or pack by relation, or fetch and pack by id, or null + targetInstance: hint?.packedTargetInstance ?? ( + report.targetUserInstance + ? this.instanceEntityService.pack(report.targetUserInstance, me) + : report.targetUserHost + ? this.instancesRepository.findOneBy({ host: report.targetUserHost }).then(instance => instance + ? this.instanceEntityService.pack(instance, me) + : null) + : null), + assignee: report.assigneeId ? hint?.packedAssignee ?? this.userEntityService.pack(report.assignee ?? report.assigneeId, me, { schema: 'UserDetailedNotMe', }) : null, forwarded: report.forwarded, @@ -73,12 +88,18 @@ export class AbuseUserReportEntityService { me, { schema: 'UserDetailedNotMe' }, ).then(users => new Map(users.map(u => [u.id, u]))); + const _targetInstances = reports + .map(({ targetUserInstance, targetUserHost }) => targetUserInstance ?? targetUserHost) + .filter((i): i is MiInstance | string => i != null); + const _instanceMap = await this.instanceEntityService.packMany(await this.instanceEntityService.fetchInstancesByHost(_targetInstances), me) + .then(instances => new Map(instances.map(i => [i.host, i]))); return Promise.all( reports.map(report => { const packedReporter = _userMap.get(report.reporterId); const packedTargetUser = _userMap.get(report.targetUserId); + const packedTargetInstance = report.targetUserHost ? _instanceMap.get(report.targetUserHost) : undefined; const packedAssignee = report.assigneeId != null ? _userMap.get(report.assigneeId) : undefined; - return this.pack(report, { packedReporter, packedTargetUser, packedAssignee }, me); + return this.pack(report, { packedReporter, packedTargetUser, packedAssignee, packedTargetInstance }, me); }), ); } diff --git a/packages/backend/src/models/AbuseUserReport.ts b/packages/backend/src/models/AbuseUserReport.ts index d43ebf9342..8f8d759004 100644 --- a/packages/backend/src/models/AbuseUserReport.ts +++ b/packages/backend/src/models/AbuseUserReport.ts @@ -4,6 +4,7 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { MiInstance } from '@/models/Instance.js'; import { id } from './util/id.js'; import { MiUser } from './User.js'; @@ -88,11 +89,31 @@ export class MiAbuseUserReport { }) public targetUserHost: string | null; + @ManyToOne(() => MiInstance, { + // TODO create a foreign key constraint after hazelnoot/labs/persisted-instance-blocks is merged + createForeignKeyConstraints: false, + }) + @JoinColumn({ + name: 'targetUserHost', + referencedColumnName: 'host', + }) + public targetUserInstance: MiInstance | null; + @Index() @Column('varchar', { length: 128, nullable: true, comment: '[Denormalized]', }) public reporterHost: string | null; + + @ManyToOne(() => MiInstance, { + // TODO create a foreign key constraint after hazelnoot/labs/persisted-instance-blocks is merged + createForeignKeyConstraints: false, + }) + @JoinColumn({ + name: 'reporterHost', + referencedColumnName: 'host', + }) + public reporterInstance: MiInstance | null; //#endregion } diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index ad0bbb5647..f052d3dd14 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -69,6 +69,11 @@ export const meta = { nullable: false, optional: false, ref: 'UserDetailedNotMe', }, + targetInstance: { + type: 'object', + nullable: true, optional: false, + ref: 'FederationInstance', + }, assignee: { type: 'object', nullable: true, optional: false, diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 5e91fb14ac..b1d5f63a8b 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -6198,6 +6198,7 @@ export type operations = { assigneeId: string | null; reporter: components['schemas']['UserDetailedNotMe']; targetUser: components['schemas']['UserDetailedNotMe']; + targetInstance: components['schemas']['FederationInstance'] | null; assignee: components['schemas']['UserDetailedNotMe'] | null; forwarded: boolean; /** @enum {string|null} */ -- cgit v1.2.3-freya From 23302fe7d8de4923f4748f6c64d39e3f57c9d98f Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 02:02:27 -0400 Subject: add relation from user->user_profile to speed up UserEntityService.pack and packMany --- .../backend/src/core/entities/UserEntityService.ts | 34 +++++++++++++++++----- packages/backend/src/models/User.ts | 4 +++ packages/backend/src/models/UserProfile.ts | 2 +- 3 files changed, 32 insertions(+), 8 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 3524119ba1..326baaefd4 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -487,7 +487,10 @@ export class UserEntityService implements OnModuleInit { includeSecrets: false, }, options); - const user = typeof src === 'object' ? src : await this.usersRepository.findOneByOrFail({ id: src }); + const user = typeof src === 'object' ? src : await this.usersRepository.findOneOrFail({ + where: { id: src }, + relations: { userProfile: true }, + }); // migration if (user.avatarId != null && user.avatarUrl === null) { @@ -521,7 +524,7 @@ export class UserEntityService implements OnModuleInit { const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false; const profile = isDetailed - ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) + ? (opts.userProfile ?? user.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) : null; let relation: UserRelation | null = null; @@ -556,7 +559,7 @@ export class UserEntityService implements OnModuleInit { } } - const mastoapi = !isDetailed ? opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id }) : null; + const mastoapi = !isDetailed ? opts.userProfile ?? user.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id }) : null; const followingCount = profile == null ? null : (profile.followingVisibility === 'public') || isMe || iAmModerator ? user.followingCount : @@ -785,8 +788,13 @@ export class UserEntityService implements OnModuleInit { const _users = users.filter((user): user is MiUser => typeof user !== 'string'); if (_users.length !== users.length) { _users.push( - ...await this.usersRepository.findBy({ - id: In(users.filter((user): user is string => typeof user === 'string')), + ...await this.usersRepository.find({ + where: { + id: In(users.filter((user): user is string => typeof user === 'string')), + }, + relations: { + userProfile: true, + }, }), ); } @@ -800,8 +808,20 @@ export class UserEntityService implements OnModuleInit { let pinNotes: Map = new Map(); if (options?.schema !== 'UserLite') { - profilesMap = await this.userProfilesRepository.findBy({ userId: In(_userIds) }) - .then(profiles => new Map(profiles.map(p => [p.userId, p]))); + const _profiles: MiUserProfile[] = []; + const _profilesToFetch: string[] = []; + for (const user of _users) { + if (user.userProfile) { + _profiles.push(user.userProfile); + } else { + _profilesToFetch.push(user.id); + } + } + if (_profilesToFetch.length > 0) { + const fetched = await this.userProfilesRepository.findBy({ userId: In(_profilesToFetch) }); + _profiles.push(...fetched); + } + profilesMap = new Map(_profiles.map(p => [p.userId, p])); const meId = me ? me.id : null; if (meId) { diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts index 3ef5817672..2f13400944 100644 --- a/packages/backend/src/models/User.ts +++ b/packages/backend/src/models/User.ts @@ -8,6 +8,7 @@ import { type UserUnsignedFetchOption, userUnsignedFetchOptions } from '@/const. import { MiInstance } from '@/models/Instance.js'; import { id } from './util/id.js'; import { MiDriveFile } from './DriveFile.js'; +import type { MiUserProfile } from './UserProfile.js'; @Entity('user') @Index(['usernameLower', 'host'], { unique: true }) @@ -395,6 +396,9 @@ export class MiUser { }) public attributionDomains: string[]; + @OneToOne('user_profile', (profile: MiUserProfile) => profile.user) + public userProfile: MiUserProfile | null; + constructor(data: Partial) { if (data == null) return; diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts index 29c453dd71..6ee72e6ddd 100644 --- a/packages/backend/src/models/UserProfile.ts +++ b/packages/backend/src/models/UserProfile.ts @@ -17,7 +17,7 @@ export class MiUserProfile { @PrimaryColumn(id()) public userId: MiUser['id']; - @OneToOne(type => MiUser, { + @OneToOne(() => MiUser, user => user.userProfile, { onDelete: 'CASCADE', }) @JoinColumn() -- cgit v1.2.3-freya From b05ccbc3aca8e008555736f25760f9f2b1b2a5e7 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 02:03:05 -0400 Subject: add relations from abuse_user_report->user_profile to speed up admin/abuse-user-reports endpoint --- packages/backend/src/models/AbuseUserReport.ts | 22 ++++++++++++++++++++++ .../api/endpoints/admin/abuse-user-reports.ts | 10 +++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/models/AbuseUserReport.ts b/packages/backend/src/models/AbuseUserReport.ts index 8f8d759004..c1a44c3d40 100644 --- a/packages/backend/src/models/AbuseUserReport.ts +++ b/packages/backend/src/models/AbuseUserReport.ts @@ -5,6 +5,7 @@ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; import { MiInstance } from '@/models/Instance.js'; +import { MiUserProfile } from '@/models/UserProfile.js'; import { id } from './util/id.js'; import { MiUser } from './User.js'; @@ -25,6 +26,13 @@ export class MiAbuseUserReport { @JoinColumn() public targetUser: MiUser | null; + @ManyToOne(() => MiUserProfile, { + onDelete: 'CASCADE', + createForeignKeyConstraints: false, + }) + @JoinColumn({ name: 'targetUserId', referencedColumnName: 'userId' }) + public targetUserProfile: MiUserProfile | null; + @Index() @Column(id()) public reporterId: MiUser['id']; @@ -35,6 +43,13 @@ export class MiAbuseUserReport { @JoinColumn() public reporter: MiUser | null; + @ManyToOne(() => MiUserProfile, { + onDelete: 'CASCADE', + createForeignKeyConstraints: false, + }) + @JoinColumn({ name: 'reporterId', referencedColumnName: 'userId' }) + public reporterProfile: MiUserProfile | null; + @Column({ ...id(), nullable: true, @@ -47,6 +62,13 @@ export class MiAbuseUserReport { @JoinColumn() public assignee: MiUser | null; + @ManyToOne(() => MiUserProfile, { + onDelete: 'CASCADE', + createForeignKeyConstraints: false, + }) + @JoinColumn({ name: 'assigneeId', referencedColumnName: 'userId' }) + public assigneeProfile: MiUserProfile | null; + @Index() @Column('boolean', { default: false, diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index f052d3dd14..f9aa3b7ae5 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -120,7 +120,15 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.abuseUserReportsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId); + const query = this.queryService.makePaginationQuery(this.abuseUserReportsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId) + .leftJoinAndSelect('report.targetUser', 'targetUser') + .leftJoinAndSelect('report.targetUserProfile', 'targetUserProfile') + .leftJoinAndSelect('report.targetUserInstance', 'targetUserInstance') + .leftJoinAndSelect('report.reporter', 'reporter') + .leftJoinAndSelect('report.reporterUserProfile', 'reporterUserProfile') + .leftJoinAndSelect('report.assignee', 'assignee') + .leftJoinAndSelect('report.assigneeUserProfile', 'assigneeUserProfile') + ; switch (ps.state) { case 'resolved': query.andWhere('report.resolved = TRUE'); break; -- cgit v1.2.3-freya From f17e4641888d82e28daaf125280a68fc13277cc9 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 02:19:30 -0400 Subject: fix WebhookTestService again --- packages/backend/src/core/WebhookTestService.ts | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts index 8c1508df24..7c85a32427 100644 --- a/packages/backend/src/core/WebhookTestService.ts +++ b/packages/backend/src/core/WebhookTestService.ts @@ -77,6 +77,7 @@ function generateDummyUser(override?: Partial): MiUser { mandatoryCW: null, rejectQuotes: false, allowUnsignedFetch: 'staff', + userProfile: null, attributionDomains: [], ...override, }; @@ -363,10 +364,15 @@ export class WebhookTestService { id: 'dummy-abuse-report1', targetUserId: 'dummy-target-user', targetUser: null, + targetUserProfile: null, + targetUserInstance: null, reporterId: 'dummy-reporter-user', reporter: null, + reporterProfile: null, + reporterInstance: null, assigneeId: null, assignee: null, + assigneeProfile: null, resolved: false, forwarded: false, comment: 'This is a dummy report for testing purposes.', -- cgit v1.2.3-freya From ee3cd216f7cdb3f9e57eca717f547e60c6bd89e5 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 02:19:40 -0400 Subject: fix TS errors in ReversiService --- packages/backend/src/core/ReversiService.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/ReversiService.ts b/packages/backend/src/core/ReversiService.ts index e31d9e5b1a..b57ab6d9cb 100644 --- a/packages/backend/src/core/ReversiService.ts +++ b/packages/backend/src/core/ReversiService.ts @@ -588,6 +588,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { lastFetchedAt: parsed.user1.lastFetchedAt != null ? new Date(parsed.user1.lastFetchedAt) : null, movedAt: parsed.user1.movedAt != null ? new Date(parsed.user1.movedAt) : null, instance: null, + userProfile: null, } : null, user2: parsed.user2 != null ? { ...parsed.user2, @@ -599,6 +600,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { lastFetchedAt: parsed.user2.lastFetchedAt != null ? new Date(parsed.user2.lastFetchedAt) : null, movedAt: parsed.user2.movedAt != null ? new Date(parsed.user2.movedAt) : null, instance: null, + userProfile: null, } : null, }; } else { -- cgit v1.2.3-freya From 16891f82246ebb4ea84a9b2011041a837f4aeb28 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 02:34:14 -0400 Subject: fix relation typo in abuse-user-reports --- packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index f9aa3b7ae5..bf71cb82c4 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -125,9 +125,9 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('report.targetUserProfile', 'targetUserProfile') .leftJoinAndSelect('report.targetUserInstance', 'targetUserInstance') .leftJoinAndSelect('report.reporter', 'reporter') - .leftJoinAndSelect('report.reporterUserProfile', 'reporterUserProfile') + .leftJoinAndSelect('report.reporterProfile', 'reporterProfile') .leftJoinAndSelect('report.assignee', 'assignee') - .leftJoinAndSelect('report.assigneeUserProfile', 'assigneeUserProfile') + .leftJoinAndSelect('report.assigneeProfile', 'assigneeProfile') ; switch (ps.state) { -- cgit v1.2.3-freya From b1876bf06e92acda34270cf2582559b2f4cc1001 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 03:08:34 -0400 Subject: remove report -> profile relations to avoid TypeORM bug https://github.com/typeorm/typeorm/issues/10469 --- packages/backend/src/models/AbuseUserReport.ts | 22 ---------------------- .../api/endpoints/admin/abuse-user-reports.ts | 6 +++--- 2 files changed, 3 insertions(+), 25 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/models/AbuseUserReport.ts b/packages/backend/src/models/AbuseUserReport.ts index c1a44c3d40..8f8d759004 100644 --- a/packages/backend/src/models/AbuseUserReport.ts +++ b/packages/backend/src/models/AbuseUserReport.ts @@ -5,7 +5,6 @@ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; import { MiInstance } from '@/models/Instance.js'; -import { MiUserProfile } from '@/models/UserProfile.js'; import { id } from './util/id.js'; import { MiUser } from './User.js'; @@ -26,13 +25,6 @@ export class MiAbuseUserReport { @JoinColumn() public targetUser: MiUser | null; - @ManyToOne(() => MiUserProfile, { - onDelete: 'CASCADE', - createForeignKeyConstraints: false, - }) - @JoinColumn({ name: 'targetUserId', referencedColumnName: 'userId' }) - public targetUserProfile: MiUserProfile | null; - @Index() @Column(id()) public reporterId: MiUser['id']; @@ -43,13 +35,6 @@ export class MiAbuseUserReport { @JoinColumn() public reporter: MiUser | null; - @ManyToOne(() => MiUserProfile, { - onDelete: 'CASCADE', - createForeignKeyConstraints: false, - }) - @JoinColumn({ name: 'reporterId', referencedColumnName: 'userId' }) - public reporterProfile: MiUserProfile | null; - @Column({ ...id(), nullable: true, @@ -62,13 +47,6 @@ export class MiAbuseUserReport { @JoinColumn() public assignee: MiUser | null; - @ManyToOne(() => MiUserProfile, { - onDelete: 'CASCADE', - createForeignKeyConstraints: false, - }) - @JoinColumn({ name: 'assigneeId', referencedColumnName: 'userId' }) - public assigneeProfile: MiUserProfile | null; - @Index() @Column('boolean', { default: false, diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index bf71cb82c4..b8200c09aa 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -122,12 +122,12 @@ export default class extends Endpoint { // eslint- super(meta, paramDef, async (ps, me) => { const query = this.queryService.makePaginationQuery(this.abuseUserReportsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId) .leftJoinAndSelect('report.targetUser', 'targetUser') - .leftJoinAndSelect('report.targetUserProfile', 'targetUserProfile') + .leftJoinAndSelect('targetUser.userProfile', 'targetUserProfile') .leftJoinAndSelect('report.targetUserInstance', 'targetUserInstance') .leftJoinAndSelect('report.reporter', 'reporter') - .leftJoinAndSelect('report.reporterProfile', 'reporterProfile') + .leftJoinAndSelect('reporter.userProfile', 'reporterProfile') .leftJoinAndSelect('report.assignee', 'assignee') - .leftJoinAndSelect('report.assigneeProfile', 'assigneeProfile') + .leftJoinAndSelect('assignee.userProfile', 'assigneeProfile') ; switch (ps.state) { -- cgit v1.2.3-freya From f4107b1c2b0632504b9fefb6c8c5608282313cc2 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 13:31:24 -0400 Subject: check if previews are disabled before anything else --- packages/backend/src/server/web/UrlPreviewService.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 2a300782c6..160cf37c00 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -123,6 +123,16 @@ export class UrlPreviewService { request: FastifyRequest, reply: FastifyReply, ): Promise { + if (!this.meta.urlPreviewEnabled) { + return reply.code(403).send({ + error: { + message: 'URL preview is disabled', + code: 'URL_PREVIEW_DISABLED', + id: '58b36e13-d2f5-0323-b0c6-76aa9dabefb8', + }, + }); + } + const url = request.query.url; if (typeof url !== 'string' || !URL.canParse(url)) { reply.code(400); @@ -135,16 +145,6 @@ export class UrlPreviewService { return; } - if (!this.meta.urlPreviewEnabled) { - return reply.code(403).send({ - error: { - message: 'URL preview is disabled', - code: 'URL_PREVIEW_DISABLED', - id: '58b36e13-d2f5-0323-b0c6-76aa9dabefb8', - }, - }); - } - // Check rate limit const auth = await this.authenticate(request); if (!await this.checkRateLimit(auth, reply)) { -- cgit v1.2.3-freya From f601cff5c5222d6f3a7c06ecbafb3d07ad63997f Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 13:31:40 -0400 Subject: check input URL scheme before continuing --- packages/backend/src/server/web/UrlPreviewService.ts | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 160cf37c00..da2660ab0f 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -139,6 +139,13 @@ export class UrlPreviewService { return; } + // Enforce HTTP(S) for input URLs + const urlScheme = this.utilityService.getUrlScheme(url); + if (urlScheme !== 'http:' && urlScheme !== 'https:') { + reply.code(400); + return; + } + const lang = request.query.lang; if (Array.isArray(lang)) { reply.code(400); -- cgit v1.2.3-freya From 865b198ab31688de957d82ea447e11be78f718dc Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 13:32:04 -0400 Subject: redirect to exclude hash from preview URL --- packages/backend/src/server/web/UrlPreviewService.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index da2660ab0f..3e0133d50e 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -152,13 +152,25 @@ export class UrlPreviewService { return; } + // Strip out hash (anchor) + const urlObj = new URL(url); + if (urlObj.hash) { + urlObj.hash = ''; + const params = new URLSearchParams({ url: urlObj.href }); + if (lang) params.set('lang', lang); + const newUrl = `/url?${params.toString()}`; + + reply.redirect(newUrl, 301); + return; + } + // Check rate limit const auth = await this.authenticate(request); if (!await this.checkRateLimit(auth, reply)) { return; } - if (this.utilityService.isBlockedHost(this.meta.blockedHosts, new URL(url).host)) { + if (this.utilityService.isBlockedHost(this.meta.blockedHosts, urlObj.host)) { return reply.code(403).send({ error: { message: 'URL is blocked', -- cgit v1.2.3-freya From a91c0de9b5b337fdb65fbd922969132d610bd8c4 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 13:32:21 -0400 Subject: cache alternate URLs in UrlPreviewService --- packages/backend/src/server/web/UrlPreviewService.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 3e0133d50e..78b2204fbb 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -185,7 +185,7 @@ export class UrlPreviewService { return; } - const cacheKey = `${url}@${lang}@${cacheFormatVersion}`; + const cacheKey = getCacheKey(url, lang); if (await this.sendCachedPreview(cacheKey, reply, fetch)) { return; } @@ -236,6 +236,18 @@ export class UrlPreviewService { // Await this to avoid hammering redis when a bunch of URLs are fetched at once await this.previewCache.set(cacheKey, summary); + // Also cache the response URL in case of redirects + if (summary.url !== url) { + const responseCacheKey = getCacheKey(summary.url, lang); + await this.previewCache.set(responseCacheKey, summary); + } + + // Also cache the ActivityPub URL, if different from the others + if (summary.activityPub && summary.activityPub !== summary.url) { + const apCacheKey = getCacheKey(summary.activityPub, lang); + await this.previewCache.set(apCacheKey, summary); + } + // Cache 1 day (matching redis), but only once we finalize the result if (!summary.activityPub || summary.haveNoteLocally) { reply.header('Cache-Control', 'public, max-age=86400'); @@ -552,3 +564,7 @@ export class UrlPreviewService { return true; } } + +function getCacheKey(url: string, lang = 'none') { + return `${url}@${lang}@${cacheFormatVersion}`; +} -- cgit v1.2.3-freya From d86530a069061e81bfa4661ee75fb896b33e3e7d Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 30 May 2025 14:28:24 -0400 Subject: remove unused fields from WebhookTestService.ts --- packages/backend/src/core/WebhookTestService.ts | 3 --- packages/backend/test/unit/AbuseReportNotificationService.ts | 3 --- 2 files changed, 6 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts index 7c85a32427..c4b01d535b 100644 --- a/packages/backend/src/core/WebhookTestService.ts +++ b/packages/backend/src/core/WebhookTestService.ts @@ -364,15 +364,12 @@ export class WebhookTestService { id: 'dummy-abuse-report1', targetUserId: 'dummy-target-user', targetUser: null, - targetUserProfile: null, targetUserInstance: null, reporterId: 'dummy-reporter-user', reporter: null, - reporterProfile: null, reporterInstance: null, assigneeId: null, assignee: null, - assigneeProfile: null, resolved: false, forwarded: false, comment: 'This is a dummy report for testing purposes.', diff --git a/packages/backend/test/unit/AbuseReportNotificationService.ts b/packages/backend/test/unit/AbuseReportNotificationService.ts index 55ec755dc7..ee68b10f1b 100644 --- a/packages/backend/test/unit/AbuseReportNotificationService.ts +++ b/packages/backend/test/unit/AbuseReportNotificationService.ts @@ -367,15 +367,12 @@ describe('AbuseReportNotificationService', () => { id: idService.gen(), targetUserId: alice.id, targetUser: alice, - targetUserProfile: null, targetUserInstance: null, reporterId: bob.id, reporter: bob, - reporterProfile: null, reporterInstance: null, assigneeId: null, assignee: null, - assigneeProfile: null, resolved: false, forwarded: false, comment: 'test', -- cgit v1.2.3-freya