diff options
| author | syuilo <4439005+syuilo@users.noreply.github.com> | 2024-05-21 17:37:35 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-05-21 17:37:35 +0900 |
| commit | 4a3c01d19890b449f5d1af2e89234c43052b26ba (patch) | |
| tree | f820af1c06b84bcad48c2f45ca2cd974b1e98af4 /packages/backend | |
| parent | Merge pull request #13816 from misskey-dev/develop (diff) | |
| parent | happy-domにメモリリークがありそう (diff) | |
| download | misskey-4a3c01d19890b449f5d1af2e89234c43052b26ba.tar.gz misskey-4a3c01d19890b449f5d1af2e89234c43052b26ba.tar.bz2 misskey-4a3c01d19890b449f5d1af2e89234c43052b26ba.zip | |
Merge branch 'develop' into release/2024.5.0
Diffstat (limited to '')
11 files changed, 107 insertions, 30 deletions
diff --git a/packages/backend/migration/1716129964060-ChannelIdDenormalizedForMiPoll.js b/packages/backend/migration/1716129964060-ChannelIdDenormalizedForMiPoll.js new file mode 100644 index 0000000000..f736378c04 --- /dev/null +++ b/packages/backend/migration/1716129964060-ChannelIdDenormalizedForMiPoll.js @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class ChannelIdDenormalizedForMiPoll1716129964060 { + name = 'ChannelIdDenormalizedForMiPoll1716129964060' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "poll" ADD "channelId" character varying(32)`); + await queryRunner.query(`COMMENT ON COLUMN "poll"."channelId" IS '[Denormalized]'`); + await queryRunner.query(`CREATE INDEX "IDX_c1240fcc9675946ea5d6c2860e" ON "poll" ("channelId") `); + await queryRunner.query(`UPDATE "poll" SET "channelId" = "note"."channelId" FROM "note" WHERE "poll"."noteId" = "note"."id"`); + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_c1240fcc9675946ea5d6c2860e"`); + await queryRunner.query(`COMMENT ON COLUMN "poll"."channelId" IS '[Denormalized]'`); + await queryRunner.query(`ALTER TABLE "poll" DROP COLUMN "channelId"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index 23b3bfdb8b..8e29252d75 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -117,7 +117,7 @@ "fluent-ffmpeg": "2.1.2", "form-data": "4.0.0", "got": "14.2.1", - "happy-dom": "14.7.1", + "happy-dom": "10.0.3", "hpagent": "1.2.0", "htmlescape": "1.1.1", "http-link-header": "1.1.3", diff --git a/packages/backend/src/core/FanoutTimelineEndpointService.ts b/packages/backend/src/core/FanoutTimelineEndpointService.ts index 884723ff81..d5058f37c2 100644 --- a/packages/backend/src/core/FanoutTimelineEndpointService.ts +++ b/packages/backend/src/core/FanoutTimelineEndpointService.ts @@ -61,8 +61,8 @@ export class FanoutTimelineEndpointService { // 呼び出し元と以下の処理をシンプルにするためにdbFallbackを置き換える if (!ps.useDbFallback) ps.dbFallback = () => Promise.resolve([]); - const shouldPrepend = ps.sinceId && !ps.untilId; - const idCompare: (a: string, b: string) => number = shouldPrepend ? (a, b) => a < b ? -1 : 1 : (a, b) => a > b ? -1 : 1; + const ascending = ps.sinceId && !ps.untilId; + const idCompare: (a: string, b: string) => number = ascending ? (a, b) => a < b ? -1 : 1 : (a, b) => a > b ? -1 : 1; const redisResult = await this.fanoutTimelineService.getMulti(ps.redisTimelines, ps.untilId, ps.sinceId); @@ -142,9 +142,7 @@ export class FanoutTimelineEndpointService { if (ps.allowPartial ? redisTimeline.length !== 0 : redisTimeline.length >= ps.limit) { // 十分Redisからとれた - const result = redisTimeline.slice(0, ps.limit); - if (shouldPrepend) result.reverse(); - return result; + return redisTimeline.slice(0, ps.limit); } } @@ -152,8 +150,7 @@ export class FanoutTimelineEndpointService { const remainingToRead = ps.limit - redisTimeline.length; let dbUntil: string | null; let dbSince: string | null; - if (shouldPrepend) { - redisTimeline.reverse(); + if (ascending) { dbUntil = ps.untilId; dbSince = noteIds[noteIds.length - 1]; } else { @@ -161,7 +158,7 @@ export class FanoutTimelineEndpointService { dbSince = ps.sinceId; } const gotFromDb = await ps.dbFallback(dbUntil, dbSince, remainingToRead); - return shouldPrepend ? [...gotFromDb, ...redisTimeline] : [...redisTimeline, ...gotFromDb]; + return [...redisTimeline, ...gotFromDb]; } return await ps.dbFallback(ps.untilId, ps.sinceId, ps.limit); diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 32104fea90..e5580f36d1 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -473,6 +473,7 @@ export class NoteCreateService implements OnApplicationShutdown { noteVisibility: insert.visibility, userId: user.id, userHost: user.host, + channelId: insert.channelId, }); await transactionalEntityManager.insert(MiPoll, poll); diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index a620d7c94b..41e5bfe9e4 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -228,7 +228,7 @@ export type SchemaTypeDef<p extends Schema> = p['items']['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<NonNullable<p['items']['allOf']>>>[] : never ) : - p['items'] extends NonNullable<Schema> ? SchemaTypeDef<p['items']>[] : + p['items'] extends NonNullable<Schema> ? SchemaType<p['items']>[] : any[] ) : p['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['anyOf']> & PartialIntersection<UnionSchemaType<p['anyOf']>> : diff --git a/packages/backend/src/models/Poll.ts b/packages/backend/src/models/Poll.ts index c2693dbb19..ca985c8b24 100644 --- a/packages/backend/src/models/Poll.ts +++ b/packages/backend/src/models/Poll.ts @@ -8,6 +8,7 @@ import { noteVisibilities } from '@/types.js'; import { id } from './util/id.js'; import { MiNote } from './Note.js'; import type { MiUser } from './User.js'; +import type { MiChannel } from "@/models/Channel.js"; @Entity('poll') export class MiPoll { @@ -58,6 +59,14 @@ export class MiPoll { comment: '[Denormalized]', }) public userHost: string | null; + + @Index() + @Column({ + ...id(), + nullable: true, + comment: '[Denormalized]', + }) + public channelId: MiChannel['id'] | null; //#endregion constructor(data: Partial<MiPoll>) { diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index 320d9fdb00..2f619380e9 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -7,7 +7,7 @@ import { In } from 'typeorm'; import * as Redis from 'ioredis'; import { Inject, Injectable } from '@nestjs/common'; import type { NotesRepository } from '@/models/_.js'; -import { obsoleteNotificationTypes, notificationTypes, FilterUnionByProperty } from '@/types.js'; +import { FilterUnionByProperty, notificationTypes, obsoleteNotificationTypes } from '@/types.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteReadService } from '@/core/NoteReadService.js'; import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js'; @@ -84,27 +84,51 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- const includeTypes = ps.includeTypes && ps.includeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][]; const excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][]; - const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 - const notificationsRes = await this.redisClient.xrevrange( - `notificationTimeline:${me.id}`, - ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+', - ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : '-', - 'COUNT', limit); + let sinceTime = ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime().toString() : null; + let untilTime = ps.untilId ? this.idService.parse(ps.untilId).date.getTime().toString() : null; - if (notificationsRes.length === 0) { - return []; - } + let notifications: MiNotification[]; + for (;;) { + let notificationsRes: [id: string, fields: string[]][]; - let notifications = notificationsRes.map(x => JSON.parse(x[1][1])).filter(x => x.id !== ps.untilId && x !== ps.sinceId) as MiNotification[]; + // sinceidのみの場合は古い順、そうでない場合は新しい順。 QueryService.makePaginationQueryも参照 + if (sinceTime && !untilTime) { + notificationsRes = await this.redisClient.xrange( + `notificationTimeline:${me.id}`, + '(' + sinceTime, + '+', + 'COUNT', ps.limit); + } else { + notificationsRes = await this.redisClient.xrevrange( + `notificationTimeline:${me.id}`, + untilTime ? '(' + untilTime : '+', + sinceTime ? '(' + sinceTime : '-', + 'COUNT', ps.limit); + } - if (includeTypes && includeTypes.length > 0) { - notifications = notifications.filter(notification => includeTypes.includes(notification.type)); - } else if (excludeTypes && excludeTypes.length > 0) { - notifications = notifications.filter(notification => !excludeTypes.includes(notification.type)); - } + if (notificationsRes.length === 0) { + return []; + } - if (notifications.length === 0) { - return []; + notifications = notificationsRes.map(x => JSON.parse(x[1][1])) as MiNotification[]; + + if (includeTypes && includeTypes.length > 0) { + notifications = notifications.filter(notification => includeTypes.includes(notification.type)); + } else if (excludeTypes && excludeTypes.length > 0) { + notifications = notifications.filter(notification => !excludeTypes.includes(notification.type)); + } + + if (notifications.length !== 0) { + // 通知が1件以上ある場合は返す + break; + } + + // フィルタしたことで通知が0件になった場合、次のページを取得する + if (ps.sinceId && !ps.untilId) { + sinceTime = notificationsRes[notificationsRes.length - 1][0]; + } else { + untilTime = notificationsRes[notificationsRes.length - 1][0]; + } } // Mark all as read diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts index ba38573065..4fd6f8682d 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts @@ -32,6 +32,7 @@ export const paramDef = { properties: { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, offset: { type: 'integer', default: 0 }, + excludeChannels: { type: 'boolean', default: false }, }, required: [], } as const; @@ -86,6 +87,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- query.setParameters(mutingQuery.getParameters()); //#endregion + //#region exclude channels + if (ps.excludeChannels) { + query.andWhere('poll.channelId IS NULL'); + } + //#endregion + const polls = await query .orderBy('poll.noteId', 'DESC') .limit(ps.limit) diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index bd81989cb9..26cfa921c5 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -110,9 +110,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- }); // リクエストされた通りに並べ替え + // 順番は保持されるけど数は減ってる可能性がある const _users: MiUser[] = []; for (const id of ps.userIds) { - _users.push(users.find(x => x.id === id)!); + const user = users.find(x => x.id === id); + if (user != null) _users.push(user); } return await Promise.all(_users.map(u => this.userEntityService.pack(u, me, { diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 1394616752..f35ec8ba31 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -438,7 +438,7 @@ export class ClientServerService { //#endregion - const renderBase = async (reply: FastifyReply) => { + const renderBase = async (reply: FastifyReply, data: { [key: string]: any } = {}) => { const meta = await this.metaService.fetch(); reply.header('Cache-Control', 'public, max-age=30'); return await reply.view('base', { @@ -447,6 +447,7 @@ export class ClientServerService { title: meta.name ?? 'Misskey', desc: meta.description, ...await this.generateCommonPugData(meta), + ...data, }); }; @@ -744,6 +745,18 @@ export class ClientServerService { }); //#endregion + //region noindex pages + // Tags + fastify.get<{ Params: { clip: string; } }>('/tags/:tag', async (request, reply) => { + return await renderBase(reply, { noindex: true }); + }); + + // User with Tags + fastify.get<{ Params: { clip: string; } }>('/user-tags/:tag', async (request, reply) => { + return await renderBase(reply, { noindex: true }); + }); + //endregion + fastify.get('/_info_card_', async (request, reply) => { const meta = await this.metaService.fetch(true); diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index 1d9146e22a..ec1325e4e9 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -50,6 +50,9 @@ html block title = title || 'Misskey' + if noindex + meta(name='robots' content='noindex') + block desc meta(name='description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨') |