diff options
Diffstat (limited to 'packages/backend/src/server')
170 files changed, 1105 insertions, 457 deletions
diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index 455acd1e47..634f5f0a4e 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -181,7 +181,7 @@ export class ActivityPubServerService { undefined, inStock ? `${partOf}?${url.query({ page: 'true', - cursor: followings[followings.length - 1].id, + cursor: followings.at(-1)!.id, })}` : undefined, ); @@ -189,7 +189,11 @@ export class ActivityPubServerService { return (this.apRendererService.addContext(rendered)); } else { // index page - const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`); + const rendered = this.apRendererService.renderOrderedCollection( + partOf, + user.followersCount, + `${partOf}?page=true`, + ); reply.header('Cache-Control', 'public, max-age=180'); this.setResponseType(request, reply); return (this.apRendererService.addContext(rendered)); @@ -269,7 +273,7 @@ export class ActivityPubServerService { undefined, inStock ? `${partOf}?${url.query({ page: 'true', - cursor: followings[followings.length - 1].id, + cursor: followings.at(-1)!.id, })}` : undefined, ); @@ -277,7 +281,11 @@ export class ActivityPubServerService { return (this.apRendererService.addContext(rendered)); } else { // index page - const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`); + const rendered = this.apRendererService.renderOrderedCollection( + partOf, + user.followingCount, + `${partOf}?page=true`, + ); reply.header('Cache-Control', 'public, max-age=180'); this.setResponseType(request, reply); return (this.apRendererService.addContext(rendered)); @@ -310,7 +318,10 @@ export class ActivityPubServerService { const rendered = this.apRendererService.renderOrderedCollection( `${this.config.url}/users/${userId}/collections/featured`, - renderedNotes.length, undefined, undefined, renderedNotes, + renderedNotes.length, + undefined, + undefined, + renderedNotes, ); reply.header('Cache-Control', 'public, max-age=180'); @@ -369,7 +380,7 @@ export class ActivityPubServerService { })) .andWhere('note.localOnly = FALSE'); - const notes = await query.take(limit).getMany(); + const notes = await query.limit(limit).getMany(); if (sinceId) notes.reverse(); @@ -387,7 +398,7 @@ export class ActivityPubServerService { })}` : undefined, notes.length ? `${partOf}?${url.query({ page: 'true', - until_id: notes[notes.length - 1].id, + until_id: notes.at(-1)!.id, })}` : undefined, ); @@ -395,7 +406,9 @@ export class ActivityPubServerService { return (this.apRendererService.addContext(rendered)); } else { // index page - const rendered = this.apRendererService.renderOrderedCollection(partOf, user.notesCount, + const rendered = this.apRendererService.renderOrderedCollection( + partOf, + user.notesCount, `${partOf}?page=true`, `${partOf}?page=true&since_id=000000000000000000000000`, ); diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 98329ddffa..2547d73365 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -3,6 +3,8 @@ import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; import { Inject, Injectable } from '@nestjs/common'; import rename from 'rename'; +import sharp from 'sharp'; +import { sharpBmp } from 'sharp-read-bmp'; import type { Config } from '@/config.js'; import type { DriveFile, DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; @@ -18,11 +20,9 @@ import { contentDisposition } from '@/misc/content-disposition.js'; import { FileInfoService } from '@/core/FileInfoService.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; -import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; import { isMimeImage } from '@/misc/is-mime-image.js'; -import sharp from 'sharp'; -import { sharpBmp } from 'sharp-read-bmp'; import { correctFilename } from '@/misc/correct-filename.js'; +import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -180,8 +180,8 @@ export class FileServerService { reply.header('Content-Disposition', contentDisposition( 'inline', - correctFilename(file.filename, image.ext) - ) + correctFilename(file.filename, image.ext), + ), ); return image.data; } @@ -278,11 +278,11 @@ export class FileServerService { }; } else { const data = (await sharpBmp(file.path, file.mime, { animated: !('static' in request.query) })) - .resize({ - height: 'emoji' in request.query ? 128 : 320, - withoutEnlargement: true, - }) - .webp(webpDefault); + .resize({ + height: 'emoji' in request.query ? 128 : 320, + withoutEnlargement: true, + }) + .webp(webpDefault); image = { data, @@ -355,8 +355,8 @@ export class FileServerService { reply.header('Content-Disposition', contentDisposition( 'inline', - correctFilename(file.filename, image.ext) - ) + correctFilename(file.filename, image.ext), + ), ); return image.data; } catch (e) { diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index c3d45e4ad6..051920958e 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -16,6 +16,7 @@ import { createTemp } from '@/misc/create-temp.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; +import { MetaService } from '@/core/MetaService.js'; import { ActivityPubServerService } from './ActivityPubServerService.js'; import { NodeinfoServerService } from './NodeinfoServerService.js'; import { ApiServerService } from './api/ApiServerService.js'; @@ -45,6 +46,7 @@ export class ServerService implements OnApplicationShutdown { @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, + private metaService: MetaService, private userEntityService: UserEntityService, private apiServerService: ApiServerService, private openApiServerService: OpenApiServerService, @@ -161,11 +163,16 @@ export class ServerService implements OnApplicationShutdown { }); fastify.get<{ Params: { x: string } }>('/identicon/:x', async (request, reply) => { - const [temp, cleanup] = await createTemp(); - await genIdenticon(request.params.x, fs.createWriteStream(temp)); reply.header('Content-Type', 'image/png'); reply.header('Cache-Control', 'public, max-age=86400'); - return fs.createReadStream(temp).on('close', () => cleanup()); + + if ((await this.metaService.fetch()).enableIdenticonGeneration) { + const [temp, cleanup] = await createTemp(); + await genIdenticon(request.params.x, fs.createWriteStream(temp)); + return fs.createReadStream(temp).on('close', () => cleanup()); + } else { + return reply.redirect('/static-assets/avatar.png'); + } }); fastify.get<{ Params: { code: string } }>('/verify-email/:code', async (request, reply) => { @@ -217,14 +224,25 @@ export class ServerService implements OnApplicationShutdown { } }); - fastify.listen({ port: this.config.port, host: '0.0.0.0' }); + if (this.config.socket) { + if (fs.existsSync(this.config.socket)) { + fs.unlinkSync(this.config.socket); + } + fastify.listen({ path: this.config.socket }, (err, address) => { + if (this.config.chmodSocket) { + fs.chmodSync(this.config.socket!, this.config.chmodSocket); + } + }); + } else { + fastify.listen({ port: this.config.port, host: '0.0.0.0' }); + } await fastify.ready(); } @bindThis public async dispose(): Promise<void> { - await this.streamingApiServerService.detach(); + await this.streamingApiServerService.detach(); await this.#fastify.close(); } diff --git a/packages/backend/src/server/WellKnownServerService.ts b/packages/backend/src/server/WellKnownServerService.ts index 9bf8deb221..aabe631fb2 100644 --- a/packages/backend/src/server/WellKnownServerService.ts +++ b/packages/backend/src/server/WellKnownServerService.ts @@ -1,18 +1,18 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import vary from 'vary'; +import fastifyAccepts from '@fastify/accepts'; import { DI } from '@/di-symbols.js'; import type { UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import { escapeAttribute, escapeValue } from '@/misc/prelude/xml.js'; import type { User } from '@/models/entities/User.js'; import * as Acct from '@/misc/acct.js'; -import { NodeinfoServerService } from './NodeinfoServerService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import type { FindOptionsWhere } from 'typeorm'; import { bindThis } from '@/decorators.js'; +import { NodeinfoServerService } from './NodeinfoServerService.js'; +import type { FindOptionsWhere } from 'typeorm'; import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; -import fastifyAccepts from '@fastify/accepts'; @Injectable() export class WellKnownServerService { diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index dad1a4132a..c4c02e7afe 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -1,8 +1,8 @@ +import { randomUUID } from 'node:crypto'; import { pipeline } from 'node:stream'; import * as fs from 'node:fs'; import { promisify } from 'node:util'; import { Inject, Injectable } from '@nestjs/common'; -import { v4 as uuid } from 'uuid'; import { DI } from '@/di-symbols.js'; import { getIpHash } from '@/misc/get-ip-hash.js'; import type { LocalUser, User } from '@/models/entities/User.js'; @@ -53,44 +53,72 @@ export class ApiCallService implements OnApplicationShutdown { }, 1000 * 60 * 60); } + #sendApiError(reply: FastifyReply, err: ApiError): void { + let statusCode = err.httpStatusCode; + if (err.httpStatusCode === 401) { + reply.header('WWW-Authenticate', 'Bearer realm="Misskey"'); + } else if (err.kind === 'client') { + reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="invalid_request", error_description="${err.message}"`); + statusCode = statusCode ?? 400; + } else if (err.kind === 'permission') { + // (ROLE_PERMISSION_DENIEDは関係ない) + if (err.code === 'PERMISSION_DENIED') { + reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="insufficient_scope", error_description="${err.message}"`); + } + statusCode = statusCode ?? 403; + } else if (!statusCode) { + statusCode = 500; + } + this.send(reply, statusCode, err); + } + + #sendAuthenticationError(reply: FastifyReply, err: unknown): void { + if (err instanceof AuthenticationError) { + const message = 'Authentication failed. Please ensure your token is correct.'; + reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="invalid_token", error_description="${message}"`); + this.send(reply, 401, new ApiError({ + message: 'Authentication failed. Please ensure your token is correct.', + code: 'AUTHENTICATION_FAILED', + id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', + })); + } else { + this.send(reply, 500, new ApiError()); + } + } + @bindThis public handleRequest( endpoint: IEndpoint & { exec: any }, request: FastifyRequest<{ Body: Record<string, unknown> | undefined, Querystring: Record<string, unknown> }>, reply: FastifyReply, - ) { + ): void { const body = request.method === 'GET' ? request.query : request.body; - const token = body?.['i']; + // https://datatracker.ietf.org/doc/html/rfc6750.html#section-2.1 (case sensitive) + const token = request.headers.authorization?.startsWith('Bearer ') + ? request.headers.authorization.slice(7) + : body?.['i']; if (token != null && typeof token !== 'string') { reply.code(400); return; } this.authenticateService.authenticate(token).then(([user, app]) => { this.call(endpoint, user, app, body, null, request).then((res) => { - if (request.method === 'GET' && endpoint.meta.cacheSec && !body?.['i'] && !user) { + if (request.method === 'GET' && endpoint.meta.cacheSec && !token && !user) { reply.header('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`); } this.send(reply, res); }).catch((err: ApiError) => { - this.send(reply, err.httpStatusCode ? err.httpStatusCode : err.kind === 'client' ? 400 : err.kind === 'permission' ? 403 : 500, err); + this.#sendApiError(reply, err); }); if (user) { this.logIp(request, user); } }).catch(err => { - if (err instanceof AuthenticationError) { - this.send(reply, 403, new ApiError({ - message: 'Authentication failed. Please ensure your token is correct.', - code: 'AUTHENTICATION_FAILED', - id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', - })); - } else { - this.send(reply, 500, new ApiError()); - } + this.#sendAuthenticationError(reply, err); }); } @@ -99,7 +127,7 @@ export class ApiCallService implements OnApplicationShutdown { endpoint: IEndpoint & { exec: any }, request: FastifyRequest<{ Body: Record<string, unknown>, Querystring: Record<string, unknown> }>, reply: FastifyReply, - ) { + ): Promise<void> { const multipartData = await request.file().catch(() => { /* Fastify throws if the remote didn't send multipart data. Return 400 below. */ }); @@ -117,7 +145,10 @@ export class ApiCallService implements OnApplicationShutdown { fields[k] = typeof v === 'object' && 'value' in v ? v.value : undefined; } - const token = fields['i']; + // https://datatracker.ietf.org/doc/html/rfc6750.html#section-2.1 (case sensitive) + const token = request.headers.authorization?.startsWith('Bearer ') + ? request.headers.authorization.slice(7) + : fields['i']; if (token != null && typeof token !== 'string') { reply.code(400); return; @@ -129,22 +160,14 @@ export class ApiCallService implements OnApplicationShutdown { }, request).then((res) => { this.send(reply, res); }).catch((err: ApiError) => { - this.send(reply, err.httpStatusCode ? err.httpStatusCode : err.kind === 'client' ? 400 : err.kind === 'permission' ? 403 : 500, err); + this.#sendApiError(reply, err); }); if (user) { this.logIp(request, user); } }).catch(err => { - if (err instanceof AuthenticationError) { - this.send(reply, 403, new ApiError({ - message: 'Authentication failed. Please ensure your token is correct.', - code: 'AUTHENTICATION_FAILED', - id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', - })); - } else { - this.send(reply, 500, new ApiError()); - } + this.#sendAuthenticationError(reply, err); }); } @@ -213,7 +236,7 @@ export class ApiCallService implements OnApplicationShutdown { } if (ep.meta.limit) { - // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app. + // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app. let limitActor: string; if (user) { limitActor = user.id; @@ -255,8 +278,8 @@ export class ApiCallService implements OnApplicationShutdown { throw new ApiError({ message: 'Your account has been suspended.', code: 'YOUR_ACCOUNT_SUSPENDED', + kind: 'permission', id: 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370', - httpStatusCode: 403, }); } } @@ -266,8 +289,8 @@ export class ApiCallService implements OnApplicationShutdown { throw new ApiError({ message: 'You have moved your account.', code: 'YOUR_ACCOUNT_MOVED', + kind: 'permission', id: '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31', - httpStatusCode: 403, }); } } @@ -278,6 +301,7 @@ export class ApiCallService implements OnApplicationShutdown { throw new ApiError({ message: 'You are not assigned to a moderator role.', code: 'ROLE_PERMISSION_DENIED', + kind: 'permission', id: 'd33d5333-db36-423d-a8f9-1a2b9549da41', }); } @@ -285,6 +309,7 @@ export class ApiCallService implements OnApplicationShutdown { throw new ApiError({ message: 'You are not assigned to an administrator role.', code: 'ROLE_PERMISSION_DENIED', + kind: 'permission', id: 'c3d38592-54c0-429d-be96-5636b0431a61', }); } @@ -296,6 +321,7 @@ export class ApiCallService implements OnApplicationShutdown { throw new ApiError({ message: 'You are not assigned to a required role.', code: 'ROLE_PERMISSION_DENIED', + kind: 'permission', id: '7f86f06f-7e15-4057-8561-f4b6d4ac755a', }); } @@ -305,6 +331,7 @@ export class ApiCallService implements OnApplicationShutdown { throw new ApiError({ message: 'Your app does not have the necessary permissions to use this endpoint.', code: 'PERMISSION_DENIED', + kind: 'permission', id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838', }); } @@ -317,7 +344,7 @@ export class ApiCallService implements OnApplicationShutdown { try { data[k] = JSON.parse(data[k]); } catch (e) { - throw new ApiError({ + throw new ApiError({ message: 'Invalid param.', code: 'INVALID_PARAM', id: '0b5f1631-7c1a-41a6-b399-cce335f34d85', @@ -335,7 +362,7 @@ export class ApiCallService implements OnApplicationShutdown { if (err instanceof ApiError || err instanceof AuthenticationError) { throw err; } else { - const errId = uuid(); + const errId = randomUUID(); this.logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, { ep: ep.name, ps: data, diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts index 4ad0197d87..8b0fff80d9 100644 --- a/packages/backend/src/server/api/AuthenticateService.ts +++ b/packages/backend/src/server/api/AuthenticateService.ts @@ -40,15 +40,15 @@ export class AuthenticateService implements OnApplicationShutdown { if (token == null) { return [null, null]; } - + if (isNativeToken(token)) { const user = await this.cacheService.localUserByNativeTokenCache.fetch(token, () => this.usersRepository.findOneBy({ token }) as Promise<LocalUser | null>); - + if (user == null) { throw new AuthenticationError('user not found'); } - + return [user, null]; } else { const accessToken = await this.accessTokensRepository.findOne({ @@ -58,24 +58,24 @@ export class AuthenticateService implements OnApplicationShutdown { token: token, // miauth }], }); - + if (accessToken == null) { throw new AuthenticationError('invalid signature'); } - + this.accessTokensRepository.update(accessToken.id, { lastUsedAt: new Date(), }); - + const user = await this.cacheService.localUserByIdCache.fetch(accessToken.userId, () => this.usersRepository.findOneBy({ id: accessToken.userId, }) as Promise<LocalUser>); - + if (accessToken.appId) { const app = await this.appCache.fetch(accessToken.appId, () => this.appsRepository.findOneByOrFail({ id: accessToken.appId! })); - + return [user, { id: accessToken.id, permission: app.permission, diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index d1ff3fe925..4e6bc46e67 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -38,7 +38,8 @@ import * as ep___admin_federation_updateInstance from './endpoints/admin/federat import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js'; import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js'; import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js'; -import * as ep___invite from './endpoints/invite.js'; +import * as ep___admin_invite_create from './endpoints/admin/invite/create.js'; +import * as ep___admin_invite_list from './endpoints/admin/invite/list.js'; import * as ep___admin_promo_create from './endpoints/admin/promo/create.js'; import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js'; import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js'; @@ -230,6 +231,10 @@ import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js'; import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js'; import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js'; import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js'; +import * as ep___invite_create from './endpoints/invite/create.js'; +import * as ep___invite_delete from './endpoints/invite/delete.js'; +import * as ep___invite_list from './endpoints/invite/list.js'; +import * as ep___invite_limit from './endpoints/invite/limit.js'; import * as ep___meta from './endpoints/meta.js'; import * as ep___emojis from './endpoints/emojis.js'; import * as ep___emoji from './endpoints/emoji.js'; @@ -378,7 +383,8 @@ const $admin_federation_updateInstance: Provider = { provide: 'ep:admin/federati const $admin_getIndexStats: Provider = { provide: 'ep:admin/get-index-stats', useClass: ep___admin_getIndexStats.default }; const $admin_getTableStats: Provider = { provide: 'ep:admin/get-table-stats', useClass: ep___admin_getTableStats.default }; const $admin_getUserIps: Provider = { provide: 'ep:admin/get-user-ips', useClass: ep___admin_getUserIps.default }; -const $invite: Provider = { provide: 'ep:invite', useClass: ep___invite.default }; +const $admin_invite_create: Provider = { provide: 'ep:admin/invite/create', useClass: ep___admin_invite_create.default }; +const $admin_invite_list: Provider = { provide: 'ep:admin/invite/list', useClass: ep___admin_invite_list.default }; const $admin_promo_create: Provider = { provide: 'ep:admin/promo/create', useClass: ep___admin_promo_create.default }; const $admin_queue_clear: Provider = { provide: 'ep:admin/queue/clear', useClass: ep___admin_queue_clear.default }; const $admin_queue_deliverDelayed: Provider = { provide: 'ep:admin/queue/deliver-delayed', useClass: ep___admin_queue_deliverDelayed.default }; @@ -570,6 +576,10 @@ const $i_webhooks_list: Provider = { provide: 'ep:i/webhooks/list', useClass: ep const $i_webhooks_show: Provider = { provide: 'ep:i/webhooks/show', useClass: ep___i_webhooks_show.default }; const $i_webhooks_update: Provider = { provide: 'ep:i/webhooks/update', useClass: ep___i_webhooks_update.default }; const $i_webhooks_delete: Provider = { provide: 'ep:i/webhooks/delete', useClass: ep___i_webhooks_delete.default }; +const $invite_create: Provider = { provide: 'ep:invite/create', useClass: ep___invite_create.default }; +const $invite_delete: Provider = { provide: 'ep:invite/delete', useClass: ep___invite_delete.default }; +const $invite_list: Provider = { provide: 'ep:invite/list', useClass: ep___invite_list.default }; +const $invite_limit: Provider = { provide: 'ep:invite/limit', useClass: ep___invite_limit.default }; const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default }; const $emojis: Provider = { provide: 'ep:emojis', useClass: ep___emojis.default }; const $emoji: Provider = { provide: 'ep:emoji', useClass: ep___emoji.default }; @@ -722,7 +732,8 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $admin_getIndexStats, $admin_getTableStats, $admin_getUserIps, - $invite, + $admin_invite_create, + $admin_invite_list, $admin_promo_create, $admin_queue_clear, $admin_queue_deliverDelayed, @@ -914,6 +925,10 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_webhooks_show, $i_webhooks_update, $i_webhooks_delete, + $invite_create, + $invite_delete, + $invite_list, + $invite_limit, $meta, $emojis, $emoji, @@ -1060,7 +1075,8 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $admin_getIndexStats, $admin_getTableStats, $admin_getUserIps, - $invite, + $admin_invite_create, + $admin_invite_list, $admin_promo_create, $admin_queue_clear, $admin_queue_deliverDelayed, @@ -1252,6 +1268,10 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_webhooks_show, $i_webhooks_update, $i_webhooks_delete, + $invite_create, + $invite_delete, + $invite_list, + $invite_limit, $meta, $emojis, $emoji, diff --git a/packages/backend/src/server/api/RateLimiterService.ts b/packages/backend/src/server/api/RateLimiterService.ts index fe2db1d66a..f6ffbfab50 100644 --- a/packages/backend/src/server/api/RateLimiterService.ts +++ b/packages/backend/src/server/api/RateLimiterService.ts @@ -38,14 +38,14 @@ export class RateLimiterService { max: 1, db: this.redisClient, }); - + minIntervalLimiter.get((err, info) => { if (err) { return reject('ERR'); } - + this.logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`); - + if (info.remaining === 0) { reject('BRIEF_REQUEST_INTERVAL'); } else { @@ -57,7 +57,7 @@ export class RateLimiterService { } }); }; - + // Long term limit const max = (): void => { const limiter = new Limiter({ @@ -66,14 +66,14 @@ export class RateLimiterService { max: limitation.max! / factor, db: this.redisClient, }); - + limiter.get((err, info) => { if (err) { return reject('ERR'); } - + this.logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`); - + if (info.remaining === 0) { reject('RATE_LIMIT_EXCEEDED'); } else { @@ -81,13 +81,13 @@ export class RateLimiterService { } }); }; - + const hasShortTermLimit = typeof limitation.minInterval === 'number'; - + const hasLongTermLimit = typeof limitation.duration === 'number' && typeof limitation.max === 'number'; - + if (hasShortTermLimit) { min(); } else if (hasLongTermLimit) { diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts index aaf1d10b42..96666f1f49 100644 --- a/packages/backend/src/server/api/SigninService.ts +++ b/packages/backend/src/server/api/SigninService.ts @@ -36,7 +36,7 @@ export class SigninService { headers: request.headers as any, success: true, }).then(x => this.signinsRepository.findOneByOrFail(x.identifiers[0])); - + // Publish signin event this.globalEventService.publishMainStream(user.id, 'signin', await this.signinEntityService.pack(record)); }); diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index b2bd7d82e7..7b215cea79 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -1,9 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; -import rndstr from 'rndstr'; import bcrypt from 'bcryptjs'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository, RegistrationTicket } from '@/models/index.js'; import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; import { CaptchaService } from '@/core/CaptchaService.js'; @@ -14,6 +13,7 @@ import { EmailService } from '@/core/EmailService.js'; import { LocalUser } from '@/models/entities/User.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { bindThis } from '@/decorators.js'; +import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js'; import { SigninService } from './SigninService.js'; import type { FastifyRequest, FastifyReply } from 'fastify'; @@ -67,7 +67,7 @@ export class SignupApiService { const body = request.body; const instance = await this.metaService.fetch(true); - + // Verify *Captcha // ただしテスト時はこの機構は障害となるため無効にする if (process.env.NODE_ENV !== 'test') { @@ -76,7 +76,7 @@ export class SignupApiService { throw new FastifyReplyError(400, err); }); } - + if (instance.enableRecaptcha && instance.recaptchaSecretKey) { await this.captchaService.verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => { throw new FastifyReplyError(400, err); @@ -89,51 +89,61 @@ export class SignupApiService { }); } } - + const username = body['username']; const password = body['password']; const host: string | null = process.env.NODE_ENV === 'test' ? (body['host'] ?? null) : null; const invitationCode = body['invitationCode']; const emailAddress = body['emailAddress']; - + if (instance.emailRequiredForSignup) { if (emailAddress == null || typeof emailAddress !== 'string') { reply.code(400); return; } - + const res = await this.emailService.validateEmailForAccount(emailAddress); if (!res.available) { reply.code(400); return; } } - + + let ticket: RegistrationTicket | null = null; + if (instance.disableRegistration) { if (invitationCode == null || typeof invitationCode !== 'string') { reply.code(400); return; } - - const ticket = await this.registrationTicketsRepository.findOneBy({ + + ticket = await this.registrationTicketsRepository.findOneBy({ code: invitationCode, }); - + if (ticket == null) { reply.code(400); return; } - - this.registrationTicketsRepository.delete(ticket.id); + + if (ticket.expiresAt && ticket.expiresAt < new Date()) { + reply.code(400); + return; + } + + if (ticket.usedAt) { + reply.code(400); + return; + } } - + if (instance.emailRequiredForSignup) { - if (await this.usersRepository.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull() })) { + if (await this.usersRepository.exist({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) { throw new FastifyReplyError(400, 'DUPLICATED_USERNAME'); } // Check deleted username duplication - if (await this.usedUsernamesRepository.findOneBy({ username: username.toLowerCase() })) { + if (await this.usedUsernamesRepository.exist({ where: { username: username.toLowerCase() } })) { throw new FastifyReplyError(400, 'USED_USERNAME'); } @@ -142,20 +152,20 @@ export class SignupApiService { throw new FastifyReplyError(400, 'DENIED_USERNAME'); } - const code = rndstr('a-z0-9', 16); + const code = secureRndstr(16, { chars: L_CHARS }); // Generate hash of password const salt = await bcrypt.genSalt(8); const hash = await bcrypt.hash(password, salt); - await this.userPendingsRepository.insert({ + const pendingUser = await this.userPendingsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), code, email: emailAddress!, username: username, password: hash, - }); + }).then(x => this.userPendingsRepository.findOneByOrFail(x.identifiers[0])); const link = `${this.config.url}/signup-complete/${code}`; @@ -163,6 +173,13 @@ export class SignupApiService { `To complete signup, please click this link:<br><a href="${link}">${link}</a>`, `To complete signup, please click this link: ${link}`); + if (ticket) { + await this.registrationTicketsRepository.update(ticket.id, { + usedAt: new Date(), + pendingUserId: pendingUser.id, + }); + } + reply.code(204); return; } else { @@ -170,12 +187,20 @@ export class SignupApiService { const { account, secret } = await this.signupService.signup({ username, password, host, }); - + const res = await this.userEntityService.pack(account, account, { detail: true, includeSecrets: true, }); - + + if (ticket) { + await this.registrationTicketsRepository.update(ticket.id, { + usedAt: new Date(), + usedBy: account, + usedById: account.id, + }); + } + return { ...res, token: secret, @@ -212,6 +237,15 @@ export class SignupApiService { emailVerifyCode: null, }); + const ticket = await this.registrationTicketsRepository.findOneBy({ pendingUserId: pendingUser.id }); + if (ticket) { + await this.registrationTicketsRepository.update(ticket.id, { + usedBy: account, + usedById: account.id, + pendingUserId: null, + }); + } + return this.signinService.signin(request, reply, account as LocalUser); } catch (err) { throw new FastifyReplyError(400, typeof err === 'string' ? err : (err as Error).toString()); diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index d1394d6d76..e4291becf0 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -10,7 +10,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { CacheService } from '@/core/CacheService.js'; -import { LocalUser } from '@/models/entities/User'; +import { LocalUser } from '@/models/entities/User.js'; import { AuthenticateService, AuthenticationError } from './AuthenticateService.js'; import MainStreamConnection from './stream/index.js'; import { ChannelsService } from './stream/ChannelsService.js'; @@ -58,11 +58,21 @@ export class StreamingApiServerService { let user: LocalUser | null = null; let app: AccessToken | null = null; + // https://datatracker.ietf.org/doc/html/rfc6750.html#section-2.1 + // Note that the standard WHATWG WebSocket API does not support setting any headers, + // but non-browser apps may still be able to set it. + const token = request.headers.authorization?.startsWith('Bearer ') + ? request.headers.authorization.slice(7) + : q.get('i'); + try { - [user, app] = await this.authenticateService.authenticate(q.get('i')); + [user, app] = await this.authenticateService.authenticate(token); } catch (e) { if (e instanceof AuthenticationError) { - socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); + socket.write([ + 'HTTP/1.1 401 Unauthorized', + 'WWW-Authenticate: Bearer realm="Misskey", error="invalid_token", error_description="Failed to authenticate"', + ].join('\r\n') + '\r\n\r\n'); } else { socket.write('HTTP/1.1 500 Internal Server Error\r\n\r\n'); } @@ -93,6 +103,13 @@ export class StreamingApiServerService { }); }); + const globalEv = new EventEmitter(); + + this.redisForSub.on('message', (_: string, data: string) => { + const parsed = JSON.parse(data); + globalEv.emit('message', parsed); + }); + this.#wss.on('connection', async (connection: WebSocket.WebSocket, request: http.IncomingMessage, ctx: { stream: MainStreamConnection, user: LocalUser | null; @@ -102,12 +119,11 @@ export class StreamingApiServerService { const ev = new EventEmitter(); - async function onRedisMessage(_: string, data: string): Promise<void> { - const parsed = JSON.parse(data); - ev.emit(parsed.channel, parsed.message); + function onRedisMessage(data: any): void { + ev.emit(data.channel, data.message); } - this.redisForSub.on('message', onRedisMessage); + globalEv.on('message', onRedisMessage); await stream.listen(ev, connection); @@ -127,7 +143,7 @@ export class StreamingApiServerService { connection.once('close', () => { ev.removeAllListeners(); stream.dispose(); - this.redisForSub.off('message', onRedisMessage); + globalEv.off('message', onRedisMessage); this.#connections.delete(connection); if (userUpdateIntervalId) clearInterval(userUpdateIntervalId); }); diff --git a/packages/backend/src/server/api/endpoint-base.ts b/packages/backend/src/server/api/endpoint-base.ts index 1555a3ca46..364fa7a19b 100644 --- a/packages/backend/src/server/api/endpoint-base.ts +++ b/packages/backend/src/server/api/endpoint-base.ts @@ -1,11 +1,13 @@ import * as fs from 'node:fs'; -import Ajv from 'ajv'; +import _Ajv from 'ajv'; import type { Schema, SchemaType } from '@/misc/json-schema.js'; import type { LocalUser } from '@/models/entities/User.js'; import type { AccessToken } from '@/models/entities/AccessToken.js'; import { ApiError } from './error.js'; import type { IEndpointMeta } from './endpoints.js'; +const Ajv = _Ajv.default; + const ajv = new Ajv({ useDefaults: true, }); @@ -32,23 +34,23 @@ export abstract class Endpoint<T extends IEndpointMeta, Ps extends Schema> { this.exec = (params: any, user: T['requireCredential'] extends true ? LocalUser : LocalUser | null, token: AccessToken | null, file?: File, ip?: string | null, headers?: Record<string, string> | null) => { let cleanup: undefined | (() => void) = undefined; - + if (meta.requireFile) { cleanup = () => { if (file) fs.unlink(file.path, () => {}); }; - + if (file == null) return Promise.reject(new ApiError({ message: 'File required.', code: 'FILE_REQUIRED', id: '4267801e-70d1-416a-b011-4ee502885d8b', })); } - + const valid = validate(params); if (!valid) { if (file) cleanup!(); - + const errors = validate.errors!; const err = new ApiError({ message: 'Invalid param.', @@ -60,7 +62,7 @@ export abstract class Endpoint<T extends IEndpointMeta, Ps extends Schema> { }); return Promise.reject(err); } - + return cb(params as SchemaType<Ps>, user, token, file, cleanup, ip, headers); }; } diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 94206ef870..41c3a29eec 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -38,7 +38,8 @@ import * as ep___admin_federation_updateInstance from './endpoints/admin/federat import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js'; import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js'; import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js'; -import * as ep___invite from './endpoints/invite.js'; +import * as ep___admin_invite_create from './endpoints/admin/invite/create.js'; +import * as ep___admin_invite_list from './endpoints/admin/invite/list.js'; import * as ep___admin_promo_create from './endpoints/admin/promo/create.js'; import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js'; import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js'; @@ -230,6 +231,10 @@ import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js'; import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js'; import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js'; import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js'; +import * as ep___invite_create from './endpoints/invite/create.js'; +import * as ep___invite_delete from './endpoints/invite/delete.js'; +import * as ep___invite_list from './endpoints/invite/list.js'; +import * as ep___invite_limit from './endpoints/invite/limit.js'; import * as ep___meta from './endpoints/meta.js'; import * as ep___emojis from './endpoints/emojis.js'; import * as ep___emoji from './endpoints/emoji.js'; @@ -376,7 +381,8 @@ const eps = [ ['admin/get-index-stats', ep___admin_getIndexStats], ['admin/get-table-stats', ep___admin_getTableStats], ['admin/get-user-ips', ep___admin_getUserIps], - ['invite', ep___invite], + ['admin/invite/create', ep___admin_invite_create], + ['admin/invite/list', ep___admin_invite_list], ['admin/promo/create', ep___admin_promo_create], ['admin/queue/clear', ep___admin_queue_clear], ['admin/queue/deliver-delayed', ep___admin_queue_deliverDelayed], @@ -568,6 +574,10 @@ const eps = [ ['i/webhooks/show', ep___i_webhooks_show], ['i/webhooks/update', ep___i_webhooks_update], ['i/webhooks/delete', ep___i_webhooks_delete], + ['invite/create', ep___invite_create], + ['invite/delete', ep___invite_delete], + ['invite/list', ep___invite_list], + ['invite/limit', ep___invite_limit], ['meta', ep___meta], ['emojis', ep___emojis], ['emoji', ep___emoji], diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index 9bba16166f..b8ea74b7c5 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -115,7 +115,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { case 'remote': query.andWhere('report.targetUserHost IS NOT NULL'); break; } - const reports = await query.take(ps.limit).getMany(); + const reports = await query.limit(ps.limit).getMany(); return await this.abuseUserReportEntityService.packMany(reports); }); diff --git a/packages/backend/src/server/api/endpoints/admin/ad/create.ts b/packages/backend/src/server/api/endpoints/admin/ad/create.ts index 917242db3f..757030839e 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts @@ -22,8 +22,9 @@ export const paramDef = { expiresAt: { type: 'integer' }, startsAt: { type: 'integer' }, imageUrl: { type: 'string', minLength: 1 }, + dayOfWeek: { type: 'integer' }, }, - required: ['url', 'memo', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'imageUrl'], + required: ['url', 'memo', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'imageUrl', 'dayOfWeek'], } as const; // eslint-disable-next-line import/no-default-export @@ -41,6 +42,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { createdAt: new Date(), expiresAt: new Date(ps.expiresAt), startsAt: new Date(ps.startsAt), + dayOfWeek: ps.dayOfWeek, url: ps.url, imageUrl: ps.imageUrl, priority: ps.priority, diff --git a/packages/backend/src/server/api/endpoints/admin/ad/list.ts b/packages/backend/src/server/api/endpoints/admin/ad/list.ts index 0b6d006052..725ddb58be 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/list.ts @@ -32,7 +32,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { ) { super(meta, paramDef, async (ps, me) => { const query = this.queryService.makePaginationQuery(this.adsRepository.createQueryBuilder('ad'), ps.sinceId, ps.untilId); - const ads = await query.take(ps.limit).getMany(); + const ads = await query.limit(ps.limit).getMany(); return ads; }); diff --git a/packages/backend/src/server/api/endpoints/admin/ad/update.ts b/packages/backend/src/server/api/endpoints/admin/ad/update.ts index dbab7e9d4f..70082290ba 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts @@ -31,8 +31,9 @@ export const paramDef = { ratio: { type: 'integer' }, expiresAt: { type: 'integer' }, startsAt: { type: 'integer' }, + dayOfWeek: { type: 'integer' }, }, - required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt'], + required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'dayOfWeek'], } as const; // eslint-disable-next-line import/no-default-export @@ -56,6 +57,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { imageUrl: ps.imageUrl, expiresAt: new Date(ps.expiresAt), startsAt: new Date(ps.startsAt), + dayOfWeek: ps.dayOfWeek, }); }); } diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index 9b20494129..11231f6e04 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -80,7 +80,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { super(meta, paramDef, async (ps, me) => { const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); - const announcements = await query.take(ps.limit).getMany(); + const announcements = await query.limit(ps.limit).getMany(); const reads = new Map<Announcement, number>(); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts index 12db1f78fb..8cf9341a71 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts @@ -47,7 +47,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { title: ps.title, text: ps.text, /* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */ - imageUrl: ps.imageUrl || null, + imageUrl: ps.imageUrl || null, }); }); } diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts index 8a4498d5fa..2901fdb774 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts @@ -76,7 +76,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } } - const files = await query.take(ps.limit).getMany(); + const files = await query.limit(ps.limit).getMany(); return await this.driveFileEntityService.packMany(files, { detail: true, withUser: true, self: true }); }); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index 509224e9c3..200ede0b06 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -1,10 +1,10 @@ import { Inject, Injectable } from '@nestjs/common'; -import rndstr from 'rndstr'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -56,6 +56,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private customEmojiService: CustomEmojiService, + private emojiEntityService: EmojiEntityService, private moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { @@ -78,9 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { emojiId: emoji.id, }); - return { - id: emoji.id, - }; + return this.emojiEntityService.packDetailed(emoji); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts index df3c28deff..8d50413e95 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts @@ -98,7 +98,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const emojis = await q .orderBy('emoji.id', 'DESC') - .take(ps.limit) + .limit(ps.limit) .getMany(); return this.emojiEntityService.packDetailedMany(emojis); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts index 4aa4ad82b4..29b20fab86 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -84,14 +84,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { if (ps.query) { //q.andWhere('emoji.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }); - //const emojis = await q.take(ps.limit).getMany(); + //const emojis = await q.limit(ps.limit).getMany(); emojis = await q.getMany(); const queryarry = ps.query.match(/\:([a-z0-9_]*)\:/g); if (queryarry) { - emojis = emojis.filter(emoji => - queryarry.includes(`:${emoji.name}:`) + emojis = emojis.filter(emoji => + queryarry.includes(`:${emoji.name}:`), ); } else { emojis = emojis.filter(emoji => @@ -101,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } emojis.splice(ps.limit + 1); } else { - emojis = await q.take(ps.limit).getMany(); + emojis = await q.limit(ps.limit).getMany(); } return this.emojiEntityService.packDetailedMany(emojis); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts index fb22bdc477..edc1af5a53 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -70,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); if (driveFile == null) throw new ApiError(meta.errors.noSuchFile); } - + await this.customEmojiService.update(ps.id, { driveFile, name: ps.name, diff --git a/packages/backend/src/server/api/endpoints/admin/invite/create.ts b/packages/backend/src/server/api/endpoints/admin/invite/create.ts new file mode 100644 index 0000000000..664b4d819f --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/invite/create.ts @@ -0,0 +1,80 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { RegistrationTicketsRepository } from '@/models/index.js'; +import { InviteCodeEntityService } from '@/core/entities/InviteCodeEntityService.js'; +import { IdService } from '@/core/IdService.js'; +import { DI } from '@/di-symbols.js'; +import { generateInviteCode } from '@/misc/generate-invite-code.js'; +import { ApiError } from '../../../error.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + errors: { + invalidDateTime: { + message: 'Invalid date-time format', + code: 'INVALID_DATE_TIME', + id: 'f1380b15-3760-4c6c-a1db-5c3aaf1cbd49', + }, + }, + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + code: { + type: 'string', + optional: false, nullable: false, + example: 'GR6S02ERUA5VR', + }, + }, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + count: { type: 'integer', minimum: 1, maximum: 100, default: 1 }, + expiresAt: { type: 'string', nullable: true }, + }, + required: [], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { + constructor( + @Inject(DI.registrationTicketsRepository) + private registrationTicketsRepository: RegistrationTicketsRepository, + + private inviteCodeEntityService: InviteCodeEntityService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + if (ps.expiresAt && isNaN(Date.parse(ps.expiresAt))) { + throw new ApiError(meta.errors.invalidDateTime); + } + + const ticketsPromises = []; + + for (let i = 0; i < ps.count; i++) { + ticketsPromises.push(this.registrationTicketsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null, + code: generateInviteCode(), + }).then(x => this.registrationTicketsRepository.findOneByOrFail(x.identifiers[0]))); + } + + const tickets = await Promise.all(ticketsPromises); + return await this.inviteCodeEntityService.packMany(tickets, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/invite/list.ts b/packages/backend/src/server/api/endpoints/admin/invite/list.ts new file mode 100644 index 0000000000..5d7a7f632c --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/invite/list.ts @@ -0,0 +1,70 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { RegistrationTicketsRepository } from '@/models/index.js'; +import { InviteCodeEntityService } from '@/core/entities/InviteCodeEntityService.js'; +import { DI } from '@/di-symbols.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, + offset: { type: 'integer', default: 0 }, + type: { type: 'string', enum: ['unused', 'used', 'expired', 'all'], default: 'all' }, + sort: { type: 'string', enum: ['+createdAt', '-createdAt', '+usedAt', '-usedAt'] }, + }, + required: [], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { + constructor( + @Inject(DI.registrationTicketsRepository) + private registrationTicketsRepository: RegistrationTicketsRepository, + + private inviteCodeEntityService: InviteCodeEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registrationTicketsRepository.createQueryBuilder('ticket') + .leftJoinAndSelect('ticket.createdBy', 'createdBy') + .leftJoinAndSelect('ticket.usedBy', 'usedBy'); + + switch (ps.type) { + case 'unused': query.andWhere('ticket.usedBy IS NULL'); break; + case 'used': query.andWhere('ticket.usedBy IS NOT NULL'); break; + case 'expired': query.andWhere('ticket.expiresAt < :now', { now: new Date() }); break; + } + + switch (ps.sort) { + case '+createdAt': query.orderBy('ticket.createdAt', 'DESC'); break; + case '-createdAt': query.orderBy('ticket.createdAt', 'ASC'); break; + case '+usedAt': query.orderBy('ticket.usedAt', 'DESC', 'NULLS LAST'); break; + case '-usedAt': query.orderBy('ticket.usedAt', 'ASC', 'NULLS FIRST'); break; + default: query.orderBy('ticket.id', 'DESC'); break; + } + + query.limit(ps.limit); + query.skip(ps.offset); + + const tickets = await query.getMany(); + + return await this.inviteCodeEntityService.packMany(tickets, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 4cc1b6011f..084bdb598b 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -1,5 +1,4 @@ import { Inject, Injectable } from '@nestjs/common'; -import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { MetaService } from '@/core/MetaService.js'; import type { Config } from '@/config.js'; @@ -20,6 +19,10 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + cacheRemoteSensitiveFiles: { + type: 'boolean', + optional: false, nullable: false, + }, emailRequiredForSignup: { type: 'boolean', optional: false, nullable: false, @@ -262,6 +265,14 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + enableServerMachineStats: { + type: 'boolean', + optional: false, nullable: false, + }, + enableIdenticonGeneration: { + type: 'boolean', + optional: false, nullable: false, + }, policies: { type: 'object', optional: false, nullable: false, @@ -324,6 +335,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { enableServiceWorker: instance.enableServiceWorker, translatorAvailable: instance.deeplAuthKey != null, cacheRemoteFiles: instance.cacheRemoteFiles, + cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles, pinnedUsers: instance.pinnedUsers, hiddenTags: instance.hiddenTags, blockedHosts: instance.blockedHosts, @@ -364,6 +376,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { enableActiveEmailValidation: instance.enableActiveEmailValidation, enableChartsForRemoteUser: instance.enableChartsForRemoteUser, enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances, + enableServerMachineStats: instance.enableServerMachineStats, + enableIdenticonGeneration: instance.enableIdenticonGeneration, policies: { ...DEFAULT_POLICIES, ...instance.policies }, }; }); diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts index bee1ffbaee..8401cf51d9 100644 --- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts @@ -50,9 +50,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { throw e; }); - const exist = await this.promoNotesRepository.findOneBy({ noteId: note.id }); + const exist = await this.promoNotesRepository.exist({ where: { noteId: note.id } }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyPromoted); } diff --git a/packages/backend/src/server/api/endpoints/admin/queue/promote.ts b/packages/backend/src/server/api/endpoints/admin/queue/promote.ts index 4e57e6613e..8330d6c82f 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/promote.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/promote.ts @@ -33,15 +33,35 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { delayedQueues = await this.queueService.deliverQueue.getDelayed(); for (let queueIndex = 0; queueIndex < delayedQueues.length; queueIndex++) { const queue = delayedQueues[queueIndex]; - await queue.promote(); + try { + await queue.promote(); + } catch (e) { + if (e instanceof Error) { + if (e.message.indexOf('not in a delayed state') !== -1) { + throw e; + } + } else { + throw e; + } + } } break; - + case 'inbox': delayedQueues = await this.queueService.inboxQueue.getDelayed(); for (let queueIndex = 0; queueIndex < delayedQueues.length; queueIndex++) { const queue = delayedQueues[queueIndex]; - await queue.promote(); + try { + await queue.promote(); + } catch (e) { + if (e instanceof Error) { + if (e.message.indexOf('not in a delayed state') !== -1) { + throw e; + } + } else { + throw e; + } + } } break; } diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts index d263f99f6e..e9c3b0e69f 100644 --- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts @@ -1,9 +1,9 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; -import rndstr from 'rndstr'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository, UserProfilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; +import { secureRndstr } from '@/misc/secure-rndstr.js'; export const meta = { tags: ['admin'], @@ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { throw new Error('cannot reset password of root'); } - const passwd = rndstr('a-zA-Z0-9', 8); + const passwd = secureRndstr(8); // Generate hash of password const hash = bcrypt.hashSync(passwd); diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update.ts b/packages/backend/src/server/api/endpoints/admin/roles/update.ts index 467f157a61..1fedab4540 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/update.ts @@ -69,8 +69,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps) => { - const role = await this.rolesRepository.findOneBy({ id: ps.roleId }); - if (role == null) { + const roleExist = await this.rolesRepository.exist({ where: { id: ps.roleId } }); + if (!roleExist) { throw new ApiError(meta.errors.noSuchRole); } diff --git a/packages/backend/src/server/api/endpoints/admin/roles/users.ts b/packages/backend/src/server/api/endpoints/admin/roles/users.ts index 35edca5460..63650bb2bf 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/users.ts @@ -64,7 +64,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .innerJoinAndSelect('assign.user', 'user'); const assigns = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await Promise.all(assigns.map(async assign => ({ diff --git a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts index 24335a21cc..69c95ef19c 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts @@ -74,7 +74,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { super(meta, paramDef, async (ps, me) => { const query = this.queryService.makePaginationQuery(this.moderationLogsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId); - const reports = await query.take(ps.limit).getMany(); + const reports = await query.limit(ps.limit).getMany(); return await this.moderationLogEntityService.packMany(reports); }); diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index f49d2a0966..6f805b6b4e 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -61,6 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const signins = await this.signinsRepository.findBy({ userId: user.id }); + const roleAssigns = await this.roleService.getUserAssigns(user.id); const roles = await this.roleService.getUserRoles(user.id); return { @@ -85,6 +86,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { signins, policies: await this.roleService.getUserPolicies(user.id), roles: await this.roleEntityService.packMany(roles, me), + roleAssigns: roleAssigns.map(a => ({ + createdAt: a.createdAt.toISOString(), + expiresAt: a.expiresAt ? a.expiresAt.toISOString() : null, + roleId: a.roleId, + })), }; }); } diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index 426973f282..0a150d1dfd 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -104,7 +104,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { default: query.orderBy('user.id', 'ASC'); break; } - query.take(ps.limit); + query.limit(ps.limit); query.skip(ps.offset); const users = await query.getMany(); diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 1de5e9efd3..144360a921 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -43,6 +43,7 @@ export const paramDef = { defaultLightTheme: { type: 'string', nullable: true }, defaultDarkTheme: { type: 'string', nullable: true }, cacheRemoteFiles: { type: 'boolean' }, + cacheRemoteSensitiveFiles: { type: 'boolean' }, emailRequiredForSignup: { type: 'boolean' }, enableHcaptcha: { type: 'boolean' }, hcaptchaSiteKey: { type: 'string', nullable: true }, @@ -96,6 +97,8 @@ export const paramDef = { enableActiveEmailValidation: { type: 'boolean' }, enableChartsForRemoteUser: { type: 'boolean' }, enableChartsForFederatedInstances: { type: 'boolean' }, + enableServerMachineStats: { type: 'boolean' }, + enableIdenticonGeneration: { type: 'boolean' }, serverRules: { type: 'array', items: { type: 'string' } }, preservedUsernames: { type: 'array', items: { type: 'string' } }, }, @@ -134,7 +137,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { if (Array.isArray(ps.sensitiveWords)) { set.sensitiveWords = ps.sensitiveWords.filter(Boolean); } - + if (ps.themeColor !== undefined) { set.themeColor = ps.themeColor; } @@ -191,6 +194,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { set.cacheRemoteFiles = ps.cacheRemoteFiles; } + if (ps.cacheRemoteSensitiveFiles !== undefined) { + set.cacheRemoteSensitiveFiles = ps.cacheRemoteSensitiveFiles; + } + if (ps.emailRequiredForSignup !== undefined) { set.emailRequiredForSignup = ps.emailRequiredForSignup; } @@ -399,6 +406,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { set.enableChartsForFederatedInstances = ps.enableChartsForFederatedInstances; } + if (ps.enableServerMachineStats !== undefined) { + set.enableServerMachineStats = ps.enableServerMachineStats; + } + + if (ps.enableIdenticonGeneration !== undefined) { + set.enableIdenticonGeneration = ps.enableIdenticonGeneration; + } + if (ps.serverRules !== undefined) { set.serverRules = ps.serverRules; } diff --git a/packages/backend/src/server/api/endpoints/announcements.ts b/packages/backend/src/server/api/endpoints/announcements.ts index 79788be4e2..735af51ee2 100644 --- a/packages/backend/src/server/api/endpoints/announcements.ts +++ b/packages/backend/src/server/api/endpoints/announcements.ts @@ -79,7 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { super(meta, paramDef, async (ps, me) => { const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); - const announcements = await query.take(ps.limit).getMany(); + const announcements = await query.limit(ps.limit).getMany(); if (me) { const reads = (await this.announcementReadsRepository.findBy({ diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index e756a9b510..2c4247cb70 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -76,6 +76,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { throw new ApiError(meta.errors.noSuchAntenna); } + this.antennasRepository.update(antenna.id, { + isActive: true, + lastUsedAt: new Date(), + }); + const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 const noteIdsRes = await this.redisClient.xrevrange( `antennaTimeline:${antenna.id}`, @@ -112,11 +117,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { this.noteReadService.read(me.id, notes); } - this.antennasRepository.update(antenna.id, { - isActive: true, - lastUsedAt: new Date(), - }); - return await this.noteEntityService.packMany(notes, me); }); } diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index 5f980bdbeb..55218b644b 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -112,6 +112,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { withReplies: ps.withReplies, withFile: ps.withFile, notify: ps.notify, + isActive: true, + lastUsedAt: new Date(), }); this.globalEventService.publishInternalEvent('antennaUpdated', await this.antennasRepository.findOneByOrFail({ id: antenna.id })); diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts index c1d0a9dd74..aaef02d03f 100644 --- a/packages/backend/src/server/api/endpoints/app/create.ts +++ b/packages/backend/src/server/api/endpoints/app/create.ts @@ -44,7 +44,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { ) { super(meta, paramDef, async (ps, me) => { // Generate secret - const secret = secureRndstr(32, true); + const secret = secureRndstr(32); // for backward compatibility const permission = unique(ps.permission.map(v => v.replace(/^(.+)(\/|-)(read|write)$/, '$3:$1'))); diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index 05842460cf..aa199ab730 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -55,15 +55,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { throw new ApiError(meta.errors.noSuchSession); } - const accessToken = secureRndstr(32, true); + const accessToken = secureRndstr(32); // Fetch exist access token - const exist = await this.accessTokensRepository.findOneBy({ - appId: session.appId, - userId: me.id, + const exist = await this.accessTokensRepository.exist({ + where: { + appId: session.appId, + userId: me.id, + }, }); - if (exist == null) { + if (!exist) { const app = await this.appsRepository.findOneByOrFail({ id: session.appId }); // Generate Hash diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts index 6108d8202d..631fb4f024 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts @@ -1,4 +1,4 @@ -import { v4 as uuid } from 'uuid'; +import { randomUUID } from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { AppsRepository, AuthSessionsRepository } from '@/models/index.js'; @@ -71,7 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } // Generate token - const token = uuid(); + const token = randomUUID(); // Create session token document const doc = await this.authSessionsRepository.insert({ diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index d9ba99f209..4ad40c8f1c 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -84,12 +84,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { }); // Check if already blocking - const exist = await this.blockingsRepository.findOneBy({ - blockerId: blocker.id, - blockeeId: blockee.id, + const exist = await this.blockingsRepository.exist({ + where: { + blockerId: blocker.id, + blockeeId: blockee.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyBlocking); } diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index 46dd26a45a..ad3d9f22b3 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -5,8 +5,8 @@ import type { UsersRepository, BlockingsRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../error.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['account'], @@ -84,12 +84,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { }); // Check not blocking - const exist = await this.blockingsRepository.findOneBy({ - blockerId: blocker.id, - blockeeId: blockee.id, + const exist = await this.blockingsRepository.exist({ + where: { + blockerId: blocker.id, + blockeeId: blockee.id, + }, }); - if (exist == null) { + if (!exist) { throw new ApiError(meta.errors.notBlocking); } diff --git a/packages/backend/src/server/api/endpoints/blocking/list.ts b/packages/backend/src/server/api/endpoints/blocking/list.ts index 969aae06f9..d61bb0d214 100644 --- a/packages/backend/src/server/api/endpoints/blocking/list.ts +++ b/packages/backend/src/server/api/endpoints/blocking/list.ts @@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('blocking.blockerId = :meId', { meId: me.id }); const blockings = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await this.blockingEntityService.packMany(blockings, me); diff --git a/packages/backend/src/server/api/endpoints/channels/featured.ts b/packages/backend/src/server/api/endpoints/channels/featured.ts index 1a8d1164c7..953f027aa2 100644 --- a/packages/backend/src/server/api/endpoints/channels/featured.ts +++ b/packages/backend/src/server/api/endpoints/channels/featured.ts @@ -41,7 +41,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('channel.isArchived = FALSE') .orderBy('channel.lastNotedAt', 'DESC'); - const channels = await query.take(10).getMany(); + const channels = await query.limit(10).getMany(); return await Promise.all(channels.map(x => this.channelEntityService.pack(x, me))); }); diff --git a/packages/backend/src/server/api/endpoints/channels/followed.ts b/packages/backend/src/server/api/endpoints/channels/followed.ts index f49f3105d5..a1656903aa 100644 --- a/packages/backend/src/server/api/endpoints/channels/followed.ts +++ b/packages/backend/src/server/api/endpoints/channels/followed.ts @@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere({ followerId: me.id }); const followings = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await Promise.all(followings.map(x => this.channelEntityService.pack(x.followeeId, me))); diff --git a/packages/backend/src/server/api/endpoints/channels/owned.ts b/packages/backend/src/server/api/endpoints/channels/owned.ts index 8fae972cb1..4561bb2e94 100644 --- a/packages/backend/src/server/api/endpoints/channels/owned.ts +++ b/packages/backend/src/server/api/endpoints/channels/owned.ts @@ -49,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere({ userId: me.id }); const channels = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await Promise.all(channels.map(x => this.channelEntityService.pack(x, me))); diff --git a/packages/backend/src/server/api/endpoints/channels/search.ts b/packages/backend/src/server/api/endpoints/channels/search.ts index a3b40b0bbd..dfb6937964 100644 --- a/packages/backend/src/server/api/endpoints/channels/search.ts +++ b/packages/backend/src/server/api/endpoints/channels/search.ts @@ -61,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } const channels = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await Promise.all(channels.map(x => this.channelEntityService.pack(x, me))); diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index c881074bab..e3119cc40f 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -77,7 +77,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1 let noteIdsRes: [string, string[]][] = []; - + if (!ps.sinceId && !ps.sinceDate) { noteIdsRes = await this.redisClient.xrevrange( `channelTimeline:${channel.id}`, @@ -105,7 +105,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } //#endregion - timeline = await query.take(ps.limit).getMany(); + timeline = await query.limit(ps.limit).getMany(); } else { const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId); diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts index c3561e2a71..2837f2cf81 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -87,12 +87,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { throw e; }); - const exist = await this.clipNotesRepository.findOneBy({ - noteId: note.id, - clipId: clip.id, + const exist = await this.clipNotesRepository.exist({ + where: { + noteId: note.id, + clipId: clip.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyClipped); } diff --git a/packages/backend/src/server/api/endpoints/clips/favorite.ts b/packages/backend/src/server/api/endpoints/clips/favorite.ts index f08caaf8d7..ce09855531 100644 --- a/packages/backend/src/server/api/endpoints/clips/favorite.ts +++ b/packages/backend/src/server/api/endpoints/clips/favorite.ts @@ -58,12 +58,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { throw new ApiError(meta.errors.noSuchClip); } - const exist = await this.clipFavoritesRepository.findOneBy({ - clipId: clip.id, - userId: me.id, + const exist = await this.clipFavoritesRepository.exist({ + where: { + clipId: clip.id, + userId: me.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyFavorited); } diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts index dcb415b752..49607babee 100644 --- a/packages/backend/src/server/api/endpoints/clips/notes.ts +++ b/packages/backend/src/server/api/endpoints/clips/notes.ts @@ -88,7 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } const notes = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await this.noteEntityService.packMany(notes, me); diff --git a/packages/backend/src/server/api/endpoints/clips/remove-note.ts b/packages/backend/src/server/api/endpoints/clips/remove-note.ts index 50c5d758bd..d0ef795819 100644 --- a/packages/backend/src/server/api/endpoints/clips/remove-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/remove-note.ts @@ -2,8 +2,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../error.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['account', 'notes', 'clips'], diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts index 4609307774..f4343248b8 100644 --- a/packages/backend/src/server/api/endpoints/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/drive/files.ts @@ -73,7 +73,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { case '-size': query.orderBy('file.size', 'ASC'); break; } - const files = await query.take(ps.limit).getMany(); + const files = await query.limit(ps.limit).getMany(); return await this.driveFileEntityService.packMany(files, { detail: false, self: true }); }); diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts index 290cd4d2ce..cdcdde7e8a 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts @@ -34,12 +34,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private driveFilesRepository: DriveFilesRepository, ) { super(meta, paramDef, async (ps, me) => { - const file = await this.driveFilesRepository.findOneBy({ - md5: ps.md5, - userId: me.id, + const exist = await this.driveFilesRepository.exist({ + where: { + md5: ps.md5, + userId: me.id, + }, }); - return file != null; + return exist; }); } } diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index 3ecbba22b5..c43f812e2f 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -40,7 +40,7 @@ export const meta = { code: 'NO_SUCH_FOLDER', id: 'ea8fb7a5-af77-4a08-b608-c0218176cd73', }, - + restrictedByRole: { message: 'This feature is restricted by your role.', code: 'RESTRICTED_BY_ROLE', diff --git a/packages/backend/src/server/api/endpoints/drive/folders.ts b/packages/backend/src/server/api/endpoints/drive/folders.ts index b41eaf4463..eb674f3e15 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders.ts @@ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { query.andWhere('folder.parentId IS NULL'); } - const folders = await query.take(ps.limit).getMany(); + const folders = await query.limit(ps.limit).getMany(); return await Promise.all(folders.map(folder => this.driveFolderEntityService.pack(folder))); }); diff --git a/packages/backend/src/server/api/endpoints/drive/stream.ts b/packages/backend/src/server/api/endpoints/drive/stream.ts index 61bcfea0c3..a1c14a8e3f 100644 --- a/packages/backend/src/server/api/endpoints/drive/stream.ts +++ b/packages/backend/src/server/api/endpoints/drive/stream.ts @@ -56,7 +56,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } } - const files = await query.take(ps.limit).getMany(); + const files = await query.limit(ps.limit).getMany(); return await this.driveFileEntityService.packMany(files, { detail: false, self: true }); }); diff --git a/packages/backend/src/server/api/endpoints/emoji.ts b/packages/backend/src/server/api/endpoints/emoji.ts index 681d3e649e..51027f35c0 100644 --- a/packages/backend/src/server/api/endpoints/emoji.ts +++ b/packages/backend/src/server/api/endpoints/emoji.ts @@ -36,7 +36,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { constructor( @Inject(DI.config) private config: Config, - + @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, diff --git a/packages/backend/src/server/api/endpoints/emojis.ts b/packages/backend/src/server/api/endpoints/emojis.ts index 13cc709d31..3c2d0ce4a4 100644 --- a/packages/backend/src/server/api/endpoints/emojis.ts +++ b/packages/backend/src/server/api/endpoints/emojis.ts @@ -43,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { constructor( @Inject(DI.config) private config: Config, - + @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, diff --git a/packages/backend/src/server/api/endpoints/federation/followers.ts b/packages/backend/src/server/api/endpoints/federation/followers.ts index be1d6c8e58..1b2f9446f8 100644 --- a/packages/backend/src/server/api/endpoints/federation/followers.ts +++ b/packages/backend/src/server/api/endpoints/federation/followers.ts @@ -47,7 +47,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('following.followeeHost = :host', { host: ps.host }); const followings = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await this.followingEntityService.packMany(followings, me, { populateFollowee: true }); diff --git a/packages/backend/src/server/api/endpoints/federation/following.ts b/packages/backend/src/server/api/endpoints/federation/following.ts index 74656ce863..c5aa1ec60b 100644 --- a/packages/backend/src/server/api/endpoints/federation/following.ts +++ b/packages/backend/src/server/api/endpoints/federation/following.ts @@ -47,7 +47,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('following.followerHost = :host', { host: ps.host }); const followings = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await this.followingEntityService.packMany(followings, me, { populateFollowee: true }); diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index 061c6eb5be..ddf1a178b1 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -126,7 +126,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { query.andWhere('instance.host like :host', { host: '%' + sqlLikeEscape(ps.host.toLowerCase()) + '%' }); } - const instances = await query.take(ps.limit).skip(ps.offset).getMany(); + const instances = await query.limit(ps.limit).skip(ps.offset).getMany(); return await this.instanceEntityService.packMany(instances); }); diff --git a/packages/backend/src/server/api/endpoints/federation/users.ts b/packages/backend/src/server/api/endpoints/federation/users.ts index a028930f21..06f252005b 100644 --- a/packages/backend/src/server/api/endpoints/federation/users.ts +++ b/packages/backend/src/server/api/endpoints/federation/users.ts @@ -47,7 +47,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('user.host = :host', { host: ps.host }); const users = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await this.userEntityService.packMany(users, me, { detail: true }); diff --git a/packages/backend/src/server/api/endpoints/flash/featured.ts b/packages/backend/src/server/api/endpoints/flash/featured.ts index 570aef96d2..99c8763b11 100644 --- a/packages/backend/src/server/api/endpoints/flash/featured.ts +++ b/packages/backend/src/server/api/endpoints/flash/featured.ts @@ -40,7 +40,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('flash.likedCount > 0') .orderBy('flash.likedCount', 'DESC'); - const flashs = await query.take(10).getMany(); + const flashs = await query.limit(10).getMany(); return await this.flashEntityService.packMany(flashs, me); }); diff --git a/packages/backend/src/server/api/endpoints/flash/like.ts b/packages/backend/src/server/api/endpoints/flash/like.ts index 23de2f3970..57245f9f41 100644 --- a/packages/backend/src/server/api/endpoints/flash/like.ts +++ b/packages/backend/src/server/api/endpoints/flash/like.ts @@ -66,12 +66,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } // if already liked - const exist = await this.flashLikesRepository.findOneBy({ - flashId: flash.id, - userId: me.id, + const exist = await this.flashLikesRepository.exist({ + where: { + flashId: flash.id, + userId: me.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyLiked); } diff --git a/packages/backend/src/server/api/endpoints/flash/my-likes.ts b/packages/backend/src/server/api/endpoints/flash/my-likes.ts index f7716ea74a..7d1149ada9 100644 --- a/packages/backend/src/server/api/endpoints/flash/my-likes.ts +++ b/packages/backend/src/server/api/endpoints/flash/my-likes.ts @@ -59,7 +59,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .leftJoinAndSelect('like.flash', 'flash'); const likes = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return this.flashLikeEntityService.packMany(likes, me); diff --git a/packages/backend/src/server/api/endpoints/flash/my.ts b/packages/backend/src/server/api/endpoints/flash/my.ts index baed7f000f..45a3b50e08 100644 --- a/packages/backend/src/server/api/endpoints/flash/my.ts +++ b/packages/backend/src/server/api/endpoints/flash/my.ts @@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('flash.userId = :meId', { meId: me.id }); const flashs = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await this.flashEntityService.packMany(flashs); diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 4ad16de911..009fc96f64 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -99,12 +99,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { }); // Check if already following - const exist = await this.followingsRepository.findOneBy({ - followerId: follower.id, - followeeId: followee.id, + const exist = await this.followingsRepository.exist({ + where: { + followerId: follower.id, + followeeId: followee.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyFollowing); } diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index 4f12db1273..77ef263169 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -5,8 +5,8 @@ import type { UsersRepository, FollowingsRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../error.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['following', 'users'], @@ -84,12 +84,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { }); // Check not following - const exist = await this.followingsRepository.findOneBy({ - followerId: follower.id, - followeeId: followee.id, + const exist = await this.followingsRepository.exist({ + where: { + followerId: follower.id, + followeeId: followee.id, + }, }); - if (exist == null) { + if (!exist) { throw new ApiError(meta.errors.notFollowing); } diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts index 22304cacda..0e57f6328f 100644 --- a/packages/backend/src/server/api/endpoints/following/invalidate.ts +++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts @@ -5,8 +5,8 @@ import type { UsersRepository, FollowingsRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../error.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['following', 'users'], diff --git a/packages/backend/src/server/api/endpoints/following/requests/list.ts b/packages/backend/src/server/api/endpoints/following/requests/list.ts index d68248fab9..29588e8731 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/list.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/list.ts @@ -64,7 +64,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('request.followeeId = :meId', { meId: me.id }); const requests = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await Promise.all(requests.map(req => this.followRequestEntityService.pack(req))); diff --git a/packages/backend/src/server/api/endpoints/gallery/featured.ts b/packages/backend/src/server/api/endpoints/gallery/featured.ts index 9994ce90d7..46347247f0 100644 --- a/packages/backend/src/server/api/endpoints/gallery/featured.ts +++ b/packages/backend/src/server/api/endpoints/gallery/featured.ts @@ -41,7 +41,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('post.likedCount > 0') .orderBy('post.likedCount', 'DESC'); - const posts = await query.take(10).getMany(); + const posts = await query.limit(10).getMany(); return await this.galleryPostEntityService.packMany(posts, me); }); diff --git a/packages/backend/src/server/api/endpoints/gallery/popular.ts b/packages/backend/src/server/api/endpoints/gallery/popular.ts index 55d3dabfb0..4ee3d68a92 100644 --- a/packages/backend/src/server/api/endpoints/gallery/popular.ts +++ b/packages/backend/src/server/api/endpoints/gallery/popular.ts @@ -40,7 +40,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('post.likedCount > 0') .orderBy('post.likedCount', 'DESC'); - const posts = await query.take(10).getMany(); + const posts = await query.limit(10).getMany(); return await this.galleryPostEntityService.packMany(posts, me); }); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts.ts b/packages/backend/src/server/api/endpoints/gallery/posts.ts index e94003eb79..b9aac3fb34 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts.ts @@ -43,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId) .innerJoinAndSelect('post.user', 'user'); - const posts = await query.take(ps.limit).getMany(); + const posts = await query.limit(ps.limit).getMany(); return await this.galleryPostEntityService.packMany(posts, me); }); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts index 6ac5fa8606..c0bb55f640 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -66,12 +66,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } // if already liked - const exist = await this.galleryLikesRepository.findOneBy({ - postId: post.id, - userId: me.id, + const exist = await this.galleryLikesRepository.exist({ + where: { + postId: post.id, + userId: me.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyLiked); } diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts index dea0f4799c..810bde03e8 100644 --- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts +++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts @@ -9,6 +9,8 @@ export const meta = { tags: ['meta'], requireCredential: false, + allowGet: true, + cacheSec: 60 * 1, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/hashtags/list.ts b/packages/backend/src/server/api/endpoints/hashtags/list.ts index 226a11de0b..693d938bf0 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/list.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/list.ts @@ -73,7 +73,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { 'tag.attachedRemoteUsersCount', ]); - const tags = await query.take(ps.limit).getMany(); + const tags = await query.limit(ps.limit).getMany(); return this.hashtagEntityService.packMany(tags); }); diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts index 4f5f979767..e2e00def79 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/search.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts @@ -41,7 +41,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .where('tag.name like :q', { q: sqlLikeEscape(ps.query.toLowerCase()) + '%' }) .orderBy('tag.count', 'DESC') .groupBy('tag.id') - .take(ps.limit) + .limit(ps.limit) .skip(ps.offset) .getMany(); diff --git a/packages/backend/src/server/api/endpoints/hashtags/trend.ts b/packages/backend/src/server/api/endpoints/hashtags/trend.ts index cf45cc6c24..ce1cd9f01f 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/trend.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/trend.ts @@ -26,6 +26,8 @@ export const meta = { tags: ['hashtags'], requireCredential: false, + allowGet: true, + cacheSec: 60 * 1, res: { type: 'array', diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts index dd3549020e..b00b005add 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/users.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts @@ -39,7 +39,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, - + private userEntityService: UserEntityService, ) { super(meta, paramDef, async (ps, me) => { @@ -68,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { case '-updatedAt': query.orderBy('user.updatedAt', 'ASC'); break; } - const users = await query.take(ps.limit).getMany(); + const users = await query.limit(ps.limit).getMany(); return await this.userEntityService.packMany(users, me, { detail: true }); }); diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts index a3e3e02a12..4d593542db 100644 --- a/packages/backend/src/server/api/endpoints/i.ts +++ b/packages/backend/src/server/api/endpoints/i.ts @@ -23,7 +23,7 @@ export const meta = { id: 'e5b3b9f0-2b8f-4b9f-9c1f-8c5c1b2e1b1a', kind: 'permission', }, - } + }, } as const; export const paramDef = { @@ -68,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { }); userProfile.loggedInDates = [...userProfile.loggedInDates, today]; } - + return await this.userEntityService.pack<true, true>(userProfile.user!, userProfile.user!, { detail: true, includeSecrets: isSecure, diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index e8985a9cd8..a7e39fc028 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -103,7 +103,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const procedures = this.twoFactorAuthenticationService.getProcedures(); if (!(procedures as any)[attestation.fmt]) { - throw new Error('unsupported fmt'); + throw new Error(`unsupported fmt: ${attestation.fmt}. Supported ones: ${Object.keys(procedures)}`); } const verificationData = (procedures as any)[attestation.fmt].verify({ diff --git a/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts index d98f60fa5f..2ef5e5a279 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts @@ -61,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { if (key.userId !== me.id) { throw new ApiError(meta.errors.accessDenied); } - + await this.userSecurityKeysRepository.update(key.id, { name: ps.name, }); diff --git a/packages/backend/src/server/api/endpoints/i/favorites.ts b/packages/backend/src/server/api/endpoints/i/favorites.ts index ce8ab4962a..bdfb63974a 100644 --- a/packages/backend/src/server/api/endpoints/i/favorites.ts +++ b/packages/backend/src/server/api/endpoints/i/favorites.ts @@ -49,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .leftJoinAndSelect('favorite.note', 'note'); const favorites = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await this.noteFavoriteEntityService.packMany(favorites, me); diff --git a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts index d1b04cb655..915639e5f7 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts @@ -60,7 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .leftJoinAndSelect('like.post', 'post'); const likes = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await this.galleryLikeEntityService.packMany(likes, me); diff --git a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts index 32d14293f7..5ba9afd4a8 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts @@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('post.userId = :meId', { meId: me.id }); const posts = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await this.galleryPostEntityService.packMany(posts, me); diff --git a/packages/backend/src/server/api/endpoints/i/import-antennas.ts b/packages/backend/src/server/api/endpoints/i/import-antennas.ts index efb5ce4223..8582e98f76 100644 --- a/packages/backend/src/server/api/endpoints/i/import-antennas.ts +++ b/packages/backend/src/server/api/endpoints/i/import-antennas.ts @@ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { constructor ( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, - + @Inject(DI.antennasRepository) private antennasRepository: AntennasRepository, @@ -66,8 +66,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private downloadService: DownloadService, ) { super(meta, paramDef, async (ps, me) => { - const users = await this.usersRepository.findOneBy({ id: me.id }); - if (users === null) throw new ApiError(meta.errors.noSuchUser); + const userExist = await this.usersRepository.exist({ where: { id: me.id } }); + if (!userExist) throw new ApiError(meta.errors.noSuchUser); const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); if (file === null) throw new ApiError(meta.errors.noSuchFile); if (file.size === 0) throw new ApiError(meta.errors.emptyFile); @@ -79,6 +79,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { this.queueService.createImportAntennasJob(me, antennas); }); } -} +} export type Antenna = (_Antenna & { userListAccts: string[] | null })[]; diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts index 811971591a..32c16300fb 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -72,7 +72,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const checkMoving = await this.accountMoveService.validateAlsoKnownAs( me, (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(), - true + true, ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index 8af278c883..1926a1f503 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -71,7 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const checkMoving = await this.accountMoveService.validateAlsoKnownAs( me, (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(), - true + true, ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts index eb0f9ba474..34f2627563 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -72,7 +72,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const checkMoving = await this.accountMoveService.validateAlsoKnownAs( me, (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(), - true + true, ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts index 4568e93901..1b3cb5359d 100644 --- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -71,7 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const checkMoving = await this.accountMoveService.validateAlsoKnownAs( me, (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(), - true + true, ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); diff --git a/packages/backend/src/server/api/endpoints/i/page-likes.ts b/packages/backend/src/server/api/endpoints/i/page-likes.ts index 70e6e0a6a8..9f073ba596 100644 --- a/packages/backend/src/server/api/endpoints/i/page-likes.ts +++ b/packages/backend/src/server/api/endpoints/i/page-likes.ts @@ -59,7 +59,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .leftJoinAndSelect('like.page', 'page'); const likes = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return this.pageLikeEntityService.packMany(likes, me); diff --git a/packages/backend/src/server/api/endpoints/i/pages.ts b/packages/backend/src/server/api/endpoints/i/pages.ts index 285aa34e91..772486befc 100644 --- a/packages/backend/src/server/api/endpoints/i/pages.ts +++ b/packages/backend/src/server/api/endpoints/i/pages.ts @@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('page.userId = :meId', { meId: me.id }); const pages = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await this.pageEntityService.packMany(pages); diff --git a/packages/backend/src/server/api/endpoints/i/read-announcement.ts b/packages/backend/src/server/api/endpoints/i/read-announcement.ts index b8922b91e5..352fe54c5d 100644 --- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts +++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts @@ -47,19 +47,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { ) { super(meta, paramDef, async (ps, me) => { // Check if announcement exists - const announcement = await this.announcementsRepository.findOneBy({ id: ps.announcementId }); + const announcementExist = await this.announcementsRepository.exist({ where: { id: ps.announcementId } }); - if (announcement == null) { + if (!announcementExist) { throw new ApiError(meta.errors.noSuchAnnouncement); } // Check if already read - const read = await this.announcementReadsRepository.findOneBy({ - announcementId: ps.announcementId, - userId: me.id, + const alreadyRead = await this.announcementReadsRepository.exist({ + where: { + announcementId: ps.announcementId, + userId: me.id, + }, }); - if (read != null) { + if (alreadyRead) { return; } diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts index 93daeb0cd7..415a60147b 100644 --- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts +++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts @@ -28,9 +28,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { - const token = await this.accessTokensRepository.findOneBy({ id: ps.tokenId }); + const tokenExist = await this.accessTokensRepository.exist({ where: { id: ps.tokenId } }); - if (token) { + if (tokenExist) { await this.accessTokensRepository.delete({ id: ps.tokenId, userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/i/signin-history.ts b/packages/backend/src/server/api/endpoints/i/signin-history.ts index 9b30a24336..aa8cb5cf42 100644 --- a/packages/backend/src/server/api/endpoints/i/signin-history.ts +++ b/packages/backend/src/server/api/endpoints/i/signin-history.ts @@ -35,7 +35,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const query = this.queryService.makePaginationQuery(this.signinsRepository.createQueryBuilder('signin'), ps.sinceId, ps.untilId) .andWhere('signin.userId = :meId', { meId: me.id }); - const history = await query.take(ps.limit).getMany(); + const history = await query.limit(ps.limit).getMany(); return await Promise.all(history.map(record => this.signinEntityService.pack(record))); }); diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 4f543a6472..58e056bd37 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -1,5 +1,4 @@ import { Inject, Injectable } from '@nestjs/common'; -import rndstr from 'rndstr'; import ms from 'ms'; import bcrypt from 'bcryptjs'; import { Endpoint } from '@/server/api/endpoint-base.js'; @@ -9,6 +8,7 @@ import { EmailService } from '@/core/EmailService.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -94,7 +94,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { this.globalEventService.publishMainStream(me.id, 'meUpdated', iObj); if (ps.email != null) { - const code = rndstr('a-z0-9', 16); + const code = secureRndstr(16, { chars: L_CHARS }); await this.userProfilesRepository.update(me.id, { emailVerifyCode: code, diff --git a/packages/backend/src/server/api/endpoints/invite/create.ts b/packages/backend/src/server/api/endpoints/invite/create.ts new file mode 100644 index 0000000000..a64184be10 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/invite/create.ts @@ -0,0 +1,82 @@ +import { MoreThan } from 'typeorm'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { RegistrationTicketsRepository } from '@/models/index.js'; +import { InviteCodeEntityService } from '@/core/entities/InviteCodeEntityService.js'; +import { IdService } from '@/core/IdService.js'; +import { RoleService } from '@/core/RoleService.js'; +import { DI } from '@/di-symbols.js'; +import { generateInviteCode } from '@/misc/generate-invite-code.js'; +import { ApiError } from '../../error.js'; + +export const meta = { + tags: ['meta'], + + requireCredential: true, + requireRolePolicy: 'canInvite', + + errors: { + exceededCreateLimit: { + message: 'You have exceeded the limit for creating an invitation code.', + code: 'EXCEEDED_LIMIT_OF_CREATE_INVITE_CODE', + id: '8b165dd3-6f37-4557-8db1-73175d63c641', + }, + }, + + res: { + type: 'object', + optional: false, nullable: false, + properties: { + code: { + type: 'string', + optional: false, nullable: false, + example: 'GR6S02ERUA5VR', + }, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: {}, + required: [], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { + constructor( + @Inject(DI.registrationTicketsRepository) + private registrationTicketsRepository: RegistrationTicketsRepository, + + private inviteCodeEntityService: InviteCodeEntityService, + private idService: IdService, + private roleService: RoleService, + ) { + super(meta, paramDef, async (ps, me) => { + const policies = await this.roleService.getUserPolicies(me.id); + + if (policies.inviteLimit) { + const count = await this.registrationTicketsRepository.countBy({ + createdAt: MoreThan(new Date(Date.now() - (policies.inviteLimitCycle * 1000 * 60))), + createdById: me.id, + }); + + if (count >= policies.inviteLimit) { + throw new ApiError(meta.errors.exceededCreateLimit); + } + } + + const ticket = await this.registrationTicketsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + createdBy: me, + createdById: me.id, + expiresAt: policies.inviteExpirationTime ? new Date(Date.now() + (policies.inviteExpirationTime * 1000 * 60)) : null, + code: generateInviteCode(), + }).then(x => this.registrationTicketsRepository.findOneByOrFail(x.identifiers[0])); + + return await this.inviteCodeEntityService.pack(ticket, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/invite/delete.ts b/packages/backend/src/server/api/endpoints/invite/delete.ts new file mode 100644 index 0000000000..afca44954d --- /dev/null +++ b/packages/backend/src/server/api/endpoints/invite/delete.ts @@ -0,0 +1,71 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { RegistrationTicketsRepository } from '@/models/index.js'; +import { RoleService } from '@/core/RoleService.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../error.js'; + +export const meta = { + tags: ['meta'], + + requireCredential: true, + requireRolePolicy: 'canInvite', + + errors: { + noSuchCode: { + message: 'No such invite code.', + code: 'NO_SUCH_INVITE_CODE', + id: 'cd4f9ae4-7854-4e3e-8df9-c296f051e634', + }, + + cantDelete: { + message: 'You can\'t delete this invite code.', + code: 'CAN_NOT_DELETE_INVITE_CODE', + id: 'ff17af39-000c-4d4e-abdf-848fa30fc1ce', + }, + + accessDenied: { + message: 'Access denied.', + code: 'ACCESS_DENIED', + id: '5eb8d909-2540-4970-90b8-dd6f86088121', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + inviteId: { type: 'string', format: 'misskey:id' }, + }, + required: ['inviteId'], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { + constructor( + @Inject(DI.registrationTicketsRepository) + private registrationTicketsRepository: RegistrationTicketsRepository, + + private roleService: RoleService, + ) { + super(meta, paramDef, async (ps, me) => { + const ticket = await this.registrationTicketsRepository.findOneBy({ id: ps.inviteId }); + const isModerator = await this.roleService.isModerator(me); + + if (ticket == null) { + throw new ApiError(meta.errors.noSuchCode); + } + + if (ticket.createdById !== me.id && !isModerator) { + throw new ApiError(meta.errors.accessDenied); + } + + if (ticket.usedAt && !isModerator) { + throw new ApiError(meta.errors.cantDelete); + } + + await this.registrationTicketsRepository.delete(ticket.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/invite.ts b/packages/backend/src/server/api/endpoints/invite/limit.ts index 5d2c479e79..9a213b7b25 100644 --- a/packages/backend/src/server/api/endpoints/invite.ts +++ b/packages/backend/src/server/api/endpoints/invite/limit.ts @@ -1,8 +1,8 @@ -import rndstr from 'rndstr'; import { Inject, Injectable } from '@nestjs/common'; +import { MoreThan } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { RegistrationTicketsRepository } from '@/models/index.js'; -import { IdService } from '@/core/IdService.js'; +import { RoleService } from '@/core/RoleService.js'; import { DI } from '@/di-symbols.js'; export const meta = { @@ -15,12 +15,9 @@ export const meta = { type: 'object', optional: false, nullable: false, properties: { - code: { - type: 'string', - optional: false, nullable: false, - example: '2ERUA5VR', - maxLength: 8, - minLength: 8, + remaining: { + type: 'integer', + optional: false, nullable: true, }, }, }, @@ -39,22 +36,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { @Inject(DI.registrationTicketsRepository) private registrationTicketsRepository: RegistrationTicketsRepository, - private idService: IdService, + private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { - const code = rndstr({ - length: 8, - chars: '2-9A-HJ-NP-Z', // [0-9A-Z] w/o [01IO] (32 patterns) - }); + const policies = await this.roleService.getUserPolicies(me.id); - await this.registrationTicketsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), - code, - }); + const count = policies.inviteLimit ? await this.registrationTicketsRepository.countBy({ + createdAt: MoreThan(new Date(Date.now() - (policies.inviteExpirationTime * 60 * 1000))), + createdById: me.id, + }) : null; return { - code, + remaining: count !== null ? Math.max(0, policies.inviteLimit - count) : null, }; }); } diff --git a/packages/backend/src/server/api/endpoints/invite/list.ts b/packages/backend/src/server/api/endpoints/invite/list.ts new file mode 100644 index 0000000000..e047790261 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/invite/list.ts @@ -0,0 +1,58 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { RegistrationTicketsRepository } from '@/models/index.js'; +import { InviteCodeEntityService } from '@/core/entities/InviteCodeEntityService.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../error.js'; + +export const meta = { + tags: ['meta'], + + requireCredential: true, + requireRolePolicy: 'canInvite', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + }, + required: [], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { + constructor( + @Inject(DI.registrationTicketsRepository) + private registrationTicketsRepository: RegistrationTicketsRepository, + + private inviteCodeEntityService: InviteCodeEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.registrationTicketsRepository.createQueryBuilder('ticket'), ps.sinceId, ps.untilId) + .andWhere('ticket.createdById = :meId', { meId: me.id }) + .leftJoinAndSelect('ticket.createdBy', 'createdBy') + .leftJoinAndSelect('ticket.usedBy', 'usedBy'); + + const tickets = await query + .limit(ps.limit) + .getMany(); + + return await this.inviteCodeEntityService.packMany(tickets, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 3b3c5caa00..adfa579558 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -1,4 +1,4 @@ -import { IsNull, LessThanOrEqual, MoreThan } from 'typeorm'; +import { IsNull, LessThanOrEqual, MoreThan, Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import JSON5 from 'json5'; import type { AdsRepository, UsersRepository } from '@/models/index.js'; @@ -83,6 +83,10 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + cacheRemoteSensitiveFiles: { + type: 'boolean', + optional: false, nullable: false, + }, emailRequiredForSignup: { type: 'boolean', optional: false, nullable: false, @@ -250,7 +254,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { constructor( @Inject(DI.config) private config: Config, - + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -263,12 +267,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { super(meta, paramDef, async (ps, me) => { const instance = await this.metaService.fetch(true); - const ads = await this.adsRepository.find({ - where: { - expiresAt: MoreThan(new Date()), - startsAt: LessThanOrEqual(new Date()), - }, - }); + const ads = await this.adsRepository.createQueryBuilder('ads') + .where('ads.expiresAt > :now', { now: new Date() }) + .andWhere('ads.startsAt <= :now', { now: new Date() }) + .andWhere(new Brackets(qb => { + // 曜日のビットフラグを確認する + qb.where('ads.dayOfWeek & :dayOfWeek > 0', { dayOfWeek: 1 << new Date().getDay() }) + .orWhere('ads.dayOfWeek = 0'); + })) + .getMany(); const response: any = { maintainerName: instance.maintainerName, @@ -311,6 +318,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { place: ad.place, ratio: ad.ratio, imageUrl: ad.imageUrl, + dayOfWeek: ad.dayOfWeek, })), enableEmail: instance.enableEmail, enableServiceWorker: instance.enableServiceWorker, @@ -325,6 +333,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { ...(ps.detail ? { cacheRemoteFiles: instance.cacheRemoteFiles, + cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles, requireSetup: (await this.usersRepository.countBy({ host: IsNull(), })) === 0, diff --git a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts index 97def86262..0ea29f04dc 100644 --- a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts +++ b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts @@ -49,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { ) { super(meta, paramDef, async (ps, me) => { // Generate access token - const accessToken = secureRndstr(32, true); + const accessToken = secureRndstr(32); const now = new Date(); diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index ee358d5c6c..ef53f9ef41 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -79,12 +79,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { }); // Check if already muting - const exist = await this.mutingsRepository.findOneBy({ - muterId: muter.id, - muteeId: mutee.id, + const exist = await this.mutingsRepository.exist({ + where: { + muterId: muter.id, + muteeId: mutee.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyMuting); } diff --git a/packages/backend/src/server/api/endpoints/mute/list.ts b/packages/backend/src/server/api/endpoints/mute/list.ts index 9ec6d17273..4711e86d6b 100644 --- a/packages/backend/src/server/api/endpoints/mute/list.ts +++ b/packages/backend/src/server/api/endpoints/mute/list.ts @@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('muting.muterId = :meId', { meId: me.id }); const mutings = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await this.mutingEntityService.packMany(mutings, me); diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts index 5fbc7aba58..9013b300e7 100644 --- a/packages/backend/src/server/api/endpoints/notes.ts +++ b/packages/backend/src/server/api/endpoints/notes.ts @@ -53,34 +53,34 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('renote.user', 'renoteUser'); - + if (ps.local) { query.andWhere('note.userHost IS NULL'); } - + if (ps.reply !== undefined) { query.andWhere(ps.reply ? 'note.replyId IS NOT NULL' : 'note.replyId IS NULL'); } - + if (ps.renote !== undefined) { query.andWhere(ps.renote ? 'note.renoteId IS NOT NULL' : 'note.renoteId IS NULL'); } - + if (ps.withFiles !== undefined) { query.andWhere(ps.withFiles ? 'note.fileIds != \'{}\'' : 'note.fileIds = \'{}\''); } - + if (ps.poll !== undefined) { query.andWhere(ps.poll ? 'note.hasPoll = TRUE' : 'note.hasPoll = FALSE'); } - + // TODO //if (bot != undefined) { // query.isBot = bot; //} - - const notes = await query.take(ps.limit).getMany(); - + + const notes = await query.limit(ps.limit).getMany(); + return await this.noteEntityService.packMany(notes); }); } diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts index 26f2d6772d..5f03fd4b74 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -68,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { this.queryService.generateBlockedUserQuery(query, me); } - const notes = await query.take(ps.limit).getMany(); + const notes = await query.limit(ps.limit).getMany(); return await this.noteEntityService.packMany(notes, me); }); diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts index 5ecf7cf458..10f43b04c0 100644 --- a/packages/backend/src/server/api/endpoints/notes/conversation.ts +++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts @@ -4,8 +4,8 @@ import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../error.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 96be5ed844..739316997a 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -217,11 +217,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // Check blocking if (renote.userId !== me.id) { - const block = await this.blockingsRepository.findOneBy({ - blockerId: renote.userId, - blockeeId: me.id, + const blockExist = await this.blockingsRepository.exist({ + where: { + blockerId: renote.userId, + blockeeId: me.id, + }, }); - if (block) { + if (blockExist) { throw new ApiError(meta.errors.youHaveBeenBlocked); } } @@ -240,11 +242,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // Check blocking if (reply.userId !== me.id) { - const block = await this.blockingsRepository.findOneBy({ - blockerId: reply.userId, - blockeeId: me.id, + const blockExist = await this.blockingsRepository.exist({ + where: { + blockerId: reply.userId, + blockeeId: me.id, + }, }); - if (block) { + if (blockExist) { throw new ApiError(meta.errors.youHaveBeenBlocked); } } diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index 611ea19560..9299d66039 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -63,12 +63,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { }); // if already favorited - const exist = await this.noteFavoritesRepository.findOneBy({ - noteId: note.id, - userId: me.id, + const exist = await this.noteFavoritesRepository.exist({ + where: { + noteId: note.id, + userId: me.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyFavorited); } diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index bdb06498bc..3a3cb0739b 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -65,7 +65,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { let notes = await query .orderBy('note.score', 'DESC') - .take(100) + .limit(100) .getMany(); notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 88c1ca7f58..4ce2fdaec7 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -88,7 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.limit(ps.limit).getMany(); process.nextTick(() => { if (me) { diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 7a3581e6e4..af94cf6087 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -137,7 +137,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.limit(ps.limit).getMany(); process.nextTick(() => { this.activeUsersChart.read(me); diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index 2ee549232c..fe7407f48a 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -110,7 +110,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.limit(ps.limit).getMany(); process.nextTick(() => { if (me) { diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index 4e9f604d8d..6ee9de1e23 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -79,7 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { query.setParameters(followingQuery.getParameters()); } - const mentions = await query.take(ps.limit).getMany(); + const mentions = await query.limit(ps.limit).getMany(); this.noteReadService.read(me.id, mentions); 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 6cdc9b902c..0b4ccdcf20 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts @@ -82,7 +82,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const polls = await query .orderBy('poll.noteId', 'DESC') - .take(ps.limit) + .limit(ps.limit) .skip(ps.offset) .getMany(); diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index d406855660..4ee12b3353 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -71,7 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { if (me) this.queryService.generateMutedUserQuery(query, me); if (me) this.queryService.generateBlockedUserQuery(query, me); - const renotes = await query.take(ps.limit).getMany(); + const renotes = await query.limit(ps.limit).getMany(); return await this.noteEntityService.packMany(renotes, me); }); diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts index f2af71d55f..900c40d32a 100644 --- a/packages/backend/src/server/api/endpoints/notes/replies.ts +++ b/packages/backend/src/server/api/endpoints/notes/replies.ts @@ -55,7 +55,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { if (me) this.queryService.generateMutedUserQuery(query, me); if (me) this.queryService.generateBlockedUserQuery(query, me); - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.limit(ps.limit).getMany(); return await this.noteEntityService.packMany(timeline, me); }); diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts index 742df0ca95..dc0a5dceee 100644 --- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts +++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts @@ -130,7 +130,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } // Search notes - const notes = await query.take(ps.limit).getMany(); + const notes = await query.limit(ps.limit).getMany(); return await this.noteEntityService.packMany(notes, me); }); diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index f6385400c3..cd0e351e45 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -58,7 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { constructor( @Inject(DI.config) private config: Config, - + private noteEntityService: NoteEntityService, private searchService: SearchService, private roleService: RoleService, @@ -68,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { if (!policies.canSearchNotes) { throw new ApiError(meta.errors.unavailable); } - + const notes = await this.searchService.searchNote(ps.query, me, { userId: ps.userId, channelId: ps.channelId, diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index 6b1b84a18e..2aec7d64dd 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -3,8 +3,8 @@ import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../error.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index e1f286439b..7e9bf85d88 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -123,7 +123,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.limit(ps.limit).getMany(); process.nextTick(() => { this.activeUsersChart.read(me); diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index 66655234a1..b91bc7b5ec 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -44,7 +44,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { constructor( @Inject(DI.config) private config: Config, - + @Inject(DI.notesRepository) private notesRepository: NotesRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index 74e459b426..e9581beedc 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -4,8 +4,8 @@ import type { UsersRepository, NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteDeleteService } from '@/core/NoteDeleteService.js'; import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../error.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index afc9bc4213..4c19e1a553 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -127,7 +127,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.limit(ps.limit).getMany(); this.activeUsersChart.read(me); diff --git a/packages/backend/src/server/api/endpoints/pages/featured.ts b/packages/backend/src/server/api/endpoints/pages/featured.ts index 31844165e2..b1c056124e 100644 --- a/packages/backend/src/server/api/endpoints/pages/featured.ts +++ b/packages/backend/src/server/api/endpoints/pages/featured.ts @@ -41,7 +41,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('page.likedCount > 0') .orderBy('page.likedCount', 'DESC'); - const pages = await query.take(10).getMany(); + const pages = await query.limit(10).getMany(); return await this.pageEntityService.packMany(pages, me); }); diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index 543c126d9c..bc66488103 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -66,12 +66,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } // if already liked - const exist = await this.pageLikesRepository.findOneBy({ - pageId: page.id, - userId: me.id, + const exist = await this.pageLikesRepository.exist({ + where: { + pageId: page.id, + userId: me.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyLiked); } diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index 90febdbce7..a76866fe14 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -3,8 +3,8 @@ import type { PromoReadsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../error.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['notes'], @@ -44,12 +44,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { throw err; }); - const exist = await this.promoReadsRepository.findOneBy({ - noteId: note.id, - userId: me.id, + const exist = await this.promoReadsRepository.exist({ + where: { + noteId: note.id, + userId: me.id, + }, }); - if (exist != null) { + if (exist) { return; } diff --git a/packages/backend/src/server/api/endpoints/renote-mute/list.ts b/packages/backend/src/server/api/endpoints/renote-mute/list.ts index b2d7addb64..cb4e1feba4 100644 --- a/packages/backend/src/server/api/endpoints/renote-mute/list.ts +++ b/packages/backend/src/server/api/endpoints/renote-mute/list.ts @@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('muting.muterId = :meId', { meId: me.id }); const mutings = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await this.renoteMutingEntityService.packMany(mutings, me); diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index 3b6ebfe281..284ed8410d 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -1,4 +1,3 @@ -import rndstr from 'rndstr'; import ms from 'ms'; import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; @@ -8,6 +7,7 @@ import { IdService } from '@/core/IdService.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { EmailService } from '@/core/EmailService.js'; +import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js'; export const meta = { tags: ['reset password'], @@ -41,7 +41,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { constructor( @Inject(DI.config) private config: Config, - + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -77,7 +77,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { return; } - const token = rndstr('a-z0-9', 64); + const token = secureRndstr(64, { chars: L_CHARS }); await this.passwordResetRequestsRepository.insert({ id: this.idService.genId(), diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts index 42e36cb04a..a30c31b727 100644 --- a/packages/backend/src/server/api/endpoints/roles/notes.ts +++ b/packages/backend/src/server/api/endpoints/roles/notes.ts @@ -71,7 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { if (role == null) { throw new ApiError(meta.errors.noSuchRole); } - if (!role.isExplorable) { + if (!role.isExplorable) { return []; } const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 diff --git a/packages/backend/src/server/api/endpoints/roles/users.ts b/packages/backend/src/server/api/endpoints/roles/users.ts index b2cb8b42a8..cc27201886 100644 --- a/packages/backend/src/server/api/endpoints/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/roles/users.ts @@ -65,7 +65,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .innerJoinAndSelect('assign.user', 'user'); const assigns = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await Promise.all(assigns.map(async assign => ({ diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts index 1620e8ae52..552441e430 100644 --- a/packages/backend/src/server/api/endpoints/server-info.ts +++ b/packages/backend/src/server/api/endpoints/server-info.ts @@ -2,9 +2,12 @@ import * as os from 'node:os'; import si from 'systeminformation'; import { Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MetaService } from '@/core/MetaService.js'; export const meta = { requireCredential: false, + allowGet: true, + cacheSec: 60 * 1, tags: ['meta'], } as const; @@ -19,8 +22,24 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { constructor( + private metaService: MetaService, ) { super(meta, paramDef, async () => { + if (!(await this.metaService.fetch()).enableServerMachineStats) return { + machine: '?', + cpu: { + model: '?', + cores: 0, + }, + mem: { + total: 0, + }, + fs: { + total: 0, + used: 0, + }, + }; + const memStats = await si.mem(); const fsStats = await si.fsSize(); diff --git a/packages/backend/src/server/api/endpoints/sw/update-registration.ts b/packages/backend/src/server/api/endpoints/sw/update-registration.ts index 9f08c8148d..b82c4bf49d 100644 --- a/packages/backend/src/server/api/endpoints/sw/update-registration.ts +++ b/packages/backend/src/server/api/endpoints/sw/update-registration.ts @@ -35,7 +35,7 @@ export const meta = { code: 'NO_SUCH_REGISTRATION', id: ' b09d8066-8064-5613-efb6-0e963b21d012', }, - } + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts index 28cd9f6ce5..2582932e3a 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -80,7 +80,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { if (me) this.queryService.generateMutedUserQueryForUsers(query, me); if (me) this.queryService.generateBlockQueryForUsers(query, me); - query.take(ps.limit); + query.limit(ps.limit); query.skip(ps.offset); const users = await query.getMany(); diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts index c5aa93baaf..c2ad420cb5 100644 --- a/packages/backend/src/server/api/endpoints/users/clips.ts +++ b/packages/backend/src/server/api/endpoints/users/clips.ts @@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('clip.isPublic = true'); const clips = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await this.clipEntityService.packMany(clips, me); diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index 97f1310c36..18d66500ab 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -97,11 +97,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { if (me == null) { throw new ApiError(meta.errors.forbidden); } else if (me.id !== user.id) { - const following = await this.followingsRepository.findOneBy({ - followeeId: user.id, - followerId: me.id, + const isFollowing = await this.followingsRepository.exist({ + where: { + followeeId: user.id, + followerId: me.id, + }, }); - if (following == null) { + if (!isFollowing) { throw new ApiError(meta.errors.forbidden); } } @@ -112,7 +114,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .innerJoinAndSelect('following.follower', 'follower'); const followings = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await this.followingEntityService.packMany(followings, me, { populateFollower: true }); diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index d406594a2e..6ea7b923d6 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -97,11 +97,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { if (me == null) { throw new ApiError(meta.errors.forbidden); } else if (me.id !== user.id) { - const following = await this.followingsRepository.findOneBy({ - followeeId: user.id, - followerId: me.id, + const isFollowing = await this.followingsRepository.exist({ + where: { + followeeId: user.id, + followerId: me.id, + }, }); - if (following == null) { + if (!isFollowing) { throw new ApiError(meta.errors.forbidden); } } @@ -112,7 +114,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .innerJoinAndSelect('following.followee', 'followee'); const followings = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await this.followingEntityService.packMany(followings, me, { populateFollowee: true }); diff --git a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts index 6e57eee5fb..3ee01953d4 100644 --- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts @@ -47,7 +47,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('post.userId = :userId', { userId: ps.userId }); const posts = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await this.galleryPostEntityService.packMany(posts, me); diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts index 09f6acde9c..b4c1e2ec87 100644 --- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -5,8 +5,8 @@ import type { NotesRepository, UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../error.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['users'], diff --git a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts index 8591e4ab96..beb0ba85ff 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts @@ -84,18 +84,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { - const list = await this.userListsRepository.findOneBy({ - id: ps.listId, - isPublic: true, + const listExist = await this.userListsRepository.exist({ + where: { + id: ps.listId, + isPublic: true, + }, }); - if (list === null) throw new ApiError(meta.errors.noSuchList); + if (!listExist) throw new ApiError(meta.errors.noSuchList); const currentCount = await this.userListsRepository.countBy({ userId: me.id, }); if (currentCount > (await this.roleService.getUserPolicies(me.id)).userListLimit) { throw new ApiError(meta.errors.tooManyUserLists); } - + const userList = await this.userListsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), @@ -114,20 +116,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { }); if (currentUser.id !== me.id) { - const block = await this.blockingsRepository.findOneBy({ - blockerId: currentUser.id, - blockeeId: me.id, + const blockExist = await this.blockingsRepository.exist({ + where: { + blockerId: currentUser.id, + blockeeId: me.id, + }, }); - if (block) { + if (blockExist) { throw new ApiError(meta.errors.youHaveBeenBlocked); } } - const exist = await this.userListJoiningsRepository.findOneBy({ - userListId: userList.id, - userId: currentUser.id, + const exist = await this.userListJoiningsRepository.exist({ + where: { + userListId: userList.id, + userId: currentUser.id, + }, }); - + if (exist) { throw new ApiError(meta.errors.alreadyAdded); } diff --git a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts index 263852fde1..2c09a47fef 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts @@ -41,21 +41,25 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { - const userList = await this.userListsRepository.findOneBy({ - id: ps.listId, - isPublic: true, + const userListExist = await this.userListsRepository.exist({ + where: { + id: ps.listId, + isPublic: true, + }, }); - if (userList === null) { + if (!userListExist) { throw new ApiError(meta.errors.noSuchList); } - const exist = await this.userListFavoritesRepository.findOneBy({ - userId: me.id, - userListId: ps.listId, + const exist = await this.userListFavoritesRepository.exist({ + where: { + userId: me.id, + userListId: ps.listId, + }, }); - if (exist !== null) { + if (exist) { throw new ApiError(meta.errors.alreadyFavorited); } diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index 925037e484..6e1f6b2c62 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -100,18 +100,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // Check blocking if (user.id !== me.id) { - const block = await this.blockingsRepository.findOneBy({ - blockerId: user.id, - blockeeId: me.id, + const blockExist = await this.blockingsRepository.exist({ + where: { + blockerId: user.id, + blockeeId: me.id, + }, }); - if (block) { + if (blockExist) { throw new ApiError(meta.errors.youHaveBeenBlocked); } } - const exist = await this.userListJoiningsRepository.findOneBy({ - userListId: userList.id, - userId: user.id, + const exist = await this.userListJoiningsRepository.exist({ + where: { + userListId: userList.id, + userId: user.id, + }, }); if (exist) { diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index 8077841c8c..3fd418d04e 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -69,10 +69,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { userListId: ps.listId, }); if (me !== null) { - additionalProperties.isLiked = (await this.userListFavoritesRepository.findOneBy({ - userId: me.id, - userListId: ps.listId, - }) !== null); + additionalProperties.isLiked = await this.userListFavoritesRepository.exist({ + where: { + userId: me.id, + userListId: ps.listId, + }, + }); } else { additionalProperties.isLiked = false; } diff --git a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts index be8e317816..a7c3b58947 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts @@ -39,12 +39,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private userListFavoritesRepository: UserListFavoritesRepository, ) { super(meta, paramDef, async (ps, me) => { - const userList = await this.userListsRepository.findOneBy({ - id: ps.listId, - isPublic: true, + const userListExist = await this.userListsRepository.exist({ + where: { + id: ps.listId, + isPublic: true, + }, }); - if (userList === null) { + if (!userListExist) { throw new ApiError(meta.errors.noSuchList); } diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index aaf94734a3..f42f84e6a7 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -120,7 +120,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.limit(ps.limit).getMany(); return await this.noteEntityService.packMany(timeline, me); }); diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts index a105103f16..e9d13ba00f 100644 --- a/packages/backend/src/server/api/endpoints/users/pages.ts +++ b/packages/backend/src/server/api/endpoints/users/pages.ts @@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .andWhere('page.visibility = \'public\''); const pages = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await this.pageEntityService.packMany(pages); diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index ac401a60ee..37fc854c33 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -73,7 +73,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { this.queryService.generateVisibilityQuery(query, me); const reactions = await query - .take(ps.limit) + .limit(ps.limit) .getMany(); return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me, { withNote: true }))); diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index 5498b8c854..d39657059a 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -44,7 +44,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, - + private userEntityService: UserEntityService, private queryService: QueryService, ) { @@ -70,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { query.setParameters(followingQuery.getParameters()); - const users = await query.take(ps.limit).skip(ps.offset).getMany(); + const users = await query.limit(ps.limit).skip(ps.offset).getMany(); return await this.userEntityService.packMany(users, me, { detail: true }); }); diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index d19d4007d6..be361e02c4 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -1,4 +1,4 @@ -import * as sanitizeHtml from 'sanitize-html'; +import sanitizeHtml from 'sanitize-html'; import { Inject, Injectable } from '@nestjs/common'; import type { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index b001159ee8..1d0c7d0c1d 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -97,7 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { users = await query .orderBy('user.usernameLower', 'ASC') - .take(ps.limit) + .limit(ps.limit) .getMany(); if (users.length < ps.limit) { @@ -110,7 +110,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const otherUsers = await otherQuery .orderBy('user.updatedAt', 'DESC') - .take(ps.limit - users.length) + .limit(ps.limit - users.length) .getMany(); users = users.concat(otherUsers); @@ -122,7 +122,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { users = await query .orderBy('user.updatedAt', 'DESC') - .take(ps.limit - users.length) + .limit(ps.limit - users.length) .getMany(); } diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index d7a60f0437..1180de3611 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -52,6 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { super(meta, paramDef, async (ps, me) => { const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日 + ps.query = ps.query.trim(); const isUsername = ps.query.startsWith('@'); let users: User[] = []; @@ -73,12 +74,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { users = await usernameQuery .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') - .take(ps.limit) + .limit(ps.limit) .skip(ps.offset) .getMany(); } else { const nameQuery = this.usersRepository.createQueryBuilder('user') - .where(new Brackets(qb => { + .where(new Brackets(qb => { qb.where('user.name ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' }); // Also search username if it qualifies as username @@ -100,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { users = await nameQuery .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') - .take(ps.limit) + .limit(ps.limit) .skip(ps.offset) .getMany(); @@ -126,7 +127,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { users = users.concat(await query .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') - .take(ps.limit) + .limit(ps.limit) .skip(ps.offset) .getMany(), ); diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index ba432c273b..8e25af64fe 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -91,6 +91,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { let user; const isModerator = await this.roleService.isModerator(me); + ps.username = ps.username?.trim(); if (ps.userIds) { if (ps.userIds.length === 0) { diff --git a/packages/backend/src/server/api/stream/ChannelsService.ts b/packages/backend/src/server/api/stream/ChannelsService.ts index c77ba66028..4a544fadfe 100644 --- a/packages/backend/src/server/api/stream/ChannelsService.ts +++ b/packages/backend/src/server/api/stream/ChannelsService.ts @@ -52,7 +52,7 @@ export class ChannelsService { case 'serverStats': return this.serverStatsChannelService; case 'queueStats': return this.queueStatsChannelService; case 'admin': return this.adminChannelService; - + default: throw new Error(`no such channel: ${name}`); } diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index e67aec9ecd..94b92e02ef 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -1,5 +1,5 @@ import { bindThis } from '@/decorators.js'; -import type Connection from '.'; +import type Connection from './index.js'; /** * Stream channel diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts index 0268fdedde..94ebf86418 100644 --- a/packages/backend/src/server/api/stream/channels/hashtag.ts +++ b/packages/backend/src/server/api/stream/channels/hashtag.ts @@ -49,7 +49,7 @@ class HashtagChannel extends Channel { if (isUserRelated(note, this.userIdsWhoMeMuting)) return; // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する if (isUserRelated(note, this.userIdsWhoBlockingMe)) return; - + if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; this.connection.cacheNote(note); diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index 1755aa94cf..fe0cc37b6b 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -26,7 +26,7 @@ class HomeTimelineChannel extends Channel { @bindThis public async init(params: any) { this.withReplies = params.withReplies as boolean; - + this.subscriber.on('notesStream', this.onNote); } diff --git a/packages/backend/src/server/api/stream/channels/role-timeline.ts b/packages/backend/src/server/api/stream/channels/role-timeline.ts index ab9c1aa0b5..6218fada97 100644 --- a/packages/backend/src/server/api/stream/channels/role-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/role-timeline.ts @@ -3,16 +3,16 @@ import { isUserRelated } from '@/misc/is-user-related.js'; import type { Packed } from '@/misc/json-schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; +import { RoleService } from '@/core/RoleService.js'; import Channel from '../channel.js'; import { StreamMessages } from '../types.js'; -import { RoleService } from '@/core/RoleService.js'; class RoleTimelineChannel extends Channel { public readonly chName = 'roleTimeline'; public static shouldShare = false; public static requireCredential = false; private roleId: string; - + constructor( private noteEntityService: NoteEntityService, private roleservice: RoleService, diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 8802fc5ab8..ea4cff0bc0 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -20,7 +20,7 @@ class UserListChannel extends Channel { private userListsRepository: UserListsRepository, private userListJoiningsRepository: UserListJoiningsRepository, private noteEntityService: NoteEntityService, - + id: string, connection: Channel['connection'], ) { @@ -34,11 +34,13 @@ class UserListChannel extends Channel { this.listId = params.listId as string; // Check existence and owner - const list = await this.userListsRepository.findOneBy({ - id: this.listId, - userId: this.user!.id, + const listExist = await this.userListsRepository.exist({ + where: { + id: this.listId, + userId: this.user!.id, + }, }); - if (!list) return; + if (!listExist) return; // Subscribe stream this.subscriber.on(`userListStream:${this.listId}`, this.send); diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index d9dba682cd..f239b06637 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -12,7 +12,7 @@ import type { Page } from '@/models/entities/Page.js'; import type { Packed } from '@/misc/json-schema.js'; import type { Webhook } from '@/models/entities/Webhook.js'; import type { Meta } from '@/models/entities/Meta.js'; -import { Role, RoleAssignment } from '@/models'; +import { Role, RoleAssignment } from '@/models/index.js'; import type Emitter from 'strict-event-emitter-types'; import type { EventEmitter } from 'events'; @@ -233,7 +233,7 @@ export type StreamMessages = { // API event definitions // ストリームごとのEmitterの辞書を用意 -type EventEmitterDictionary = { [x in keyof StreamMessages]: Emitter<EventEmitter, { [y in StreamMessages[x]['name']]: (e: StreamMessages[x]['payload']) => void }> }; +type EventEmitterDictionary = { [x in keyof StreamMessages]: Emitter.default<EventEmitter, { [y in StreamMessages[x]['name']]: (e: StreamMessages[x]['payload']) => void }> }; // 共用体型を交差型にする型 https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; // Emitter辞書から共用体型を作り、UnionToIntersectionで交差型にする diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 07ba2731c3..b5eea07775 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -1,7 +1,7 @@ +import { randomUUID } from 'node:crypto'; import { dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; -import { v4 as uuid } from 'uuid'; import { createBullBoard } from '@bull-board/api'; import { BullAdapter } from '@bull-board/api/bullAdapter.js'; import { FastifyAdapter } from '@bull-board/fastify'; @@ -676,7 +676,7 @@ export class ClientServerService { }); fastify.setErrorHandler(async (error, request, reply) => { - const errId = uuid(); + const errId = randomUUID(); this.clientLoggerService.logger.error(`Internal error occured in ${request.routerPath}: ${error.message}`, { path: request.routerPath, params: request.params, diff --git a/packages/backend/src/server/web/FeedService.ts b/packages/backend/src/server/web/FeedService.ts index 0c0e92cc04..0bd0d3c692 100644 --- a/packages/backend/src/server/web/FeedService.ts +++ b/packages/backend/src/server/web/FeedService.ts @@ -38,9 +38,9 @@ export class FeedService { link: `${this.config.url}/@${user.username}`, name: user.name ?? user.username, }; - + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); - + const notes = await this.notesRepository.find({ where: { userId: user.id, @@ -50,7 +50,7 @@ export class FeedService { order: { createdAt: -1 }, take: 20, }); - + const feed = new Feed({ id: author.link, title: `${author.name} (@${user.username}@${this.config.host})`, @@ -66,13 +66,13 @@ export class FeedService { author, copyright: user.name ?? user.username, }); - + for (const note of notes) { const files = note.fileIds.length > 0 ? await this.driveFilesRepository.findBy({ id: In(note.fileIds), }) : []; const file = files.find(file => file.type.startsWith('image/')); - + feed.addItem({ title: `New note by ${author.name}`, link: `${this.config.url}/notes/${note.id}`, @@ -82,7 +82,7 @@ export class FeedService { image: file ? this.driveFileEntityService.getPublicUrl(file) ?? undefined : undefined, }); } - + return feed; } } diff --git a/packages/backend/src/server/web/bios.js b/packages/backend/src/server/web/bios.js index c2ce5c3814..51899dd3a3 100644 --- a/packages/backend/src/server/web/bios.js +++ b/packages/backend/src/server/web/bios.js @@ -8,7 +8,7 @@ window.onload = async () => { const promise = new Promise((resolve, reject) => { // Append a credential if (i) data.i = i; - + // Send request window.fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, { method: 'POST', @@ -17,7 +17,7 @@ window.onload = async () => { cache: 'no-cache' }).then(async (res) => { const body = res.status === 204 ? null : await res.json(); - + if (res.status === 200) { resolve(body); } else if (res.status === 204) { @@ -27,7 +27,7 @@ window.onload = async () => { } }).catch(reject); }); - + return promise; }; diff --git a/packages/backend/src/server/web/cli.js b/packages/backend/src/server/web/cli.js index 3467f7ac2a..5bb576a27b 100644 --- a/packages/backend/src/server/web/cli.js +++ b/packages/backend/src/server/web/cli.js @@ -8,7 +8,7 @@ window.onload = async () => { const promise = new Promise((resolve, reject) => { // Append a credential if (i) data.i = i; - + // Send request fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, { headers: { @@ -20,7 +20,7 @@ window.onload = async () => { cache: 'no-cache' }).then(async (res) => { const body = res.status === 204 ? null : await res.json(); - + if (res.status === 200) { resolve(body); } else if (res.status === 204) { @@ -30,7 +30,7 @@ window.onload = async () => { } }).catch(reject); }); - + return promise; }; diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index 1216fc73f7..2b61c6bc2f 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -35,7 +35,7 @@ html link(rel='prefetch' href=infoImageUrl) link(rel='prefetch' href=notFoundImageUrl) //- https://github.com/misskey-dev/misskey/issues/9842 - link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.21.0') + link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.25.0') link(rel='modulepreload' href=`/vite/${clientEntry.file}`) if !config.clientManifestExists @@ -55,8 +55,8 @@ html block meta block og - meta(property='og:title' content= title || 'Misskey') - meta(property='og:description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨') + meta(property='og:title' content= title || 'Misskey') + meta(property='og:description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨') meta(property='og:image' content= img) meta(property='twitter:card' content='summary') diff --git a/packages/backend/src/server/web/views/error.pug b/packages/backend/src/server/web/views/error.pug index b177ae4110..44ebf53cf7 100644 --- a/packages/backend/src/server/web/views/error.pug +++ b/packages/backend/src/server/web/views/error.pug @@ -32,12 +32,12 @@ body path(stroke="none", d="M0 0h24v24H0z", fill="none") path(d="M12 9v2m0 4v.01") path(d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75") - + h1 An error has occurred! button.button-big(onclick="location.reload();") span.button-label-big Refresh - + p.dont-worry Don't worry, it's (probably) not your fault. p If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID. diff --git a/packages/backend/src/server/web/views/info-card.pug b/packages/backend/src/server/web/views/info-card.pug index 1d62778ce1..2a4954ec8b 100644 --- a/packages/backend/src/server/web/views/info-card.pug +++ b/packages/backend/src/server/web/views/info-card.pug @@ -47,4 +47,4 @@ html header#banner(style=`background-image: url(${meta.bannerUrl})`) div#title= meta.name || host div#content - div#description= meta.description + div#description!= meta.description diff --git a/packages/backend/src/server/web/views/note.pug b/packages/backend/src/server/web/views/note.pug index ea0917a80e..9bc652b6a1 100644 --- a/packages/backend/src/server/web/views/note.pug +++ b/packages/backend/src/server/web/views/note.pug @@ -5,8 +5,8 @@ block vars - const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`; - const url = `${config.url}/notes/${note.id}`; - const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null; - - const image = (note.files || []).find(file => file.type.startsWith('image/') && !file.isSensitive) - - const video = (note.files || []).find(file => file.type.startsWith('video/') && !file.isSensitive) + - const images = (note.files || []).filter(file => file.type.startsWith('image/') && !file.isSensitive) + - const videos = (note.files || []).filter(file => file.type.startsWith('video/') && !file.isSensitive) block title = `${title} | ${instanceName}` @@ -19,15 +19,17 @@ block og meta(property='og:title' content= title) meta(property='og:description' content= summary) meta(property='og:url' content= url) - if video - meta(property='og:video:url' content= video.url) - meta(property='og:video:secure_url' content= video.url) - meta(property='og:video:type' content= video.type) - // FIXME: add width and height - // FIXME: add embed player for Twitter - if image + if videos.length + each video in videos + meta(property='og:video:url' content= video.url) + meta(property='og:video:secure_url' content= video.url) + meta(property='og:video:type' content= video.type) + // FIXME: add width and height + // FIXME: add embed player for Twitter + if images.length meta(property='twitter:card' content='summary_large_image') - meta(property='og:image' content= image.url) + each image in images + meta(property='og:image' content= image.url) else meta(property='twitter:card' content='summary') meta(property='og:image' content= avatarUrl) @@ -43,7 +45,7 @@ block meta meta(name='misskey:user-username' content=user.username) meta(name='misskey:user-id' content=user.id) meta(name='misskey:note-id' content=note.id) - + // todo if user.twitter meta(name='twitter:creator' content=`@${user.twitter.screenName}`) |