Merge branch 'MisskeyIO:io' into io
This commit is contained in:
@@ -11,7 +11,7 @@ import * as url from '@/misc/prelude/url.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import type { ILocalUser, User } from '@/models/entities/User.js';
|
||||
import type { LocalUser, User } from '@/models/entities/User.js';
|
||||
import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js';
|
||||
import type { Following } from '@/models/entities/Following.js';
|
||||
import { countIf } from '@/misc/prelude/array.js';
|
||||
@@ -183,13 +183,13 @@ export class ActivityPubServerService {
|
||||
);
|
||||
|
||||
this.setResponseType(request, reply);
|
||||
return (this.apRendererService.renderActivity(rendered));
|
||||
return (this.apRendererService.addContext(rendered));
|
||||
} else {
|
||||
// index page
|
||||
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.renderActivity(rendered));
|
||||
return (this.apRendererService.addContext(rendered));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,13 +271,13 @@ export class ActivityPubServerService {
|
||||
);
|
||||
|
||||
this.setResponseType(request, reply);
|
||||
return (this.apRendererService.renderActivity(rendered));
|
||||
return (this.apRendererService.addContext(rendered));
|
||||
} else {
|
||||
// index page
|
||||
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.renderActivity(rendered));
|
||||
return (this.apRendererService.addContext(rendered));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ export class ActivityPubServerService {
|
||||
|
||||
reply.header('Cache-Control', 'public, max-age=180');
|
||||
this.setResponseType(request, reply);
|
||||
return (this.apRendererService.renderActivity(rendered));
|
||||
return (this.apRendererService.addContext(rendered));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -389,7 +389,7 @@ export class ActivityPubServerService {
|
||||
);
|
||||
|
||||
this.setResponseType(request, reply);
|
||||
return (this.apRendererService.renderActivity(rendered));
|
||||
return (this.apRendererService.addContext(rendered));
|
||||
} else {
|
||||
// index page
|
||||
const rendered = this.apRendererService.renderOrderedCollection(partOf, user.notesCount,
|
||||
@@ -398,7 +398,7 @@ export class ActivityPubServerService {
|
||||
);
|
||||
reply.header('Cache-Control', 'public, max-age=180');
|
||||
this.setResponseType(request, reply);
|
||||
return (this.apRendererService.renderActivity(rendered));
|
||||
return (this.apRendererService.addContext(rendered));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,7 +411,7 @@ export class ActivityPubServerService {
|
||||
|
||||
reply.header('Cache-Control', 'public, max-age=180');
|
||||
this.setResponseType(request, reply);
|
||||
return (this.apRendererService.renderActivity(await this.apRendererService.renderPerson(user as ILocalUser)));
|
||||
return (this.apRendererService.addContext(await this.apRendererService.renderPerson(user as LocalUser)));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -441,6 +441,14 @@ export class ActivityPubServerService {
|
||||
fastify.addContentTypeParser('application/activity+json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore'));
|
||||
fastify.addContentTypeParser('application/ld+json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore'));
|
||||
|
||||
fastify.addHook('onRequest', (request, reply, done) => {
|
||||
reply.header('Access-Control-Allow-Headers', 'Accept');
|
||||
reply.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
||||
reply.header('Access-Control-Allow-Origin', '*');
|
||||
reply.header('Access-Control-Expose-Headers', 'Vary');
|
||||
done();
|
||||
});
|
||||
|
||||
//#region Routing
|
||||
// inbox (limit: 64kb)
|
||||
fastify.post('/inbox', { bodyLimit: 1024 * 64 }, async (request, reply) => await this.inbox(request, reply));
|
||||
@@ -473,7 +481,7 @@ export class ActivityPubServerService {
|
||||
|
||||
reply.header('Cache-Control', 'public, max-age=180');
|
||||
this.setResponseType(request, reply);
|
||||
return (this.apRendererService.renderActivity(await this.apRendererService.renderNote(note, false)));
|
||||
return this.apRendererService.addContext(await this.apRendererService.renderNote(note, false));
|
||||
});
|
||||
|
||||
// note activity
|
||||
@@ -494,7 +502,7 @@ export class ActivityPubServerService {
|
||||
|
||||
reply.header('Cache-Control', 'public, max-age=180');
|
||||
this.setResponseType(request, reply);
|
||||
return (this.apRendererService.renderActivity(await this.packActivity(note)));
|
||||
return (this.apRendererService.addContext(await this.packActivity(note)));
|
||||
});
|
||||
|
||||
// outbox
|
||||
@@ -537,7 +545,7 @@ export class ActivityPubServerService {
|
||||
if (this.userEntityService.isLocalUser(user)) {
|
||||
reply.header('Cache-Control', 'public, max-age=180');
|
||||
this.setResponseType(request, reply);
|
||||
return (this.apRendererService.renderActivity(this.apRendererService.renderKey(user, keypair)));
|
||||
return (this.apRendererService.addContext(this.apRendererService.renderKey(user, keypair)));
|
||||
} else {
|
||||
reply.code(400);
|
||||
return;
|
||||
@@ -581,7 +589,7 @@ export class ActivityPubServerService {
|
||||
|
||||
reply.header('Cache-Control', 'public, max-age=180');
|
||||
this.setResponseType(request, reply);
|
||||
return (this.apRendererService.renderActivity(await this.apRendererService.renderEmoji(emoji)));
|
||||
return (this.apRendererService.addContext(await this.apRendererService.renderEmoji(emoji)));
|
||||
});
|
||||
|
||||
// like
|
||||
@@ -602,7 +610,7 @@ export class ActivityPubServerService {
|
||||
|
||||
reply.header('Cache-Control', 'public, max-age=180');
|
||||
this.setResponseType(request, reply);
|
||||
return (this.apRendererService.renderActivity(await this.apRendererService.renderLike(reaction, note)));
|
||||
return (this.apRendererService.addContext(await this.apRendererService.renderLike(reaction, note)));
|
||||
});
|
||||
|
||||
// follow
|
||||
@@ -628,7 +636,7 @@ export class ActivityPubServerService {
|
||||
|
||||
reply.header('Cache-Control', 'public, max-age=180');
|
||||
this.setResponseType(request, reply);
|
||||
return (this.apRendererService.renderActivity(this.apRendererService.renderFollow(follower, followee)));
|
||||
return (this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee)));
|
||||
});
|
||||
|
||||
done();
|
||||
|
@@ -150,6 +150,12 @@ export class FileServerService {
|
||||
file.cleanup();
|
||||
return await reply.redirect(301, url.toString());
|
||||
} else if (file.mime.startsWith('video/')) {
|
||||
const externalThumbnail = this.videoProcessingService.getExternalVideoThumbnailUrl(file.url);
|
||||
if (externalThumbnail) {
|
||||
file.cleanup();
|
||||
return await reply.redirect(301, externalThumbnail);
|
||||
}
|
||||
|
||||
image = await this.videoProcessingService.generateVideoThumbnail(file.path);
|
||||
}
|
||||
}
|
||||
@@ -220,7 +226,10 @@ export class FileServerService {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.config.externalMediaProxyEnabled) {
|
||||
// アバタークロップなど、どうしてもオリジンである必要がある場合
|
||||
const mustOrigin = 'origin' in request.query;
|
||||
|
||||
if (this.config.externalMediaProxyEnabled && !mustOrigin) {
|
||||
// 外部のメディアプロキシが有効なら、そちらにリダイレクト
|
||||
|
||||
reply.header('Cache-Control', 'public, max-age=259200'); // 3 days
|
||||
@@ -255,8 +264,21 @@ export class FileServerService {
|
||||
const isConvertibleImage = isMimeImage(file.mime, 'sharp-convertible-image');
|
||||
const isAnimationConvertibleImage = isMimeImage(file.mime, 'sharp-animation-convertible-image');
|
||||
|
||||
if (
|
||||
'emoji' in request.query ||
|
||||
'avatar' in request.query ||
|
||||
'static' in request.query ||
|
||||
'preview' in request.query ||
|
||||
'badge' in request.query
|
||||
) {
|
||||
if (!isConvertibleImage) {
|
||||
// 画像でないなら404でお茶を濁す
|
||||
throw new StatusError('Unexpected mime', 404);
|
||||
}
|
||||
}
|
||||
|
||||
let image: IImageStreamable | null = null;
|
||||
if (('emoji' in request.query || 'avatar' in request.query) && isConvertibleImage) {
|
||||
if ('emoji' in request.query || 'avatar' in request.query) {
|
||||
if (!isAnimationConvertibleImage && !('static' in request.query)) {
|
||||
image = {
|
||||
data: fs.createReadStream(file.path),
|
||||
@@ -277,16 +299,11 @@ export class FileServerService {
|
||||
type: 'image/webp',
|
||||
};
|
||||
}
|
||||
} else if ('static' in request.query && isConvertibleImage) {
|
||||
} else if ('static' in request.query) {
|
||||
image = this.imageProcessingService.convertToWebpStream(file.path, 498, 280);
|
||||
} else if ('preview' in request.query && isConvertibleImage) {
|
||||
} else if ('preview' in request.query) {
|
||||
image = this.imageProcessingService.convertToWebpStream(file.path, 200, 200);
|
||||
} else if ('badge' in request.query) {
|
||||
if (!isConvertibleImage) {
|
||||
// 画像でないなら404でお茶を濁す
|
||||
throw new StatusError('Unexpected mime', 404);
|
||||
}
|
||||
|
||||
const mask = sharp(file.path)
|
||||
.resize(96, 96, {
|
||||
fit: 'inside',
|
||||
@@ -381,7 +398,7 @@ export class FileServerService {
|
||||
state: 'remote',
|
||||
mime, ext,
|
||||
path, cleanup,
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
cleanup();
|
||||
throw e;
|
||||
@@ -415,7 +432,7 @@ export class FileServerService {
|
||||
url: file.uri,
|
||||
fileRole: isThumbnail ? 'thumbnail' : isWebpublic ? 'webpublic' : 'original',
|
||||
file,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const path = this.internalStorageService.resolvePath(key);
|
||||
@@ -438,6 +455,6 @@ export class FileServerService {
|
||||
mime: file.type,
|
||||
ext: null,
|
||||
path,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { IsNull, MoreThan } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { NotesRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
|
@@ -30,8 +30,6 @@ import { HashtagChannelService } from './api/stream/channels/hashtag.js';
|
||||
import { HomeTimelineChannelService } from './api/stream/channels/home-timeline.js';
|
||||
import { HybridTimelineChannelService } from './api/stream/channels/hybrid-timeline.js';
|
||||
import { LocalTimelineChannelService } from './api/stream/channels/local-timeline.js';
|
||||
import { MessagingIndexChannelService } from './api/stream/channels/messaging-index.js';
|
||||
import { MessagingChannelService } from './api/stream/channels/messaging.js';
|
||||
import { QueueStatsChannelService } from './api/stream/channels/queue-stats.js';
|
||||
import { ServerStatsChannelService } from './api/stream/channels/server-stats.js';
|
||||
import { UserListChannelService } from './api/stream/channels/user-list.js';
|
||||
@@ -71,8 +69,6 @@ import { UserListChannelService } from './api/stream/channels/user-list.js';
|
||||
HomeTimelineChannelService,
|
||||
HybridTimelineChannelService,
|
||||
LocalTimelineChannelService,
|
||||
MessagingIndexChannelService,
|
||||
MessagingChannelService,
|
||||
QueueStatsChannelService,
|
||||
ServerStatsChannelService,
|
||||
UserListChannelService,
|
||||
|
@@ -1,14 +1,13 @@
|
||||
import cluster from 'node:cluster';
|
||||
import * as fs from 'node:fs';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import Fastify from 'fastify';
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import Fastify, { FastifyInstance } from 'fastify';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { EmojisRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { envOption } from '@/env.js';
|
||||
import * as Acct from '@/misc/acct.js';
|
||||
import { genIdenticon } from '@/misc/gen-identicon.js';
|
||||
import { createTemp } from '@/misc/create-temp.js';
|
||||
@@ -24,8 +23,9 @@ import { FileServerService } from './FileServerService.js';
|
||||
import { ClientServerService } from './web/ClientServerService.js';
|
||||
|
||||
@Injectable()
|
||||
export class ServerService {
|
||||
export class ServerService implements OnApplicationShutdown {
|
||||
private logger: Logger;
|
||||
#fastify: FastifyInstance;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
@@ -55,11 +55,12 @@ export class ServerService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public launch() {
|
||||
public async launch() {
|
||||
const fastify = Fastify({
|
||||
trustProxy: true,
|
||||
logger: !['production', 'test'].includes(process.env.NODE_ENV ?? ''),
|
||||
});
|
||||
this.#fastify = fastify;
|
||||
|
||||
// HSTS
|
||||
// 6months (15552000sec)
|
||||
@@ -76,7 +77,7 @@ export class ServerService {
|
||||
fastify.register(this.nodeinfoServerService.createServer);
|
||||
fastify.register(this.wellKnownServerService.createServer);
|
||||
|
||||
fastify.get<{ Params: { path: string }; Querystring: { static?: any; }; }>('/emoji/:path(.*)', async (request, reply) => {
|
||||
fastify.get<{ Params: { path: string }; Querystring: { static?: any; badge?: any; }; }>('/emoji/:path(.*)', async (request, reply) => {
|
||||
const path = request.params.path;
|
||||
|
||||
reply.header('Cache-Control', 'public, max-age=86400');
|
||||
@@ -106,11 +107,19 @@ export class ServerService {
|
||||
}
|
||||
}
|
||||
|
||||
const url = new URL(`${this.config.mediaProxy}/emoji.webp`);
|
||||
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
||||
url.searchParams.set('url', emoji.publicUrl || emoji.originalUrl);
|
||||
url.searchParams.set('emoji', '1');
|
||||
if ('static' in request.query) url.searchParams.set('static', '1');
|
||||
let url: URL;
|
||||
if ('badge' in request.query) {
|
||||
url = new URL(`${this.config.mediaProxy}/emoji.png`);
|
||||
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
||||
url.searchParams.set('url', emoji.publicUrl || emoji.originalUrl);
|
||||
url.searchParams.set('badge', '1');
|
||||
} else {
|
||||
url = new URL(`${this.config.mediaProxy}/emoji.webp`);
|
||||
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
||||
url.searchParams.set('url', emoji.publicUrl || emoji.originalUrl);
|
||||
url.searchParams.set('emoji', '1');
|
||||
if ('static' in request.query) url.searchParams.set('static', '1');
|
||||
}
|
||||
|
||||
return await reply.redirect(
|
||||
301,
|
||||
@@ -196,5 +205,11 @@ export class ServerService {
|
||||
});
|
||||
|
||||
fastify.listen({ port: this.config.port, host: '0.0.0.0' });
|
||||
|
||||
await fastify.ready();
|
||||
}
|
||||
|
||||
async onApplicationShutdown(signal: string): Promise<void> {
|
||||
await this.#fastify.close();
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { IsNull, MoreThan } from 'typeorm';
|
||||
import { IsNull } from 'typeorm';
|
||||
import vary from 'vary';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { UsersRepository } from '@/models/index.js';
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { performance } from 'perf_hooks';
|
||||
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 { CacheableLocalUser, ILocalUser, User } from '@/models/entities/User.js';
|
||||
import type { LocalUser, User } from '@/models/entities/User.js';
|
||||
import type { AccessToken } from '@/models/entities/AccessToken.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import type { UserIpsRepository } from '@/models/index.js';
|
||||
@@ -100,18 +100,21 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||
request: FastifyRequest<{ Body: Record<string, unknown>, Querystring: Record<string, unknown> }>,
|
||||
reply: FastifyReply,
|
||||
) {
|
||||
const multipartData = await request.file();
|
||||
const multipartData = await request.file().catch(() => {
|
||||
/* Fastify throws if the remote didn't send multipart data. Return 400 below. */
|
||||
});
|
||||
if (multipartData == null) {
|
||||
reply.code(400);
|
||||
reply.send();
|
||||
return;
|
||||
}
|
||||
|
||||
const [path] = await createTemp();
|
||||
await pump(multipartData.file, fs.createWriteStream(path));
|
||||
|
||||
const fields = {} as Record<string, string | undefined>;
|
||||
const fields = {} as Record<string, unknown>;
|
||||
for (const [k, v] of Object.entries(multipartData.fields)) {
|
||||
fields[k] = v.value;
|
||||
fields[k] = typeof v === 'object' && 'value' in v ? v.value : undefined;
|
||||
}
|
||||
|
||||
const token = fields['i'];
|
||||
@@ -168,7 +171,7 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async logIp(request: FastifyRequest, user: ILocalUser) {
|
||||
private async logIp(request: FastifyRequest, user: LocalUser) {
|
||||
const meta = await this.metaService.fetch();
|
||||
if (!meta.enableIpLogging) return;
|
||||
const ip = request.ip;
|
||||
@@ -194,7 +197,7 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||
@bindThis
|
||||
private async call(
|
||||
ep: IEndpoint & { exec: any },
|
||||
user: CacheableLocalUser | null | undefined,
|
||||
user: LocalUser | null | undefined,
|
||||
token: AccessToken | null | undefined,
|
||||
data: any,
|
||||
file: {
|
||||
@@ -220,22 +223,24 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||
|
||||
const limit = Object.assign({}, ep.meta.limit);
|
||||
|
||||
if (!limit.key) {
|
||||
limit.key = ep.name;
|
||||
if (limit.key == null) {
|
||||
(limit as any).key = ep.name;
|
||||
}
|
||||
|
||||
// TODO: 毎リクエスト計算するのもあれだしキャッシュしたい
|
||||
const factor = user ? (await this.roleService.getUserPolicies(user.id)).rateLimitFactor : 1;
|
||||
|
||||
// Rate limit
|
||||
await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor, factor).catch(err => {
|
||||
throw new ApiError({
|
||||
message: 'Rate limit exceeded. Please try again later.',
|
||||
code: 'RATE_LIMIT_EXCEEDED',
|
||||
id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
|
||||
httpStatusCode: 429,
|
||||
if (factor > 0) {
|
||||
// Rate limit
|
||||
await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor, factor).catch(err => {
|
||||
throw new ApiError({
|
||||
message: 'Rate limit exceeded. Please try again later.',
|
||||
code: 'RATE_LIMIT_EXCEEDED',
|
||||
id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
|
||||
httpStatusCode: 429,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (ep.meta.requireCredential || ep.meta.requireModerator || ep.meta.requireAdmin) {
|
||||
@@ -319,6 +324,7 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||
if (err instanceof ApiError) {
|
||||
throw err;
|
||||
} else {
|
||||
const errId = uuid();
|
||||
this.logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, {
|
||||
ep: ep.name,
|
||||
ps: data,
|
||||
@@ -326,14 +332,15 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||
message: err.message,
|
||||
code: err.name,
|
||||
stack: err.stack,
|
||||
id: errId,
|
||||
},
|
||||
});
|
||||
console.error(err);
|
||||
console.error(err, errId);
|
||||
throw new ApiError(null, {
|
||||
e: {
|
||||
message: err.message,
|
||||
code: err.name,
|
||||
stack: err.stack,
|
||||
id: errId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import type Logger from '@/logger.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
export class ApiLoggerService {
|
||||
|
@@ -2,13 +2,13 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import cors from '@fastify/cors';
|
||||
import multipart from '@fastify/multipart';
|
||||
import fastifyCookie from '@fastify/cookie';
|
||||
import { ModuleRef, repl } from '@nestjs/core';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { UsersRepository, InstancesRepository, AccessTokensRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import endpoints, { IEndpoint } from './endpoints.js';
|
||||
import endpoints from './endpoints.js';
|
||||
import { ApiCallService } from './ApiCallService.js';
|
||||
import { SignupApiService } from './SignupApiService.js';
|
||||
import { SigninApiService } from './SigninApiService.js';
|
||||
@@ -73,28 +73,32 @@ export class ApiServerService {
|
||||
Params: { endpoint: string; },
|
||||
Body: Record<string, unknown>,
|
||||
Querystring: Record<string, unknown>,
|
||||
}>('/' + endpoint.name, (request, reply) => {
|
||||
}>('/' + endpoint.name, async (request, reply) => {
|
||||
if (request.method === 'GET' && !endpoint.meta.allowGet) {
|
||||
reply.code(405);
|
||||
reply.send();
|
||||
return;
|
||||
}
|
||||
|
||||
this.apiCallService.handleMultipartRequest(ep, request, reply);
|
||||
|
||||
// Await so that any error can automatically be translated to HTTP 500
|
||||
await this.apiCallService.handleMultipartRequest(ep, request, reply);
|
||||
return reply;
|
||||
});
|
||||
} else {
|
||||
fastify.all<{
|
||||
Params: { endpoint: string; },
|
||||
Body: Record<string, unknown>,
|
||||
Querystring: Record<string, unknown>,
|
||||
}>('/' + endpoint.name, { bodyLimit: 1024 * 32 }, (request, reply) => {
|
||||
}>('/' + endpoint.name, { bodyLimit: 1024 * 32 }, async (request, reply) => {
|
||||
if (request.method === 'GET' && !endpoint.meta.allowGet) {
|
||||
reply.code(405);
|
||||
reply.send();
|
||||
return;
|
||||
}
|
||||
|
||||
this.apiCallService.handleRequest(ep, request, reply);
|
||||
|
||||
// Await so that any error can automatically be translated to HTTP 500
|
||||
await this.apiCallService.handleRequest(ep, request, reply);
|
||||
return reply;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -160,6 +164,22 @@ export class ApiServerService {
|
||||
}
|
||||
});
|
||||
|
||||
// Make sure any unknown path under /api returns HTTP 404 Not Found,
|
||||
// because otherwise ClientServerService will return the base client HTML
|
||||
// page with HTTP 200.
|
||||
fastify.get('*', (request, reply) => {
|
||||
reply.code(404);
|
||||
// Mock ApiCallService.send's error handling
|
||||
reply.send({
|
||||
error: {
|
||||
message: 'Unknown API endpoint.',
|
||||
code: 'UNKNOWN_API_ENDPOINT',
|
||||
id: '2ca3b769-540a-4f08-9dd5-b5a825b6d0f1',
|
||||
kind: 'client',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { AccessTokensRepository, AppsRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { CacheableLocalUser, ILocalUser } from '@/models/entities/User.js';
|
||||
import type { LocalUser } from '@/models/entities/User.js';
|
||||
import type { AccessToken } from '@/models/entities/AccessToken.js';
|
||||
import { Cache } from '@/misc/cache.js';
|
||||
import type { App } from '@/models/entities/App.js';
|
||||
@@ -36,14 +36,14 @@ export class AuthenticateService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async authenticate(token: string | null | undefined): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> {
|
||||
public async authenticate(token: string | null | undefined): Promise<[LocalUser | null | undefined, AccessToken | null | undefined]> {
|
||||
if (token == null) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
if (isNativeToken(token)) {
|
||||
const user = await this.userCacheService.localUserByNativeTokenCache.fetch(token,
|
||||
() => this.usersRepository.findOneBy({ token }) as Promise<ILocalUser | null>);
|
||||
() => this.usersRepository.findOneBy({ token }) as Promise<LocalUser | null>);
|
||||
|
||||
if (user == null) {
|
||||
throw new AuthenticationError('user not found');
|
||||
@@ -70,7 +70,7 @@ export class AuthenticateService {
|
||||
const user = await this.userCacheService.localUserByIdCache.fetch(accessToken.userId,
|
||||
() => this.usersRepository.findOneBy({
|
||||
id: accessToken.userId,
|
||||
}) as Promise<ILocalUser>);
|
||||
}) as Promise<LocalUser>);
|
||||
|
||||
if (accessToken.appId) {
|
||||
const app = await this.appCache.fetch(accessToken.appId,
|
||||
|
@@ -66,6 +66,7 @@ import * as ep___admin_roles_update from './endpoints/admin/roles/update.js';
|
||||
import * as ep___admin_roles_assign from './endpoints/admin/roles/assign.js';
|
||||
import * as ep___admin_roles_unassign from './endpoints/admin/roles/unassign.js';
|
||||
import * as ep___admin_roles_updateDefaultPolicies from './endpoints/admin/roles/update-default-policies.js';
|
||||
import * as ep___admin_roles_users from './endpoints/admin/roles/users.js';
|
||||
import * as ep___announcements from './endpoints/announcements.js';
|
||||
import * as ep___antennas_create from './endpoints/antennas/create.js';
|
||||
import * as ep___antennas_delete from './endpoints/antennas/delete.js';
|
||||
@@ -170,6 +171,7 @@ import * as ep___i_2fa_keyDone from './endpoints/i/2fa/key-done.js';
|
||||
import * as ep___i_2fa_passwordLess from './endpoints/i/2fa/password-less.js';
|
||||
import * as ep___i_2fa_registerKey from './endpoints/i/2fa/register-key.js';
|
||||
import * as ep___i_2fa_register from './endpoints/i/2fa/register.js';
|
||||
import * as ep___i_2fa_updateKey from './endpoints/i/2fa/update-key.js';
|
||||
import * as ep___i_2fa_removeKey from './endpoints/i/2fa/remove-key.js';
|
||||
import * as ep___i_2fa_unregister from './endpoints/i/2fa/unregister.js';
|
||||
import * as ep___i_apps from './endpoints/i/apps.js';
|
||||
@@ -195,7 +197,6 @@ import * as ep___i_notifications from './endpoints/i/notifications.js';
|
||||
import * as ep___i_pageLikes from './endpoints/i/page-likes.js';
|
||||
import * as ep___i_pages from './endpoints/i/pages.js';
|
||||
import * as ep___i_pin from './endpoints/i/pin.js';
|
||||
import * as ep___i_readAllMessagingMessages from './endpoints/i/read-all-messaging-messages.js';
|
||||
import * as ep___i_readAllUnreadNotes from './endpoints/i/read-all-unread-notes.js';
|
||||
import * as ep___i_readAnnouncement from './endpoints/i/read-announcement.js';
|
||||
import * as ep___i_regenerateToken from './endpoints/i/regenerate-token.js';
|
||||
@@ -212,17 +213,11 @@ import * as ep___i_signinHistory from './endpoints/i/signin-history.js';
|
||||
import * as ep___i_unpin from './endpoints/i/unpin.js';
|
||||
import * as ep___i_updateEmail from './endpoints/i/update-email.js';
|
||||
import * as ep___i_update from './endpoints/i/update.js';
|
||||
import * as ep___i_userGroupInvites from './endpoints/i/user-group-invites.js';
|
||||
import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
|
||||
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___messaging_history from './endpoints/messaging/history.js';
|
||||
import * as ep___messaging_messages from './endpoints/messaging/messages.js';
|
||||
import * as ep___messaging_messages_create from './endpoints/messaging/messages/create.js';
|
||||
import * as ep___messaging_messages_delete from './endpoints/messaging/messages/delete.js';
|
||||
import * as ep___messaging_messages_read from './endpoints/messaging/messages/read.js';
|
||||
import * as ep___meta from './endpoints/meta.js';
|
||||
import * as ep___emojis from './endpoints/emojis.js';
|
||||
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
||||
@@ -283,6 +278,9 @@ import * as ep___flash_myLikes from './endpoints/flash/my-likes.js';
|
||||
import * as ep___ping from './endpoints/ping.js';
|
||||
import * as ep___pinnedUsers from './endpoints/pinned-users.js';
|
||||
import * as ep___promo_read from './endpoints/promo/read.js';
|
||||
import * as ep___roles_list from './endpoints/roles/list.js';
|
||||
import * as ep___roles_show from './endpoints/roles/show.js';
|
||||
import * as ep___roles_users from './endpoints/roles/users.js';
|
||||
import * as ep___requestResetPassword from './endpoints/request-reset-password.js';
|
||||
import * as ep___resetDb from './endpoints/reset-db.js';
|
||||
import * as ep___resetPassword from './endpoints/reset-password.js';
|
||||
@@ -300,18 +298,6 @@ import * as ep___users_followers from './endpoints/users/followers.js';
|
||||
import * as ep___users_following from './endpoints/users/following.js';
|
||||
import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
|
||||
import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
|
||||
import * as ep___users_groups_create from './endpoints/users/groups/create.js';
|
||||
import * as ep___users_groups_delete from './endpoints/users/groups/delete.js';
|
||||
import * as ep___users_groups_invitations_accept from './endpoints/users/groups/invitations/accept.js';
|
||||
import * as ep___users_groups_invitations_reject from './endpoints/users/groups/invitations/reject.js';
|
||||
import * as ep___users_groups_invite from './endpoints/users/groups/invite.js';
|
||||
import * as ep___users_groups_joined from './endpoints/users/groups/joined.js';
|
||||
import * as ep___users_groups_leave from './endpoints/users/groups/leave.js';
|
||||
import * as ep___users_groups_owned from './endpoints/users/groups/owned.js';
|
||||
import * as ep___users_groups_pull from './endpoints/users/groups/pull.js';
|
||||
import * as ep___users_groups_show from './endpoints/users/groups/show.js';
|
||||
import * as ep___users_groups_transfer from './endpoints/users/groups/transfer.js';
|
||||
import * as ep___users_groups_update from './endpoints/users/groups/update.js';
|
||||
import * as ep___users_lists_create from './endpoints/users/lists/create.js';
|
||||
import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
|
||||
import * as ep___users_lists_list from './endpoints/users/lists/list.js';
|
||||
@@ -401,6 +387,7 @@ const $admin_roles_update: Provider = { provide: 'ep:admin/roles/update', useCla
|
||||
const $admin_roles_assign: Provider = { provide: 'ep:admin/roles/assign', useClass: ep___admin_roles_assign.default };
|
||||
const $admin_roles_unassign: Provider = { provide: 'ep:admin/roles/unassign', useClass: ep___admin_roles_unassign.default };
|
||||
const $admin_roles_updateDefaultPolicies: Provider = { provide: 'ep:admin/roles/update-default-policies', useClass: ep___admin_roles_updateDefaultPolicies.default };
|
||||
const $admin_roles_users: Provider = { provide: 'ep:admin/roles/users', useClass: ep___admin_roles_users.default };
|
||||
const $announcements: Provider = { provide: 'ep:announcements', useClass: ep___announcements.default };
|
||||
const $antennas_create: Provider = { provide: 'ep:antennas/create', useClass: ep___antennas_create.default };
|
||||
const $antennas_delete: Provider = { provide: 'ep:antennas/delete', useClass: ep___antennas_delete.default };
|
||||
@@ -505,6 +492,7 @@ const $i_2fa_keyDone: Provider = { provide: 'ep:i/2fa/key-done', useClass: ep___
|
||||
const $i_2fa_passwordLess: Provider = { provide: 'ep:i/2fa/password-less', useClass: ep___i_2fa_passwordLess.default };
|
||||
const $i_2fa_registerKey: Provider = { provide: 'ep:i/2fa/register-key', useClass: ep___i_2fa_registerKey.default };
|
||||
const $i_2fa_register: Provider = { provide: 'ep:i/2fa/register', useClass: ep___i_2fa_register.default };
|
||||
const $i_2fa_updateKey: Provider = { provide: 'ep:i/2fa/update-key', useClass: ep___i_2fa_updateKey.default };
|
||||
const $i_2fa_removeKey: Provider = { provide: 'ep:i/2fa/remove-key', useClass: ep___i_2fa_removeKey.default };
|
||||
const $i_2fa_unregister: Provider = { provide: 'ep:i/2fa/unregister', useClass: ep___i_2fa_unregister.default };
|
||||
const $i_apps: Provider = { provide: 'ep:i/apps', useClass: ep___i_apps.default };
|
||||
@@ -530,7 +518,6 @@ const $i_notifications: Provider = { provide: 'ep:i/notifications', useClass: ep
|
||||
const $i_pageLikes: Provider = { provide: 'ep:i/page-likes', useClass: ep___i_pageLikes.default };
|
||||
const $i_pages: Provider = { provide: 'ep:i/pages', useClass: ep___i_pages.default };
|
||||
const $i_pin: Provider = { provide: 'ep:i/pin', useClass: ep___i_pin.default };
|
||||
const $i_readAllMessagingMessages: Provider = { provide: 'ep:i/read-all-messaging-messages', useClass: ep___i_readAllMessagingMessages.default };
|
||||
const $i_readAllUnreadNotes: Provider = { provide: 'ep:i/read-all-unread-notes', useClass: ep___i_readAllUnreadNotes.default };
|
||||
const $i_readAnnouncement: Provider = { provide: 'ep:i/read-announcement', useClass: ep___i_readAnnouncement.default };
|
||||
const $i_regenerateToken: Provider = { provide: 'ep:i/regenerate-token', useClass: ep___i_regenerateToken.default };
|
||||
@@ -547,17 +534,11 @@ const $i_signinHistory: Provider = { provide: 'ep:i/signin-history', useClass: e
|
||||
const $i_unpin: Provider = { provide: 'ep:i/unpin', useClass: ep___i_unpin.default };
|
||||
const $i_updateEmail: Provider = { provide: 'ep:i/update-email', useClass: ep___i_updateEmail.default };
|
||||
const $i_update: Provider = { provide: 'ep:i/update', useClass: ep___i_update.default };
|
||||
const $i_userGroupInvites: Provider = { provide: 'ep:i/user-group-invites', useClass: ep___i_userGroupInvites.default };
|
||||
const $i_webhooks_create: Provider = { provide: 'ep:i/webhooks/create', useClass: ep___i_webhooks_create.default };
|
||||
const $i_webhooks_list: Provider = { provide: 'ep:i/webhooks/list', useClass: ep___i_webhooks_list.default };
|
||||
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 $messaging_history: Provider = { provide: 'ep:messaging/history', useClass: ep___messaging_history.default };
|
||||
const $messaging_messages: Provider = { provide: 'ep:messaging/messages', useClass: ep___messaging_messages.default };
|
||||
const $messaging_messages_create: Provider = { provide: 'ep:messaging/messages/create', useClass: ep___messaging_messages_create.default };
|
||||
const $messaging_messages_delete: Provider = { provide: 'ep:messaging/messages/delete', useClass: ep___messaging_messages_delete.default };
|
||||
const $messaging_messages_read: Provider = { provide: 'ep:messaging/messages/read', useClass: ep___messaging_messages_read.default };
|
||||
const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default };
|
||||
const $emojis: Provider = { provide: 'ep:emojis', useClass: ep___emojis.default };
|
||||
const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default };
|
||||
@@ -618,6 +599,9 @@ const $flash_myLikes: Provider = { provide: 'ep:flash/my-likes', useClass: ep___
|
||||
const $ping: Provider = { provide: 'ep:ping', useClass: ep___ping.default };
|
||||
const $pinnedUsers: Provider = { provide: 'ep:pinned-users', useClass: ep___pinnedUsers.default };
|
||||
const $promo_read: Provider = { provide: 'ep:promo/read', useClass: ep___promo_read.default };
|
||||
const $roles_list: Provider = { provide: 'ep:roles/list', useClass: ep___roles_list.default };
|
||||
const $roles_show: Provider = { provide: 'ep:roles/show', useClass: ep___roles_show.default };
|
||||
const $roles_users: Provider = { provide: 'ep:roles/users', useClass: ep___roles_users.default };
|
||||
const $requestResetPassword: Provider = { provide: 'ep:request-reset-password', useClass: ep___requestResetPassword.default };
|
||||
const $resetDb: Provider = { provide: 'ep:reset-db', useClass: ep___resetDb.default };
|
||||
const $resetPassword: Provider = { provide: 'ep:reset-password', useClass: ep___resetPassword.default };
|
||||
@@ -635,18 +619,6 @@ const $users_followers: Provider = { provide: 'ep:users/followers', useClass: ep
|
||||
const $users_following: Provider = { provide: 'ep:users/following', useClass: ep___users_following.default };
|
||||
const $users_gallery_posts: Provider = { provide: 'ep:users/gallery/posts', useClass: ep___users_gallery_posts.default };
|
||||
const $users_getFrequentlyRepliedUsers: Provider = { provide: 'ep:users/get-frequently-replied-users', useClass: ep___users_getFrequentlyRepliedUsers.default };
|
||||
const $users_groups_create: Provider = { provide: 'ep:users/groups/create', useClass: ep___users_groups_create.default };
|
||||
const $users_groups_delete: Provider = { provide: 'ep:users/groups/delete', useClass: ep___users_groups_delete.default };
|
||||
const $users_groups_invitations_accept: Provider = { provide: 'ep:users/groups/invitations/accept', useClass: ep___users_groups_invitations_accept.default };
|
||||
const $users_groups_invitations_reject: Provider = { provide: 'ep:users/groups/invitations/reject', useClass: ep___users_groups_invitations_reject.default };
|
||||
const $users_groups_invite: Provider = { provide: 'ep:users/groups/invite', useClass: ep___users_groups_invite.default };
|
||||
const $users_groups_joined: Provider = { provide: 'ep:users/groups/joined', useClass: ep___users_groups_joined.default };
|
||||
const $users_groups_leave: Provider = { provide: 'ep:users/groups/leave', useClass: ep___users_groups_leave.default };
|
||||
const $users_groups_owned: Provider = { provide: 'ep:users/groups/owned', useClass: ep___users_groups_owned.default };
|
||||
const $users_groups_pull: Provider = { provide: 'ep:users/groups/pull', useClass: ep___users_groups_pull.default };
|
||||
const $users_groups_show: Provider = { provide: 'ep:users/groups/show', useClass: ep___users_groups_show.default };
|
||||
const $users_groups_transfer: Provider = { provide: 'ep:users/groups/transfer', useClass: ep___users_groups_transfer.default };
|
||||
const $users_groups_update: Provider = { provide: 'ep:users/groups/update', useClass: ep___users_groups_update.default };
|
||||
const $users_lists_create: Provider = { provide: 'ep:users/lists/create', useClass: ep___users_lists_create.default };
|
||||
const $users_lists_delete: Provider = { provide: 'ep:users/lists/delete', useClass: ep___users_lists_delete.default };
|
||||
const $users_lists_list: Provider = { provide: 'ep:users/lists/list', useClass: ep___users_lists_list.default };
|
||||
@@ -740,6 +712,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$admin_roles_assign,
|
||||
$admin_roles_unassign,
|
||||
$admin_roles_updateDefaultPolicies,
|
||||
$admin_roles_users,
|
||||
$announcements,
|
||||
$antennas_create,
|
||||
$antennas_delete,
|
||||
@@ -844,6 +817,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$i_2fa_passwordLess,
|
||||
$i_2fa_registerKey,
|
||||
$i_2fa_register,
|
||||
$i_2fa_updateKey,
|
||||
$i_2fa_removeKey,
|
||||
$i_2fa_unregister,
|
||||
$i_apps,
|
||||
@@ -869,7 +843,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$i_pageLikes,
|
||||
$i_pages,
|
||||
$i_pin,
|
||||
$i_readAllMessagingMessages,
|
||||
$i_readAllUnreadNotes,
|
||||
$i_readAnnouncement,
|
||||
$i_regenerateToken,
|
||||
@@ -886,17 +859,11 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$i_unpin,
|
||||
$i_updateEmail,
|
||||
$i_update,
|
||||
$i_userGroupInvites,
|
||||
$i_webhooks_create,
|
||||
$i_webhooks_list,
|
||||
$i_webhooks_show,
|
||||
$i_webhooks_update,
|
||||
$i_webhooks_delete,
|
||||
$messaging_history,
|
||||
$messaging_messages,
|
||||
$messaging_messages_create,
|
||||
$messaging_messages_delete,
|
||||
$messaging_messages_read,
|
||||
$meta,
|
||||
$emojis,
|
||||
$miauth_genToken,
|
||||
@@ -957,6 +924,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$ping,
|
||||
$pinnedUsers,
|
||||
$promo_read,
|
||||
$roles_list,
|
||||
$roles_show,
|
||||
$roles_users,
|
||||
$requestResetPassword,
|
||||
$resetDb,
|
||||
$resetPassword,
|
||||
@@ -974,18 +944,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$users_following,
|
||||
$users_gallery_posts,
|
||||
$users_getFrequentlyRepliedUsers,
|
||||
$users_groups_create,
|
||||
$users_groups_delete,
|
||||
$users_groups_invitations_accept,
|
||||
$users_groups_invitations_reject,
|
||||
$users_groups_invite,
|
||||
$users_groups_joined,
|
||||
$users_groups_leave,
|
||||
$users_groups_owned,
|
||||
$users_groups_pull,
|
||||
$users_groups_show,
|
||||
$users_groups_transfer,
|
||||
$users_groups_update,
|
||||
$users_lists_create,
|
||||
$users_lists_delete,
|
||||
$users_lists_list,
|
||||
@@ -1073,6 +1031,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$admin_roles_assign,
|
||||
$admin_roles_unassign,
|
||||
$admin_roles_updateDefaultPolicies,
|
||||
$admin_roles_users,
|
||||
$announcements,
|
||||
$antennas_create,
|
||||
$antennas_delete,
|
||||
@@ -1177,6 +1136,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$i_2fa_passwordLess,
|
||||
$i_2fa_registerKey,
|
||||
$i_2fa_register,
|
||||
$i_2fa_updateKey,
|
||||
$i_2fa_removeKey,
|
||||
$i_2fa_unregister,
|
||||
$i_apps,
|
||||
@@ -1202,7 +1162,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$i_pageLikes,
|
||||
$i_pages,
|
||||
$i_pin,
|
||||
$i_readAllMessagingMessages,
|
||||
$i_readAllUnreadNotes,
|
||||
$i_readAnnouncement,
|
||||
$i_regenerateToken,
|
||||
@@ -1219,17 +1178,11 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$i_unpin,
|
||||
$i_updateEmail,
|
||||
$i_update,
|
||||
$i_userGroupInvites,
|
||||
$i_webhooks_create,
|
||||
$i_webhooks_list,
|
||||
$i_webhooks_show,
|
||||
$i_webhooks_update,
|
||||
$i_webhooks_delete,
|
||||
$messaging_history,
|
||||
$messaging_messages,
|
||||
$messaging_messages_create,
|
||||
$messaging_messages_delete,
|
||||
$messaging_messages_read,
|
||||
$meta,
|
||||
$emojis,
|
||||
$miauth_genToken,
|
||||
@@ -1290,6 +1243,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$ping,
|
||||
$pinnedUsers,
|
||||
$promo_read,
|
||||
$roles_list,
|
||||
$roles_show,
|
||||
$roles_users,
|
||||
$requestResetPassword,
|
||||
$resetDb,
|
||||
$resetPassword,
|
||||
@@ -1305,18 +1261,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$users_following,
|
||||
$users_gallery_posts,
|
||||
$users_getFrequentlyRepliedUsers,
|
||||
$users_groups_create,
|
||||
$users_groups_delete,
|
||||
$users_groups_invitations_accept,
|
||||
$users_groups_invitations_reject,
|
||||
$users_groups_invite,
|
||||
$users_groups_joined,
|
||||
$users_groups_leave,
|
||||
$users_groups_owned,
|
||||
$users_groups_pull,
|
||||
$users_groups_show,
|
||||
$users_groups_transfer,
|
||||
$users_groups_update,
|
||||
$users_lists_create,
|
||||
$users_lists_delete,
|
||||
$users_lists_list,
|
||||
|
@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { NotesRepository, UsersRepository } from '@/models/index.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import type { User } from '@/models/entities/User.js';
|
||||
import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
|
||||
import type { Note } from '@/models/entities/Note.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
@@ -45,7 +45,7 @@ export class GetterService {
|
||||
throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.');
|
||||
}
|
||||
|
||||
return user;
|
||||
return user as LocalUser | RemoteUser;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import * as speakeasy from 'speakeasy';
|
||||
import * as OTPAuth from 'otpauth';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { UserSecurityKeysRepository, SigninsRepository, UserProfilesRepository, AttestationChallengesRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { getIpHash } from '@/misc/get-ip-hash.js';
|
||||
import type { ILocalUser } from '@/models/entities/User.js';
|
||||
import type { LocalUser } from '@/models/entities/User.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
@@ -105,7 +105,7 @@ export class SigninApiService {
|
||||
const user = await this.usersRepository.findOneBy({
|
||||
usernameLower: username.toLowerCase(),
|
||||
host: IsNull(),
|
||||
}) as ILocalUser;
|
||||
}) as LocalUser;
|
||||
|
||||
if (user == null) {
|
||||
return error(404, {
|
||||
@@ -155,19 +155,19 @@ export class SigninApiService {
|
||||
});
|
||||
}
|
||||
|
||||
const verified = (speakeasy as any).totp.verify({
|
||||
secret: profile.twoFactorSecret,
|
||||
encoding: 'base32',
|
||||
token: token,
|
||||
window: 2,
|
||||
const delta = OTPAuth.TOTP.validate({
|
||||
secret: OTPAuth.Secret.fromBase32(profile.twoFactorSecret!),
|
||||
digits: 6,
|
||||
token,
|
||||
window: 1,
|
||||
});
|
||||
|
||||
if (verified) {
|
||||
return this.signinService.signin(request, reply, user);
|
||||
} else {
|
||||
if (delta === null) {
|
||||
return await fail(403, {
|
||||
id: 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f',
|
||||
});
|
||||
} else {
|
||||
return this.signinService.signin(request, reply, user);
|
||||
}
|
||||
} else if (body.credentialId && body.clientDataJSON && body.authenticatorData && body.signature) {
|
||||
if (!same && !profile.usePasswordLessLogin) {
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { SigninsRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { SigninsRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { ILocalUser } from '@/models/entities/User.js';
|
||||
import type { LocalUser } from '@/models/entities/User.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { SigninEntityService } from '@/core/entities/SigninEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
@@ -25,7 +25,7 @@ export class SigninService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public signin(request: FastifyRequest, reply: FastifyReply, user: ILocalUser) {
|
||||
public signin(request: FastifyRequest, reply: FastifyReply, user: LocalUser) {
|
||||
setImmediate(async () => {
|
||||
// Append signin history
|
||||
const record = await this.signinsRepository.insert({
|
||||
|
@@ -10,7 +10,7 @@ import { IdService } from '@/core/IdService.js';
|
||||
import { SignupService } from '@/core/SignupService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { EmailService } from '@/core/EmailService.js';
|
||||
import { ILocalUser } from '@/models/entities/User.js';
|
||||
import { LocalUser } from '@/models/entities/User.js';
|
||||
import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { SigninService } from './SigninService.js';
|
||||
@@ -194,7 +194,7 @@ export class SignupApiService {
|
||||
emailVerifyCode: null,
|
||||
});
|
||||
|
||||
return this.signinService.signin(request, reply, account as ILocalUser);
|
||||
return this.signinService.signin(request, reply, account as LocalUser);
|
||||
} catch (err) {
|
||||
throw new FastifyReplyError(400, typeof err === 'string' ? err : (err as Error).toString());
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import * as fs from 'node:fs';
|
||||
import Ajv from 'ajv';
|
||||
import type { Schema, SchemaType } from '@/misc/schema.js';
|
||||
import type { CacheableLocalUser } from '@/models/entities/User.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';
|
||||
@@ -20,17 +20,17 @@ type File = {
|
||||
};
|
||||
|
||||
// TODO: paramsの型をT['params']のスキーマ定義から推論する
|
||||
type executor<T extends IEndpointMeta, Ps extends Schema> =
|
||||
(params: SchemaType<Ps>, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: File, cleanup?: () => any, ip?: string | null, headers?: Record<string, string> | null) =>
|
||||
type Executor<T extends IEndpointMeta, Ps extends Schema> =
|
||||
(params: SchemaType<Ps>, user: T['requireCredential'] extends true ? LocalUser : LocalUser | null, token: AccessToken | null, file?: File, cleanup?: () => any, ip?: string | null, headers?: Record<string, string> | null) =>
|
||||
Promise<T['res'] extends undefined ? Response : SchemaType<NonNullable<T['res']>>>;
|
||||
|
||||
export abstract class Endpoint<T extends IEndpointMeta, Ps extends Schema> {
|
||||
public exec: (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: File, ip?: string | null, headers?: Record<string, string> | null) => Promise<any>;
|
||||
public exec: (params: any, user: T['requireCredential'] extends true ? LocalUser : LocalUser | null, token: AccessToken | null, file?: File, ip?: string | null, headers?: Record<string, string> | null) => Promise<any>;
|
||||
|
||||
constructor(meta: T, paramDef: Ps, cb: executor<T, Ps>) {
|
||||
constructor(meta: T, paramDef: Ps, cb: Executor<T, Ps>) {
|
||||
const validate = ajv.compile(paramDef);
|
||||
|
||||
this.exec = (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: File, ip?: string | null, headers?: Record<string, string> | null) => {
|
||||
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) {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import type { Schema } from '@/misc/schema.js';
|
||||
import { RolePolicies } from '@/core/RoleService.js';
|
||||
|
||||
import * as ep___admin_meta from './endpoints/admin/meta.js';
|
||||
import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
|
||||
@@ -65,6 +66,7 @@ import * as ep___admin_roles_update from './endpoints/admin/roles/update.js';
|
||||
import * as ep___admin_roles_assign from './endpoints/admin/roles/assign.js';
|
||||
import * as ep___admin_roles_unassign from './endpoints/admin/roles/unassign.js';
|
||||
import * as ep___admin_roles_updateDefaultPolicies from './endpoints/admin/roles/update-default-policies.js';
|
||||
import * as ep___admin_roles_users from './endpoints/admin/roles/users.js';
|
||||
import * as ep___announcements from './endpoints/announcements.js';
|
||||
import * as ep___antennas_create from './endpoints/antennas/create.js';
|
||||
import * as ep___antennas_delete from './endpoints/antennas/delete.js';
|
||||
@@ -169,6 +171,7 @@ import * as ep___i_2fa_keyDone from './endpoints/i/2fa/key-done.js';
|
||||
import * as ep___i_2fa_passwordLess from './endpoints/i/2fa/password-less.js';
|
||||
import * as ep___i_2fa_registerKey from './endpoints/i/2fa/register-key.js';
|
||||
import * as ep___i_2fa_register from './endpoints/i/2fa/register.js';
|
||||
import * as ep___i_2fa_updateKey from './endpoints/i/2fa/update-key.js';
|
||||
import * as ep___i_2fa_removeKey from './endpoints/i/2fa/remove-key.js';
|
||||
import * as ep___i_2fa_unregister from './endpoints/i/2fa/unregister.js';
|
||||
import * as ep___i_apps from './endpoints/i/apps.js';
|
||||
@@ -194,7 +197,6 @@ import * as ep___i_notifications from './endpoints/i/notifications.js';
|
||||
import * as ep___i_pageLikes from './endpoints/i/page-likes.js';
|
||||
import * as ep___i_pages from './endpoints/i/pages.js';
|
||||
import * as ep___i_pin from './endpoints/i/pin.js';
|
||||
import * as ep___i_readAllMessagingMessages from './endpoints/i/read-all-messaging-messages.js';
|
||||
import * as ep___i_readAllUnreadNotes from './endpoints/i/read-all-unread-notes.js';
|
||||
import * as ep___i_readAnnouncement from './endpoints/i/read-announcement.js';
|
||||
import * as ep___i_regenerateToken from './endpoints/i/regenerate-token.js';
|
||||
@@ -211,17 +213,11 @@ import * as ep___i_signinHistory from './endpoints/i/signin-history.js';
|
||||
import * as ep___i_unpin from './endpoints/i/unpin.js';
|
||||
import * as ep___i_updateEmail from './endpoints/i/update-email.js';
|
||||
import * as ep___i_update from './endpoints/i/update.js';
|
||||
import * as ep___i_userGroupInvites from './endpoints/i/user-group-invites.js';
|
||||
import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
|
||||
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___messaging_history from './endpoints/messaging/history.js';
|
||||
import * as ep___messaging_messages from './endpoints/messaging/messages.js';
|
||||
import * as ep___messaging_messages_create from './endpoints/messaging/messages/create.js';
|
||||
import * as ep___messaging_messages_delete from './endpoints/messaging/messages/delete.js';
|
||||
import * as ep___messaging_messages_read from './endpoints/messaging/messages/read.js';
|
||||
import * as ep___meta from './endpoints/meta.js';
|
||||
import * as ep___emojis from './endpoints/emojis.js';
|
||||
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
||||
@@ -282,6 +278,9 @@ import * as ep___flash_myLikes from './endpoints/flash/my-likes.js';
|
||||
import * as ep___ping from './endpoints/ping.js';
|
||||
import * as ep___pinnedUsers from './endpoints/pinned-users.js';
|
||||
import * as ep___promo_read from './endpoints/promo/read.js';
|
||||
import * as ep___roles_list from './endpoints/roles/list.js';
|
||||
import * as ep___roles_show from './endpoints/roles/show.js';
|
||||
import * as ep___roles_users from './endpoints/roles/users.js';
|
||||
import * as ep___requestResetPassword from './endpoints/request-reset-password.js';
|
||||
import * as ep___resetDb from './endpoints/reset-db.js';
|
||||
import * as ep___resetPassword from './endpoints/reset-password.js';
|
||||
@@ -299,18 +298,6 @@ import * as ep___users_followers from './endpoints/users/followers.js';
|
||||
import * as ep___users_following from './endpoints/users/following.js';
|
||||
import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
|
||||
import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
|
||||
import * as ep___users_groups_create from './endpoints/users/groups/create.js';
|
||||
import * as ep___users_groups_delete from './endpoints/users/groups/delete.js';
|
||||
import * as ep___users_groups_invitations_accept from './endpoints/users/groups/invitations/accept.js';
|
||||
import * as ep___users_groups_invitations_reject from './endpoints/users/groups/invitations/reject.js';
|
||||
import * as ep___users_groups_invite from './endpoints/users/groups/invite.js';
|
||||
import * as ep___users_groups_joined from './endpoints/users/groups/joined.js';
|
||||
import * as ep___users_groups_leave from './endpoints/users/groups/leave.js';
|
||||
import * as ep___users_groups_owned from './endpoints/users/groups/owned.js';
|
||||
import * as ep___users_groups_pull from './endpoints/users/groups/pull.js';
|
||||
import * as ep___users_groups_show from './endpoints/users/groups/show.js';
|
||||
import * as ep___users_groups_transfer from './endpoints/users/groups/transfer.js';
|
||||
import * as ep___users_groups_update from './endpoints/users/groups/update.js';
|
||||
import * as ep___users_lists_create from './endpoints/users/lists/create.js';
|
||||
import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
|
||||
import * as ep___users_lists_list from './endpoints/users/lists/list.js';
|
||||
@@ -398,6 +385,7 @@ const eps = [
|
||||
['admin/roles/assign', ep___admin_roles_assign],
|
||||
['admin/roles/unassign', ep___admin_roles_unassign],
|
||||
['admin/roles/update-default-policies', ep___admin_roles_updateDefaultPolicies],
|
||||
['admin/roles/users', ep___admin_roles_users],
|
||||
['announcements', ep___announcements],
|
||||
['antennas/create', ep___antennas_create],
|
||||
['antennas/delete', ep___antennas_delete],
|
||||
@@ -502,6 +490,7 @@ const eps = [
|
||||
['i/2fa/password-less', ep___i_2fa_passwordLess],
|
||||
['i/2fa/register-key', ep___i_2fa_registerKey],
|
||||
['i/2fa/register', ep___i_2fa_register],
|
||||
['i/2fa/update-key', ep___i_2fa_updateKey],
|
||||
['i/2fa/remove-key', ep___i_2fa_removeKey],
|
||||
['i/2fa/unregister', ep___i_2fa_unregister],
|
||||
['i/apps', ep___i_apps],
|
||||
@@ -527,7 +516,6 @@ const eps = [
|
||||
['i/page-likes', ep___i_pageLikes],
|
||||
['i/pages', ep___i_pages],
|
||||
['i/pin', ep___i_pin],
|
||||
['i/read-all-messaging-messages', ep___i_readAllMessagingMessages],
|
||||
['i/read-all-unread-notes', ep___i_readAllUnreadNotes],
|
||||
['i/read-announcement', ep___i_readAnnouncement],
|
||||
['i/regenerate-token', ep___i_regenerateToken],
|
||||
@@ -544,17 +532,11 @@ const eps = [
|
||||
['i/unpin', ep___i_unpin],
|
||||
['i/update-email', ep___i_updateEmail],
|
||||
['i/update', ep___i_update],
|
||||
['i/user-group-invites', ep___i_userGroupInvites],
|
||||
['i/webhooks/create', ep___i_webhooks_create],
|
||||
['i/webhooks/list', ep___i_webhooks_list],
|
||||
['i/webhooks/show', ep___i_webhooks_show],
|
||||
['i/webhooks/update', ep___i_webhooks_update],
|
||||
['i/webhooks/delete', ep___i_webhooks_delete],
|
||||
['messaging/history', ep___messaging_history],
|
||||
['messaging/messages', ep___messaging_messages],
|
||||
['messaging/messages/create', ep___messaging_messages_create],
|
||||
['messaging/messages/delete', ep___messaging_messages_delete],
|
||||
['messaging/messages/read', ep___messaging_messages_read],
|
||||
['meta', ep___meta],
|
||||
['emojis', ep___emojis],
|
||||
['miauth/gen-token', ep___miauth_genToken],
|
||||
@@ -615,6 +597,9 @@ const eps = [
|
||||
['ping', ep___ping],
|
||||
['pinned-users', ep___pinnedUsers],
|
||||
['promo/read', ep___promo_read],
|
||||
['roles/list', ep___roles_list],
|
||||
['roles/show', ep___roles_show],
|
||||
['roles/users', ep___roles_users],
|
||||
['request-reset-password', ep___requestResetPassword],
|
||||
['reset-db', ep___resetDb],
|
||||
['reset-password', ep___resetPassword],
|
||||
@@ -632,18 +617,6 @@ const eps = [
|
||||
['users/following', ep___users_following],
|
||||
['users/gallery/posts', ep___users_gallery_posts],
|
||||
['users/get-frequently-replied-users', ep___users_getFrequentlyRepliedUsers],
|
||||
['users/groups/create', ep___users_groups_create],
|
||||
['users/groups/delete', ep___users_groups_delete],
|
||||
['users/groups/invitations/accept', ep___users_groups_invitations_accept],
|
||||
['users/groups/invitations/reject', ep___users_groups_invitations_reject],
|
||||
['users/groups/invite', ep___users_groups_invite],
|
||||
['users/groups/joined', ep___users_groups_joined],
|
||||
['users/groups/leave', ep___users_groups_leave],
|
||||
['users/groups/owned', ep___users_groups_owned],
|
||||
['users/groups/pull', ep___users_groups_pull],
|
||||
['users/groups/show', ep___users_groups_show],
|
||||
['users/groups/transfer', ep___users_groups_transfer],
|
||||
['users/groups/update', ep___users_groups_update],
|
||||
['users/lists/create', ep___users_lists_create],
|
||||
['users/lists/delete', ep___users_lists_delete],
|
||||
['users/lists/list', ep___users_lists_list],
|
||||
@@ -697,7 +670,7 @@ export interface IEndpointMeta {
|
||||
*/
|
||||
readonly requireAdmin?: boolean;
|
||||
|
||||
readonly requireRolePolicy?: string;
|
||||
readonly requireRolePolicy?: keyof RolePolicies;
|
||||
|
||||
/**
|
||||
* エンドポイントのリミテーションに関するやつ
|
||||
@@ -768,8 +741,8 @@ export interface IEndpoint {
|
||||
const endpoints: IEndpoint[] = (eps as [string, any]).map(([name, ep]) => {
|
||||
return {
|
||||
name: name,
|
||||
meta: ep.meta ?? {},
|
||||
params: ep.paramDef,
|
||||
get meta() { return ep.meta ?? {}; },
|
||||
get params() { return ep.paramDef; },
|
||||
};
|
||||
});
|
||||
|
||||
|
@@ -20,9 +20,10 @@ export const paramDef = {
|
||||
priority: { type: 'string' },
|
||||
ratio: { type: 'integer' },
|
||||
expiresAt: { type: 'integer' },
|
||||
startsAt: { type: 'integer' },
|
||||
imageUrl: { type: 'string', minLength: 1 },
|
||||
},
|
||||
required: ['url', 'memo', 'place', 'priority', 'ratio', 'expiresAt', 'imageUrl'],
|
||||
required: ['url', 'memo', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'imageUrl'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@@ -39,6 +40,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
id: this.idService.genId(),
|
||||
createdAt: new Date(),
|
||||
expiresAt: new Date(ps.expiresAt),
|
||||
startsAt: new Date(ps.startsAt),
|
||||
url: ps.url,
|
||||
imageUrl: ps.imageUrl,
|
||||
priority: ps.priority,
|
||||
|
@@ -31,9 +31,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.adsRepository.createQueryBuilder('ad'), ps.sinceId, ps.untilId)
|
||||
.andWhere('ad.expiresAt > :now', { now: new Date() });
|
||||
|
||||
const query = this.queryService.makePaginationQuery(this.adsRepository.createQueryBuilder('ad'), ps.sinceId, ps.untilId);
|
||||
const ads = await query.take(ps.limit).getMany();
|
||||
|
||||
return ads;
|
||||
|
@@ -30,8 +30,9 @@ export const paramDef = {
|
||||
priority: { type: 'string' },
|
||||
ratio: { type: 'integer' },
|
||||
expiresAt: { type: 'integer' },
|
||||
startsAt: { type: 'integer' },
|
||||
},
|
||||
required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt'],
|
||||
required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@@ -54,6 +55,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
memo: ps.memo,
|
||||
imageUrl: ps.imageUrl,
|
||||
expiresAt: new Date(ps.expiresAt),
|
||||
startsAt: new Date(ps.startsAt),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { DriveFilesRepository } from '@/models/index.js';
|
||||
import type { DriveFilesRepository, UsersRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
@@ -138,19 +138,13 @@ export const meta = {
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
url: { type: 'string' },
|
||||
},
|
||||
anyOf: [
|
||||
{
|
||||
properties: {
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['fileId'],
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
url: { type: 'string' },
|
||||
},
|
||||
required: ['url'],
|
||||
},
|
||||
{ required: ['fileId'] },
|
||||
{ required: ['url'] },
|
||||
],
|
||||
} as const;
|
||||
|
||||
@@ -161,6 +155,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
@@ -178,7 +175,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
throw new ApiError(meta.errors.noSuchFile);
|
||||
}
|
||||
|
||||
const isModerator = await this.roleService.isModerator(me);
|
||||
const owner = file.userId ? await this.usersRepository.findOneByOrFail({
|
||||
id: file.userId,
|
||||
}) : null;
|
||||
|
||||
const iAmModerator = await this.roleService.isModerator(me);
|
||||
const ownerIsModerator = owner ? await this.roleService.isModerator(owner) : false;
|
||||
|
||||
return {
|
||||
id: file.id,
|
||||
@@ -207,8 +209,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
name: file.name,
|
||||
md5: file.md5,
|
||||
createdAt: file.createdAt.toISOString(),
|
||||
requestIp: isModerator ? file.requestIp : null,
|
||||
requestHeaders: isModerator ? file.requestHeaders : null,
|
||||
requestIp: iAmModerator ? file.requestIp : null,
|
||||
requestHeaders: iAmModerator && !ownerIsModerator ? file.requestHeaders : null,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@@ -56,7 +56,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
await this.db.queryResultCache!.remove(['meta_emojis']);
|
||||
|
||||
this.globalEventService.publishBroadcastStream('emojiUpdated', {
|
||||
emojis: await this.emojiEntityService.packMany(ps.ids),
|
||||
emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import rndstr from 'rndstr';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { DriveFilesRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
@@ -17,7 +16,7 @@ export const meta = {
|
||||
errors: {
|
||||
noSuchFile: {
|
||||
message: 'No such file.',
|
||||
code: 'MO_SUCH_FILE',
|
||||
code: 'NO_SUCH_FILE',
|
||||
id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf',
|
||||
},
|
||||
},
|
||||
|
@@ -92,7 +92,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
await this.db.queryResultCache!.remove(['meta_emojis']);
|
||||
|
||||
this.globalEventService.publishBroadcastStream('emojiAdded', {
|
||||
emoji: await this.emojiEntityService.pack(copied.id),
|
||||
emoji: await this.emojiEntityService.packDetailed(copied.id),
|
||||
});
|
||||
|
||||
return {
|
||||
|
@@ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
|
||||
this.globalEventService.publishBroadcastStream('emojiDeleted', {
|
||||
emojis: await this.emojiEntityService.packMany(emojis),
|
||||
emojis: await this.emojiEntityService.packDetailedMany(emojis),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -4,9 +4,9 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { EmojisRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
@@ -57,7 +57,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
await this.db.queryResultCache!.remove(['meta_emojis']);
|
||||
|
||||
this.globalEventService.publishBroadcastStream('emojiDeleted', {
|
||||
emojis: [ await this.emojiEntityService.pack(emoji) ],
|
||||
emojis: [await this.emojiEntityService.packDetailed(emoji)],
|
||||
});
|
||||
|
||||
this.moderationLogService.insertModerationLog(me, 'deleteEmoji', {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
|
||||
|
@@ -101,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return this.emojiEntityService.packMany(emojis, { omitHost: false, omitId: false, withUrl: false });
|
||||
return this.emojiEntityService.packDetailedMany(emojis);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -98,7 +98,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
emojis = await q.take(ps.limit).getMany();
|
||||
}
|
||||
|
||||
return this.emojiEntityService.packMany(emojis, { omitHost: false, omitId: false, withUrl: false });
|
||||
return this.emojiEntityService.packDetailedMany(emojis);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -56,7 +56,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
await this.db.queryResultCache!.remove(['meta_emojis']);
|
||||
|
||||
this.globalEventService.publishBroadcastStream('emojiUpdated', {
|
||||
emojis: await this.emojiEntityService.packMany(ps.ids),
|
||||
emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -52,7 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
await this.db.queryResultCache!.remove(['meta_emojis']);
|
||||
|
||||
this.globalEventService.publishBroadcastStream('emojiUpdated', {
|
||||
emojis: await this.emojiEntityService.packMany(ps.ids),
|
||||
emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
await this.db.queryResultCache!.remove(['meta_emojis']);
|
||||
|
||||
this.globalEventService.publishBroadcastStream('emojiUpdated', {
|
||||
emojis: await this.emojiEntityService.packMany(ps.ids),
|
||||
emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -3,9 +3,9 @@ import { DataSource } from 'typeorm';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { EmojisRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
@@ -68,15 +68,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
await this.db.queryResultCache!.remove(['meta_emojis']);
|
||||
|
||||
const updated = await this.emojiEntityService.pack(emoji.id);
|
||||
const updated = await this.emojiEntityService.packDetailed(emoji.id);
|
||||
|
||||
if (emoji.name === ps.name) {
|
||||
this.globalEventService.publishBroadcastStream('emojiUpdated', {
|
||||
emojis: [ updated ],
|
||||
emojis: [updated],
|
||||
});
|
||||
} else {
|
||||
this.globalEventService.publishBroadcastStream('emojiDeleted', {
|
||||
emojis: [ await this.emojiEntityService.pack(emoji) ],
|
||||
emojis: [await this.emojiEntityService.packDetailed(emoji)],
|
||||
});
|
||||
|
||||
this.globalEventService.publishBroadcastStream('emojiAdded', {
|
||||
|
@@ -54,86 +54,22 @@ export const meta = {
|
||||
},
|
||||
mascotImageUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
optional: false, nullable: true,
|
||||
default: '/assets/ai.png',
|
||||
},
|
||||
bannerUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
errorImageUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
optional: false, nullable: true,
|
||||
default: 'https://xn--931a.moe/aiart/yubitun.png',
|
||||
},
|
||||
iconUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
maxNoteTextLength: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
emojis: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
aliases: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
category: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
host: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'url',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ads: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
place: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'url',
|
||||
},
|
||||
imageUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'url',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
enableEmail: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
@@ -146,10 +82,6 @@ export const meta = {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
proxyAccountName: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
userStarForReactionFallback: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
@@ -228,7 +160,7 @@ export const meta = {
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
smtpPort: {
|
||||
type: 'string',
|
||||
type: 'number',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
smtpUser: {
|
||||
@@ -299,6 +231,10 @@ export const meta = {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
policies: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -349,7 +285,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
iconUrl: instance.iconUrl,
|
||||
backgroundImageUrl: instance.backgroundImageUrl,
|
||||
logoImageUrl: instance.logoImageUrl,
|
||||
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
|
||||
defaultLightTheme: instance.defaultLightTheme,
|
||||
defaultDarkTheme: instance.defaultDarkTheme,
|
||||
enableEmail: instance.enableEmail,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { URL } from 'node:url';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { RelayService } from '@/core/RelayService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { RelayService } from '@/core/RelayService.js';
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { RelayService } from '@/core/RelayService.js';
|
||||
|
||||
|
@@ -49,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
const actor = await this.instanceActorService.getInstanceActor();
|
||||
const targetUser = await this.usersRepository.findOneByOrFail({ id: report.targetUserId });
|
||||
|
||||
this.queueService.deliver(actor, this.apRendererService.renderActivity(this.apRendererService.renderFlag(actor, [targetUser.uri!], report.comment)), targetUser.inbox);
|
||||
this.queueService.deliver(actor, this.apRendererService.addContext(this.apRendererService.renderFlag(actor, targetUser.uri!, report.comment)), targetUser.inbox);
|
||||
}
|
||||
|
||||
await this.abuseUserReportsRepository.update(report.id, {
|
||||
|
@@ -1,10 +1,8 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { RolesRepository, UsersRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -39,6 +37,10 @@ export const paramDef = {
|
||||
properties: {
|
||||
roleId: { type: 'string', format: 'misskey:id' },
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
expiresAt: {
|
||||
type: 'integer',
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
required: [
|
||||
'roleId',
|
||||
@@ -56,12 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
@Inject(DI.rolesRepository)
|
||||
private rolesRepository: RolesRepository,
|
||||
|
||||
@Inject(DI.roleAssignmentsRepository)
|
||||
private roleAssignmentsRepository: RoleAssignmentsRepository,
|
||||
|
||||
private globalEventService: GlobalEventService,
|
||||
private roleService: RoleService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const role = await this.rolesRepository.findOneBy({ id: ps.roleId });
|
||||
@@ -78,19 +75,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
throw new ApiError(meta.errors.noSuchUser);
|
||||
}
|
||||
|
||||
const date = new Date();
|
||||
const created = await this.roleAssignmentsRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: date,
|
||||
roleId: role.id,
|
||||
userId: user.id,
|
||||
}).then(x => this.roleAssignmentsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
if (ps.expiresAt && ps.expiresAt <= Date.now()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.rolesRepository.update(ps.roleId, {
|
||||
lastUsedAt: new Date(),
|
||||
});
|
||||
|
||||
this.globalEventService.publishInternalEvent('userRoleAssigned', created);
|
||||
await this.roleService.assign(user.id, role.id, ps.expiresAt ? new Date(ps.expiresAt) : null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { RolesRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { RoleEntityService } from '@/core/entities/RoleEntityService.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -33,7 +32,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
const roles = await this.rolesRepository.find({
|
||||
order: { lastUsedAt: 'DESC' },
|
||||
});
|
||||
return await this.roleEntityService.packMany(roles, me, { detail: false });
|
||||
return await this.roleEntityService.packMany(roles, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -39,12 +39,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
private roleEntityService: RoleEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps) => {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const role = await this.rolesRepository.findOneBy({ id: ps.roleId });
|
||||
if (role == null) {
|
||||
throw new ApiError(meta.errors.noSuchRole);
|
||||
}
|
||||
return await this.roleEntityService.pack(role);
|
||||
return await this.roleEntityService.pack(role, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,8 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { RolesRepository, UsersRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -62,12 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
@Inject(DI.rolesRepository)
|
||||
private rolesRepository: RolesRepository,
|
||||
|
||||
@Inject(DI.roleAssignmentsRepository)
|
||||
private roleAssignmentsRepository: RoleAssignmentsRepository,
|
||||
|
||||
private globalEventService: GlobalEventService,
|
||||
private roleService: RoleService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const role = await this.rolesRepository.findOneBy({ id: ps.roleId });
|
||||
@@ -84,18 +77,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
throw new ApiError(meta.errors.noSuchUser);
|
||||
}
|
||||
|
||||
const roleAssignment = await this.roleAssignmentsRepository.findOneBy({ userId: user.id, roleId: role.id });
|
||||
if (roleAssignment == null) {
|
||||
throw new ApiError(meta.errors.notAssigned);
|
||||
}
|
||||
|
||||
await this.roleAssignmentsRepository.delete(roleAssignment.id);
|
||||
|
||||
this.rolesRepository.update(ps.roleId, {
|
||||
lastUsedAt: new Date(),
|
||||
});
|
||||
|
||||
this.globalEventService.publishInternalEvent('userRoleUnassigned', roleAssignment);
|
||||
await this.roleService.unassign(user.id, role.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,6 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { RolesRepository } from '@/models/index.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
|
||||
export const meta = {
|
||||
|
@@ -0,0 +1,78 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Brackets } from 'typeorm';
|
||||
import type { RoleAssignmentsRepository, RolesRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin', 'role', 'users'],
|
||||
|
||||
requireCredential: false,
|
||||
requireAdmin: true,
|
||||
|
||||
errors: {
|
||||
noSuchRole: {
|
||||
message: 'No such role.',
|
||||
code: 'NO_SUCH_ROLE',
|
||||
id: '224eff5e-2488-4b18-b3e7-f50d94421648',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
roleId: { type: 'string', format: 'misskey:id' },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
},
|
||||
required: ['roleId'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.rolesRepository)
|
||||
private rolesRepository: RolesRepository,
|
||||
|
||||
@Inject(DI.roleAssignmentsRepository)
|
||||
private roleAssignmentsRepository: RoleAssignmentsRepository,
|
||||
|
||||
private queryService: QueryService,
|
||||
private userEntityService: UserEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const role = await this.rolesRepository.findOneBy({
|
||||
id: ps.roleId,
|
||||
});
|
||||
|
||||
if (role == null) {
|
||||
throw new ApiError(meta.errors.noSuchRole);
|
||||
}
|
||||
|
||||
const query = this.queryService.makePaginationQuery(this.roleAssignmentsRepository.createQueryBuilder('assign'), ps.sinceId, ps.untilId)
|
||||
.andWhere('assign.roleId = :roleId', { roleId: role.id })
|
||||
.andWhere(new Brackets(qb => { qb
|
||||
.where('assign.expiresAt IS NULL')
|
||||
.orWhere('assign.expiresAt > :now', { now: new Date() });
|
||||
}))
|
||||
.innerJoinAndSelect('assign.user', 'user');
|
||||
|
||||
const assigns = await query
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await Promise.all(assigns.map(async assign => ({
|
||||
id: assign.id,
|
||||
createdAt: assign.createdAt,
|
||||
user: await this.userEntityService.pack(assign.user!, me, { detail: true }),
|
||||
expiresAt: assign.expiresAt,
|
||||
})));
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { EmailService } from '@/core/EmailService.js';
|
||||
|
||||
|
@@ -59,12 +59,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
throw new Error('cannot show info of admin');
|
||||
}
|
||||
|
||||
if (!await this.roleService.isAdministrator(_me)) {
|
||||
return {
|
||||
isSuspended: user.isSuspended,
|
||||
};
|
||||
}
|
||||
|
||||
const signins = await this.signinsRepository.findBy({ userId: user.id });
|
||||
|
||||
const roles = await this.roleService.getUserRoles(user.id);
|
||||
@@ -89,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
moderationNote: profile.moderationNote,
|
||||
signins,
|
||||
policies: await this.roleService.getUserPolicies(user.id),
|
||||
roles: await this.roleEntityService.packMany(roles, me, { detail: false }),
|
||||
roles: await this.roleEntityService.packMany(roles, me),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@@ -2,10 +2,8 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
import type { Meta } from '@/models/entities/Meta.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
|
||||
export const meta = {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { UserListsRepository, UserGroupJoiningsRepository, AntennasRepository } from '@/models/index.js';
|
||||
import type { UserListsRepository, AntennasRepository } from '@/models/index.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
@@ -22,12 +22,6 @@ export const meta = {
|
||||
id: '95063e93-a283-4b8b-9aa5-bcdb8df69a7f',
|
||||
},
|
||||
|
||||
noSuchUserGroup: {
|
||||
message: 'No such user group.',
|
||||
code: 'NO_SUCH_USER_GROUP',
|
||||
id: 'aa3c0b9a-8cae-47c0-92ac-202ce5906682',
|
||||
},
|
||||
|
||||
tooManyAntennas: {
|
||||
message: 'You cannot create antenna any more.',
|
||||
code: 'TOO_MANY_ANTENNAS',
|
||||
@@ -46,9 +40,8 @@ export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||
src: { type: 'string', enum: ['home', 'all', 'users', 'list', 'group'] },
|
||||
src: { type: 'string', enum: ['home', 'all', 'users', 'list'] },
|
||||
userListId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
userGroupId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
keywords: { type: 'array', items: {
|
||||
type: 'array', items: {
|
||||
type: 'string',
|
||||
@@ -80,9 +73,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
@Inject(DI.userListsRepository)
|
||||
private userListsRepository: UserListsRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
private antennaEntityService: AntennaEntityService,
|
||||
private roleService: RoleService,
|
||||
private idService: IdService,
|
||||
@@ -97,7 +87,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
|
||||
let userList;
|
||||
let userGroupJoining;
|
||||
|
||||
if (ps.src === 'list' && ps.userListId) {
|
||||
userList = await this.userListsRepository.findOneBy({
|
||||
@@ -108,15 +97,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
if (userList == null) {
|
||||
throw new ApiError(meta.errors.noSuchUserList);
|
||||
}
|
||||
} else if (ps.src === 'group' && ps.userGroupId) {
|
||||
userGroupJoining = await this.userGroupJoiningsRepository.findOneBy({
|
||||
userGroupId: ps.userGroupId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (userGroupJoining == null) {
|
||||
throw new ApiError(meta.errors.noSuchUserGroup);
|
||||
}
|
||||
}
|
||||
|
||||
const antenna = await this.antennasRepository.insert({
|
||||
@@ -126,7 +106,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
name: ps.name,
|
||||
src: ps.src,
|
||||
userListId: userList ? userList.id : null,
|
||||
userGroupJoiningId: userGroupJoining ? userGroupJoining.id : null,
|
||||
keywords: ps.keywords,
|
||||
excludeKeywords: ps.excludeKeywords,
|
||||
users: ps.users,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { AntennasRepository, UserListsRepository, UserGroupJoiningsRepository } from '@/models/index.js';
|
||||
import type { AntennasRepository, UserListsRepository } from '@/models/index.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
@@ -25,12 +25,6 @@ export const meta = {
|
||||
code: 'NO_SUCH_USER_LIST',
|
||||
id: '1c6b35c9-943e-48c2-81e4-2844989407f7',
|
||||
},
|
||||
|
||||
noSuchUserGroup: {
|
||||
message: 'No such user group.',
|
||||
code: 'NO_SUCH_USER_GROUP',
|
||||
id: '109ed789-b6eb-456e-b8a9-6059d567d385',
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
@@ -45,9 +39,8 @@ export const paramDef = {
|
||||
properties: {
|
||||
antennaId: { type: 'string', format: 'misskey:id' },
|
||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||
src: { type: 'string', enum: ['home', 'all', 'users', 'list', 'group'] },
|
||||
src: { type: 'string', enum: ['home', 'all', 'users', 'list'] },
|
||||
userListId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
userGroupId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
keywords: { type: 'array', items: {
|
||||
type: 'array', items: {
|
||||
type: 'string',
|
||||
@@ -78,9 +71,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
@Inject(DI.userListsRepository)
|
||||
private userListsRepository: UserListsRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
private antennaEntityService: AntennaEntityService,
|
||||
private globalEventService: GlobalEventService,
|
||||
@@ -97,7 +87,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
|
||||
let userList;
|
||||
let userGroupJoining;
|
||||
|
||||
if (ps.src === 'list' && ps.userListId) {
|
||||
userList = await this.userListsRepository.findOneBy({
|
||||
@@ -108,22 +97,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
if (userList == null) {
|
||||
throw new ApiError(meta.errors.noSuchUserList);
|
||||
}
|
||||
} else if (ps.src === 'group' && ps.userGroupId) {
|
||||
userGroupJoining = await this.userGroupJoiningsRepository.findOneBy({
|
||||
userGroupId: ps.userGroupId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (userGroupJoining == null) {
|
||||
throw new ApiError(meta.errors.noSuchUserGroup);
|
||||
}
|
||||
}
|
||||
|
||||
await this.antennasRepository.update(antenna.id, {
|
||||
name: ps.name,
|
||||
src: ps.src,
|
||||
userListId: userList ? userList.id : null,
|
||||
userGroupJoiningId: userGroupJoining ? userGroupJoining.id : null,
|
||||
keywords: ps.keywords,
|
||||
excludeKeywords: ps.excludeKeywords,
|
||||
users: ps.users,
|
||||
|
@@ -1,8 +1,7 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { ApResolverService } from '@/core/activitypub/ApResolverService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['federation'],
|
||||
|
@@ -3,7 +3,7 @@ import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UsersRepository, NotesRepository } from '@/models/index.js';
|
||||
import type { Note } from '@/models/entities/Note.js';
|
||||
import type { CacheableLocalUser, User } from '@/models/entities/User.js';
|
||||
import type { LocalUser, User } from '@/models/entities/User.js';
|
||||
import { isActor, isPost, getApId } from '@/core/activitypub/type.js';
|
||||
import type { SchemaType } from '@/misc/schema.js';
|
||||
import { ApResolverService } from '@/core/activitypub/ApResolverService.js';
|
||||
@@ -114,7 +114,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
* URIからUserかNoteを解決する
|
||||
*/
|
||||
@bindThis
|
||||
private async fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
|
||||
private async fetchAny(uri: string, me: LocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
|
||||
// ブロックしてたら中断
|
||||
const fetchedMeta = await this.metaService.fetch();
|
||||
if (this.utilityService.isBlockedHost(fetchedMeta.blockedHosts, this.utilityService.extractDbHost(uri))) return null;
|
||||
@@ -147,7 +147,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async mergePack(me: CacheableLocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise<SchemaType<typeof meta.res> | null> {
|
||||
private async mergePack(me: LocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise<SchemaType<typeof meta.res> | null> {
|
||||
if (user != null) {
|
||||
return {
|
||||
type: 'User',
|
||||
|
@@ -82,6 +82,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
|
||||
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
|
||||
.leftJoinAndSelect('note.channel', 'channel');
|
||||
|
||||
if (me) {
|
||||
this.queryService.generateMutedUserQuery(query, me);
|
||||
this.queryService.generateMutedNoteQuery(query, me);
|
||||
this.queryService.generateBlockedUserQuery(query, me);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
const timeline = await query.take(ps.limit).getMany();
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { getJsonSchema } from '@/core/chart/core.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { getJsonSchema } from '@/core/chart/core.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import ApRequestChart from '@/core/chart/charts/ap-request.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { getJsonSchema } from '@/core/chart/core.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import DriveChart from '@/core/chart/charts/drive.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { getJsonSchema } from '@/core/chart/core.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import FederationChart from '@/core/chart/charts/federation.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { getJsonSchema } from '@/core/chart/core.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import InstanceChart from '@/core/chart/charts/instance.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { getJsonSchema } from '@/core/chart/core.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import NotesChart from '@/core/chart/charts/notes.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { getJsonSchema } from '@/core/chart/core.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { getJsonSchema } from '@/core/chart/core.js';
|
||||
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { getJsonSchema } from '@/core/chart/core.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import PerUserNotesChart from '@/core/chart/charts/per-user-notes.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { getJsonSchema } from '@/core/chart/core.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import PerUserPvChart from '@/core/chart/charts/per-user-pv.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { getJsonSchema } from '@/core/chart/core.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import PerUserReactionsChart from '@/core/chart/charts/per-user-reactions.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { getJsonSchema } from '@/core/chart/core.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import UsersChart from '@/core/chart/charts/users.js';
|
||||
|
@@ -1,72 +1,72 @@
|
||||
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';
|
||||
|
||||
export const meta = {
|
||||
tags: ['account', 'notes', 'clips'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
errors: {
|
||||
noSuchClip: {
|
||||
message: 'No such clip.',
|
||||
code: 'NO_SUCH_CLIP',
|
||||
id: 'b80525c6-97f7-49d7-a42d-ebccd49cfd52',
|
||||
},
|
||||
|
||||
noSuchNote: {
|
||||
message: 'No such note.',
|
||||
code: 'NO_SUCH_NOTE',
|
||||
id: 'aff017de-190e-434b-893e-33a9ff5049d8',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
clipId: { type: 'string', format: 'misskey:id' },
|
||||
noteId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['clipId', 'noteId'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.clipsRepository)
|
||||
private clipsRepository: ClipsRepository,
|
||||
|
||||
@Inject(DI.clipNotesRepository)
|
||||
private clipNotesRepository: ClipNotesRepository,
|
||||
|
||||
private getterService: GetterService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const clip = await this.clipsRepository.findOneBy({
|
||||
id: ps.clipId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (clip == null) {
|
||||
throw new ApiError(meta.errors.noSuchClip);
|
||||
}
|
||||
|
||||
const note = await this.getterService.getNote(ps.noteId).catch(err => {
|
||||
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
|
||||
throw err;
|
||||
});
|
||||
|
||||
await this.clipNotesRepository.delete({
|
||||
noteId: note.id,
|
||||
clipId: clip.id,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
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';
|
||||
|
||||
export const meta = {
|
||||
tags: ['account', 'notes', 'clips'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
errors: {
|
||||
noSuchClip: {
|
||||
message: 'No such clip.',
|
||||
code: 'NO_SUCH_CLIP',
|
||||
id: 'b80525c6-97f7-49d7-a42d-ebccd49cfd52',
|
||||
},
|
||||
|
||||
noSuchNote: {
|
||||
message: 'No such note.',
|
||||
code: 'NO_SUCH_NOTE',
|
||||
id: 'aff017de-190e-434b-893e-33a9ff5049d8',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
clipId: { type: 'string', format: 'misskey:id' },
|
||||
noteId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['clipId', 'noteId'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.clipsRepository)
|
||||
private clipsRepository: ClipsRepository,
|
||||
|
||||
@Inject(DI.clipNotesRepository)
|
||||
private clipNotesRepository: ClipNotesRepository,
|
||||
|
||||
private getterService: GetterService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const clip = await this.clipsRepository.findOneBy({
|
||||
id: ps.clipId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (clip == null) {
|
||||
throw new ApiError(meta.errors.noSuchClip);
|
||||
}
|
||||
|
||||
const note = await this.getterService.getNote(ps.noteId).catch(err => {
|
||||
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
|
||||
throw err;
|
||||
});
|
||||
|
||||
await this.clipNotesRepository.delete({
|
||||
noteId: note.id,
|
||||
clipId: clip.id,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
|
@@ -39,19 +39,13 @@ export const meta = {
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
url: { type: 'string' },
|
||||
},
|
||||
anyOf: [
|
||||
{
|
||||
properties: {
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['fileId'],
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
url: { type: 'string' },
|
||||
},
|
||||
required: ['url'],
|
||||
},
|
||||
{ required: ['fileId'] },
|
||||
{ required: ['url'] },
|
||||
],
|
||||
} as const;
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/index.js';
|
||||
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import ms from 'ms';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { DriveFilesRepository } from '@/models/index.js';
|
||||
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { EmailService } from '@/core/EmailService.js';
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { IsNull, MoreThan } from 'typeorm';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { EmojisRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
@@ -82,11 +82,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
});
|
||||
|
||||
return {
|
||||
emojis: await this.emojiEntityService.packMany(emojis, {
|
||||
omitId: true,
|
||||
omitHost: true,
|
||||
withUrl: true,
|
||||
}),
|
||||
emojis: await this.emojiEntityService.packSimpleMany(emojis),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import endpoints from '../endpoints.js';
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import endpoints from '../endpoints.js';
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import ms from 'ms';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
|
||||
|
@@ -10,6 +10,8 @@ export const meta = {
|
||||
tags: ['federation'],
|
||||
|
||||
requireCredential: false,
|
||||
allowGet: true,
|
||||
cacheSec: 3600,
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
|
@@ -1,13 +1,10 @@
|
||||
import ms from 'ms';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { DriveFilesRepository, FlashsRepository, PagesRepository } from '@/models/index.js';
|
||||
import type { FlashsRepository } from '@/models/index.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { Page } from '@/models/entities/Page.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { PageEntityService } from '@/core/entities/PageEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['flash'],
|
||||
|
@@ -1,7 +1,5 @@
|
||||
import { IsNull } from 'typeorm';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UsersRepository, FlashsRepository } from '@/models/index.js';
|
||||
import type { Flash } from '@/models/entities/Flash.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import ms from 'ms';
|
||||
import { Not } from 'typeorm';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { FlashsRepository, DriveFilesRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { FollowingsRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { FollowingsRepository } from '@/models/index.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||
|
@@ -7,7 +7,6 @@ import type { DriveFile } from '@/models/entities/DriveFile.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['gallery'],
|
||||
|
@@ -2,11 +2,9 @@ import ms from 'ms';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { DriveFilesRepository, GalleryPostsRepository } from '@/models/index.js';
|
||||
import { GalleryPost } from '@/models/entities/GalleryPost.js';
|
||||
import type { DriveFile } from '@/models/entities/DriveFile.js';
|
||||
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['gallery'],
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import * as speakeasy from 'speakeasy';
|
||||
import * as OTPAuth from 'otpauth';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import type { UserProfilesRepository } from '@/models/index.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -22,8 +25,14 @@ export const paramDef = {
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private globalEventService: GlobalEventService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const token = ps.token.replace(/\s/g, '');
|
||||
@@ -34,13 +43,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
throw new Error('二段階認証の設定が開始されていません');
|
||||
}
|
||||
|
||||
const verified = (speakeasy as any).totp.verify({
|
||||
secret: profile.twoFactorTempSecret,
|
||||
encoding: 'base32',
|
||||
token: token,
|
||||
const delta = OTPAuth.TOTP.validate({
|
||||
secret: OTPAuth.Secret.fromBase32(profile.twoFactorTempSecret),
|
||||
digits: 6,
|
||||
token,
|
||||
window: 1,
|
||||
});
|
||||
|
||||
if (!verified) {
|
||||
if (delta === null) {
|
||||
throw new Error('not verified');
|
||||
}
|
||||
|
||||
@@ -48,6 +58,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
twoFactorSecret: profile.twoFactorTempSecret,
|
||||
twoFactorEnabled: true,
|
||||
});
|
||||
|
||||
// Publish meUpdated event
|
||||
this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
|
||||
detail: true,
|
||||
includeSecrets: true,
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -25,7 +25,7 @@ export const paramDef = {
|
||||
attestationObject: { type: 'string' },
|
||||
password: { type: 'string' },
|
||||
challengeId: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
name: { type: 'string', minLength: 1, maxLength: 30 },
|
||||
},
|
||||
required: ['clientDataJSON', 'attestationObject', 'password', 'challengeId', 'name'],
|
||||
} as const;
|
||||
|
@@ -1,12 +1,23 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UserProfilesRepository } from '@/models/index.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
||||
secure: true,
|
||||
|
||||
errors: {
|
||||
noKey: {
|
||||
message: 'No security key.',
|
||||
code: 'NO_SECURITY_KEY',
|
||||
id: 'f9c54d7f-d4c2-4d3c-9a8g-a70daac86512',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
@@ -23,11 +34,45 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
|
||||
@Inject(DI.userSecurityKeysRepository)
|
||||
private userSecurityKeysRepository: UserSecurityKeysRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private globalEventService: GlobalEventService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
if (ps.value === true) {
|
||||
// セキュリティキーがなければパスワードレスを有効にはできない
|
||||
const keyCount = await this.userSecurityKeysRepository.count({
|
||||
where: {
|
||||
userId: me.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
lastUsed: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (keyCount === 0) {
|
||||
await this.userProfilesRepository.update(me.id, {
|
||||
usePasswordLessLogin: false,
|
||||
});
|
||||
|
||||
throw new ApiError(meta.errors.noKey);
|
||||
}
|
||||
}
|
||||
|
||||
await this.userProfilesRepository.update(me.id, {
|
||||
usePasswordLessLogin: ps.value,
|
||||
});
|
||||
|
||||
// Publish meUpdated event
|
||||
this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
|
||||
detail: true,
|
||||
includeSecrets: true,
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import * as speakeasy from 'speakeasy';
|
||||
import * as OTPAuth from 'otpauth';
|
||||
import * as QRCode from 'qrcode';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserProfilesRepository } from '@/models/index.js';
|
||||
@@ -42,25 +42,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
|
||||
// Generate user's secret key
|
||||
const secret = speakeasy.generateSecret({
|
||||
length: 32,
|
||||
});
|
||||
const secret = new OTPAuth.Secret();
|
||||
|
||||
await this.userProfilesRepository.update(me.id, {
|
||||
twoFactorTempSecret: secret.base32,
|
||||
});
|
||||
|
||||
// Get the data URL of the authenticator URL
|
||||
const url = speakeasy.otpauthURL({
|
||||
secret: secret.base32,
|
||||
encoding: 'base32',
|
||||
const totp = new OTPAuth.TOTP({
|
||||
secret,
|
||||
digits: 6,
|
||||
label: me.username,
|
||||
issuer: this.config.host,
|
||||
});
|
||||
const dataUrl = await QRCode.toDataURL(url);
|
||||
const url = totp.toString();
|
||||
const qr = await QRCode.toDataURL(url);
|
||||
|
||||
return {
|
||||
qr: dataUrl,
|
||||
qr,
|
||||
url,
|
||||
secret: secret.base32,
|
||||
label: me.username,
|
||||
|
@@ -2,7 +2,6 @@ import bcrypt from 'bcryptjs';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js';
|
||||
import type { UsersRepository } from '@/models/index.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
@@ -51,6 +50,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
id: ps.credentialId,
|
||||
});
|
||||
|
||||
// 使われているキーがなくなったらパスワードレスログインをやめる
|
||||
const keyCount = await this.userSecurityKeysRepository.count({
|
||||
where: {
|
||||
userId: me.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
lastUsed: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (keyCount === 0) {
|
||||
await this.userProfilesRepository.update(me.id, {
|
||||
usePasswordLessLogin: false,
|
||||
});
|
||||
}
|
||||
|
||||
// Publish meUpdated event
|
||||
this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
|
||||
detail: true,
|
||||
|
@@ -1,7 +1,9 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import type { UserProfilesRepository } from '@/models/index.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -24,6 +26,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private globalEventService: GlobalEventService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
|
||||
@@ -38,7 +43,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
await this.userProfilesRepository.update(me.id, {
|
||||
twoFactorSecret: null,
|
||||
twoFactorEnabled: false,
|
||||
usePasswordLessLogin: false,
|
||||
});
|
||||
|
||||
// Publish meUpdated event
|
||||
this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
|
||||
detail: true,
|
||||
includeSecrets: true,
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,78 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
||||
secure: true,
|
||||
|
||||
errors: {
|
||||
noSuchKey: {
|
||||
message: 'No such key.',
|
||||
code: 'NO_SUCH_KEY',
|
||||
id: 'f9c5467f-d492-4d3c-9a8g-a70dacc86512',
|
||||
},
|
||||
|
||||
accessDenied: {
|
||||
message: 'You do not have edit privilege of the channel.',
|
||||
code: 'ACCESS_DENIED',
|
||||
id: '1fb7cb09-d46a-4fff-b8df-057708cce513',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', minLength: 1, maxLength: 30 },
|
||||
credentialId: { type: 'string' },
|
||||
},
|
||||
required: ['name', 'credentialId'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userSecurityKeysRepository)
|
||||
private userSecurityKeysRepository: UserSecurityKeysRepository,
|
||||
|
||||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private globalEventService: GlobalEventService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const key = await this.userSecurityKeysRepository.findOneBy({
|
||||
id: ps.credentialId,
|
||||
});
|
||||
|
||||
if (key == null) {
|
||||
throw new ApiError(meta.errors.noSuchKey);
|
||||
}
|
||||
|
||||
if (key.userId !== me.id) {
|
||||
throw new ApiError(meta.errors.accessDenied);
|
||||
}
|
||||
|
||||
await this.userSecurityKeysRepository.update(key.id, {
|
||||
name: ps.name,
|
||||
});
|
||||
|
||||
// Publish meUpdated event
|
||||
this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
|
||||
detail: true,
|
||||
includeSecrets: true,
|
||||
}));
|
||||
|
||||
return {};
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { AchievementService, ACHIEVEMENT_TYPES } from '@/core/AchievementService.js';
|
||||
|
||||
export const meta = {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user