Merge remote-tracking branch 'misskey-dev/develop' into io
This commit is contained in:
commit
50caa3fd5c
252 changed files with 4312 additions and 3337 deletions
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class MakeRepositoryUrlNullable1707808106310 {
|
||||
name = 'MakeRepositoryUrlNullable1707808106310'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "repositoryUrl" DROP NOT NULL`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "repositoryUrl" SET NOT NULL`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class RepositoryUrlFromSyuiloToMisskeyDev1708266695091 {
|
||||
name = 'RepositoryUrlFromSyuiloToMisskeyDev1708266695091'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`UPDATE "meta" SET "repositoryUrl" = 'https://github.com/misskey-dev/misskey' WHERE "repositoryUrl" = 'https://github.com/syuilo/misskey'`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
// no valid down migration
|
||||
}
|
||||
}
|
|
@ -46,8 +46,8 @@
|
|||
"@swc/core-win32-arm64-msvc": "1.3.107",
|
||||
"@swc/core-win32-ia32-msvc": "1.3.107",
|
||||
"@swc/core-win32-x64-msvc": "1.3.107",
|
||||
"@tensorflow/tfjs": "4.4.0",
|
||||
"@tensorflow/tfjs-node": "4.4.0",
|
||||
"@tensorflow/tfjs": "4.17.0",
|
||||
"@tensorflow/tfjs-node": "4.17.0",
|
||||
"bufferutil": "4.0.8",
|
||||
"slacc-android-arm-eabi": "0.0.10",
|
||||
"slacc-android-arm64": "0.0.10",
|
||||
|
@ -65,27 +65,27 @@
|
|||
"utf-8-validate": "6.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.412.0",
|
||||
"@aws-sdk/lib-storage": "3.412.0",
|
||||
"@bull-board/api": "5.14.0",
|
||||
"@bull-board/fastify": "5.14.0",
|
||||
"@bull-board/ui": "5.14.0",
|
||||
"@aws-sdk/client-s3": "3.515.0",
|
||||
"@aws-sdk/lib-storage": "3.515.0",
|
||||
"@bull-board/api": "5.14.1",
|
||||
"@bull-board/fastify": "5.14.1",
|
||||
"@bull-board/ui": "5.14.1",
|
||||
"@discordapp/twemoji": "15.0.2",
|
||||
"@fastify/accepts": "4.3.0",
|
||||
"@fastify/cookie": "9.3.1",
|
||||
"@fastify/cors": "8.5.0",
|
||||
"@fastify/cors": "9.0.1",
|
||||
"@fastify/express": "2.3.0",
|
||||
"@fastify/http-proxy": "9.3.0",
|
||||
"@fastify/http-proxy": "9.4.0",
|
||||
"@fastify/multipart": "8.1.0",
|
||||
"@fastify/static": "6.12.0",
|
||||
"@fastify/view": "8.2.0",
|
||||
"@misskey-dev/sharp-read-bmp": "^1.1.1",
|
||||
"@fastify/static": "7.0.1",
|
||||
"@fastify/view": "9.0.0",
|
||||
"@misskey-dev/sharp-read-bmp": "^1.2.0",
|
||||
"@misskey-dev/summaly": "^5.0.3",
|
||||
"@nestjs/common": "10.3.2",
|
||||
"@nestjs/core": "10.3.2",
|
||||
"@nestjs/testing": "10.3.2",
|
||||
"@nestjs/common": "10.3.3",
|
||||
"@nestjs/core": "10.3.3",
|
||||
"@nestjs/testing": "10.3.3",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@simplewebauthn/server": "9.0.2",
|
||||
"@simplewebauthn/server": "9.0.3",
|
||||
"@sinonjs/fake-timers": "11.2.2",
|
||||
"@smithy/node-http-handler": "2.3.1",
|
||||
"@swc/cli": "0.1.65",
|
||||
|
@ -98,18 +98,18 @@
|
|||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "2.0.5",
|
||||
"body-parser": "1.20.2",
|
||||
"bullmq": "5.1.9",
|
||||
"bullmq": "5.3.0",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"cbor": "9.0.2",
|
||||
"chalk": "5.3.0",
|
||||
"chalk-template": "1.1.0",
|
||||
"chokidar": "3.5.3",
|
||||
"chokidar": "3.6.0",
|
||||
"cli-highlight": "2.1.11",
|
||||
"color-convert": "2.0.1",
|
||||
"content-disposition": "0.5.4",
|
||||
"date-fns": "2.30.0",
|
||||
"date-fns": "3.3.1",
|
||||
"deep-email-validator": "0.1.21",
|
||||
"fastify": "4.26.0",
|
||||
"fastify": "4.26.1",
|
||||
"fastify-raw-body": "4.3.0",
|
||||
"feed": "4.2.2",
|
||||
"file-type": "19.0.0",
|
||||
|
@ -135,11 +135,11 @@
|
|||
"misskey-js": "workspace:*",
|
||||
"misskey-reversi": "workspace:*",
|
||||
"ms": "3.0.0-canary.1",
|
||||
"nanoid": "5.0.5",
|
||||
"nanoid": "5.0.6",
|
||||
"nested-property": "4.0.0",
|
||||
"node-fetch": "3.3.2",
|
||||
"nodemailer": "6.9.9",
|
||||
"nsfwjs": "2.4.2",
|
||||
"nsfwjs": "3.0.0",
|
||||
"oauth": "0.10.0",
|
||||
"oauth2orize": "1.12.0",
|
||||
"oauth2orize-pkce": "0.1.2",
|
||||
|
@ -147,7 +147,7 @@
|
|||
"otpauth": "9.2.2",
|
||||
"parse5": "7.1.2",
|
||||
"pg": "8.11.3",
|
||||
"pino": "8.18.0",
|
||||
"pino": "8.19.0",
|
||||
"pino-pretty": "10.3.1",
|
||||
"pkce-challenge": "4.1.0",
|
||||
"probe-image-size": "7.2.3",
|
||||
|
@ -160,17 +160,17 @@
|
|||
"ratelimiter": "3.4.1",
|
||||
"re2": "1.20.9",
|
||||
"redis-lock": "0.1.4",
|
||||
"reflect-metadata": "0.1.14",
|
||||
"reflect-metadata": "0.2.1",
|
||||
"rename": "1.0.4",
|
||||
"rss-parser": "3.13.0",
|
||||
"rxjs": "7.8.1",
|
||||
"sanitize-html": "2.11.0",
|
||||
"sanitize-html": "2.12.0",
|
||||
"secure-json-parse": "2.7.0",
|
||||
"sharp": "0.33.2",
|
||||
"slacc": "0.0.10",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"systeminformation": "5.21.24",
|
||||
"systeminformation": "5.22.0",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.1",
|
||||
"tsc-alias": "1.8.8",
|
||||
|
@ -186,7 +186,7 @@
|
|||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||
"@nestjs/platform-express": "10.3.2",
|
||||
"@nestjs/platform-express": "10.3.3",
|
||||
"@simplewebauthn/types": "9.0.1",
|
||||
"@swc/jest": "0.2.36",
|
||||
"@types/accepts": "1.3.7",
|
||||
|
@ -204,20 +204,20 @@
|
|||
"@types/jsrsasign": "10.5.12",
|
||||
"@types/mime-types": "2.1.4",
|
||||
"@types/ms": "0.7.34",
|
||||
"@types/node": "20.11.17",
|
||||
"@types/node": "20.11.19",
|
||||
"@types/nodemailer": "6.4.14",
|
||||
"@types/oauth": "0.9.4",
|
||||
"@types/oauth2orize": "1.11.3",
|
||||
"@types/oauth2orize-pkce": "0.1.2",
|
||||
"@types/pg": "8.11.0",
|
||||
"@types/pug": "2.0.10",
|
||||
"@types/punycode": "2.1.3",
|
||||
"@types/punycode": "2.1.4",
|
||||
"@types/qrcode": "1.5.5",
|
||||
"@types/random-seed": "0.3.5",
|
||||
"@types/ratelimiter": "3.4.6",
|
||||
"@types/rename": "1.0.7",
|
||||
"@types/sanitize-html": "2.11.0",
|
||||
"@types/semver": "7.5.6",
|
||||
"@types/semver": "7.5.7",
|
||||
"@types/simple-oauth2": "5.0.7",
|
||||
"@types/sinonjs__fake-timers": "8.1.5",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
|
@ -225,8 +225,8 @@
|
|||
"@types/vary": "1.1.3",
|
||||
"@types/web-push": "3.6.3",
|
||||
"@types/ws": "8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "6.21.0",
|
||||
"@typescript-eslint/parser": "6.21.0",
|
||||
"@typescript-eslint/eslint-plugin": "7.0.2",
|
||||
"@typescript-eslint/parser": "7.0.2",
|
||||
"aws-sdk-client-mock": "3.0.1",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.56.0",
|
||||
|
|
|
@ -16,10 +16,10 @@ import type { OnApplicationShutdown } from '@nestjs/common';
|
|||
|
||||
@Injectable()
|
||||
export class CacheService implements OnApplicationShutdown {
|
||||
public userByIdCache: MemoryKVCache<MiUser, MiUser | string>;
|
||||
public localUserByNativeTokenCache: MemoryKVCache<MiLocalUser | null, string | null>;
|
||||
public userByIdCache: MemoryKVCache<MiUser>;
|
||||
public localUserByNativeTokenCache: MemoryKVCache<MiLocalUser | null>;
|
||||
public localUserByIdCache: MemoryKVCache<MiLocalUser>;
|
||||
public uriPersonCache: MemoryKVCache<MiUser | null, string | null>;
|
||||
public uriPersonCache: MemoryKVCache<MiUser | null>;
|
||||
public userProfileCache: RedisKVCache<MiUserProfile>;
|
||||
public userMutingsCache: RedisKVCache<Set<string>>;
|
||||
public userBlockingCache: RedisKVCache<Set<string>>;
|
||||
|
@ -56,42 +56,10 @@ export class CacheService implements OnApplicationShutdown {
|
|||
) {
|
||||
//this.onMessage = this.onMessage.bind(this);
|
||||
|
||||
const localUserByIdCache = new MemoryKVCache<MiLocalUser>(1000 * 60 * 60 * 6 /* 6h */);
|
||||
this.localUserByIdCache = localUserByIdCache;
|
||||
|
||||
// ローカルユーザーならlocalUserByIdCacheにデータを追加し、こちらにはid(文字列)だけを追加する
|
||||
const userByIdCache = new MemoryKVCache<MiUser, MiUser | string>(1000 * 60 * 60 * 6 /* 6h */, {
|
||||
toMapConverter: user => {
|
||||
if (user.host === null) {
|
||||
localUserByIdCache.set(user.id, user as MiLocalUser);
|
||||
return user.id;
|
||||
}
|
||||
|
||||
return user;
|
||||
},
|
||||
fromMapConverter: userOrId => typeof userOrId === 'string' ? localUserByIdCache.get(userOrId) : userOrId,
|
||||
});
|
||||
this.userByIdCache = userByIdCache;
|
||||
|
||||
this.localUserByNativeTokenCache = new MemoryKVCache<MiLocalUser | null, string | null>(Infinity, {
|
||||
toMapConverter: user => {
|
||||
if (user === null) return null;
|
||||
|
||||
localUserByIdCache.set(user.id, user);
|
||||
return user.id;
|
||||
},
|
||||
fromMapConverter: id => id === null ? null : localUserByIdCache.get(id),
|
||||
});
|
||||
this.uriPersonCache = new MemoryKVCache<MiUser | null, string | null>(Infinity, {
|
||||
toMapConverter: user => {
|
||||
if (user === null) return null;
|
||||
if (user.isDeleted) return null;
|
||||
|
||||
userByIdCache.set(user.id, user);
|
||||
return user.id;
|
||||
},
|
||||
fromMapConverter: id => id === null ? null : userByIdCache.get(id),
|
||||
});
|
||||
this.userByIdCache = new MemoryKVCache<MiUser>(Infinity);
|
||||
this.localUserByNativeTokenCache = new MemoryKVCache<MiLocalUser | null>(Infinity);
|
||||
this.localUserByIdCache = new MemoryKVCache<MiLocalUser>(Infinity);
|
||||
this.uriPersonCache = new MemoryKVCache<MiUser | null>(Infinity);
|
||||
|
||||
this.userProfileCache = new RedisKVCache<MiUserProfile>(this.redisClient, 'userProfile', {
|
||||
lifetime: 1000 * 60 * 30, // 30m
|
||||
|
@ -165,14 +133,14 @@ export class CacheService implements OnApplicationShutdown {
|
|||
if (user == null) {
|
||||
this.userByIdCache.delete(body.id);
|
||||
for (const [k, v] of this.uriPersonCache.cache.entries()) {
|
||||
if (v.value === body.id) {
|
||||
if (v.value?.id === body.id) {
|
||||
this.uriPersonCache.delete(k);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.userByIdCache.set(user.id, user);
|
||||
for (const [k, v] of this.uriPersonCache.cache.entries()) {
|
||||
if (v.value === user.id) {
|
||||
if (v.value?.id === user.id) {
|
||||
this.uriPersonCache.set(k, user);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import isSvg from 'is-svg';
|
|||
import probeImageSize from 'probe-image-size';
|
||||
import { type predictionType } from 'nsfwjs';
|
||||
import sharp from 'sharp';
|
||||
import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
|
||||
import { encode } from 'blurhash';
|
||||
import { createTempDir } from '@/misc/create-temp.js';
|
||||
import { AiService } from '@/core/AiService.js';
|
||||
|
@ -122,7 +123,7 @@ export class FileInfoService {
|
|||
'image/avif',
|
||||
'image/svg+xml',
|
||||
].includes(type.mime)) {
|
||||
blurhash = await this.getBlurhash(path).catch(e => {
|
||||
blurhash = await this.getBlurhash(path, type.mime).catch(e => {
|
||||
warnings.push(`getBlurhash failed: ${e}`);
|
||||
return undefined;
|
||||
});
|
||||
|
@ -407,9 +408,9 @@ export class FileInfoService {
|
|||
* Calculate average color of image
|
||||
*/
|
||||
@bindThis
|
||||
private getBlurhash(path: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
sharp(path)
|
||||
private getBlurhash(path: string, type: string): Promise<string> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
(await sharpBmp(path, type))
|
||||
.raw()
|
||||
.ensureAlpha()
|
||||
.resize(64, 64, { fit: 'inside' })
|
||||
|
|
|
@ -30,12 +30,12 @@ import { RoleService } from '@/core/RoleService.js';
|
|||
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||
|
||||
const FALLBACK = '❤';
|
||||
const FALLBACK = '\u2764';
|
||||
const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
|
||||
|
||||
const legacies: Record<string, string> = {
|
||||
'like': '👍',
|
||||
'love': '❤', // ここに記述する場合は異体字セレクタを入れない
|
||||
'love': '\u2764', // ハート、異体字セレクタを入れない
|
||||
'laugh': '😆',
|
||||
'hmm': '🤔',
|
||||
'surprise': '😮',
|
||||
|
@ -120,7 +120,7 @@ export class ReactionService {
|
|||
let reaction = _reaction ?? FALLBACK;
|
||||
|
||||
if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) {
|
||||
reaction = '❤️';
|
||||
reaction = '\u2764';
|
||||
} else if (_reaction) {
|
||||
const custom = reaction.match(isCustomEmojiRegexp);
|
||||
if (custom) {
|
||||
|
@ -327,35 +327,36 @@ export class ReactionService {
|
|||
//#endregion
|
||||
}
|
||||
|
||||
/**
|
||||
* 文字列タイプのレガシーな形式のリアクションを現在の形式に変換しつつ、
|
||||
* データベース上には存在する「0個のリアクションがついている」という情報を削除する。
|
||||
*/
|
||||
@bindThis
|
||||
public convertLegacyReactions(reactions: Record<string, number>) {
|
||||
const _reactions = {} as Record<string, number>;
|
||||
public convertLegacyReactions(reactions: MiNote['reactions']): MiNote['reactions'] {
|
||||
return Object.entries(reactions)
|
||||
.filter(([, count]) => {
|
||||
// `ReactionService.prototype.delete`ではリアクション削除時に、
|
||||
// `MiNote['reactions']`のエントリの値をデクリメントしているが、
|
||||
// デクリメントしているだけなのでエントリ自体は0を値として持つ形で残り続ける。
|
||||
// そのため、この処理がなければ、「0個のリアクションがついている」ということになってしまう。
|
||||
return count > 0;
|
||||
})
|
||||
.map(([reaction, count]) => {
|
||||
// unchecked indexed access
|
||||
const convertedReaction = legacies[reaction] as string | undefined;
|
||||
|
||||
for (const reaction of Object.keys(reactions)) {
|
||||
if (reactions[reaction] <= 0) continue;
|
||||
const key = this.decodeReaction(convertedReaction ?? reaction).reaction;
|
||||
|
||||
if (Object.keys(legacies).includes(reaction)) {
|
||||
if (_reactions[legacies[reaction]]) {
|
||||
_reactions[legacies[reaction]] += reactions[reaction];
|
||||
} else {
|
||||
_reactions[legacies[reaction]] = reactions[reaction];
|
||||
}
|
||||
} else {
|
||||
if (_reactions[reaction]) {
|
||||
_reactions[reaction] += reactions[reaction];
|
||||
} else {
|
||||
_reactions[reaction] = reactions[reaction];
|
||||
}
|
||||
}
|
||||
}
|
||||
return [key, count] as const;
|
||||
})
|
||||
.reduce<MiNote['reactions']>((acc, [key, count]) => {
|
||||
// unchecked indexed access
|
||||
const prevCount = acc[key] as number | undefined;
|
||||
|
||||
const _reactions2 = {} as Record<string, number>;
|
||||
acc[key] = (prevCount ?? 0) + count;
|
||||
|
||||
for (const reaction of Object.keys(_reactions)) {
|
||||
_reactions2[this.decodeReaction(reaction).reaction] = _reactions[reaction];
|
||||
}
|
||||
|
||||
return _reactions2;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
verifyRegistrationResponse,
|
||||
} from '@simplewebauthn/server';
|
||||
import { AttestationFormat, isoCBOR } from '@simplewebauthn/server/helpers';
|
||||
import { tinyCbor } from '@simplewebauthn/server/esm/deps.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { UserSecurityKeysRepository } from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
|
@ -198,7 +199,7 @@ export class WebAuthnService {
|
|||
if (cert[0] === 0x04) { // 前の実装ではいつも 0x04 で始まっていた
|
||||
const halfLength = (cert.length - 1) / 2;
|
||||
|
||||
const cborMap = new Map<number, number | ArrayBufferLike>();
|
||||
const cborMap = new Map<number, tinyCbor.CBORType>();
|
||||
cborMap.set(1, 2); // kty, EC2
|
||||
cborMap.set(3, -7); // alg, ES256
|
||||
cborMap.set(-1, 1); // crv, P256
|
||||
|
|
|
@ -192,28 +192,14 @@ export class RedisSingleCache<T> {
|
|||
|
||||
// TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする?
|
||||
|
||||
function nothingToDo<T, V = T>(value: T): V {
|
||||
return value as unknown as V;
|
||||
}
|
||||
|
||||
export class MemoryKVCache<T, V = T> {
|
||||
public cache: Map<string, { date: number; value: V; }>;
|
||||
export class MemoryKVCache<T> {
|
||||
public cache: Map<string, { date: number; value: T; }>;
|
||||
private lifetime: number;
|
||||
private gcIntervalHandle: NodeJS.Timeout;
|
||||
private toMapConverter: (value: T) => V;
|
||||
private fromMapConverter: (cached: V) => T | undefined;
|
||||
|
||||
constructor(lifetime: MemoryKVCache<never>['lifetime'], options: {
|
||||
toMapConverter: (value: T) => V;
|
||||
fromMapConverter: (cached: V) => T | undefined;
|
||||
} = {
|
||||
toMapConverter: nothingToDo,
|
||||
fromMapConverter: nothingToDo,
|
||||
}) {
|
||||
constructor(lifetime: MemoryKVCache<never>['lifetime']) {
|
||||
this.cache = new Map();
|
||||
this.lifetime = lifetime;
|
||||
this.toMapConverter = options.toMapConverter;
|
||||
this.fromMapConverter = options.fromMapConverter;
|
||||
|
||||
this.gcIntervalHandle = setInterval(() => {
|
||||
this.gc();
|
||||
|
@ -224,7 +210,7 @@ export class MemoryKVCache<T, V = T> {
|
|||
public set(key: string, value: T): void {
|
||||
this.cache.set(key, {
|
||||
date: Date.now(),
|
||||
value: this.toMapConverter(value),
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -236,7 +222,7 @@ export class MemoryKVCache<T, V = T> {
|
|||
this.cache.delete(key);
|
||||
return undefined;
|
||||
}
|
||||
return this.fromMapConverter(cached.value);
|
||||
return cached.value;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -247,10 +233,9 @@ export class MemoryKVCache<T, V = T> {
|
|||
/**
|
||||
* キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
|
||||
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
|
||||
* fetcherの引数はcacheに保存されている値があれば渡されます
|
||||
*/
|
||||
@bindThis
|
||||
public async fetch(key: string, fetcher: (value: V | undefined) => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> {
|
||||
public async fetch(key: string, fetcher: () => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> {
|
||||
const cachedValue = this.get(key);
|
||||
if (cachedValue !== undefined) {
|
||||
if (validator) {
|
||||
|
@ -265,7 +250,7 @@ export class MemoryKVCache<T, V = T> {
|
|||
}
|
||||
|
||||
// Cache MISS
|
||||
const value = await fetcher(this.cache.get(key)?.value);
|
||||
const value = await fetcher();
|
||||
this.set(key, value);
|
||||
return value;
|
||||
}
|
||||
|
@ -273,10 +258,9 @@ export class MemoryKVCache<T, V = T> {
|
|||
/**
|
||||
* キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
|
||||
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
|
||||
* fetcherの引数はcacheに保存されている値があれば渡されます
|
||||
*/
|
||||
@bindThis
|
||||
public async fetchMaybe(key: string, fetcher: (value: V | undefined) => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
|
||||
public async fetchMaybe(key: string, fetcher: () => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
|
||||
const cachedValue = this.get(key);
|
||||
if (cachedValue !== undefined) {
|
||||
if (validator) {
|
||||
|
@ -291,7 +275,7 @@ export class MemoryKVCache<T, V = T> {
|
|||
}
|
||||
|
||||
// Cache MISS
|
||||
const value = await fetcher(this.cache.get(key)?.value);
|
||||
const value = await fetcher();
|
||||
if (value !== undefined) {
|
||||
this.set(key, value);
|
||||
}
|
||||
|
|
9
packages/backend/src/misc/fastify-hook-handlers.ts
Normal file
9
packages/backend/src/misc/fastify-hook-handlers.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import type { onRequestHookHandler } from 'fastify';
|
||||
|
||||
export const handleRequestRedirectToOmitSearch: onRequestHookHandler = (request, reply, done) => {
|
||||
const index = request.url.indexOf('?');
|
||||
if (~index) {
|
||||
reply.redirect(301, request.url.slice(0, index));
|
||||
}
|
||||
done();
|
||||
};
|
|
@ -39,7 +39,17 @@ import { packedRenoteMutingSchema } from '@/models/json-schema/renote-muting.js'
|
|||
import { packedUserListMembershipSchema, packedUserListSchema } from '@/models/json-schema/user-list.js';
|
||||
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
|
||||
import { packedSigninSchema } from '@/models/json-schema/signin.js';
|
||||
import { packedRoleLiteSchema, packedRoleSchema, packedRolePoliciesSchema } from '@/models/json-schema/role.js';
|
||||
import {
|
||||
packedRoleLiteSchema,
|
||||
packedRoleSchema,
|
||||
packedRolePoliciesSchema,
|
||||
packedRoleCondFormulaLogicsSchema,
|
||||
packedRoleCondFormulaValueNot,
|
||||
packedRoleCondFormulaValueIsLocalOrRemoteSchema,
|
||||
packedRoleCondFormulaValueCreatedSchema,
|
||||
packedRoleCondFormulaFollowersOrFollowingOrNotesSchema,
|
||||
packedRoleCondFormulaValueSchema,
|
||||
} from '@/models/json-schema/role.js';
|
||||
import { packedAdSchema } from '@/models/json-schema/ad.js';
|
||||
import { packedReversiGameLiteSchema, packedReversiGameDetailedSchema } from '@/models/json-schema/reversi-game.js';
|
||||
|
||||
|
@ -86,6 +96,12 @@ export const refs = {
|
|||
FlashLike: packedFlashLikeSchema,
|
||||
|
||||
Signin: packedSigninSchema,
|
||||
RoleCondFormulaLogics: packedRoleCondFormulaLogicsSchema,
|
||||
RoleCondFormulaValueNot: packedRoleCondFormulaValueNot,
|
||||
RoleCondFormulaValueIsLocalOrRemote: packedRoleCondFormulaValueIsLocalOrRemoteSchema,
|
||||
RoleCondFormulaValueCreated: packedRoleCondFormulaValueCreatedSchema,
|
||||
RoleCondFormulaFollowersOrFollowingOrNotes: packedRoleCondFormulaFollowersOrFollowingOrNotesSchema,
|
||||
RoleCondFormulaValue: packedRoleCondFormulaValueSchema,
|
||||
RoleLite: packedRoleLiteSchema,
|
||||
Role: packedRoleSchema,
|
||||
RolePolicies: packedRolePoliciesSchema,
|
||||
|
|
|
@ -48,7 +48,7 @@ export class MiBubbleGameRecord {
|
|||
@Column('jsonb', {
|
||||
default: [],
|
||||
})
|
||||
public logs: any[];
|
||||
public logs: number[][];
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
|
|
|
@ -258,6 +258,8 @@ export class MiMeta {
|
|||
})
|
||||
public turnstileSecretKey: string | null;
|
||||
|
||||
// chaptcha系を追加した際にはnodeinfoのレスポンスに追加するのを忘れないようにすること
|
||||
|
||||
@Column('enum', {
|
||||
enum: ['none', 'all', 'local', 'remote'],
|
||||
default: 'none',
|
||||
|
@ -362,9 +364,9 @@ export class MiMeta {
|
|||
@Column('varchar', {
|
||||
length: 1024,
|
||||
default: 'https://github.com/misskey-dev/misskey',
|
||||
nullable: false,
|
||||
nullable: true,
|
||||
})
|
||||
public repositoryUrl: string;
|
||||
public repositoryUrl: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
|
|
|
@ -69,7 +69,7 @@ type CondFormulaValueNotesMoreThanOrEq = {
|
|||
value: number;
|
||||
};
|
||||
|
||||
export type RoleCondFormulaValue =
|
||||
export type RoleCondFormulaValue = { id: string } & (
|
||||
CondFormulaValueAnd |
|
||||
CondFormulaValueOr |
|
||||
CondFormulaValueNot |
|
||||
|
@ -82,7 +82,8 @@ export type RoleCondFormulaValue =
|
|||
CondFormulaValueFollowingLessThanOrEq |
|
||||
CondFormulaValueFollowingMoreThanOrEq |
|
||||
CondFormulaValueNotesLessThanOrEq |
|
||||
CondFormulaValueNotesMoreThanOrEq;
|
||||
CondFormulaValueNotesMoreThanOrEq
|
||||
);
|
||||
|
||||
@Entity('role')
|
||||
export class MiRole {
|
||||
|
|
|
@ -47,12 +47,12 @@ export const packedReversiGameLiteSchema = {
|
|||
user1: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'User',
|
||||
ref: 'UserLite',
|
||||
},
|
||||
user2: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'User',
|
||||
ref: 'UserLite',
|
||||
},
|
||||
winnerId: {
|
||||
type: 'string',
|
||||
|
@ -62,7 +62,7 @@ export const packedReversiGameLiteSchema = {
|
|||
winner: {
|
||||
type: 'object',
|
||||
optional: false, nullable: true,
|
||||
ref: 'User',
|
||||
ref: 'UserLite',
|
||||
},
|
||||
surrenderedUserId: {
|
||||
type: 'string',
|
||||
|
@ -165,12 +165,12 @@ export const packedReversiGameDetailedSchema = {
|
|||
user1: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'User',
|
||||
ref: 'UserLite',
|
||||
},
|
||||
user2: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'User',
|
||||
ref: 'UserLite',
|
||||
},
|
||||
winnerId: {
|
||||
type: 'string',
|
||||
|
@ -180,7 +180,7 @@ export const packedReversiGameDetailedSchema = {
|
|||
winner: {
|
||||
type: 'object',
|
||||
optional: false, nullable: true,
|
||||
ref: 'User',
|
||||
ref: 'UserLite',
|
||||
},
|
||||
surrenderedUserId: {
|
||||
type: 'string',
|
||||
|
@ -226,6 +226,9 @@ export const packedReversiGameDetailedSchema = {
|
|||
items: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
},
|
||||
map: {
|
||||
|
|
|
@ -1,3 +1,129 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export const packedRoleCondFormulaLogicsSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string', optional: false,
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
enum: ['and', 'or'],
|
||||
},
|
||||
values: {
|
||||
type: 'array',
|
||||
nullable: false, optional: false,
|
||||
items: {
|
||||
ref: 'RoleCondFormulaValue',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const packedRoleCondFormulaValueNot = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string', optional: false,
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
enum: ['not'],
|
||||
},
|
||||
value: {
|
||||
type: 'object',
|
||||
optional: false,
|
||||
ref: 'RoleCondFormulaValue',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const packedRoleCondFormulaValueIsLocalOrRemoteSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string', optional: false,
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
enum: ['isLocal', 'isRemote'],
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const packedRoleCondFormulaValueCreatedSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string', optional: false,
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
enum: [
|
||||
'createdLessThan',
|
||||
'createdMoreThan',
|
||||
],
|
||||
},
|
||||
sec: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const packedRoleCondFormulaFollowersOrFollowingOrNotesSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string', optional: false,
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
enum: [
|
||||
'followersLessThanOrEq',
|
||||
'followersMoreThanOrEq',
|
||||
'followingLessThanOrEq',
|
||||
'followingMoreThanOrEq',
|
||||
'notesLessThanOrEq',
|
||||
'notesMoreThanOrEq',
|
||||
],
|
||||
},
|
||||
value: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const packedRoleCondFormulaValueSchema = {
|
||||
type: 'object',
|
||||
oneOf: [
|
||||
{
|
||||
ref: 'RoleCondFormulaLogics',
|
||||
},
|
||||
{
|
||||
ref: 'RoleCondFormulaValueNot',
|
||||
},
|
||||
{
|
||||
ref: 'RoleCondFormulaValueIsLocalOrRemote',
|
||||
},
|
||||
{
|
||||
ref: 'RoleCondFormulaValueCreated',
|
||||
},
|
||||
{
|
||||
ref: 'RoleCondFormulaFollowersOrFollowingOrNotes',
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
||||
export const packedRolePoliciesSchema = {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
|
@ -198,6 +324,7 @@ export const packedRoleSchema = {
|
|||
condFormula: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'RoleCondFormulaValue',
|
||||
},
|
||||
isPublic: {
|
||||
type: 'boolean',
|
||||
|
|
|
@ -3,16 +3,38 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
const notificationRecieveConfig = {
|
||||
export const notificationRecieveConfig = {
|
||||
type: 'object',
|
||||
nullable: false, optional: true,
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
enum: ['all', 'following', 'follower', 'mutualFollow', 'list', 'never'],
|
||||
oneOf: [
|
||||
{
|
||||
type: 'object',
|
||||
nullable: false,
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
nullable: false,
|
||||
enum: ['all', 'following', 'follower', 'mutualFollow', 'never'],
|
||||
},
|
||||
},
|
||||
required: ['type'],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
nullable: false,
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
nullable: false,
|
||||
enum: ['list'],
|
||||
},
|
||||
userListId: {
|
||||
type: 'string',
|
||||
format: 'misskey:id',
|
||||
},
|
||||
},
|
||||
required: ['type', 'userListId'],
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
||||
export const packedUserLiteSchema = {
|
||||
|
@ -538,15 +560,20 @@ export const packedMeDetailedOnlySchema = {
|
|||
type: 'object',
|
||||
nullable: false, optional: false,
|
||||
properties: {
|
||||
app: notificationRecieveConfig,
|
||||
quote: notificationRecieveConfig,
|
||||
reply: notificationRecieveConfig,
|
||||
follow: notificationRecieveConfig,
|
||||
renote: notificationRecieveConfig,
|
||||
mention: notificationRecieveConfig,
|
||||
reaction: notificationRecieveConfig,
|
||||
pollEnded: notificationRecieveConfig,
|
||||
receiveFollowRequest: notificationRecieveConfig,
|
||||
note: { optional: true, ...notificationRecieveConfig },
|
||||
follow: { optional: true, ...notificationRecieveConfig },
|
||||
mention: { optional: true, ...notificationRecieveConfig },
|
||||
reply: { optional: true, ...notificationRecieveConfig },
|
||||
renote: { optional: true, ...notificationRecieveConfig },
|
||||
quote: { optional: true, ...notificationRecieveConfig },
|
||||
reaction: { optional: true, ...notificationRecieveConfig },
|
||||
pollEnded: { optional: true, ...notificationRecieveConfig },
|
||||
receiveFollowRequest: { optional: true, ...notificationRecieveConfig },
|
||||
followRequestAccepted: { optional: true, ...notificationRecieveConfig },
|
||||
roleAssigned: { optional: true, ...notificationRecieveConfig },
|
||||
achievementEarned: { optional: true, ...notificationRecieveConfig },
|
||||
app: { optional: true, ...notificationRecieveConfig },
|
||||
test: { optional: true, ...notificationRecieveConfig },
|
||||
},
|
||||
},
|
||||
emailNotificationTypes: {
|
||||
|
|
|
@ -27,6 +27,7 @@ import { LoggerService } from '@/core/LoggerService.js';
|
|||
import { bindThis } from '@/decorators.js';
|
||||
import { isMimeImage } from '@/misc/is-mime-image.js';
|
||||
import { correctFilename } from '@/misc/correct-filename.js';
|
||||
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
|
||||
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
|
@ -67,20 +68,23 @@ export class FileServerService {
|
|||
done();
|
||||
});
|
||||
|
||||
fastify.get('/files/app-default.jpg', (request, reply) => {
|
||||
const file = fs.createReadStream(`${_dirname}/assets/dummy.png`);
|
||||
reply.header('Content-Type', 'image/jpeg');
|
||||
reply.header('Cache-Control', 'max-age=31536000, immutable');
|
||||
return reply.send(file);
|
||||
});
|
||||
fastify.register((fastify, options, done) => {
|
||||
fastify.addHook('onRequest', handleRequestRedirectToOmitSearch);
|
||||
fastify.get('/files/app-default.jpg', (request, reply) => {
|
||||
const file = fs.createReadStream(`${_dirname}/assets/dummy.png`);
|
||||
reply.header('Content-Type', 'image/jpeg');
|
||||
reply.header('Cache-Control', 'max-age=31536000, immutable');
|
||||
return reply.send(file);
|
||||
});
|
||||
|
||||
fastify.get<{ Params: { key: string; } }>('/files/:key', async (request, reply) => {
|
||||
return await this.sendDriveFile(request, reply)
|
||||
.catch(err => this.errorHandler(request, reply, err));
|
||||
});
|
||||
fastify.get<{ Params: { key: string; } }>('/files/:key/*', async (request, reply) => {
|
||||
return await this.sendDriveFile(request, reply)
|
||||
.catch(err => this.errorHandler(request, reply, err));
|
||||
fastify.get<{ Params: { key: string; } }>('/files/:key', async (request, reply) => {
|
||||
return await this.sendDriveFile(request, reply)
|
||||
.catch(err => this.errorHandler(request, reply, err));
|
||||
});
|
||||
fastify.get<{ Params: { key: string; } }>('/files/:key/*', async (request, reply) => {
|
||||
return await reply.redirect(301, `${this.config.url}/files/${request.params.key}`);
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
fastify.get<{
|
||||
|
|
|
@ -37,12 +37,12 @@ export class NodeinfoServerService {
|
|||
@bindThis
|
||||
public getLinks() {
|
||||
return [{
|
||||
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1',
|
||||
href: this.config.url + nodeinfo2_1path,
|
||||
}, {
|
||||
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
|
||||
href: this.config.url + nodeinfo2_0path,
|
||||
}];
|
||||
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1',
|
||||
href: this.config.url + nodeinfo2_1path
|
||||
}, {
|
||||
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
|
||||
href: this.config.url + nodeinfo2_0path,
|
||||
}];
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -117,6 +117,8 @@ export class NodeinfoServerService {
|
|||
emailRequiredForSignup: meta.emailRequiredForSignup,
|
||||
enableHcaptcha: meta.enableHcaptcha,
|
||||
enableRecaptcha: meta.enableRecaptcha,
|
||||
enableMcaptcha: meta.enableMcaptcha,
|
||||
enableTurnstile: meta.enableTurnstile,
|
||||
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
|
||||
enableEmail: meta.enableEmail,
|
||||
enableServiceWorker: meta.enableServiceWorker,
|
||||
|
|
|
@ -15,9 +15,6 @@ export const meta = {
|
|||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
kind: 'write:admin:delete-account',
|
||||
|
||||
res: {
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
@ -84,6 +84,24 @@ export const meta = {
|
|||
properties: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
width: {
|
||||
type: 'number',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
height: {
|
||||
type: 'number',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
orientation: {
|
||||
type: 'number',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
avgColor: {
|
||||
type: 'string',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
storedInternal: {
|
||||
type: 'boolean',
|
||||
|
|
|
@ -18,6 +18,18 @@ export const meta = {
|
|||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
additionalProperties: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
count: {
|
||||
type: 'number',
|
||||
},
|
||||
size: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
required: ['count', 'size'],
|
||||
},
|
||||
example: {
|
||||
migrations: {
|
||||
count: 66,
|
||||
|
|
|
@ -454,7 +454,7 @@ export const meta = {
|
|||
},
|
||||
repositoryUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
summalyProxy: {
|
||||
type: 'string',
|
||||
|
|
|
@ -17,7 +17,7 @@ export const meta = {
|
|||
tags: ['admin', 'role', 'users'],
|
||||
|
||||
requireCredential: false,
|
||||
requireAdmin: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:roles',
|
||||
|
||||
errors: {
|
||||
|
|
|
@ -10,6 +10,7 @@ import { DI } from '@/di-symbols.js';
|
|||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { RoleEntityService } from '@/core/entities/RoleEntityService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { notificationRecieveConfig } from '@/models/json-schema/user.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
@ -21,6 +22,157 @@ export const meta = {
|
|||
res: {
|
||||
type: 'object',
|
||||
nullable: false, optional: false,
|
||||
properties: {
|
||||
email: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
emailVerified: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
autoAcceptFollowed: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
noCrawle: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
preventAiLearning: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
alwaysMarkNsfw: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
autoSensitive: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
carefulBot: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
injectFeaturedNote: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
receiveAnnouncementEmail: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
mutedWords: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
mutedInstances: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
notificationRecieveConfig: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
note: { optional: true, ...notificationRecieveConfig },
|
||||
follow: { optional: true, ...notificationRecieveConfig },
|
||||
mention: { optional: true, ...notificationRecieveConfig },
|
||||
reply: { optional: true, ...notificationRecieveConfig },
|
||||
renote: { optional: true, ...notificationRecieveConfig },
|
||||
quote: { optional: true, ...notificationRecieveConfig },
|
||||
reaction: { optional: true, ...notificationRecieveConfig },
|
||||
pollEnded: { optional: true, ...notificationRecieveConfig },
|
||||
receiveFollowRequest: { optional: true, ...notificationRecieveConfig },
|
||||
followRequestAccepted: { optional: true, ...notificationRecieveConfig },
|
||||
roleAssigned: { optional: true, ...notificationRecieveConfig },
|
||||
achievementEarned: { optional: true, ...notificationRecieveConfig },
|
||||
app: { optional: true, ...notificationRecieveConfig },
|
||||
test: { optional: true, ...notificationRecieveConfig },
|
||||
},
|
||||
},
|
||||
isModerator: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isSilenced: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isSuspended: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isHibernated: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
lastActiveDate: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
moderationNote: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
signins: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
ref: 'Signin',
|
||||
},
|
||||
},
|
||||
policies: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'RolePolicies',
|
||||
},
|
||||
roles: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
ref: 'Role',
|
||||
},
|
||||
},
|
||||
roleAssigns: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
expiresAt: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
roleId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
@ -92,7 +244,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
isLimited: isLimited,
|
||||
isSuspended: user.isSuspended,
|
||||
isHibernated: user.isHibernated,
|
||||
lastActiveDate: user.lastActiveDate,
|
||||
lastActiveDate: user.lastActiveDate ? user.lastActiveDate.toISOString() : null,
|
||||
moderationNote: profile.moderationNote ?? '',
|
||||
signins,
|
||||
policies: await this.roleService.getUserPolicies(user.id),
|
||||
|
|
|
@ -437,7 +437,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
|
||||
if (ps.repositoryUrl !== undefined) {
|
||||
set.repositoryUrl = ps.repositoryUrl ?? 'https://github.com/misskey-dev/misskey';
|
||||
set.repositoryUrl = URL.canParse(ps.repositoryUrl!) ? ps.repositoryUrl : null;
|
||||
}
|
||||
|
||||
if (ps.feedbackUrl !== undefined) {
|
||||
|
|
|
@ -24,9 +24,19 @@ export const meta = {
|
|||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
id: { type: 'string', format: 'misskey:id' },
|
||||
score: { type: 'integer' },
|
||||
user: { ref: 'UserLite' },
|
||||
id: {
|
||||
type: 'string', format: 'misskey:id',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
score: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
optional: true, nullable: false,
|
||||
ref: 'UserLite',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -29,9 +29,6 @@ export const meta = {
|
|||
id: 'eb627bc7-574b-4a52-a860-3c3eae772b88',
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
@ -39,7 +36,15 @@ export const paramDef = {
|
|||
properties: {
|
||||
score: { type: 'integer', minimum: 0 },
|
||||
seed: { type: 'string', minLength: 1, maxLength: 1024 },
|
||||
logs: { type: 'array' },
|
||||
logs: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
},
|
||||
gameMode: { type: 'string' },
|
||||
gameVersion: { type: 'integer' },
|
||||
},
|
||||
|
|
|
@ -15,6 +15,19 @@ export const meta = {
|
|||
requireCredential: true,
|
||||
|
||||
secure: true,
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
backupCodes: {
|
||||
type: 'array',
|
||||
optional: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
@ -53,7 +53,7 @@ export const meta = {
|
|||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -21,21 +21,26 @@ export const meta = {
|
|||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false,
|
||||
format: 'misskey:id',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: true,
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
optional: false,
|
||||
format: 'date-time',
|
||||
},
|
||||
lastUsedAt: {
|
||||
type: 'string',
|
||||
optional: true,
|
||||
format: 'date-time',
|
||||
},
|
||||
permission: {
|
||||
type: 'array',
|
||||
optional: false,
|
||||
uniqueItems: true,
|
||||
items: {
|
||||
type: 'string',
|
||||
|
|
|
@ -23,16 +23,19 @@ export const meta = {
|
|||
id: {
|
||||
type: 'string',
|
||||
format: 'misskey:id',
|
||||
optional: false,
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false,
|
||||
},
|
||||
callbackUrl: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
permission: {
|
||||
type: 'array',
|
||||
optional: false,
|
||||
uniqueItems: true,
|
||||
items: {
|
||||
type: 'string',
|
||||
|
@ -40,6 +43,7 @@ export const meta = {
|
|||
},
|
||||
isAuthorized: {
|
||||
type: 'boolean',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -22,6 +22,15 @@ export const meta = {
|
|||
|
||||
res: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
optional: false,
|
||||
},
|
||||
value: {
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
@ -50,7 +59,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
|
||||
return {
|
||||
updatedAt: item.updatedAt,
|
||||
updatedAt: item.updatedAt.toISOString(),
|
||||
value: item.value,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -13,6 +13,9 @@ export const meta = {
|
|||
|
||||
res: {
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
|
|
@ -10,6 +10,13 @@ import { RegistryApiService } from '@/core/RegistryApiService.js';
|
|||
export const meta = {
|
||||
requireCredential: true,
|
||||
kind: 'read:account',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
@ -32,6 +32,7 @@ import { HttpRequestService } from '@/core/HttpRequestService.js';
|
|||
import type { Config } from '@/config.js';
|
||||
import { safeForSql } from '@/misc/safe-for-sql.js';
|
||||
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||
import { notificationRecieveConfig } from '@/models/json-schema/user.js';
|
||||
import { ApiLoggerService } from '../../ApiLoggerService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
|
@ -183,7 +184,26 @@ export const paramDef = {
|
|||
mutedInstances: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
notificationRecieveConfig: { type: 'object' },
|
||||
notificationRecieveConfig: {
|
||||
type: 'object',
|
||||
nullable: false,
|
||||
properties: {
|
||||
note: notificationRecieveConfig,
|
||||
follow: notificationRecieveConfig,
|
||||
mention: notificationRecieveConfig,
|
||||
reply: notificationRecieveConfig,
|
||||
renote: notificationRecieveConfig,
|
||||
quote: notificationRecieveConfig,
|
||||
reaction: notificationRecieveConfig,
|
||||
pollEnded: notificationRecieveConfig,
|
||||
receiveFollowRequest: notificationRecieveConfig,
|
||||
followRequestAccepted: notificationRecieveConfig,
|
||||
roleAssigned: notificationRecieveConfig,
|
||||
achievementEarned: notificationRecieveConfig,
|
||||
app: notificationRecieveConfig,
|
||||
test: notificationRecieveConfig,
|
||||
},
|
||||
},
|
||||
emailNotificationTypes: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
|
|
|
@ -109,7 +109,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
url: webhook.url,
|
||||
secret: webhook.secret,
|
||||
active: webhook.active,
|
||||
latestSentAt: webhook.latestSentAt?.toISOString(),
|
||||
latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null,
|
||||
latestStatus: webhook.latestStatus,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -73,7 +73,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
url: webhook.url,
|
||||
secret: webhook.secret,
|
||||
active: webhook.active,
|
||||
latestSentAt: webhook.latestSentAt?.toISOString(),
|
||||
latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null,
|
||||
latestStatus: webhook.latestStatus,
|
||||
}
|
||||
));
|
||||
|
|
|
@ -85,7 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
url: webhook.url,
|
||||
secret: webhook.secret,
|
||||
active: webhook.active,
|
||||
latestSentAt: webhook.latestSentAt?.toISOString(),
|
||||
latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null,
|
||||
latestStatus: webhook.latestStatus,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -69,12 +69,12 @@ export const meta = {
|
|||
},
|
||||
repositoryUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
optional: false, nullable: true,
|
||||
default: 'https://github.com/misskey-dev/misskey',
|
||||
},
|
||||
feedbackUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
optional: false, nullable: true,
|
||||
default: 'https://github.com/misskey-dev/misskey/issues/new',
|
||||
},
|
||||
defaultDarkTheme: {
|
||||
|
|
|
@ -14,9 +14,6 @@ export const meta = {
|
|||
|
||||
errors: {
|
||||
},
|
||||
|
||||
res: {
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
@ -30,6 +30,9 @@ export const meta = {
|
|||
},
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: true,
|
||||
ref: 'ReversiGameDetailed',
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
|
|
@ -19,20 +19,24 @@ export const meta = {
|
|||
id: {
|
||||
type: 'string',
|
||||
format: 'misskey:id',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
required: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
string: {
|
||||
type: 'string',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
default: {
|
||||
type: 'string',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
nullableDefault: {
|
||||
type: 'string',
|
||||
default: 'hello',
|
||||
nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -34,6 +34,7 @@ import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
|
|||
import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, MiMeta, NotesRepository, PagesRepository, ReversiGamesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { deepClone } from '@/misc/clone.js';
|
||||
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
@ -195,7 +196,7 @@ export class ClientServerService {
|
|||
// Authenticate
|
||||
fastify.addHook('onRequest', async (request, reply) => {
|
||||
// %71ueueとかでリクエストされたら困るため
|
||||
const url = decodeURI(request.routeOptions.url);
|
||||
const url = decodeURI(request.routeOptions.url ?? '');
|
||||
if (url === bullBoardPath || url.startsWith(bullBoardPath + '/')) {
|
||||
const token = request.cookies.token;
|
||||
if (token == null) {
|
||||
|
@ -266,11 +267,16 @@ export class ClientServerService {
|
|||
|
||||
//#region vite assets
|
||||
if (this.config.clientManifestExists) {
|
||||
fastify.register(fastifyStatic, {
|
||||
root: viteOut,
|
||||
prefix: '/vite/',
|
||||
maxAge: ms('30 days'),
|
||||
decorateReply: false,
|
||||
fastify.register((fastify, options, done) => {
|
||||
fastify.register(fastifyStatic, {
|
||||
root: viteOut,
|
||||
prefix: '/vite/',
|
||||
maxAge: ms('30 days'),
|
||||
immutable: true,
|
||||
decorateReply: false,
|
||||
});
|
||||
fastify.addHook('onRequest', handleRequestRedirectToOmitSearch);
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
const port = (process.env.VITE_PORT ?? '5173');
|
||||
|
|
|
@ -90,4 +90,45 @@ describe('ReactionService', () => {
|
|||
assert.strictEqual(await reactionService.normalize('unknown'), '❤');
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertLegacyReactions', () => {
|
||||
test('空の入力に対しては何もしない', () => {
|
||||
const input = {};
|
||||
assert.deepStrictEqual(reactionService.convertLegacyReactions(input), input);
|
||||
});
|
||||
|
||||
test('Unicode絵文字リアクションを変換してしまわない', () => {
|
||||
const input = { '👍': 1, '🍮': 2 };
|
||||
assert.deepStrictEqual(reactionService.convertLegacyReactions(input), input);
|
||||
});
|
||||
|
||||
test('カスタム絵文字リアクションを変換してしまわない', () => {
|
||||
const input = { ':like@.:': 1, ':pudding@example.tld:': 2 };
|
||||
assert.deepStrictEqual(reactionService.convertLegacyReactions(input), input);
|
||||
});
|
||||
|
||||
test('文字列によるレガシーなリアクションを変換する', () => {
|
||||
const input = { 'like': 1, 'pudding': 2 };
|
||||
const output = { '👍': 1, '🍮': 2 };
|
||||
assert.deepStrictEqual(reactionService.convertLegacyReactions(input), output);
|
||||
});
|
||||
|
||||
test('host部分が省略されたレガシーなカスタム絵文字リアクションを変換する', () => {
|
||||
const input = { ':custom_emoji:': 1 };
|
||||
const output = { ':custom_emoji@.:': 1 };
|
||||
assert.deepStrictEqual(reactionService.convertLegacyReactions(input), output);
|
||||
});
|
||||
|
||||
test('「0個のリアクション」情報を削除する', () => {
|
||||
const input = { 'angry': 0 };
|
||||
const output = {};
|
||||
assert.deepStrictEqual(reactionService.convertLegacyReactions(input), output);
|
||||
});
|
||||
|
||||
test('host部分の有無によりデコードすると同じ表記になるカスタム絵文字リアクションの個数情報を正しく足し合わせる', () => {
|
||||
const input = { ':custom_emoji:': 1, ':custom_emoji@.:': 2 };
|
||||
const output = { ':custom_emoji@.:': 3 };
|
||||
assert.deepStrictEqual(reactionService.convertLegacyReactions(input), output);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue