Compare commits
27 Commits
2025.2.1
...
renovate/n
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a492f2c7e1 | ||
![]() |
c7cbe60a2d | ||
![]() |
f3be426383 | ||
![]() |
e8a6629cb5 | ||
![]() |
44658ae981 | ||
![]() |
19384efbc5 | ||
![]() |
adf22143aa | ||
![]() |
a17acf647b | ||
![]() |
01a3eabc4e | ||
![]() |
59567a7ccc | ||
![]() |
7fb8fccd57 | ||
![]() |
a4711ab4c1 | ||
![]() |
bbe404a0b2 | ||
![]() |
0610bd657f | ||
![]() |
77667cf80d | ||
![]() |
801a2ec1db | ||
![]() |
2a96e39bb3 | ||
![]() |
616cccf251 | ||
![]() |
7114523d84 | ||
![]() |
5d683728f3 | ||
![]() |
b8632f389d | ||
![]() |
830da5e9f1 | ||
![]() |
e2eddd5b1a | ||
![]() |
d4f9bf1f11 | ||
![]() |
734c78ddd1 | ||
![]() |
c63c3462dd | ||
![]() |
a3bba23b7d |
@@ -5,7 +5,7 @@
|
|||||||
"workspaceFolder": "/workspace",
|
"workspaceFolder": "/workspace",
|
||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/node:1": {
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
"version": "22.11.0"
|
"version": "22.14.0"
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers-extra/features/corepack:1": {
|
"ghcr.io/devcontainers-extra/features/corepack:1": {
|
||||||
"version": "0.31.0"
|
"version": "0.31.0"
|
||||||
|
2
.github/workflows/get-api-diff.yml
vendored
2
.github/workflows/get-api-diff.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [22.11.0]
|
node-version: [22.14.0]
|
||||||
api-json-name: [api-base.json, api-head.json]
|
api-json-name: [api-base.json, api-head.json]
|
||||||
include:
|
include:
|
||||||
- api-json-name: api-base.json
|
- api-json-name: api-base.json
|
||||||
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -79,7 +79,7 @@ jobs:
|
|||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- run: pnpm i --frozen-lockfile
|
- run: pnpm i --frozen-lockfile
|
||||||
- name: Restore eslint cache
|
- name: Restore eslint cache
|
||||||
uses: actions/cache@v4.2.1
|
uses: actions/cache@v4.2.2
|
||||||
with:
|
with:
|
||||||
path: ${{ env.eslint-cache-path }}
|
path: ${{ env.eslint-cache-path }}
|
||||||
key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }}
|
key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
2
.github/workflows/on-release-created.yml
vendored
2
.github/workflows/on-release-created.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [22.11.0]
|
node-version: [22.14.0]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.2.2
|
- uses: actions/checkout@v4.2.2
|
||||||
|
4
.github/workflows/test-backend.yml
vendored
4
.github/workflows/test-backend.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [22.11.0]
|
node-version: [22.14.0]
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
@@ -92,7 +92,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [22.11.0]
|
node-version: [22.14.0]
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
|
18
.github/workflows/test-federation.yml
vendored
18
.github/workflows/test-federation.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [22.11.0]
|
node-version: [22.14.0]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -62,14 +62,30 @@ jobs:
|
|||||||
bash ./setup.sh
|
bash ./setup.sh
|
||||||
sudo chmod 644 ./certificates/*.test.key
|
sudo chmod 644 ./certificates/*.test.key
|
||||||
- name: Start servers
|
- name: Start servers
|
||||||
|
id: start_servers
|
||||||
|
continue-on-error: true
|
||||||
# https://github.com/docker/compose/issues/1294#issuecomment-374847206
|
# https://github.com/docker/compose/issues/1294#issuecomment-374847206
|
||||||
run: |
|
run: |
|
||||||
cd packages/backend/test-federation
|
cd packages/backend/test-federation
|
||||||
docker compose up -d --scale tester=0
|
docker compose up -d --scale tester=0
|
||||||
|
- name: Print start_servers error
|
||||||
|
if: ${{ steps.start_servers.outcome == 'failure' }}
|
||||||
|
run: |
|
||||||
|
cd packages/backend/test-federation
|
||||||
|
docker compose logs | tail -n 300
|
||||||
|
exit 1
|
||||||
- name: Test
|
- name: Test
|
||||||
|
id: test
|
||||||
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
cd packages/backend/test-federation
|
cd packages/backend/test-federation
|
||||||
docker compose run --no-deps tester
|
docker compose run --no-deps tester
|
||||||
|
- name: Log
|
||||||
|
if: ${{ steps.test.outcome == 'failure' }}
|
||||||
|
run: |
|
||||||
|
cd packages/backend/test-federation
|
||||||
|
docker compose logs
|
||||||
|
exit 1
|
||||||
- name: Stop servers
|
- name: Stop servers
|
||||||
run: |
|
run: |
|
||||||
cd packages/backend/test-federation
|
cd packages/backend/test-federation
|
||||||
|
4
.github/workflows/test-frontend.yml
vendored
4
.github/workflows/test-frontend.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [22.11.0]
|
node-version: [22.14.0]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.2.2
|
- uses: actions/checkout@v4.2.2
|
||||||
@@ -69,7 +69,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [22.11.0]
|
node-version: [22.14.0]
|
||||||
browser: [chrome]
|
browser: [chrome]
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
2
.github/workflows/test-misskey-js.yml
vendored
2
.github/workflows/test-misskey-js.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [22.11.0]
|
node-version: [22.14.0]
|
||||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
2
.github/workflows/test-production.yml
vendored
2
.github/workflows/test-production.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [22.11.0]
|
node-version: [22.14.0]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.2.2
|
- uses: actions/checkout@v4.2.2
|
||||||
|
2
.github/workflows/validate-api-json.yml
vendored
2
.github/workflows/validate-api-json.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [22.11.0]
|
node-version: [22.14.0]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.2.2
|
- uses: actions/checkout@v4.2.2
|
||||||
|
@@ -1 +1 @@
|
|||||||
22.11.0
|
22.14.0
|
||||||
|
17
CHANGELOG.md
17
CHANGELOG.md
@@ -1,3 +1,20 @@
|
|||||||
|
## 2025.3.0
|
||||||
|
|
||||||
|
### General
|
||||||
|
- Enhance: プロキシアカウントをシステムアカウントとして作成するように
|
||||||
|
- Fix: システムアカウントが削除できる問題を修正
|
||||||
|
|
||||||
|
### Client
|
||||||
|
- Enhance: モデレーターがセンシティブ設定を変更する際に確認ダイアログを出すように
|
||||||
|
- Enhance: 「UIのアニメーションを減らす」で画面上のエフェクトも減らせるように
|
||||||
|
- Fix: 削除して編集の削除タイミングを投稿後になるように `#14498`
|
||||||
|
- Fix: フォローされたときのメッセージがちらつくことがある問題を修正
|
||||||
|
- Fix: 投稿ダイアログがサイズ限界を超えた際にスクロールできない問題を修正
|
||||||
|
|
||||||
|
### Server
|
||||||
|
- Fix: 特定のケースでActivityPubの処理がデッドロックになることがあるのを修正
|
||||||
|
|
||||||
|
|
||||||
## 2025.2.1
|
## 2025.2.1
|
||||||
|
|
||||||
### General
|
### General
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# syntax = docker/dockerfile:1.4
|
# syntax = docker/dockerfile:1.4
|
||||||
|
|
||||||
ARG NODE_VERSION=22.11.0-bookworm
|
ARG NODE_VERSION=22.14.0-bookworm
|
||||||
|
|
||||||
# build assets & compile TypeScript
|
# build assets & compile TypeScript
|
||||||
|
|
||||||
|
@@ -233,7 +233,7 @@ describe('After user setup', () => {
|
|||||||
cy.get('[data-cy-post-form-text]').type('Hello, Misskey!');
|
cy.get('[data-cy-post-form-text]').type('Hello, Misskey!');
|
||||||
cy.get('[data-cy-open-post-form-submit]').click();
|
cy.get('[data-cy-open-post-form-submit]').click();
|
||||||
|
|
||||||
cy.contains('Hello, Misskey!');
|
cy.contains('Hello, Misskey!', { timeout: 15000 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('open note form with hotkey', () => {
|
it('open note form with hotkey', () => {
|
||||||
|
12
locales/index.d.ts
vendored
12
locales/index.d.ts
vendored
@@ -5262,6 +5262,14 @@ export interface Locale extends ILocale {
|
|||||||
* " {emoji} " をリアクションしますか?
|
* " {emoji} " をリアクションしますか?
|
||||||
*/
|
*/
|
||||||
"reactAreYouSure": ParameterizedString<"emoji">;
|
"reactAreYouSure": ParameterizedString<"emoji">;
|
||||||
|
/**
|
||||||
|
* このメディアをセンシティブとして設定しますか?
|
||||||
|
*/
|
||||||
|
"markAsSensitiveConfirm": string;
|
||||||
|
/**
|
||||||
|
* このメディアのセンシティブ指定を解除しますか?
|
||||||
|
*/
|
||||||
|
"unmarkAsSensitiveConfirm": string;
|
||||||
"_accountSettings": {
|
"_accountSettings": {
|
||||||
/**
|
/**
|
||||||
* コンテンツの表示にログインを必須にする
|
* コンテンツの表示にログインを必須にする
|
||||||
@@ -10058,6 +10066,10 @@ export interface Locale extends ILocale {
|
|||||||
* ギャラリーの投稿を削除
|
* ギャラリーの投稿を削除
|
||||||
*/
|
*/
|
||||||
"deleteGalleryPost": string;
|
"deleteGalleryPost": string;
|
||||||
|
/**
|
||||||
|
* プロキシアカウントの説明を更新
|
||||||
|
*/
|
||||||
|
"updateProxyAccountDescription": string;
|
||||||
};
|
};
|
||||||
"_fileViewer": {
|
"_fileViewer": {
|
||||||
/**
|
/**
|
||||||
|
@@ -1311,6 +1311,8 @@ federationSpecified: "このサーバーはホワイトリスト連合で運用
|
|||||||
federationDisabled: "このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。"
|
federationDisabled: "このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。"
|
||||||
confirmOnReact: "リアクションする際に確認する"
|
confirmOnReact: "リアクションする際に確認する"
|
||||||
reactAreYouSure: "\" {emoji} \" をリアクションしますか?"
|
reactAreYouSure: "\" {emoji} \" をリアクションしますか?"
|
||||||
|
markAsSensitiveConfirm: "このメディアをセンシティブとして設定しますか?"
|
||||||
|
unmarkAsSensitiveConfirm: "このメディアのセンシティブ指定を解除しますか?"
|
||||||
|
|
||||||
_accountSettings:
|
_accountSettings:
|
||||||
requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
|
requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
|
||||||
@@ -2664,6 +2666,7 @@ _moderationLogTypes:
|
|||||||
deletePage: "ページを削除"
|
deletePage: "ページを削除"
|
||||||
deleteFlash: "Playを削除"
|
deleteFlash: "Playを削除"
|
||||||
deleteGalleryPost: "ギャラリーの投稿を削除"
|
deleteGalleryPost: "ギャラリーの投稿を削除"
|
||||||
|
updateProxyAccountDescription: "プロキシアカウントの説明を更新"
|
||||||
|
|
||||||
_fileViewer:
|
_fileViewer:
|
||||||
title: "ファイルの詳細"
|
title: "ファイルの詳細"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2025.2.1",
|
"version": "2025.3.0-beta.0",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"build-storybook": "pnpm --filter frontend build-storybook",
|
"build-storybook": "pnpm --filter frontend build-storybook",
|
||||||
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
|
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
|
||||||
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
||||||
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
"start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
||||||
"init": "pnpm migrate",
|
"init": "pnpm migrate",
|
||||||
"migrate": "cd packages/backend && pnpm migrate",
|
"migrate": "cd packages/backend && pnpm migrate",
|
||||||
"revert": "cd packages/backend && pnpm revert",
|
"revert": "cd packages/backend && pnpm revert",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
"cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts",
|
"cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts",
|
||||||
"cy:run": "pnpm cypress run",
|
"cy:run": "pnpm cypress run",
|
||||||
"e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run",
|
"e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run",
|
||||||
"e2e-dev-container": "cp ./.config/cypress-devcontainer.yml ./.config/test.yml && pnpm start-server-and-test start:test http://localhost:61812 cy:run",
|
"e2e-dev-container": "ncp ./.config/cypress-devcontainer.yml ./.config/test.yml && pnpm start-server-and-test start:test http://localhost:61812 cy:run",
|
||||||
"jest": "cd packages/backend && pnpm jest",
|
"jest": "cd packages/backend && pnpm jest",
|
||||||
"jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage",
|
"jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage",
|
||||||
"test": "pnpm -r test",
|
"test": "pnpm -r test",
|
||||||
|
37
packages/backend/migration/1740121393164-system-accounts.js
Normal file
37
packages/backend/migration/1740121393164-system-accounts.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class SystemAccounts1740121393164 {
|
||||||
|
name = 'SystemAccounts1740121393164'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`CREATE TABLE "system_account" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "type" character varying(256) NOT NULL, CONSTRAINT "PK_edb56f4aaf9ddd50ee556da97ba" PRIMARY KEY ("id"))`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_41a3c87a37aea616ee459369e1" ON "system_account" ("userId") `);
|
||||||
|
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_c362033aee0ea51011386a5a7e" ON "system_account" ("type") `);
|
||||||
|
await queryRunner.query(`ALTER TABLE "system_account" ADD CONSTRAINT "FK_41a3c87a37aea616ee459369e12" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
|
||||||
|
const instanceActor = await queryRunner.query(`SELECT "id" FROM "user" WHERE "username" = 'instance.actor'`);
|
||||||
|
if (instanceActor.length > 0) {
|
||||||
|
await queryRunner.query(`INSERT INTO "system_account" ("id", "userId", "type") VALUES ('${instanceActor[0].id}', '${instanceActor[0].id}', 'actor')`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const relayActor = await queryRunner.query(`SELECT "id" FROM "user" WHERE "username" = 'relay.actor'`);
|
||||||
|
if (relayActor.length > 0) {
|
||||||
|
await queryRunner.query(`INSERT INTO "system_account" ("id", "userId", "type") VALUES ('${relayActor[0].id}', '${relayActor[0].id}', 'relay')`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = await queryRunner.query(`SELECT "proxyAccountId" FROM "meta" ORDER BY "id" DESC LIMIT 1`);
|
||||||
|
if (!meta && meta.length >= 1 && meta[0].proxyAccountId) {
|
||||||
|
await queryRunner.query(`INSERT INTO "system_account" ("id", "userId", "type") VALUES ('${meta[0].proxyAccountId}', '${meta[0].proxyAccountId}', 'proxy')`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "system_account" DROP CONSTRAINT "FK_41a3c87a37aea616ee459369e12"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_c362033aee0ea51011386a5a7e"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_41a3c87a37aea616ee459369e1"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "system_account"`);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class SystemAccounts21740129169650 {
|
||||||
|
name = 'SystemAccounts21740129169650'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP CONSTRAINT "FK_ab1bc0c1e209daa77b8e8d212ad"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "proxyAccountId"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "proxyAccountId" character varying(32)`);
|
||||||
|
const proxyAccountId = await queryRunner.query(`SELECT "userId" FROM "system_account" WHERE "type" = 'proxy' ORDER BY "id" DESC LIMIT 1`);
|
||||||
|
if (proxyAccountId && proxyAccountId.length >= 1) {
|
||||||
|
await queryRunner.query(`UPDATE "meta" SET "proxyAccountId" = '${proxyAccountId[0].userId}'`);
|
||||||
|
}
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD CONSTRAINT "FK_ab1bc0c1e209daa77b8e8d212ad" FOREIGN KEY ("proxyAccountId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class SystemAccounts31740133121105 {
|
||||||
|
name = 'SystemAccounts31740133121105'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "rootUserId" character varying(32)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD CONSTRAINT "FK_c80e4079d632f95eac06a9d28cc" FOREIGN KEY ("rootUserId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
|
||||||
|
|
||||||
|
const users = await queryRunner.query(`SELECT "id" FROM "user" WHERE "isRoot" = true LIMIT 1`);
|
||||||
|
if (users.length > 0) {
|
||||||
|
await queryRunner.query(`UPDATE "meta" SET "rootUserId" = $1`, [users[0].id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP CONSTRAINT "FK_c80e4079d632f95eac06a9d28cc"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "rootUserId"`);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class SystemAccounts41740993126937 {
|
||||||
|
name = 'SystemAccounts41740993126937'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isRoot"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
// down 実行時は isRoot = true のユーザーが存在しなくなるため手動で対応する必要あり
|
||||||
|
await queryRunner.query(`ALTER TABLE "user" ADD "isRoot" boolean NOT NULL DEFAULT false`);
|
||||||
|
}
|
||||||
|
}
|
@@ -133,7 +133,7 @@ const $meta: Provider = {
|
|||||||
for (const key in body.after) {
|
for (const key in body.after) {
|
||||||
(meta as any)[key] = (body.after as any)[key];
|
(meta as any)[key] = (body.after as any)[key];
|
||||||
}
|
}
|
||||||
meta.proxyAccount = null; // joinなカラムは通常取ってこないので
|
meta.rootUser = null; // joinなカラムは通常取ってこないので
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@@ -10,9 +10,9 @@ import { bindThis } from '@/decorators.js';
|
|||||||
import type { AbuseUserReportsRepository, MiAbuseUserReport, MiUser, UsersRepository } from '@/models/_.js';
|
import type { AbuseUserReportsRepository, MiAbuseUserReport, MiUser, UsersRepository } from '@/models/_.js';
|
||||||
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
|
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
|
||||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
import { IdService } from './IdService.js';
|
import { IdService } from './IdService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -27,7 +27,7 @@ export class AbuseReportService {
|
|||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private abuseReportNotificationService: AbuseReportNotificationService,
|
private abuseReportNotificationService: AbuseReportNotificationService,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
private instanceActorService: InstanceActorService,
|
private systemAccountService: SystemAccountService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
private moderationLogService: ModerationLogService,
|
private moderationLogService: ModerationLogService,
|
||||||
) {
|
) {
|
||||||
@@ -136,7 +136,7 @@ export class AbuseReportService {
|
|||||||
forwarded: true,
|
forwarded: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const actor = await this.instanceActorService.getInstanceActor();
|
const actor = await this.systemAccountService.fetch('actor');
|
||||||
const targetUser = await this.usersRepository.findOneByOrFail({ id: report.targetUserId });
|
const targetUser = await this.usersRepository.findOneByOrFail({ id: report.targetUserId });
|
||||||
|
|
||||||
const flag = this.apRendererService.renderFlag(actor, targetUser.uri!, report.comment);
|
const flag = this.apRendererService.renderFlag(actor, targetUser.uri!, report.comment);
|
||||||
|
@@ -20,10 +20,10 @@ import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
|||||||
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
|
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
|
||||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { ProxyAccountService } from '@/core/ProxyAccountService.js';
|
|
||||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
import InstanceChart from '@/core/chart/charts/instance.js';
|
import InstanceChart from '@/core/chart/charts/instance.js';
|
||||||
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
|
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AccountMoveService {
|
export class AccountMoveService {
|
||||||
@@ -55,12 +55,12 @@ export class AccountMoveService {
|
|||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
private apDeliverManagerService: ApDeliverManagerService,
|
private apDeliverManagerService: ApDeliverManagerService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private proxyAccountService: ProxyAccountService,
|
|
||||||
private perUserFollowingChart: PerUserFollowingChart,
|
private perUserFollowingChart: PerUserFollowingChart,
|
||||||
private federatedInstanceService: FederatedInstanceService,
|
private federatedInstanceService: FederatedInstanceService,
|
||||||
private instanceChart: InstanceChart,
|
private instanceChart: InstanceChart,
|
||||||
private relayService: RelayService,
|
private relayService: RelayService,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
|
private systemAccountService: SystemAccountService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,11 +126,11 @@ export class AccountMoveService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// follow the new account
|
// follow the new account
|
||||||
const proxy = await this.proxyAccountService.fetch();
|
const proxy = await this.systemAccountService.fetch('proxy');
|
||||||
const followings = await this.followingsRepository.findBy({
|
const followings = await this.followingsRepository.findBy({
|
||||||
followeeId: src.id,
|
followeeId: src.id,
|
||||||
followerHost: IsNull(), // follower is local
|
followerHost: IsNull(), // follower is local
|
||||||
followerId: proxy ? Not(proxy.id) : undefined,
|
followerId: Not(proxy.id),
|
||||||
});
|
});
|
||||||
const followJobs = followings.map(following => ({
|
const followJobs = followings.map(following => ({
|
||||||
from: { id: following.followerId },
|
from: { id: following.followerId },
|
||||||
@@ -250,10 +250,8 @@ export class AccountMoveService {
|
|||||||
|
|
||||||
// Have the proxy account follow the new account in the same way as UserListService.push
|
// Have the proxy account follow the new account in the same way as UserListService.push
|
||||||
if (this.userEntityService.isRemoteUser(dst)) {
|
if (this.userEntityService.isRemoteUser(dst)) {
|
||||||
const proxy = await this.proxyAccountService.fetch();
|
const proxy = await this.systemAccountService.fetch('proxy');
|
||||||
if (proxy) {
|
this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: dst.id } }]);
|
||||||
this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: dst.id } }]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -24,7 +24,6 @@ import { AppLockService } from './AppLockService.js';
|
|||||||
import { AchievementService } from './AchievementService.js';
|
import { AchievementService } from './AchievementService.js';
|
||||||
import { AvatarDecorationService } from './AvatarDecorationService.js';
|
import { AvatarDecorationService } from './AvatarDecorationService.js';
|
||||||
import { CaptchaService } from './CaptchaService.js';
|
import { CaptchaService } from './CaptchaService.js';
|
||||||
import { CreateSystemUserService } from './CreateSystemUserService.js';
|
|
||||||
import { CustomEmojiService } from './CustomEmojiService.js';
|
import { CustomEmojiService } from './CustomEmojiService.js';
|
||||||
import { DeleteAccountService } from './DeleteAccountService.js';
|
import { DeleteAccountService } from './DeleteAccountService.js';
|
||||||
import { DownloadService } from './DownloadService.js';
|
import { DownloadService } from './DownloadService.js';
|
||||||
@@ -37,7 +36,7 @@ import { HashtagService } from './HashtagService.js';
|
|||||||
import { HttpRequestService } from './HttpRequestService.js';
|
import { HttpRequestService } from './HttpRequestService.js';
|
||||||
import { IdService } from './IdService.js';
|
import { IdService } from './IdService.js';
|
||||||
import { ImageProcessingService } from './ImageProcessingService.js';
|
import { ImageProcessingService } from './ImageProcessingService.js';
|
||||||
import { InstanceActorService } from './InstanceActorService.js';
|
import { SystemAccountService } from './SystemAccountService.js';
|
||||||
import { InternalStorageService } from './InternalStorageService.js';
|
import { InternalStorageService } from './InternalStorageService.js';
|
||||||
import { MetaService } from './MetaService.js';
|
import { MetaService } from './MetaService.js';
|
||||||
import { MfmService } from './MfmService.js';
|
import { MfmService } from './MfmService.js';
|
||||||
@@ -69,7 +68,6 @@ import { UserSuspendService } from './UserSuspendService.js';
|
|||||||
import { UserAuthService } from './UserAuthService.js';
|
import { UserAuthService } from './UserAuthService.js';
|
||||||
import { VideoProcessingService } from './VideoProcessingService.js';
|
import { VideoProcessingService } from './VideoProcessingService.js';
|
||||||
import { UserWebhookService } from './UserWebhookService.js';
|
import { UserWebhookService } from './UserWebhookService.js';
|
||||||
import { ProxyAccountService } from './ProxyAccountService.js';
|
|
||||||
import { UtilityService } from './UtilityService.js';
|
import { UtilityService } from './UtilityService.js';
|
||||||
import { FileInfoService } from './FileInfoService.js';
|
import { FileInfoService } from './FileInfoService.js';
|
||||||
import { SearchService } from './SearchService.js';
|
import { SearchService } from './SearchService.js';
|
||||||
@@ -167,7 +165,6 @@ const $AppLockService: Provider = { provide: 'AppLockService', useExisting: AppL
|
|||||||
const $AchievementService: Provider = { provide: 'AchievementService', useExisting: AchievementService };
|
const $AchievementService: Provider = { provide: 'AchievementService', useExisting: AchievementService };
|
||||||
const $AvatarDecorationService: Provider = { provide: 'AvatarDecorationService', useExisting: AvatarDecorationService };
|
const $AvatarDecorationService: Provider = { provide: 'AvatarDecorationService', useExisting: AvatarDecorationService };
|
||||||
const $CaptchaService: Provider = { provide: 'CaptchaService', useExisting: CaptchaService };
|
const $CaptchaService: Provider = { provide: 'CaptchaService', useExisting: CaptchaService };
|
||||||
const $CreateSystemUserService: Provider = { provide: 'CreateSystemUserService', useExisting: CreateSystemUserService };
|
|
||||||
const $CustomEmojiService: Provider = { provide: 'CustomEmojiService', useExisting: CustomEmojiService };
|
const $CustomEmojiService: Provider = { provide: 'CustomEmojiService', useExisting: CustomEmojiService };
|
||||||
const $DeleteAccountService: Provider = { provide: 'DeleteAccountService', useExisting: DeleteAccountService };
|
const $DeleteAccountService: Provider = { provide: 'DeleteAccountService', useExisting: DeleteAccountService };
|
||||||
const $DownloadService: Provider = { provide: 'DownloadService', useExisting: DownloadService };
|
const $DownloadService: Provider = { provide: 'DownloadService', useExisting: DownloadService };
|
||||||
@@ -180,7 +177,6 @@ const $HashtagService: Provider = { provide: 'HashtagService', useExisting: Hash
|
|||||||
const $HttpRequestService: Provider = { provide: 'HttpRequestService', useExisting: HttpRequestService };
|
const $HttpRequestService: Provider = { provide: 'HttpRequestService', useExisting: HttpRequestService };
|
||||||
const $IdService: Provider = { provide: 'IdService', useExisting: IdService };
|
const $IdService: Provider = { provide: 'IdService', useExisting: IdService };
|
||||||
const $ImageProcessingService: Provider = { provide: 'ImageProcessingService', useExisting: ImageProcessingService };
|
const $ImageProcessingService: Provider = { provide: 'ImageProcessingService', useExisting: ImageProcessingService };
|
||||||
const $InstanceActorService: Provider = { provide: 'InstanceActorService', useExisting: InstanceActorService };
|
|
||||||
const $InternalStorageService: Provider = { provide: 'InternalStorageService', useExisting: InternalStorageService };
|
const $InternalStorageService: Provider = { provide: 'InternalStorageService', useExisting: InternalStorageService };
|
||||||
const $MetaService: Provider = { provide: 'MetaService', useExisting: MetaService };
|
const $MetaService: Provider = { provide: 'MetaService', useExisting: MetaService };
|
||||||
const $MfmService: Provider = { provide: 'MfmService', useExisting: MfmService };
|
const $MfmService: Provider = { provide: 'MfmService', useExisting: MfmService };
|
||||||
@@ -191,7 +187,7 @@ const $NotePiningService: Provider = { provide: 'NotePiningService', useExisting
|
|||||||
const $NoteReadService: Provider = { provide: 'NoteReadService', useExisting: NoteReadService };
|
const $NoteReadService: Provider = { provide: 'NoteReadService', useExisting: NoteReadService };
|
||||||
const $NotificationService: Provider = { provide: 'NotificationService', useExisting: NotificationService };
|
const $NotificationService: Provider = { provide: 'NotificationService', useExisting: NotificationService };
|
||||||
const $PollService: Provider = { provide: 'PollService', useExisting: PollService };
|
const $PollService: Provider = { provide: 'PollService', useExisting: PollService };
|
||||||
const $ProxyAccountService: Provider = { provide: 'ProxyAccountService', useExisting: ProxyAccountService };
|
const $SystemAccountService: Provider = { provide: 'SystemAccountService', useExisting: SystemAccountService };
|
||||||
const $PushNotificationService: Provider = { provide: 'PushNotificationService', useExisting: PushNotificationService };
|
const $PushNotificationService: Provider = { provide: 'PushNotificationService', useExisting: PushNotificationService };
|
||||||
const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService };
|
const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService };
|
||||||
const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService };
|
const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService };
|
||||||
@@ -318,7 +314,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
AchievementService,
|
AchievementService,
|
||||||
AvatarDecorationService,
|
AvatarDecorationService,
|
||||||
CaptchaService,
|
CaptchaService,
|
||||||
CreateSystemUserService,
|
|
||||||
CustomEmojiService,
|
CustomEmojiService,
|
||||||
DeleteAccountService,
|
DeleteAccountService,
|
||||||
DownloadService,
|
DownloadService,
|
||||||
@@ -331,7 +326,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
HttpRequestService,
|
HttpRequestService,
|
||||||
IdService,
|
IdService,
|
||||||
ImageProcessingService,
|
ImageProcessingService,
|
||||||
InstanceActorService,
|
|
||||||
InternalStorageService,
|
InternalStorageService,
|
||||||
MetaService,
|
MetaService,
|
||||||
MfmService,
|
MfmService,
|
||||||
@@ -342,7 +336,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
NoteReadService,
|
NoteReadService,
|
||||||
NotificationService,
|
NotificationService,
|
||||||
PollService,
|
PollService,
|
||||||
ProxyAccountService,
|
SystemAccountService,
|
||||||
PushNotificationService,
|
PushNotificationService,
|
||||||
QueryService,
|
QueryService,
|
||||||
ReactionService,
|
ReactionService,
|
||||||
@@ -465,7 +459,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$AchievementService,
|
$AchievementService,
|
||||||
$AvatarDecorationService,
|
$AvatarDecorationService,
|
||||||
$CaptchaService,
|
$CaptchaService,
|
||||||
$CreateSystemUserService,
|
|
||||||
$CustomEmojiService,
|
$CustomEmojiService,
|
||||||
$DeleteAccountService,
|
$DeleteAccountService,
|
||||||
$DownloadService,
|
$DownloadService,
|
||||||
@@ -478,7 +471,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$HttpRequestService,
|
$HttpRequestService,
|
||||||
$IdService,
|
$IdService,
|
||||||
$ImageProcessingService,
|
$ImageProcessingService,
|
||||||
$InstanceActorService,
|
|
||||||
$InternalStorageService,
|
$InternalStorageService,
|
||||||
$MetaService,
|
$MetaService,
|
||||||
$MfmService,
|
$MfmService,
|
||||||
@@ -489,7 +481,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$NoteReadService,
|
$NoteReadService,
|
||||||
$NotificationService,
|
$NotificationService,
|
||||||
$PollService,
|
$PollService,
|
||||||
$ProxyAccountService,
|
$SystemAccountService,
|
||||||
$PushNotificationService,
|
$PushNotificationService,
|
||||||
$QueryService,
|
$QueryService,
|
||||||
$ReactionService,
|
$ReactionService,
|
||||||
@@ -613,7 +605,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
AchievementService,
|
AchievementService,
|
||||||
AvatarDecorationService,
|
AvatarDecorationService,
|
||||||
CaptchaService,
|
CaptchaService,
|
||||||
CreateSystemUserService,
|
|
||||||
CustomEmojiService,
|
CustomEmojiService,
|
||||||
DeleteAccountService,
|
DeleteAccountService,
|
||||||
DownloadService,
|
DownloadService,
|
||||||
@@ -626,7 +617,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
HttpRequestService,
|
HttpRequestService,
|
||||||
IdService,
|
IdService,
|
||||||
ImageProcessingService,
|
ImageProcessingService,
|
||||||
InstanceActorService,
|
|
||||||
InternalStorageService,
|
InternalStorageService,
|
||||||
MetaService,
|
MetaService,
|
||||||
MfmService,
|
MfmService,
|
||||||
@@ -637,7 +627,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
NoteReadService,
|
NoteReadService,
|
||||||
NotificationService,
|
NotificationService,
|
||||||
PollService,
|
PollService,
|
||||||
ProxyAccountService,
|
SystemAccountService,
|
||||||
PushNotificationService,
|
PushNotificationService,
|
||||||
QueryService,
|
QueryService,
|
||||||
ReactionService,
|
ReactionService,
|
||||||
@@ -759,7 +749,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$AchievementService,
|
$AchievementService,
|
||||||
$AvatarDecorationService,
|
$AvatarDecorationService,
|
||||||
$CaptchaService,
|
$CaptchaService,
|
||||||
$CreateSystemUserService,
|
|
||||||
$CustomEmojiService,
|
$CustomEmojiService,
|
||||||
$DeleteAccountService,
|
$DeleteAccountService,
|
||||||
$DownloadService,
|
$DownloadService,
|
||||||
@@ -772,7 +761,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$HttpRequestService,
|
$HttpRequestService,
|
||||||
$IdService,
|
$IdService,
|
||||||
$ImageProcessingService,
|
$ImageProcessingService,
|
||||||
$InstanceActorService,
|
|
||||||
$InternalStorageService,
|
$InternalStorageService,
|
||||||
$MetaService,
|
$MetaService,
|
||||||
$MfmService,
|
$MfmService,
|
||||||
@@ -783,7 +771,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$NoteReadService,
|
$NoteReadService,
|
||||||
$NotificationService,
|
$NotificationService,
|
||||||
$PollService,
|
$PollService,
|
||||||
$ProxyAccountService,
|
$SystemAccountService,
|
||||||
$PushNotificationService,
|
$PushNotificationService,
|
||||||
$QueryService,
|
$QueryService,
|
||||||
$ReactionService,
|
$ReactionService,
|
||||||
|
@@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { randomUUID } from 'node:crypto';
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import bcrypt from 'bcryptjs';
|
|
||||||
import { IsNull, DataSource } from 'typeorm';
|
|
||||||
import { genRsaKeyPair } from '@/misc/gen-key-pair.js';
|
|
||||||
import { MiUser } from '@/models/User.js';
|
|
||||||
import { MiUserProfile } from '@/models/UserProfile.js';
|
|
||||||
import { IdService } from '@/core/IdService.js';
|
|
||||||
import { MiUserKeypair } from '@/models/UserKeypair.js';
|
|
||||||
import { MiUsedUsername } from '@/models/UsedUsername.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import generateNativeUserToken from '@/misc/generate-native-user-token.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CreateSystemUserService {
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.db)
|
|
||||||
private db: DataSource,
|
|
||||||
|
|
||||||
private idService: IdService,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public async createSystemUser(username: string): Promise<MiUser> {
|
|
||||||
const password = randomUUID();
|
|
||||||
|
|
||||||
// Generate hash of password
|
|
||||||
const salt = await bcrypt.genSalt(8);
|
|
||||||
const hash = await bcrypt.hash(password, salt);
|
|
||||||
|
|
||||||
// Generate secret
|
|
||||||
const secret = generateNativeUserToken();
|
|
||||||
|
|
||||||
const keyPair = await genRsaKeyPair();
|
|
||||||
|
|
||||||
let account!: MiUser;
|
|
||||||
|
|
||||||
// Start transaction
|
|
||||||
await this.db.transaction(async transactionalEntityManager => {
|
|
||||||
const exist = await transactionalEntityManager.findOneBy(MiUser, {
|
|
||||||
usernameLower: username.toLowerCase(),
|
|
||||||
host: IsNull(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (exist) throw new Error('the user is already exists');
|
|
||||||
|
|
||||||
account = await transactionalEntityManager.insert(MiUser, {
|
|
||||||
id: this.idService.gen(),
|
|
||||||
username: username,
|
|
||||||
usernameLower: username.toLowerCase(),
|
|
||||||
host: null,
|
|
||||||
token: secret,
|
|
||||||
isRoot: false,
|
|
||||||
isLocked: true,
|
|
||||||
isExplorable: false,
|
|
||||||
isBot: true,
|
|
||||||
}).then(x => transactionalEntityManager.findOneByOrFail(MiUser, x.identifiers[0]));
|
|
||||||
|
|
||||||
await transactionalEntityManager.insert(MiUserKeypair, {
|
|
||||||
publicKey: keyPair.publicKey,
|
|
||||||
privateKey: keyPair.privateKey,
|
|
||||||
userId: account.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
await transactionalEntityManager.insert(MiUserProfile, {
|
|
||||||
userId: account.id,
|
|
||||||
autoAcceptFollowed: false,
|
|
||||||
password: hash,
|
|
||||||
});
|
|
||||||
|
|
||||||
await transactionalEntityManager.insert(MiUsedUsername, {
|
|
||||||
createdAt: new Date(),
|
|
||||||
username: username.toLowerCase(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return account;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Not, IsNull } from 'typeorm';
|
import { Not, IsNull } from 'typeorm';
|
||||||
import type { FollowingsRepository, MiUser, UsersRepository } from '@/models/_.js';
|
import type { FollowingsRepository, MiMeta, MiUser, UsersRepository } from '@/models/_.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
@@ -13,10 +13,14 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
|
|||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteAccountService {
|
export class DeleteAccountService {
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
@@ -28,6 +32,7 @@ export class DeleteAccountService {
|
|||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private moderationLogService: ModerationLogService,
|
private moderationLogService: ModerationLogService,
|
||||||
|
private systemAccountService: SystemAccountService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,8 +41,13 @@ export class DeleteAccountService {
|
|||||||
id: string;
|
id: string;
|
||||||
host: string | null;
|
host: string | null;
|
||||||
}, moderator?: MiUser): Promise<void> {
|
}, moderator?: MiUser): Promise<void> {
|
||||||
|
if (this.meta.rootUserId === user.id) throw new Error('cannot delete a root account');
|
||||||
|
|
||||||
const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
|
const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
|
||||||
if (_user.isRoot) throw new Error('cannot delete a root account');
|
|
||||||
|
if (user.host === null && _user.username.includes('.')) {
|
||||||
|
throw new Error('cannot delete a system account');
|
||||||
|
}
|
||||||
|
|
||||||
if (moderator != null) {
|
if (moderator != null) {
|
||||||
this.moderationLogService.log(moderator, 'deleteAccount', {
|
this.moderationLogService.log(moderator, 'deleteAccount', {
|
||||||
|
@@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { IsNull, Not } from 'typeorm';
|
|
||||||
import type { MiLocalUser } from '@/models/User.js';
|
|
||||||
import type { UsersRepository } from '@/models/_.js';
|
|
||||||
import { MemorySingleCache } from '@/misc/cache.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
|
|
||||||
const ACTOR_USERNAME = 'instance.actor' as const;
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class InstanceActorService {
|
|
||||||
private cache: MemorySingleCache<MiLocalUser>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.usersRepository)
|
|
||||||
private usersRepository: UsersRepository,
|
|
||||||
|
|
||||||
private createSystemUserService: CreateSystemUserService,
|
|
||||||
) {
|
|
||||||
this.cache = new MemorySingleCache<MiLocalUser>(Infinity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public async realLocalUsersPresent(): Promise<boolean> {
|
|
||||||
return await this.usersRepository.existsBy({
|
|
||||||
host: IsNull(),
|
|
||||||
username: Not(ACTOR_USERNAME),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public async getInstanceActor(): Promise<MiLocalUser> {
|
|
||||||
const cached = this.cache.get();
|
|
||||||
if (cached) return cached;
|
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({
|
|
||||||
host: IsNull(),
|
|
||||||
username: ACTOR_USERNAME,
|
|
||||||
}) as MiLocalUser | undefined;
|
|
||||||
|
|
||||||
if (user) {
|
|
||||||
this.cache.set(user);
|
|
||||||
return user;
|
|
||||||
} else {
|
|
||||||
const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as MiLocalUser;
|
|
||||||
this.cache.set(created);
|
|
||||||
return created;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -53,7 +53,7 @@ export class MetaService implements OnApplicationShutdown {
|
|||||||
case 'metaUpdated': {
|
case 'metaUpdated': {
|
||||||
this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
||||||
...(body.after),
|
...(body.after),
|
||||||
proxyAccount: null, // joinなカラムは通常取ってこないので
|
rootUser: null, // joinなカラムは通常取ってこないので
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -113,17 +113,20 @@ export class MetaService implements OnApplicationShutdown {
|
|||||||
|
|
||||||
if (before) {
|
if (before) {
|
||||||
await transactionalEntityManager.update(MiMeta, before.id, data);
|
await transactionalEntityManager.update(MiMeta, before.id, data);
|
||||||
|
|
||||||
const metas = await transactionalEntityManager.find(MiMeta, {
|
|
||||||
order: {
|
|
||||||
id: 'DESC',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return metas[0];
|
|
||||||
} else {
|
} else {
|
||||||
return await transactionalEntityManager.save(MiMeta, data);
|
await transactionalEntityManager.save(MiMeta, {
|
||||||
|
...data,
|
||||||
|
id: 'x',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const afters = await transactionalEntityManager.find(MiMeta, {
|
||||||
|
order: {
|
||||||
|
id: 'DESC',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return afters[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data.hiddenTags) {
|
if (data.hiddenTags) {
|
||||||
|
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import type { MiMeta, UsersRepository } from '@/models/_.js';
|
|
||||||
import type { MiLocalUser } from '@/models/User.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ProxyAccountService {
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.meta)
|
|
||||||
private meta: MiMeta,
|
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
|
||||||
private usersRepository: UsersRepository,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public async fetch(): Promise<MiLocalUser | null> {
|
|
||||||
if (this.meta.proxyAccountId == null) return null;
|
|
||||||
return await this.usersRepository.findOneByOrFail({ id: this.meta.proxyAccountId }) as MiLocalUser;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -4,53 +4,34 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { IsNull } from 'typeorm';
|
import type { MiUser } from '@/models/User.js';
|
||||||
import type { MiLocalUser, MiUser } from '@/models/User.js';
|
import type { RelaysRepository } from '@/models/_.js';
|
||||||
import type { RelaysRepository, UsersRepository } from '@/models/_.js';
|
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { MemorySingleCache } from '@/misc/cache.js';
|
import { MemorySingleCache } from '@/misc/cache.js';
|
||||||
import type { MiRelay } from '@/models/Relay.js';
|
import type { MiRelay } from '@/models/Relay.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
|
|
||||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { deepClone } from '@/misc/clone.js';
|
import { deepClone } from '@/misc/clone.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
const ACTOR_USERNAME = 'relay.actor' as const;
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RelayService {
|
export class RelayService {
|
||||||
private relaysCache: MemorySingleCache<MiRelay[]>;
|
private relaysCache: MemorySingleCache<MiRelay[]>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.usersRepository)
|
|
||||||
private usersRepository: UsersRepository,
|
|
||||||
|
|
||||||
@Inject(DI.relaysRepository)
|
@Inject(DI.relaysRepository)
|
||||||
private relaysRepository: RelaysRepository,
|
private relaysRepository: RelaysRepository,
|
||||||
|
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
private createSystemUserService: CreateSystemUserService,
|
private systemAccountService: SystemAccountService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
) {
|
) {
|
||||||
this.relaysCache = new MemorySingleCache<MiRelay[]>(1000 * 60 * 10); // 10m
|
this.relaysCache = new MemorySingleCache<MiRelay[]>(1000 * 60 * 10); // 10m
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
|
||||||
private async getRelayActor(): Promise<MiLocalUser> {
|
|
||||||
const user = await this.usersRepository.findOneBy({
|
|
||||||
host: IsNull(),
|
|
||||||
username: ACTOR_USERNAME,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (user) return user as MiLocalUser;
|
|
||||||
|
|
||||||
const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME);
|
|
||||||
return created as MiLocalUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async addRelay(inbox: string): Promise<MiRelay> {
|
public async addRelay(inbox: string): Promise<MiRelay> {
|
||||||
const relay = await this.relaysRepository.insertOne({
|
const relay = await this.relaysRepository.insertOne({
|
||||||
@@ -59,8 +40,8 @@ export class RelayService {
|
|||||||
status: 'requesting',
|
status: 'requesting',
|
||||||
});
|
});
|
||||||
|
|
||||||
const relayActor = await this.getRelayActor();
|
const relayActor = await this.systemAccountService.fetch('relay');
|
||||||
const follow = await this.apRendererService.renderFollowRelay(relay, relayActor);
|
const follow = this.apRendererService.renderFollowRelay(relay, relayActor);
|
||||||
const activity = this.apRendererService.addContext(follow);
|
const activity = this.apRendererService.addContext(follow);
|
||||||
this.queueService.deliver(relayActor, activity, relay.inbox, false);
|
this.queueService.deliver(relayActor, activity, relay.inbox, false);
|
||||||
|
|
||||||
@@ -77,7 +58,7 @@ export class RelayService {
|
|||||||
throw new Error('relay not found');
|
throw new Error('relay not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const relayActor = await this.getRelayActor();
|
const relayActor = await this.systemAccountService.fetch('relay');
|
||||||
const follow = this.apRendererService.renderFollowRelay(relay, relayActor);
|
const follow = this.apRendererService.renderFollowRelay(relay, relayActor);
|
||||||
const undo = this.apRendererService.renderUndo(follow, relayActor);
|
const undo = this.apRendererService.renderUndo(follow, relayActor);
|
||||||
const activity = this.apRendererService.addContext(undo);
|
const activity = this.apRendererService.addContext(undo);
|
||||||
|
@@ -101,7 +101,6 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
private rootUserIdCache: MemorySingleCache<MiUser['id']>;
|
|
||||||
private rolesCache: MemorySingleCache<MiRole[]>;
|
private rolesCache: MemorySingleCache<MiRole[]>;
|
||||||
private roleAssignmentByUserIdCache: MemoryKVCache<MiRoleAssignment[]>;
|
private roleAssignmentByUserIdCache: MemoryKVCache<MiRoleAssignment[]>;
|
||||||
private notificationService: NotificationService;
|
private notificationService: NotificationService;
|
||||||
@@ -137,7 +136,6 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||||||
private moderationLogService: ModerationLogService,
|
private moderationLogService: ModerationLogService,
|
||||||
private fanoutTimelineService: FanoutTimelineService,
|
private fanoutTimelineService: FanoutTimelineService,
|
||||||
) {
|
) {
|
||||||
this.rootUserIdCache = new MemorySingleCache<MiUser['id']>(1000 * 60 * 60 * 24 * 7); // 1week. rootユーザのIDは不変なので長めに
|
|
||||||
this.rolesCache = new MemorySingleCache<MiRole[]>(1000 * 60 * 60); // 1h
|
this.rolesCache = new MemorySingleCache<MiRole[]>(1000 * 60 * 60); // 1h
|
||||||
this.roleAssignmentByUserIdCache = new MemoryKVCache<MiRoleAssignment[]>(1000 * 60 * 5); // 5m
|
this.roleAssignmentByUserIdCache = new MemoryKVCache<MiRoleAssignment[]>(1000 * 60 * 5); // 5m
|
||||||
|
|
||||||
@@ -406,15 +404,15 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async isModerator(user: { id: MiUser['id']; isRoot: MiUser['isRoot'] } | null): Promise<boolean> {
|
public async isModerator(user: { id: MiUser['id'] } | null): Promise<boolean> {
|
||||||
if (user == null) return false;
|
if (user == null) return false;
|
||||||
return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isModerator || r.isAdministrator);
|
return (this.meta.rootUserId === user.id) || (await this.getUserRoles(user.id)).some(r => r.isModerator || r.isAdministrator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async isAdministrator(user: { id: MiUser['id']; isRoot: MiUser['isRoot'] } | null): Promise<boolean> {
|
public async isAdministrator(user: { id: MiUser['id'] } | null): Promise<boolean> {
|
||||||
if (user == null) return false;
|
if (user == null) return false;
|
||||||
return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isAdministrator);
|
return (this.meta.rootUserId === user.id) || (await this.getUserRoles(user.id)).some(r => r.isAdministrator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
@@ -463,16 +461,8 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||||||
.map(a => a.userId),
|
.map(a => a.userId),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (includeRoot) {
|
if (includeRoot && this.meta.rootUserId) {
|
||||||
const rootUserId = await this.rootUserIdCache.fetch(async () => {
|
resultSet.add(this.meta.rootUserId);
|
||||||
const it = await this.usersRepository.createQueryBuilder('users')
|
|
||||||
.select('id')
|
|
||||||
.where({ isRoot: true })
|
|
||||||
.getRawOne<{ id: string }>();
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
return it!.id;
|
|
||||||
});
|
|
||||||
resultSet.add(rootUserId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...resultSet].sort((x, y) => x.localeCompare(y));
|
return [...resultSet].sort((x, y) => x.localeCompare(y));
|
||||||
|
@@ -14,13 +14,14 @@ import { MiUserProfile } from '@/models/UserProfile.js';
|
|||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { MiUserKeypair } from '@/models/UserKeypair.js';
|
import { MiUserKeypair } from '@/models/UserKeypair.js';
|
||||||
import { MiUsedUsername } from '@/models/UsedUsername.js';
|
import { MiUsedUsername } from '@/models/UsedUsername.js';
|
||||||
import generateUserToken from '@/misc/generate-native-user-token.js';
|
import { generateNativeUserToken } from '@/misc/token.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import UsersChart from '@/core/chart/charts/users.js';
|
import UsersChart from '@/core/chart/charts/users.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { UserService } from '@/core/UserService.js';
|
import { UserService } from '@/core/UserService.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SignupService {
|
export class SignupService {
|
||||||
@@ -41,7 +42,8 @@ export class SignupService {
|
|||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private instanceActorService: InstanceActorService,
|
private systemAccountService: SystemAccountService,
|
||||||
|
private metaService: MetaService,
|
||||||
private usersChart: UsersChart,
|
private usersChart: UsersChart,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
@@ -74,7 +76,7 @@ export class SignupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate secret
|
// Generate secret
|
||||||
const secret = generateUserToken();
|
const secret = generateNativeUserToken();
|
||||||
|
|
||||||
// Check username duplication
|
// Check username duplication
|
||||||
if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
|
if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
|
||||||
@@ -86,9 +88,7 @@ export class SignupService {
|
|||||||
throw new Error('USED_USERNAME');
|
throw new Error('USED_USERNAME');
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTheFirstUser = !await this.instanceActorService.realLocalUsersPresent();
|
if (!opts.ignorePreservedUsernames && this.meta.rootUserId != null) {
|
||||||
|
|
||||||
if (!opts.ignorePreservedUsernames && !isTheFirstUser) {
|
|
||||||
const isPreserved = this.meta.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
|
const isPreserved = this.meta.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
|
||||||
if (isPreserved) {
|
if (isPreserved) {
|
||||||
throw new Error('USED_USERNAME');
|
throw new Error('USED_USERNAME');
|
||||||
@@ -129,7 +129,6 @@ export class SignupService {
|
|||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
host: this.utilityService.toPunyNullable(host),
|
host: this.utilityService.toPunyNullable(host),
|
||||||
token: secret,
|
token: secret,
|
||||||
isRoot: isTheFirstUser,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await transactionalEntityManager.save(new MiUserKeypair({
|
await transactionalEntityManager.save(new MiUserKeypair({
|
||||||
@@ -153,6 +152,10 @@ export class SignupService {
|
|||||||
this.usersChart.update(account, true);
|
this.usersChart.update(account, true);
|
||||||
this.userService.notifySystemWebhook(account, 'userCreated');
|
this.userService.notifySystemWebhook(account, 'userCreated');
|
||||||
|
|
||||||
|
if (this.meta.rootUserId == null) {
|
||||||
|
await this.metaService.update({ rootUserId: account.id });
|
||||||
|
}
|
||||||
|
|
||||||
return { account, secret };
|
return { account, secret };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
172
packages/backend/src/core/SystemAccountService.ts
Normal file
172
packages/backend/src/core/SystemAccountService.ts
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { randomUUID } from 'node:crypto';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { DataSource, IsNull } from 'typeorm';
|
||||||
|
import bcrypt from 'bcryptjs';
|
||||||
|
import { MiLocalUser, MiUser } from '@/models/User.js';
|
||||||
|
import { MiSystemAccount, MiUsedUsername, MiUserKeypair, MiUserProfile, type UsersRepository, type SystemAccountsRepository } from '@/models/_.js';
|
||||||
|
import type { MiMeta, UserProfilesRepository } from '@/models/_.js';
|
||||||
|
import { MemoryKVCache } from '@/misc/cache.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { generateNativeUserToken } from '@/misc/token.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { genRsaKeyPair } from '@/misc/gen-key-pair.js';
|
||||||
|
|
||||||
|
export const SYSTEM_ACCOUNT_TYPES = ['actor', 'relay', 'proxy'] as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SystemAccountService {
|
||||||
|
private cache: MemoryKVCache<MiLocalUser>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.db)
|
||||||
|
private db: DataSource,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
|
@Inject(DI.systemAccountsRepository)
|
||||||
|
private systemAccountsRepository: SystemAccountsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.usersRepository)
|
||||||
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@Inject(DI.userProfilesRepository)
|
||||||
|
private userProfilesRepository: UserProfilesRepository,
|
||||||
|
|
||||||
|
private idService: IdService,
|
||||||
|
) {
|
||||||
|
this.cache = new MemoryKVCache<MiLocalUser>(1000 * 60 * 10); // 10m
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async list(): Promise<MiSystemAccount[]> {
|
||||||
|
const accounts = await this.systemAccountsRepository.findBy({});
|
||||||
|
|
||||||
|
return accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async fetch(type: typeof SYSTEM_ACCOUNT_TYPES[number]): Promise<MiLocalUser> {
|
||||||
|
const cached = this.cache.get(type);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
|
const systemAccount = await this.systemAccountsRepository.findOne({
|
||||||
|
where: { type: type },
|
||||||
|
relations: ['user'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (systemAccount) {
|
||||||
|
this.cache.set(type, systemAccount.user as MiLocalUser);
|
||||||
|
return systemAccount.user as MiLocalUser;
|
||||||
|
} else {
|
||||||
|
const created = await this.createCorrespondingUser(type, {
|
||||||
|
username: `system.${type}`, // NOTE: (できれば避けたいが) . が含まれるかどうかでシステムアカウントかどうかを判定している処理もあるので変えないように
|
||||||
|
name: this.meta.name,
|
||||||
|
});
|
||||||
|
this.cache.set(type, created);
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async createCorrespondingUser(type: typeof SYSTEM_ACCOUNT_TYPES[number], extra: {
|
||||||
|
username: MiUser['username'];
|
||||||
|
name?: MiUser['name'];
|
||||||
|
}): Promise<MiLocalUser> {
|
||||||
|
const password = randomUUID();
|
||||||
|
|
||||||
|
// Generate hash of password
|
||||||
|
const salt = await bcrypt.genSalt(8);
|
||||||
|
const hash = await bcrypt.hash(password, salt);
|
||||||
|
|
||||||
|
// Generate secret
|
||||||
|
const secret = generateNativeUserToken();
|
||||||
|
|
||||||
|
const keyPair = await genRsaKeyPair();
|
||||||
|
|
||||||
|
let account!: MiUser;
|
||||||
|
|
||||||
|
// Start transaction
|
||||||
|
await this.db.transaction(async transactionalEntityManager => {
|
||||||
|
const exist = await transactionalEntityManager.findOneBy(MiUser, {
|
||||||
|
usernameLower: extra.username.toLowerCase(),
|
||||||
|
host: IsNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exist) {
|
||||||
|
account = exist;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
account = await transactionalEntityManager.insert(MiUser, {
|
||||||
|
id: this.idService.gen(),
|
||||||
|
username: extra.username,
|
||||||
|
usernameLower: extra.username.toLowerCase(),
|
||||||
|
host: null,
|
||||||
|
token: secret,
|
||||||
|
isLocked: true,
|
||||||
|
isExplorable: false,
|
||||||
|
isBot: true,
|
||||||
|
name: extra.name,
|
||||||
|
}).then(x => transactionalEntityManager.findOneByOrFail(MiUser, x.identifiers[0]));
|
||||||
|
|
||||||
|
await transactionalEntityManager.insert(MiUserKeypair, {
|
||||||
|
publicKey: keyPair.publicKey,
|
||||||
|
privateKey: keyPair.privateKey,
|
||||||
|
userId: account.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await transactionalEntityManager.insert(MiUserProfile, {
|
||||||
|
userId: account.id,
|
||||||
|
autoAcceptFollowed: false,
|
||||||
|
password: hash,
|
||||||
|
});
|
||||||
|
|
||||||
|
await transactionalEntityManager.insert(MiUsedUsername, {
|
||||||
|
createdAt: new Date(),
|
||||||
|
username: extra.username.toLowerCase(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await transactionalEntityManager.insert(MiSystemAccount, {
|
||||||
|
id: this.idService.gen(),
|
||||||
|
userId: account.id,
|
||||||
|
type: type,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return account as MiLocalUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async updateCorrespondingUserProfile(type: typeof SYSTEM_ACCOUNT_TYPES[number], extra: {
|
||||||
|
name?: string;
|
||||||
|
description?: MiUserProfile['description'];
|
||||||
|
}): Promise<MiLocalUser> {
|
||||||
|
const user = await this.fetch(type);
|
||||||
|
|
||||||
|
const updates = {} as Partial<MiUser>;
|
||||||
|
if (extra.name !== undefined) updates.name = extra.name;
|
||||||
|
|
||||||
|
if (Object.keys(updates).length > 0) {
|
||||||
|
await this.usersRepository.update(user.id, updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
const profileUpdates = {} as Partial<MiUserProfile>;
|
||||||
|
if (extra.description !== undefined) profileUpdates.description = extra.description;
|
||||||
|
|
||||||
|
if (Object.keys(profileUpdates).length > 0) {
|
||||||
|
await this.userProfilesRepository.update(user.id, profileUpdates);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = await this.usersRepository.findOneByOrFail({ id: user.id }) as MiLocalUser;
|
||||||
|
this.cache.set(type, updated);
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
}
|
@@ -15,11 +15,11 @@ import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
|||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { ProxyAccountService } from '@/core/ProxyAccountService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import { RedisKVCache } from '@/misc/cache.js';
|
import { RedisKVCache } from '@/misc/cache.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserListService implements OnApplicationShutdown, OnModuleInit {
|
export class UserListService implements OnApplicationShutdown, OnModuleInit {
|
||||||
@@ -43,8 +43,8 @@ export class UserListService implements OnApplicationShutdown, OnModuleInit {
|
|||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private proxyAccountService: ProxyAccountService,
|
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
|
private systemAccountService: SystemAccountService,
|
||||||
) {
|
) {
|
||||||
this.membersCache = new RedisKVCache<Set<string>>(this.redisClient, 'userListMembers', {
|
this.membersCache = new RedisKVCache<Set<string>>(this.redisClient, 'userListMembers', {
|
||||||
lifetime: 1000 * 60 * 30, // 30m
|
lifetime: 1000 * 60 * 30, // 30m
|
||||||
@@ -111,10 +111,8 @@ export class UserListService implements OnApplicationShutdown, OnModuleInit {
|
|||||||
|
|
||||||
// このインスタンス内にこのリモートユーザーをフォローしているユーザーがいなくても投稿を受け取るためにダミーのユーザーがフォローしたということにする
|
// このインスタンス内にこのリモートユーザーをフォローしているユーザーがいなくても投稿を受け取るためにダミーのユーザーがフォローしたということにする
|
||||||
if (this.userEntityService.isRemoteUser(target)) {
|
if (this.userEntityService.isRemoteUser(target)) {
|
||||||
const proxy = await this.proxyAccountService.fetch();
|
const proxy = await this.systemAccountService.fetch('proxy');
|
||||||
if (proxy) {
|
this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: target.id } }]);
|
||||||
this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: target.id } }]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -73,7 +73,6 @@ function generateDummyUser(override?: Partial<MiUser>): MiUser {
|
|||||||
isLocked: false,
|
isLocked: false,
|
||||||
isBot: false,
|
isBot: false,
|
||||||
isCat: true,
|
isCat: true,
|
||||||
isRoot: false,
|
|
||||||
isExplorable: true,
|
isExplorable: true,
|
||||||
isHibernated: false,
|
isHibernated: false,
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
|
@@ -507,19 +507,12 @@ export class ApInboxService {
|
|||||||
return `skip: delete actor ${actor.uri} !== ${uri}`;
|
return `skip: delete actor ${actor.uri} !== ${uri}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: actor.id });
|
if (!(await this.usersRepository.update({ id: actor.id, isDeleted: false }, { isDeleted: true })).affected) {
|
||||||
if (user == null) {
|
return 'skip: already deleted or actor not found';
|
||||||
return 'skip: actor not found';
|
|
||||||
} else if (user.isDeleted) {
|
|
||||||
return 'skip: already deleted';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const job = await this.queueService.createDeleteAccountJob(actor);
|
const job = await this.queueService.createDeleteAccountJob(actor);
|
||||||
|
|
||||||
await this.usersRepository.update(actor.id, {
|
|
||||||
isDeleted: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.globalEventService.publishInternalEvent('remoteUserUpdated', { id: actor.id });
|
this.globalEventService.publishInternalEvent('remoteUserUpdated', { id: actor.id });
|
||||||
|
|
||||||
return `ok: queued ${job.name} ${job.id}`;
|
return `ok: queued ${job.name} ${job.id}`;
|
||||||
|
@@ -23,7 +23,7 @@ import { MfmService } from '@/core/MfmService.js';
|
|||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
import type { MiUserKeypair } from '@/models/UserKeypair.js';
|
import type { MiUserKeypair } from '@/models/UserKeypair.js';
|
||||||
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository } from '@/models/_.js';
|
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository, MiMeta } from '@/models/_.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
@@ -39,6 +39,9 @@ export class ApRendererService {
|
|||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
@@ -186,7 +189,7 @@ export class ApRendererService {
|
|||||||
url: emoji.publicUrl || emoji.originalUrl,
|
url: emoji.publicUrl || emoji.originalUrl,
|
||||||
},
|
},
|
||||||
_misskey_license: {
|
_misskey_license: {
|
||||||
freeText: emoji.license
|
freeText: emoji.license,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -255,6 +258,38 @@ export class ApRendererService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public renderIdenticon(user: MiLocalUser): IApImage {
|
||||||
|
return {
|
||||||
|
type: 'Image',
|
||||||
|
url: this.userEntityService.getIdenticonUrl(user),
|
||||||
|
sensitive: false,
|
||||||
|
name: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public renderSystemAvatar(user: MiLocalUser): IApImage {
|
||||||
|
if (this.meta.iconUrl == null) return this.renderIdenticon(user);
|
||||||
|
return {
|
||||||
|
type: 'Image',
|
||||||
|
url: this.meta.iconUrl,
|
||||||
|
sensitive: false,
|
||||||
|
name: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public renderSystemBanner(): IApImage | null {
|
||||||
|
if (this.meta.bannerUrl == null) return null;
|
||||||
|
return {
|
||||||
|
type: 'Image',
|
||||||
|
url: this.meta.bannerUrl,
|
||||||
|
sensitive: false,
|
||||||
|
name: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public renderKey(user: MiLocalUser, key: MiUserKeypair, postfix?: string): IKey {
|
public renderKey(user: MiLocalUser, key: MiUserKeypair, postfix?: string): IKey {
|
||||||
return {
|
return {
|
||||||
@@ -503,8 +538,8 @@ export class ApRendererService {
|
|||||||
_misskey_requireSigninToViewContents: user.requireSigninToViewContents,
|
_misskey_requireSigninToViewContents: user.requireSigninToViewContents,
|
||||||
_misskey_makeNotesFollowersOnlyBefore: user.makeNotesFollowersOnlyBefore,
|
_misskey_makeNotesFollowersOnlyBefore: user.makeNotesFollowersOnlyBefore,
|
||||||
_misskey_makeNotesHiddenBefore: user.makeNotesHiddenBefore,
|
_misskey_makeNotesHiddenBefore: user.makeNotesHiddenBefore,
|
||||||
icon: avatar ? this.renderImage(avatar) : null,
|
icon: avatar ? this.renderImage(avatar) : isSystem ? this.renderSystemAvatar(user) : this.renderIdenticon(user),
|
||||||
image: banner ? this.renderImage(banner) : null,
|
image: banner ? this.renderImage(banner) : isSystem ? this.renderSystemBanner() : null,
|
||||||
tag,
|
tag,
|
||||||
manuallyApprovesFollowers: user.isLocked,
|
manuallyApprovesFollowers: user.isLocked,
|
||||||
discoverable: user.isExplorable,
|
discoverable: user.isExplorable,
|
||||||
|
@@ -6,7 +6,6 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { IsNull, Not } from 'typeorm';
|
import { IsNull, Not } from 'typeorm';
|
||||||
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
|
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
|
||||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
|
||||||
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js';
|
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||||
@@ -15,13 +14,14 @@ import { UtilityService } from '@/core/UtilityService.js';
|
|||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
import { isCollectionOrOrderedCollection } from './type.js';
|
import { isCollectionOrOrderedCollection } from './type.js';
|
||||||
import { ApDbResolverService } from './ApDbResolverService.js';
|
import { ApDbResolverService } from './ApDbResolverService.js';
|
||||||
import { ApRendererService } from './ApRendererService.js';
|
import { ApRendererService } from './ApRendererService.js';
|
||||||
import { ApRequestService } from './ApRequestService.js';
|
import { ApRequestService } from './ApRequestService.js';
|
||||||
import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
|
||||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
|
||||||
import { FetchAllowSoftFailMask } from './misc/check-against-url.js';
|
import { FetchAllowSoftFailMask } from './misc/check-against-url.js';
|
||||||
|
import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
||||||
|
|
||||||
export class Resolver {
|
export class Resolver {
|
||||||
private history: Set<string>;
|
private history: Set<string>;
|
||||||
@@ -37,7 +37,7 @@ export class Resolver {
|
|||||||
private noteReactionsRepository: NoteReactionsRepository,
|
private noteReactionsRepository: NoteReactionsRepository,
|
||||||
private followRequestsRepository: FollowRequestsRepository,
|
private followRequestsRepository: FollowRequestsRepository,
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private instanceActorService: InstanceActorService,
|
private systemAccountService: SystemAccountService,
|
||||||
private apRequestService: ApRequestService,
|
private apRequestService: ApRequestService,
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
@@ -105,7 +105,7 @@ export class Resolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.config.signToActivityPubGet && !this.user) {
|
if (this.config.signToActivityPubGet && !this.user) {
|
||||||
this.user = await this.instanceActorService.getInstanceActor();
|
this.user = await this.systemAccountService.fetch('actor');
|
||||||
}
|
}
|
||||||
|
|
||||||
const object = (this.user
|
const object = (this.user
|
||||||
@@ -119,7 +119,7 @@ export class Resolver {
|
|||||||
) {
|
) {
|
||||||
throw new IdentifiableError('72180409-793c-4973-868e-5a118eb5519b', 'invalid response');
|
throw new IdentifiableError('72180409-793c-4973-868e-5a118eb5519b', 'invalid response');
|
||||||
}
|
}
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ export class ApResolverService {
|
|||||||
private followRequestsRepository: FollowRequestsRepository,
|
private followRequestsRepository: FollowRequestsRepository,
|
||||||
|
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private instanceActorService: InstanceActorService,
|
private systemAccountService: SystemAccountService,
|
||||||
private apRequestService: ApRequestService,
|
private apRequestService: ApRequestService,
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
@@ -222,7 +222,7 @@ export class ApResolverService {
|
|||||||
this.noteReactionsRepository,
|
this.noteReactionsRepository,
|
||||||
this.followRequestsRepository,
|
this.followRequestsRepository,
|
||||||
this.utilityService,
|
this.utilityService,
|
||||||
this.instanceActorService,
|
this.systemAccountService,
|
||||||
this.apRequestService,
|
this.apRequestService,
|
||||||
this.httpRequestService,
|
this.httpRequestService,
|
||||||
this.apRendererService,
|
this.apRendererService,
|
||||||
|
@@ -594,7 +594,9 @@ export class ApPersonService implements OnModuleInit {
|
|||||||
if (moving) updates.movedAt = new Date();
|
if (moving) updates.movedAt = new Date();
|
||||||
|
|
||||||
// Update user
|
// Update user
|
||||||
await this.usersRepository.update(exist.id, updates);
|
if (!(await this.usersRepository.update({ id: exist.id, isDeleted: false }, updates)).affected) {
|
||||||
|
return 'skip';
|
||||||
|
}
|
||||||
|
|
||||||
if (person.publicKey) {
|
if (person.publicKey) {
|
||||||
await this.userPublickeysRepository.update({ userId: exist.id }, {
|
await this.userPublickeysRepository.update({ userId: exist.id }, {
|
||||||
@@ -699,7 +701,7 @@ export class ApPersonService implements OnModuleInit {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async updateFeatured(userId: MiUser['id'], resolver?: Resolver): Promise<void> {
|
public async updateFeatured(userId: MiUser['id'], resolver?: Resolver): Promise<void> {
|
||||||
const user = await this.usersRepository.findOneByOrFail({ id: userId });
|
const user = await this.usersRepository.findOneByOrFail({ id: userId, isDeleted: false });
|
||||||
if (!this.userEntityService.isRemoteUser(user)) return;
|
if (!this.userEntityService.isRemoteUser(user)) return;
|
||||||
if (!user.featured) return;
|
if (!user.featured) return;
|
||||||
|
|
||||||
|
@@ -11,8 +11,7 @@ import type { MiMeta } from '@/models/Meta.js';
|
|||||||
import type { AdsRepository } from '@/models/_.js';
|
import type { AdsRepository } from '@/models/_.js';
|
||||||
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||||
@@ -29,8 +28,7 @@ export class MetaEntityService {
|
|||||||
@Inject(DI.adsRepository)
|
@Inject(DI.adsRepository)
|
||||||
private adsRepository: AdsRepository,
|
private adsRepository: AdsRepository,
|
||||||
|
|
||||||
private userEntityService: UserEntityService,
|
private systemAccountService: SystemAccountService,
|
||||||
private instanceActorService: InstanceActorService,
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
@@ -149,14 +147,14 @@ export class MetaEntityService {
|
|||||||
|
|
||||||
const packed = await this.pack(instance);
|
const packed = await this.pack(instance);
|
||||||
|
|
||||||
const proxyAccount = instance.proxyAccountId ? await this.userEntityService.pack(instance.proxyAccountId).catch(() => null) : null;
|
const proxyAccount = await this.systemAccountService.fetch('proxy');
|
||||||
|
|
||||||
const packDetailed: Packed<'MetaDetailed'> = {
|
const packDetailed: Packed<'MetaDetailed'> = {
|
||||||
...packed,
|
...packed,
|
||||||
cacheRemoteFiles: instance.cacheRemoteFiles,
|
cacheRemoteFiles: instance.cacheRemoteFiles,
|
||||||
cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles,
|
cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles,
|
||||||
requireSetup: !await this.instanceActorService.realLocalUsersPresent(),
|
requireSetup: this.meta.rootUserId == null,
|
||||||
proxyAccountName: proxyAccount ? proxyAccount.username : null,
|
proxyAccountName: proxyAccount.username,
|
||||||
features: {
|
features: {
|
||||||
localTimeline: instance.policies.ltlAvailable,
|
localTimeline: instance.policies.ltlAvailable,
|
||||||
globalTimeline: instance.policies.gtlAvailable,
|
globalTimeline: instance.policies.gtlAvailable,
|
||||||
|
@@ -28,6 +28,7 @@ import type {
|
|||||||
FollowingsRepository,
|
FollowingsRepository,
|
||||||
FollowRequestsRepository,
|
FollowRequestsRepository,
|
||||||
MiFollowing,
|
MiFollowing,
|
||||||
|
MiMeta,
|
||||||
MiUserNotePining,
|
MiUserNotePining,
|
||||||
MiUserProfile,
|
MiUserProfile,
|
||||||
MutingsRepository,
|
MutingsRepository,
|
||||||
@@ -100,6 +101,9 @@ export class UserEntityService implements OnModuleInit {
|
|||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.redis)
|
@Inject(DI.redis)
|
||||||
private redisClient: Redis.Redis,
|
private redisClient: Redis.Redis,
|
||||||
|
|
||||||
@@ -381,7 +385,11 @@ export class UserEntityService implements OnModuleInit {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public getIdenticonUrl(user: MiUser): string {
|
public getIdenticonUrl(user: MiUser): string {
|
||||||
return `${this.config.url}/identicon/${user.username.toLowerCase()}@${user.host ?? this.config.host}`;
|
if ((user.host == null || user.host === this.config.host) && user.username.includes('.') && this.meta.iconUrl) { // ローカルのシステムアカウントの場合
|
||||||
|
return this.meta.iconUrl;
|
||||||
|
} else {
|
||||||
|
return `${this.config.url}/identicon/${user.username.toLowerCase()}@${user.host ?? this.config.host}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@@ -74,6 +74,7 @@ export const DI = {
|
|||||||
registryItemsRepository: Symbol('registryItemsRepository'),
|
registryItemsRepository: Symbol('registryItemsRepository'),
|
||||||
webhooksRepository: Symbol('webhooksRepository'),
|
webhooksRepository: Symbol('webhooksRepository'),
|
||||||
systemWebhooksRepository: Symbol('systemWebhooksRepository'),
|
systemWebhooksRepository: Symbol('systemWebhooksRepository'),
|
||||||
|
systemAccountsRepository: Symbol('systemAccountsRepository'),
|
||||||
adsRepository: Symbol('adsRepository'),
|
adsRepository: Symbol('adsRepository'),
|
||||||
passwordResetRequestsRepository: Symbol('passwordResetRequestsRepository'),
|
passwordResetRequestsRepository: Symbol('passwordResetRequestsRepository'),
|
||||||
retentionAggregationsRepository: Symbol('retentionAggregationsRepository'),
|
retentionAggregationsRepository: Symbol('retentionAggregationsRepository'),
|
||||||
|
@@ -1,7 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
|
||||||
export default (token: string) => token.length === 16;
|
|
@@ -5,5 +5,6 @@
|
|||||||
|
|
||||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
export const generateNativeUserToken = () => secureRndstr(16);
|
||||||
export default () => secureRndstr(16);
|
|
||||||
|
export const isNativeUserToken = (token: string) => token.length === 16;
|
@@ -3,7 +3,7 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Entity, Column, PrimaryColumn, ManyToOne, JoinColumn } from 'typeorm';
|
import { Entity, Column, PrimaryColumn, ManyToOne } from 'typeorm';
|
||||||
import { id } from './util/id.js';
|
import { id } from './util/id.js';
|
||||||
import { MiUser } from './User.js';
|
import { MiUser } from './User.js';
|
||||||
|
|
||||||
@@ -15,6 +15,18 @@ export class MiMeta {
|
|||||||
})
|
})
|
||||||
public id: string;
|
public id: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
...id(),
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public rootUserId: MiUser['id'] | null;
|
||||||
|
|
||||||
|
@ManyToOne(type => MiUser, {
|
||||||
|
onDelete: 'SET NULL',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public rootUser: MiUser | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 1024, nullable: true,
|
length: 1024, nullable: true,
|
||||||
})
|
})
|
||||||
@@ -172,18 +184,6 @@ export class MiMeta {
|
|||||||
})
|
})
|
||||||
public cacheRemoteSensitiveFiles: boolean;
|
public cacheRemoteSensitiveFiles: boolean;
|
||||||
|
|
||||||
@Column({
|
|
||||||
...id(),
|
|
||||||
nullable: true,
|
|
||||||
})
|
|
||||||
public proxyAccountId: MiUser['id'] | null;
|
|
||||||
|
|
||||||
@ManyToOne(type => MiUser, {
|
|
||||||
onDelete: 'SET NULL',
|
|
||||||
})
|
|
||||||
@JoinColumn()
|
|
||||||
public proxyAccount: MiUser | null;
|
|
||||||
|
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: false,
|
default: false,
|
||||||
})
|
})
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Provider } from '@nestjs/common';
|
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import {
|
import {
|
||||||
@@ -63,6 +62,7 @@ import {
|
|||||||
MiRoleAssignment,
|
MiRoleAssignment,
|
||||||
MiSignin,
|
MiSignin,
|
||||||
MiSwSubscription,
|
MiSwSubscription,
|
||||||
|
MiSystemAccount,
|
||||||
MiSystemWebhook,
|
MiSystemWebhook,
|
||||||
MiUsedUsername,
|
MiUsedUsername,
|
||||||
MiUser,
|
MiUser,
|
||||||
@@ -77,8 +77,9 @@ import {
|
|||||||
MiUserProfile,
|
MiUserProfile,
|
||||||
MiUserPublickey,
|
MiUserPublickey,
|
||||||
MiUserSecurityKey,
|
MiUserSecurityKey,
|
||||||
MiWebhook
|
MiWebhook,
|
||||||
} from './_.js';
|
} from './_.js';
|
||||||
|
import type { Provider } from '@nestjs/common';
|
||||||
import type { DataSource } from 'typeorm';
|
import type { DataSource } from 'typeorm';
|
||||||
|
|
||||||
const $usersRepository: Provider = {
|
const $usersRepository: Provider = {
|
||||||
@@ -285,6 +286,12 @@ const $swSubscriptionsRepository: Provider = {
|
|||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const $systemAccountsRepository: Provider = {
|
||||||
|
provide: DI.systemAccountsRepository,
|
||||||
|
useFactory: (db: DataSource) => db.getRepository(MiSystemAccount),
|
||||||
|
inject: [DI.db],
|
||||||
|
};
|
||||||
|
|
||||||
const $hashtagsRepository: Provider = {
|
const $hashtagsRepository: Provider = {
|
||||||
provide: DI.hashtagsRepository,
|
provide: DI.hashtagsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiHashtag).extend(miRepository as MiRepository<MiHashtag>),
|
useFactory: (db: DataSource) => db.getRepository(MiHashtag).extend(miRepository as MiRepository<MiHashtag>),
|
||||||
@@ -532,6 +539,7 @@ const $reversiGamesRepository: Provider = {
|
|||||||
$renoteMutingsRepository,
|
$renoteMutingsRepository,
|
||||||
$blockingsRepository,
|
$blockingsRepository,
|
||||||
$swSubscriptionsRepository,
|
$swSubscriptionsRepository,
|
||||||
|
$systemAccountsRepository,
|
||||||
$hashtagsRepository,
|
$hashtagsRepository,
|
||||||
$abuseUserReportsRepository,
|
$abuseUserReportsRepository,
|
||||||
$abuseReportNotificationRecipientRepository,
|
$abuseReportNotificationRecipientRepository,
|
||||||
@@ -603,6 +611,7 @@ const $reversiGamesRepository: Provider = {
|
|||||||
$renoteMutingsRepository,
|
$renoteMutingsRepository,
|
||||||
$blockingsRepository,
|
$blockingsRepository,
|
||||||
$swSubscriptionsRepository,
|
$swSubscriptionsRepository,
|
||||||
|
$systemAccountsRepository,
|
||||||
$hashtagsRepository,
|
$hashtagsRepository,
|
||||||
$abuseUserReportsRepository,
|
$abuseUserReportsRepository,
|
||||||
$abuseReportNotificationRecipientRepository,
|
$abuseReportNotificationRecipientRepository,
|
||||||
|
31
packages/backend/src/models/SystemAccount.ts
Normal file
31
packages/backend/src/models/SystemAccount.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||||
|
import { Serialized } from '@/types.js';
|
||||||
|
import { id } from './util/id.js';
|
||||||
|
import { MiUser } from './User.js';
|
||||||
|
|
||||||
|
@Entity('system_account')
|
||||||
|
@Index(['type'], { unique: true })
|
||||||
|
export class MiSystemAccount {
|
||||||
|
@PrimaryColumn(id())
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column(id())
|
||||||
|
public userId: MiUser['id'];
|
||||||
|
|
||||||
|
@ManyToOne(type => MiUser, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public user: MiUser | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 256,
|
||||||
|
})
|
||||||
|
public type: string;
|
||||||
|
}
|
@@ -184,12 +184,6 @@ export class MiUser {
|
|||||||
})
|
})
|
||||||
public isCat: boolean;
|
public isCat: boolean;
|
||||||
|
|
||||||
@Column('boolean', {
|
|
||||||
default: false,
|
|
||||||
comment: 'Whether the User is the root.',
|
|
||||||
})
|
|
||||||
public isRoot: boolean;
|
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: true,
|
default: true,
|
||||||
|
@@ -56,6 +56,7 @@ import { MiRegistryItem } from '@/models/RegistryItem.js';
|
|||||||
import { MiRelay } from '@/models/Relay.js';
|
import { MiRelay } from '@/models/Relay.js';
|
||||||
import { MiSignin } from '@/models/Signin.js';
|
import { MiSignin } from '@/models/Signin.js';
|
||||||
import { MiSwSubscription } from '@/models/SwSubscription.js';
|
import { MiSwSubscription } from '@/models/SwSubscription.js';
|
||||||
|
import { MiSystemAccount } from '@/models/SystemAccount.js';
|
||||||
import { MiUsedUsername } from '@/models/UsedUsername.js';
|
import { MiUsedUsername } from '@/models/UsedUsername.js';
|
||||||
import { MiUser } from '@/models/User.js';
|
import { MiUser } from '@/models/User.js';
|
||||||
import { MiUserIp } from '@/models/UserIp.js';
|
import { MiUserIp } from '@/models/UserIp.js';
|
||||||
@@ -171,6 +172,7 @@ export {
|
|||||||
MiRelay,
|
MiRelay,
|
||||||
MiSignin,
|
MiSignin,
|
||||||
MiSwSubscription,
|
MiSwSubscription,
|
||||||
|
MiSystemAccount,
|
||||||
MiUsedUsername,
|
MiUsedUsername,
|
||||||
MiUser,
|
MiUser,
|
||||||
MiUserIp,
|
MiUserIp,
|
||||||
@@ -242,6 +244,7 @@ export type RegistryItemsRepository = Repository<MiRegistryItem> & MiRepository<
|
|||||||
export type RelaysRepository = Repository<MiRelay> & MiRepository<MiRelay>;
|
export type RelaysRepository = Repository<MiRelay> & MiRepository<MiRelay>;
|
||||||
export type SigninsRepository = Repository<MiSignin> & MiRepository<MiSignin>;
|
export type SigninsRepository = Repository<MiSignin> & MiRepository<MiSignin>;
|
||||||
export type SwSubscriptionsRepository = Repository<MiSwSubscription> & MiRepository<MiSwSubscription>;
|
export type SwSubscriptionsRepository = Repository<MiSwSubscription> & MiRepository<MiSwSubscription>;
|
||||||
|
export type SystemAccountsRepository = Repository<MiSystemAccount> & MiRepository<MiSystemAccount>;
|
||||||
export type UsedUsernamesRepository = Repository<MiUsedUsername> & MiRepository<MiUsedUsername>;
|
export type UsedUsernamesRepository = Repository<MiUsedUsername> & MiRepository<MiUsedUsername>;
|
||||||
export type UsersRepository = Repository<MiUser> & MiRepository<MiUser>;
|
export type UsersRepository = Repository<MiUser> & MiRepository<MiUser>;
|
||||||
export type UserIpsRepository = Repository<MiUserIp> & MiRepository<MiUserIp>;
|
export type UserIpsRepository = Repository<MiUserIp> & MiRepository<MiUserIp>;
|
||||||
|
@@ -82,6 +82,7 @@ import { MiReversiGame } from '@/models/ReversiGame.js';
|
|||||||
import { Config } from '@/config.js';
|
import { Config } from '@/config.js';
|
||||||
import MisskeyLogger from '@/logger.js';
|
import MisskeyLogger from '@/logger.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { MiSystemAccount } from './models/SystemAccount.js';
|
||||||
|
|
||||||
pg.types.setTypeParser(20, Number);
|
pg.types.setTypeParser(20, Number);
|
||||||
|
|
||||||
@@ -206,6 +207,7 @@ export const entities = [
|
|||||||
MiEmoji,
|
MiEmoji,
|
||||||
MiHashtag,
|
MiHashtag,
|
||||||
MiSwSubscription,
|
MiSwSubscription,
|
||||||
|
MiSystemAccount,
|
||||||
MiAbuseUserReport,
|
MiAbuseUserReport,
|
||||||
MiAbuseReportNotificationRecipient,
|
MiAbuseReportNotificationRecipient,
|
||||||
MiRegistrationTicket,
|
MiRegistrationTicket,
|
||||||
|
@@ -9,11 +9,11 @@ import type { Config } from '@/config.js';
|
|||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||||
import { MemorySingleCache } from '@/misc/cache.js';
|
import { MemorySingleCache } from '@/misc/cache.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import NotesChart from '@/core/chart/charts/notes.js';
|
import NotesChart from '@/core/chart/charts/notes.js';
|
||||||
import UsersChart from '@/core/chart/charts/users.js';
|
import UsersChart from '@/core/chart/charts/users.js';
|
||||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
||||||
|
|
||||||
const nodeinfo2_1path = '/nodeinfo/2.1';
|
const nodeinfo2_1path = '/nodeinfo/2.1';
|
||||||
@@ -26,7 +26,7 @@ export class NodeinfoServerService {
|
|||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
private userEntityService: UserEntityService,
|
private systemAccountService: SystemAccountService,
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
private notesChart: NotesChart,
|
private notesChart: NotesChart,
|
||||||
private usersChart: UsersChart,
|
private usersChart: UsersChart,
|
||||||
@@ -70,7 +70,7 @@ export class NodeinfoServerService {
|
|||||||
const activeHalfyear = null;
|
const activeHalfyear = null;
|
||||||
const activeMonth = null;
|
const activeMonth = null;
|
||||||
|
|
||||||
const proxyAccount = meta.proxyAccountId ? await this.userEntityService.pack(meta.proxyAccountId).catch(() => null) : null;
|
const proxyAccount = await this.systemAccountService.fetch('proxy');
|
||||||
|
|
||||||
const basePolicies = { ...DEFAULT_POLICIES, ...meta.policies };
|
const basePolicies = { ...DEFAULT_POLICIES, ...meta.policies };
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ export class NodeinfoServerService {
|
|||||||
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
|
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
|
||||||
enableEmail: meta.enableEmail,
|
enableEmail: meta.enableEmail,
|
||||||
enableServiceWorker: meta.enableServiceWorker,
|
enableServiceWorker: meta.enableServiceWorker,
|
||||||
proxyAccountName: proxyAccount ? proxyAccount.username : null,
|
proxyAccountName: proxyAccount.username,
|
||||||
themeColor: meta.themeColor ?? '#86b300',
|
themeColor: meta.themeColor ?? '#86b300',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@@ -371,7 +371,7 @@ export class ApiCallService implements OnApplicationShutdown {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((ep.meta.requireModerator || ep.meta.requireAdmin) && !user!.isRoot) {
|
if ((ep.meta.requireModerator || ep.meta.requireAdmin) && (this.meta.rootUserId !== user!.id)) {
|
||||||
const myRoles = await this.roleService.getUserRoles(user!.id);
|
const myRoles = await this.roleService.getUserRoles(user!.id);
|
||||||
if (ep.meta.requireModerator && !myRoles.some(r => r.isModerator || r.isAdministrator)) {
|
if (ep.meta.requireModerator && !myRoles.some(r => r.isModerator || r.isAdministrator)) {
|
||||||
throw new ApiError({
|
throw new ApiError({
|
||||||
@@ -391,7 +391,7 @@ export class ApiCallService implements OnApplicationShutdown {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ep.meta.requireRolePolicy != null && !user!.isRoot) {
|
if (ep.meta.requireRolePolicy != null && (this.meta.rootUserId !== user!.id)) {
|
||||||
const myRoles = await this.roleService.getUserRoles(user!.id);
|
const myRoles = await this.roleService.getUserRoles(user!.id);
|
||||||
const policies = await this.roleService.getUserPolicies(user!.id);
|
const policies = await this.roleService.getUserPolicies(user!.id);
|
||||||
if (!policies[ep.meta.requireRolePolicy] && !myRoles.some(r => r.isAdministrator)) {
|
if (!policies[ep.meta.requireRolePolicy] && !myRoles.some(r => r.isAdministrator)) {
|
||||||
|
@@ -11,7 +11,7 @@ import type { MiAccessToken } from '@/models/AccessToken.js';
|
|||||||
import { MemoryKVCache } from '@/misc/cache.js';
|
import { MemoryKVCache } from '@/misc/cache.js';
|
||||||
import type { MiApp } from '@/models/App.js';
|
import type { MiApp } from '@/models/App.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import isNativeToken from '@/misc/is-native-token.js';
|
import { isNativeUserToken } from '@/misc/token.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
|
||||||
export class AuthenticationError extends Error {
|
export class AuthenticationError extends Error {
|
||||||
@@ -46,7 +46,7 @@ export class AuthenticateService implements OnApplicationShutdown {
|
|||||||
return [null, null];
|
return [null, null];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNativeToken(token)) {
|
if (isNativeUserToken(token)) {
|
||||||
const user = await this.cacheService.localUserByNativeTokenCache.fetch(token,
|
const user = await this.cacheService.localUserByNativeTokenCache.fetch(token,
|
||||||
() => this.usersRepository.findOneBy({ token }) as Promise<MiLocalUser | null>);
|
() => this.usersRepository.findOneBy({ token }) as Promise<MiLocalUser | null>);
|
||||||
|
|
||||||
|
@@ -100,6 +100,7 @@ export * as 'admin/unset-user-banner' from './endpoints/admin/unset-user-banner.
|
|||||||
export * as 'admin/unsuspend-user' from './endpoints/admin/unsuspend-user.js';
|
export * as 'admin/unsuspend-user' from './endpoints/admin/unsuspend-user.js';
|
||||||
export * as 'admin/update-abuse-user-report' from './endpoints/admin/update-abuse-user-report.js';
|
export * as 'admin/update-abuse-user-report' from './endpoints/admin/update-abuse-user-report.js';
|
||||||
export * as 'admin/update-meta' from './endpoints/admin/update-meta.js';
|
export * as 'admin/update-meta' from './endpoints/admin/update-meta.js';
|
||||||
|
export * as 'admin/update-proxy-account' from './endpoints/admin/update-proxy-account.js';
|
||||||
export * as 'admin/update-user-note' from './endpoints/admin/update-user-note.js';
|
export * as 'admin/update-user-note' from './endpoints/admin/update-user-note.js';
|
||||||
export * as 'announcements' from './endpoints/announcements.js';
|
export * as 'announcements' from './endpoints/announcements.js';
|
||||||
export * as 'announcements/show' from './endpoints/announcements/show.js';
|
export * as 'announcements/show' from './endpoints/announcements/show.js';
|
||||||
|
@@ -4,12 +4,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { IsNull } from 'typeorm';
|
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { UsersRepository } from '@/models/_.js';
|
import type { MiMeta, UsersRepository } from '@/models/_.js';
|
||||||
import { SignupService } from '@/core/SignupService.js';
|
import { SignupService } from '@/core/SignupService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
|
||||||
import { localUsernameSchema, passwordSchema } from '@/models/User.js';
|
import { localUsernameSchema, passwordSchema } from '@/models/User.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
@@ -62,18 +60,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private signupService: SignupService,
|
private signupService: SignupService,
|
||||||
private instanceActorService: InstanceActorService,
|
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, _me, token) => {
|
super(meta, paramDef, async (ps, _me, token) => {
|
||||||
const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null;
|
const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null;
|
||||||
const realUsers = await this.instanceActorService.realLocalUsersPresent();
|
|
||||||
|
|
||||||
if (!realUsers && me == null && token == null) {
|
if (this.serverSettings.rootUserId == null && me == null && token == null) {
|
||||||
// 初回セットアップの場合
|
// 初回セットアップの場合
|
||||||
if (this.config.setupPassword != null) {
|
if (this.config.setupPassword != null) {
|
||||||
// 初期パスワードが設定されている場合
|
// 初期パスワードが設定されている場合
|
||||||
@@ -85,7 +84,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
// 初期パスワードが設定されていないのに初期パスワードが入力された場合
|
// 初期パスワードが設定されていないのに初期パスワードが入力された場合
|
||||||
throw new ApiError(meta.errors.wrongInitialPassword);
|
throw new ApiError(meta.errors.wrongInitialPassword);
|
||||||
}
|
}
|
||||||
} else if ((realUsers && !me?.isRoot) || token !== null) {
|
} else if ((this.serverSettings.rootUserId != null && (this.serverSettings.rootUserId !== me?.id)) || token !== null) {
|
||||||
// 初回セットアップではなく、管理者でない場合 or 外部トークンを使用している場合
|
// 初回セットアップではなく、管理者でない場合 or 外部トークンを使用している場合
|
||||||
throw new ApiError(meta.errors.accessDenied);
|
throw new ApiError(meta.errors.accessDenied);
|
||||||
}
|
}
|
||||||
|
@@ -42,10 +42,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
throw new Error('user not found');
|
throw new Error('user not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.isRoot) {
|
|
||||||
throw new Error('cannot delete a root account');
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.deleteAccoountService.deleteAccount(user, me);
|
await this.deleteAccoountService.deleteAccount(user, me);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,7 @@ import { MetaService } from '@/core/MetaService.js';
|
|||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['meta'],
|
tags: ['meta'],
|
||||||
@@ -237,7 +238,7 @@ export const meta = {
|
|||||||
},
|
},
|
||||||
proxyAccountId: {
|
proxyAccountId: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: false,
|
||||||
format: 'id',
|
format: 'id',
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
@@ -545,10 +546,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
|
private systemAccountService: SystemAccountService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async () => {
|
super(meta, paramDef, async () => {
|
||||||
const instance = await this.metaService.fetch(true);
|
const instance = await this.metaService.fetch(true);
|
||||||
|
|
||||||
|
const proxy = await this.systemAccountService.fetch('proxy');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
maintainerName: instance.maintainerName,
|
maintainerName: instance.maintainerName,
|
||||||
maintainerEmail: instance.maintainerEmail,
|
maintainerEmail: instance.maintainerEmail,
|
||||||
@@ -613,7 +617,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity,
|
sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity,
|
||||||
setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically,
|
setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically,
|
||||||
enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos,
|
enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos,
|
||||||
proxyAccountId: instance.proxyAccountId,
|
proxyAccountId: proxy.id,
|
||||||
email: instance.email,
|
email: instance.email,
|
||||||
smtpSecure: instance.smtpSecure,
|
smtpSecure: instance.smtpSecure,
|
||||||
smtpHost: instance.smtpHost,
|
smtpHost: instance.smtpHost,
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
|
import type { UsersRepository, UserProfilesRepository, MiMeta } from '@/models/_.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
@@ -43,6 +43,9 @@ export const paramDef = {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
@@ -58,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
throw new Error('user not found');
|
throw new Error('user not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.isRoot) {
|
if (this.serverSettings.rootUserId === user.id) {
|
||||||
throw new Error('cannot reset password of root');
|
throw new Error('cannot reset password of root');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -89,7 +89,6 @@ export const paramDef = {
|
|||||||
sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
|
sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
|
||||||
setSensitiveFlagAutomatically: { type: 'boolean' },
|
setSensitiveFlagAutomatically: { type: 'boolean' },
|
||||||
enableSensitiveMediaDetectionForVideos: { type: 'boolean' },
|
enableSensitiveMediaDetectionForVideos: { type: 'boolean' },
|
||||||
proxyAccountId: { type: 'string', format: 'misskey:id', nullable: true },
|
|
||||||
maintainerName: { type: 'string', nullable: true },
|
maintainerName: { type: 'string', nullable: true },
|
||||||
maintainerEmail: { type: 'string', nullable: true },
|
maintainerEmail: { type: 'string', nullable: true },
|
||||||
langs: {
|
langs: {
|
||||||
@@ -394,10 +393,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
set.enableSensitiveMediaDetectionForVideos = ps.enableSensitiveMediaDetectionForVideos;
|
set.enableSensitiveMediaDetectionForVideos = ps.enableSensitiveMediaDetectionForVideos;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.proxyAccountId !== undefined) {
|
|
||||||
set.proxyAccountId = ps.proxyAccountId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.maintainerName !== undefined) {
|
if (ps.maintainerName !== undefined) {
|
||||||
set.maintainerName = ps.maintainerName;
|
set.maintainerName = ps.maintainerName;
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import {
|
||||||
|
descriptionSchema,
|
||||||
|
} from '@/models/User.js';
|
||||||
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
kind: 'write:admin:account',
|
||||||
|
|
||||||
|
res: {
|
||||||
|
type: 'object',
|
||||||
|
nullable: false, optional: false,
|
||||||
|
ref: 'UserDetailed',
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
description: { ...descriptionSchema, nullable: true },
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
private userEntityService: UserEntityService,
|
||||||
|
private moderationLogService: ModerationLogService,
|
||||||
|
private systemAccountService: SystemAccountService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const proxy = await this.systemAccountService.updateCorrespondingUserProfile('proxy', {
|
||||||
|
description: ps.description,
|
||||||
|
});
|
||||||
|
|
||||||
|
const updated = await this.userEntityService.pack(proxy.id, proxy, {
|
||||||
|
schema: 'MeDetailed',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ps.description !== undefined) {
|
||||||
|
this.moderationLogService.log(me, 'updateProxyAccountDescription', {
|
||||||
|
before: null, //TODO
|
||||||
|
after: ps.description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -3,7 +3,7 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
@@ -19,6 +19,8 @@ import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
|||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
|
||||||
import * as Acct from '@/misc/acct.js';
|
import * as Acct from '@/misc/acct.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { MiMeta } from '@/models/_.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['users'],
|
tags: ['users'],
|
||||||
@@ -81,6 +83,9 @@ export const paramDef = {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
private remoteUserResolveService: RemoteUserResolveService,
|
private remoteUserResolveService: RemoteUserResolveService,
|
||||||
private apiLoggerService: ApiLoggerService,
|
private apiLoggerService: ApiLoggerService,
|
||||||
private accountMoveService: AccountMoveService,
|
private accountMoveService: AccountMoveService,
|
||||||
@@ -92,7 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
// check parameter
|
// check parameter
|
||||||
if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchUser);
|
if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchUser);
|
||||||
// abort if user is the root
|
// abort if user is the root
|
||||||
if (me.isRoot) throw new ApiError(meta.errors.rootForbidden);
|
if (this.serverSettings.rootUserId === me.id) throw new ApiError(meta.errors.rootForbidden);
|
||||||
// abort if user has already moved
|
// abort if user has already moved
|
||||||
if (me.movedToUri) throw new ApiError(meta.errors.alreadyMoved);
|
if (me.movedToUri) throw new ApiError(meta.errors.alreadyMoved);
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ import bcrypt from 'bcryptjs';
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
|
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
|
||||||
import generateUserToken from '@/misc/generate-native-user-token.js';
|
import { generateNativeUserToken } from '@/misc/token.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
throw new Error('incorrect password');
|
throw new Error('incorrect password');
|
||||||
}
|
}
|
||||||
|
|
||||||
const newToken = generateUserToken();
|
const newToken = generateNativeUserToken();
|
||||||
|
|
||||||
await this.usersRepository.update(me.id, {
|
await this.usersRepository.update(me.id, {
|
||||||
token: newToken,
|
token: newToken,
|
||||||
|
@@ -6,9 +6,12 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { resetDb } from '@/misc/reset-db.js';
|
import { resetDb } from '@/misc/reset-db.js';
|
||||||
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['non-productive'],
|
tags: ['non-productive'],
|
||||||
@@ -36,13 +39,27 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
|
|
||||||
@Inject(DI.redis)
|
@Inject(DI.redis)
|
||||||
private redisClient: Redis.Redis,
|
private redisClient: Redis.Redis,
|
||||||
|
|
||||||
|
private loggerService: LoggerService,
|
||||||
|
private metaService: MetaService,
|
||||||
|
private globalEventService: GlobalEventService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
if (process.env.NODE_ENV !== 'test') throw new Error('NODE_ENV is not a test');
|
if (process.env.NODE_ENV !== 'test') throw new Error('NODE_ENV is not a test');
|
||||||
|
|
||||||
await redisClient.flushdb();
|
const logger = this.loggerService.getLogger('reset-db');
|
||||||
|
logger.info('---- Resetting database...');
|
||||||
|
|
||||||
|
await this.redisClient.flushdb();
|
||||||
await resetDb(this.db);
|
await resetDb(this.db);
|
||||||
|
|
||||||
|
// DIコンテナで管理しているmetaのインスタンスには上記のリセット処理が届かないため、
|
||||||
|
// 初期値を流して明示的にリフレッシュする
|
||||||
|
const meta = await this.metaService.fetch(true);
|
||||||
|
this.globalEventService.publishInternalEvent('metaUpdated', { after: meta });
|
||||||
|
|
||||||
|
logger.info('---- Database reset complete.');
|
||||||
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -122,6 +122,7 @@ export const moderationLogTypes = [
|
|||||||
'deletePage',
|
'deletePage',
|
||||||
'deleteFlash',
|
'deleteFlash',
|
||||||
'deleteGalleryPost',
|
'deleteGalleryPost',
|
||||||
|
'updateProxyAccountDescription',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type ModerationLogPayloads = {
|
export type ModerationLogPayloads = {
|
||||||
@@ -374,25 +375,29 @@ export type ModerationLogPayloads = {
|
|||||||
postUserUsername: string;
|
postUserUsername: string;
|
||||||
post: any;
|
post: any;
|
||||||
};
|
};
|
||||||
|
updateProxyAccountDescription: {
|
||||||
|
before: string | null;
|
||||||
|
after: string | null;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Serialized<T> = {
|
export type Serialized<T> = {
|
||||||
[K in keyof T]:
|
[K in keyof T]:
|
||||||
T[K] extends Date
|
T[K] extends Date
|
||||||
? string
|
? string
|
||||||
: T[K] extends (Date | null)
|
: T[K] extends (Date | null)
|
||||||
? (string | null)
|
? (string | null)
|
||||||
: T[K] extends Record<string, any>
|
: T[K] extends Record<string, any>
|
||||||
? Serialized<T[K]>
|
? Serialized<T[K]>
|
||||||
: T[K] extends (Record<string, any> | null)
|
: T[K] extends (Record<string, any> | null)
|
||||||
? (Serialized<T[K]> | null)
|
? (Serialized<T[K]> | null)
|
||||||
: T[K] extends (Record<string, any> | undefined)
|
: T[K] extends (Record<string, any> | undefined)
|
||||||
? (Serialized<T[K]> | undefined)
|
? (Serialized<T[K]> | undefined)
|
||||||
: T[K];
|
: T[K];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FilterUnionByProperty<
|
export type FilterUnionByProperty<
|
||||||
Union,
|
Union,
|
||||||
Property extends string | number | symbol,
|
Property extends string | number | symbol,
|
||||||
Condition
|
Condition,
|
||||||
> = Union extends Record<Property, Condition> ? Union : never;
|
> = Union extends Record<Property, Condition> ? Union : never;
|
||||||
|
@@ -12,7 +12,7 @@ services:
|
|||||||
retries: 20
|
retries: 20
|
||||||
|
|
||||||
misskey:
|
misskey:
|
||||||
image: node:20
|
image: node:22.14.0
|
||||||
env_file:
|
env_file:
|
||||||
- ./.config/docker.env
|
- ./.config/docker.env
|
||||||
environment:
|
environment:
|
||||||
|
@@ -16,12 +16,16 @@ services:
|
|||||||
"
|
"
|
||||||
|
|
||||||
tester:
|
tester:
|
||||||
image: node:20
|
image: node:22.14.0
|
||||||
depends_on:
|
depends_on:
|
||||||
a.test:
|
a.test:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
misskey.a.test:
|
||||||
|
condition: service_healthy
|
||||||
b.test:
|
b.test:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
misskey.b.test:
|
||||||
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- NODE_ENV=development
|
||||||
- NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/rootCA.crt
|
- NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/rootCA.crt
|
||||||
@@ -82,7 +86,7 @@ services:
|
|||||||
command: pnpm -F backend test:fed
|
command: pnpm -F backend test:fed
|
||||||
|
|
||||||
daemon:
|
daemon:
|
||||||
image: node:20
|
image: node:22.14.0
|
||||||
depends_on:
|
depends_on:
|
||||||
redis.test:
|
redis.test:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
@@ -35,7 +35,7 @@ describe('Abuse report', () => {
|
|||||||
const reportsInB = await bModerator.client.request('admin/abuse-user-reports', {});
|
const reportsInB = await bModerator.client.request('admin/abuse-user-reports', {});
|
||||||
const reportInB = reportsInB.filter(report => report.comment.includes(comment))[0];
|
const reportInB = reportsInB.filter(report => report.comment.includes(comment))[0];
|
||||||
// NOTE: reporter is not Alice, and is not moderator in A
|
// NOTE: reporter is not Alice, and is not moderator in A
|
||||||
strictEqual(reportInB.reporter.url, 'https://a.test/@instance.actor');
|
strictEqual(reportInB.reporter.url, 'https://a.test/@system.actor');
|
||||||
strictEqual(reportInB.targetUserId, bob.id);
|
strictEqual(reportInB.targetUserId, bob.id);
|
||||||
|
|
||||||
// NOTE: cannot forward multiple times
|
// NOTE: cannot forward multiple times
|
||||||
|
@@ -37,6 +37,7 @@ describe('User', () => {
|
|||||||
'id',
|
'id',
|
||||||
'host',
|
'host',
|
||||||
'avatarUrl',
|
'avatarUrl',
|
||||||
|
'avatarBlurhash',
|
||||||
'instance',
|
'instance',
|
||||||
'badgeRoles',
|
'badgeRoles',
|
||||||
'url',
|
'url',
|
||||||
@@ -379,7 +380,8 @@ describe('User', () => {
|
|||||||
strictEqual(followers.length, 1); // followed by Bob
|
strictEqual(followers.length, 1); // followed by Bob
|
||||||
|
|
||||||
await alice.client.request('i/delete-account', { password: alice.password });
|
await alice.client.request('i/delete-account', { password: alice.password });
|
||||||
await sleep();
|
// NOTE: user deletion query is slow
|
||||||
|
await sleep(4000);
|
||||||
|
|
||||||
const following = await bob.client.request('users/following', { userId: bob.id });
|
const following = await bob.client.request('users/following', { userId: bob.id });
|
||||||
strictEqual(following.length, 0); // no following relation
|
strictEqual(following.length, 0); // no following relation
|
||||||
@@ -477,7 +479,8 @@ describe('User', () => {
|
|||||||
strictEqual(followers.length, 1); // followed by Bob
|
strictEqual(followers.length, 1); // followed by Bob
|
||||||
|
|
||||||
await aAdmin.client.request('admin/suspend-user', { userId: alice.id });
|
await aAdmin.client.request('admin/suspend-user', { userId: alice.id });
|
||||||
await sleep();
|
// NOTE: user deletion query is slow
|
||||||
|
await sleep(4000);
|
||||||
|
|
||||||
const following = await bob.client.request('users/following', { userId: bob.id });
|
const following = await bob.client.request('users/following', { userId: bob.id });
|
||||||
strictEqual(following.length, 0); // no following relation
|
strictEqual(following.length, 0); // no following relation
|
||||||
|
@@ -36,7 +36,7 @@ export type Request = <
|
|||||||
|
|
||||||
type Host = 'a.test' | 'b.test';
|
type Host = 'a.test' | 'b.test';
|
||||||
|
|
||||||
export async function sleep(ms = 200): Promise<void> {
|
export async function sleep(ms = 250): Promise<void> {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,14 +7,10 @@ import type { Config } from '@/config.js';
|
|||||||
import type { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
|
import type { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
|
||||||
import type { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
import type { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
import type { ApRequestService } from '@/core/activitypub/ApRequestService.js';
|
import type { ApRequestService } from '@/core/activitypub/ApRequestService.js';
|
||||||
import { Resolver } from '@/core/activitypub/ApResolverService.js';
|
|
||||||
import type { IObject } from '@/core/activitypub/type.js';
|
import type { IObject } from '@/core/activitypub/type.js';
|
||||||
import type { HttpRequestService } from '@/core/HttpRequestService.js';
|
import type { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||||
import type { InstanceActorService } from '@/core/InstanceActorService.js';
|
|
||||||
import type { LoggerService } from '@/core/LoggerService.js';
|
import type { LoggerService } from '@/core/LoggerService.js';
|
||||||
import type { MetaService } from '@/core/MetaService.js';
|
|
||||||
import type { UtilityService } from '@/core/UtilityService.js';
|
import type { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
import type {
|
import type {
|
||||||
FollowRequestsRepository,
|
FollowRequestsRepository,
|
||||||
MiMeta,
|
MiMeta,
|
||||||
@@ -23,6 +19,9 @@ import type {
|
|||||||
PollsRepository,
|
PollsRepository,
|
||||||
UsersRepository,
|
UsersRepository,
|
||||||
} from '@/models/_.js';
|
} from '@/models/_.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { Resolver } from '@/core/activitypub/ApResolverService.js';
|
||||||
|
|
||||||
type MockResponse = {
|
type MockResponse = {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -43,7 +42,7 @@ export class MockResolver extends Resolver {
|
|||||||
{} as NoteReactionsRepository,
|
{} as NoteReactionsRepository,
|
||||||
{} as FollowRequestsRepository,
|
{} as FollowRequestsRepository,
|
||||||
{} as UtilityService,
|
{} as UtilityService,
|
||||||
{} as InstanceActorService,
|
{} as SystemAccountService,
|
||||||
{} as ApRequestService,
|
{} as ApRequestService,
|
||||||
{} as HttpRequestService,
|
{} as HttpRequestService,
|
||||||
{} as ApRendererService,
|
{} as ApRendererService,
|
||||||
|
@@ -149,9 +149,9 @@ describe('AbuseReportNotificationService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true });
|
root = await createUser({ username: 'root', usernameLower: 'root' });
|
||||||
alice = await createUser({ username: 'alice', usernameLower: 'alice', isRoot: false });
|
alice = await createUser({ username: 'alice', usernameLower: 'alice' });
|
||||||
bob = await createUser({ username: 'bob', usernameLower: 'bob', isRoot: false });
|
bob = await createUser({ username: 'bob', usernameLower: 'bob' });
|
||||||
systemWebhook1 = await createWebhook();
|
systemWebhook1 = await createWebhook();
|
||||||
systemWebhook2 = await createWebhook();
|
systemWebhook2 = await createWebhook();
|
||||||
|
|
||||||
|
@@ -79,9 +79,9 @@ describe('FlashService', () => {
|
|||||||
userProfilesRepository = app.get(DI.userProfilesRepository);
|
userProfilesRepository = app.get(DI.userProfilesRepository);
|
||||||
idService = app.get(IdService);
|
idService = app.get(IdService);
|
||||||
|
|
||||||
root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true });
|
root = await createUser({ username: 'root', usernameLower: 'root' });
|
||||||
alice = await createUser({ username: 'alice', usernameLower: 'alice', isRoot: false });
|
alice = await createUser({ username: 'alice', usernameLower: 'alice' });
|
||||||
bob = await createUser({ username: 'bob', usernameLower: 'bob', isRoot: false });
|
bob = await createUser({ username: 'bob', usernameLower: 'bob' });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
@@ -3,24 +3,21 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
|
||||||
|
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import { jest } from '@jest/globals';
|
import { jest } from '@jest/globals';
|
||||||
import { ModuleMocker } from 'jest-mock';
|
|
||||||
import { Test } from '@nestjs/testing';
|
import { Test } from '@nestjs/testing';
|
||||||
import { GlobalModule } from '@/GlobalModule.js';
|
import { ModuleMocker } from 'jest-mock';
|
||||||
import { RelayService } from '@/core/RelayService.js';
|
|
||||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
|
||||||
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
|
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
|
||||||
import { IdService } from '@/core/IdService.js';
|
|
||||||
import type { RelaysRepository } from '@/models/_.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import type { TestingModule } from '@nestjs/testing';
|
import type { TestingModule } from '@nestjs/testing';
|
||||||
import type { MockFunctionMetadata } from 'jest-mock';
|
import type { MockFunctionMetadata } from 'jest-mock';
|
||||||
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
|
import { RelayService } from '@/core/RelayService.js';
|
||||||
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
import { GlobalModule } from '@/GlobalModule.js';
|
||||||
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
|
|
||||||
const moduleMocker = new ModuleMocker(global);
|
const moduleMocker = new ModuleMocker(global);
|
||||||
|
|
||||||
@@ -28,8 +25,6 @@ describe('RelayService', () => {
|
|||||||
let app: TestingModule;
|
let app: TestingModule;
|
||||||
let relayService: RelayService;
|
let relayService: RelayService;
|
||||||
let queueService: jest.Mocked<QueueService>;
|
let queueService: jest.Mocked<QueueService>;
|
||||||
let relaysRepository: RelaysRepository;
|
|
||||||
let userEntityService: UserEntityService;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await Test.createTestingModule({
|
app = await Test.createTestingModule({
|
||||||
@@ -38,10 +33,10 @@ describe('RelayService', () => {
|
|||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
IdService,
|
IdService,
|
||||||
CreateSystemUserService,
|
|
||||||
ApRendererService,
|
ApRendererService,
|
||||||
RelayService,
|
RelayService,
|
||||||
UserEntityService,
|
UserEntityService,
|
||||||
|
SystemAccountService,
|
||||||
UtilityService,
|
UtilityService,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@@ -61,8 +56,6 @@ describe('RelayService', () => {
|
|||||||
|
|
||||||
relayService = app.get<RelayService>(RelayService);
|
relayService = app.get<RelayService>(RelayService);
|
||||||
queueService = app.get<QueueService>(QueueService) as jest.Mocked<QueueService>;
|
queueService = app.get<QueueService>(QueueService) as jest.Mocked<QueueService>;
|
||||||
relaysRepository = app.get<RelaysRepository>(DI.relaysRepository);
|
|
||||||
userEntityService = app.get<UserEntityService>(UserEntityService);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
@@ -57,6 +57,12 @@ describe('RoleService', () => {
|
|||||||
return await usersRepository.findOneByOrFail(x.identifiers[0]);
|
return await usersRepository.findOneByOrFail(x.identifiers[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createRoot(data: Partial<MiUser> = {}) {
|
||||||
|
const user = await createUser(data);
|
||||||
|
meta.rootUserId = user.id;
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
async function createRole(data: Partial<MiRole> = {}) {
|
async function createRole(data: Partial<MiRole> = {}) {
|
||||||
const x = await rolesRepository.insert({
|
const x = await rolesRepository.insert({
|
||||||
id: genAidx(Date.now()),
|
id: genAidx(Date.now()),
|
||||||
@@ -279,7 +285,7 @@ describe('RoleService', () => {
|
|||||||
describe('getModeratorIds', () => {
|
describe('getModeratorIds', () => {
|
||||||
test('includeAdmins = false, includeRoot = false, excludeExpire = false', async () => {
|
test('includeAdmins = false, includeRoot = false, excludeExpire = false', async () => {
|
||||||
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
||||||
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
|
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||||
@@ -305,7 +311,7 @@ describe('RoleService', () => {
|
|||||||
|
|
||||||
test('includeAdmins = false, includeRoot = false, excludeExpire = true', async () => {
|
test('includeAdmins = false, includeRoot = false, excludeExpire = true', async () => {
|
||||||
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
||||||
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
|
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||||
@@ -331,7 +337,7 @@ describe('RoleService', () => {
|
|||||||
|
|
||||||
test('includeAdmins = true, includeRoot = false, excludeExpire = false', async () => {
|
test('includeAdmins = true, includeRoot = false, excludeExpire = false', async () => {
|
||||||
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
||||||
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
|
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||||
@@ -357,7 +363,7 @@ describe('RoleService', () => {
|
|||||||
|
|
||||||
test('includeAdmins = true, includeRoot = false, excludeExpire = true', async () => {
|
test('includeAdmins = true, includeRoot = false, excludeExpire = true', async () => {
|
||||||
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
||||||
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
|
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||||
@@ -383,7 +389,7 @@ describe('RoleService', () => {
|
|||||||
|
|
||||||
test('includeAdmins = false, includeRoot = true, excludeExpire = false', async () => {
|
test('includeAdmins = false, includeRoot = true, excludeExpire = false', async () => {
|
||||||
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
|
||||||
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
|
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||||
@@ -409,7 +415,7 @@ describe('RoleService', () => {
|
|||||||
|
|
||||||
test('root has moderator role', async () => {
|
test('root has moderator role', async () => {
|
||||||
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
|
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
|
||||||
createUser(), createUser(), createUser(), createUser({ isRoot: true }),
|
createUser(), createUser(), createUser(), createRoot(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||||
@@ -433,7 +439,7 @@ describe('RoleService', () => {
|
|||||||
|
|
||||||
test('root has administrator role', async () => {
|
test('root has administrator role', async () => {
|
||||||
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
|
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
|
||||||
createUser(), createUser(), createUser(), createUser({ isRoot: true }),
|
createUser(), createUser(), createUser(), createRoot(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||||
@@ -457,7 +463,7 @@ describe('RoleService', () => {
|
|||||||
|
|
||||||
test('root has moderator role(expire)', async () => {
|
test('root has moderator role(expire)', async () => {
|
||||||
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
|
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
|
||||||
createUser(), createUser(), createUser(), createUser({ isRoot: true }),
|
createUser(), createUser(), createUser(), createRoot(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||||
|
@@ -97,7 +97,7 @@ describe('SystemWebhookService', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function beforeEachImpl() {
|
async function beforeEachImpl() {
|
||||||
root = await createUser({ isRoot: true, username: 'root', usernameLower: 'root' });
|
root = await createUser({ username: 'root', usernameLower: 'root' });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function afterEachImpl() {
|
async function afterEachImpl() {
|
||||||
|
@@ -113,7 +113,7 @@ describe('UserSearchService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true });
|
root = await createUser({ username: 'root', usernameLower: 'root' });
|
||||||
alice = await createUser({ username: 'Alice', usernameLower: 'alice' });
|
alice = await createUser({ username: 'Alice', usernameLower: 'alice' });
|
||||||
alyce = await createUser({ username: 'Alyce', usernameLower: 'alyce' });
|
alyce = await createUser({ username: 'Alyce', usernameLower: 'alyce' });
|
||||||
alycia = await createUser({ username: 'Alycia', usernameLower: 'alycia' });
|
alycia = await createUser({ username: 'Alycia', usernameLower: 'alycia' });
|
||||||
|
@@ -91,7 +91,7 @@ describe('UserWebhookService', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function beforeEachImpl() {
|
async function beforeEachImpl() {
|
||||||
root = await createUser({ isRoot: true, username: 'root', usernameLower: 'root' });
|
root = await createUser({ username: 'root', usernameLower: 'root' });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function afterEachImpl() {
|
async function afterEachImpl() {
|
||||||
|
@@ -88,8 +88,8 @@ describe('WebhookTestService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true });
|
root = await createUser({ username: 'root', usernameLower: 'root' });
|
||||||
alice = await createUser({ username: 'alice', usernameLower: 'alice', isRoot: false });
|
alice = await createUser({ username: 'alice', usernameLower: 'alice' });
|
||||||
|
|
||||||
userWebhookService.fetchWebhooks.mockReturnValue(Promise.resolve([
|
userWebhookService.fetchWebhooks.mockReturnValue(Promise.resolve([
|
||||||
{ id: 'dummy-webhook', active: true, userId: alice.id } as MiWebhook,
|
{ id: 'dummy-webhook', active: true, userId: alice.id } as MiWebhook,
|
||||||
|
@@ -316,7 +316,7 @@ describe('CheckModeratorsActivityProcessorService', () => {
|
|||||||
createUser({}, { email: 'user2@example.com', emailVerified: false }),
|
createUser({}, { email: 'user2@example.com', emailVerified: false }),
|
||||||
createUser({}, { email: null, emailVerified: false }),
|
createUser({}, { email: null, emailVerified: false }),
|
||||||
createUser({}, { email: 'user4@example.com', emailVerified: true }),
|
createUser({}, { email: 'user4@example.com', emailVerified: true }),
|
||||||
createUser({ isRoot: true }, { email: 'root@example.com', emailVerified: true }),
|
createUser({}, { email: 'root@example.com', emailVerified: true }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
mockModeratorRole([user1, user2, user3, root]);
|
mockModeratorRole([user1, user2, user3, root]);
|
||||||
@@ -349,7 +349,7 @@ describe('CheckModeratorsActivityProcessorService', () => {
|
|||||||
createUser({}, { email: 'user2@example.com', emailVerified: false }),
|
createUser({}, { email: 'user2@example.com', emailVerified: false }),
|
||||||
createUser({}, { email: null, emailVerified: false }),
|
createUser({}, { email: null, emailVerified: false }),
|
||||||
createUser({}, { email: 'user4@example.com', emailVerified: true }),
|
createUser({}, { email: 'user4@example.com', emailVerified: true }),
|
||||||
createUser({ isRoot: true }, { email: 'root@example.com', emailVerified: true }),
|
createUser({}, { email: 'root@example.com', emailVerified: true }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
mockModeratorRole([user1, user2, user3, root]);
|
mockModeratorRole([user1, user2, user3, root]);
|
||||||
|
@@ -25,16 +25,16 @@
|
|||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"frontend-shared": "workspace:*",
|
"frontend-shared": "workspace:*",
|
||||||
"punycode.js": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"rollup": "4.34.8",
|
"rollup": "4.34.9",
|
||||||
"sass": "1.85.0",
|
"sass": "1.85.1",
|
||||||
"shiki": "3.0.0",
|
"shiki": "3.1.0",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tsc-alias": "1.8.10",
|
"tsc-alias": "1.8.11",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "5.7.3",
|
"typescript": "5.8.2",
|
||||||
"uuid": "11.1.0",
|
"uuid": "11.1.0",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"vite": "6.1.1",
|
"vite": "6.2.0",
|
||||||
"vue": "3.5.13"
|
"vue": "3.5.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -42,29 +42,29 @@
|
|||||||
"@testing-library/vue": "8.1.0",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/estree": "1.0.6",
|
"@types/estree": "1.0.6",
|
||||||
"@types/micromatch": "4.0.9",
|
"@types/micromatch": "4.0.9",
|
||||||
"@types/node": "22.13.5",
|
"@types/node": "22.13.9",
|
||||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/ws": "8.5.14",
|
"@types/ws": "8.18.0",
|
||||||
"@typescript-eslint/eslint-plugin": "8.24.1",
|
"@typescript-eslint/eslint-plugin": "8.26.0",
|
||||||
"@typescript-eslint/parser": "8.24.1",
|
"@typescript-eslint/parser": "8.26.0",
|
||||||
"@vitest/coverage-v8": "3.0.6",
|
"@vitest/coverage-v8": "3.0.7",
|
||||||
"@vue/runtime-core": "3.5.13",
|
"@vue/runtime-core": "3.5.13",
|
||||||
"acorn": "8.14.0",
|
"acorn": "8.14.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"eslint-plugin-vue": "9.32.0",
|
"eslint-plugin-vue": "9.33.0",
|
||||||
"fast-glob": "3.3.3",
|
"fast-glob": "3.3.3",
|
||||||
"happy-dom": "17.1.4",
|
"happy-dom": "17.2.2",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"micromatch": "4.0.8",
|
"micromatch": "4.0.8",
|
||||||
"msw": "2.7.1",
|
"msw": "2.7.3",
|
||||||
"nodemon": "3.1.9",
|
"nodemon": "3.1.9",
|
||||||
"prettier": "3.5.2",
|
"prettier": "3.5.3",
|
||||||
"start-server-and-test": "2.0.10",
|
"start-server-and-test": "2.0.10",
|
||||||
"vite-plugin-turbosnap": "1.0.3",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
"vue-component-type-helpers": "2.2.4",
|
"vue-component-type-helpers": "2.2.8",
|
||||||
"vue-eslint-parser": "9.4.3",
|
"vue-eslint-parser": "9.4.3",
|
||||||
"vue-tsc": "2.2.4"
|
"vue-tsc": "2.2.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,13 +21,13 @@
|
|||||||
"lint": "pnpm typecheck && pnpm eslint"
|
"lint": "pnpm typecheck && pnpm eslint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "22.13.5",
|
"@types/node": "22.13.9",
|
||||||
"@typescript-eslint/eslint-plugin": "8.24.1",
|
"@typescript-eslint/eslint-plugin": "8.26.0",
|
||||||
"@typescript-eslint/parser": "8.24.1",
|
"@typescript-eslint/parser": "8.26.0",
|
||||||
"esbuild": "0.25.0",
|
"esbuild": "0.25.0",
|
||||||
"eslint-plugin-vue": "9.32.0",
|
"eslint-plugin-vue": "9.33.0",
|
||||||
"nodemon": "3.1.9",
|
"nodemon": "3.1.9",
|
||||||
"typescript": "5.7.3",
|
"typescript": "5.8.2",
|
||||||
"vue-eslint-parser": "9.4.3"
|
"vue-eslint-parser": "9.4.3"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
@@ -40,9 +40,9 @@
|
|||||||
"chartjs-chart-matrix": "2.0.1",
|
"chartjs-chart-matrix": "2.0.1",
|
||||||
"chartjs-plugin-gradient": "0.6.1",
|
"chartjs-plugin-gradient": "0.6.1",
|
||||||
"chartjs-plugin-zoom": "2.2.0",
|
"chartjs-plugin-zoom": "2.2.0",
|
||||||
"chromatic": "11.25.2",
|
"chromatic": "11.27.0",
|
||||||
"compare-versions": "6.1.1",
|
"compare-versions": "6.1.1",
|
||||||
"cropperjs": "2.0.0-rc.2",
|
"cropperjs": "2.0.0",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "4.1.0",
|
||||||
"estree-walker": "3.0.3",
|
"estree-walker": "3.0.3",
|
||||||
"eventemitter3": "5.0.1",
|
"eventemitter3": "5.0.1",
|
||||||
@@ -58,84 +58,84 @@
|
|||||||
"misskey-reversi": "workspace:*",
|
"misskey-reversi": "workspace:*",
|
||||||
"photoswipe": "5.4.4",
|
"photoswipe": "5.4.4",
|
||||||
"punycode.js": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"rollup": "4.34.8",
|
"rollup": "4.34.9",
|
||||||
"sanitize-html": "2.14.0",
|
"sanitize-html": "2.14.0",
|
||||||
"sass": "1.85.0",
|
"sass": "1.85.1",
|
||||||
"shiki": "3.0.0",
|
"shiki": "3.1.0",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"three": "0.173.0",
|
"three": "0.174.0",
|
||||||
"throttle-debounce": "5.0.2",
|
"throttle-debounce": "5.0.2",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tsc-alias": "1.8.10",
|
"tsc-alias": "1.8.11",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "5.7.3",
|
"typescript": "5.8.2",
|
||||||
"uuid": "11.1.0",
|
"uuid": "11.1.0",
|
||||||
"v-code-diff": "1.13.1",
|
"v-code-diff": "1.13.1",
|
||||||
"vite": "6.1.1",
|
"vite": "6.2.0",
|
||||||
"vue": "3.5.13",
|
"vue": "3.5.13",
|
||||||
"vuedraggable": "next"
|
"vuedraggable": "next"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/summaly": "5.2.0",
|
"@misskey-dev/summaly": "5.2.0",
|
||||||
"@storybook/addon-actions": "8.5.8",
|
"@storybook/addon-actions": "8.6.3",
|
||||||
"@storybook/addon-essentials": "8.5.8",
|
"@storybook/addon-essentials": "8.6.3",
|
||||||
"@storybook/addon-interactions": "8.5.8",
|
"@storybook/addon-interactions": "8.6.3",
|
||||||
"@storybook/addon-links": "8.5.8",
|
"@storybook/addon-links": "8.6.3",
|
||||||
"@storybook/addon-mdx-gfm": "8.5.8",
|
"@storybook/addon-mdx-gfm": "8.6.3",
|
||||||
"@storybook/addon-storysource": "8.5.8",
|
"@storybook/addon-storysource": "8.6.3",
|
||||||
"@storybook/blocks": "8.5.8",
|
"@storybook/blocks": "8.6.3",
|
||||||
"@storybook/components": "8.5.8",
|
"@storybook/components": "8.6.3",
|
||||||
"@storybook/core-events": "8.5.8",
|
"@storybook/core-events": "8.6.3",
|
||||||
"@storybook/manager-api": "8.5.8",
|
"@storybook/manager-api": "8.6.3",
|
||||||
"@storybook/preview-api": "8.5.8",
|
"@storybook/preview-api": "8.6.3",
|
||||||
"@storybook/react": "8.5.8",
|
"@storybook/react": "8.6.3",
|
||||||
"@storybook/react-vite": "8.5.8",
|
"@storybook/react-vite": "8.6.3",
|
||||||
"@storybook/test": "8.5.8",
|
"@storybook/test": "8.6.3",
|
||||||
"@storybook/theming": "8.5.8",
|
"@storybook/theming": "8.6.3",
|
||||||
"@storybook/types": "8.5.8",
|
"@storybook/types": "8.6.3",
|
||||||
"@storybook/vue3": "8.5.8",
|
"@storybook/vue3": "8.6.3",
|
||||||
"@storybook/vue3-vite": "8.5.8",
|
"@storybook/vue3-vite": "8.6.3",
|
||||||
"@testing-library/vue": "8.1.0",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/canvas-confetti": "1.9.0",
|
"@types/canvas-confetti": "1.9.0",
|
||||||
"@types/estree": "1.0.6",
|
"@types/estree": "1.0.6",
|
||||||
"@types/matter-js": "0.19.8",
|
"@types/matter-js": "0.19.8",
|
||||||
"@types/micromatch": "4.0.9",
|
"@types/micromatch": "4.0.9",
|
||||||
"@types/node": "22.13.5",
|
"@types/node": "22.13.9",
|
||||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||||
"@types/sanitize-html": "2.13.0",
|
"@types/sanitize-html": "2.13.0",
|
||||||
"@types/seedrandom": "3.0.8",
|
"@types/seedrandom": "3.0.8",
|
||||||
"@types/throttle-debounce": "5.0.2",
|
"@types/throttle-debounce": "5.0.2",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/ws": "8.5.14",
|
"@types/ws": "8.18.0",
|
||||||
"@typescript-eslint/eslint-plugin": "8.24.1",
|
"@typescript-eslint/eslint-plugin": "8.26.0",
|
||||||
"@typescript-eslint/parser": "8.24.1",
|
"@typescript-eslint/parser": "8.26.0",
|
||||||
"@vitest/coverage-v8": "3.0.6",
|
"@vitest/coverage-v8": "3.0.7",
|
||||||
"@vue/runtime-core": "3.5.13",
|
"@vue/runtime-core": "3.5.13",
|
||||||
"acorn": "8.14.0",
|
"acorn": "8.14.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "14.0.3",
|
"cypress": "14.1.0",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"eslint-plugin-vue": "9.32.0",
|
"eslint-plugin-vue": "9.33.0",
|
||||||
"fast-glob": "3.3.3",
|
"fast-glob": "3.3.3",
|
||||||
"happy-dom": "17.1.4",
|
"happy-dom": "17.2.2",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"micromatch": "4.0.8",
|
"micromatch": "4.0.8",
|
||||||
"msw": "2.7.1",
|
"msw": "2.7.3",
|
||||||
"msw-storybook-addon": "2.0.4",
|
"msw-storybook-addon": "2.0.4",
|
||||||
"nodemon": "3.1.9",
|
"nodemon": "3.1.9",
|
||||||
"prettier": "3.5.2",
|
"prettier": "3.5.3",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "19.0.0",
|
||||||
"seedrandom": "3.0.5",
|
"seedrandom": "3.0.5",
|
||||||
"start-server-and-test": "2.0.10",
|
"start-server-and-test": "2.0.10",
|
||||||
"storybook": "8.5.8",
|
"storybook": "8.6.3",
|
||||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||||
"vite-plugin-turbosnap": "1.0.3",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
"vitest": "3.0.6",
|
"vitest": "3.0.7",
|
||||||
"vitest-fetch-mock": "0.4.3",
|
"vitest-fetch-mock": "0.4.5",
|
||||||
"vue-component-type-helpers": "2.2.4",
|
"vue-component-type-helpers": "2.2.8",
|
||||||
"vue-eslint-parser": "9.4.3",
|
"vue-eslint-parser": "9.4.3",
|
||||||
"vue-tsc": "2.2.4"
|
"vue-tsc": "2.2.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -413,7 +413,7 @@ function computeButtonTitle(ev: MouseEvent): void {
|
|||||||
|
|
||||||
function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef, ev?: MouseEvent) {
|
function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef, ev?: MouseEvent) {
|
||||||
const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined;
|
const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined;
|
||||||
if (el) {
|
if (el && defaultStore.state.animation) {
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
const x = rect.left + (el.offsetWidth / 2);
|
const x = rect.left + (el.offsetWidth / 2);
|
||||||
const y = rect.top + (el.offsetHeight / 2);
|
const y = rect.top + (el.offsetHeight / 2);
|
||||||
|
@@ -259,7 +259,14 @@ function showMenu(ev: MouseEvent) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSensitive(file: Misskey.entities.DriveFile) {
|
async function toggleSensitive(file: Misskey.entities.DriveFile) {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: file.isSensitive ? i18n.ts.unmarkAsSensitiveConfirm : i18n.ts.markAsSensitiveConfirm,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
os.apiWithDialog('drive/files/update', {
|
os.apiWithDialog('drive/files/update', {
|
||||||
fileId: file.id,
|
fileId: file.id,
|
||||||
isSensitive: !file.isSensitive,
|
isSensitive: !file.isSensitive,
|
||||||
|
@@ -124,11 +124,21 @@ function showMenu(ev: MouseEvent) {
|
|||||||
|
|
||||||
if (iAmModerator) {
|
if (iAmModerator) {
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
text: i18n.ts.markAsSensitive,
|
text: props.image.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
|
||||||
icon: 'ti ti-eye-exclamation',
|
icon: 'ti ti-eye-exclamation',
|
||||||
danger: true,
|
danger: true,
|
||||||
action: () => {
|
action: async () => {
|
||||||
os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true });
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: props.image.isSensitive ? i18n.ts.unmarkAsSensitiveConfirm : i18n.ts.markAsSensitiveConfirm,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
os.apiWithDialog('drive/files/update', {
|
||||||
|
fileId: props.image.id,
|
||||||
|
isSensitive: !props.image.isSensitive,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -284,7 +284,14 @@ function showMenu(ev: MouseEvent) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSensitive(file: Misskey.entities.DriveFile) {
|
async function toggleSensitive(file: Misskey.entities.DriveFile) {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: file.isSensitive ? i18n.ts.unmarkAsSensitiveConfirm : i18n.ts.markAsSensitiveConfirm,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
os.apiWithDialog('drive/files/update', {
|
os.apiWithDialog('drive/files/update', {
|
||||||
fileId: file.id,
|
fileId: file.id,
|
||||||
isSensitive: !file.isSensitive,
|
isSensitive: !file.isSensitive,
|
||||||
|
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<img :class="$style.icon" :src="avatarUrl" alt="">
|
<img :class="$style.icon" :src="avatarUrl" alt="">
|
||||||
<span>
|
<span>
|
||||||
<span>@{{ username }}</span>
|
<span>@{{ username }}</span>
|
||||||
<span v-if="(host != localHost) || defaultStore.state.showFullAcct" :class="$style.host">@{{ toUnicode(host) }}</span>
|
<span v-if="(host != localHost)" :class="$style.host">@{{ toUnicode(host) }}</span>
|
||||||
</span>
|
</span>
|
||||||
</MkA>
|
</MkA>
|
||||||
</template>
|
</template>
|
||||||
@@ -17,10 +17,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
import { toUnicode } from 'punycode.js';
|
import { toUnicode } from 'punycode.js';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { host as localHost } from '@@/js/config.js';
|
import { host as localHost } from '@@/js/config.js';
|
||||||
|
import type { MkABehavior } from '@/components/global/MkA.vue';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { getStaticImageUrl } from '@/scripts/media-proxy.js';
|
import { getStaticImageUrl } from '@/scripts/media-proxy.js';
|
||||||
import type { MkABehavior } from '@/components/global/MkA.vue';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
username: string;
|
username: string;
|
||||||
|
@@ -479,7 +479,7 @@ function react(): void {
|
|||||||
reaction: '❤️',
|
reaction: '❤️',
|
||||||
});
|
});
|
||||||
const el = reactButton.value;
|
const el = reactButton.value;
|
||||||
if (el) {
|
if (el && defaultStore.state.animation) {
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
const x = rect.left + (el.offsetWidth / 2);
|
const x = rect.left + (el.offsetWidth / 2);
|
||||||
const y = rect.top + (el.offsetHeight / 2);
|
const y = rect.top + (el.offsetHeight / 2);
|
||||||
|
@@ -442,7 +442,7 @@ function react(): void {
|
|||||||
reaction: '❤️',
|
reaction: '❤️',
|
||||||
});
|
});
|
||||||
const el = reactButton.value;
|
const el = reactButton.value;
|
||||||
if (el) {
|
if (el && defaultStore.state.animation) {
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
const x = rect.left + (el.offsetWidth / 2);
|
const x = rect.left + (el.offsetWidth / 2);
|
||||||
const y = rect.top + (el.offsetHeight / 2);
|
const y = rect.top + (el.offsetHeight / 2);
|
||||||
|
@@ -67,7 +67,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
|
<MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
|
||||||
<div v-show="useCw" :class="$style.cwOuter">
|
<div v-show="useCw" :class="$style.cwOuter">
|
||||||
<input ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown" @keyup="onKeyup" @compositionend="onCompositionEnd">
|
<input ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown" @keyup="onKeyup" @compositionend="onCompositionEnd">
|
||||||
<div v-if="maxCwTextLength - cwTextLength < 20" :class="['_acrylic', $style.cwTextCount, { [$style.cwTextOver]: cwTextLength > maxCwTextLength }]">{{ maxCwTextLength - cwTextLength }}</div>
|
<div v-if="maxCwTextLength - cwTextLength < 20" :class="['_acrylic', $style.cwTextCount, { [$style.cwTextOver]: cwTextLength > maxCwTextLength }]">{{ maxCwTextLength - cwTextLength }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="[$style.textOuter, { [$style.withCw]: useCw }]">
|
<div :class="[$style.textOuter, { [$style.withCw]: useCw }]">
|
||||||
<div v-if="channel" :class="$style.colorBar" :style="{ background: channel.color }"></div>
|
<div v-if="channel" :class="$style.colorBar" :style="{ background: channel.color }"></div>
|
||||||
@@ -104,18 +104,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed } from 'vue';
|
import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed } from 'vue';
|
||||||
import type { ShallowRef } from 'vue';
|
|
||||||
import * as mfm from 'mfm-js';
|
import * as mfm from 'mfm-js';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import insertTextAtCursor from 'insert-text-at-cursor';
|
import insertTextAtCursor from 'insert-text-at-cursor';
|
||||||
import { toASCII } from 'punycode.js';
|
import { toASCII } from 'punycode.js';
|
||||||
import { host, url } from '@@/js/config.js';
|
import { host, url } from '@@/js/config.js';
|
||||||
|
import type { ShallowRef } from 'vue';
|
||||||
import type { PostFormProps } from '@/types/post-form.js';
|
import type { PostFormProps } from '@/types/post-form.js';
|
||||||
import MkNoteSimple from '@/components/MkNoteSimple.vue';
|
import type { PollEditorModelValue } from '@/components/MkPollEditor.vue';
|
||||||
import MkNotePreview from '@/components/MkNotePreview.vue';
|
import MkNotePreview from '@/components/MkNotePreview.vue';
|
||||||
import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
|
import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
|
||||||
import MkPollEditor from '@/components/MkPollEditor.vue';
|
import MkPollEditor from '@/components/MkPollEditor.vue';
|
||||||
import type { PollEditorModelValue } from '@/components/MkPollEditor.vue';
|
import MkNoteSimple from '@/components/MkNoteSimple.vue';
|
||||||
import { erase, unique } from '@/scripts/array.js';
|
import { erase, unique } from '@/scripts/array.js';
|
||||||
import { extractMentions } from '@/scripts/extract-mentions.js';
|
import { extractMentions } from '@/scripts/extract-mentions.js';
|
||||||
import { formatTimeString } from '@/scripts/format-time-string.js';
|
import { formatTimeString } from '@/scripts/format-time-string.js';
|
||||||
@@ -150,6 +150,7 @@ const props = withDefaults(defineProps<PostFormProps & {
|
|||||||
autofocus: true,
|
autofocus: true,
|
||||||
mock: false,
|
mock: false,
|
||||||
initialLocalOnly: undefined,
|
initialLocalOnly: undefined,
|
||||||
|
deleteInitialNoteAfterPost: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
provide('mock', props.mock);
|
provide('mock', props.mock);
|
||||||
@@ -751,7 +752,7 @@ async function post(ev?: MouseEvent) {
|
|||||||
if (ev) {
|
if (ev) {
|
||||||
const el = (ev.currentTarget ?? ev.target) as HTMLElement | null;
|
const el = (ev.currentTarget ?? ev.target) as HTMLElement | null;
|
||||||
|
|
||||||
if (el) {
|
if (el && defaultStore.state.animation) {
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
const x = rect.left + (el.offsetWidth / 2);
|
const x = rect.left + (el.offsetWidth / 2);
|
||||||
const y = rect.top + (el.offsetHeight / 2);
|
const y = rect.top + (el.offsetHeight / 2);
|
||||||
@@ -845,6 +846,12 @@ async function post(ev?: MouseEvent) {
|
|||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
// 削除して編集の対象ノートを削除
|
||||||
|
if (props.initialNote && props.deleteInitialNoteAfterPost) {
|
||||||
|
misskeyApi('notes/delete', {
|
||||||
|
noteId: props.initialNote.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
deleteDraft();
|
deleteDraft();
|
||||||
emit('posted');
|
emit('posted');
|
||||||
if (postData.text && postData.text !== '') {
|
if (postData.text && postData.text !== '') {
|
||||||
@@ -896,6 +903,11 @@ async function post(ev?: MouseEvent) {
|
|||||||
if (m === 0 && s === 0) {
|
if (m === 0 && s === 0) {
|
||||||
claimAchievement('postedAt0min0sec');
|
claimAchievement('postedAt0min0sec');
|
||||||
}
|
}
|
||||||
|
if (props.initialNote && props.deleteInitialNoteAfterPost) {
|
||||||
|
if (date.getTime() - new Date(props.initialNote.createdAt).getTime() < 1000 * 60 && props.initialNote.userId === $i.id) {
|
||||||
|
claimAchievement('noteDeletedWithin1min');
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
posting.value = false;
|
posting.value = false;
|
||||||
@@ -1070,6 +1082,8 @@ defineExpose({
|
|||||||
&.modal {
|
&.modal {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 520px;
|
max-width: 520px;
|
||||||
|
overflow-x: clip;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,8 +4,23 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkModal ref="modal" :preferType="'dialog'" @click="modal?.close()" @closed="onModalClosed()" @esc="modal?.close()">
|
<MkModal
|
||||||
<MkPostForm ref="form" :class="$style.form" v-bind="props" autofocus freezeAfterPosted @posted="onPosted" @cancel="modal?.close()" @esc="modal?.close()"/>
|
ref="modal"
|
||||||
|
:preferType="'dialog'"
|
||||||
|
@click="modal?.close()"
|
||||||
|
@closed="onModalClosed()"
|
||||||
|
@esc="modal?.close()"
|
||||||
|
>
|
||||||
|
<MkPostForm
|
||||||
|
ref="form"
|
||||||
|
:class="$style.form"
|
||||||
|
v-bind="props"
|
||||||
|
autofocus
|
||||||
|
freezeAfterPosted
|
||||||
|
@posted="onPosted"
|
||||||
|
@cancel="modal?.close()"
|
||||||
|
@esc="modal?.close()"
|
||||||
|
/>
|
||||||
</MkModal>
|
</MkModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template>
|
<template>
|
||||||
<span>
|
<span>
|
||||||
<span>@{{ user.username }}</span>
|
<span>@{{ user.username }}</span>
|
||||||
<span v-if="user.host || detail || defaultStore.state.showFullAcct" style="opacity: 0.5;">@{{ user.host || host }}</span>
|
<span v-if="user.host || detail" style="opacity: 0.5;">@{{ user.host || host }}</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -14,7 +14,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { toUnicode } from 'punycode.js';
|
import { toUnicode } from 'punycode.js';
|
||||||
import { host as hostRaw } from '@@/js/config.js';
|
import { host as hostRaw } from '@@/js/config.js';
|
||||||
import { defaultStore } from '@/store.js';
|
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
user: Misskey.entities.UserLite;
|
user: Misskey.entities.UserLite;
|
||||||
|
@@ -4,12 +4,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
||||||
|
import { defaultStore } from '@/store.js';
|
||||||
import { popup } from '@/os.js';
|
import { popup } from '@/os.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mounted(el, binding, vn) {
|
mounted(el, binding, vn) {
|
||||||
// 明示的に false であればバインドしない
|
// 明示的に false であればバインドしない
|
||||||
if (binding.value === false) return;
|
if (binding.value === false) return;
|
||||||
|
if (!defaultStore.state.animation) return;
|
||||||
|
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener('click', () => {
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
|
@@ -36,8 +36,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkA v-if="file.user" class="user" :to="`/admin/user/${file.user.id}`">
|
<MkA v-if="file.user" class="user" :to="`/admin/user/${file.user.id}`">
|
||||||
<MkUserCardMini :user="file.user"/>
|
<MkUserCardMini :user="file.user"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<MkSwitch v-model="isSensitive" @update:modelValue="toggleIsSensitive">{{ i18n.ts.sensitive }}</MkSwitch>
|
<MkSwitch :modelValue="isSensitive" @update:modelValue="toggleSensitive">{{ i18n.ts.sensitive }}</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -117,9 +118,21 @@ async function del() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleIsSensitive(v) {
|
async function toggleSensitive() {
|
||||||
await misskeyApi('drive/files/update', { fileId: props.fileId, isSensitive: v });
|
if (!file.value) return;
|
||||||
isSensitive.value = v;
|
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: isSensitive.value ? i18n.ts.unmarkAsSensitiveConfirm : i18n.ts.markAsSensitiveConfirm,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
isSensitive.value = !isSensitive.value;
|
||||||
|
|
||||||
|
os.apiWithDialog('drive/files/update', {
|
||||||
|
fileId: file.value.id,
|
||||||
|
isSensitive: !file.value.isSensitive,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => [{
|
const headerActions = computed(() => [{
|
||||||
|
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkInfo v-if="['instance.actor', 'relay.actor'].includes(user.username)">{{ i18n.ts.isSystemAccount }}</MkInfo>
|
<MkInfo v-if="isSystem">{{ i18n.ts.isSystemAccount }}</MkInfo>
|
||||||
|
|
||||||
<FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ i18n.ts.instanceInfo }}</FormLink>
|
<FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ i18n.ts.instanceInfo }}</FormLink>
|
||||||
|
|
||||||
@@ -37,21 +37,23 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template #value><span class="_monospace">{{ ips[0].ip }}</span></template>
|
<template #value><span class="_monospace">{{ ips[0].ip }}</span></template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
-->
|
-->
|
||||||
<MkKeyValue oneline>
|
<template v-if="!isSystem">
|
||||||
<template #key>{{ i18n.ts.createdAt }}</template>
|
<MkKeyValue oneline>
|
||||||
<template #value><span class="_monospace"><MkTime :time="user.createdAt" :mode="'detail'"/></span></template>
|
<template #key>{{ i18n.ts.createdAt }}</template>
|
||||||
</MkKeyValue>
|
<template #value><span class="_monospace"><MkTime :time="user.createdAt" :mode="'detail'"/></span></template>
|
||||||
<MkKeyValue v-if="info" oneline>
|
</MkKeyValue>
|
||||||
<template #key>{{ i18n.ts.lastActiveDate }}</template>
|
<MkKeyValue v-if="info" oneline>
|
||||||
<template #value><span class="_monospace"><MkTime :time="info.lastActiveDate" :mode="'detail'"/></span></template>
|
<template #key>{{ i18n.ts.lastActiveDate }}</template>
|
||||||
</MkKeyValue>
|
<template #value><span class="_monospace"><MkTime :time="info.lastActiveDate" :mode="'detail'"/></span></template>
|
||||||
<MkKeyValue v-if="info" oneline>
|
</MkKeyValue>
|
||||||
<template #key>{{ i18n.ts.email }}</template>
|
<MkKeyValue v-if="info" oneline>
|
||||||
<template #value><span class="_monospace">{{ info.email }}</span></template>
|
<template #key>{{ i18n.ts.email }}</template>
|
||||||
</MkKeyValue>
|
<template #value><span class="_monospace">{{ info.email }}</span></template>
|
||||||
|
</MkKeyValue>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkTextarea v-model="moderationNote" manualSave>
|
<MkTextarea v-if="!isSystem" v-model="moderationNote" manualSave>
|
||||||
<template #label>{{ i18n.ts.moderationNote }}</template>
|
<template #label>{{ i18n.ts.moderationNote }}</template>
|
||||||
<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
|
<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
|
||||||
</MkTextarea>
|
</MkTextarea>
|
||||||
@@ -92,7 +94,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</FormSection>
|
</FormSection>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<FormSection>
|
<FormSection v-if="!isSystem">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
|
<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
|
||||||
|
|
||||||
@@ -252,6 +254,7 @@ const ap = ref<any>(null);
|
|||||||
const moderator = ref(false);
|
const moderator = ref(false);
|
||||||
const silenced = ref(false);
|
const silenced = ref(false);
|
||||||
const suspended = ref(false);
|
const suspended = ref(false);
|
||||||
|
const isSystem = ref(false);
|
||||||
const moderationNote = ref('');
|
const moderationNote = ref('');
|
||||||
const filesPagination = {
|
const filesPagination = {
|
||||||
endpoint: 'admin/drive/files' as const,
|
endpoint: 'admin/drive/files' as const,
|
||||||
@@ -288,6 +291,7 @@ function createFetcher() {
|
|||||||
silenced.value = info.value.isSilenced;
|
silenced.value = info.value.isSilenced;
|
||||||
suspended.value = info.value.isSuspended;
|
suspended.value = info.value.isSuspended;
|
||||||
moderationNote.value = info.value.moderationNote;
|
moderationNote.value = info.value.moderationNote;
|
||||||
|
isSystem.value = user.value.host == null && user.value.username.includes('.');
|
||||||
|
|
||||||
watch(moderationNote, async () => {
|
watch(moderationNote, async () => {
|
||||||
await misskeyApi('admin/update-user-note', { userId: user.value.id, text: moderationNote.value });
|
await misskeyApi('admin/update-user-note', { userId: user.value.id, text: moderationNote.value });
|
||||||
@@ -507,7 +511,15 @@ watch(user, () => {
|
|||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
const headerTabs = computed(() => [{
|
const headerTabs = computed(() => isSystem.value ? [{
|
||||||
|
key: 'overview',
|
||||||
|
title: i18n.ts.overview,
|
||||||
|
icon: 'ti ti-info-circle',
|
||||||
|
}, {
|
||||||
|
key: 'raw',
|
||||||
|
title: 'Raw',
|
||||||
|
icon: 'ti ti-code',
|
||||||
|
}] : [{
|
||||||
key: 'overview',
|
key: 'overview',
|
||||||
title: i18n.ts.overview,
|
title: i18n.ts.overview,
|
||||||
icon: 'ti ti-info-circle',
|
icon: 'ti ti-info-circle',
|
||||||
|
@@ -170,6 +170,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/>
|
<CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="log.type === 'updateProxyAccountDescription'">
|
||||||
|
<div :class="$style.diff">
|
||||||
|
<CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>raw</summary>
|
<summary>raw</summary>
|
||||||
|
@@ -238,15 +238,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkFolder>
|
<MkFolder>
|
||||||
<template #icon><i class="ti ti-ghost"></i></template>
|
<template #icon><i class="ti ti-ghost"></i></template>
|
||||||
<template #label>{{ i18n.ts.proxyAccount }}</template>
|
<template #label>{{ i18n.ts.proxyAccount }}</template>
|
||||||
|
<template v-if="proxyAccountForm.modified.value" #footer>
|
||||||
|
<MkFormFooter :form="proxyAccountForm"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkInfo>{{ i18n.ts.proxyAccountDescription }}</MkInfo>
|
<MkInfo>{{ i18n.ts.proxyAccountDescription }}</MkInfo>
|
||||||
<MkKeyValue>
|
|
||||||
<template #key>{{ i18n.ts.proxyAccount }}</template>
|
|
||||||
<template #value>{{ proxyAccount ? `@${proxyAccount.username}` : i18n.ts.none }}</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
|
|
||||||
<MkButton primary @click="chooseProxyAccount">{{ i18n.ts.selectAccount }}</MkButton>
|
<MkTextarea v-model="proxyAccountForm.state.description" :max="500" tall mfmAutocomplete :mfmPreview="true">
|
||||||
|
<template #label>{{ i18n.ts._profile.description }}</template>
|
||||||
|
<template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template>
|
||||||
|
</MkTextarea>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
</div>
|
</div>
|
||||||
@@ -256,7 +258,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, reactive } from 'vue';
|
||||||
import XHeader from './_header_.vue';
|
import XHeader from './_header_.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
@@ -277,7 +279,7 @@ import MkRadios from '@/components/MkRadios.vue';
|
|||||||
|
|
||||||
const meta = await misskeyApi('admin/meta');
|
const meta = await misskeyApi('admin/meta');
|
||||||
|
|
||||||
const proxyAccount = ref(meta.proxyAccountId ? await misskeyApi('users/show', { userId: meta.proxyAccountId }) : null);
|
const proxyAccount = await misskeyApi('users/show', { userId: meta.proxyAccountId });
|
||||||
|
|
||||||
const infoForm = useForm({
|
const infoForm = useForm({
|
||||||
name: meta.name ?? '',
|
name: meta.name ?? '',
|
||||||
@@ -378,16 +380,14 @@ const federationForm = useForm({
|
|||||||
fetchInstance(true);
|
fetchInstance(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
function chooseProxyAccount() {
|
const proxyAccountForm = useForm({
|
||||||
os.selectUser({ localOnly: true }).then(user => {
|
description: proxyAccount.description,
|
||||||
proxyAccount.value = user;
|
}, async (state) => {
|
||||||
os.apiWithDialog('admin/update-meta', {
|
await os.apiWithDialog('admin/update-proxy-account', {
|
||||||
proxyAccountId: user.id,
|
description: state.description,
|
||||||
}).then(() => {
|
|
||||||
fetchInstance(true);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
fetchInstance(true);
|
||||||
|
});
|
||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
|
@@ -120,6 +120,7 @@ import { i18n } from '@/i18n.js';
|
|||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
|
import { apLookup } from '@/scripts/lookup.js';
|
||||||
import { useRouter } from '@/router/supplier.js';
|
import { useRouter } from '@/router/supplier.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
@@ -260,13 +261,7 @@ async function search() {
|
|||||||
text: i18n.ts.lookupConfirm,
|
text: i18n.ts.lookupConfirm,
|
||||||
});
|
});
|
||||||
if (!confirm.canceled) {
|
if (!confirm.canceled) {
|
||||||
const promise = misskeyApi('ap/show', {
|
const res = await apLookup(searchParams.value.query);
|
||||||
uri: searchParams.value.query,
|
|
||||||
});
|
|
||||||
|
|
||||||
os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
|
|
||||||
|
|
||||||
const res = await promise;
|
|
||||||
|
|
||||||
if (res.type === 'User') {
|
if (res.type === 'User') {
|
||||||
router.push(`/@${res.object.username}@${res.object.host}`);
|
router.push(`/@${res.object.username}@${res.object.host}`);
|
||||||
|
@@ -13,7 +13,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<div class="profile _gaps">
|
<div class="profile _gaps">
|
||||||
<MkAccountMoved v-if="user.movedTo" :movedTo="user.movedTo"/>
|
<MkAccountMoved v-if="user.movedTo" :movedTo="user.movedTo"/>
|
||||||
<MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!" class="warn"/>
|
<MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!"/>
|
||||||
|
<MkInfo v-if="user.host == null && user.username.includes('.')">{{ i18n.ts.isSystemAccount }}</MkInfo>
|
||||||
|
|
||||||
<div :key="user.id" class="main _panel">
|
<div :key="user.id" class="main _panel">
|
||||||
<div class="banner-container" :style="style">
|
<div class="banner-container" :style="style">
|
||||||
@@ -48,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="user.followedMessage != null" class="followedMessage">
|
<div v-if="user.followedMessage != null" class="followedMessage">
|
||||||
<MkFukidashi class="fukidashi" :tail="narrow ? 'none' : 'left'" negativeMargin shadow>
|
<MkFukidashi class="fukidashi" :tail="narrow ? 'none' : 'left'" negativeMargin>
|
||||||
<div class="messageHeader">{{ i18n.ts.messageToFollower }}</div>
|
<div class="messageHeader">{{ i18n.ts.messageToFollower }}</div>
|
||||||
<div><MkSparkle><Mfm :plain="true" :text="user.followedMessage" :author="user"/></MkSparkle></div>
|
<div><MkSparkle><Mfm :plain="true" :text="user.followedMessage" :author="user"/></MkSparkle></div>
|
||||||
</MkFukidashi>
|
</MkFukidashi>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user