diff --git a/package.json b/package.json index 61962125ca..aedb161902 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "13.11.0-beta.3", + "version": "13.11.0-beta.4", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/backend/migration/1680775031481-avatar-url-and-banner-url.js b/packages/backend/migration/1680775031481-avatar-url-and-banner-url.js new file mode 100644 index 0000000000..7c5fe7ac5e --- /dev/null +++ b/packages/backend/migration/1680775031481-avatar-url-and-banner-url.js @@ -0,0 +1,17 @@ +export class AvatarUrlAndBannerUrl1680775031481 { + name = 'AvatarUrlAndBannerUrl1680775031481' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" ADD "avatarUrl" character varying(512)`); + await queryRunner.query(`ALTER TABLE "user" ADD "bannerUrl" character varying(512)`); + await queryRunner.query(`ALTER TABLE "user" ADD "avatarBlurhash" character varying(128)`); + await queryRunner.query(`ALTER TABLE "user" ADD "bannerBlurhash" character varying(128)`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "bannerBlurhash"`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarBlurhash"`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "bannerUrl"`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarUrl"`); + } +} diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index 67e907c271..664e7eb4ea 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -31,6 +31,7 @@ import type { UtilityService } from '@/core/UtilityService.js'; import type { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; import { MetaService } from '@/core/MetaService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js'; import { extractApHashtags } from './tag.js'; import type { OnModuleInit } from '@nestjs/common'; @@ -49,6 +50,7 @@ const summaryLength = 2048; export class ApPersonService implements OnModuleInit { private utilityService: UtilityService; private userEntityService: UserEntityService; + private driveFileEntityService: DriveFileEntityService; private idService: IdService; private globalEventService: GlobalEventService; private metaService: MetaService; @@ -113,6 +115,7 @@ export class ApPersonService implements OnModuleInit { onModuleInit() { this.utilityService = this.moduleRef.get('UtilityService'); this.userEntityService = this.moduleRef.get('UserEntityService'); + this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService'); this.idService = this.moduleRef.get('IdService'); this.globalEventService = this.moduleRef.get('GlobalEventService'); this.metaService = this.moduleRef.get('MetaService'); @@ -356,32 +359,44 @@ export class ApPersonService implements OnModuleInit { const avatarId = avatar ? avatar.id : null; const bannerId = banner ? banner.id : null; + const avatarUrl = avatar ? this.driveFileEntityService.getPublicUrl(avatar, 'avatar') : null; + const bannerUrl = banner ? this.driveFileEntityService.getPublicUrl(banner) : null; + const avatarBlurhash = avatar ? avatar.blurhash : null; + const bannerBlurhash = banner ? banner.blurhash : null; await this.usersRepository.update(user!.id, { avatarId, bannerId, + avatarUrl, + bannerUrl, + avatarBlurhash, + bannerBlurhash, }); - user!.avatarId = avatarId; - user!.bannerId = bannerId; - //#endregion + user!.avatarId = avatarId; + user!.bannerId = bannerId; + user!.avatarUrl = avatarUrl; + user!.bannerUrl = bannerUrl; + user!.avatarBlurhash = avatarBlurhash; + user!.bannerBlurhash = bannerBlurhash; + //#endregion - //#region カスタム絵文字取得 - const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host).catch(err => { - this.logger.info(`extractEmojis: ${err}`); - return [] as Emoji[]; - }); + //#region カスタム絵文字取得 + const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host).catch(err => { + this.logger.info(`extractEmojis: ${err}`); + return [] as Emoji[]; + }); - const emojiNames = emojis.map(emoji => emoji.name); + const emojiNames = emojis.map(emoji => emoji.name); - await this.usersRepository.update(user!.id, { - emojis: emojiNames, - }); - //#endregion + await this.usersRepository.update(user!.id, { + emojis: emojiNames, + }); + //#endregion - await this.updateFeatured(user!.id, resolver).catch(err => this.logger.error(err)); + await this.updateFeatured(user!.id, resolver).catch(err => this.logger.error(err)); - return user!; + return user!; } /** @@ -463,10 +478,14 @@ export class ApPersonService implements OnModuleInit { if (avatar) { updates.avatarId = avatar.id; + updates.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar'); + updates.avatarBlurhash = avatar.blurhash; } if (banner) { updates.bannerId = banner.id; + updates.bannerUrl = this.driveFileEntityService.getPublicUrl(banner); + updates.bannerBlurhash = banner.blurhash; } // Update user diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index 2d40f444cb..d82f36d971 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -266,6 +266,7 @@ export class DriveFileEntityService { fileIds: DriveFile['id'][], options?: PackOptions, ): Promise['id'], Packed<'DriveFile'> | null>> { + if (fileIds.length === 0) return new Map(); const files = await this.driveFilesRepository.findBy({ id: In(fileIds) }); const packedFiles = await this.packMany(files, options); const map = new Map['id'], Packed<'DriveFile'> | null>(packedFiles.map(f => [f.id, f])); @@ -280,6 +281,7 @@ export class DriveFileEntityService { fileIds: DriveFile['id'][], options?: PackOptions, ): Promise[]> { + if (fileIds.length === 0) return []; const filesMap = await this.packManyByIdsMap(fileIds, options); return fileIds.map(id => filesMap.get(id)).filter(isNotNull); } diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 94b3029c58..f23e0f38ea 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -183,6 +183,11 @@ export class NoteEntityService implements OnModuleInit { // 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない } + // パフォーマンスのためノートが作成されてから1秒以上経っていない場合はリアクションを取得しない + if (note.createdAt.getTime() + 1000 > Date.now()) { + return undefined; + } + const reaction = await this.noteReactionsRepository.findOneBy({ userId: meId, noteId: note.id, @@ -395,7 +400,8 @@ export class NoteEntityService implements OnModuleInit { const myReactionsMap = new Map(); if (meId) { const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!); - const targets = [...notes.map(n => n.id), ...renoteIds]; + // パフォーマンスのためノートが作成されてから1秒以上経っていない場合はリアクションを取得しない + const targets = [...notes.filter(n => n.createdAt.getTime() + 1000 < Date.now()).map(n => n.id), ...renoteIds]; const myReactions = await this.noteReactionsRepository.findBy({ userId: meId, noteId: In(targets), @@ -409,7 +415,7 @@ export class NoteEntityService implements OnModuleInit { await this.customEmojiService.prefetchEmojis(this.aggregateNoteEmojis(notes)); // TODO: 本当は renote とか reply がないのに renoteId とか replyId があったらここで解決しておく const fileIds = notes.map(n => [n.fileIds, n.renote?.fileIds, n.reply?.fileIds]).flat(2).filter(isNotNull); - const packedFiles = await this.driveFileEntityService.packManyByIdsMap(fileIds); + const packedFiles = fileIds.length > 0 ? await this.driveFileEntityService.packManyByIdsMap(fileIds) : new Map(); return await Promise.all(notes.map(n => this.pack(n, me, { ...options, diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index 6b9a9d3320..0dc63d969f 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -108,27 +108,30 @@ export class NotificationEntityService implements OnModuleInit { ) { if (notifications.length === 0) return []; - const noteIds = notifications.map(x => x.noteId).filter(isNotNull); + let validNotifications = notifications; + + const noteIds = validNotifications.map(x => x.noteId).filter(isNotNull); const notes = noteIds.length > 0 ? await this.notesRepository.find({ where: { id: In(noteIds) }, - relations: ['user', 'user.avatar', 'user.banner', 'reply', 'reply.user', 'reply.user.avatar', 'reply.user.banner', 'renote', 'renote.user', 'renote.user.avatar', 'renote.user.banner'], + relations: ['user', 'reply', 'reply.user', 'renote', 'renote.user'], }) : []; const packedNotesArray = await this.noteEntityService.packMany(notes, { id: meId }, { detail: true, }); const packedNotes = new Map(packedNotesArray.map(p => [p.id, p])); - const userIds = notifications.map(x => x.notifierId).filter(isNotNull); + validNotifications = validNotifications.filter(x => x.noteId == null || packedNotes.has(x.noteId)); + + const userIds = validNotifications.map(x => x.notifierId).filter(isNotNull); const users = userIds.length > 0 ? await this.usersRepository.find({ where: { id: In(userIds) }, - relations: ['avatar', 'banner'], }) : []; const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }, { detail: false, }); const packedUsers = new Map(packedUsersArray.map(p => [p.id, p])); - return await Promise.all(notifications.map(x => this.pack(x, meId, {}, { + return await Promise.all(validNotifications.map(x => this.pack(x, meId, {}, { packedNotes, packedUsers, }))); diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index f2f5e4b582..6d8a4dc14e 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -269,27 +269,6 @@ export class UserEntityService implements OnModuleInit { ); } - @bindThis - public async getAvatarUrl(user: User): Promise { - if (user.avatar) { - return this.driveFileEntityService.getPublicUrl(user.avatar, 'avatar') ?? this.getIdenticonUrl(user); - } else if (user.avatarId) { - const avatar = await this.driveFilesRepository.findOneByOrFail({ id: user.avatarId }); - return this.driveFileEntityService.getPublicUrl(avatar, 'avatar') ?? this.getIdenticonUrl(user); - } else { - return this.getIdenticonUrl(user); - } - } - - @bindThis - public getAvatarUrlSync(user: User): string { - if (user.avatar) { - return this.driveFileEntityService.getPublicUrl(user.avatar, 'avatar') ?? this.getIdenticonUrl(user); - } else { - return this.getIdenticonUrl(user); - } - } - @bindThis public getIdenticonUrl(user: User): string { return `${this.config.url}/identicon/${user.username.toLowerCase()}@${user.host ?? this.config.host}`; @@ -309,19 +288,23 @@ export class UserEntityService implements OnModuleInit { includeSecrets: false, }, options); - let user: User; + const user = typeof src === 'object' ? src : await this.usersRepository.findOneByOrFail({ id: src }); - if (typeof src === 'object') { - user = src; - if (src.avatar === undefined && src.avatarId) src.avatar = await this.driveFilesRepository.findOneBy({ id: src.avatarId }) ?? null; - if (src.banner === undefined && src.bannerId) src.banner = await this.driveFilesRepository.findOneBy({ id: src.bannerId }) ?? null; - } else { - user = await this.usersRepository.findOneOrFail({ - where: { id: src }, - relations: { - avatar: true, - banner: true, - }, + // migration + if (user.avatarId != null && user.avatarUrl === null) { + const avatar = await this.driveFilesRepository.findOneByOrFail({ id: user.avatarId }); + user.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar'); + this.usersRepository.update(user.id, { + avatarUrl: user.avatarUrl, + avatarBlurhash: avatar.blurhash, + }); + } + if (user.bannerId != null && user.bannerUrl === null) { + const banner = await this.driveFilesRepository.findOneByOrFail({ id: user.bannerId }); + user.bannerUrl = this.driveFileEntityService.getPublicUrl(banner); + this.usersRepository.update(user.id, { + bannerUrl: user.bannerUrl, + bannerBlurhash: banner.blurhash, }); } @@ -356,8 +339,8 @@ export class UserEntityService implements OnModuleInit { name: user.name, username: user.username, host: user.host, - avatarUrl: this.getAvatarUrlSync(user), - avatarBlurhash: user.avatar?.blurhash ?? null, + avatarUrl: user.avatarUrl ?? this.getIdenticonUrl(user), + avatarBlurhash: user.avatarBlurhash, isBot: user.isBot ?? falsy, isCat: user.isCat ?? falsy, instance: user.host ? this.userInstanceCache.fetch(user.host, @@ -386,8 +369,8 @@ export class UserEntityService implements OnModuleInit { createdAt: user.createdAt.toISOString(), updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null, lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null, - bannerUrl: user.banner ? this.driveFileEntityService.getPublicUrl(user.banner) : null, - bannerBlurhash: user.banner?.blurhash ?? null, + bannerUrl: user.bannerUrl, + bannerBlurhash: user.bannerBlurhash, isLocked: user.isLocked, isSilenced: this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote), isSuspended: user.isSuspended ?? falsy, diff --git a/packages/backend/src/models/entities/User.ts b/packages/backend/src/models/entities/User.ts index 0a8b89ea06..ca53c57d5a 100644 --- a/packages/backend/src/models/entities/User.ts +++ b/packages/backend/src/models/entities/User.ts @@ -100,6 +100,26 @@ export class User { @JoinColumn() public banner: DriveFile | null; + @Column('varchar', { + length: 512, nullable: true, + }) + public avatarUrl: string | null; + + @Column('varchar', { + length: 512, nullable: true, + }) + public bannerUrl: string | null; + + @Column('varchar', { + length: 128, nullable: true, + }) + public avatarBlurhash: string | null; + + @Column('varchar', { + length: 128, nullable: true, + }) + public bannerBlurhash: string | null; + @Index() @Column('varchar', { length: 128, array: true, default: '{}', diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 3f116845cb..9257fee13e 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -149,13 +149,12 @@ export class ServerService implements OnApplicationShutdown { host: (host == null) || (host === this.config.host) ? IsNull() : host, isSuspended: false, }, - relations: ['avatar'], }); reply.header('Cache-Control', 'public, max-age=86400'); if (user) { - reply.redirect(this.userEntityService.getAvatarUrlSync(user)); + reply.redirect(user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user)); } else { reply.redirect('/static-assets/user-unknown.png'); } diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 364f9d9c05..f08c20ae48 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -95,16 +95,10 @@ export default class extends Endpoint { const query = this.notesRepository.createQueryBuilder('note') .where('note.id IN (:...noteIds)', { noteIds: noteIds }) .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); this.queryService.generateMutedUserQuery(query, me); diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index eef343d139..dfc2a582dc 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -93,16 +93,10 @@ export default class extends Endpoint { const query = this.notesRepository.createQueryBuilder('note') .where('note.id IN (:...noteIds)', { noteIds: noteIds }) .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') .leftJoinAndSelect('note.channel', 'channel'); if (me) { diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts index 6818d31cc4..dcb415b752 100644 --- a/packages/backend/src/server/api/endpoints/clips/notes.ts +++ b/packages/backend/src/server/api/endpoints/clips/notes.ts @@ -75,16 +75,10 @@ export default class extends Endpoint { const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) .innerJoin(this.clipNotesRepository.metadata.targetName, 'clipNote', 'clipNote.noteId = note.id') .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') .andWhere('clipNote.clipId = :clipId', { clipId: clip.id }); if (me) { diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index c20f2b7913..be1c72b207 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -19,6 +19,7 @@ import { HashtagService } from '@/core/HashtagService.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; import { CacheService } from '@/core/CacheService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -148,6 +149,7 @@ export default class extends Endpoint { private pagesRepository: PagesRepository, private userEntityService: UserEntityService, + private driveFileEntityService: DriveFileEntityService, private globalEventService: GlobalEventService, private userFollowingService: UserFollowingService, private accountUpdateService: AccountUpdateService, @@ -170,8 +172,6 @@ export default class extends Endpoint { if (ps.location !== undefined) profileUpdates.location = ps.location; if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday; if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility; - if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId; - if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId; if (ps.mutedWords !== undefined) { // TODO: ちゃんと数える const length = JSON.stringify(ps.mutedWords).length; @@ -217,6 +217,10 @@ export default class extends Endpoint { if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar); if (!avatar.type.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage); + + updates.avatarId = avatar.id; + updates.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar'); + updates.avatarBlurhash = avatar.blurhash; } if (ps.bannerId) { @@ -224,6 +228,10 @@ export default class extends Endpoint { if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner); if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage); + + updates.bannerId = banner.id; + updates.bannerUrl = this.driveFileEntityService.getPublicUrl(banner); + updates.bannerBlurhash = banner.blurhash; } if (ps.pinnedPageId) { diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts index 0a8f2292ac..5fbc7aba58 100644 --- a/packages/backend/src/server/api/endpoints/notes.ts +++ b/packages/backend/src/server/api/endpoints/notes.ts @@ -49,16 +49,10 @@ export default class extends Endpoint { .andWhere('note.visibility = \'public\'') .andWhere('note.localOnly = FALSE') .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + .leftJoinAndSelect('renote.user', 'renoteUser'); if (ps.local) { query.andWhere('note.userHost IS NULL'); diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts index ea7a825f9d..26f2d6772d 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -57,16 +57,10 @@ export default class extends Endpoint { })); })) .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); if (me) { diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index 6bf17b222a..bdb06498bc 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -53,16 +53,10 @@ export default class extends Endpoint { .andWhere('note.createdAt > :date', { date: new Date(Date.now() - day) }) .andWhere('note.visibility = \'public\'') .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + .leftJoinAndSelect('renote.user', 'renoteUser'); if (ps.channelId) query.andWhere('note.channelId = :channelId', { channelId: ps.channelId }); diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 9118d33936..c11c1eac40 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -73,16 +73,10 @@ export default class extends Endpoint { .andWhere('note.visibility = \'public\'') .andWhere('note.channelId IS NULL') .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateRepliesQuery(query, me); if (me) { diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 8a7ec65ab4..89abd91c7e 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -91,16 +91,10 @@ export default class extends Endpoint { .orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)'); })) .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') .setParameters(followingQuery.getParameters()); this.queryService.generateChannelQuery(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index 8c1c07a9f4..afdafc7c55 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -80,16 +80,10 @@ export default class extends Endpoint { .andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで .andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)') .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateChannelQuery(query, me); this.queryService.generateRepliesQuery(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index dcb0d0adcb..4e9f604d8d 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -60,16 +60,10 @@ export default class extends Endpoint { .orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`); })) .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); this.queryService.generateMutedUserQuery(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index f758bfe9b1..4772c4f809 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -75,7 +75,7 @@ export default class extends Endpoint { order: { id: -1, }, - relations: ['user', 'user.avatar', 'user.banner', 'note'], + relations: ['user', 'note'], }); return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me))); diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index 026a1baa3e..d406855660 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -4,8 +4,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../error.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['notes'], @@ -62,16 +62,10 @@ export default class extends Endpoint { const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) .andWhere('note.renoteId = :renoteId', { renoteId: note.id }) .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); if (me) this.queryService.generateMutedUserQuery(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts index 4df95962c8..f2af71d55f 100644 --- a/packages/backend/src/server/api/endpoints/notes/replies.ts +++ b/packages/backend/src/server/api/endpoints/notes/replies.ts @@ -46,16 +46,10 @@ export default class extends Endpoint { const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) .andWhere('note.replyId = :replyId', { replyId: ps.noteId }) .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); if (me) this.queryService.generateMutedUserQuery(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts index da1a4bcc46..2956bf1cbd 100644 --- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts +++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts @@ -71,16 +71,10 @@ export default class extends Endpoint { super(meta, paramDef, async (ps, me) => { const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); if (me) this.queryService.generateMutedUserQuery(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index 5db5b6267f..fb5abd917f 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -85,16 +85,10 @@ export default class extends Endpoint { query .andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }) .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); if (me) this.queryService.generateMutedUserQuery(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index d9e72d2603..c6ee1e5c2b 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -70,16 +70,10 @@ export default class extends Endpoint { ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + .leftJoinAndSelect('renote.user', 'renoteUser'); if (followees.length > 0) { const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)]; diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index 9b23103fd4..afc9bc4213 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -84,16 +84,10 @@ export default class extends Endpoint { const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) .innerJoin(this.userListJoiningsRepository.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId') .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') .andWhere('userListJoining.userListId = :userListId', { userListId: list.id }); this.queryService.generateVisibilityQuery(query, me); diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index aab32cc58c..aaf94734a3 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -74,16 +74,10 @@ export default class extends Endpoint { const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('note.userId = :userId', { userId: user.id }) .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); if (me) { diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index fb76f07e48..99ae1b7af6 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -419,7 +419,7 @@ export class ClientServerService { reply.header('Cache-Control', 'public, max-age=15'); return await reply.view('user', { user, profile, me, - avatarUrl: await this.userEntityService.getAvatarUrl(user), + avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user), sub: request.params.sub, instanceName: meta.name ?? 'Misskey', icon: meta.iconUrl, @@ -464,7 +464,7 @@ export class ClientServerService { return await reply.view('note', { note: _note, profile, - avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: note.userId })), + avatarUrl: _note.user.avatarUrl, // TODO: Let locale changeable by instance setting summary: getNoteSummary(_note), instanceName: meta.name ?? 'Misskey', @@ -503,7 +503,7 @@ export class ClientServerService { return await reply.view('page', { page: _page, profile, - avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: page.userId })), + avatarUrl: _page.user.avatarUrl, instanceName: meta.name ?? 'Misskey', icon: meta.iconUrl, themeColor: meta.themeColor, @@ -527,7 +527,7 @@ export class ClientServerService { return await reply.view('flash', { flash: _flash, profile, - avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: flash.userId })), + avatarUrl: _flash.user.avatarUrl, instanceName: meta.name ?? 'Misskey', icon: meta.iconUrl, themeColor: meta.themeColor, @@ -551,7 +551,7 @@ export class ClientServerService { return await reply.view('clip', { clip: _clip, profile, - avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: clip.userId })), + avatarUrl: _clip.user.avatarUrl, instanceName: meta.name ?? 'Misskey', icon: meta.iconUrl, themeColor: meta.themeColor, @@ -573,7 +573,7 @@ export class ClientServerService { return await reply.view('gallery-post', { post: _post, profile, - avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: post.userId })), + avatarUrl: _post.user.avatarUrl, instanceName: meta.name ?? 'Misskey', icon: meta.iconUrl, themeColor: meta.themeColor, diff --git a/packages/backend/src/server/web/FeedService.ts b/packages/backend/src/server/web/FeedService.ts index a14609adf9..0c0e92cc04 100644 --- a/packages/backend/src/server/web/FeedService.ts +++ b/packages/backend/src/server/web/FeedService.ts @@ -58,7 +58,7 @@ export class FeedService { generator: 'Misskey', description: `${user.notesCount} Notes, ${profile.ffVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.ffVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`, link: author.link, - image: await this.userEntityService.getAvatarUrl(user), + image: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user), feedLinks: { json: `${author.link}.json`, atom: `${author.link}.atom`, diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue index 60f61ed293..7e0696f8bc 100644 --- a/packages/frontend/src/pages/about-misskey.vue +++ b/packages/frontend/src/pages/about-misskey.vue @@ -217,6 +217,7 @@ const patrons = [ '氷月氷華里', 'Ebise Lutica', '巣黒るい@リスケモ男の娘VTuber!', + 'ふぇいぽむ', ]; let thereIsTreasure = $ref($i && !claimedAchievements.includes('foundTreasure'));