mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-18 02:35:32 +02:00
Merge branch 'develop' into mahjong
This commit is contained in:
@@ -7,7 +7,6 @@ import { randomUUID } from 'node:crypto';
|
||||
import * as fs from 'node:fs';
|
||||
import * as stream from 'node:stream/promises';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { getIpHash } from '@/misc/get-ip-hash.js';
|
||||
import type { MiLocalUser, MiUser } from '@/models/User.js';
|
||||
@@ -37,6 +36,7 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||
private logger: Logger;
|
||||
private userIpHistories: Map<MiUser['id'], Set<string>>;
|
||||
private userIpHistoriesClearIntervalId: NodeJS.Timeout;
|
||||
private Sentry: typeof import('@sentry/node') | null = null;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.meta)
|
||||
@@ -59,6 +59,12 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||
this.userIpHistoriesClearIntervalId = setInterval(() => {
|
||||
this.userIpHistories.clear();
|
||||
}, 1000 * 60 * 60);
|
||||
|
||||
if (this.config.sentryForBackend) {
|
||||
import('@sentry/node').then((Sentry) => {
|
||||
this.Sentry = Sentry;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#sendApiError(reply: FastifyReply, err: ApiError): void {
|
||||
@@ -120,8 +126,8 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||
},
|
||||
});
|
||||
|
||||
if (this.config.sentryForBackend) {
|
||||
Sentry.captureMessage(`Internal error occurred in ${ep.name}: ${err.message}`, {
|
||||
if (this.Sentry != null) {
|
||||
this.Sentry.captureMessage(`Internal error occurred in ${ep.name}: ${err.message}`, {
|
||||
level: 'error',
|
||||
user: {
|
||||
id: userId,
|
||||
@@ -307,11 +313,14 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||
}
|
||||
|
||||
if (ep.meta.limit) {
|
||||
// koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
|
||||
let limitActor: string;
|
||||
let limitActor: string | null = null;
|
||||
if (user) {
|
||||
limitActor = user.id;
|
||||
} else {
|
||||
} else if (this.config.enableIpRateLimit) {
|
||||
if (process.env.NODE_ENV === 'production' && (request.ip === '::1' || request.ip === '127.0.0.1')) {
|
||||
this.logger.warn('Recieved API request from localhost IP address for rate limiting in production environment. This is likely due to an improper trustProxy setting in the config file.');
|
||||
}
|
||||
|
||||
limitActor = getIpHash(request.ip);
|
||||
}
|
||||
|
||||
@@ -324,21 +333,17 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||
// TODO: 毎リクエスト計算するのもあれだしキャッシュしたい
|
||||
const factor = user ? (await this.roleService.getUserPolicies(user.id)).rateLimitFactor : 1;
|
||||
|
||||
if (factor > 0) {
|
||||
if (limitActor != null && factor > 0) {
|
||||
// Rate limit
|
||||
await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor, factor).catch(err => {
|
||||
if ('info' in err) {
|
||||
// errはLimiter.LimiterInfoであることが期待される
|
||||
throw new ApiError({
|
||||
message: 'Rate limit exceeded. Please try again later.',
|
||||
code: 'RATE_LIMIT_EXCEEDED',
|
||||
id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
|
||||
httpStatusCode: 429,
|
||||
}, err.info);
|
||||
} else {
|
||||
throw new TypeError('information must be a rate-limiter information.');
|
||||
}
|
||||
});
|
||||
const rateLimit = await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor, factor);
|
||||
if (rateLimit != null) {
|
||||
throw new ApiError({
|
||||
message: 'Rate limit exceeded. Please try again later.',
|
||||
code: 'RATE_LIMIT_EXCEEDED',
|
||||
id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
|
||||
httpStatusCode: 429,
|
||||
}, rateLimit.info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +426,7 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||
if (['boolean', 'number', 'integer'].includes(param.type ?? '') && typeof data[k] === 'string') {
|
||||
try {
|
||||
data[k] = JSON.parse(data[k]);
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
throw new ApiError({
|
||||
message: 'Invalid param.',
|
||||
code: 'INVALID_PARAM',
|
||||
@@ -436,8 +441,8 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||
}
|
||||
|
||||
// API invoking
|
||||
if (this.config.sentryForBackend) {
|
||||
return await Sentry.startSpan({
|
||||
if (this.Sentry != null) {
|
||||
return await this.Sentry.startSpan({
|
||||
name: 'API: ' + ep.name,
|
||||
}, () => ep.exec(data, user, token, file, request.ip, request.headers)
|
||||
.catch((err: Error) => this.#onExecError(ep, data, err, user?.id)));
|
||||
|
||||
@@ -176,6 +176,17 @@ export class ApiServerService {
|
||||
}
|
||||
});
|
||||
|
||||
fastify.all('/clear-browser-cache', (request, reply) => {
|
||||
if (['GET', 'POST'].includes(request.method)) {
|
||||
reply.header('Clear-Site-Data', '"cache", "prefetchCache", "prerenderCache", "executionContexts"');
|
||||
reply.code(204);
|
||||
reply.send();
|
||||
} else {
|
||||
reply.code(405);
|
||||
reply.send();
|
||||
}
|
||||
});
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -40,8 +40,8 @@ export class GetterService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getNoteWithUser(noteId: MiNote['id']) {
|
||||
const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user'] });
|
||||
public async getNoteWithRelations(noteId: MiNote['id']) {
|
||||
const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user', 'reply', 'renote', 'reply.user', 'renote.user'] });
|
||||
|
||||
if (note == null) {
|
||||
throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.');
|
||||
|
||||
@@ -12,6 +12,14 @@ import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { IEndpointMeta } from './endpoints.js';
|
||||
|
||||
type RateLimitInfo = {
|
||||
code: 'BRIEF_REQUEST_INTERVAL',
|
||||
info: Limiter.LimiterInfo,
|
||||
} | {
|
||||
code: 'RATE_LIMIT_EXCEEDED',
|
||||
info: Limiter.LimiterInfo,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class RateLimiterService {
|
||||
private logger: Logger;
|
||||
@@ -31,77 +39,55 @@ export class RateLimiterService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public limit(limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string, factor = 1) {
|
||||
{
|
||||
if (this.disabled) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
private checkLimiter(options: Limiter.LimiterOption): Promise<Limiter.LimiterInfo> {
|
||||
return new Promise<Limiter.LimiterInfo>((resolve, reject) => {
|
||||
new Limiter(options).get((err, info) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(info);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Short-term limit
|
||||
const min = new Promise<void>((ok, reject) => {
|
||||
const minIntervalLimiter = new Limiter({
|
||||
id: `${actor}:${limitation.key}:min`,
|
||||
duration: limitation.minInterval! * factor,
|
||||
max: 1,
|
||||
db: this.redisClient,
|
||||
});
|
||||
@bindThis
|
||||
public async limit(limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string, factor = 1): Promise<RateLimitInfo | null> {
|
||||
if (this.disabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
minIntervalLimiter.get((err, info) => {
|
||||
if (err) {
|
||||
return reject({ code: 'ERR', info });
|
||||
}
|
||||
|
||||
this.logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`);
|
||||
|
||||
if (info.remaining === 0) {
|
||||
return reject({ code: 'BRIEF_REQUEST_INTERVAL', info });
|
||||
} else {
|
||||
if (hasLongTermLimit) {
|
||||
return max.then(ok, reject);
|
||||
} else {
|
||||
return ok();
|
||||
}
|
||||
}
|
||||
});
|
||||
// Short-term limit
|
||||
if (limitation.minInterval != null) {
|
||||
const info = await this.checkLimiter({
|
||||
id: `${actor}:${limitation.key}:min`,
|
||||
duration: limitation.minInterval * factor,
|
||||
max: 1,
|
||||
db: this.redisClient,
|
||||
});
|
||||
|
||||
// Long term limit
|
||||
const max = new Promise<void>((ok, reject) => {
|
||||
const limiter = new Limiter({
|
||||
id: `${actor}:${limitation.key}`,
|
||||
duration: limitation.duration! * factor,
|
||||
max: limitation.max! / factor,
|
||||
db: this.redisClient,
|
||||
});
|
||||
this.logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`);
|
||||
|
||||
limiter.get((err, info) => {
|
||||
if (err) {
|
||||
return reject({ code: 'ERR', info });
|
||||
}
|
||||
|
||||
this.logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`);
|
||||
|
||||
if (info.remaining === 0) {
|
||||
return reject({ code: 'RATE_LIMIT_EXCEEDED', info });
|
||||
} else {
|
||||
return ok();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const hasShortTermLimit = typeof limitation.minInterval === 'number';
|
||||
|
||||
const hasLongTermLimit =
|
||||
typeof limitation.duration === 'number' &&
|
||||
typeof limitation.max === 'number';
|
||||
|
||||
if (hasShortTermLimit) {
|
||||
return min;
|
||||
} else if (hasLongTermLimit) {
|
||||
return max;
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
if (info.remaining === 0) {
|
||||
return { code: 'BRIEF_REQUEST_INTERVAL', info };
|
||||
}
|
||||
}
|
||||
|
||||
// Long term limit
|
||||
if (limitation.duration != null && limitation.max != null) {
|
||||
const info = await this.checkLimiter({
|
||||
id: `${actor}:${limitation.key}`,
|
||||
duration: limitation.duration,
|
||||
max: limitation.max / factor,
|
||||
db: this.redisClient,
|
||||
});
|
||||
|
||||
this.logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`);
|
||||
|
||||
if (info.remaining === 0) {
|
||||
return { code: 'RATE_LIMIT_EXCEEDED', info };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
UserSecurityKeysRepository,
|
||||
UsersRepository,
|
||||
} from '@/models/_.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { getIpHash } from '@/misc/get-ip-hash.js';
|
||||
import type { MiLocalUser } from '@/models/User.js';
|
||||
@@ -23,6 +24,7 @@ import { bindThis } from '@/decorators.js';
|
||||
import { WebAuthnService } from '@/core/WebAuthnService.js';
|
||||
import { UserAuthService } from '@/core/UserAuthService.js';
|
||||
import { CaptchaService } from '@/core/CaptchaService.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
|
||||
import { RateLimiterService } from './RateLimiterService.js';
|
||||
import { SigninService } from './SigninService.js';
|
||||
@@ -31,6 +33,8 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
|
||||
@Injectable()
|
||||
export class SigninApiService {
|
||||
private logger: Logger;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
@@ -50,6 +54,7 @@ export class SigninApiService {
|
||||
@Inject(DI.signinsRepository)
|
||||
private signinsRepository: SigninsRepository,
|
||||
|
||||
private loggerService: LoggerService,
|
||||
private idService: IdService,
|
||||
private rateLimiterService: RateLimiterService,
|
||||
private signinService: SigninService,
|
||||
@@ -57,6 +62,7 @@ export class SigninApiService {
|
||||
private webAuthnService: WebAuthnService,
|
||||
private captchaService: CaptchaService,
|
||||
) {
|
||||
this.logger = this.loggerService.getLogger('Signin');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -89,18 +95,22 @@ export class SigninApiService {
|
||||
return { error };
|
||||
}
|
||||
|
||||
try {
|
||||
// not more than 1 attempt per second and not more than 10 attempts per hour
|
||||
await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip));
|
||||
} catch (err) {
|
||||
reply.code(429);
|
||||
return {
|
||||
error: {
|
||||
message: 'Too many failed attempts to sign in. Try again later.',
|
||||
code: 'TOO_MANY_AUTHENTICATION_FAILURES',
|
||||
id: '22d05606-fbcf-421a-a2db-b32610dcfd1b',
|
||||
},
|
||||
};
|
||||
if (this.config.enableIpRateLimit) {
|
||||
if (process.env.NODE_ENV === 'production' && (request.ip === '::1' || request.ip === '127.0.0.1')) {
|
||||
this.logger.warn('Recieved signin request from localhost IP address for rate limiting in production environment. This is likely due to an improper trustProxy setting in the config file.');
|
||||
}
|
||||
const rateLimit = await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip));
|
||||
if (rateLimit != null) {
|
||||
reply.code(429);
|
||||
return {
|
||||
error: {
|
||||
message: 'Too many failed attempts to sign in. Try again later.',
|
||||
code: 'TOO_MANY_AUTHENTICATION_FAILURES',
|
||||
id: '22d05606-fbcf-421a-a2db-b32610dcfd1b',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof username !== 'string') {
|
||||
@@ -221,7 +231,7 @@ export class SigninApiService {
|
||||
|
||||
try {
|
||||
await this.userAuthService.twoFactorAuthenticate(profile, token);
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
return await fail(403, {
|
||||
id: 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f',
|
||||
});
|
||||
|
||||
@@ -84,19 +84,25 @@ export class SigninWithPasskeyApiService {
|
||||
return error(status ?? 500, failure ?? { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' });
|
||||
};
|
||||
|
||||
try {
|
||||
if (this.config.enableIpRateLimit) {
|
||||
if (process.env.NODE_ENV === 'production' && (request.ip === '::1' || request.ip === '127.0.0.1')) {
|
||||
this.logger.warn('Recieved signin with passkey request from localhost IP address for rate limiting in production environment. This is likely due to an improper trustProxy setting in the config file.');
|
||||
}
|
||||
|
||||
try {
|
||||
// Not more than 1 API call per 250ms and not more than 100 attempts per 30min
|
||||
// NOTE: 1 Sign-in require 2 API calls
|
||||
await this.rateLimiterService.limit({ key: 'signin-with-passkey', duration: 60 * 30 * 1000, max: 200, minInterval: 250 }, getIpHash(request.ip));
|
||||
} catch (err) {
|
||||
reply.code(429);
|
||||
return {
|
||||
error: {
|
||||
message: 'Too many failed attempts to sign in. Try again later.',
|
||||
code: 'TOO_MANY_AUTHENTICATION_FAILURES',
|
||||
id: '22d05606-fbcf-421a-a2db-b32610dcfd1b',
|
||||
},
|
||||
};
|
||||
await this.rateLimiterService.limit({ key: 'signin-with-passkey', duration: 60 * 30 * 1000, max: 200, minInterval: 250 }, getIpHash(request.ip));
|
||||
} catch (_) {
|
||||
reply.code(429);
|
||||
return {
|
||||
error: {
|
||||
message: 'Too many failed attempts to sign in. Try again later.',
|
||||
code: 'TOO_MANY_AUTHENTICATION_FAILURES',
|
||||
id: '22d05606-fbcf-421a-a2db-b32610dcfd1b',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Initiate Passkey Auth challenge with context
|
||||
|
||||
@@ -129,7 +129,8 @@ export class SignupApiService {
|
||||
|
||||
let ticket: MiRegistrationTicket | null = null;
|
||||
|
||||
if (this.meta.disableRegistration) {
|
||||
// テスト時はこの機構は障害となるため無効にする
|
||||
if (process.env.NODE_ENV !== 'test' && this.meta.disableRegistration) {
|
||||
if (invitationCode == null || typeof invitationCode !== 'string') {
|
||||
reply.code(400);
|
||||
return;
|
||||
@@ -254,7 +255,7 @@ export class SignupApiService {
|
||||
throw new FastifyReplyError(400, 'EXPIRED');
|
||||
}
|
||||
|
||||
const { account, secret } = await this.signupService.signup({
|
||||
const { account } = await this.signupService.signup({
|
||||
username: pendingUser.username,
|
||||
passwordHash: pendingUser.password,
|
||||
});
|
||||
|
||||
@@ -8,17 +8,14 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import * as WebSocket from 'ws';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { UsersRepository, MiAccessToken } from '@/models/_.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import type { MiAccessToken } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { MiLocalUser } from '@/models/User.js';
|
||||
import { UserService } from '@/core/UserService.js';
|
||||
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
|
||||
import { AuthenticateService, AuthenticationError } from './AuthenticateService.js';
|
||||
import MainStreamConnection from './stream/Connection.js';
|
||||
import { ChannelsService } from './stream/ChannelsService.js';
|
||||
import MainStreamConnection, { ConnectionRequest } from './stream/Connection.js';
|
||||
import type * as http from 'node:http';
|
||||
import { ContextIdFactory, ModuleRef } from '@nestjs/core';
|
||||
|
||||
@Injectable()
|
||||
export class StreamingApiServerService {
|
||||
@@ -30,15 +27,9 @@ export class StreamingApiServerService {
|
||||
@Inject(DI.redisForSub)
|
||||
private redisForSub: Redis.Redis,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
private cacheService: CacheService,
|
||||
private moduleRef: ModuleRef,
|
||||
private authenticateService: AuthenticateService,
|
||||
private channelsService: ChannelsService,
|
||||
private notificationService: NotificationService,
|
||||
private usersService: UserService,
|
||||
private channelFollowingService: ChannelFollowingService,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -92,13 +83,12 @@ export class StreamingApiServerService {
|
||||
return;
|
||||
}
|
||||
|
||||
const stream = new MainStreamConnection(
|
||||
this.channelsService,
|
||||
this.notificationService,
|
||||
this.cacheService,
|
||||
this.channelFollowingService,
|
||||
user, app,
|
||||
);
|
||||
const contextId = ContextIdFactory.create();
|
||||
this.moduleRef.registerRequestByContextId<ConnectionRequest>({
|
||||
user,
|
||||
token: app,
|
||||
}, contextId);
|
||||
const stream = await this.moduleRef.create(MainStreamConnection, contextId);
|
||||
|
||||
await stream.init();
|
||||
|
||||
@@ -121,7 +111,7 @@ export class StreamingApiServerService {
|
||||
user: MiLocalUser | null;
|
||||
app: MiAccessToken | null
|
||||
}) => {
|
||||
const { stream, user, app } = ctx;
|
||||
const { stream, user } = ctx;
|
||||
|
||||
const ev = new EventEmitter();
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ export * as 'admin/queue/inbox-delayed' from './endpoints/admin/queue/inbox-dela
|
||||
export * as 'admin/queue/retry-job' from './endpoints/admin/queue/retry-job.js';
|
||||
export * as 'admin/queue/remove-job' from './endpoints/admin/queue/remove-job.js';
|
||||
export * as 'admin/queue/show-job' from './endpoints/admin/queue/show-job.js';
|
||||
export * as 'admin/queue/show-job-logs' from './endpoints/admin/queue/show-job-logs.js';
|
||||
export * as 'admin/queue/promote-jobs' from './endpoints/admin/queue/promote-jobs.js';
|
||||
export * as 'admin/queue/jobs' from './endpoints/admin/queue/jobs.js';
|
||||
export * as 'admin/queue/stats' from './endpoints/admin/queue/stats.js';
|
||||
@@ -142,6 +143,9 @@ export * as 'channels/timeline' from './endpoints/channels/timeline.js';
|
||||
export * as 'channels/unfavorite' from './endpoints/channels/unfavorite.js';
|
||||
export * as 'channels/unfollow' from './endpoints/channels/unfollow.js';
|
||||
export * as 'channels/update' from './endpoints/channels/update.js';
|
||||
export * as 'channels/mute/create' from './endpoints/channels/mute/create.js';
|
||||
export * as 'channels/mute/delete' from './endpoints/channels/mute/delete.js';
|
||||
export * as 'channels/mute/list' from './endpoints/channels/mute/list.js';
|
||||
export * as 'charts/active-users' from './endpoints/charts/active-users.js';
|
||||
export * as 'charts/ap-request' from './endpoints/charts/ap-request.js';
|
||||
export * as 'charts/drive' from './endpoints/charts/drive.js';
|
||||
@@ -168,6 +172,7 @@ export * as 'clips/update' from './endpoints/clips/update.js';
|
||||
export * as 'drive' from './endpoints/drive.js';
|
||||
export * as 'drive/files' from './endpoints/drive/files.js';
|
||||
export * as 'drive/files/attached-notes' from './endpoints/drive/files/attached-notes.js';
|
||||
export * as 'drive/files/attached-chat-messages' from './endpoints/drive/files/attached-chat-messages.js';
|
||||
export * as 'drive/files/check-existence' from './endpoints/drive/files/check-existence.js';
|
||||
export * as 'drive/files/create' from './endpoints/drive/files/create.js';
|
||||
export * as 'drive/files/delete' from './endpoints/drive/files/delete.js';
|
||||
@@ -175,6 +180,7 @@ export * as 'drive/files/find' from './endpoints/drive/files/find.js';
|
||||
export * as 'drive/files/find-by-hash' from './endpoints/drive/files/find-by-hash.js';
|
||||
export * as 'drive/files/show' from './endpoints/drive/files/show.js';
|
||||
export * as 'drive/files/update' from './endpoints/drive/files/update.js';
|
||||
export * as 'drive/files/move-bulk' from './endpoints/drive/files/move-bulk.js';
|
||||
export * as 'drive/files/upload-from-url' from './endpoints/drive/files/upload-from-url.js';
|
||||
export * as 'drive/folders' from './endpoints/drive/folders.js';
|
||||
export * as 'drive/folders/create' from './endpoints/drive/folders/create.js';
|
||||
@@ -207,6 +213,7 @@ export * as 'flash/my-likes' from './endpoints/flash/my-likes.js';
|
||||
export * as 'flash/show' from './endpoints/flash/show.js';
|
||||
export * as 'flash/unlike' from './endpoints/flash/unlike.js';
|
||||
export * as 'flash/update' from './endpoints/flash/update.js';
|
||||
export * as 'flash/search' from './endpoints/flash/search.js';
|
||||
export * as 'following/create' from './endpoints/following/create.js';
|
||||
export * as 'following/delete' from './endpoints/following/delete.js';
|
||||
export * as 'following/invalidate' from './endpoints/following/invalidate.js';
|
||||
@@ -312,6 +319,11 @@ export * as 'notes/clips' from './endpoints/notes/clips.js';
|
||||
export * as 'notes/conversation' from './endpoints/notes/conversation.js';
|
||||
export * as 'notes/create' from './endpoints/notes/create.js';
|
||||
export * as 'notes/delete' from './endpoints/notes/delete.js';
|
||||
export * as 'notes/drafts/list' from './endpoints/notes/drafts/list.js';
|
||||
export * as 'notes/drafts/create' from './endpoints/notes/drafts/create.js';
|
||||
export * as 'notes/drafts/delete' from './endpoints/notes/drafts/delete.js';
|
||||
export * as 'notes/drafts/update' from './endpoints/notes/drafts/update.js';
|
||||
export * as 'notes/drafts/count' from './endpoints/notes/drafts/count.js';
|
||||
export * as 'notes/favorites/create' from './endpoints/notes/favorites/create.js';
|
||||
export * as 'notes/favorites/delete' from './endpoints/notes/favorites/delete.js';
|
||||
export * as 'notes/featured' from './endpoints/notes/featured.js';
|
||||
@@ -329,6 +341,7 @@ export * as 'notes/replies' from './endpoints/notes/replies.js';
|
||||
export * as 'notes/search' from './endpoints/notes/search.js';
|
||||
export * as 'notes/search-by-tag' from './endpoints/notes/search-by-tag.js';
|
||||
export * as 'notes/show' from './endpoints/notes/show.js';
|
||||
export * as 'notes/show-partial-bulk' from './endpoints/notes/show-partial-bulk.js';
|
||||
export * as 'notes/state' from './endpoints/notes/state.js';
|
||||
export * as 'notes/thread-muting/create' from './endpoints/notes/thread-muting/create.js';
|
||||
export * as 'notes/thread-muting/delete' from './endpoints/notes/thread-muting/delete.js';
|
||||
@@ -384,6 +397,7 @@ export * as 'users/featured-notes' from './endpoints/users/featured-notes.js';
|
||||
export * as 'users/flashs' from './endpoints/users/flashs.js';
|
||||
export * as 'users/followers' from './endpoints/users/followers.js';
|
||||
export * as 'users/following' from './endpoints/users/following.js';
|
||||
export * as 'users/get-following-birthday-users' from './endpoints/users/get-following-birthday-users.js';
|
||||
export * as 'users/gallery/posts' from './endpoints/users/gallery/posts.js';
|
||||
export * as 'users/get-frequently-replied-users' from './endpoints/users/get-frequently-replied-users.js';
|
||||
export * as 'users/lists/create' from './endpoints/users/lists/create.js';
|
||||
@@ -408,6 +422,7 @@ export * as 'users/search' from './endpoints/users/search.js';
|
||||
export * as 'users/search-by-username-and-host' from './endpoints/users/search-by-username-and-host.js';
|
||||
export * as 'users/show' from './endpoints/users/show.js';
|
||||
export * as 'users/update-memo' from './endpoints/users/update-memo.js';
|
||||
export * as 'verify-email' from './endpoints/verify-email.js';
|
||||
export * as 'chat/messages/create-to-user' from './endpoints/chat/messages/create-to-user.js';
|
||||
export * as 'chat/messages/create-to-room' from './endpoints/chat/messages/create-to-room.js';
|
||||
export * as 'chat/messages/delete' from './endpoints/chat/messages/delete.js';
|
||||
@@ -432,4 +447,5 @@ export * as 'chat/rooms/invitations/ignore' from './endpoints/chat/rooms/invitat
|
||||
export * as 'chat/rooms/invitations/inbox' from './endpoints/chat/rooms/invitations/inbox.js';
|
||||
export * as 'chat/rooms/invitations/outbox' from './endpoints/chat/rooms/invitations/outbox.js';
|
||||
export * as 'chat/history' from './endpoints/chat/history.js';
|
||||
export * as 'chat/read-all' from './endpoints/chat/read-all.js';
|
||||
export * as 'v2/admin/emoji/list' from './endpoints/v2/admin/emoji/list.js';
|
||||
|
||||
@@ -98,6 +98,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
state: { type: 'string', nullable: true, default: null },
|
||||
reporterOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' },
|
||||
targetUserOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' },
|
||||
@@ -115,7 +117,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.abuseUserReportsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId);
|
||||
const query = this.queryService.makePaginationQuery(this.abuseUserReportsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate);
|
||||
|
||||
switch (ps.state) {
|
||||
case 'resolved': query.andWhere('report.resolved = TRUE'); break;
|
||||
|
||||
@@ -34,13 +34,22 @@ export const meta = {
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'MeDetailed',
|
||||
properties: {
|
||||
token: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
allOf: [
|
||||
{
|
||||
type: 'object',
|
||||
ref: 'MeDetailed',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
token: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ export const paramDef = {
|
||||
startsAt: { type: 'integer' },
|
||||
imageUrl: { type: 'string', minLength: 1 },
|
||||
dayOfWeek: { type: 'integer' },
|
||||
isSensitive: { type: 'boolean' },
|
||||
},
|
||||
required: ['url', 'memo', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'imageUrl', 'dayOfWeek'],
|
||||
} as const;
|
||||
@@ -55,6 +56,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
expiresAt: new Date(ps.expiresAt),
|
||||
startsAt: new Date(ps.startsAt),
|
||||
dayOfWeek: ps.dayOfWeek,
|
||||
isSensitive: ps.isSensitive,
|
||||
url: ps.url,
|
||||
imageUrl: ps.imageUrl,
|
||||
priority: ps.priority,
|
||||
@@ -73,6 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
expiresAt: ad.expiresAt.toISOString(),
|
||||
startsAt: ad.startsAt.toISOString(),
|
||||
dayOfWeek: ad.dayOfWeek,
|
||||
isSensitive: ad.isSensitive,
|
||||
url: ad.url,
|
||||
imageUrl: ad.imageUrl,
|
||||
priority: ad.priority,
|
||||
|
||||
@@ -34,6 +34,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
publishing: { type: 'boolean', default: null, nullable: true },
|
||||
},
|
||||
required: [],
|
||||
@@ -48,7 +50,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.adsRepository.createQueryBuilder('ad'), ps.sinceId, ps.untilId);
|
||||
const query = this.queryService.makePaginationQuery(this.adsRepository.createQueryBuilder('ad'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate);
|
||||
if (ps.publishing === true) {
|
||||
query.andWhere('ad.expiresAt > :now', { now: new Date() }).andWhere('ad.startsAt <= :now', { now: new Date() });
|
||||
} else if (ps.publishing === false) {
|
||||
@@ -61,6 +63,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
expiresAt: ad.expiresAt.toISOString(),
|
||||
startsAt: ad.startsAt.toISOString(),
|
||||
dayOfWeek: ad.dayOfWeek,
|
||||
isSensitive: ad.isSensitive,
|
||||
url: ad.url,
|
||||
imageUrl: ad.imageUrl,
|
||||
memo: ad.memo,
|
||||
|
||||
@@ -39,6 +39,7 @@ export const paramDef = {
|
||||
expiresAt: { type: 'integer' },
|
||||
startsAt: { type: 'integer' },
|
||||
dayOfWeek: { type: 'integer' },
|
||||
isSensitive: { type: 'boolean' },
|
||||
},
|
||||
required: ['id'],
|
||||
} as const;
|
||||
@@ -66,6 +67,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : undefined,
|
||||
startsAt: ps.startsAt ? new Date(ps.startsAt) : undefined,
|
||||
dayOfWeek: ps.dayOfWeek,
|
||||
isSensitive: ps.isSensitive,
|
||||
});
|
||||
|
||||
const updatedAd = await this.adsRepository.findOneByOrFail({ id: ad.id });
|
||||
|
||||
@@ -72,7 +72,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private announcementService: AnnouncementService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const { raw, packed } = await this.announcementService.create({
|
||||
const { packed } = await this.announcementService.create({
|
||||
updatedAt: null,
|
||||
title: ps.title,
|
||||
text: ps.text,
|
||||
|
||||
@@ -49,6 +49,36 @@ export const meta = {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
icon: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['info', 'warning', 'error', 'success'],
|
||||
},
|
||||
display: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['normal', 'banner', 'dialog'],
|
||||
},
|
||||
isActive: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
forExistingUsers: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
silence: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
needConfirmationToRead: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
imageUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
@@ -68,6 +98,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
userId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
status: { type: 'string', enum: ['all', 'active', 'archived'], default: 'active' },
|
||||
},
|
||||
@@ -87,7 +119,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId);
|
||||
const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate);
|
||||
|
||||
if (ps.status === 'archived') {
|
||||
query.andWhere('announcement.isActive = false');
|
||||
|
||||
@@ -71,6 +71,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
userId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
},
|
||||
required: [],
|
||||
|
||||
@@ -34,6 +34,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
userId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
type: { type: 'string', nullable: true, pattern: /^[a-zA-Z0-9\/\-*]+$/.toString().slice(1, -1) },
|
||||
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' },
|
||||
@@ -57,7 +59,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId);
|
||||
const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate);
|
||||
|
||||
if (ps.userId) {
|
||||
query.andWhere('file.userId = :userId', { userId: ps.userId });
|
||||
|
||||
@@ -157,19 +157,42 @@ export const meta = {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
maybeSensitive: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
maybePorn: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
requestIp: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
requestHeaders: {
|
||||
type: 'object',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
url: { type: 'string' },
|
||||
},
|
||||
anyOf: [
|
||||
{ required: ['fileId'] },
|
||||
{ required: ['url'] },
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['fileId'],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: 'string' },
|
||||
},
|
||||
required: ['url'],
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
||||
@@ -186,15 +209,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const file = ps.fileId ? await this.driveFilesRepository.findOneBy({ id: ps.fileId }) : await this.driveFilesRepository.findOne({
|
||||
where: [{
|
||||
url: ps.url,
|
||||
}, {
|
||||
thumbnailUrl: ps.url,
|
||||
}, {
|
||||
webpublicUrl: ps.url,
|
||||
}],
|
||||
});
|
||||
const file = await this.driveFilesRepository.findOneBy(
|
||||
'fileId' in ps
|
||||
? { id: ps.fileId }
|
||||
: [{ url: ps.url }, { thumbnailUrl: ps.url }, { webpublicUrl: ps.url }],
|
||||
);
|
||||
|
||||
if (file == null) {
|
||||
throw new ApiError(meta.errors.noSuchFile);
|
||||
|
||||
@@ -76,7 +76,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
try {
|
||||
// Create file
|
||||
driveFile = await this.driveService.uploadFromUrl({ url: emoji.originalUrl, user: null, force: true });
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
// TODO: need to return Drive Error
|
||||
throw new ApiError();
|
||||
}
|
||||
|
||||
@@ -24,39 +24,7 @@ export const meta = {
|
||||
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,
|
||||
},
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
category: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
host: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
ref: 'EmojiDetailed',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -74,6 +42,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
@@ -89,7 +59,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private emojiEntityService: EmojiEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId);
|
||||
const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate);
|
||||
|
||||
if (ps.host == null) {
|
||||
q.andWhere('emoji.host IS NOT NULL');
|
||||
|
||||
@@ -24,39 +24,7 @@ export const meta = {
|
||||
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,
|
||||
},
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
category: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
host: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
description: 'The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files.',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
ref: 'EmojiDetailed',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -68,6 +36,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
@@ -82,7 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId)
|
||||
const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('emoji.host IS NULL');
|
||||
|
||||
let emojis: MiEmoji[];
|
||||
|
||||
@@ -37,29 +37,45 @@ export const meta = {
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', format: 'misskey:id' },
|
||||
name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
category: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'Use `null` to reset the category.',
|
||||
allOf: [
|
||||
{
|
||||
anyOf: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
category: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'Use `null` to reset the category.',
|
||||
},
|
||||
aliases: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
license: { type: 'string', nullable: true },
|
||||
isSensitive: { type: 'boolean' },
|
||||
localOnly: { type: 'boolean' },
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
},
|
||||
},
|
||||
aliases: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
license: { type: 'string', nullable: true },
|
||||
isSensitive: { type: 'boolean' },
|
||||
localOnly: { type: 'boolean' },
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
},
|
||||
anyOf: [
|
||||
{ required: ['id'] },
|
||||
{ required: ['name'] },
|
||||
],
|
||||
} as const;
|
||||
|
||||
@@ -78,10 +94,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
}
|
||||
|
||||
// JSON schemeのanyOfの型変換がうまくいっていないらしい
|
||||
const required = { id: ps.id, name: ps.name } as
|
||||
| { id: MiEmoji['id']; name?: string }
|
||||
| { id?: MiEmoji['id']; name: string };
|
||||
const required = 'id' in ps
|
||||
? { id: ps.id, name: 'name' in ps ? ps.name as string : undefined }
|
||||
: { name: ps.name };
|
||||
|
||||
const error = await this.customEmojiService.update({
|
||||
...required,
|
||||
@@ -102,7 +117,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
case 'SAME_NAME_EMOJI_EXISTS': throw new ApiError(meta.errors.sameNameEmojiExists);
|
||||
}
|
||||
// 網羅性チェック
|
||||
const mustBeNever: never = error;
|
||||
const _mustBeNever: never = error;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
for (let i = 0; i < ps.count; i++) {
|
||||
ticketsPromises.push(this.registrationTicketsRepository.insertOne({
|
||||
id: this.idService.gen(),
|
||||
createdBy: me,
|
||||
createdById: me.id,
|
||||
expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null,
|
||||
code: generateInviteCode(),
|
||||
}));
|
||||
|
||||
@@ -223,10 +223,12 @@ export const meta = {
|
||||
sensitiveMediaDetection: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['none', 'all', 'local', 'remote'],
|
||||
},
|
||||
sensitiveMediaDetectionSensitivity: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'],
|
||||
},
|
||||
setSensitiveFlagAutomatically: {
|
||||
type: 'boolean',
|
||||
@@ -425,6 +427,9 @@ export const meta = {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
clientOptions: {
|
||||
ref: 'MetaClientOptions',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
@@ -469,6 +474,10 @@ export const meta = {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
feedbackUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
summalyProxy: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
@@ -495,6 +504,10 @@ export const meta = {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
urlPreviewAllowRedirect: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
urlPreviewTimeout: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
@@ -528,6 +541,61 @@ export const meta = {
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
deliverSuspendedSoftware: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
software: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
versionRange: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
singleUserMode: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
ugcVisibilityForVisitor: {
|
||||
type: 'string',
|
||||
enum: ['all', 'local', 'none'],
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
proxyRemoteFiles: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
signToActivityPubGet: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
allowExternalApRedirect: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
enableRemoteNotesCleaning: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
remoteNotesCleaningExpiryDaysForEachNotes: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
remoteNotesCleaningMaxProcessingDurationInMinutes: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
showRoleBadgesOfRemoteUsers: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -595,6 +663,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
logoImageUrl: instance.logoImageUrl,
|
||||
defaultLightTheme: instance.defaultLightTheme,
|
||||
defaultDarkTheme: instance.defaultDarkTheme,
|
||||
clientOptions: instance.clientOptions,
|
||||
enableEmail: instance.enableEmail,
|
||||
enableServiceWorker: instance.enableServiceWorker,
|
||||
translatorAvailable: instance.deeplAuthKey != null,
|
||||
@@ -665,6 +734,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
notesPerOneAd: instance.notesPerOneAd,
|
||||
summalyProxy: instance.urlPreviewSummaryProxyUrl,
|
||||
urlPreviewEnabled: instance.urlPreviewEnabled,
|
||||
urlPreviewAllowRedirect: instance.urlPreviewAllowRedirect,
|
||||
urlPreviewTimeout: instance.urlPreviewTimeout,
|
||||
urlPreviewMaximumContentLength: instance.urlPreviewMaximumContentLength,
|
||||
urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength,
|
||||
@@ -672,6 +742,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl,
|
||||
federation: instance.federation,
|
||||
federationHosts: instance.federationHosts,
|
||||
deliverSuspendedSoftware: instance.deliverSuspendedSoftware,
|
||||
singleUserMode: instance.singleUserMode,
|
||||
ugcVisibilityForVisitor: instance.ugcVisibilityForVisitor,
|
||||
proxyRemoteFiles: instance.proxyRemoteFiles,
|
||||
signToActivityPubGet: instance.signToActivityPubGet,
|
||||
allowExternalApRedirect: instance.allowExternalApRedirect,
|
||||
enableRemoteNotesCleaning: instance.enableRemoteNotesCleaning,
|
||||
remoteNotesCleaningExpiryDaysForEachNotes: instance.remoteNotesCleaningExpiryDaysForEachNotes,
|
||||
remoteNotesCleaningMaxProcessingDurationInMinutes: instance.remoteNotesCleaningMaxProcessingDurationInMinutes,
|
||||
showRoleBadgesOfRemoteUsers: instance.showRoleBadgesOfRemoteUsers,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -52,18 +52,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const jobs = await this.deliverQueue.getJobs(['delayed']);
|
||||
|
||||
const res = [] as [string, number][];
|
||||
const counts = new Map<string, number>();
|
||||
|
||||
for (const job of jobs) {
|
||||
const host = new URL(job.data.to).host;
|
||||
if (res.find(x => x[0] === host)) {
|
||||
res.find(x => x[0] === host)![1]++;
|
||||
} else {
|
||||
res.push([host, 1]);
|
||||
}
|
||||
counts.set(host, (counts.get(host) ?? 0) + 1);
|
||||
}
|
||||
|
||||
res.sort((a, b) => b[1] - a[1]);
|
||||
const res = [...counts.entries()].sort((a, b) => b[1] - a[1]);
|
||||
|
||||
return res;
|
||||
});
|
||||
|
||||
@@ -52,18 +52,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const jobs = await this.inboxQueue.getJobs(['delayed']);
|
||||
|
||||
const res = [] as [string, number][];
|
||||
const counts = new Map<string, number>();
|
||||
|
||||
for (const job of jobs) {
|
||||
const host = new URL(job.data.signature.keyId).host;
|
||||
if (res.find(x => x[0] === host)) {
|
||||
res.find(x => x[0] === host)![1]++;
|
||||
} else {
|
||||
res.push([host, 1]);
|
||||
}
|
||||
counts.set(host, (counts.get(host) ?? 0) + 1);
|
||||
}
|
||||
|
||||
res.sort((a, b) => b[1] - a[1]);
|
||||
const res = [...counts.entries()].sort((a, b) => b[1] - a[1]);
|
||||
|
||||
return res;
|
||||
});
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -14,13 +13,22 @@ export const meta = {
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:queue',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
optional: false, nullable: false,
|
||||
ref: 'QueueJob',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
queue: { type: 'string', enum: QUEUE_TYPES },
|
||||
state: { type: 'array', items: { type: 'string', enum: ['active', 'wait', 'delayed', 'completed', 'failed'] } },
|
||||
state: { type: 'array', items: { type: 'string', enum: ['active', 'wait', 'delayed', 'completed', 'failed', 'paused'] } },
|
||||
search: { type: 'string' },
|
||||
},
|
||||
required: ['queue', 'state'],
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -14,6 +13,118 @@ export const meta = {
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:queue',
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: QUEUE_TYPES,
|
||||
},
|
||||
qualifiedName: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
counts: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
additionalProperties: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
isPaused: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
metrics: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
completed: {
|
||||
optional: false, nullable: false,
|
||||
ref: 'QueueMetrics',
|
||||
},
|
||||
failed: {
|
||||
optional: false, nullable: false,
|
||||
ref: 'QueueMetrics',
|
||||
},
|
||||
},
|
||||
},
|
||||
db: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
version: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['cluster', 'standalone', 'sentinel'],
|
||||
},
|
||||
runId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
processId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
port: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
os: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
uptime: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
memory: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
total: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
used: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
fragmentationRatio: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
peak: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
clients: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
blocked: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
connected: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -14,6 +13,47 @@ export const meta = {
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:queue',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: QUEUE_TYPES,
|
||||
},
|
||||
counts: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
additionalProperties: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
isPaused: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
metrics: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
completed: {
|
||||
optional: false, nullable: false,
|
||||
ref: 'QueueMetrics',
|
||||
},
|
||||
failed: {
|
||||
optional: false, nullable: false,
|
||||
ref: 'QueueMetrics',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:queue',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
optional: false, nullable: false,
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
queue: { type: 'string', enum: QUEUE_TYPES },
|
||||
jobId: { type: 'string' },
|
||||
},
|
||||
required: ['queue', 'jobId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private queueService: QueueService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
return this.queueService.queueGetJobLogs(ps.queue, ps.jobId);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -14,6 +13,11 @@ export const meta = {
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:queue',
|
||||
|
||||
res: {
|
||||
optional: false, nullable: false,
|
||||
ref: 'QueueJob',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
@@ -28,7 +32,6 @@ export const paramDef = {
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private moderationLogService: ModerationLogService,
|
||||
private queueService: QueueService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, UserWebhookDeliverQueue, SystemWebhookDeliverQueue } from '@/core/QueueModule.js';
|
||||
import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, PostScheduledNoteQueue, InboxQueue, ObjectStorageQueue, SystemQueue, UserWebhookDeliverQueue, SystemWebhookDeliverQueue } from '@/core/QueueModule.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
@@ -49,6 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
constructor(
|
||||
@Inject('queue:system') public systemQueue: SystemQueue,
|
||||
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
|
||||
@Inject('queue:postScheduledNote') public postScheduledNoteQueue: PostScheduledNoteQueue,
|
||||
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
|
||||
@Inject('queue:inbox') public inboxQueue: InboxQueue,
|
||||
@Inject('queue:db') public dbQueue: DbQueue,
|
||||
|
||||
@@ -49,6 +49,8 @@ export const paramDef = {
|
||||
roleId: { type: 'string', format: 'misskey:id' },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
},
|
||||
required: ['roleId'],
|
||||
@@ -76,7 +78,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
throw new ApiError(meta.errors.noSuchRole);
|
||||
}
|
||||
|
||||
const query = this.queryService.makePaginationQuery(this.roleAssignmentsRepository.createQueryBuilder('assign'), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.roleAssignmentsRepository.createQueryBuilder('assign'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('assign.roleId = :roleId', { roleId: role.id })
|
||||
.andWhere(new Brackets(qb => {
|
||||
qb
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import * as os from 'node:os';
|
||||
import si from 'systeminformation';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
import * as Redis from 'ioredis';
|
||||
@@ -112,6 +111,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
) {
|
||||
super(meta, paramDef, async () => {
|
||||
const si = await import('systeminformation');
|
||||
|
||||
const memStats = await si.mem();
|
||||
const fsStats = await si.fsSize();
|
||||
const netInterface = await si.networkInterfaceDefault();
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { ModerationLogsRepository } from '@/models/_.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ModerationLogEntityService } from '@/core/entities/ModerationLogEntityService.js';
|
||||
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
@@ -63,8 +64,11 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
type: { type: 'string', nullable: true },
|
||||
userId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
search: { type: 'string', nullable: true },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
@@ -79,19 +83,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.moderationLogsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId);
|
||||
const query = this.queryService.makePaginationQuery(this.moderationLogsRepository.createQueryBuilder('log'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate);
|
||||
|
||||
if (ps.type != null) {
|
||||
query.andWhere('report.type = :type', { type: ps.type });
|
||||
query.andWhere('log.type = :type', { type: ps.type });
|
||||
}
|
||||
|
||||
if (ps.userId != null) {
|
||||
query.andWhere('report.userId = :userId', { userId: ps.userId });
|
||||
query.andWhere('log.userId = :userId', { userId: ps.userId });
|
||||
}
|
||||
|
||||
const reports = await query.limit(ps.limit).getMany();
|
||||
if (ps.search != null) {
|
||||
const escapedSearch = sqlLikeEscape(ps.search);
|
||||
query.andWhere('log.info::text ILIKE :search', { search: `%${escapedSearch}%` });
|
||||
}
|
||||
|
||||
return await this.moderationLogEntityService.packMany(reports);
|
||||
const logs = await query.limit(ps.limit).getMany();
|
||||
|
||||
return await this.moderationLogEntityService.packMany(logs);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ export const meta = {
|
||||
quote: { optional: true, ...notificationRecieveConfig },
|
||||
reaction: { optional: true, ...notificationRecieveConfig },
|
||||
pollEnded: { optional: true, ...notificationRecieveConfig },
|
||||
scheduledNotePosted: { optional: true, ...notificationRecieveConfig },
|
||||
scheduledNotePostFailed: { optional: true, ...notificationRecieveConfig },
|
||||
receiveFollowRequest: { optional: true, ...notificationRecieveConfig },
|
||||
followRequestAccepted: { optional: true, ...notificationRecieveConfig },
|
||||
roleAssigned: { optional: true, ...notificationRecieveConfig },
|
||||
|
||||
@@ -48,8 +48,8 @@ export const paramDef = {
|
||||
},
|
||||
secret: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
maxLength: 1024,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
required: [
|
||||
@@ -57,7 +57,6 @@ export const paramDef = {
|
||||
'name',
|
||||
'on',
|
||||
'url',
|
||||
'secret',
|
||||
],
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ export const paramDef = {
|
||||
},
|
||||
secret: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
maxLength: 1024,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
required: [
|
||||
@@ -62,7 +62,6 @@ export const paramDef = {
|
||||
'name',
|
||||
'on',
|
||||
'url',
|
||||
'secret',
|
||||
],
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiMeta } from '@/models/Meta.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
@@ -67,6 +68,14 @@ export const paramDef = {
|
||||
description: { type: 'string', nullable: true },
|
||||
defaultLightTheme: { type: 'string', nullable: true },
|
||||
defaultDarkTheme: { type: 'string', nullable: true },
|
||||
clientOptions: {
|
||||
type: 'object', nullable: false,
|
||||
properties: {
|
||||
entrancePageStyle: { type: 'string', nullable: false, enum: ['classic', 'simple'] },
|
||||
showTimelineForVisitor: { type: 'boolean', nullable: false },
|
||||
showActivitiesForVisitor: { type: 'boolean', nullable: false },
|
||||
},
|
||||
},
|
||||
cacheRemoteFiles: { type: 'boolean' },
|
||||
cacheRemoteSensitiveFiles: { type: 'boolean' },
|
||||
emailRequiredForSignup: { type: 'boolean' },
|
||||
@@ -170,6 +179,7 @@ export const paramDef = {
|
||||
description: '[Deprecated] Use "urlPreviewSummaryProxyUrl" instead.',
|
||||
},
|
||||
urlPreviewEnabled: { type: 'boolean' },
|
||||
urlPreviewAllowRedirect: { type: 'boolean' },
|
||||
urlPreviewTimeout: { type: 'integer' },
|
||||
urlPreviewMaximumContentLength: { type: 'integer' },
|
||||
urlPreviewRequireContentLength: { type: 'boolean' },
|
||||
@@ -185,6 +195,29 @@ export const paramDef = {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
deliverSuspendedSoftware: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
software: { type: 'string' },
|
||||
versionRange: { type: 'string' },
|
||||
},
|
||||
required: ['software', 'versionRange'],
|
||||
},
|
||||
},
|
||||
singleUserMode: { type: 'boolean' },
|
||||
ugcVisibilityForVisitor: {
|
||||
type: 'string',
|
||||
enum: ['all', 'local', 'none'],
|
||||
},
|
||||
proxyRemoteFiles: { type: 'boolean' },
|
||||
signToActivityPubGet: { type: 'boolean' },
|
||||
allowExternalApRedirect: { type: 'boolean' },
|
||||
enableRemoteNotesCleaning: { type: 'boolean' },
|
||||
remoteNotesCleaningExpiryDaysForEachNotes: { type: 'number' },
|
||||
remoteNotesCleaningMaxProcessingDurationInMinutes: { type: 'number' },
|
||||
showRoleBadgesOfRemoteUsers: { type: 'boolean' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
@@ -192,6 +225,9 @@ export const paramDef = {
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.meta)
|
||||
private serverSettings: MiMeta,
|
||||
|
||||
private metaService: MetaService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
) {
|
||||
@@ -303,6 +339,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
set.defaultDarkTheme = ps.defaultDarkTheme;
|
||||
}
|
||||
|
||||
if (ps.clientOptions !== undefined) {
|
||||
set.clientOptions = {
|
||||
...serverSettings.clientOptions,
|
||||
...ps.clientOptions,
|
||||
};
|
||||
}
|
||||
|
||||
if (ps.cacheRemoteFiles !== undefined) {
|
||||
set.cacheRemoteFiles = ps.cacheRemoteFiles;
|
||||
}
|
||||
@@ -645,6 +688,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
set.urlPreviewEnabled = ps.urlPreviewEnabled;
|
||||
}
|
||||
|
||||
if (ps.urlPreviewAllowRedirect !== undefined) {
|
||||
set.urlPreviewAllowRedirect = ps.urlPreviewAllowRedirect;
|
||||
}
|
||||
|
||||
if (ps.urlPreviewTimeout !== undefined) {
|
||||
set.urlPreviewTimeout = ps.urlPreviewTimeout;
|
||||
}
|
||||
@@ -671,10 +718,50 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
set.federation = ps.federation;
|
||||
}
|
||||
|
||||
if (ps.deliverSuspendedSoftware !== undefined) {
|
||||
set.deliverSuspendedSoftware = ps.deliverSuspendedSoftware;
|
||||
}
|
||||
|
||||
if (Array.isArray(ps.federationHosts)) {
|
||||
set.federationHosts = ps.federationHosts.filter(Boolean).map(x => x.toLowerCase());
|
||||
}
|
||||
|
||||
if (ps.singleUserMode !== undefined) {
|
||||
set.singleUserMode = ps.singleUserMode;
|
||||
}
|
||||
|
||||
if (ps.ugcVisibilityForVisitor !== undefined) {
|
||||
set.ugcVisibilityForVisitor = ps.ugcVisibilityForVisitor;
|
||||
}
|
||||
|
||||
if (ps.proxyRemoteFiles !== undefined) {
|
||||
set.proxyRemoteFiles = ps.proxyRemoteFiles;
|
||||
}
|
||||
|
||||
if (ps.signToActivityPubGet !== undefined) {
|
||||
set.signToActivityPubGet = ps.signToActivityPubGet;
|
||||
}
|
||||
|
||||
if (ps.allowExternalApRedirect !== undefined) {
|
||||
set.allowExternalApRedirect = ps.allowExternalApRedirect;
|
||||
}
|
||||
|
||||
if (ps.enableRemoteNotesCleaning !== undefined) {
|
||||
set.enableRemoteNotesCleaning = ps.enableRemoteNotesCleaning;
|
||||
}
|
||||
|
||||
if (ps.remoteNotesCleaningExpiryDaysForEachNotes !== undefined) {
|
||||
set.remoteNotesCleaningExpiryDaysForEachNotes = ps.remoteNotesCleaningExpiryDaysForEachNotes;
|
||||
}
|
||||
|
||||
if (ps.remoteNotesCleaningMaxProcessingDurationInMinutes !== undefined) {
|
||||
set.remoteNotesCleaningMaxProcessingDurationInMinutes = ps.remoteNotesCleaningMaxProcessingDurationInMinutes;
|
||||
}
|
||||
|
||||
if (ps.showRoleBadgesOfRemoteUsers !== undefined) {
|
||||
set.showRoleBadgesOfRemoteUsers = ps.showRoleBadgesOfRemoteUsers;
|
||||
}
|
||||
|
||||
const before = await this.metaService.fetch(true);
|
||||
|
||||
await this.metaService.update(set);
|
||||
|
||||
@@ -33,6 +33,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
isActive: { type: 'boolean', default: true },
|
||||
},
|
||||
required: [],
|
||||
@@ -48,7 +50,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private announcementEntityService: AnnouncementEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('announcement.isActive = :isActive', { isActive: ps.isActive })
|
||||
.andWhere(new Brackets(qb => {
|
||||
if (me) qb.orWhere('announcement.userId = :meId', { meId: me.id });
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import { Brackets } from 'typeorm';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { NotesRepository, AntennasRepository } from '@/models/_.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
@@ -14,6 +15,7 @@ import { IdService } from '@/core/IdService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||
import { ChannelMutingService } from '@/core/ChannelMutingService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -69,6 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private channelMutingService: ChannelMutingService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||
@@ -108,12 +111,26 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
.leftJoinAndSelect('reply.user', 'replyUser')
|
||||
.leftJoinAndSelect('renote.user', 'renoteUser');
|
||||
|
||||
// -- ミュートされたチャンネル対策
|
||||
const mutingChannelIds = await this.channelMutingService
|
||||
.list({ requestUserId: me.id }, { idOnly: true })
|
||||
.then(x => x.map(x => x.id));
|
||||
if (mutingChannelIds.length > 0) {
|
||||
query.andWhere(new Brackets(qb => {
|
||||
qb.orWhere('note.channelId IS NULL');
|
||||
qb.orWhere('note.channelId NOT IN (:...mutingChannelIds)', { mutingChannelIds });
|
||||
}));
|
||||
query.andWhere(new Brackets(qb => {
|
||||
qb.orWhere('note.renoteChannelId IS NULL');
|
||||
qb.orWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds });
|
||||
}));
|
||||
}
|
||||
|
||||
// NOTE: センシティブ除外の設定はこのエンドポイントでは無視する。
|
||||
// https://github.com/misskey-dev/misskey/pull/15346#discussion_r1929950255
|
||||
|
||||
this.queryService.generateVisibilityQuery(query, me);
|
||||
this.queryService.generateMutedUserQueryForNotes(query, me);
|
||||
this.queryService.generateBlockedUserQueryForNotes(query, me);
|
||||
this.queryService.generateBaseNoteFilteringQuery(query, me);
|
||||
|
||||
const notes = await query.getMany();
|
||||
if (sinceId != null && untilId == null) {
|
||||
|
||||
@@ -43,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private apResolverService: ApResolverService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const resolver = this.apResolverService.createResolver();
|
||||
const resolver = await this.apResolverService.createResolver();
|
||||
const object = await resolver.resolve(ps.uri);
|
||||
return object;
|
||||
});
|
||||
|
||||
@@ -18,9 +18,9 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['federation'],
|
||||
@@ -148,7 +148,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
if (this.utilityService.isSelfHost(host)) return null;
|
||||
|
||||
// リモートから一旦オブジェクトフェッチ
|
||||
const resolver = this.apResolverService.createResolver();
|
||||
const resolver = await this.apResolverService.createResolver();
|
||||
// allow ap/show exclusively to lookup URLs that are cross-origin or non-canonical (like https://alice.example.com/@bob@bob.example.com -> https://bob.example.com/@bob)
|
||||
const object = await resolver.resolve(uri, FetchAllowSoftFailMask.CrossOrigin | FetchAllowSoftFailMask.NonCanonicalId).catch((err) => {
|
||||
if (err instanceof IdentifiableError) {
|
||||
@@ -215,7 +215,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
type: 'Note',
|
||||
object,
|
||||
};
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
@@ -48,7 +50,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.blockingsRepository.createQueryBuilder('blocking'), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.blockingsRepository.createQueryBuilder('blocking'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('blocking.blockerId = :meId', { meId: me.id });
|
||||
|
||||
const blockings = await query
|
||||
|
||||
@@ -46,7 +46,7 @@ export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', minLength: 1, maxLength: 128 },
|
||||
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
|
||||
description: { type: 'string', nullable: true, maxLength: 2048 },
|
||||
bannerId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
color: { type: 'string', minLength: 1, maxLength: 16 },
|
||||
isSensitive: { type: 'boolean', nullable: true },
|
||||
|
||||
@@ -33,6 +33,8 @@ export const paramDef = {
|
||||
properties: {
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 },
|
||||
},
|
||||
required: [],
|
||||
@@ -48,7 +50,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.channelFollowingsRepository.createQueryBuilder(), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService
|
||||
.makePaginationQuery(
|
||||
this.channelFollowingsRepository.createQueryBuilder(),
|
||||
ps.sinceId,
|
||||
ps.untilId,
|
||||
ps.sinceDate,
|
||||
ps.untilDate,
|
||||
'followeeId',
|
||||
)
|
||||
.andWhere({ followerId: me.id });
|
||||
|
||||
const followings = await query
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { ChannelsRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { ChannelMutingService } from '@/core/ChannelMutingService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['channels', 'mute'],
|
||||
|
||||
requireCredential: true,
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:channels',
|
||||
|
||||
errors: {
|
||||
noSuchChannel: {
|
||||
message: 'No such Channel.',
|
||||
code: 'NO_SUCH_CHANNEL',
|
||||
id: '7174361e-d58f-31d6-2e7c-6fb830786a3f',
|
||||
},
|
||||
|
||||
alreadyMuting: {
|
||||
message: 'You are already muting that user.',
|
||||
code: 'ALREADY_MUTING_CHANNEL',
|
||||
id: '5a251978-769a-da44-3e89-3931e43bb592',
|
||||
},
|
||||
|
||||
expiresAtIsPast: {
|
||||
message: 'Cannot set past date to "expiresAt".',
|
||||
code: 'EXPIRES_AT_IS_PAST',
|
||||
id: '42b32236-df2c-a45f-fdbf-def67268f749',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
channelId: { type: 'string', format: 'misskey:id' },
|
||||
expiresAt: {
|
||||
type: 'integer',
|
||||
nullable: true,
|
||||
description: 'A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute.',
|
||||
},
|
||||
},
|
||||
required: ['channelId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.channelsRepository)
|
||||
private channelsRepository: ChannelsRepository,
|
||||
private channelMutingService: ChannelMutingService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// Check if exists the channel
|
||||
const targetChannel = await this.channelsRepository.findOneBy({ id: ps.channelId });
|
||||
if (!targetChannel) {
|
||||
throw new ApiError(meta.errors.noSuchChannel);
|
||||
}
|
||||
|
||||
// Check if already muting
|
||||
const exist = await this.channelMutingService.isMuted({
|
||||
requestUserId: me.id,
|
||||
targetChannelId: targetChannel.id,
|
||||
});
|
||||
if (exist) {
|
||||
throw new ApiError(meta.errors.alreadyMuting);
|
||||
}
|
||||
|
||||
// Check if expiresAt is past
|
||||
if (ps.expiresAt && ps.expiresAt <= Date.now()) {
|
||||
throw new ApiError(meta.errors.expiresAtIsPast);
|
||||
}
|
||||
|
||||
await this.channelMutingService.mute({
|
||||
requestUserId: me.id,
|
||||
targetChannelId: targetChannel.id,
|
||||
expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { ChannelsRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChannelMutingService } from '@/core/ChannelMutingService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['channels', 'mute'],
|
||||
|
||||
requireCredential: true,
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:channels',
|
||||
|
||||
errors: {
|
||||
noSuchChannel: {
|
||||
message: 'No such Channel.',
|
||||
code: 'NO_SUCH_CHANNEL',
|
||||
id: 'e7998769-6e94-d9c2-6b8f-94a527314aba',
|
||||
},
|
||||
|
||||
notMuting: {
|
||||
message: 'You are not muting that channel.',
|
||||
code: 'NOT_MUTING_CHANNEL',
|
||||
id: '14d55962-6ea8-d990-1333-d6bef78dc2ab',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
channelId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['channelId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.channelsRepository)
|
||||
private channelsRepository: ChannelsRepository,
|
||||
private channelMutingService: ChannelMutingService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// Check if exists the channel
|
||||
const targetChannel = await this.channelsRepository.findOneBy({ id: ps.channelId });
|
||||
if (!targetChannel) {
|
||||
throw new ApiError(meta.errors.noSuchChannel);
|
||||
}
|
||||
|
||||
// Check muting
|
||||
const exist = await this.channelMutingService.isMuted({
|
||||
requestUserId: me.id,
|
||||
targetChannelId: targetChannel.id,
|
||||
});
|
||||
if (!exist) {
|
||||
throw new ApiError(meta.errors.notMuting);
|
||||
}
|
||||
|
||||
await this.channelMutingService.unmute({
|
||||
requestUserId: me.id,
|
||||
targetChannelId: targetChannel.id,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { ChannelMutingService } from '@/core/ChannelMutingService.js';
|
||||
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['channels', 'mute'],
|
||||
|
||||
requireCredential: true,
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'read:channels',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'Channel',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private channelMutingService: ChannelMutingService,
|
||||
private channelEntityService: ChannelEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const mutings = await this.channelMutingService.list({
|
||||
requestUserId: me.id,
|
||||
});
|
||||
return await this.channelEntityService.packMany(mutings, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,8 @@ export const paramDef = {
|
||||
properties: {
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 },
|
||||
},
|
||||
required: [],
|
||||
@@ -48,7 +50,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('channel.isArchived = FALSE')
|
||||
.andWhere({ userId: me.id });
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ export const paramDef = {
|
||||
type: { type: 'string', enum: ['nameAndDescription', 'nameOnly'], default: 'nameAndDescription' },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 },
|
||||
},
|
||||
required: ['query'],
|
||||
@@ -50,7 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('channel.isArchived = FALSE');
|
||||
|
||||
if (ps.query !== '') {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { DI } from '@/di-symbols.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
||||
import { MiLocalUser } from '@/models/User.js';
|
||||
import { ChannelMutingService } from '@/core/ChannelMutingService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -70,6 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
|
||||
private activeUsersChart: ActiveUsersChart,
|
||||
private channelMutingService: ChannelMutingService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||
@@ -98,6 +100,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
useDbFallback: true,
|
||||
redisTimelines: [`channelTimeline:${channel.id}`],
|
||||
excludePureRenotes: false,
|
||||
ignoreAuthorChannelFromMute: true,
|
||||
dbFallback: async (untilId, sinceId, limit) => {
|
||||
return await this.getFromDb({ untilId, sinceId, limit, channelId: channel.id }, me);
|
||||
},
|
||||
@@ -121,9 +124,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
.leftJoinAndSelect('renote.user', 'renoteUser')
|
||||
.leftJoinAndSelect('note.channel', 'channel');
|
||||
|
||||
this.queryService.generateBaseNoteFilteringQuery(query, me);
|
||||
|
||||
if (me) {
|
||||
this.queryService.generateMutedUserQueryForNotes(query, me);
|
||||
this.queryService.generateBlockedUserQueryForNotes(query, me);
|
||||
const mutingChannelIds = await this.channelMutingService
|
||||
.list({ requestUserId: me.id }, { idOnly: true })
|
||||
.then(x => x.map(x => x.id).filter(x => x !== ps.channelId));
|
||||
if (mutingChannelIds.length > 0) {
|
||||
query.andWhere('note.channelId NOT IN (:...mutingChannelIds)', { mutingChannelIds });
|
||||
query.andWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds });
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export const paramDef = {
|
||||
properties: {
|
||||
channelId: { type: 'string', format: 'misskey:id' },
|
||||
name: { type: 'string', minLength: 1, maxLength: 128 },
|
||||
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
|
||||
description: { type: 'string', nullable: true, maxLength: 2048 },
|
||||
bannerId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
isArchived: { type: 'boolean', nullable: true },
|
||||
pinnedNoteIds: {
|
||||
|
||||
@@ -16,9 +16,6 @@ export const meta = {
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
res: {
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchMessage: {
|
||||
message: 'No such message.',
|
||||
|
||||
@@ -16,9 +16,6 @@ export const meta = {
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
res: {
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchMessage: {
|
||||
message: 'No such message.',
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
@@ -42,6 +43,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
roomId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['roomId'],
|
||||
@@ -52,8 +55,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
constructor(
|
||||
private chatEntityService: ChatEntityService,
|
||||
private chatService: ChatService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
||||
|
||||
await this.chatService.checkChatAvailability(me.id, 'read');
|
||||
|
||||
const room = await this.chatService.findRoomById(ps.roomId);
|
||||
@@ -65,7 +72,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
|
||||
const messages = await this.chatService.roomTimeline(room.id, ps.limit, ps.sinceId, ps.untilId);
|
||||
const messages = await this.chatService.roomTimeline(room.id, ps.limit, sinceId, untilId);
|
||||
|
||||
this.chatService.readRoomChatMessage(me.id, room.id);
|
||||
|
||||
|
||||
@@ -16,9 +16,6 @@ export const meta = {
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
res: {
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchMessage: {
|
||||
message: 'No such message.',
|
||||
|
||||
@@ -10,6 +10,7 @@ import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
@@ -43,6 +44,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['userId'],
|
||||
@@ -54,8 +57,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private chatEntityService: ChatEntityService,
|
||||
private chatService: ChatService,
|
||||
private getterService: GetterService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
||||
|
||||
await this.chatService.checkChatAvailability(me.id, 'read');
|
||||
|
||||
const other = await this.getterService.getUser(ps.userId).catch(err => {
|
||||
@@ -63,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
throw err;
|
||||
});
|
||||
|
||||
const messages = await this.chatService.userTimeline(me.id, other.id, ps.limit, ps.sinceId, ps.untilId);
|
||||
const messages = await this.chatService.userTimeline(me.id, other.id, ps.limit, sinceId, untilId);
|
||||
|
||||
this.chatService.readUserChatMessage(me.id, other.id);
|
||||
|
||||
|
||||
40
packages/backend/src/server/api/endpoints/chat/read-all.ts
Normal file
40
packages/backend/src/server/api/endpoints/chat/read-all.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
errors: {
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
},
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.chatService.checkChatAvailability(me.id, 'read');
|
||||
|
||||
await this.chatService.readAllChatMessages(me.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,6 @@ export const meta = {
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
res: {
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
|
||||
@@ -16,9 +16,6 @@ export const meta = {
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
res: {
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
@@ -37,6 +38,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -45,11 +48,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
constructor(
|
||||
private chatEntityService: ChatEntityService,
|
||||
private chatService: ChatService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
||||
|
||||
await this.chatService.checkChatAvailability(me.id, 'read');
|
||||
|
||||
const invitations = await this.chatService.getReceivedRoomInvitationsWithPagination(me.id, ps.limit, ps.sinceId, ps.untilId);
|
||||
const invitations = await this.chatService.getReceivedRoomInvitationsWithPagination(me.id, ps.limit, sinceId, untilId);
|
||||
return this.chatEntityService.packRoomInvitations(invitations, me);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
@@ -44,6 +45,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
},
|
||||
required: ['roomId'],
|
||||
} as const;
|
||||
@@ -53,8 +56,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
private chatEntityService: ChatEntityService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
||||
|
||||
await this.chatService.checkChatAvailability(me.id, 'read');
|
||||
|
||||
const room = await this.chatService.findMyRoomById(me.id, ps.roomId);
|
||||
@@ -62,7 +69,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
|
||||
const invitations = await this.chatService.getSentRoomInvitationsWithPagination(ps.roomId, ps.limit, ps.sinceId, ps.untilId);
|
||||
const invitations = await this.chatService.getSentRoomInvitationsWithPagination(ps.roomId, ps.limit, sinceId, untilId);
|
||||
return this.chatEntityService.packRoomInvitations(invitations, me);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,9 +16,6 @@ export const meta = {
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
res: {
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
@@ -37,6 +38,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -45,11 +48,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
private chatEntityService: ChatEntityService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
||||
|
||||
await this.chatService.checkChatAvailability(me.id, 'read');
|
||||
|
||||
const memberships = await this.chatService.getMyMemberships(me.id, ps.limit, ps.sinceId, ps.untilId);
|
||||
const memberships = await this.chatService.getMyMemberships(me.id, ps.limit, sinceId, untilId);
|
||||
|
||||
return this.chatEntityService.packRoomMemberships(memberships, me, {
|
||||
populateUser: false,
|
||||
|
||||
@@ -16,9 +16,6 @@ export const meta = {
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
res: {
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
@@ -43,6 +44,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
},
|
||||
required: ['roomId'],
|
||||
} as const;
|
||||
@@ -52,8 +55,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
private chatEntityService: ChatEntityService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
||||
|
||||
await this.chatService.checkChatAvailability(me.id, 'read');
|
||||
|
||||
const room = await this.chatService.findRoomById(ps.roomId);
|
||||
@@ -65,7 +72,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
|
||||
const memberships = await this.chatService.getRoomMembershipsWithPagination(room.id, ps.limit, ps.sinceId, ps.untilId);
|
||||
const memberships = await this.chatService.getRoomMembershipsWithPagination(room.id, ps.limit, sinceId, untilId);
|
||||
|
||||
return this.chatEntityService.packRoomMemberships(memberships, me, {
|
||||
populateUser: true,
|
||||
|
||||
@@ -16,9 +16,6 @@ export const meta = {
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
res: {
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
@@ -37,6 +38,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -45,11 +48,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
constructor(
|
||||
private chatEntityService: ChatEntityService,
|
||||
private chatService: ChatService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
||||
|
||||
await this.chatService.checkChatAvailability(me.id, 'read');
|
||||
|
||||
const rooms = await this.chatService.getOwnedRoomsWithPagination(me.id, ps.limit, ps.sinceId, ps.untilId);
|
||||
const rooms = await this.chatService.getOwnedRoomsWithPagination(me.id, ps.limit, sinceId, untilId);
|
||||
return this.chatEntityService.packRooms(rooms, me);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import type { ClipsRepository } from '@/models/_.js';
|
||||
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
@@ -29,7 +30,13 @@ export const meta = {
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
properties: {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
@@ -39,12 +46,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
@Inject(DI.clipsRepository)
|
||||
private clipsRepository: ClipsRepository,
|
||||
|
||||
private queryService: QueryService,
|
||||
private clipEntityService: ClipEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const clips = await this.clipsRepository.findBy({
|
||||
userId: me.id,
|
||||
});
|
||||
const query = this.queryService.makePaginationQuery(this.clipsRepository.createQueryBuilder('clip'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('clip.userId = :userId', { userId: me.id });
|
||||
|
||||
const clips = await query.limit(ps.limit).getMany();
|
||||
|
||||
return await this.clipEntityService.packMany(clips, me);
|
||||
});
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Brackets } from 'typeorm';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { NotesRepository, ClipsRepository, ClipNotesRepository } from '@/models/_.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -44,6 +46,9 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
search: { type: 'string', minLength: 1, maxLength: 100, nullable: true },
|
||||
},
|
||||
required: ['clipId'],
|
||||
} as const;
|
||||
@@ -76,7 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
throw new ApiError(meta.errors.noSuchClip);
|
||||
}
|
||||
|
||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.innerJoin(this.clipNotesRepository.metadata.targetName, 'clipNote', 'clipNote.noteId = note.id')
|
||||
.innerJoinAndSelect('note.user', 'user')
|
||||
.leftJoinAndSelect('note.reply', 'reply')
|
||||
@@ -85,10 +90,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
.leftJoinAndSelect('renote.user', 'renoteUser')
|
||||
.andWhere('clipNote.clipId = :clipId', { clipId: clip.id });
|
||||
|
||||
this.queryService.generateVisibilityQuery(query, me);
|
||||
this.queryService.generateBlockedHostQueryForNote(query);
|
||||
// this.queryService.generateSuspendedUserQueryForNote(query); // To avoid problems with removing notes, ignoring suspended user for now
|
||||
if (me) {
|
||||
this.queryService.generateVisibilityQuery(query, me);
|
||||
this.queryService.generateMutedUserQueryForNotes(query, me);
|
||||
this.queryService.generateBlockedUserQueryForNotes(query, me);
|
||||
this.queryService.generateMutedUserQueryForNotes(query, me, { noteColumn: 'renote' });
|
||||
this.queryService.generateBlockedUserQueryForNotes(query, me, { noteColumn: 'renote' });
|
||||
}
|
||||
|
||||
if (ps.search != null) {
|
||||
for (const word of ps.search.trim().split(' ')) {
|
||||
query.andWhere(new Brackets(qb => {
|
||||
qb.orWhere('note.text ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
|
||||
qb.orWhere('note.cw ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
const notes = await query
|
||||
|
||||
@@ -34,6 +34,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
|
||||
type: { type: 'string', nullable: true, pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) },
|
||||
sort: { type: 'string', nullable: true, enum: ['+createdAt', '-createdAt', '+name', '-name', '+size', '-size', null] },
|
||||
@@ -51,7 +53,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('file.userId = :userId', { userId: me.id });
|
||||
|
||||
if (ps.folderId) {
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { DriveFilesRepository, ChatMessagesRepository } from '@/models/_.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['drive', 'chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:drive',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatMessage',
|
||||
},
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchFile: {
|
||||
message: 'No such file.',
|
||||
code: 'NO_SUCH_FILE',
|
||||
id: '485ce26d-f5d2-4313-9783-e689d131eafb',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['fileId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
@Inject(DI.chatMessagesRepository)
|
||||
private chatMessagesRepository: ChatMessagesRepository,
|
||||
|
||||
private chatService: ChatService,
|
||||
private chatEntityService: ChatEntityService,
|
||||
private queryService: QueryService,
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const isModerator = await this.roleService.isModerator(me);
|
||||
|
||||
if (!isModerator) {
|
||||
await this.chatService.checkChatAvailability(me.id, 'read');
|
||||
}
|
||||
|
||||
const file = await this.driveFilesRepository.findOneBy({
|
||||
id: ps.fileId,
|
||||
userId: isModerator ? undefined : me.id,
|
||||
});
|
||||
|
||||
if (file == null) {
|
||||
throw new ApiError(meta.errors.noSuchFile);
|
||||
}
|
||||
|
||||
const query = this.queryService.makePaginationQuery(this.chatMessagesRepository.createQueryBuilder('message'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate);
|
||||
query.andWhere('message.fileId = :fileId', { fileId: file.id });
|
||||
|
||||
const messages = await query.limit(ps.limit).getMany();
|
||||
|
||||
return await this.chatEntityService.packMessagesDetailed(messages, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@ import type { NotesRepository, DriveFilesRepository } from '@/models/_.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['drive', 'notes'],
|
||||
@@ -45,6 +45,8 @@ export const paramDef = {
|
||||
properties: {
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
@@ -75,7 +77,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
throw new ApiError(meta.errors.noSuchFile);
|
||||
}
|
||||
|
||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId);
|
||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate);
|
||||
query.andWhere(':file <@ note.fileIds', { file: [file.id] });
|
||||
|
||||
const notes = await query.limit(ps.limit).getMany();
|
||||
|
||||
@@ -10,9 +10,9 @@ import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
import { DriveService } from '@/core/DriveService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import { MiMeta } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['drive'],
|
||||
@@ -56,6 +56,19 @@ export const meta = {
|
||||
code: 'NO_FREE_SPACE',
|
||||
id: 'd08dbc37-a6a9-463a-8c47-96c32ab5f064',
|
||||
},
|
||||
|
||||
maxFileSizeExceeded: {
|
||||
message: 'Cannot upload the file because it exceeds the maximum file size.',
|
||||
code: 'MAX_FILE_SIZE_EXCEEDED',
|
||||
id: 'b9d8c348-33f0-4673-b9a9-5d4da058977a',
|
||||
httpStatusCode: 413,
|
||||
},
|
||||
|
||||
unallowedFileType: {
|
||||
message: 'Cannot upload the file because it is an unallowed file type.',
|
||||
code: 'UNALLOWED_FILE_TYPE',
|
||||
id: '4becd248-7f2c-48c4-a9f0-75edc4f9a1ea',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -115,6 +128,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
if (err instanceof IdentifiableError) {
|
||||
if (err.id === '282f77bf-5816-4f72-9264-aa14d8261a21') throw new ApiError(meta.errors.inappropriate);
|
||||
if (err.id === 'c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6') throw new ApiError(meta.errors.noFreeSpace);
|
||||
if (err.id === 'f9e4e5f3-4df4-40b5-b400-f236945f7073') throw new ApiError(meta.errors.maxFileSizeExceeded);
|
||||
if (err.id === 'bd71c601-f9b0-4808-9137-a330647ced9b') throw new ApiError(meta.errors.unallowedFileType);
|
||||
}
|
||||
throw new ApiError();
|
||||
} finally {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { DriveService } from '@/core/DriveService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['drive'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:drive',
|
||||
|
||||
errors: {
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fileIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 100, items: { type: 'string', format: 'misskey:id' } },
|
||||
folderId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
},
|
||||
required: ['fileIds'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private driveService: DriveService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.driveService.moveFiles(ps.fileIds, ps.folderId ?? null, me.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -43,14 +43,21 @@ export const meta = {
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
url: { type: 'string' },
|
||||
},
|
||||
anyOf: [
|
||||
{ required: ['fileId'] },
|
||||
{ required: ['url'] },
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['fileId'],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: 'string' },
|
||||
},
|
||||
required: ['url'],
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
||||
@@ -64,21 +71,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
let file: MiDriveFile | null = null;
|
||||
|
||||
if (ps.fileId) {
|
||||
file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
} else if (ps.url) {
|
||||
file = await this.driveFilesRepository.findOne({
|
||||
where: [{
|
||||
url: ps.url,
|
||||
}, {
|
||||
webpublicUrl: ps.url,
|
||||
}, {
|
||||
thumbnailUrl: ps.url,
|
||||
}],
|
||||
});
|
||||
}
|
||||
const file = await this.driveFilesRepository.findOneBy(
|
||||
'fileId' in ps
|
||||
? { id: ps.fileId }
|
||||
: [{ url: ps.url }, { webpublicUrl: ps.url }, { thumbnailUrl: ps.url }],
|
||||
);
|
||||
|
||||
if (file == null) {
|
||||
throw new ApiError(meta.errors.noSuchFile);
|
||||
|
||||
@@ -34,6 +34,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
|
||||
},
|
||||
required: [],
|
||||
@@ -49,7 +51,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.driveFoldersRepository.createQueryBuilder('folder'), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.driveFoldersRepository.createQueryBuilder('folder'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('folder.userId = :userId', { userId: me.id });
|
||||
|
||||
if (ps.folderId) {
|
||||
|
||||
@@ -34,6 +34,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
type: { type: 'string', pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) },
|
||||
},
|
||||
required: [],
|
||||
@@ -49,7 +51,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('file.userId = :userId', { userId: me.id });
|
||||
|
||||
if (ps.type) {
|
||||
|
||||
@@ -32,6 +32,8 @@ export const paramDef = {
|
||||
host: { type: 'string' },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
},
|
||||
required: ['host'],
|
||||
@@ -47,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('following.followeeHost = :host', { host: ps.host });
|
||||
|
||||
const followings = await query
|
||||
|
||||
@@ -32,6 +32,8 @@ export const paramDef = {
|
||||
host: { type: 'string' },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
},
|
||||
required: ['host'],
|
||||
@@ -47,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('following.followerHost = :host', { host: ps.host });
|
||||
|
||||
const followings = await query
|
||||
|
||||
@@ -32,6 +32,8 @@ export const paramDef = {
|
||||
host: { type: 'string' },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
},
|
||||
required: ['host'],
|
||||
@@ -47,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.usersRepository.createQueryBuilder('user'), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.usersRepository.createQueryBuilder('user'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('user.host = :host', { host: ps.host });
|
||||
|
||||
const users = await query
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { FlashLikesRepository } from '@/models/_.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { FlashLikeEntityService } from '@/core/entities/FlashLikeEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { FlashService } from '@/core/FlashService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['account', 'flash'],
|
||||
@@ -44,6 +43,9 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
search: { type: 'string', minLength: 1, maxLength: 100, nullable: true },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
@@ -51,20 +53,18 @@ export const paramDef = {
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.flashLikesRepository)
|
||||
private flashLikesRepository: FlashLikesRepository,
|
||||
|
||||
private flashLikeEntityService: FlashLikeEntityService,
|
||||
private queryService: QueryService,
|
||||
private flashService: FlashService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.flashLikesRepository.createQueryBuilder('like'), ps.sinceId, ps.untilId)
|
||||
.andWhere('like.userId = :meId', { meId: me.id })
|
||||
.leftJoinAndSelect('like.flash', 'flash');
|
||||
|
||||
const likes = await query
|
||||
.limit(ps.limit)
|
||||
.getMany();
|
||||
const likes = await this.flashService.myLikes(me.id, {
|
||||
sinceId: ps.sinceId,
|
||||
untilId: ps.untilId,
|
||||
sinceDate: ps.sinceDate,
|
||||
untilDate: ps.untilDate,
|
||||
limit: ps.limit,
|
||||
search: ps.search,
|
||||
});
|
||||
|
||||
return this.flashLikeEntityService.packMany(likes, me);
|
||||
});
|
||||
|
||||
@@ -34,6 +34,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
@@ -48,7 +50,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.flashsRepository.createQueryBuilder('flash'), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.flashsRepository.createQueryBuilder('flash'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('flash.userId = :meId', { meId: me.id });
|
||||
|
||||
const flashs = await query
|
||||
|
||||
59
packages/backend/src/server/api/endpoints/flash/search.ts
Normal file
59
packages/backend/src/server/api/endpoints/flash/search.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { FlashService } from '@/core/FlashService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['flash'],
|
||||
|
||||
requireCredential: false,
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'Flash',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: { type: 'string', minLength: 1, maxLength: 100 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 },
|
||||
},
|
||||
required: ['query'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private flashService: FlashService,
|
||||
private flashEntityService: FlashEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const result = await this.flashService.search(ps.query, {
|
||||
sinceId: ps.sinceId,
|
||||
untilId: ps.untilId,
|
||||
sinceDate: ps.sinceDate,
|
||||
untilDate: ps.untilDate,
|
||||
limit: ps.limit,
|
||||
});
|
||||
|
||||
return await this.flashEntityService.packMany(result, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -73,8 +73,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
updatedAt: new Date(),
|
||||
...Object.fromEntries(
|
||||
Object.entries(ps).filter(
|
||||
([key, val]) => (key !== 'flashId') && Object.hasOwn(paramDef.properties, key)
|
||||
)
|
||||
([key, val]) => (key !== 'flashId') && Object.hasOwn(paramDef.properties, key),
|
||||
),
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -49,6 +49,8 @@ export const paramDef = {
|
||||
properties: {
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
},
|
||||
required: [],
|
||||
@@ -64,7 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.followRequestsRepository.createQueryBuilder('request'), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.followRequestsRepository.createQueryBuilder('request'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('request.followeeId = :meId', { meId: me.id });
|
||||
|
||||
const requests = await query
|
||||
|
||||
@@ -49,6 +49,8 @@ export const paramDef = {
|
||||
properties: {
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
},
|
||||
required: [],
|
||||
@@ -64,7 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.followRequestsRepository.createQueryBuilder('request'), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.followRequestsRepository.createQueryBuilder('request'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('request.followerId = :meId', { meId: me.id });
|
||||
|
||||
const requests = await query
|
||||
|
||||
@@ -30,6 +30,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
@@ -44,7 +46,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.innerJoinAndSelect('post.user', 'user');
|
||||
|
||||
const posts = await query.limit(ps.limit).getMany();
|
||||
|
||||
@@ -81,7 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
try {
|
||||
await this.userAuthService.twoFactorAuthenticate(profile, token);
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
throw new Error('authentication failed');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
try {
|
||||
await this.userAuthService.twoFactorAuthenticate(profile, token);
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
throw new Error('authentication failed');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
try {
|
||||
await this.userAuthService.twoFactorAuthenticate(profile, token);
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
throw new Error('authentication failed');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
try {
|
||||
await this.userAuthService.twoFactorAuthenticate(profile, token);
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
throw new Error('authentication failed');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
try {
|
||||
await this.userAuthService.twoFactorAuthenticate(profile, token);
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
throw new Error('authentication failed');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,14 @@ export const meta = {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
iconUrl: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -88,6 +96,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
createdAt: this.idService.parse(token.id).date.toISOString(),
|
||||
lastUsedAt: token.lastUsedAt?.toISOString(),
|
||||
permission: token.app ? token.app.permission : token.permission,
|
||||
iconUrl: token.iconUrl,
|
||||
description: token.description ?? token.app?.description ?? null,
|
||||
})));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
try {
|
||||
await this.userAuthService.twoFactorAuthenticate(profile, token);
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
throw new Error('authentication failed');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { AchievementService, ACHIEVEMENT_TYPES } from '@/core/AchievementService.js';
|
||||
import { AchievementService } from '@/core/AchievementService.js';
|
||||
import { ACHIEVEMENT_TYPES } from '@/models/UserProfile.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
||||
@@ -49,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
try {
|
||||
await this.userAuthService.twoFactorAuthenticate(profile, token);
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
throw new Error('authentication failed');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
@@ -48,7 +50,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.noteFavoritesRepository.createQueryBuilder('favorite'), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.noteFavoritesRepository.createQueryBuilder('favorite'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('favorite.userId = :meId', { meId: me.id })
|
||||
.leftJoinAndSelect('favorite.note', 'note');
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user