mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-06-07 22:04:10 +02:00
Merge branch 'develop' into copilot/add-user-mute-settings
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Misskey API</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script
|
||||
id="api-reference"
|
||||
data-url="/api.json"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.4 KiB |
@@ -9,7 +9,7 @@ window.onload = async () => {
|
||||
const account = JSON.parse(localStorage.getItem('account'));
|
||||
const i = account.token;
|
||||
|
||||
const api = (endpoint, data = {}) => {
|
||||
const _api = (endpoint, data = {}) => {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
// Append a credential
|
||||
if (i) data.i = i;
|
||||
|
||||
121
packages/backend/build.js
Normal file
121
packages/backend/build.js
Normal file
@@ -0,0 +1,121 @@
|
||||
import fs from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { build } from 'esbuild';
|
||||
import { swcPlugin } from 'esbuild-plugin-swc';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8'));
|
||||
|
||||
const resolveTsPathsPlugin = {
|
||||
name: 'resolve-ts-paths',
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^\.{1,2}\/.*\.js$/ }, (args) => {
|
||||
if (args.importer) {
|
||||
const absPath = join(args.resolveDir, args.path);
|
||||
const tsPath = absPath.slice(0, -3) + '.ts';
|
||||
if (fs.existsSync(tsPath)) return { path: tsPath };
|
||||
const tsxPath = absPath.slice(0, -3) + '.tsx';
|
||||
if (fs.existsSync(tsxPath)) return { path: tsxPath };
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const externalIpaddrPlugin = {
|
||||
name: 'external-ipaddr',
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^ipaddr\.js$/ }, (args) => {
|
||||
return { path: args.path, external: true };
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/** @type {import('esbuild').BuildOptions} */
|
||||
const options = {
|
||||
entryPoints: ['./src/boot/entry.ts'],
|
||||
minify: true,
|
||||
keepNames: true,
|
||||
bundle: true,
|
||||
outdir: './built/boot',
|
||||
target: 'node22',
|
||||
platform: 'node',
|
||||
format: 'esm',
|
||||
sourcemap: 'linked',
|
||||
packages: 'external',
|
||||
banner: {
|
||||
js: 'import { createRequire as topLevelCreateRequire } from "module";' +
|
||||
'import ___url___ from "url";' +
|
||||
'const require = topLevelCreateRequire(import.meta.url);' +
|
||||
'const __filename = ___url___.fileURLToPath(import.meta.url);' +
|
||||
'const __dirname = ___url___.fileURLToPath(new URL(".", import.meta.url));',
|
||||
},
|
||||
plugins: [
|
||||
externalIpaddrPlugin,
|
||||
resolveTsPathsPlugin,
|
||||
swcPlugin({
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: 'typescript',
|
||||
decorators: true,
|
||||
dynamicImport: true,
|
||||
},
|
||||
transform: {
|
||||
legacyDecorator: true,
|
||||
decoratorMetadata: true,
|
||||
},
|
||||
experimental: {
|
||||
keepImportAssertions: true,
|
||||
},
|
||||
baseUrl: join(_dirname, 'src'),
|
||||
paths: {
|
||||
'@/*': ['*'],
|
||||
},
|
||||
target: 'esnext',
|
||||
keepClassNames: true,
|
||||
},
|
||||
}),
|
||||
externalIpaddrPlugin,
|
||||
],
|
||||
// external: [
|
||||
// 'slacc-*',
|
||||
// 'class-transformer',
|
||||
// 'class-validator',
|
||||
// '@sentry/*',
|
||||
// '@nestjs/websockets/socket-module',
|
||||
// '@nestjs/microservices/microservices-module',
|
||||
// '@nestjs/microservices',
|
||||
// '@napi-rs/canvas-win32-x64-msvc',
|
||||
// 'mock-aws-s3',
|
||||
// 'aws-sdk',
|
||||
// 'nock',
|
||||
// 'sharp',
|
||||
// 'jsdom',
|
||||
// 're2',
|
||||
// '@napi-rs/canvas',
|
||||
// ],
|
||||
};
|
||||
|
||||
const args = process.argv.slice(2).map(arg => arg.toLowerCase());
|
||||
|
||||
if (!args.includes('--no-clean')) {
|
||||
fs.rmSync('./built', { recursive: true, force: true });
|
||||
}
|
||||
|
||||
await buildSrc();
|
||||
|
||||
async function buildSrc() {
|
||||
console.log(`[${_package.name}] start building...`);
|
||||
|
||||
await build(options)
|
||||
.then(() => {
|
||||
console.log(`[${_package.name}] build succeeded.`);
|
||||
})
|
||||
.catch((err) => {
|
||||
process.stderr.write(err.stderr || err.message || err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
console.log(`[${_package.name}] finish building.`);
|
||||
}
|
||||
@@ -25,7 +25,6 @@ export default [
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'import/order': ['warn', {
|
||||
groups: [
|
||||
'builtin',
|
||||
|
||||
20
packages/backend/migration/1767169026317-birthday-index.js
Normal file
20
packages/backend/migration/1767169026317-birthday-index.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class BirthdayIndex1767169026317 {
|
||||
name = 'BirthdayIndex1767169026317'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_de22cd2b445eee31ae51cdbe99"`);
|
||||
await queryRunner.query(`CREATE OR REPLACE FUNCTION get_birthday_date(birthday TEXT) RETURNS SMALLINT AS $$ BEGIN RETURN CAST((SUBSTR(birthday, 6, 2) || SUBSTR(birthday, 9, 2)) AS SMALLINT); END; $$ LANGUAGE plpgsql IMMUTABLE;`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_USERPROFILE_BIRTHDAY_DATE" ON "user_profile" (get_birthday_date("birthday"))`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`CREATE INDEX "IDX_de22cd2b445eee31ae51cdbe99" ON "user_profile" (substr("birthday", 6, 5))`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_USERPROFILE_BIRTHDAY_DATE"`);
|
||||
await queryRunner.query(`DROP FUNCTION IF EXISTS get_birthday_date(birthday TEXT)`);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { loadConfig } from './built/config.js';
|
||||
import { entities } from './built/postgres.js';
|
||||
import { loadConfig } from './src-js/config.js';
|
||||
import { entities } from './src-js/postgres.js';
|
||||
|
||||
const isConcurrentIndexMigrationEnabled = process.env.MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY === '1';
|
||||
|
||||
|
||||
@@ -12,17 +12,17 @@
|
||||
"start:test": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
||||
"migrate": "pnpm compile-config && pnpm typeorm migration:run -d ormconfig.js",
|
||||
"revert": "pnpm compile-config && pnpm typeorm migration:revert -d ormconfig.js",
|
||||
"cli": "pnpm compile-config && node ./built/boot/cli.js",
|
||||
"cli": "pnpm compile-config && node ./src-js/boot/cli.js",
|
||||
"check:connect": "pnpm compile-config && node ./scripts/check_connect.js",
|
||||
"compile-config": "node ./scripts/compile_config.js",
|
||||
"build": "swc src -d built -D --strip-leading-paths",
|
||||
"build": "swc src -d src-js -D --strip-leading-paths && node ./build.js",
|
||||
"build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc --strip-leading-paths",
|
||||
"watch:swc": "swc src -d built -D -w --strip-leading-paths",
|
||||
"build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
|
||||
"build:tsc": "tsgo -p tsconfig.json && tsc-alias -p tsconfig.json",
|
||||
"watch": "pnpm compile-config && node ./scripts/watch.mjs",
|
||||
"restart": "pnpm build && pnpm start",
|
||||
"dev": "pnpm compile-config && node ./scripts/dev.mjs",
|
||||
"typecheck": "tsc --noEmit && tsc -p test --noEmit && tsc -p test-federation --noEmit",
|
||||
"typecheck": "tsgo --noEmit && tsgo -p test --noEmit && tsgo -p test-federation --noEmit",
|
||||
"eslint": "eslint --quiet \"{src,test-federation}/**/*.ts\"",
|
||||
"lint": "pnpm typecheck && pnpm eslint",
|
||||
"jest": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.unit.cjs",
|
||||
@@ -41,20 +41,20 @@
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-android-arm64": "1.3.11",
|
||||
"@swc/core-darwin-arm64": "1.15.3",
|
||||
"@swc/core-darwin-x64": "1.15.3",
|
||||
"@swc/core-darwin-arm64": "1.15.21",
|
||||
"@swc/core-darwin-x64": "1.15.21",
|
||||
"@swc/core-freebsd-x64": "1.3.11",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.15.3",
|
||||
"@swc/core-linux-arm64-gnu": "1.15.3",
|
||||
"@swc/core-linux-arm64-musl": "1.15.3",
|
||||
"@swc/core-linux-x64-gnu": "1.15.3",
|
||||
"@swc/core-linux-x64-musl": "1.15.3",
|
||||
"@swc/core-win32-arm64-msvc": "1.15.3",
|
||||
"@swc/core-win32-ia32-msvc": "1.15.3",
|
||||
"@swc/core-win32-x64-msvc": "1.15.3",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.15.21",
|
||||
"@swc/core-linux-arm64-gnu": "1.15.21",
|
||||
"@swc/core-linux-arm64-musl": "1.15.21",
|
||||
"@swc/core-linux-x64-gnu": "1.15.21",
|
||||
"@swc/core-linux-x64-musl": "1.15.21",
|
||||
"@swc/core-win32-arm64-msvc": "1.15.21",
|
||||
"@swc/core-win32-ia32-msvc": "1.15.21",
|
||||
"@swc/core-win32-x64-msvc": "1.15.21",
|
||||
"@tensorflow/tfjs": "4.22.0",
|
||||
"@tensorflow/tfjs-node": "4.22.0",
|
||||
"bufferutil": "4.0.9",
|
||||
"bufferutil": "4.1.0",
|
||||
"slacc-android-arm-eabi": "0.0.10",
|
||||
"slacc-android-arm64": "0.0.10",
|
||||
"slacc-darwin-arm64": "0.0.10",
|
||||
@@ -68,43 +68,42 @@
|
||||
"slacc-linux-x64-musl": "0.0.10",
|
||||
"slacc-win32-arm64-msvc": "0.0.10",
|
||||
"slacc-win32-x64-msvc": "0.0.10",
|
||||
"utf-8-validate": "6.0.5"
|
||||
"utf-8-validate": "6.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.947.0",
|
||||
"@aws-sdk/lib-storage": "3.947.0",
|
||||
"@aws-sdk/client-s3": "3.1016.0",
|
||||
"@aws-sdk/lib-storage": "3.1016.0",
|
||||
"@discordapp/twemoji": "16.0.1",
|
||||
"@fastify/accepts": "5.0.4",
|
||||
"@fastify/cors": "11.1.0",
|
||||
"@fastify/express": "4.0.2",
|
||||
"@fastify/http-proxy": "11.4.1",
|
||||
"@fastify/multipart": "9.3.0",
|
||||
"@fastify/static": "8.3.0",
|
||||
"@kitajs/html": "4.2.11",
|
||||
"@fastify/cors": "11.2.0",
|
||||
"@fastify/express": "4.0.4",
|
||||
"@fastify/http-proxy": "11.4.2",
|
||||
"@fastify/multipart": "9.4.0",
|
||||
"@fastify/static": "9.0.0",
|
||||
"@kitajs/html": "4.2.13",
|
||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||
"@misskey-dev/summaly": "5.2.5",
|
||||
"@napi-rs/canvas": "0.1.84",
|
||||
"@nestjs/common": "11.1.9",
|
||||
"@nestjs/core": "11.1.9",
|
||||
"@nestjs/testing": "11.1.9",
|
||||
"@napi-rs/canvas": "0.1.97",
|
||||
"@nestjs/common": "11.1.17",
|
||||
"@nestjs/core": "11.1.17",
|
||||
"@nestjs/testing": "11.1.17",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@sentry/node": "10.29.0",
|
||||
"@sentry/profiling-node": "10.29.0",
|
||||
"@simplewebauthn/server": "13.2.2",
|
||||
"@sinonjs/fake-timers": "15.0.0",
|
||||
"@smithy/node-http-handler": "4.4.5",
|
||||
"@swc/cli": "0.7.9",
|
||||
"@swc/core": "1.15.3",
|
||||
"@sentry/node": "10.45.0",
|
||||
"@sentry/profiling-node": "10.45.0",
|
||||
"@simplewebauthn/server": "13.3.0",
|
||||
"@sinonjs/fake-timers": "15.1.1",
|
||||
"@smithy/node-http-handler": "4.5.0",
|
||||
"@swc/cli": "0.8.0",
|
||||
"@swc/core": "1.15.21",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
"@types/redis-info": "3.0.3",
|
||||
"accepts": "1.3.8",
|
||||
"ajv": "8.17.1",
|
||||
"ajv": "8.18.0",
|
||||
"archiver": "7.0.1",
|
||||
"async-mutex": "0.5.0",
|
||||
"bcryptjs": "3.0.3",
|
||||
"blurhash": "2.0.5",
|
||||
"body-parser": "2.2.1",
|
||||
"bullmq": "5.65.1",
|
||||
"body-parser": "2.2.2",
|
||||
"bullmq": "5.71.0",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"chalk": "5.6.2",
|
||||
"chalk-template": "1.1.2",
|
||||
@@ -113,76 +112,74 @@
|
||||
"content-disposition": "1.0.1",
|
||||
"date-fns": "4.1.0",
|
||||
"deep-email-validator": "0.1.21",
|
||||
"fastify": "5.6.2",
|
||||
"fastify": "5.8.4",
|
||||
"fastify-raw-body": "5.0.0",
|
||||
"feed": "5.1.0",
|
||||
"file-type": "21.1.1",
|
||||
"feed": "5.2.0",
|
||||
"file-type": "21.3.4",
|
||||
"fluent-ffmpeg": "2.1.3",
|
||||
"form-data": "4.0.5",
|
||||
"got": "14.6.5",
|
||||
"got": "14.6.6",
|
||||
"hpagent": "1.2.0",
|
||||
"http-link-header": "1.1.3",
|
||||
"i18n": "workspace:*",
|
||||
"ioredis": "5.8.2",
|
||||
"ioredis": "5.10.1",
|
||||
"ip-cidr": "4.0.2",
|
||||
"ipaddr.js": "2.3.0",
|
||||
"is-svg": "6.1.0",
|
||||
"json5": "2.2.3",
|
||||
"jsonld": "9.0.0",
|
||||
"juice": "11.0.3",
|
||||
"meilisearch": "0.54.0",
|
||||
"juice": "11.1.1",
|
||||
"meilisearch": "0.56.0",
|
||||
"mfm-js": "0.25.0",
|
||||
"mime-types": "3.0.2",
|
||||
"misskey-js": "workspace:*",
|
||||
"misskey-reversi": "workspace:*",
|
||||
"ms": "3.0.0-canary.202508261828",
|
||||
"nanoid": "5.1.6",
|
||||
"nanoid": "5.1.7",
|
||||
"nested-property": "4.0.0",
|
||||
"node-fetch": "3.3.2",
|
||||
"node-html-parser": "7.0.1",
|
||||
"nodemailer": "7.0.11",
|
||||
"nsfwjs": "4.2.0",
|
||||
"node-html-parser": "7.1.0",
|
||||
"nodemailer": "8.0.3",
|
||||
"nsfwjs": "4.3.0",
|
||||
"oauth2orize": "1.12.0",
|
||||
"oauth2orize-pkce": "0.1.2",
|
||||
"os-utils": "0.0.14",
|
||||
"otpauth": "9.4.1",
|
||||
"pg": "8.16.3",
|
||||
"pkce-challenge": "5.0.1",
|
||||
"otpauth": "9.5.0",
|
||||
"pg": "8.20.0",
|
||||
"pkce-challenge": "6.0.0",
|
||||
"probe-image-size": "7.2.3",
|
||||
"promise-limit": "2.7.0",
|
||||
"qrcode": "1.5.4",
|
||||
"random-seed": "0.3.0",
|
||||
"ratelimiter": "3.4.1",
|
||||
"re2": "1.22.3",
|
||||
"redis-info": "3.1.0",
|
||||
"re2": "1.23.3",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rename": "1.0.4",
|
||||
"rss-parser": "3.13.0",
|
||||
"rxjs": "7.8.2",
|
||||
"sanitize-html": "2.17.0",
|
||||
"sanitize-html": "2.17.2",
|
||||
"secure-json-parse": "4.1.0",
|
||||
"semver": "7.7.3",
|
||||
"semver": "7.7.4",
|
||||
"sharp": "0.33.5",
|
||||
"slacc": "0.0.10",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"systeminformation": "5.27.12",
|
||||
"systeminformation": "5.31.5",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.5",
|
||||
"tsc-alias": "1.8.16",
|
||||
"typeorm": "0.3.28",
|
||||
"typescript": "5.9.3",
|
||||
"ulid": "3.0.2",
|
||||
"vary": "1.1.2",
|
||||
"web-push": "3.6.7",
|
||||
"ws": "8.18.3",
|
||||
"ws": "8.20.0",
|
||||
"xev": "3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@kitajs/ts-html-plugin": "4.1.3",
|
||||
"@nestjs/platform-express": "11.1.9",
|
||||
"@sentry/vue": "10.29.0",
|
||||
"@kitajs/ts-html-plugin": "4.1.4",
|
||||
"@nestjs/platform-express": "11.1.17",
|
||||
"@sentry/vue": "10.45.0",
|
||||
"@simplewebauthn/types": "12.0.0",
|
||||
"@swc/jest": "0.2.39",
|
||||
"@types/accepts": "1.3.7",
|
||||
@@ -196,18 +193,18 @@
|
||||
"@types/jsonld": "1.5.15",
|
||||
"@types/mime-types": "3.0.1",
|
||||
"@types/ms": "2.1.0",
|
||||
"@types/node": "24.10.2",
|
||||
"@types/nodemailer": "7.0.4",
|
||||
"@types/node": "24.12.0",
|
||||
"@types/nodemailer": "7.0.11",
|
||||
"@types/oauth2orize": "1.11.5",
|
||||
"@types/oauth2orize-pkce": "0.1.2",
|
||||
"@types/pg": "8.15.6",
|
||||
"@types/pg": "8.20.0",
|
||||
"@types/qrcode": "1.5.6",
|
||||
"@types/random-seed": "0.3.5",
|
||||
"@types/ratelimiter": "3.4.6",
|
||||
"@types/rename": "1.0.7",
|
||||
"@types/sanitize-html": "2.16.0",
|
||||
"@types/sanitize-html": "2.16.1",
|
||||
"@types/semver": "7.7.1",
|
||||
"@types/simple-oauth2": "5.0.7",
|
||||
"@types/simple-oauth2": "5.0.8",
|
||||
"@types/sinonjs__fake-timers": "15.0.1",
|
||||
"@types/supertest": "6.0.3",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
@@ -215,21 +212,22 @@
|
||||
"@types/vary": "1.1.3",
|
||||
"@types/web-push": "3.6.4",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.49.0",
|
||||
"@typescript-eslint/parser": "8.49.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.2",
|
||||
"@typescript-eslint/parser": "8.57.2",
|
||||
"aws-sdk-client-mock": "4.1.0",
|
||||
"cbor": "10.0.11",
|
||||
"cbor": "10.0.12",
|
||||
"cross-env": "10.1.0",
|
||||
"esbuild-plugin-swc": "1.0.1",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"execa": "9.6.1",
|
||||
"fkill": "10.0.1",
|
||||
"fkill": "10.0.3",
|
||||
"jest": "29.7.0",
|
||||
"jest-mock": "29.7.0",
|
||||
"js-yaml": "4.1.1",
|
||||
"nodemon": "3.1.11",
|
||||
"pid-port": "2.0.0",
|
||||
"nodemon": "3.1.14",
|
||||
"pid-port": "2.1.0",
|
||||
"simple-oauth2": "5.1.0",
|
||||
"supertest": "7.1.4",
|
||||
"vite": "7.2.7"
|
||||
"supertest": "7.2.2",
|
||||
"vite": "8.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
|
||||
import Redis from 'ioredis';
|
||||
import { loadConfig } from '../built/config.js';
|
||||
import { createPostgresDataSource } from '../built/postgres.js';
|
||||
import { loadConfig } from '../src-js/config.js';
|
||||
import { createPostgresDataSource } from '../src-js/postgres.js';
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
@@ -16,26 +16,22 @@ async function connectToPostgres() {
|
||||
}
|
||||
|
||||
async function connectToRedis(redisOptions) {
|
||||
return await new Promise(async (resolve, reject) => {
|
||||
const redis = new Redis({
|
||||
let redis;
|
||||
try {
|
||||
redis = new Redis({
|
||||
...redisOptions,
|
||||
lazyConnect: true,
|
||||
reconnectOnError: false,
|
||||
showFriendlyErrorStack: true,
|
||||
});
|
||||
redis.on('error', e => reject(e));
|
||||
|
||||
try {
|
||||
await redis.connect();
|
||||
resolve();
|
||||
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
|
||||
} finally {
|
||||
redis.disconnect(false);
|
||||
}
|
||||
});
|
||||
await Promise.race([
|
||||
new Promise((_, reject) => redis.on('error', e => reject(e))),
|
||||
redis.connect(),
|
||||
]);
|
||||
} finally {
|
||||
redis.disconnect(false);
|
||||
}
|
||||
}
|
||||
|
||||
// If not all of these are defined, the default one gets reused.
|
||||
@@ -50,7 +46,7 @@ const promises = Array
|
||||
]))
|
||||
.map(connectToRedis)
|
||||
.concat([
|
||||
connectToPostgres()
|
||||
connectToPostgres(),
|
||||
]);
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { writeFileSync, existsSync } from 'node:fs';
|
||||
import { execa } from 'execa';
|
||||
import { writeFileSync, existsSync } from "node:fs";
|
||||
|
||||
async function main() {
|
||||
if (!process.argv.includes('--no-build')) {
|
||||
@@ -19,10 +19,10 @@ async function main() {
|
||||
}
|
||||
|
||||
/** @type {import('../src/config.js')} */
|
||||
const { loadConfig } = await import('../built/config.js');
|
||||
const { loadConfig } = await import('../src-js/config.js');
|
||||
|
||||
/** @type {import('../src/server/api/openapi/gen-spec.js')} */
|
||||
const { genOpenapiSpec } = await import('../built/server/api/openapi/gen-spec.js');
|
||||
const { genOpenapiSpec } = await import('../src-js/server/api/openapi/gen-spec.js');
|
||||
|
||||
const config = loadConfig();
|
||||
const spec = genOpenapiSpec(config, true);
|
||||
|
||||
@@ -14,24 +14,56 @@ import { fork } from 'node:child_process';
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
import * as http from 'node:http';
|
||||
import * as fs from 'node:fs/promises';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const SAMPLE_COUNT = 3; // Number of samples to measure
|
||||
const STARTUP_TIMEOUT = 120000; // 120 seconds timeout for server startup
|
||||
const MEMORY_SETTLE_TIME = 10000; // Wait 10 seconds after startup for memory to settle
|
||||
|
||||
async function measureMemory() {
|
||||
const startTime = Date.now();
|
||||
const keys = {
|
||||
VmPeak: 0,
|
||||
VmSize: 0,
|
||||
VmHWM: 0,
|
||||
VmRSS: 0,
|
||||
VmData: 0,
|
||||
VmStk: 0,
|
||||
VmExe: 0,
|
||||
VmLib: 0,
|
||||
VmPTE: 0,
|
||||
VmSwap: 0,
|
||||
};
|
||||
|
||||
async function getMemoryUsage(pid) {
|
||||
const status = await fs.readFile(`/proc/${pid}/status`, 'utf-8');
|
||||
|
||||
const result = {};
|
||||
for (const key of Object.keys(keys)) {
|
||||
const match = status.match(new RegExp(`${key}:\\s+(\\d+)\\s+kB`));
|
||||
if (match) {
|
||||
result[key] = parseInt(match[1], 10);
|
||||
} else {
|
||||
throw new Error(`Failed to parse ${key} from /proc/${pid}/status`);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function measureMemory() {
|
||||
// Start the Misskey backend server using fork to enable IPC
|
||||
const serverProcess = fork(join(__dirname, '../built/boot/entry.js'), [], {
|
||||
const serverProcess = fork(join(__dirname, '../built/boot/entry.js'), ['expose-gc'], {
|
||||
cwd: join(__dirname, '..'),
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: 'test',
|
||||
NODE_ENV: 'production',
|
||||
MK_DISABLE_CLUSTERING: '1',
|
||||
},
|
||||
stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
|
||||
execArgv: [...process.execArgv, '--expose-gc'],
|
||||
});
|
||||
|
||||
let serverReady = false;
|
||||
@@ -57,6 +89,40 @@ async function measureMemory() {
|
||||
process.stderr.write(`[server error] ${err}\n`);
|
||||
});
|
||||
|
||||
async function triggerGc() {
|
||||
const ok = new Promise((resolve) => {
|
||||
serverProcess.once('message', (message) => {
|
||||
if (message === 'gc ok') resolve();
|
||||
});
|
||||
});
|
||||
|
||||
serverProcess.send('gc');
|
||||
|
||||
await ok;
|
||||
|
||||
await setTimeout(1000);
|
||||
}
|
||||
|
||||
function createRequest() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = http.request({
|
||||
host: 'localhost',
|
||||
port: 61812,
|
||||
path: '/api/meta',
|
||||
method: 'POST',
|
||||
}, (res) => {
|
||||
res.on('data', () => { });
|
||||
res.on('end', () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
req.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for server to be ready or timeout
|
||||
const startupStartTime = Date.now();
|
||||
while (!serverReady) {
|
||||
@@ -73,46 +139,23 @@ async function measureMemory() {
|
||||
// Wait for memory to settle
|
||||
await setTimeout(MEMORY_SETTLE_TIME);
|
||||
|
||||
// Get memory usage from the server process via /proc
|
||||
const pid = serverProcess.pid;
|
||||
let memoryInfo;
|
||||
|
||||
try {
|
||||
const fs = await import('node:fs/promises');
|
||||
const beforeGc = await getMemoryUsage(pid);
|
||||
|
||||
// Read /proc/[pid]/status for detailed memory info
|
||||
const status = await fs.readFile(`/proc/${pid}/status`, 'utf-8');
|
||||
const vmRssMatch = status.match(/VmRSS:\s+(\d+)\s+kB/);
|
||||
const vmDataMatch = status.match(/VmData:\s+(\d+)\s+kB/);
|
||||
const vmSizeMatch = status.match(/VmSize:\s+(\d+)\s+kB/);
|
||||
await triggerGc();
|
||||
|
||||
memoryInfo = {
|
||||
rss: vmRssMatch ? parseInt(vmRssMatch[1], 10) * 1024 : null,
|
||||
heapUsed: vmDataMatch ? parseInt(vmDataMatch[1], 10) * 1024 : null,
|
||||
vmSize: vmSizeMatch ? parseInt(vmSizeMatch[1], 10) * 1024 : null,
|
||||
};
|
||||
} catch (err) {
|
||||
// Fallback: use ps command
|
||||
process.stderr.write(`Warning: Could not read /proc/${pid}/status: ${err}\n`);
|
||||
const afterGc = await getMemoryUsage(pid);
|
||||
|
||||
const { execSync } = await import('node:child_process');
|
||||
try {
|
||||
const ps = execSync(`ps -o rss= -p ${pid}`, { encoding: 'utf-8' });
|
||||
const rssKb = parseInt(ps.trim(), 10);
|
||||
memoryInfo = {
|
||||
rss: rssKb * 1024,
|
||||
heapUsed: null,
|
||||
vmSize: null,
|
||||
};
|
||||
} catch {
|
||||
memoryInfo = {
|
||||
rss: null,
|
||||
heapUsed: null,
|
||||
vmSize: null,
|
||||
error: 'Could not measure memory',
|
||||
};
|
||||
}
|
||||
}
|
||||
// create some http requests to simulate load
|
||||
const REQUEST_COUNT = 10;
|
||||
await Promise.all(
|
||||
Array.from({ length: REQUEST_COUNT }).map(() => createRequest()),
|
||||
);
|
||||
|
||||
await triggerGc();
|
||||
|
||||
const afterRequest = await getMemoryUsage(pid);
|
||||
|
||||
// Stop the server
|
||||
serverProcess.kill('SIGTERM');
|
||||
@@ -135,15 +178,51 @@ async function measureMemory() {
|
||||
|
||||
const result = {
|
||||
timestamp: new Date().toISOString(),
|
||||
startupTimeMs: startupTime,
|
||||
memory: memoryInfo,
|
||||
beforeGc,
|
||||
afterGc,
|
||||
afterRequest,
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// 直列の方が時間的に分散されて正確そうだから直列でやる
|
||||
const results = [];
|
||||
for (let i = 0; i < SAMPLE_COUNT; i++) {
|
||||
const res = await measureMemory();
|
||||
results.push(res);
|
||||
}
|
||||
|
||||
// Calculate averages
|
||||
const beforeGc = structuredClone(keys);
|
||||
const afterGc = structuredClone(keys);
|
||||
const afterRequest = structuredClone(keys);
|
||||
for (const res of results) {
|
||||
for (const key of Object.keys(keys)) {
|
||||
beforeGc[key] += res.beforeGc[key];
|
||||
afterGc[key] += res.afterGc[key];
|
||||
afterRequest[key] += res.afterRequest[key];
|
||||
}
|
||||
}
|
||||
for (const key of Object.keys(keys)) {
|
||||
beforeGc[key] = Math.round(beforeGc[key] / SAMPLE_COUNT);
|
||||
afterGc[key] = Math.round(afterGc[key] / SAMPLE_COUNT);
|
||||
afterRequest[key] = Math.round(afterRequest[key] / SAMPLE_COUNT);
|
||||
}
|
||||
|
||||
const result = {
|
||||
timestamp: new Date().toISOString(),
|
||||
beforeGc,
|
||||
afterGc,
|
||||
afterRequest,
|
||||
};
|
||||
|
||||
// Output as JSON to stdout
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
}
|
||||
|
||||
measureMemory().catch((err) => {
|
||||
main().catch((err) => {
|
||||
console.error(JSON.stringify({
|
||||
error: err.message,
|
||||
timestamp: new Date().toISOString(),
|
||||
|
||||
@@ -21,7 +21,7 @@ import { execa } from 'execa';
|
||||
});
|
||||
}, 3000);
|
||||
|
||||
execa('tsc', ['-w', '-p', 'tsconfig.json'], {
|
||||
execa('tsgo', ['-w', '-p', 'tsconfig.json'], {
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
|
||||
@@ -86,6 +86,18 @@ if (!envOption.disableClustering) {
|
||||
ev.mount();
|
||||
}
|
||||
|
||||
process.on('message', msg => {
|
||||
if (msg === 'gc') {
|
||||
if (global.gc != null) {
|
||||
logger.info('Manual GC triggered');
|
||||
global.gc();
|
||||
if (process.send != null) process.send('gc ok');
|
||||
} else {
|
||||
logger.warn('Manual GC requested but gc is not available. Start the process with --expose-gc to enable this feature.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
readyRef.value = true;
|
||||
|
||||
// ユニットテスト時にMisskeyが子プロセスで起動された時のため
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
*/
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname } from 'node:path';
|
||||
import * as os from 'node:os';
|
||||
import cluster from 'node:cluster';
|
||||
import chalk from 'chalk';
|
||||
@@ -17,20 +15,15 @@ import { showMachineInfo } from '@/misc/show-machine-info.js';
|
||||
import { envOption } from '@/env.js';
|
||||
import { jobQueue, server } from './common.js';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
|
||||
const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, 'utf-8'));
|
||||
|
||||
const logger = new Logger('core', 'cyan');
|
||||
const bootLogger = logger.createSubLogger('boot', 'magenta');
|
||||
|
||||
const themeColor = chalk.hex('#86b300');
|
||||
|
||||
function greet() {
|
||||
function greet(props: { version: string }) {
|
||||
if (!envOption.quiet) {
|
||||
//#region Misskey logo
|
||||
const v = `v${meta.version}`;
|
||||
const v = `v${props.version}`;
|
||||
console.log(themeColor(' _____ _ _ '));
|
||||
console.log(themeColor(' | |_|___ ___| |_ ___ _ _ '));
|
||||
console.log(themeColor(' | | | | |_ -|_ -| \'_| -_| | |'));
|
||||
@@ -46,7 +39,7 @@ function greet() {
|
||||
}
|
||||
|
||||
bootLogger.info('Welcome to Misskey!');
|
||||
bootLogger.info(`Misskey v${meta.version}`, null, true);
|
||||
bootLogger.info(`Misskey v${props.version}`, null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,15 +50,15 @@ export async function masterMain() {
|
||||
|
||||
// initialize app
|
||||
try {
|
||||
greet();
|
||||
config = loadConfigBoot();
|
||||
greet({ version: config.version });
|
||||
showEnvironment();
|
||||
await showMachineInfo(bootLogger);
|
||||
showNodejsVersion();
|
||||
config = loadConfigBoot();
|
||||
//await connectDb();
|
||||
if (config.pidFile) fs.writeFileSync(config.pidFile, process.pid.toString());
|
||||
} catch (e) {
|
||||
bootLogger.error('Fatal error occurred during initialization', null, true);
|
||||
bootLogger.error('Fatal error occurred during initialization: ' + e, null, true);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import { type FastifyServerOptions } from 'fastify';
|
||||
import type * as Sentry from '@sentry/node';
|
||||
import type * as SentryVue from '@sentry/vue';
|
||||
import type { RedisOptions } from 'ioredis';
|
||||
import type { ManifestChunk } from 'vite';
|
||||
|
||||
type RedisOptionsSource = Partial<RedisOptions> & {
|
||||
host: string;
|
||||
@@ -30,6 +29,7 @@ type Source = {
|
||||
socket?: string;
|
||||
trustProxy?: FastifyServerOptions['trustProxy'];
|
||||
chmodSocket?: string;
|
||||
enableIpRateLimit?: boolean;
|
||||
disableHsts?: boolean;
|
||||
db: {
|
||||
host: string;
|
||||
@@ -120,8 +120,9 @@ export type Config = {
|
||||
url: string;
|
||||
port: number;
|
||||
socket: string | undefined;
|
||||
trustProxy: FastifyServerOptions['trustProxy'];
|
||||
trustProxy: NonNullable<FastifyServerOptions['trustProxy']>;
|
||||
chmodSocket: string | undefined;
|
||||
enableIpRateLimit: boolean;
|
||||
disableHsts: boolean | undefined;
|
||||
db: {
|
||||
host: string;
|
||||
@@ -187,9 +188,7 @@ export type Config = {
|
||||
authUrl: string;
|
||||
driveUrl: string;
|
||||
userAgent: string;
|
||||
frontendEntry: ManifestChunk;
|
||||
frontendManifestExists: boolean;
|
||||
frontendEmbedEntry: ManifestChunk;
|
||||
frontendEmbedManifestExists: boolean;
|
||||
mediaProxy: string;
|
||||
externalMediaProxyEnabled: boolean;
|
||||
@@ -217,25 +216,37 @@ export type FulltextSearchProvider = 'sqlLike' | 'sqlPgroonga' | 'meilisearch';
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
|
||||
const compiledConfigFilePathForTest = resolve(_dirname, '../../../built/._config_.json');
|
||||
/** Path of repository root directory */
|
||||
let rootDir = _dirname;
|
||||
// 見つかるまで上に遡る
|
||||
while (!fs.existsSync(resolve(rootDir, 'packages'))) {
|
||||
const parentDir = dirname(rootDir);
|
||||
if (parentDir === rootDir) {
|
||||
throw new Error('Cannot find root directory');
|
||||
}
|
||||
rootDir = parentDir;
|
||||
}
|
||||
|
||||
export const compiledConfigFilePath = fs.existsSync(compiledConfigFilePathForTest) ? compiledConfigFilePathForTest : resolve(_dirname, '../../../built/.config.json');
|
||||
/** Path of configuration directory */
|
||||
const configDir = resolve(rootDir, '.config');
|
||||
/** Path of built directory */
|
||||
const projectBuiltDir = resolve(rootDir, 'built');
|
||||
|
||||
const compiledConfigFilePathForTest = resolve(projectBuiltDir, '._config_.json');
|
||||
|
||||
export const compiledConfigFilePath = fs.existsSync(compiledConfigFilePathForTest)
|
||||
? compiledConfigFilePathForTest
|
||||
: resolve(projectBuiltDir, '.config.json');
|
||||
|
||||
export function loadConfig(): Config {
|
||||
if (!fs.existsSync(compiledConfigFilePath)) {
|
||||
throw new Error('Compiled configuration file not found. Try running \'pnpm compile-config\'.');
|
||||
}
|
||||
|
||||
const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/meta.json`, 'utf-8'));
|
||||
const meta = JSON.parse(fs.readFileSync(resolve(projectBuiltDir, 'meta.json'), 'utf-8'));
|
||||
|
||||
const frontendManifestExists = fs.existsSync(_dirname + '/../../../built/_frontend_vite_/manifest.json');
|
||||
const frontendEmbedManifestExists = fs.existsSync(_dirname + '/../../../built/_frontend_embed_vite_/manifest.json');
|
||||
const frontendManifest = frontendManifestExists ?
|
||||
JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_vite_/manifest.json`, 'utf-8'))
|
||||
: { 'src/_boot_.ts': { file: null } };
|
||||
const frontendEmbedManifest = frontendEmbedManifestExists ?
|
||||
JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_embed_vite_/manifest.json`, 'utf-8'))
|
||||
: { 'src/boot.ts': { file: null } };
|
||||
const frontendManifestExists = fs.existsSync(resolve(projectBuiltDir, '_frontend_vite_/manifest.json'));
|
||||
const frontendEmbedManifestExists = fs.existsSync(resolve(projectBuiltDir, '_frontend_embed_vite_/manifest.json'));
|
||||
|
||||
const config = JSON.parse(fs.readFileSync(compiledConfigFilePath, 'utf-8')) as Source;
|
||||
|
||||
@@ -263,9 +274,17 @@ export function loadConfig(): Config {
|
||||
url: url.origin,
|
||||
port: config.port ?? parseInt(process.env.PORT ?? '', 10),
|
||||
socket: config.socket,
|
||||
trustProxy: config.trustProxy,
|
||||
trustProxy: config.trustProxy ?? [
|
||||
'10.0.0.0/8',
|
||||
'172.16.0.0/12',
|
||||
'192.168.0.0/16',
|
||||
'127.0.0.1/32',
|
||||
'::1/128',
|
||||
'fc00::/7',
|
||||
],
|
||||
chmodSocket: config.chmodSocket,
|
||||
disableHsts: config.disableHsts,
|
||||
enableIpRateLimit: config.enableIpRateLimit ?? true,
|
||||
host,
|
||||
hostname,
|
||||
scheme,
|
||||
@@ -309,9 +328,7 @@ export function loadConfig(): Config {
|
||||
config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator
|
||||
: null,
|
||||
userAgent: `Misskey/${version} (${config.url})`,
|
||||
frontendEntry: frontendManifest['src/_boot_.ts'],
|
||||
frontendManifestExists: frontendManifestExists,
|
||||
frontendEmbedEntry: frontendEmbedManifest['src/boot.ts'],
|
||||
frontendEmbedManifestExists: frontendEmbedManifestExists,
|
||||
perChannelMaxNoteCacheCount: config.perChannelMaxNoteCacheCount ?? 1000,
|
||||
perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500,
|
||||
@@ -324,7 +341,7 @@ export function loadConfig(): Config {
|
||||
function tryCreateUrl(url: string) {
|
||||
try {
|
||||
return new URL(url);
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
throw new Error(`url="${url}" is not a valid URL.`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export class AccountMoveService {
|
||||
*/
|
||||
@bindThis
|
||||
public async moveFromLocal(src: MiLocalUser, dst: MiLocalUser | MiRemoteUser): Promise<unknown> {
|
||||
const srcUri = this.userEntityService.getUserUri(src);
|
||||
const _srcUri = this.userEntityService.getUserUri(src);
|
||||
const dstUri = this.userEntityService.getUserUri(dst);
|
||||
|
||||
// add movedToUri to indicate that the user has moved
|
||||
|
||||
@@ -7,11 +7,10 @@ import * as fs from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname } from 'node:path';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import si from 'systeminformation';
|
||||
import { Mutex } from 'async-mutex';
|
||||
import fetch from 'node-fetch';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { NSFWJS, PredictionType } from 'nsfwjs';
|
||||
import type { NSFWJS, PredictionType } from 'nsfwjs/core';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
@@ -44,7 +43,7 @@ export class AiService {
|
||||
tf.env().global.fetch = fetch;
|
||||
|
||||
if (this.model == null) {
|
||||
const nsfw = await import('nsfwjs');
|
||||
const nsfw = await import('nsfwjs/core');
|
||||
await this.modelLoadMutex.runExclusive(async () => {
|
||||
if (this.model == null) {
|
||||
this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 });
|
||||
@@ -84,6 +83,7 @@ export class AiService {
|
||||
|
||||
@bindThis
|
||||
private async getCpuFlags(): Promise<string[]> {
|
||||
const si = await import('systeminformation');
|
||||
const str = await si.cpuFlags();
|
||||
return str.split(/\s+/);
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ export class AnnouncementService {
|
||||
announcementId: announcementId,
|
||||
userId: user.id,
|
||||
});
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ export class AvatarDecorationService implements OnApplicationShutdown {
|
||||
const obj = JSON.parse(data);
|
||||
|
||||
if (obj.channel === 'internal') {
|
||||
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||
const { type, body: _ } = obj.message as GlobalEvents['internal']['payload'];
|
||||
switch (type) {
|
||||
case 'avatarDecorationCreated':
|
||||
case 'avatarDecorationUpdated':
|
||||
|
||||
@@ -141,7 +141,7 @@ import { ApLoggerService } from './activitypub/ApLoggerService.js';
|
||||
import { ApMfmService } from './activitypub/ApMfmService.js';
|
||||
import { ApRendererService } from './activitypub/ApRendererService.js';
|
||||
import { ApRequestService } from './activitypub/ApRequestService.js';
|
||||
import { ApResolverService } from './activitypub/ApResolverService.js';
|
||||
import { ApResolverService, Resolver } from './activitypub/ApResolverService.js';
|
||||
import { JsonLdService } from './activitypub/JsonLdService.js';
|
||||
import { RemoteLoggerService } from './RemoteLoggerService.js';
|
||||
import { RemoteUserResolveService } from './RemoteUserResolveService.js';
|
||||
@@ -447,6 +447,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
ApRendererService,
|
||||
ApRequestService,
|
||||
ApResolverService,
|
||||
Resolver,
|
||||
JsonLdService,
|
||||
RemoteLoggerService,
|
||||
RemoteUserResolveService,
|
||||
@@ -745,6 +746,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
ApRendererService,
|
||||
ApRequestService,
|
||||
ApResolverService,
|
||||
Resolver,
|
||||
JsonLdService,
|
||||
RemoteLoggerService,
|
||||
RemoteUserResolveService,
|
||||
|
||||
@@ -366,7 +366,7 @@ export class EmailService {
|
||||
valid: true,
|
||||
reason: null,
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (_) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'network',
|
||||
|
||||
@@ -484,25 +484,13 @@ export class FileInfoService {
|
||||
* Calculate blurhash string of image
|
||||
*/
|
||||
@bindThis
|
||||
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' })
|
||||
.toBuffer((err, buffer, info) => {
|
||||
if (err) return reject(err);
|
||||
|
||||
let hash;
|
||||
|
||||
try {
|
||||
hash = blurhash.encode(new Uint8ClampedArray(buffer), info.width, info.height, 5, 5);
|
||||
} catch (e) {
|
||||
return reject(e);
|
||||
}
|
||||
|
||||
resolve(hash);
|
||||
});
|
||||
});
|
||||
private async getBlurhash(path: string, type: string): Promise<string> {
|
||||
const sharp = await sharpBmp(path, type);
|
||||
const { data: buffer, info } = await sharp
|
||||
.raw()
|
||||
.ensureAlpha()
|
||||
.resize(64, 64, { fit: 'inside' })
|
||||
.toBuffer({ resolveWithObject: true });
|
||||
return blurhash.encode(new Uint8ClampedArray(buffer), info.width, info.height, 5, 5);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,11 +38,7 @@ export interface BroadcastTypes {
|
||||
emojis: Packed<'EmojiDetailed'>[];
|
||||
};
|
||||
emojiDeleted: {
|
||||
emojis: {
|
||||
id?: string;
|
||||
name: string;
|
||||
[other: string]: any;
|
||||
}[];
|
||||
emojis: Packed<'EmojiDetailed'>[];
|
||||
};
|
||||
announcementCreated: {
|
||||
announcement: Packed<'Announcement'>;
|
||||
@@ -133,6 +129,9 @@ export interface NoteEventTypes {
|
||||
type NoteStreamEventTypes = {
|
||||
[key in keyof NoteEventTypes]: {
|
||||
id: MiNote['id'];
|
||||
userId: MiNote['userId'];
|
||||
visibility: MiNote['visibility'];
|
||||
visibleUserIds: MiNote['visibleUserIds'];
|
||||
body: NoteEventTypes[key];
|
||||
};
|
||||
};
|
||||
@@ -382,9 +381,12 @@ export class GlobalEventService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishNoteStream<K extends keyof NoteEventTypes>(noteId: MiNote['id'], type: K, value?: NoteEventTypes[K]): void {
|
||||
this.publish(`noteStream:${noteId}`, type, {
|
||||
id: noteId,
|
||||
public publishNoteStream<K extends keyof NoteEventTypes>(note: MiNote, type: K, value?: NoteEventTypes[K]): void {
|
||||
this.publish(`noteStream:${note.id}`, type, {
|
||||
id: note.id,
|
||||
userId: note.userId,
|
||||
visibility: note.visibility,
|
||||
visibleUserIds: note.visibleUserIds,
|
||||
body: value,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ export class MfmService {
|
||||
try {
|
||||
const date = new Date(parseInt(text, 10) * 1000);
|
||||
return `<time datetime="${escapeHtml(date.toISOString())}">${escapeHtml(date.toISOString())}</time>`;
|
||||
} catch (err) {
|
||||
} catch (_) {
|
||||
return fnDefault(node);
|
||||
}
|
||||
}
|
||||
@@ -376,7 +376,7 @@ export class MfmService {
|
||||
try {
|
||||
const url = new URL(node.props.url);
|
||||
return `<a href="${escapeHtml(url.href)}">${toHtml(node.children)}</a>`;
|
||||
} catch (err) {
|
||||
} catch (_) {
|
||||
return `[${toHtml(node.children)}](${escapeHtml(node.props.url)})`;
|
||||
}
|
||||
},
|
||||
@@ -390,7 +390,7 @@ export class MfmService {
|
||||
try {
|
||||
const url = new URL(href);
|
||||
return `<a href="${escapeHtml(url.href)}" class="u-url mention">${escapeHtml(acct)}</a>`;
|
||||
} catch (err) {
|
||||
} catch (_) {
|
||||
return escapeHtml(acct);
|
||||
}
|
||||
},
|
||||
@@ -419,7 +419,7 @@ export class MfmService {
|
||||
try {
|
||||
const url = new URL(node.props.url);
|
||||
return `<a href="${escapeHtml(url.href)}">${escapeHtml(node.props.url)}</a>`;
|
||||
} catch (err) {
|
||||
} catch (_) {
|
||||
return escapeHtml(node.props.url);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -68,7 +68,7 @@ export class NoteDeleteService {
|
||||
}
|
||||
|
||||
if (!quiet) {
|
||||
this.globalEventService.publishNoteStream(note.id, 'deleted', {
|
||||
this.globalEventService.publishNoteStream(note, 'deleted', {
|
||||
deletedAt: deletedAt,
|
||||
});
|
||||
|
||||
|
||||
@@ -187,9 +187,9 @@ export class NoteDraftService {
|
||||
}
|
||||
|
||||
//#region visibleUsers
|
||||
let visibleUsers: MiUser[] = [];
|
||||
let _visibleUsers: MiUser[] = [];
|
||||
if (data.visibleUserIds != null && data.visibleUserIds.length > 0) {
|
||||
visibleUsers = await this.usersRepository.findBy({
|
||||
_visibleUsers = await this.usersRepository.findBy({
|
||||
id: In(data.visibleUserIds),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ export class PollService {
|
||||
const index = choice + 1; // In SQL, array index is 1 based
|
||||
await this.pollsRepository.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE "noteId" = '${poll.noteId}'`);
|
||||
|
||||
this.globalEventService.publishNoteStream(note.id, 'pollVoted', {
|
||||
this.globalEventService.publishNoteStream(note, 'pollVoted', {
|
||||
choice: choice,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
@@ -259,7 +259,7 @@ export class QueryService {
|
||||
|
||||
@bindThis
|
||||
public generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: { id: MiUser['id'] } | null): void {
|
||||
// This code must always be synchronized with the checks in Notes.isVisibleForMe.
|
||||
// This code must always be synchronized with the checks in NoteEntityService.isVisibleForMe and Stream abstract class Channel.isNoteVisibleForMe.
|
||||
if (me == null) {
|
||||
q.andWhere(new Brackets(qb => {
|
||||
qb
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { MetricsTime, type JobType } from 'bullmq';
|
||||
import { parse as parseRedisInfo } from 'redis-info';
|
||||
import type { IActivity } from '@/core/activitypub/type.js';
|
||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import type { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js';
|
||||
@@ -86,6 +85,19 @@ const REPEATABLE_SYSTEM_JOB_DEF = [{
|
||||
pattern: '0 4 * * *',
|
||||
}];
|
||||
|
||||
function parseRedisInfo(infoText: string): Record<string, string> {
|
||||
const fields = infoText
|
||||
.split('\n')
|
||||
.filter(line => line.length > 0 && !line.startsWith('#'))
|
||||
.map(line => line.trim().split(':'));
|
||||
|
||||
const result: Record<string, string> = {};
|
||||
for (const [key, value] of fields) {
|
||||
result[key] = value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class QueueService {
|
||||
constructor(
|
||||
@@ -890,7 +902,7 @@ export class QueueService {
|
||||
},
|
||||
db: {
|
||||
version: db.redis_version,
|
||||
mode: db.redis_mode,
|
||||
mode: db.redis_mode as 'cluster' | 'standalone' | 'sentinel',
|
||||
runId: db.run_id,
|
||||
processId: db.process_id,
|
||||
port: parseInt(db.tcp_port),
|
||||
|
||||
@@ -244,7 +244,7 @@ export class ReactionService {
|
||||
},
|
||||
});
|
||||
|
||||
this.globalEventService.publishNoteStream(note.id, 'reacted', {
|
||||
this.globalEventService.publishNoteStream(note, 'reacted', {
|
||||
reaction: decodedReaction.reaction,
|
||||
emoji: customEmoji != null ? {
|
||||
name: customEmoji.host ? `${customEmoji.name}@${customEmoji.host}` : `${customEmoji.name}@.`,
|
||||
@@ -318,7 +318,7 @@ export class ReactionService {
|
||||
.execute();
|
||||
}
|
||||
|
||||
this.globalEventService.publishNoteStream(note.id, 'unreacted', {
|
||||
this.globalEventService.publishNoteStream(note, 'unreacted', {
|
||||
reaction: this.decodeReaction(exist.reaction).reaction,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
@@ -314,7 +314,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_) {
|
||||
// TODO: log error
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -190,8 +190,7 @@ export class SearchService {
|
||||
return this.searchNoteByMeiliSearch(q, me, opts, pagination);
|
||||
}
|
||||
default: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const typeCheck: never = this.provider;
|
||||
const _: never = this.provider;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ export class UserSuspendService {
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await this.postSuspend(user).catch(e => {});
|
||||
await this.unFollowAll(user).catch(e => {});
|
||||
await this.postSuspend(user).catch(_ => {});
|
||||
await this.unFollowAll(user).catch(_ => {});
|
||||
})();
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export class UserSuspendService {
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await this.postUnsuspend(user).catch(e => {});
|
||||
await this.postUnsuspend(user).catch(_ => {});
|
||||
})();
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ export class UtilityService {
|
||||
try {
|
||||
// TODO: RE2インスタンスをキャッシュ
|
||||
return new RE2(regexp[1], regexp[2]).test(text);
|
||||
} catch (err) {
|
||||
} catch (_) {
|
||||
// This should never happen due to input sanitisation.
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ export class ApInboxService {
|
||||
if (isCollectionOrOrderedCollection(activity)) {
|
||||
const results = [] as [string, string | void][];
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
resolver ??= this.apResolverService.createResolver();
|
||||
resolver ??= await this.apResolverService.createResolver();
|
||||
|
||||
const items = toArray(isCollection(activity) ? activity.items : activity.orderedItems);
|
||||
if (items.length >= resolver.getRecursionLimit()) {
|
||||
@@ -221,7 +221,7 @@ export class ApInboxService {
|
||||
this.logger.info(`Accept: ${uri}`);
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
resolver ??= this.apResolverService.createResolver();
|
||||
resolver ??= await this.apResolverService.createResolver();
|
||||
|
||||
const object = await resolver.resolve(activity.object).catch(err => {
|
||||
this.logger.error(`Resolution failed: ${err}`);
|
||||
@@ -284,7 +284,7 @@ export class ApInboxService {
|
||||
this.logger.info(`Announce: ${uri}`);
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
resolver ??= this.apResolverService.createResolver();
|
||||
resolver ??= await this.apResolverService.createResolver();
|
||||
|
||||
if (!activity.object) return 'skip: activity has no object property';
|
||||
const targetUri = getApId(activity.object);
|
||||
@@ -406,7 +406,7 @@ export class ApInboxService {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
resolver ??= this.apResolverService.createResolver();
|
||||
resolver ??= await this.apResolverService.createResolver();
|
||||
|
||||
const object = await resolver.resolve(activity.object).catch(e => {
|
||||
this.logger.error(`Resolution failed: ${e}`);
|
||||
@@ -575,7 +575,7 @@ export class ApInboxService {
|
||||
this.logger.info(`Reject: ${uri}`);
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
resolver ??= this.apResolverService.createResolver();
|
||||
resolver ??= await this.apResolverService.createResolver();
|
||||
|
||||
const object = await resolver.resolve(activity.object).catch(e => {
|
||||
this.logger.error(`Resolution failed: ${e}`);
|
||||
@@ -642,7 +642,7 @@ export class ApInboxService {
|
||||
this.logger.info(`Undo: ${uri}`);
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
resolver ??= this.apResolverService.createResolver();
|
||||
resolver ??= await this.apResolverService.createResolver();
|
||||
|
||||
const object = await resolver.resolve(activity.object).catch(e => {
|
||||
this.logger.error(`Resolution failed: ${e}`);
|
||||
@@ -774,7 +774,7 @@ export class ApInboxService {
|
||||
this.logger.debug('Update');
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
resolver ??= this.apResolverService.createResolver();
|
||||
resolver ??= await this.apResolverService.createResolver();
|
||||
|
||||
const object = await resolver.resolve(activity.object).catch(e => {
|
||||
this.logger.error(`Resolution failed: ${e}`);
|
||||
|
||||
@@ -515,7 +515,7 @@ export class ApRendererService {
|
||||
const restPart = maybeUrl.slice(match[0].length);
|
||||
|
||||
return `<a href="${urlPartParsed.href}" rel="me nofollow noopener" target="_blank">${urlPart}</a>${restPart}`;
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
return maybeUrl;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -81,7 +81,7 @@ export class ApRequestCreator {
|
||||
}, args.additionalHeaders),
|
||||
};
|
||||
|
||||
const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']);
|
||||
const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host']);
|
||||
|
||||
return {
|
||||
request,
|
||||
@@ -226,7 +226,7 @@ export class ApRequestService {
|
||||
return await this.signedGet(href, user, allowSoftfail, false);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
// something went wrong parsing the HTML, ignore the whole thing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,17 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import { IsNull, Not } from 'typeorm';
|
||||
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
|
||||
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js';
|
||||
import type {
|
||||
FollowRequestsRepository,
|
||||
MiMeta,
|
||||
NoteReactionsRepository,
|
||||
NotesRepository,
|
||||
PollsRepository,
|
||||
UsersRepository
|
||||
} from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
@@ -16,26 +23,43 @@ import { LoggerService } from '@/core/LoggerService.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import type { ICollection, IObject, IOrderedCollection } from './type.js';
|
||||
import { isCollectionOrOrderedCollection } from './type.js';
|
||||
import { ApDbResolverService } from './ApDbResolverService.js';
|
||||
import { ApRendererService } from './ApRendererService.js';
|
||||
import { ApRequestService } from './ApRequestService.js';
|
||||
import { FetchAllowSoftFailMask } from './misc/check-against-url.js';
|
||||
import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
|
||||
@Injectable({ scope: Scope.TRANSIENT })
|
||||
export class Resolver {
|
||||
private history: Set<string>;
|
||||
private user?: MiLocalUser;
|
||||
private logger: Logger;
|
||||
private recursionLimit = 256;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.meta)
|
||||
private meta: MiMeta,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
@Inject(DI.pollsRepository)
|
||||
private pollsRepository: PollsRepository,
|
||||
|
||||
@Inject(DI.noteReactionsRepository)
|
||||
private noteReactionsRepository: NoteReactionsRepository,
|
||||
|
||||
@Inject(DI.followRequestsRepository)
|
||||
private followRequestsRepository: FollowRequestsRepository,
|
||||
|
||||
private utilityService: UtilityService,
|
||||
private systemAccountService: SystemAccountService,
|
||||
private apRequestService: ApRequestService,
|
||||
@@ -43,7 +67,6 @@ export class Resolver {
|
||||
private apRendererService: ApRendererService,
|
||||
private apDbResolverService: ApDbResolverService,
|
||||
private loggerService: LoggerService,
|
||||
private recursionLimit = 256,
|
||||
) {
|
||||
this.history = new Set();
|
||||
this.logger = this.loggerService.getLogger('ap-resolve');
|
||||
@@ -180,54 +203,12 @@ export class Resolver {
|
||||
@Injectable()
|
||||
export class ApResolverService {
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.meta)
|
||||
private meta: MiMeta,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
@Inject(DI.pollsRepository)
|
||||
private pollsRepository: PollsRepository,
|
||||
|
||||
@Inject(DI.noteReactionsRepository)
|
||||
private noteReactionsRepository: NoteReactionsRepository,
|
||||
|
||||
@Inject(DI.followRequestsRepository)
|
||||
private followRequestsRepository: FollowRequestsRepository,
|
||||
|
||||
private utilityService: UtilityService,
|
||||
private systemAccountService: SystemAccountService,
|
||||
private apRequestService: ApRequestService,
|
||||
private httpRequestService: HttpRequestService,
|
||||
private apRendererService: ApRendererService,
|
||||
private apDbResolverService: ApDbResolverService,
|
||||
private loggerService: LoggerService,
|
||||
private moduleRef: ModuleRef,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public createResolver(): Resolver {
|
||||
return new Resolver(
|
||||
this.config,
|
||||
this.meta,
|
||||
this.usersRepository,
|
||||
this.notesRepository,
|
||||
this.pollsRepository,
|
||||
this.noteReactionsRepository,
|
||||
this.followRequestsRepository,
|
||||
this.utilityService,
|
||||
this.systemAccountService,
|
||||
this.apRequestService,
|
||||
this.httpRequestService,
|
||||
this.apRendererService,
|
||||
this.apDbResolverService,
|
||||
this.loggerService,
|
||||
);
|
||||
public async createResolver(): Promise<Resolver> {
|
||||
return await this.moduleRef.create(Resolver);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export class ApImageService {
|
||||
throw new Error('actor has been suspended');
|
||||
}
|
||||
|
||||
const image = await this.apResolverService.createResolver().resolve(value);
|
||||
const image = await (await this.apResolverService.createResolver()).resolve(value);
|
||||
|
||||
if (!isDocument(image)) return null;
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ export class ApNoteService {
|
||||
@bindThis
|
||||
public async createNote(value: string | IObject, actor?: MiRemoteUser, resolver?: Resolver, silent = false): Promise<MiNote | null> {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
if (resolver == null) resolver = this.apResolverService.createResolver();
|
||||
if (resolver == null) resolver = await this.apResolverService.createResolver();
|
||||
|
||||
const object = await resolver.resolve(value);
|
||||
|
||||
|
||||
@@ -310,7 +310,7 @@ export class ApPersonService implements OnModuleInit {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
if (resolver == null) resolver = this.apResolverService.createResolver();
|
||||
if (resolver == null) resolver = await this.apResolverService.createResolver();
|
||||
|
||||
const object = await resolver.resolve(uri);
|
||||
if (object.id == null) throw new Error('invalid object.id: ' + object.id);
|
||||
@@ -500,7 +500,7 @@ export class ApPersonService implements OnModuleInit {
|
||||
//#endregion
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
if (resolver == null) resolver = this.apResolverService.createResolver();
|
||||
if (resolver == null) resolver = await this.apResolverService.createResolver();
|
||||
|
||||
const object = hint ?? await resolver.resolve(uri);
|
||||
|
||||
@@ -678,7 +678,7 @@ export class ApPersonService implements OnModuleInit {
|
||||
|
||||
// リモートサーバーからフェッチしてきて登録
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
if (resolver == null) resolver = this.apResolverService.createResolver();
|
||||
if (resolver == null) resolver = await this.apResolverService.createResolver();
|
||||
return await this.createPerson(uri, resolver);
|
||||
}
|
||||
|
||||
@@ -707,7 +707,7 @@ export class ApPersonService implements OnModuleInit {
|
||||
|
||||
this.logger.info(`Updating the featured: ${user.uri}`);
|
||||
|
||||
const _resolver = resolver ?? this.apResolverService.createResolver();
|
||||
const _resolver = resolver ?? await this.apResolverService.createResolver();
|
||||
|
||||
// Resolve to (Ordered)Collection Object
|
||||
const collection = await _resolver.resolveCollection(user.featured);
|
||||
|
||||
@@ -45,7 +45,7 @@ export class ApQuestionService {
|
||||
@bindThis
|
||||
public async extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise<IPoll> {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
if (resolver == null) resolver = this.apResolverService.createResolver();
|
||||
if (resolver == null) resolver = await this.apResolverService.createResolver();
|
||||
|
||||
const question = await resolver.resolve(source);
|
||||
if (!isQuestion(question)) throw new Error('invalid type');
|
||||
@@ -91,7 +91,7 @@ export class ApQuestionService {
|
||||
|
||||
// resolve new Question object
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
if (resolver == null) resolver = this.apResolverService.createResolver();
|
||||
if (resolver == null) resolver = await this.apResolverService.createResolver();
|
||||
const question = await resolver.resolve(value);
|
||||
this.logger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`);
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ export class ChatEntityService {
|
||||
const reactions: { reaction: string; }[] = [];
|
||||
|
||||
for (const record of message.reactions) {
|
||||
const [userId, reaction] = record.split('/');
|
||||
const [, reaction] = record.split('/');
|
||||
reactions.push({
|
||||
reaction,
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ import { deepClone } from '@/misc/clone.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isMimeImage } from '@/misc/is-mime-image.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { uniqueByKey } from '@/misc/unique-by-key.js';
|
||||
import { UtilityService } from '../UtilityService.js';
|
||||
import { VideoProcessingService } from '../VideoProcessingService.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
@@ -226,6 +227,7 @@ export class DriveFileEntityService {
|
||||
options?: PackOptions,
|
||||
hint?: {
|
||||
packedUser?: Packed<'UserLite'>
|
||||
packedFolder?: Packed<'DriveFolder'>
|
||||
},
|
||||
): Promise<Packed<'DriveFile'> | null> {
|
||||
const opts = Object.assign({
|
||||
@@ -250,9 +252,9 @@ export class DriveFileEntityService {
|
||||
thumbnailUrl: this.getThumbnailUrl(file),
|
||||
comment: file.comment,
|
||||
folderId: file.folderId,
|
||||
folder: opts.detail && file.folderId ? this.driveFolderEntityService.pack(file.folderId, {
|
||||
folder: opts.detail && file.folderId ? (hint?.packedFolder ?? this.driveFolderEntityService.pack(file.folderId, {
|
||||
detail: true,
|
||||
}) : null,
|
||||
})) : null,
|
||||
userId: file.userId,
|
||||
user: (opts.withUser && file.userId) ? hint?.packedUser ?? this.userEntityService.pack(file.userId) : null,
|
||||
});
|
||||
@@ -263,10 +265,41 @@ export class DriveFileEntityService {
|
||||
files: MiDriveFile[],
|
||||
options?: PackOptions,
|
||||
): Promise<Packed<'DriveFile'>[]> {
|
||||
const _user = files.map(({ user, userId }) => user ?? userId).filter(x => x != null);
|
||||
const _userMap = await this.userEntityService.packMany(_user)
|
||||
.then(users => new Map(users.map(user => [user.id, user])));
|
||||
const items = await Promise.all(files.map(f => this.packNullable(f, options, f.userId ? { packedUser: _userMap.get(f.userId) } : {})));
|
||||
// -- ユーザ情報の事前取得 --
|
||||
|
||||
let userMap: Map<string, Packed<'UserLite'>> | null = null;
|
||||
if (options?.withUser) {
|
||||
const users = files
|
||||
.map(({ user, userId }) => user ?? userId)
|
||||
.filter(x => x != null);
|
||||
|
||||
const uniqueUsers = uniqueByKey(users, (user) => typeof user === 'string' ? user : user.id);
|
||||
const packedUsers = await this.userEntityService.packMany(uniqueUsers);
|
||||
userMap = new Map(packedUsers.map(user => [user.id, user]));
|
||||
}
|
||||
|
||||
// -- フォルダ情報の事前取得 --
|
||||
|
||||
let folderMap: Map<string, Packed<'DriveFolder'>> | null = null;
|
||||
if (options?.detail) {
|
||||
const folders = files
|
||||
.map(({ folder, folderId }) => folder ?? folderId)
|
||||
.filter(x => x != null);
|
||||
|
||||
const uniqueFolders = uniqueByKey(folders, (folder) => typeof folder === 'string' ? folder : folder.id);
|
||||
const packedFolders = await this.driveFolderEntityService.packMany(uniqueFolders, { detail: true });
|
||||
folderMap = new Map(packedFolders.map(folder => [folder.id, folder]));
|
||||
}
|
||||
|
||||
const items = await Promise.all(files.map(f => this.packNullable(
|
||||
f,
|
||||
options,
|
||||
{
|
||||
packedUser: f.userId ? userMap?.get(f.userId) : undefined,
|
||||
packedFolder: f.folderId ? folderMap?.get(f.folderId) : undefined,
|
||||
},
|
||||
)));
|
||||
|
||||
return items.filter(x => x != null);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ import type { } from '@/models/Blocking.js';
|
||||
import type { MiDriveFolder } from '@/models/DriveFolder.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { In } from 'typeorm';
|
||||
import { uniqueByKey } from '@/misc/unique-by-key.js';
|
||||
import { splitIdAndObjects } from '@/misc/split-id-and-objects.js';
|
||||
|
||||
@Injectable()
|
||||
export class DriveFolderEntityService {
|
||||
@@ -32,12 +35,20 @@ export class DriveFolderEntityService {
|
||||
options?: {
|
||||
detail: boolean
|
||||
},
|
||||
hint?: {
|
||||
folderMap?: Map<string, MiDriveFolder>;
|
||||
foldersCountMap?: Map<string, number> | null;
|
||||
filesCountMap?: Map<string, number> | null;
|
||||
parentPacker?: (id: string) => Promise<Packed<'DriveFolder'>>;
|
||||
},
|
||||
): Promise<Packed<'DriveFolder'>> {
|
||||
const opts = Object.assign({
|
||||
detail: false,
|
||||
}, options);
|
||||
|
||||
const folder = typeof src === 'object' ? src : await this.driveFoldersRepository.findOneByOrFail({ id: src });
|
||||
const folder = typeof src === 'object'
|
||||
? src
|
||||
: hint?.folderMap?.get(src) ?? await this.driveFoldersRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return await awaitAll({
|
||||
id: folder.id,
|
||||
@@ -46,20 +57,141 @@ export class DriveFolderEntityService {
|
||||
parentId: folder.parentId,
|
||||
|
||||
...(opts.detail ? {
|
||||
foldersCount: this.driveFoldersRepository.countBy({
|
||||
parentId: folder.id,
|
||||
}),
|
||||
filesCount: this.driveFilesRepository.countBy({
|
||||
folderId: folder.id,
|
||||
}),
|
||||
foldersCount: hint?.foldersCountMap?.get(folder.id)
|
||||
?? this.driveFoldersRepository.countBy({
|
||||
parentId: folder.id,
|
||||
}),
|
||||
filesCount: hint?.filesCountMap?.get(folder.id)
|
||||
?? this.driveFilesRepository.countBy({
|
||||
folderId: folder.id,
|
||||
}),
|
||||
|
||||
...(folder.parentId ? {
|
||||
parent: this.pack(folder.parentId, {
|
||||
detail: true,
|
||||
}),
|
||||
parent: hint?.parentPacker
|
||||
? hint.parentPacker(folder.parentId)
|
||||
: this.pack(folder.parentId, { detail: true }, hint),
|
||||
} : {}),
|
||||
} : {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async packMany(
|
||||
src: Array<MiDriveFolder['id'] | MiDriveFolder>,
|
||||
options?: {
|
||||
detail: boolean
|
||||
},
|
||||
): Promise<Array<Packed<'DriveFolder'>>> {
|
||||
/**
|
||||
* 重複を除去しつつ、必要なDriveFolderオブジェクトをすべて取得する
|
||||
*/
|
||||
const collectUniqueObjects = async (src: Array<MiDriveFolder['id'] | MiDriveFolder>) => {
|
||||
const uniqueSrc = uniqueByKey(
|
||||
src,
|
||||
(s) => typeof s === 'string' ? s : s.id,
|
||||
);
|
||||
const { ids, objects } = splitIdAndObjects(uniqueSrc);
|
||||
|
||||
const uniqueObjects = new Map<string, MiDriveFolder>(objects.map(s => [s.id, s]));
|
||||
const needsFetchIds = ids.filter(id => !uniqueObjects.has(id));
|
||||
|
||||
if (needsFetchIds.length > 0) {
|
||||
const fetchedObjects = await this.driveFoldersRepository.find({
|
||||
where: {
|
||||
id: In(needsFetchIds),
|
||||
},
|
||||
});
|
||||
for (const obj of fetchedObjects) {
|
||||
uniqueObjects.set(obj.id, obj);
|
||||
}
|
||||
}
|
||||
|
||||
return uniqueObjects;
|
||||
};
|
||||
|
||||
/**
|
||||
* 親フォルダーを再帰的に収集する
|
||||
*/
|
||||
const collectAncestors = async (folderMap: Map<string, MiDriveFolder>) => {
|
||||
for (;;) {
|
||||
const parentIds = new Set<string>();
|
||||
for (const folder of folderMap.values()) {
|
||||
if (folder.parentId != null && !folderMap.has(folder.parentId)) {
|
||||
parentIds.add(folder.parentId);
|
||||
}
|
||||
}
|
||||
|
||||
if (parentIds.size === 0) break;
|
||||
|
||||
const fetchedParents = await this.driveFoldersRepository.find({
|
||||
where: {
|
||||
id: In([...parentIds]),
|
||||
},
|
||||
});
|
||||
|
||||
if (fetchedParents.length === 0) break;
|
||||
|
||||
for (const parent of fetchedParents) {
|
||||
folderMap.set(parent.id, parent);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const opts = Object.assign({
|
||||
detail: false,
|
||||
}, options);
|
||||
|
||||
const folderMap = await collectUniqueObjects(src);
|
||||
|
||||
let foldersCountMap: Map<string, number> | null = null;
|
||||
let filesCountMap: Map<string, number> | null = null;
|
||||
if (opts.detail) {
|
||||
await collectAncestors(folderMap);
|
||||
|
||||
const ids = [...folderMap.keys()];
|
||||
if (ids.length > 0) {
|
||||
const folderCounts = await this.driveFoldersRepository.createQueryBuilder('folder')
|
||||
.select('folder.parentId', 'parentId')
|
||||
.addSelect('COUNT(*)', 'count')
|
||||
.where('folder.parentId IN (:...ids)', { ids })
|
||||
.groupBy('folder.parentId')
|
||||
.getRawMany<{ parentId: string; count: string }>();
|
||||
|
||||
const fileCounts = await this.driveFilesRepository.createQueryBuilder('file')
|
||||
.select('file.folderId', 'folderId')
|
||||
.addSelect('COUNT(*)', 'count')
|
||||
.where('file.folderId IN (:...ids)', { ids })
|
||||
.groupBy('file.folderId')
|
||||
.getRawMany<{ folderId: string; count: string }>();
|
||||
|
||||
foldersCountMap = new Map(folderCounts.map(row => [row.parentId, Number(row.count)]));
|
||||
filesCountMap = new Map(fileCounts.map(row => [row.folderId, Number(row.count)]));
|
||||
} else {
|
||||
foldersCountMap = new Map();
|
||||
filesCountMap = new Map();
|
||||
}
|
||||
}
|
||||
|
||||
const packedMap = new Map<string, Promise<Packed<'DriveFolder'>>>();
|
||||
const packFromId = (id: string): Promise<Packed<'DriveFolder'>> => {
|
||||
const cached = packedMap.get(id);
|
||||
if (cached) return cached;
|
||||
|
||||
const folder = folderMap.get(id);
|
||||
if (!folder) {
|
||||
throw new Error(`DriveFolder not found: ${id}`);
|
||||
}
|
||||
|
||||
const packedPromise = this.pack(folder, options, {
|
||||
folderMap,
|
||||
foldersCountMap,
|
||||
filesCountMap,
|
||||
parentPacker: packFromId,
|
||||
});
|
||||
packedMap.set(id, packedPromise);
|
||||
|
||||
return packedPromise;
|
||||
};
|
||||
|
||||
return Promise.all(src.map(s => packFromId(typeof s === 'string' ? s : s.id)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export class EmojiEntityService {
|
||||
|
||||
@bindThis
|
||||
public packSimpleMany(
|
||||
emojis: any[],
|
||||
emojis: (MiEmoji['id'] | MiEmoji)[],
|
||||
) {
|
||||
return Promise.all(emojis.map(x => this.packSimple(x)));
|
||||
}
|
||||
@@ -69,7 +69,7 @@ export class EmojiEntityService {
|
||||
|
||||
@bindThis
|
||||
public packDetailedMany(
|
||||
emojis: any[],
|
||||
emojis: (MiEmoji['id'] | MiEmoji)[],
|
||||
): Promise<Packed<'EmojiDetailed'>[]> {
|
||||
return Promise.all(emojis.map(x => this.packDetailed(x)));
|
||||
}
|
||||
|
||||
@@ -55,13 +55,13 @@ export class MetaEntityService {
|
||||
if (instance.defaultLightTheme) {
|
||||
try {
|
||||
defaultLightTheme = JSON.stringify(JSON5.parse(instance.defaultLightTheme));
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
}
|
||||
}
|
||||
if (instance.defaultDarkTheme) {
|
||||
try {
|
||||
defaultDarkTheme = JSON.stringify(JSON5.parse(instance.defaultDarkTheme));
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import { DebounceLoader } from '@/misc/loader.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { shouldHideNoteByTime } from '@/misc/should-hide-note-by-time.js';
|
||||
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
import type { CustomEmojiService } from '../CustomEmojiService.js';
|
||||
import type { ReactionService } from '../ReactionService.js';
|
||||
@@ -66,6 +67,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||
private reactionService: ReactionService;
|
||||
private reactionsBufferingService: ReactionsBufferingService;
|
||||
private idService: IdService;
|
||||
private cacheService: CacheService;
|
||||
private noteLoader = new DebounceLoader(this.findNoteOrFail);
|
||||
|
||||
constructor(
|
||||
@@ -101,6 +103,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||
//private reactionService: ReactionService,
|
||||
//private reactionsBufferingService: ReactionsBufferingService,
|
||||
//private idService: IdService,
|
||||
//private cacheService: CacheService,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -111,6 +114,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||
this.reactionService = this.moduleRef.get('ReactionService');
|
||||
this.reactionsBufferingService = this.moduleRef.get('ReactionsBufferingService');
|
||||
this.idService = this.moduleRef.get('IdService');
|
||||
this.cacheService = this.moduleRef.get('CacheService');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -125,75 +129,65 @@ export class NoteEntityService implements OnModuleInit {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise<void> {
|
||||
if (meId === packedNote.userId) return;
|
||||
|
||||
public async shouldHideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise<boolean> {
|
||||
if (meId === packedNote.userId) return false;
|
||||
// TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
|
||||
let hide = false;
|
||||
|
||||
if (packedNote.user.requireSigninToViewContents && meId == null) {
|
||||
hide = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!hide) {
|
||||
const hiddenBefore = packedNote.user.makeNotesHiddenBefore;
|
||||
if (shouldHideNoteByTime(hiddenBefore, packedNote.createdAt)) {
|
||||
hide = true;
|
||||
}
|
||||
const hiddenBefore = packedNote.user.makeNotesHiddenBefore;
|
||||
if (shouldHideNoteByTime(hiddenBefore, packedNote.createdAt)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// visibility が specified かつ自分が指定されていなかったら非表示
|
||||
if (!hide) {
|
||||
if (packedNote.visibility === 'specified') {
|
||||
if (meId == null) {
|
||||
hide = true;
|
||||
} else {
|
||||
// 指定されているかどうか
|
||||
const specified = packedNote.visibleUserIds!.some(id => meId === id);
|
||||
if (packedNote.visibility === 'specified') {
|
||||
if (meId == null) {
|
||||
return true;
|
||||
} else {
|
||||
// 指定されているかどうか
|
||||
const specified = packedNote.visibleUserIds!.some(id => meId === id);
|
||||
|
||||
if (!specified) {
|
||||
hide = true;
|
||||
}
|
||||
if (!specified) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
|
||||
if (!hide) {
|
||||
if (packedNote.visibility === 'followers') {
|
||||
if (meId == null) {
|
||||
hide = true;
|
||||
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
|
||||
// 自分の投稿に対するリプライ
|
||||
hide = false;
|
||||
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
|
||||
// 自分へのメンション
|
||||
hide = false;
|
||||
} else {
|
||||
// フォロワーかどうか
|
||||
// TODO: 当関数呼び出しごとにクエリが走るのは重そうだからなんとかする
|
||||
const isFollowing = await this.followingsRepository.exists({
|
||||
where: {
|
||||
followeeId: packedNote.userId,
|
||||
followerId: meId,
|
||||
},
|
||||
});
|
||||
|
||||
hide = !isFollowing;
|
||||
if (packedNote.visibility === 'followers') {
|
||||
if (meId == null) {
|
||||
return true;
|
||||
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
|
||||
// 自分の投稿に対するリプライ
|
||||
return false;
|
||||
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
|
||||
// 自分へのメンション
|
||||
return false;
|
||||
} else {
|
||||
// フォロワーかどうか
|
||||
const followings = await this.cacheService.userFollowingsCache.fetch(meId);
|
||||
if (!Object.hasOwn(followings, packedNote.userId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hide) {
|
||||
packedNote.visibleUserIds = undefined;
|
||||
packedNote.fileIds = [];
|
||||
packedNote.files = [];
|
||||
packedNote.text = null;
|
||||
packedNote.poll = undefined;
|
||||
packedNote.cw = null;
|
||||
packedNote.isHidden = true;
|
||||
// TODO: hiddenReason みたいなのを提供しても良さそう
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public hideNote(packedNote: Packed<'Note'>): void {
|
||||
packedNote.visibleUserIds = undefined;
|
||||
packedNote.fileIds = [];
|
||||
packedNote.files = [];
|
||||
packedNote.text = null;
|
||||
packedNote.poll = undefined;
|
||||
packedNote.cw = null;
|
||||
packedNote.isHidden = true;
|
||||
// TODO: hiddenReason みたいなのを提供しても良さそう
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -278,7 +272,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||
|
||||
@bindThis
|
||||
public async isVisibleForMe(note: MiNote, meId: MiUser['id'] | null): Promise<boolean> {
|
||||
// This code must always be synchronized with the checks in generateVisibilityQuery.
|
||||
// This code must always be synchronized with the checks in QueryService.generateVisibilityQuery.
|
||||
// visibility が specified かつ自分が指定されていなかったら非表示
|
||||
if (note.visibility === 'specified') {
|
||||
if (meId == null) {
|
||||
@@ -468,8 +462,8 @@ export class NoteEntityService implements OnModuleInit {
|
||||
|
||||
this.treatVisibility(packed);
|
||||
|
||||
if (!opts.skipHide) {
|
||||
await this.hideNote(packed, meId);
|
||||
if (!opts.skipHide && await this.shouldHideNote(packed, meId)) {
|
||||
this.hideNote(packed);
|
||||
}
|
||||
|
||||
return packed;
|
||||
|
||||
@@ -54,7 +54,7 @@ export class NoteReactionEntityService implements OnModuleInit {
|
||||
packedUser?: Packed<'UserLite'>
|
||||
},
|
||||
): Promise<Packed<'NoteReaction'>> {
|
||||
const opts = Object.assign({
|
||||
const _opts = Object.assign({
|
||||
}, options);
|
||||
|
||||
const reaction = typeof src === 'object' ? src : await this.noteReactionsRepository.findOneByOrFail({ id: src });
|
||||
@@ -90,7 +90,7 @@ export class NoteReactionEntityService implements OnModuleInit {
|
||||
packedUser?: Packed<'UserLite'>
|
||||
},
|
||||
): Promise<Packed<'NoteReactionWithNote'>> {
|
||||
const opts = Object.assign({
|
||||
const _opts = Object.assign({
|
||||
}, options);
|
||||
|
||||
const reaction = typeof src === 'object' ? src : await this.noteReactionsRepository.findOneByOrFail({ id: src });
|
||||
|
||||
@@ -14,6 +14,10 @@ import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
|
||||
function assertBw(bw: string): bw is Packed<'ReversiGameDetailed'>['bw'] {
|
||||
return ['random', '1', '2'].includes(bw);
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ReversiGameEntityService {
|
||||
constructor(
|
||||
@@ -58,7 +62,7 @@ export class ReversiGameEntityService {
|
||||
surrenderedUserId: game.surrenderedUserId,
|
||||
timeoutUserId: game.timeoutUserId,
|
||||
black: game.black,
|
||||
bw: game.bw,
|
||||
bw: assertBw(game.bw) ? game.bw : 'random',
|
||||
isLlotheo: game.isLlotheo,
|
||||
canPutEverywhere: game.canPutEverywhere,
|
||||
loopedBoard: game.loopedBoard,
|
||||
@@ -116,7 +120,7 @@ export class ReversiGameEntityService {
|
||||
surrenderedUserId: game.surrenderedUserId,
|
||||
timeoutUserId: game.timeoutUserId,
|
||||
black: game.black,
|
||||
bw: game.bw,
|
||||
bw: assertBw(game.bw) ? game.bw : 'random',
|
||||
isLlotheo: game.isLlotheo,
|
||||
canPutEverywhere: game.canPutEverywhere,
|
||||
loopedBoard: game.loopedBoard,
|
||||
|
||||
@@ -720,7 +720,7 @@ export class UserEntityService implements OnModuleInit {
|
||||
me,
|
||||
{
|
||||
...options,
|
||||
userProfile: profilesMap.get(u.id),
|
||||
userProfile: profilesMap?.get(u.id),
|
||||
userRelations: userRelations,
|
||||
userMemos: userMemos,
|
||||
pinNotes: pinNotes,
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import si from 'systeminformation';
|
||||
import Xev from 'xev';
|
||||
import * as osUtils from 'os-utils';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
import { MiMeta } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
const ev = new Xev();
|
||||
|
||||
@@ -97,12 +96,14 @@ function cpuUsage(): Promise<number> {
|
||||
|
||||
// MEMORY STAT
|
||||
async function mem() {
|
||||
const si = await import('systeminformation');
|
||||
const data = await si.mem();
|
||||
return data;
|
||||
}
|
||||
|
||||
// NETWORK STAT
|
||||
async function net() {
|
||||
const si = await import('systeminformation');
|
||||
const iface = await si.networkInterfaceDefault();
|
||||
const data = await si.networkStats(iface);
|
||||
return data[0];
|
||||
@@ -110,5 +111,6 @@ async function net() {
|
||||
|
||||
// FS STAT
|
||||
async function fs() {
|
||||
const si = await import('systeminformation');
|
||||
return await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 }));
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export async function checkWordMute(note: NoteLike, me: UserLike | null | undefi
|
||||
|
||||
try {
|
||||
return new RE2(regexp[1], regexp[2]).test(text);
|
||||
} catch (err) {
|
||||
} catch (_) {
|
||||
// This should never happen due to input sanitisation.
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export function getIpHash(ip: string): string {
|
||||
// (this means for IPv4 the entire address is used)
|
||||
const prefix = IPCIDR.createAddress(ip).mask(64);
|
||||
return 'ip-' + BigInt('0b' + prefix).toString(36);
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
const prefix = IPCIDR.createAddress(ip.replace(/:[0-9]+$/, '')).mask(64);
|
||||
return 'ip-' + BigInt('0b' + prefix).toString(36);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export class I18n<T extends Record<string, any>> {
|
||||
}
|
||||
}
|
||||
return str;
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
console.warn(`missing localization '${key}'`);
|
||||
return key;
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ import {
|
||||
packedMetaDetailedOnlySchema,
|
||||
packedMetaDetailedSchema,
|
||||
packedMetaLiteSchema,
|
||||
packedMetaClientOptionsSchema,
|
||||
} from '@/models/json-schema/meta.js';
|
||||
import { packedUserWebhookSchema } from '@/models/json-schema/user-webhook.js';
|
||||
import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js';
|
||||
@@ -135,6 +136,7 @@ export const refs = {
|
||||
MetaLite: packedMetaLiteSchema,
|
||||
MetaDetailedOnly: packedMetaDetailedOnlySchema,
|
||||
MetaDetailed: packedMetaDetailedSchema,
|
||||
MetaClientOptions: packedMetaClientOptionsSchema,
|
||||
UserWebhook: packedUserWebhookSchema,
|
||||
SystemWebhook: packedSystemWebhookSchema,
|
||||
AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema,
|
||||
@@ -262,8 +264,6 @@ type ObjectSchemaTypeDef<p extends Schema> =
|
||||
never :
|
||||
any;
|
||||
|
||||
type ObjectSchemaType<p extends Schema> = NullOrUndefined<p, ObjectSchemaTypeDef<p>>;
|
||||
|
||||
export type SchemaTypeDef<p extends Schema> =
|
||||
p['type'] extends 'null' ? null :
|
||||
p['type'] extends 'integer' ? number :
|
||||
|
||||
@@ -4,15 +4,11 @@
|
||||
*/
|
||||
|
||||
import * as os from 'node:os';
|
||||
import sysUtils from 'systeminformation';
|
||||
import type Logger from '@/logger.js';
|
||||
|
||||
export async function showMachineInfo(parentLogger: Logger) {
|
||||
const logger = parentLogger.createSubLogger('machine');
|
||||
logger.debug(`Hostname: ${os.hostname()}`);
|
||||
logger.debug(`Platform: ${process.platform} Arch: ${process.arch}`);
|
||||
const mem = await sysUtils.mem();
|
||||
const totalmem = (mem.total / 1024 / 1024 / 1024).toFixed(1);
|
||||
const availmem = (mem.available / 1024 / 1024 / 1024).toFixed(1);
|
||||
logger.debug(`CPU: ${os.cpus().length} core MEM: ${totalmem}GB (available: ${availmem}GB)`);
|
||||
logger.debug(`CPU: ${os.cpus().length} core MEM: ${(os.totalmem() / 1024 / 1024 / 1024).toFixed(1)}GB (available: ${(os.freemem() / 1024 / 1024 / 1024).toFixed(1)}GB)`);
|
||||
}
|
||||
|
||||
27
packages/backend/src/misc/split-id-and-objects.ts
Normal file
27
packages/backend/src/misc/split-id-and-objects.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/**
|
||||
* idとオブジェクトを分離する
|
||||
* @param input idまたはオブジェクトの配列
|
||||
* @returns idの配列とオブジェクトの配列
|
||||
*/
|
||||
export function splitIdAndObjects<T extends { id: string }>(input: (T | string)[]): { ids: string[]; objects: T[] } {
|
||||
const ids: string[] = [];
|
||||
const objects : T[] = [];
|
||||
|
||||
for (const item of input) {
|
||||
if (typeof item === 'string') {
|
||||
ids.push(item);
|
||||
} else {
|
||||
objects.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ids,
|
||||
objects,
|
||||
};
|
||||
}
|
||||
21
packages/backend/src/misc/unique-by-key.ts
Normal file
21
packages/backend/src/misc/unique-by-key.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/**
|
||||
* itemsの中でkey関数が返す値が重複しないようにした配列を返す
|
||||
* @param items 重複を除去したい配列
|
||||
* @param key 重複判定に使うキーを返す関数
|
||||
* @returns 重複を除去した配列
|
||||
*/
|
||||
export function uniqueByKey<TItem, TKey = string>(items: Iterable<TItem>, key: (item: TItem) => TKey): TItem[] {
|
||||
const map = new Map<TKey, TItem>();
|
||||
for (const item of items) {
|
||||
const k = key(item);
|
||||
if (!map.has(k)) {
|
||||
map.set(k, item);
|
||||
}
|
||||
}
|
||||
return [...map.values()];
|
||||
}
|
||||
@@ -67,7 +67,7 @@ export class MiAbuseReportNotificationRecipient {
|
||||
/**
|
||||
* 通知先のユーザ.
|
||||
*/
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'userId', referencedColumnName: 'id', foreignKeyConstraintName: 'FK_abuse_report_notification_recipient_userId1' })
|
||||
@@ -76,7 +76,7 @@ export class MiAbuseReportNotificationRecipient {
|
||||
/**
|
||||
* 通知先のユーザプロフィール.
|
||||
*/
|
||||
@ManyToOne(type => MiUserProfile, {
|
||||
@ManyToOne(() => MiUserProfile, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'userId', referencedColumnName: 'userId', foreignKeyConstraintName: 'FK_abuse_report_notification_recipient_userId2' })
|
||||
@@ -96,7 +96,7 @@ export class MiAbuseReportNotificationRecipient {
|
||||
/**
|
||||
* 通知先のシステムWebhook.
|
||||
*/
|
||||
@ManyToOne(type => MiSystemWebhook, {
|
||||
@ManyToOne(() => MiSystemWebhook, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'systemWebhookId', referencedColumnName: 'id', foreignKeyConstraintName: 'FK_abuse_report_notification_recipient_systemWebhookId' })
|
||||
|
||||
@@ -18,7 +18,7 @@ export class MiAbuseUserReport {
|
||||
@Column(id())
|
||||
public targetUserId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -28,7 +28,7 @@ export class MiAbuseUserReport {
|
||||
@Column(id())
|
||||
public reporterId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -40,7 +40,7 @@ export class MiAbuseUserReport {
|
||||
})
|
||||
public assigneeId: MiUser['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -41,7 +41,7 @@ export class MiAccessToken {
|
||||
@Column(id())
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -53,7 +53,7 @@ export class MiAccessToken {
|
||||
})
|
||||
public appId: MiApp['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiApp, {
|
||||
@ManyToOne(() => MiApp, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -79,7 +79,7 @@ export class MiAnnouncement {
|
||||
})
|
||||
public userId: MiUser['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -18,7 +18,7 @@ export class MiAnnouncementRead {
|
||||
@Column(id())
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -28,7 +28,7 @@ export class MiAnnouncementRead {
|
||||
@Column(id())
|
||||
public announcementId: MiAnnouncement['id'];
|
||||
|
||||
@ManyToOne(type => MiAnnouncement, {
|
||||
@ManyToOne(() => MiAnnouncement, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -24,7 +24,7 @@ export class MiAntenna {
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -45,7 +45,7 @@ export class MiAntenna {
|
||||
})
|
||||
public userListId: MiUserList['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiUserList, {
|
||||
@ManyToOne(() => MiUserList, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -20,7 +20,7 @@ export class MiApp {
|
||||
})
|
||||
public userId: MiUser['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'SET NULL',
|
||||
nullable: true,
|
||||
})
|
||||
|
||||
@@ -25,7 +25,7 @@ export class MiAuthSession {
|
||||
})
|
||||
public userId: MiUser['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
nullable: true,
|
||||
})
|
||||
@@ -35,7 +35,7 @@ export class MiAuthSession {
|
||||
@Column(id())
|
||||
public appId: MiApp['id'];
|
||||
|
||||
@ManyToOne(type => MiApp, {
|
||||
@ManyToOne(() => MiApp, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -20,7 +20,7 @@ export class MiBlocking {
|
||||
})
|
||||
public blockeeId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -33,7 +33,7 @@ export class MiBlocking {
|
||||
})
|
||||
public blockerId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -18,7 +18,7 @@ export class MiBubbleGameRecord {
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -27,7 +27,7 @@ export class MiChannel {
|
||||
})
|
||||
public userId: MiUser['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -52,7 +52,7 @@ export class MiChannel {
|
||||
})
|
||||
public bannerId: MiDriveFile['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiDriveFile, {
|
||||
@ManyToOne(() => MiDriveFile, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -20,7 +20,7 @@ export class MiChannelFavorite {
|
||||
})
|
||||
public channelId: MiChannel['id'];
|
||||
|
||||
@ManyToOne(type => MiChannel, {
|
||||
@ManyToOne(() => MiChannel, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -32,7 +32,7 @@ export class MiChannelFavorite {
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -21,7 +21,7 @@ export class MiChannelFollowing {
|
||||
})
|
||||
public followeeId: MiChannel['id'];
|
||||
|
||||
@ManyToOne(type => MiChannel, {
|
||||
@ManyToOne(() => MiChannel, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -34,7 +34,7 @@ export class MiChannelFollowing {
|
||||
})
|
||||
public followerId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -20,7 +20,7 @@ export class MiChannelMuting {
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -32,7 +32,7 @@ export class MiChannelMuting {
|
||||
})
|
||||
public channelId: MiChannel['id'];
|
||||
|
||||
@ManyToOne(type => MiChannel, {
|
||||
@ManyToOne(() => MiChannel, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -19,7 +19,7 @@ export class MiChatApproval {
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -31,7 +31,7 @@ export class MiChatApproval {
|
||||
})
|
||||
public otherId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -20,7 +20,7 @@ export class MiChatMessage {
|
||||
})
|
||||
public fromUserId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -32,7 +32,7 @@ export class MiChatMessage {
|
||||
})
|
||||
public toUserId: MiUser['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -44,7 +44,7 @@ export class MiChatMessage {
|
||||
})
|
||||
public toRoomId: MiChatRoom['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiChatRoom, {
|
||||
@ManyToOne(() => MiChatRoom, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -72,7 +72,7 @@ export class MiChatMessage {
|
||||
})
|
||||
public fileId: MiDriveFile['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiDriveFile, {
|
||||
@ManyToOne(() => MiDriveFile, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -23,7 +23,7 @@ export class MiChatRoom {
|
||||
})
|
||||
public ownerId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -20,7 +20,7 @@ export class MiChatRoomInvitation {
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -32,7 +32,7 @@ export class MiChatRoomInvitation {
|
||||
})
|
||||
public roomId: MiChatRoom['id'];
|
||||
|
||||
@ManyToOne(type => MiChatRoom, {
|
||||
@ManyToOne(() => MiChatRoom, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -20,7 +20,7 @@ export class MiChatRoomMembership {
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -32,7 +32,7 @@ export class MiChatRoomMembership {
|
||||
})
|
||||
public roomId: MiChatRoom['id'];
|
||||
|
||||
@ManyToOne(type => MiChatRoom, {
|
||||
@ManyToOne(() => MiChatRoom, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -25,7 +25,7 @@ export class MiClip {
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -18,7 +18,7 @@ export class MiClipFavorite {
|
||||
@Column(id())
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -27,7 +27,7 @@ export class MiClipFavorite {
|
||||
@Column(id())
|
||||
public clipId: MiClip['id'];
|
||||
|
||||
@ManyToOne(type => MiClip, {
|
||||
@ManyToOne(() => MiClip, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -21,7 +21,7 @@ export class MiClipNote {
|
||||
})
|
||||
public noteId: MiNote['id'];
|
||||
|
||||
@ManyToOne(type => MiNote, {
|
||||
@ManyToOne(() => MiNote, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -34,7 +34,7 @@ export class MiClipNote {
|
||||
})
|
||||
public clipId: MiClip['id'];
|
||||
|
||||
@ManyToOne(type => MiClip, {
|
||||
@ManyToOne(() => MiClip, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -22,7 +22,7 @@ export class MiDriveFile {
|
||||
})
|
||||
public userId: MiUser['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -142,7 +142,7 @@ export class MiDriveFile {
|
||||
})
|
||||
public folderId: MiDriveFolder['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiDriveFolder, {
|
||||
@ManyToOne(() => MiDriveFolder, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -26,7 +26,7 @@ export class MiDriveFolder {
|
||||
})
|
||||
public userId: MiUser['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -40,7 +40,7 @@ export class MiDriveFolder {
|
||||
})
|
||||
public parentId: MiDriveFolder['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiDriveFolder, {
|
||||
@ManyToOne(() => MiDriveFolder, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -38,7 +38,7 @@ export class MiFlash {
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -18,7 +18,7 @@ export class MiFlashLike {
|
||||
@Column(id())
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -27,7 +27,7 @@ export class MiFlashLike {
|
||||
@Column(id())
|
||||
public flashId: MiFlash['id'];
|
||||
|
||||
@ManyToOne(type => MiFlash, {
|
||||
@ManyToOne(() => MiFlash, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -20,7 +20,7 @@ export class MiFollowRequest {
|
||||
})
|
||||
public followeeId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -33,7 +33,7 @@ export class MiFollowRequest {
|
||||
})
|
||||
public followerId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -21,7 +21,7 @@ export class MiFollowing {
|
||||
})
|
||||
public followeeId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -34,7 +34,7 @@ export class MiFollowing {
|
||||
})
|
||||
public followerId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -18,7 +18,7 @@ export class MiGalleryLike {
|
||||
@Column(id())
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -27,7 +27,7 @@ export class MiGalleryLike {
|
||||
@Column(id())
|
||||
public postId: MiGalleryPost['id'];
|
||||
|
||||
@ManyToOne(type => MiGalleryPost, {
|
||||
@ManyToOne(() => MiGalleryPost, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -36,7 +36,7 @@ export class MiGalleryPost {
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -21,7 +21,7 @@ export class MiMeta {
|
||||
})
|
||||
public rootUserId: MiUser['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'SET NULL',
|
||||
nullable: true,
|
||||
})
|
||||
@@ -725,7 +725,11 @@ export class MiMeta {
|
||||
@Column('jsonb', {
|
||||
default: { },
|
||||
})
|
||||
public clientOptions: Record<string, any>;
|
||||
public clientOptions: {
|
||||
entrancePageStyle: 'classic' | 'simple';
|
||||
showTimelineForVisitor: boolean;
|
||||
showActivitiesForVisitor: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export type SoftwareSuspension = {
|
||||
|
||||
@@ -16,7 +16,7 @@ export class MiModerationLog {
|
||||
@Column(id())
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -32,7 +32,7 @@ export class MiMuting {
|
||||
})
|
||||
public muteeId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -45,7 +45,7 @@ export class MiMuting {
|
||||
})
|
||||
public muterId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -35,7 +35,7 @@ export class MiNote {
|
||||
})
|
||||
public replyId: MiNote['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiNote, {
|
||||
@ManyToOne(() => MiNote, {
|
||||
createForeignKeyConstraints: false,
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -49,7 +49,7 @@ export class MiNote {
|
||||
})
|
||||
public renoteId: MiNote['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiNote, {
|
||||
@ManyToOne(() => MiNote, {
|
||||
createForeignKeyConstraints: false,
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -83,7 +83,7 @@ export class MiNote {
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -208,7 +208,7 @@ export class MiNote {
|
||||
})
|
||||
public channelId: MiChannel['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiChannel, {
|
||||
@ManyToOne(() => MiChannel, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -27,7 +27,7 @@ export class MiNoteDraft {
|
||||
public replyId: MiNote['id'] | null;
|
||||
|
||||
// There is a possibility that replyId is not null but reply is null when the reply note is deleted.
|
||||
@ManyToOne(type => MiNote, {
|
||||
@ManyToOne(() => MiNote, {
|
||||
createForeignKeyConstraints: false,
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -42,7 +42,7 @@ export class MiNoteDraft {
|
||||
public renoteId: MiNote['id'] | null;
|
||||
|
||||
// There is a possibility that renoteId is not null but renote is null when the renote note is deleted.
|
||||
@ManyToOne(type => MiNote, {
|
||||
@ManyToOne(() => MiNote, {
|
||||
createForeignKeyConstraints: false,
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -66,7 +66,7 @@ export class MiNoteDraft {
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -120,7 +120,7 @@ export class MiNoteDraft {
|
||||
|
||||
// There is a possibility that channelId is not null but channel is null when the channel is deleted.
|
||||
// (deleting channel is not implemented so it's not happening now but may happen in the future)
|
||||
@ManyToOne(type => MiChannel, {
|
||||
@ManyToOne(() => MiChannel, {
|
||||
createForeignKeyConstraints: false,
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -18,7 +18,7 @@ export class MiNoteFavorite {
|
||||
@Column(id())
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -27,7 +27,7 @@ export class MiNoteFavorite {
|
||||
@Column(id())
|
||||
public noteId: MiNote['id'];
|
||||
|
||||
@ManyToOne(type => MiNote, {
|
||||
@ManyToOne(() => MiNote, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -18,7 +18,7 @@ export class MiNoteReaction {
|
||||
@Column(id())
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -28,7 +28,7 @@ export class MiNoteReaction {
|
||||
@Column(id())
|
||||
public noteId: MiNote['id'];
|
||||
|
||||
@ManyToOne(type => MiNote, {
|
||||
@ManyToOne(() => MiNote, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -19,7 +19,7 @@ export class MiNoteThreadMuting {
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -56,7 +56,7 @@ export class MiPage {
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -68,7 +68,7 @@ export class MiPage {
|
||||
})
|
||||
public eyeCatchingImageId: MiDriveFile['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiDriveFile, {
|
||||
@ManyToOne(() => MiDriveFile, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -18,7 +18,7 @@ export class MiPageLike {
|
||||
@Column(id())
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
@@ -27,7 +27,7 @@ export class MiPageLike {
|
||||
@Column(id())
|
||||
public pageId: MiPage['id'];
|
||||
|
||||
@ManyToOne(type => MiPage, {
|
||||
@ManyToOne(() => MiPage, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
@@ -24,7 +24,7 @@ export class MiPasswordResetRequest {
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
@ManyToOne(() => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user