Merge branch 'misskey-dev:develop' into notes-embed

This commit is contained in:
kakkokari-gtyih 2023-04-06 22:19:38 +09:00 committed by GitHub
commit bdcaf7a998
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 1027 additions and 1051 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "13.11.0.beta-3", "version": "13.11.0-beta.4",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",
@ -50,13 +50,13 @@
"gulp-replace": "1.1.4", "gulp-replace": "1.1.4",
"gulp-terser": "2.1.0", "gulp-terser": "2.1.0",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"typescript": "5.0.2" "typescript": "5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@types/gulp": "4.0.10", "@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1", "@types/gulp-rename": "2.0.1",
"@typescript-eslint/eslint-plugin": "5.57.0", "@typescript-eslint/eslint-plugin": "5.57.1",
"@typescript-eslint/parser": "5.57.0", "@typescript-eslint/parser": "5.57.1",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "12.9.0", "cypress": "12.9.0",
"eslint": "8.37.0", "eslint": "8.37.0",

View File

@ -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"`);
}
}

View File

@ -22,24 +22,24 @@
"test-and-coverage": "pnpm jest-and-coverage" "test-and-coverage": "pnpm jest-and-coverage"
}, },
"optionalDependencies": { "optionalDependencies": {
"@swc/core-android-arm64": "^1.3.11", "@swc/core-android-arm64": "1.3.11",
"@swc/core-darwin-arm64": "^1.3.42", "@swc/core-darwin-arm64": "1.3.46",
"@swc/core-darwin-x64": "^1.3.42", "@swc/core-darwin-x64": "1.3.46",
"@swc/core-linux-arm-gnueabihf": "^1.3.42", "@swc/core-linux-arm-gnueabihf": "1.3.46",
"@swc/core-linux-arm64-gnu": "^1.3.42", "@swc/core-linux-arm64-gnu": "1.3.46",
"@swc/core-linux-arm64-musl": "^1.3.42", "@swc/core-linux-arm64-musl": "1.3.46",
"@swc/core-linux-x64-gnu": "^1.3.42", "@swc/core-linux-x64-gnu": "1.3.46",
"@swc/core-linux-x64-musl": "^1.3.42", "@swc/core-linux-x64-musl": "1.3.46",
"@swc/core-win32-arm64-msvc": "^1.3.42", "@swc/core-win32-arm64-msvc": "1.3.46",
"@swc/core-win32-ia32-msvc": "^1.3.42", "@swc/core-win32-ia32-msvc": "1.3.46",
"@swc/core-win32-x64-msvc": "^1.3.42", "@swc/core-win32-x64-msvc": "1.3.46",
"@tensorflow/tfjs": "4.2.0", "@tensorflow/tfjs": "4.2.0",
"@tensorflow/tfjs-node": "4.2.0" "@tensorflow/tfjs-node": "4.2.0"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "3.301.0", "@aws-sdk/client-s3": "3.306.0",
"@aws-sdk/lib-storage": "3.301.0", "@aws-sdk/lib-storage": "3.306.0",
"@aws-sdk/node-http-handler": "3.296.0", "@aws-sdk/node-http-handler": "3.306.0",
"@bull-board/api": "5.0.0", "@bull-board/api": "5.0.0",
"@bull-board/fastify": "5.0.0", "@bull-board/fastify": "5.0.0",
"@bull-board/ui": "5.0.0", "@bull-board/ui": "5.0.0",
@ -49,15 +49,15 @@
"@fastify/cors": "8.2.1", "@fastify/cors": "8.2.1",
"@fastify/http-proxy": "9.0.0", "@fastify/http-proxy": "9.0.0",
"@fastify/multipart": "7.5.0", "@fastify/multipart": "7.5.0",
"@fastify/static": "6.9.0", "@fastify/static": "6.10.0",
"@fastify/view": "7.4.1", "@fastify/view": "7.4.1",
"@nestjs/common": "9.3.12", "@nestjs/common": "9.4.0",
"@nestjs/core": "9.3.12", "@nestjs/core": "9.4.0",
"@nestjs/testing": "9.3.12", "@nestjs/testing": "9.4.0",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@sinonjs/fake-timers": "10.0.2", "@sinonjs/fake-timers": "10.0.2",
"@swc/cli": "0.1.62", "@swc/cli": "0.1.62",
"@swc/core": "1.3.42", "@swc/core": "1.3.46",
"accepts": "1.3.8", "accepts": "1.3.8",
"ajv": "8.12.0", "ajv": "8.12.0",
"archiver": "5.3.1", "archiver": "5.3.1",
@ -136,8 +136,8 @@
"tsc-alias": "1.8.5", "tsc-alias": "1.8.5",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",
"typeorm": "0.3.11", "typeorm": "0.3.13",
"typescript": "5.0.2", "typescript": "5.0.3",
"ulid": "2.3.0", "ulid": "2.3.0",
"unzipper": "0.10.11", "unzipper": "0.10.11",
"uuid": "9.0.0", "uuid": "9.0.0",
@ -190,8 +190,8 @@
"@types/web-push": "3.3.2", "@types/web-push": "3.3.2",
"@types/websocket": "1.0.5", "@types/websocket": "1.0.5",
"@types/ws": "8.5.4", "@types/ws": "8.5.4",
"@typescript-eslint/eslint-plugin": "5.57.0", "@typescript-eslint/eslint-plugin": "5.57.1",
"@typescript-eslint/parser": "5.57.0", "@typescript-eslint/parser": "5.57.1",
"aws-sdk-client-mock": "^2.1.1", "aws-sdk-client-mock": "^2.1.1",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "8.37.0", "eslint": "8.37.0",

View File

@ -31,6 +31,7 @@ import type { UtilityService } from '@/core/UtilityService.js';
import type { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.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 { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js';
import { extractApHashtags } from './tag.js'; import { extractApHashtags } from './tag.js';
import type { OnModuleInit } from '@nestjs/common'; import type { OnModuleInit } from '@nestjs/common';
@ -49,6 +50,7 @@ const summaryLength = 2048;
export class ApPersonService implements OnModuleInit { export class ApPersonService implements OnModuleInit {
private utilityService: UtilityService; private utilityService: UtilityService;
private userEntityService: UserEntityService; private userEntityService: UserEntityService;
private driveFileEntityService: DriveFileEntityService;
private idService: IdService; private idService: IdService;
private globalEventService: GlobalEventService; private globalEventService: GlobalEventService;
private metaService: MetaService; private metaService: MetaService;
@ -113,6 +115,7 @@ export class ApPersonService implements OnModuleInit {
onModuleInit() { onModuleInit() {
this.utilityService = this.moduleRef.get('UtilityService'); this.utilityService = this.moduleRef.get('UtilityService');
this.userEntityService = this.moduleRef.get('UserEntityService'); this.userEntityService = this.moduleRef.get('UserEntityService');
this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService');
this.idService = this.moduleRef.get('IdService'); this.idService = this.moduleRef.get('IdService');
this.globalEventService = this.moduleRef.get('GlobalEventService'); this.globalEventService = this.moduleRef.get('GlobalEventService');
this.metaService = this.moduleRef.get('MetaService'); this.metaService = this.moduleRef.get('MetaService');
@ -356,14 +359,26 @@ export class ApPersonService implements OnModuleInit {
const avatarId = avatar ? avatar.id : null; const avatarId = avatar ? avatar.id : null;
const bannerId = banner ? banner.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, { await this.usersRepository.update(user!.id, {
avatarId, avatarId,
bannerId, bannerId,
avatarUrl,
bannerUrl,
avatarBlurhash,
bannerBlurhash,
}); });
user!.avatarId = avatarId; user!.avatarId = avatarId;
user!.bannerId = bannerId; user!.bannerId = bannerId;
user!.avatarUrl = avatarUrl;
user!.bannerUrl = bannerUrl;
user!.avatarBlurhash = avatarBlurhash;
user!.bannerBlurhash = bannerBlurhash;
//#endregion //#endregion
//#region カスタム絵文字取得 //#region カスタム絵文字取得
@ -463,10 +478,14 @@ export class ApPersonService implements OnModuleInit {
if (avatar) { if (avatar) {
updates.avatarId = avatar.id; updates.avatarId = avatar.id;
updates.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar');
updates.avatarBlurhash = avatar.blurhash;
} }
if (banner) { if (banner) {
updates.bannerId = banner.id; updates.bannerId = banner.id;
updates.bannerUrl = this.driveFileEntityService.getPublicUrl(banner);
updates.bannerBlurhash = banner.blurhash;
} }
// Update user // Update user

View File

@ -266,6 +266,7 @@ export class DriveFileEntityService {
fileIds: DriveFile['id'][], fileIds: DriveFile['id'][],
options?: PackOptions, options?: PackOptions,
): Promise<Map<Packed<'DriveFile'>['id'], Packed<'DriveFile'> | null>> { ): Promise<Map<Packed<'DriveFile'>['id'], Packed<'DriveFile'> | null>> {
if (fileIds.length === 0) return new Map();
const files = await this.driveFilesRepository.findBy({ id: In(fileIds) }); const files = await this.driveFilesRepository.findBy({ id: In(fileIds) });
const packedFiles = await this.packMany(files, options); const packedFiles = await this.packMany(files, options);
const map = new Map<Packed<'DriveFile'>['id'], Packed<'DriveFile'> | null>(packedFiles.map(f => [f.id, f])); const map = new Map<Packed<'DriveFile'>['id'], Packed<'DriveFile'> | null>(packedFiles.map(f => [f.id, f]));
@ -280,6 +281,7 @@ export class DriveFileEntityService {
fileIds: DriveFile['id'][], fileIds: DriveFile['id'][],
options?: PackOptions, options?: PackOptions,
): Promise<Packed<'DriveFile'>[]> { ): Promise<Packed<'DriveFile'>[]> {
if (fileIds.length === 0) return [];
const filesMap = await this.packManyByIdsMap(fileIds, options); const filesMap = await this.packManyByIdsMap(fileIds, options);
return fileIds.map(id => filesMap.get(id)).filter(isNotNull); return fileIds.map(id => filesMap.get(id)).filter(isNotNull);
} }

View File

@ -183,6 +183,11 @@ export class NoteEntityService implements OnModuleInit {
// 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない // 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない
} }
// パフォーマンスのためートが作成されてから1秒以上経っていない場合はリアクションを取得しない
if (note.createdAt.getTime() + 1000 > Date.now()) {
return undefined;
}
const reaction = await this.noteReactionsRepository.findOneBy({ const reaction = await this.noteReactionsRepository.findOneBy({
userId: meId, userId: meId,
noteId: note.id, noteId: note.id,
@ -395,7 +400,8 @@ export class NoteEntityService implements OnModuleInit {
const myReactionsMap = new Map<Note['id'], NoteReaction | null>(); const myReactionsMap = new Map<Note['id'], NoteReaction | null>();
if (meId) { if (meId) {
const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!); 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({ const myReactions = await this.noteReactionsRepository.findBy({
userId: meId, userId: meId,
noteId: In(targets), noteId: In(targets),
@ -409,7 +415,7 @@ export class NoteEntityService implements OnModuleInit {
await this.customEmojiService.prefetchEmojis(this.aggregateNoteEmojis(notes)); await this.customEmojiService.prefetchEmojis(this.aggregateNoteEmojis(notes));
// TODO: 本当は renote とか reply がないのに renoteId とか replyId があったらここで解決しておく // TODO: 本当は renote とか reply がないのに renoteId とか replyId があったらここで解決しておく
const fileIds = notes.map(n => [n.fileIds, n.renote?.fileIds, n.reply?.fileIds]).flat(2).filter(isNotNull); 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, { return await Promise.all(notes.map(n => this.pack(n, me, {
...options, ...options,

View File

@ -108,27 +108,30 @@ export class NotificationEntityService implements OnModuleInit {
) { ) {
if (notifications.length === 0) return []; 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({ const notes = noteIds.length > 0 ? await this.notesRepository.find({
where: { id: In(noteIds) }, 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 }, { const packedNotesArray = await this.noteEntityService.packMany(notes, { id: meId }, {
detail: true, detail: true,
}); });
const packedNotes = new Map(packedNotesArray.map(p => [p.id, p])); 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({ const users = userIds.length > 0 ? await this.usersRepository.find({
where: { id: In(userIds) }, where: { id: In(userIds) },
relations: ['avatar', 'banner'],
}) : []; }) : [];
const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }, { const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }, {
detail: false, detail: false,
}); });
const packedUsers = new Map(packedUsersArray.map(p => [p.id, p])); 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, packedNotes,
packedUsers, packedUsers,
}))); })));

View File

@ -269,27 +269,6 @@ export class UserEntityService implements OnModuleInit {
); );
} }
@bindThis
public async getAvatarUrl(user: User): Promise<string> {
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 @bindThis
public getIdenticonUrl(user: User): string { public getIdenticonUrl(user: User): string {
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}`;
@ -309,19 +288,23 @@ export class UserEntityService implements OnModuleInit {
includeSecrets: false, includeSecrets: false,
}, options); }, options);
let user: User; const user = typeof src === 'object' ? src : await this.usersRepository.findOneByOrFail({ id: src });
if (typeof src === 'object') { // migration
user = src; if (user.avatarId != null && user.avatarUrl === null) {
if (src.avatar === undefined && src.avatarId) src.avatar = await this.driveFilesRepository.findOneBy({ id: src.avatarId }) ?? null; const avatar = await this.driveFilesRepository.findOneByOrFail({ id: user.avatarId });
if (src.banner === undefined && src.bannerId) src.banner = await this.driveFilesRepository.findOneBy({ id: src.bannerId }) ?? null; user.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar');
} else { this.usersRepository.update(user.id, {
user = await this.usersRepository.findOneOrFail({ avatarUrl: user.avatarUrl,
where: { id: src }, avatarBlurhash: avatar.blurhash,
relations: { });
avatar: true, }
banner: true, 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, name: user.name,
username: user.username, username: user.username,
host: user.host, host: user.host,
avatarUrl: this.getAvatarUrlSync(user), avatarUrl: user.avatarUrl ?? this.getIdenticonUrl(user),
avatarBlurhash: user.avatar?.blurhash ?? null, avatarBlurhash: user.avatarBlurhash,
isBot: user.isBot ?? falsy, isBot: user.isBot ?? falsy,
isCat: user.isCat ?? falsy, isCat: user.isCat ?? falsy,
instance: user.host ? this.userInstanceCache.fetch(user.host, instance: user.host ? this.userInstanceCache.fetch(user.host,
@ -386,8 +369,8 @@ export class UserEntityService implements OnModuleInit {
createdAt: user.createdAt.toISOString(), createdAt: user.createdAt.toISOString(),
updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null, updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null,
lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null, lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null,
bannerUrl: user.banner ? this.driveFileEntityService.getPublicUrl(user.banner) : null, bannerUrl: user.bannerUrl,
bannerBlurhash: user.banner?.blurhash ?? null, bannerBlurhash: user.bannerBlurhash,
isLocked: user.isLocked, isLocked: user.isLocked,
isSilenced: this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote), isSilenced: this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote),
isSuspended: user.isSuspended ?? falsy, isSuspended: user.isSuspended ?? falsy,

View File

@ -100,6 +100,26 @@ export class User {
@JoinColumn() @JoinColumn()
public banner: DriveFile | null; 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() @Index()
@Column('varchar', { @Column('varchar', {
length: 128, array: true, default: '{}', length: 128, array: true, default: '{}',

View File

@ -149,13 +149,12 @@ export class ServerService implements OnApplicationShutdown {
host: (host == null) || (host === this.config.host) ? IsNull() : host, host: (host == null) || (host === this.config.host) ? IsNull() : host,
isSuspended: false, isSuspended: false,
}, },
relations: ['avatar'],
}); });
reply.header('Cache-Control', 'public, max-age=86400'); reply.header('Cache-Control', 'public, max-age=86400');
if (user) { if (user) {
reply.redirect(this.userEntityService.getAvatarUrlSync(user)); reply.redirect(user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user));
} else { } else {
reply.redirect('/static-assets/user-unknown.png'); reply.redirect('/static-assets/user-unknown.png');
} }

View File

@ -95,16 +95,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const query = this.notesRepository.createQueryBuilder('note') const query = this.notesRepository.createQueryBuilder('note')
.where('note.id IN (:...noteIds)', { noteIds: noteIds }) .where('note.id IN (:...noteIds)', { noteIds: noteIds })
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') .leftJoinAndSelect('renote.user', 'renoteUser');
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
this.queryService.generateVisibilityQuery(query, me); this.queryService.generateVisibilityQuery(query, me);
this.queryService.generateMutedUserQuery(query, me); this.queryService.generateMutedUserQuery(query, me);

View File

@ -93,16 +93,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const query = this.notesRepository.createQueryBuilder('note') const query = this.notesRepository.createQueryBuilder('note')
.where('note.id IN (:...noteIds)', { noteIds: noteIds }) .where('note.id IN (:...noteIds)', { noteIds: noteIds })
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser') .leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
.leftJoinAndSelect('note.channel', 'channel'); .leftJoinAndSelect('note.channel', 'channel');
if (me) { if (me) {

View File

@ -75,16 +75,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.innerJoin(this.clipNotesRepository.metadata.targetName, 'clipNote', 'clipNote.noteId = note.id') .innerJoin(this.clipNotesRepository.metadata.targetName, 'clipNote', 'clipNote.noteId = note.id')
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser') .leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
.andWhere('clipNote.clipId = :clipId', { clipId: clip.id }); .andWhere('clipNote.clipId = :clipId', { clipId: clip.id });
if (me) { if (me) {

View File

@ -19,6 +19,7 @@ import { HashtagService } from '@/core/HashtagService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
import { CacheService } from '@/core/CacheService.js'; import { CacheService } from '@/core/CacheService.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import { ApiError } from '../../error.js'; import { ApiError } from '../../error.js';
export const meta = { export const meta = {
@ -148,6 +149,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private pagesRepository: PagesRepository, private pagesRepository: PagesRepository,
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private driveFileEntityService: DriveFileEntityService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private userFollowingService: UserFollowingService, private userFollowingService: UserFollowingService,
private accountUpdateService: AccountUpdateService, private accountUpdateService: AccountUpdateService,
@ -170,8 +172,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
if (ps.location !== undefined) profileUpdates.location = ps.location; if (ps.location !== undefined) profileUpdates.location = ps.location;
if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday; if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility; 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) { if (ps.mutedWords !== undefined) {
// TODO: ちゃんと数える // TODO: ちゃんと数える
const length = JSON.stringify(ps.mutedWords).length; const length = JSON.stringify(ps.mutedWords).length;
@ -217,6 +217,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar); if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar);
if (!avatar.type.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage); 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) { if (ps.bannerId) {
@ -224,6 +228,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner); if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner);
if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage); 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) { if (ps.pinnedPageId) {

View File

@ -49,16 +49,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
.andWhere('note.visibility = \'public\'') .andWhere('note.visibility = \'public\'')
.andWhere('note.localOnly = FALSE') .andWhere('note.localOnly = FALSE')
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') .leftJoinAndSelect('renote.user', 'renoteUser');
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
if (ps.local) { if (ps.local) {
query.andWhere('note.userHost IS NULL'); query.andWhere('note.userHost IS NULL');

View File

@ -57,16 +57,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
})); }));
})) }))
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') .leftJoinAndSelect('renote.user', 'renoteUser');
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
this.queryService.generateVisibilityQuery(query, me); this.queryService.generateVisibilityQuery(query, me);
if (me) { if (me) {

View File

@ -53,16 +53,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
.andWhere('note.createdAt > :date', { date: new Date(Date.now() - day) }) .andWhere('note.createdAt > :date', { date: new Date(Date.now() - day) })
.andWhere('note.visibility = \'public\'') .andWhere('note.visibility = \'public\'')
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') .leftJoinAndSelect('renote.user', 'renoteUser');
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
if (ps.channelId) query.andWhere('note.channelId = :channelId', { channelId: ps.channelId }); if (ps.channelId) query.andWhere('note.channelId = :channelId', { channelId: ps.channelId });

View File

@ -73,16 +73,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
.andWhere('note.visibility = \'public\'') .andWhere('note.visibility = \'public\'')
.andWhere('note.channelId IS NULL') .andWhere('note.channelId IS NULL')
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') .leftJoinAndSelect('renote.user', 'renoteUser');
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
this.queryService.generateRepliesQuery(query, me); this.queryService.generateRepliesQuery(query, me);
if (me) { if (me) {

View File

@ -91,16 +91,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)'); .orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
})) }))
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser') .leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
.setParameters(followingQuery.getParameters()); .setParameters(followingQuery.getParameters());
this.queryService.generateChannelQuery(query, me); this.queryService.generateChannelQuery(query, me);

View File

@ -80,16 +80,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
.andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで .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)') .andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)')
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') .leftJoinAndSelect('renote.user', 'renoteUser');
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
this.queryService.generateChannelQuery(query, me); this.queryService.generateChannelQuery(query, me);
this.queryService.generateRepliesQuery(query, me); this.queryService.generateRepliesQuery(query, me);

View File

@ -60,16 +60,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
.orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`); .orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`);
})) }))
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') .leftJoinAndSelect('renote.user', 'renoteUser');
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
this.queryService.generateVisibilityQuery(query, me); this.queryService.generateVisibilityQuery(query, me);
this.queryService.generateMutedUserQuery(query, me); this.queryService.generateMutedUserQuery(query, me);

View File

@ -75,7 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
order: { order: {
id: -1, id: -1,
}, },
relations: ['user', 'user.avatar', 'user.banner', 'note'], relations: ['user', 'note'],
}); });
return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me))); return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me)));

View File

@ -4,8 +4,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import { QueryService } from '@/core/QueryService.js'; import { QueryService } from '@/core/QueryService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
import { GetterService } from '@/server/api/GetterService.js'; import { GetterService } from '@/server/api/GetterService.js';
import { ApiError } from '../../error.js';
export const meta = { export const meta = {
tags: ['notes'], tags: ['notes'],
@ -62,16 +62,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere('note.renoteId = :renoteId', { renoteId: note.id }) .andWhere('note.renoteId = :renoteId', { renoteId: note.id })
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') .leftJoinAndSelect('renote.user', 'renoteUser');
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
this.queryService.generateVisibilityQuery(query, me); this.queryService.generateVisibilityQuery(query, me);
if (me) this.queryService.generateMutedUserQuery(query, me); if (me) this.queryService.generateMutedUserQuery(query, me);

View File

@ -46,16 +46,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere('note.replyId = :replyId', { replyId: ps.noteId }) .andWhere('note.replyId = :replyId', { replyId: ps.noteId })
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') .leftJoinAndSelect('renote.user', 'renoteUser');
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
this.queryService.generateVisibilityQuery(query, me); this.queryService.generateVisibilityQuery(query, me);
if (me) this.queryService.generateMutedUserQuery(query, me); if (me) this.queryService.generateMutedUserQuery(query, me);

View File

@ -71,16 +71,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') .leftJoinAndSelect('renote.user', 'renoteUser');
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
this.queryService.generateVisibilityQuery(query, me); this.queryService.generateVisibilityQuery(query, me);
if (me) this.queryService.generateMutedUserQuery(query, me); if (me) this.queryService.generateMutedUserQuery(query, me);

View File

@ -85,16 +85,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
query query
.andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }) .andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` })
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') .leftJoinAndSelect('renote.user', 'renoteUser');
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
this.queryService.generateVisibilityQuery(query, me); this.queryService.generateVisibilityQuery(query, me);
if (me) this.queryService.generateMutedUserQuery(query, me); if (me) this.queryService.generateMutedUserQuery(query, me);

View File

@ -70,16 +70,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) 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日前まで .andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') .leftJoinAndSelect('renote.user', 'renoteUser');
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
if (followees.length > 0) { if (followees.length > 0) {
const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)]; const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)];

View File

@ -84,16 +84,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.innerJoin(this.userListJoiningsRepository.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId') .innerJoin(this.userListJoiningsRepository.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId')
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser') .leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
.andWhere('userListJoining.userListId = :userListId', { userListId: list.id }); .andWhere('userListJoining.userListId = :userListId', { userListId: list.id });
this.queryService.generateVisibilityQuery(query, me); this.queryService.generateVisibilityQuery(query, me);

View File

@ -74,16 +74,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('note.userId = :userId', { userId: user.id }) .andWhere('note.userId = :userId', { userId: user.id })
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') .leftJoinAndSelect('renote.user', 'renoteUser');
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
this.queryService.generateVisibilityQuery(query, me); this.queryService.generateVisibilityQuery(query, me);
if (me) { if (me) {

View File

@ -419,7 +419,7 @@ export class ClientServerService {
reply.header('Cache-Control', 'public, max-age=15'); reply.header('Cache-Control', 'public, max-age=15');
return await reply.view('user', { return await reply.view('user', {
user, profile, me, user, profile, me,
avatarUrl: await this.userEntityService.getAvatarUrl(user), avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
sub: request.params.sub, sub: request.params.sub,
instanceName: meta.name ?? 'Misskey', instanceName: meta.name ?? 'Misskey',
icon: meta.iconUrl, icon: meta.iconUrl,
@ -464,7 +464,7 @@ export class ClientServerService {
return await reply.view('note', { return await reply.view('note', {
note: _note, note: _note,
profile, profile,
avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: note.userId })), avatarUrl: _note.user.avatarUrl,
// TODO: Let locale changeable by instance setting // TODO: Let locale changeable by instance setting
summary: getNoteSummary(_note), summary: getNoteSummary(_note),
instanceName: meta.name ?? 'Misskey', instanceName: meta.name ?? 'Misskey',
@ -511,7 +511,7 @@ export class ClientServerService {
return await reply.view('page', { return await reply.view('page', {
page: _page, page: _page,
profile, profile,
avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: page.userId })), avatarUrl: _page.user.avatarUrl,
instanceName: meta.name ?? 'Misskey', instanceName: meta.name ?? 'Misskey',
icon: meta.iconUrl, icon: meta.iconUrl,
themeColor: meta.themeColor, themeColor: meta.themeColor,
@ -535,7 +535,7 @@ export class ClientServerService {
return await reply.view('flash', { return await reply.view('flash', {
flash: _flash, flash: _flash,
profile, profile,
avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: flash.userId })), avatarUrl: _flash.user.avatarUrl,
instanceName: meta.name ?? 'Misskey', instanceName: meta.name ?? 'Misskey',
icon: meta.iconUrl, icon: meta.iconUrl,
themeColor: meta.themeColor, themeColor: meta.themeColor,
@ -559,7 +559,7 @@ export class ClientServerService {
return await reply.view('clip', { return await reply.view('clip', {
clip: _clip, clip: _clip,
profile, profile,
avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: clip.userId })), avatarUrl: _clip.user.avatarUrl,
instanceName: meta.name ?? 'Misskey', instanceName: meta.name ?? 'Misskey',
icon: meta.iconUrl, icon: meta.iconUrl,
themeColor: meta.themeColor, themeColor: meta.themeColor,
@ -581,7 +581,7 @@ export class ClientServerService {
return await reply.view('gallery-post', { return await reply.view('gallery-post', {
post: _post, post: _post,
profile, profile,
avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: post.userId })), avatarUrl: _post.user.avatarUrl,
instanceName: meta.name ?? 'Misskey', instanceName: meta.name ?? 'Misskey',
icon: meta.iconUrl, icon: meta.iconUrl,
themeColor: meta.themeColor, themeColor: meta.themeColor,

View File

@ -58,7 +58,7 @@ export class FeedService {
generator: 'Misskey', generator: 'Misskey',
description: `${user.notesCount} Notes, ${profile.ffVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.ffVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`, description: `${user.notesCount} Notes, ${profile.ffVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.ffVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`,
link: author.link, link: author.link,
image: await this.userEntityService.getAvatarUrl(user), image: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
feedLinks: { feedLinks: {
json: `${author.link}.json`, json: `${author.link}.json`,
atom: `${author.link}.atom`, atom: `${author.link}.atom`,

View File

@ -59,13 +59,13 @@
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"syuilo-password-strength": "0.0.1", "syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.150.1", "three": "0.151.3",
"throttle-debounce": "5.0.0", "throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tsc-alias": "1.8.5", "tsc-alias": "1.8.5",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",
"typescript": "5.0.2", "typescript": "5.0.3",
"uuid": "9.0.0", "uuid": "9.0.0",
"vanilla-tilt": "1.8.0", "vanilla-tilt": "1.8.0",
"vite": "4.2.1", "vite": "4.2.1",
@ -92,10 +92,10 @@
"@storybook/types": "7.0.2", "@storybook/types": "7.0.2",
"@storybook/vue3": "7.0.2", "@storybook/vue3": "7.0.2",
"@storybook/vue3-vite": "7.0.2", "@storybook/vue3-vite": "7.0.2",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "5.16.5",
"@testing-library/vue": "^6.6.1", "@testing-library/vue": "7.0.0",
"@types/escape-regexp": "0.0.1", "@types/escape-regexp": "0.0.1",
"@types/estree": "^1.0.0", "@types/estree": "1.0.0",
"@types/gulp": "4.0.10", "@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1", "@types/gulp-rename": "2.0.1",
"@types/iframe-resizer": "^3.5.9", "@types/iframe-resizer": "^3.5.9",
@ -110,32 +110,32 @@
"@types/uuid": "9.0.1", "@types/uuid": "9.0.1",
"@types/websocket": "1.0.5", "@types/websocket": "1.0.5",
"@types/ws": "8.5.4", "@types/ws": "8.5.4",
"@typescript-eslint/eslint-plugin": "5.57.0", "@typescript-eslint/eslint-plugin": "5.57.1",
"@typescript-eslint/parser": "5.57.0", "@typescript-eslint/parser": "5.57.1",
"@vitest/coverage-c8": "^0.29.8", "@vitest/coverage-c8": "^0.29.8",
"@vue/runtime-core": "3.2.47", "@vue/runtime-core": "3.2.47",
"astring": "^1.8.4", "astring": "^1.8.4",
"chokidar-cli": "^3.0.0", "chokidar-cli": "^3.0.0",
"chromatic": "^6.17.2", "chromatic": "6.17.3",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "12.9.0", "cypress": "12.9.0",
"eslint": "8.37.0", "eslint": "8.37.0",
"eslint-plugin-import": "2.27.5", "eslint-plugin-import": "2.27.5",
"eslint-plugin-vue": "9.10.0", "eslint-plugin-vue": "9.10.0",
"fast-glob": "^3.2.12", "fast-glob": "3.2.12",
"happy-dom": "8.9.0", "happy-dom": "8.9.0",
"msw": "^1.1.0", "msw": "1.2.1",
"msw-storybook-addon": "^1.8.0", "msw-storybook-addon": "1.8.0",
"prettier": "^2.8.4", "prettier": "2.8.7",
"react": "^18.2.0", "react": "18.2.0",
"react-dom": "^18.2.0", "react-dom": "18.2.0",
"start-server-and-test": "2.0.0", "start-server-and-test": "2.0.0",
"storybook": "7.0.2", "storybook": "7.0.2",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"summaly": "github:misskey-dev/summaly", "summaly": "github:misskey-dev/summaly",
"vitest": "^0.29.8", "vitest": "0.29.8",
"vitest-fetch-mock": "^0.2.2", "vitest-fetch-mock": "0.2.2",
"vue-eslint-parser": "9.1.0", "vue-eslint-parser": "9.1.1",
"vue-tsc": "1.2.0" "vue-tsc": "1.2.0"
} }
} }

View File

@ -217,6 +217,7 @@ const patrons = [
'氷月氷華里', '氷月氷華里',
'Ebise Lutica', 'Ebise Lutica',
'巣黒るい@リスケモ男の娘VTuber!', '巣黒るい@リスケモ男の娘VTuber!',
'ふぇいぽむ',
]; ];
let thereIsTreasure = $ref($i && !claimedAchievements.includes('foundTreasure')); let thereIsTreasure = $ref($i && !claimedAchievements.includes('foundTreasure'));

View File

@ -24,23 +24,23 @@
"@swc/jest": "0.2.24", "@swc/jest": "0.2.24",
"@types/jest": "29.5.0", "@types/jest": "29.5.0",
"@types/node": "18.15.11", "@types/node": "18.15.11",
"@typescript-eslint/eslint-plugin": "5.57.0", "@typescript-eslint/eslint-plugin": "5.57.1",
"@typescript-eslint/parser": "5.57.0", "@typescript-eslint/parser": "5.57.1",
"eslint": "8.37.0", "eslint": "8.37.0",
"jest": "^29.5.0", "jest": "29.5.0",
"jest-fetch-mock": "^3.0.3", "jest-fetch-mock": "3.0.3",
"jest-websocket-mock": "2.4.0", "jest-websocket-mock": "2.4.0",
"mock-socket": "9.2.1", "mock-socket": "9.2.1",
"tsd": "0.28.1", "tsd": "0.28.1",
"typescript": "5.0.2" "typescript": "5.0.3"
}, },
"files": [ "files": [
"built" "built"
], ],
"dependencies": { "dependencies": {
"@swc/cli": "0.1.62", "@swc/cli": "0.1.62",
"@swc/core": "1.3.42", "@swc/core": "1.3.46",
"eventemitter3": "5.0.0", "eventemitter3": "5.0.0",
"reconnecting-websocket": "^4.4.0" "reconnecting-websocket": "4.4.0"
} }
} }

View File

@ -14,10 +14,10 @@
"misskey-js": "workspace:*" "misskey-js": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/parser": "5.57.0", "@typescript-eslint/parser": "5.57.1",
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67", "@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
"eslint": "8.37.0", "eslint": "8.37.0",
"eslint-plugin-import": "2.27.5", "eslint-plugin-import": "2.27.5",
"typescript": "5.0.2" "typescript": "5.0.3"
} }
} }

File diff suppressed because it is too large Load Diff