Merge branch 'develop' into detect-size

This commit is contained in:
FineArchs 2024-02-14 01:54:15 +09:00 committed by GitHub
commit 5e6d44a265
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 113 additions and 87 deletions

View File

@ -14,9 +14,9 @@ export type FanoutTimelineName =
| `homeTimeline:${string}` | `homeTimeline:${string}`
| `homeTimelineWithFiles:${string}` // only notes with files are included | `homeTimelineWithFiles:${string}` // only notes with files are included
// local timeline // local timeline
| 'localTimeline' // replies are not included | `localTimeline` // replies are not included
| 'localTimelineWithFiles' // only non-reply notes with files are included | `localTimelineWithFiles` // only non-reply notes with files are included
| 'localTimelineWithReplies' // only replies are included | `localTimelineWithReplies` // only replies are included
| `localTimelineWithReplyTo:${string}` // Only replies to specific local user are included. Parameter is reply user id. | `localTimelineWithReplyTo:${string}` // Only replies to specific local user are included. Parameter is reply user id.
// antenna // antenna

View File

@ -12,11 +12,11 @@ import type { Config } from '@/config.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js'; import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js';
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, RelationshipQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js'; import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, RelationshipQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js';
import type { DbJobData, DeliverJobData, RelationshipJobData, ThinUser } from '../queue/types.js'; import type { DbJobData, DeliverJobData, RelationshipJobData, ThinUser } from '../queue/types.js';
import type httpSignature from '@peertube/http-signature'; import type httpSignature from '@peertube/http-signature';
import type * as Bull from 'bullmq'; import type * as Bull from 'bullmq';
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
@Injectable() @Injectable()
export class QueueService { export class QueueService {

View File

@ -31,7 +31,7 @@ export class ApMfmService {
const parsed = mfm.parse(srcMfm); const parsed = mfm.parse(srcMfm);
if (!apAppend && parsed.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) { if (!apAppend && parsed?.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) {
noMisskeyContent = true; noMisskeyContent = true;
} }

View File

@ -0,0 +1,9 @@
import type { onRequestHookHandler } from 'fastify';
export const handleRequestRedirectToOmitSearch: onRequestHookHandler = (request, reply, done) => {
const index = request.url.indexOf('?');
if (~index) {
reply.redirect(301, request.url.slice(0, index));
}
done();
};

View File

@ -34,7 +34,7 @@ export class RelationshipProcessorService {
@bindThis @bindThis
public async processFollow(job: Bull.Job<RelationshipJobData>): Promise<string> { public async processFollow(job: Bull.Job<RelationshipJobData>): Promise<string> {
this.logger.info(`${job.data.from.id} is trying to follow ${job.data.to.id} ${job.data.withReplies ? 'with replies' : 'without replies'}`); this.logger.info(`${job.data.from.id} is trying to follow ${job.data.to.id} ${job.data.withReplies ? "with replies" : "without replies"}`);
await this.userFollowingService.follow(job.data.from, job.data.to, { await this.userFollowingService.follow(job.data.from, job.data.to, {
requestId: job.data.requestId, requestId: job.data.requestId,
silent: job.data.silent, silent: job.data.silent,

View File

@ -27,6 +27,7 @@ import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { isMimeImage } from '@/misc/is-mime-image.js'; import { isMimeImage } from '@/misc/is-mime-image.js';
import { correctFilename } from '@/misc/correct-filename.js'; import { correctFilename } from '@/misc/correct-filename.js';
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
const _filename = fileURLToPath(import.meta.url); const _filename = fileURLToPath(import.meta.url);
@ -67,20 +68,23 @@ export class FileServerService {
done(); done();
}); });
fastify.get('/files/app-default.jpg', (request, reply) => { fastify.register((fastify, options, done) => {
const file = fs.createReadStream(`${_dirname}/assets/dummy.png`); fastify.addHook('onRequest', handleRequestRedirectToOmitSearch);
reply.header('Content-Type', 'image/jpeg'); fastify.get('/files/app-default.jpg', (request, reply) => {
reply.header('Cache-Control', 'max-age=31536000, immutable'); const file = fs.createReadStream(`${_dirname}/assets/dummy.png`);
return reply.send(file); reply.header('Content-Type', 'image/jpeg');
}); reply.header('Cache-Control', 'max-age=31536000, immutable');
return reply.send(file);
});
fastify.get<{ Params: { key: string; } }>('/files/:key', async (request, reply) => { fastify.get<{ Params: { key: string; } }>('/files/:key', async (request, reply) => {
return await this.sendDriveFile(request, reply) return await this.sendDriveFile(request, reply)
.catch(err => this.errorHandler(request, reply, err)); .catch(err => this.errorHandler(request, reply, err));
}); });
fastify.get<{ Params: { key: string; } }>('/files/:key/*', async (request, reply) => { fastify.get<{ Params: { key: string; } }>('/files/:key/*', async (request, reply) => {
return await this.sendDriveFile(request, reply) return await reply.redirect(301, `${this.config.url}/files/${request.params.key}`);
.catch(err => this.errorHandler(request, reply, err)); });
done();
}); });
fastify.get<{ fastify.get<{

View File

@ -37,12 +37,12 @@ export class NodeinfoServerService {
@bindThis @bindThis
public getLinks() { public getLinks() {
return [{ return [{
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1', rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1',
href: this.config.url + nodeinfo2_1path, href: this.config.url + nodeinfo2_1path
}, { }, {
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0', rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
href: this.config.url + nodeinfo2_0path, href: this.config.url + nodeinfo2_0path,
}]; }];
} }
@bindThis @bindThis

View File

@ -25,8 +25,8 @@ export const meta = {
items: { items: {
type: 'object', type: 'object',
}, },
}, }
}, }
}, },
} as const; } as const;

View File

@ -71,7 +71,7 @@ export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
userId: { type: 'string', format: 'misskey:id' }, userId: { type: 'string', format: 'misskey:id' },
withReplies: { type: 'boolean' }, withReplies: { type: 'boolean' }
}, },
required: ['userId'], required: ['userId'],
} as const; } as const;

View File

@ -6,7 +6,7 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UsersRepository } from '@/models/_.js'; import type { UsersRepository } from '@/models/_.js';
import { safeForSql } from '@/misc/safe-for-sql.js'; import { safeForSql } from "@/misc/safe-for-sql.js";
import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';

View File

@ -14,7 +14,7 @@ export const meta = {
tags: ['account'], tags: ['account'],
requireCredential: true, requireCredential: true,
kind: 'read:account', kind: "read:account",
res: { res: {
type: 'object', type: 'object',

View File

@ -38,9 +38,9 @@ export const meta = {
type: 'array', type: 'array',
uniqueItems: true, uniqueItems: true,
items: { items: {
type: 'string', type: 'string'
}, },
}, }
}, },
}, },
}, },

View File

@ -35,7 +35,7 @@ export const meta = {
type: 'array', type: 'array',
uniqueItems: true, uniqueItems: true,
items: { items: {
type: 'string', type: 'string'
}, },
}, },
isAuthorized: { isAuthorized: {

View File

@ -22,7 +22,7 @@ export const meta = {
res: { res: {
type: 'object', type: 'object',
}, }
} as const; } as const;
export const paramDef = { export const paramDef = {

View File

@ -22,7 +22,7 @@ export const meta = {
res: { res: {
type: 'object', type: 'object',
}, }
} as const; } as const;
export const paramDef = { export const paramDef = {

View File

@ -22,8 +22,8 @@ export const meta = {
type: 'array', type: 'array',
items: { items: {
type: 'string', type: 'string',
}, }
}, }
}, },
domain: { domain: {
type: 'string', type: 'string',
@ -31,7 +31,7 @@ export const meta = {
}, },
}, },
}, },
}, }
} as const; } as const;
export const paramDef = { export const paramDef = {

View File

@ -33,7 +33,7 @@ export const meta = {
properties: { properties: {
id: { id: {
type: 'string', type: 'string',
format: 'misskey:id', format: 'misskey:id'
}, },
userId: { userId: {
type: 'string', type: 'string',
@ -45,7 +45,7 @@ export const meta = {
items: { items: {
type: 'string', type: 'string',
enum: webhookEventTypes, enum: webhookEventTypes,
}, }
}, },
url: { type: 'string' }, url: { type: 'string' },
secret: { type: 'string' }, secret: { type: 'string' },

View File

@ -23,7 +23,7 @@ export const meta = {
properties: { properties: {
id: { id: {
type: 'string', type: 'string',
format: 'misskey:id', format: 'misskey:id'
}, },
userId: { userId: {
type: 'string', type: 'string',
@ -35,7 +35,7 @@ export const meta = {
items: { items: {
type: 'string', type: 'string',
enum: webhookEventTypes, enum: webhookEventTypes,
}, }
}, },
url: { type: 'string' }, url: { type: 'string' },
secret: { type: 'string' }, secret: { type: 'string' },
@ -43,8 +43,8 @@ export const meta = {
latestSentAt: { type: 'string', format: 'date-time', nullable: true }, latestSentAt: { type: 'string', format: 'date-time', nullable: true },
latestStatus: { type: 'integer', nullable: true }, latestStatus: { type: 'integer', nullable: true },
}, },
}, }
}, }
} as const; } as const;
export const paramDef = { export const paramDef = {

View File

@ -30,7 +30,7 @@ export const meta = {
properties: { properties: {
id: { id: {
type: 'string', type: 'string',
format: 'misskey:id', format: 'misskey:id'
}, },
userId: { userId: {
type: 'string', type: 'string',
@ -42,7 +42,7 @@ export const meta = {
items: { items: {
type: 'string', type: 'string',
enum: webhookEventTypes, enum: webhookEventTypes,
}, }
}, },
url: { type: 'string' }, url: { type: 'string' },
secret: { type: 'string' }, secret: { type: 'string' },

View File

@ -47,7 +47,7 @@ export const meta = {
bothWithRepliesAndWithFiles: { bothWithRepliesAndWithFiles: {
message: 'Specifying both withReplies and withFiles is not supported', message: 'Specifying both withReplies and withFiles is not supported',
code: 'BOTH_WITH_REPLIES_AND_WITH_FILES', code: 'BOTH_WITH_REPLIES_AND_WITH_FILES',
id: 'dfaa3eb7-8002-4cb7-bcc4-1095df46656f', id: 'dfaa3eb7-8002-4cb7-bcc4-1095df46656f'
}, },
}, },
} as const; } as const;

View File

@ -18,7 +18,7 @@ export const meta = {
properties: { properties: {
id: { id: {
type: 'string', type: 'string',
format: 'misskey:id', format: 'misskey:id'
}, },
required: { required: {
type: 'boolean', type: 'boolean',
@ -34,8 +34,8 @@ export const meta = {
default: 'hello', default: 'hello',
nullable: true, nullable: true,
}, },
}, }
}, }
} as const; } as const;
export const paramDef = { export const paramDef = {

View File

@ -34,6 +34,7 @@ import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, MiMeta, NotesRepository, PagesRepository, ReversiGamesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, MiMeta, NotesRepository, PagesRepository, ReversiGamesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
import { deepClone } from '@/misc/clone.js'; import { deepClone } from '@/misc/clone.js';
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { FlashEntityService } from '@/core/entities/FlashEntityService.js'; import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
@ -253,11 +254,16 @@ export class ClientServerService {
//#region vite assets //#region vite assets
if (this.config.clientManifestExists) { if (this.config.clientManifestExists) {
fastify.register(fastifyStatic, { fastify.register((fastify, options, done) => {
root: viteOut, fastify.register(fastifyStatic, {
prefix: '/vite/', root: viteOut,
maxAge: ms('30 days'), prefix: '/vite/',
decorateReply: false, maxAge: ms('30 days'),
immutable: true,
decorateReply: false,
});
fastify.addHook('onRequest', handleRequestRedirectToOmitSearch);
done();
}); });
} else { } else {
const port = (process.env.VITE_PORT ?? '5173'); const port = (process.env.VITE_PORT ?? '5173');
@ -292,11 +298,16 @@ export class ClientServerService {
decorateReply: false, decorateReply: false,
}); });
fastify.register(fastifyStatic, { fastify.register((fastify, options, done) => {
root: tarball, fastify.register(fastifyStatic, {
prefix: '/tarball/', root: tarball,
immutable: true, prefix: '/tarball/',
decorateReply: false, maxAge: ms('30 days'),
immutable: true,
decorateReply: false,
});
fastify.addHook('onRequest', handleRequestRedirectToOmitSearch);
done();
}); });
fastify.get('/favicon.ico', async (request, reply) => { fastify.get('/favicon.ico', async (request, reply) => {

View File

@ -7,10 +7,11 @@ process.env.NODE_ENV = 'test';
import * as assert from 'assert'; import * as assert from 'assert';
import { MiNote } from '@/models/Note.js'; import { MiNote } from '@/models/Note.js';
import type { Packed } from '@/misc/json-schema.js';
import { api, initTestDb, makeStreamCatcher, post, signup, uploadFile } from '../utils.js'; import { api, initTestDb, makeStreamCatcher, post, signup, uploadFile } from '../utils.js';
import type * as misskey from 'misskey-js'; import type * as misskey from 'misskey-js';
import type{ Repository } from 'typeorm'; import type{ Repository } from 'typeorm'
import type { Packed } from '@/misc/json-schema.js';
describe('Drive', () => { describe('Drive', () => {
let Notes: Repository<MiNote>; let Notes: Repository<MiNote>;
@ -30,7 +31,7 @@ describe('Drive', () => {
const marker = Math.random().toString(); const marker = Math.random().toString();
const url = 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg'; const url = 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg'
const catcher = makeStreamCatcher( const catcher = makeStreamCatcher(
alice, alice,
@ -50,7 +51,7 @@ describe('Drive', () => {
assert.strictEqual(res.status, 204); assert.strictEqual(res.status, 204);
assert.strictEqual(file.name, 'Lenna.jpg'); assert.strictEqual(file.name, 'Lenna.jpg');
assert.strictEqual(file.type, 'image/jpeg'); assert.strictEqual(file.type, 'image/jpeg');
}); })
test('ローカルからアップロードできる', async () => { test('ローカルからアップロードできる', async () => {
// APIレスポンスを直接使用するので utils.js uploadFile が通過することで成功とする // APIレスポンスを直接使用するので utils.js uploadFile が通過することで成功とする
@ -58,27 +59,27 @@ describe('Drive', () => {
const res = await uploadFile(alice, { path: 'Lenna.jpg', name: 'テスト画像' }); const res = await uploadFile(alice, { path: 'Lenna.jpg', name: 'テスト画像' });
assert.strictEqual(res.body?.name, 'テスト画像.jpg'); assert.strictEqual(res.body?.name, 'テスト画像.jpg');
assert.strictEqual(res.body.type, 'image/jpeg'); assert.strictEqual(res.body?.type, 'image/jpeg');
}); })
test('添付ノート一覧を取得できる', async () => { test('添付ノート一覧を取得できる', async () => {
const ids = (await Promise.all([uploadFile(alice), uploadFile(alice), uploadFile(alice)])).map(elm => elm.body!.id); const ids = (await Promise.all([uploadFile(alice), uploadFile(alice), uploadFile(alice)])).map(elm => elm.body!.id)
const note0 = await post(alice, { fileIds: [ids[0]] }); const note0 = await post(alice, { fileIds: [ids[0]] });
const note1 = await post(alice, { fileIds: [ids[0], ids[1]] }); const note1 = await post(alice, { fileIds: [ids[0], ids[1]] });
const attached0 = await api('drive/files/attached-notes', { fileId: ids[0] }, alice); const attached0 = await api('drive/files/attached-notes', { fileId: ids[0] }, alice);
assert.strictEqual(attached0.body.length, 2); assert.strictEqual(attached0.body.length, 2);
assert.strictEqual(attached0.body[0].id, note1.id); assert.strictEqual(attached0.body[0].id, note1.id)
assert.strictEqual(attached0.body[1].id, note0.id); assert.strictEqual(attached0.body[1].id, note0.id)
const attached1 = await api('drive/files/attached-notes', { fileId: ids[1] }, alice); const attached1 = await api('drive/files/attached-notes', { fileId: ids[1] }, alice);
assert.strictEqual(attached1.body.length, 1); assert.strictEqual(attached1.body.length, 1);
assert.strictEqual(attached1.body[0].id, note1.id); assert.strictEqual(attached1.body[0].id, note1.id)
const attached2 = await api('drive/files/attached-notes', { fileId: ids[2] }, alice); const attached2 = await api('drive/files/attached-notes', { fileId: ids[2] }, alice);
assert.strictEqual(attached2.body.length, 0); assert.strictEqual(attached2.body.length, 0)
}); })
test('添付ノート一覧は他の人から見えない', async () => { test('添付ノート一覧は他の人から見えない', async () => {
const file = await uploadFile(alice); const file = await uploadFile(alice);
@ -88,6 +89,7 @@ describe('Drive', () => {
const res = await api('drive/files/attached-notes', { fileId: file.body!.id }, bob); const res = await api('drive/files/attached-notes', { fileId: file.body!.id }, bob);
assert.strictEqual(res.status, 400); assert.strictEqual(res.status, 400);
assert.strictEqual('error' in res.body, true); assert.strictEqual('error' in res.body, true);
});
})
}); });

View File

@ -13,10 +13,10 @@ import fetch, { File, RequestInit } from 'node-fetch';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { JSDOM } from 'jsdom'; import { JSDOM } from 'jsdom';
import { DEFAULT_POLICIES } from '@/core/RoleService.js'; import { DEFAULT_POLICIES } from '@/core/RoleService.js';
import { Packed } from '@/misc/json-schema.js';
import { entities } from '../src/postgres.js'; import { entities } from '../src/postgres.js';
import { loadConfig } from '../src/config.js'; import { loadConfig } from '../src/config.js';
import type * as misskey from 'misskey-js'; import type * as misskey from 'misskey-js';
import { Packed } from '@/misc/json-schema.js';
export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js'; export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js';
@ -123,9 +123,9 @@ export function randomString(chars = 'abcdefghijklmnopqrstuvwxyz0123456789', len
function timeoutPromise<T>(p: Promise<T>, timeout: number): Promise<T> { function timeoutPromise<T>(p: Promise<T>, timeout: number): Promise<T> {
return Promise.race([ return Promise.race([
p, p,
new Promise((reject) => { new Promise((reject) =>{
setTimeout(() => { reject(new Error('timed out')); }, timeout); setTimeout(() => { reject(new Error('timed out')); }, timeout)
}) as never, }) as never
]); ]);
} }
@ -343,7 +343,7 @@ export const uploadUrl = async (user: UserToken, url: string): Promise<Packed<'D
'main', 'main',
(msg) => msg.type === 'urlUploadFinished' && msg.body.marker === marker, (msg) => msg.type === 'urlUploadFinished' && msg.body.marker === marker,
(msg) => msg.body.file as Packed<'DriveFile'>, (msg) => msg.body.file as Packed<'DriveFile'>,
60 * 1000, 60 * 1000
); );
await api('drive/files/upload-from-url', { await api('drive/files/upload-from-url', {
@ -434,20 +434,20 @@ export const waitFire = async (user: UserToken, channel: string, trgr: () => any
* @returns extractorを通した値を得る * @returns extractorを通した値を得る
*/ */
export function makeStreamCatcher<T>( export function makeStreamCatcher<T>(
user: UserToken, user: UserToken,
channel: string, channel: string,
cond: (message: Record<string, any>) => boolean, cond: (message: Record<string, any>) => boolean,
extractor: (message: Record<string, any>) => T, extractor: (message: Record<string, any>) => T,
timeout = 60 * 1000): Promise<T> { timeout = 60 * 1000): Promise<T> {
let ws: WebSocket; let ws: WebSocket
const p = new Promise<T>(async (resolve) => { const p = new Promise<T>(async (resolve) => {
ws = await connectStream(user, channel, (msg) => { ws = await connectStream(user, channel, (msg) => {
if (cond(msg)) { if (cond(msg)) {
resolve(extractor(msg)); resolve(extractor(msg))
} }
}); });
}).finally(() => { }).finally(() => {
ws.close(); ws?.close();
}); });
return timeoutPromise(p, timeout); return timeoutPromise(p, timeout);

View File

@ -101,7 +101,7 @@ const announcements = {
limit: 10, limit: 10,
}; };
const isTimelineAvailable = ref(instance.policies.ltlAvailable || instance.policies.gtlAvailable); const isTimelineAvailable = ref(instance.policies?.ltlAvailable || instance.policies?.gtlAvailable);
const showMenu = ref(false); const showMenu = ref(false);
const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD); const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);