summaryrefslogtreecommitdiff
path: root/packages/backend
diff options
context:
space:
mode:
authorsyuilo <4439005+syuilo@users.noreply.github.com>2024-05-21 17:37:35 +0900
committerGitHub <noreply@github.com>2024-05-21 17:37:35 +0900
commit4a3c01d19890b449f5d1af2e89234c43052b26ba (patch)
treef820af1c06b84bcad48c2f45ca2cd974b1e98af4 /packages/backend
parentMerge pull request #13816 from misskey-dev/develop (diff)
parenthappy-domにメモリリークがありそう (diff)
downloadmisskey-4a3c01d19890b449f5d1af2e89234c43052b26ba.tar.gz
misskey-4a3c01d19890b449f5d1af2e89234c43052b26ba.tar.bz2
misskey-4a3c01d19890b449f5d1af2e89234c43052b26ba.zip
Merge branch 'develop' into release/2024.5.0
Diffstat (limited to '')
-rw-r--r--packages/backend/migration/1716129964060-ChannelIdDenormalizedForMiPoll.js21
-rw-r--r--packages/backend/package.json2
-rw-r--r--packages/backend/src/core/FanoutTimelineEndpointService.ts13
-rw-r--r--packages/backend/src/core/NoteCreateService.ts1
-rw-r--r--packages/backend/src/misc/json-schema.ts2
-rw-r--r--packages/backend/src/models/Poll.ts9
-rw-r--r--packages/backend/src/server/api/endpoints/i/notifications.ts60
-rw-r--r--packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/users/show.ts4
-rw-r--r--packages/backend/src/server/web/ClientServerService.ts15
-rw-r--r--packages/backend/src/server/web/views/base.pug3
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 ✨🚀✨')