Merge remote-tracking branch 'misskey-original/develop' into develop
# Conflicts: # packages/backend/src/models/RepositoryModule.ts # packages/backend/src/models/_.ts # packages/backend/src/postgres.ts # packages/frontend/src/components/MkDrive.file.vue # packages/frontend/src/components/MkEmojiPicker.vue # packages/frontend/src/pages/admin/index.vue # packages/frontend/src/pages/user/home.vue # packages/frontend/src/router.ts # packages/frontend/src/ui/universal.vue
This commit is contained in:
commit
33507e24ff
8
.github/workflows/api-misskey-js.yml
vendored
8
.github/workflows/api-misskey-js.yml
vendored
|
@ -1,6 +1,12 @@
|
||||||
name: API report (misskey.js)
|
name: API report (misskey.js)
|
||||||
|
|
||||||
on: [push, pull_request]
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- packages/misskey-js/**
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- packages/misskey-js/**
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
report:
|
report:
|
||||||
|
|
12
.github/workflows/lint.yml
vendored
12
.github/workflows/lint.yml
vendored
|
@ -5,7 +5,19 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- develop
|
- develop
|
||||||
|
paths:
|
||||||
|
- packages/backend/**
|
||||||
|
- packages/frontend/**
|
||||||
|
- packages/sw/**
|
||||||
|
- packages/misskey-js/**
|
||||||
|
- packages/shared/.eslintrc.js
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- packages/backend/**
|
||||||
|
- packages/frontend/**
|
||||||
|
- packages/sw/**
|
||||||
|
- packages/misskey-js/**
|
||||||
|
- packages/shared/.eslintrc.js
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pnpm_install:
|
pnpm_install:
|
||||||
|
|
64
.github/workflows/test-backend.yml
vendored
64
.github/workflows/test-backend.yml
vendored
|
@ -5,10 +5,18 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- develop
|
- develop
|
||||||
|
paths:
|
||||||
|
- packages/backend/**
|
||||||
|
# for permissions
|
||||||
|
- packages/misskey-js/**
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- packages/backend/**
|
||||||
|
# for permissions
|
||||||
|
- packages/misskey-js/**
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
jest:
|
unit:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
|
@ -51,8 +59,58 @@ jobs:
|
||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm build
|
run: pnpm build
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm jest-and-coverage
|
run: pnpm --filter backend test-and-coverage
|
||||||
- name: Upload Coverage
|
- name: Upload to Codecov
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
files: ./packages/backend/coverage/coverage-final.json
|
||||||
|
|
||||||
|
e2e:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [20.10.0]
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:15
|
||||||
|
ports:
|
||||||
|
- 54312:5432
|
||||||
|
env:
|
||||||
|
POSTGRES_DB: test-misskey
|
||||||
|
POSTGRES_HOST_AUTH_METHOD: trust
|
||||||
|
redis:
|
||||||
|
image: redis:7
|
||||||
|
ports:
|
||||||
|
- 56312:6379
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4.1.1
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v2
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
run_install: false
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v4.0.1
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
cache: 'pnpm'
|
||||||
|
- run: corepack enable
|
||||||
|
- run: pnpm i --frozen-lockfile
|
||||||
|
- name: Check pnpm-lock.yaml
|
||||||
|
run: git diff --exit-code pnpm-lock.yaml
|
||||||
|
- name: Copy Configure
|
||||||
|
run: cp .github/misskey/test.yml .config
|
||||||
|
- name: Build
|
||||||
|
run: pnpm build
|
||||||
|
- name: Test
|
||||||
|
run: pnpm --filter backend test-and-coverage:e2e
|
||||||
|
- name: Upload to Codecov
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
13
.github/workflows/test-frontend.yml
vendored
13
.github/workflows/test-frontend.yml
vendored
|
@ -5,7 +5,20 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- develop
|
- develop
|
||||||
|
paths:
|
||||||
|
- packages/frontend/**
|
||||||
|
# for permissions
|
||||||
|
- packages/misskey-js/**
|
||||||
|
# for e2e
|
||||||
|
- packages/backend/**
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- packages/frontend/**
|
||||||
|
# for permissions
|
||||||
|
- packages/misskey-js/**
|
||||||
|
# for e2e
|
||||||
|
- packages/backend/**
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
vitest:
|
vitest:
|
||||||
|
|
4
.github/workflows/test-misskey-js.yml
vendored
4
.github/workflows/test-misskey-js.yml
vendored
|
@ -6,8 +6,12 @@ name: Test (misskey.js)
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ develop ]
|
branches: [ develop ]
|
||||||
|
paths:
|
||||||
|
- packages/misskey-js/**
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ develop ]
|
branches: [ develop ]
|
||||||
|
paths:
|
||||||
|
- packages/misskey-js/**
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
|
47
.github/workflows/validate-api-json.yml
vendored
Normal file
47
.github/workflows/validate-api-json.yml
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
name: Test (backend)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
paths:
|
||||||
|
- packages/backend/**
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- packages/backend/**
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate-api-json:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [20.10.0]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4.1.1
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v2
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
run_install: false
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v4.0.1
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
cache: 'pnpm'
|
||||||
|
- name: Install swagger-cli
|
||||||
|
run: npm i -g swagger-cli
|
||||||
|
- run: corepack enable
|
||||||
|
- run: pnpm i --frozen-lockfile
|
||||||
|
- name: Check pnpm-lock.yaml
|
||||||
|
run: git diff --exit-code pnpm-lock.yaml
|
||||||
|
- name: Copy Configure
|
||||||
|
run: cp .config/example.yml .config/default.yml
|
||||||
|
- name: Build and generate
|
||||||
|
run: pnpm build && pnpm --filter backend generate-api-json
|
||||||
|
- name: Validation
|
||||||
|
run: swagger-cli validate ./packages/backend/built/api.json
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -41,6 +41,7 @@ docker-compose.yml
|
||||||
# misskey
|
# misskey
|
||||||
/build
|
/build
|
||||||
built
|
built
|
||||||
|
built-test
|
||||||
/data
|
/data
|
||||||
/.cache-loader
|
/.cache-loader
|
||||||
/db
|
/db
|
||||||
|
|
|
@ -21,14 +21,21 @@
|
||||||
### Client
|
### Client
|
||||||
- Feat: 新しいゲームを追加
|
- Feat: 新しいゲームを追加
|
||||||
- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように
|
- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように
|
||||||
|
- Enhance: チャンネルノートのピン留めをノートのメニューからできるように
|
||||||
|
- Enhance: 管理者の場合はAPI tokenの発行画面で管理機能に関する権限を付与できるように
|
||||||
|
- Enhance: AiScriptを0.17.0に更新 [CHANGELOG](https://github.com/aiscript-dev/aiscript/blob/bb89d132b633a622d3cb0eff0d0cc7e476c0cfdd/CHANGELOG.md)
|
||||||
|
- 配列の範囲外・非整数のインデックスへの代入が完全禁止になるので注意
|
||||||
|
- Enhance: 絵文字ピッカー・オートコンプリートで、完全一致した絵文字を優先的に表示するように
|
||||||
- Fix: ネイティブモードの絵文字がモノクロにならないように
|
- Fix: ネイティブモードの絵文字がモノクロにならないように
|
||||||
- Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正
|
- Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正
|
||||||
- Enhance: チャンネルノートのピン留めをノートのメニューからできるよ
|
- Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正
|
||||||
|
- Fix: v2023.12.1で追加された`$[clickable ...]`および`onClickEv`が正しく機能していないのを修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
|
- Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
|
||||||
- Enhance: ActivityPub Deliver queueでBodyを事前処理するように (#12916)
|
- Enhance: ActivityPub Deliver queueでBodyを事前処理するように (#12916)
|
||||||
- Enhance: クリップをエクスポートできるように
|
- Enhance: クリップをエクスポートできるように
|
||||||
|
- Fix: `drive/files/update`でファイル名のバリデーションが機能していない問題を修正
|
||||||
|
|
||||||
## 2023.12.2
|
## 2023.12.2
|
||||||
|
|
||||||
|
|
24
locales/index.d.ts
vendored
24
locales/index.d.ts
vendored
|
@ -657,6 +657,7 @@ export interface Locale {
|
||||||
"small": string;
|
"small": string;
|
||||||
"generateAccessToken": string;
|
"generateAccessToken": string;
|
||||||
"permission": string;
|
"permission": string;
|
||||||
|
"adminPermission": string;
|
||||||
"enableAll": string;
|
"enableAll": string;
|
||||||
"disableAll": string;
|
"disableAll": string;
|
||||||
"tokenRequested": string;
|
"tokenRequested": string;
|
||||||
|
@ -1236,6 +1237,20 @@ export interface Locale {
|
||||||
"addMfmFunction": string;
|
"addMfmFunction": string;
|
||||||
"enableQuickAddMfmFunction": string;
|
"enableQuickAddMfmFunction": string;
|
||||||
"bubbleGame": string;
|
"bubbleGame": string;
|
||||||
|
"sfx": string;
|
||||||
|
"soundWillBePlayed": string;
|
||||||
|
"showReplay": string;
|
||||||
|
"replay": string;
|
||||||
|
"replaying": string;
|
||||||
|
"ranking": string;
|
||||||
|
"_bubbleGame": {
|
||||||
|
"howToPlay": string;
|
||||||
|
"_howToPlay": {
|
||||||
|
"section1": string;
|
||||||
|
"section2": string;
|
||||||
|
"section3": string;
|
||||||
|
};
|
||||||
|
};
|
||||||
"_announcement": {
|
"_announcement": {
|
||||||
"forExistingUsers": string;
|
"forExistingUsers": string;
|
||||||
"forExistingUsersDescription": string;
|
"forExistingUsersDescription": string;
|
||||||
|
@ -1700,6 +1715,15 @@ export interface Locale {
|
||||||
"title": string;
|
"title": string;
|
||||||
"description": string;
|
"description": string;
|
||||||
};
|
};
|
||||||
|
"_bubbleGameExplodingHead": {
|
||||||
|
"title": string;
|
||||||
|
"description": string;
|
||||||
|
};
|
||||||
|
"_bubbleGameDoubleExplodingHead": {
|
||||||
|
"title": string;
|
||||||
|
"description": string;
|
||||||
|
"flavor": string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
"_role": {
|
"_role": {
|
||||||
|
|
|
@ -654,6 +654,7 @@ medium: "中"
|
||||||
small: "小"
|
small: "小"
|
||||||
generateAccessToken: "アクセストークンの発行"
|
generateAccessToken: "アクセストークンの発行"
|
||||||
permission: "権限"
|
permission: "権限"
|
||||||
|
adminPermission: "管理者権限"
|
||||||
enableAll: "全て有効にする"
|
enableAll: "全て有効にする"
|
||||||
disableAll: "全て無効にする"
|
disableAll: "全て無効にする"
|
||||||
tokenRequested: "アカウントへのアクセス許可"
|
tokenRequested: "アカウントへのアクセス許可"
|
||||||
|
@ -1233,6 +1234,19 @@ decorate: "デコる"
|
||||||
addMfmFunction: "装飾を追加"
|
addMfmFunction: "装飾を追加"
|
||||||
enableQuickAddMfmFunction: "高度なMFMのピッカーを表示する"
|
enableQuickAddMfmFunction: "高度なMFMのピッカーを表示する"
|
||||||
bubbleGame: "バブルゲーム"
|
bubbleGame: "バブルゲーム"
|
||||||
|
sfx: "効果音"
|
||||||
|
soundWillBePlayed: "サウンドが再生されます"
|
||||||
|
showReplay: "リプレイを見る"
|
||||||
|
replay: "リプレイ"
|
||||||
|
replaying: "リプレイ中"
|
||||||
|
ranking: "ランキング"
|
||||||
|
|
||||||
|
_bubbleGame:
|
||||||
|
howToPlay: "遊び方"
|
||||||
|
_howToPlay:
|
||||||
|
section1: "位置を調整してハコにモノを落とします。"
|
||||||
|
section2: "同じ種類のモノがくっつくと別のモノに変化して、スコアが得られます。"
|
||||||
|
section3: "モノがハコからあふれるとゲームオーバーです。ハコからあふれないようにしつつモノを融合させてハイスコアを目指そう!"
|
||||||
|
|
||||||
_announcement:
|
_announcement:
|
||||||
forExistingUsers: "既存ユーザーのみ"
|
forExistingUsers: "既存ユーザーのみ"
|
||||||
|
@ -1611,6 +1625,13 @@ _achievements:
|
||||||
_tutorialCompleted:
|
_tutorialCompleted:
|
||||||
title: "Misskey初心者講座 修了証"
|
title: "Misskey初心者講座 修了証"
|
||||||
description: "チュートリアルを完了した"
|
description: "チュートリアルを完了した"
|
||||||
|
_bubbleGameExplodingHead:
|
||||||
|
title: "🤯"
|
||||||
|
description: "バブルゲームで最も大きいモノを出した"
|
||||||
|
_bubbleGameDoubleExplodingHead:
|
||||||
|
title: "ダブル🤯"
|
||||||
|
description: "バブルゲームで最も大きいモノを2つ同時に出した"
|
||||||
|
flavor: "これくらいの おべんとばこに 🤯 🤯 ちょっとつめて"
|
||||||
|
|
||||||
_role:
|
_role:
|
||||||
new: "ロールの作成"
|
new: "ロールの作成"
|
||||||
|
|
|
@ -160,7 +160,6 @@ module.exports = {
|
||||||
testMatch: [
|
testMatch: [
|
||||||
"<rootDir>/test/unit/**/*.ts",
|
"<rootDir>/test/unit/**/*.ts",
|
||||||
"<rootDir>/src/**/*.test.ts",
|
"<rootDir>/src/**/*.test.ts",
|
||||||
"<rootDir>/test/e2e/**/*.ts",
|
|
||||||
],
|
],
|
||||||
|
|
||||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||||
|
|
15
packages/backend/jest.config.e2e.cjs
Normal file
15
packages/backend/jest.config.e2e.cjs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* For a detailed explanation regarding each configuration property and type check, visit:
|
||||||
|
* https://jestjs.io/docs/en/configuration.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
const base = require('./jest.config.cjs')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...base,
|
||||||
|
globalSetup: "<rootDir>/built-test/entry.js",
|
||||||
|
setupFilesAfterEnv: ["<rootDir>/test/jest.setup.ts"],
|
||||||
|
testMatch: [
|
||||||
|
"<rootDir>/test/e2e/**/*.ts",
|
||||||
|
],
|
||||||
|
};
|
14
packages/backend/jest.config.unit.cjs
Normal file
14
packages/backend/jest.config.unit.cjs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* For a detailed explanation regarding each configuration property and type check, visit:
|
||||||
|
* https://jestjs.io/docs/en/configuration.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
const base = require('./jest.config.cjs')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...base,
|
||||||
|
testMatch: [
|
||||||
|
"<rootDir>/test/unit/**/*.ts",
|
||||||
|
"<rootDir>/src/**/*.test.ts",
|
||||||
|
],
|
||||||
|
};
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class BubbleGameRecord1704959805077 {
|
||||||
|
name = 'BubbleGameRecord1704959805077'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`CREATE TABLE "bubble_game_record" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "seededAt" TIMESTAMP WITH TIME ZONE NOT NULL, "seed" character varying(1024) NOT NULL, "gameVersion" integer NOT NULL, "gameMode" character varying(128) NOT NULL, "score" integer NOT NULL, "logs" jsonb NOT NULL DEFAULT '[]', "isVerified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_a75395fe404b392e2893b50d7ea" PRIMARY KEY ("id"))`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_75276757070d21fdfaf4c05290" ON "bubble_game_record" ("userId") `);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_4ae7053179014915d1432d3f40" ON "bubble_game_record" ("seededAt") `);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_26d4ee490b5a487142d35466ee" ON "bubble_game_record" ("score") `);
|
||||||
|
await queryRunner.query(`ALTER TABLE "bubble_game_record" ADD CONSTRAINT "FK_75276757070d21fdfaf4c052909" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "bubble_game_record" DROP CONSTRAINT "FK_75276757070d21fdfaf4c052909"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_26d4ee490b5a487142d35466ee"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_4ae7053179014915d1432d3f40"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_75276757070d21fdfaf4c05290"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "bubble_game_record"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@
|
||||||
"revert": "pnpm typeorm migration:revert -d ormconfig.js",
|
"revert": "pnpm typeorm migration:revert -d ormconfig.js",
|
||||||
"check:connect": "node ./check_connect.js",
|
"check:connect": "node ./check_connect.js",
|
||||||
"build": "swc src -d built -D",
|
"build": "swc src -d built -D",
|
||||||
|
"build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc",
|
||||||
"watch:swc": "swc src -d built -D -w",
|
"watch:swc": "swc src -d built -D -w",
|
||||||
"build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
|
"build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
|
||||||
"watch": "node watch.mjs",
|
"watch": "node watch.mjs",
|
||||||
|
@ -21,11 +22,15 @@
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"eslint": "eslint --quiet \"src/**/*.ts\"",
|
"eslint": "eslint --quiet \"src/**/*.ts\"",
|
||||||
"lint": "pnpm typecheck && pnpm eslint",
|
"lint": "pnpm typecheck && pnpm eslint",
|
||||||
"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit",
|
"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs",
|
||||||
"jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit",
|
"jest:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.e2e.cjs",
|
||||||
|
"jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.unit.cjs",
|
||||||
|
"jest-and-coverage:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.e2e.cjs",
|
||||||
"jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache",
|
"jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache",
|
||||||
"test": "pnpm jest",
|
"test": "pnpm jest",
|
||||||
|
"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
|
||||||
"test-and-coverage": "pnpm jest-and-coverage",
|
"test-and-coverage": "pnpm jest-and-coverage",
|
||||||
|
"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
|
||||||
"generate-api-json": "node ./generate_api_json.js"
|
"generate-api-json": "node ./generate_api_json.js"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
@ -178,6 +183,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "29.7.0",
|
"@jest/globals": "29.7.0",
|
||||||
"@misskey-dev/eslint-plugin": "^1.0.0",
|
"@misskey-dev/eslint-plugin": "^1.0.0",
|
||||||
|
"@nestjs/platform-express": "^10.3.0",
|
||||||
"@simplewebauthn/typescript-types": "8.3.4",
|
"@simplewebauthn/typescript-types": "8.3.4",
|
||||||
"@swc/jest": "0.2.29",
|
"@swc/jest": "0.2.29",
|
||||||
"@types/accepts": "1.3.7",
|
"@types/accepts": "1.3.7",
|
||||||
|
@ -226,9 +232,11 @@
|
||||||
"eslint": "8.56.0",
|
"eslint": "8.56.0",
|
||||||
"eslint-plugin-import": "2.29.1",
|
"eslint-plugin-import": "2.29.1",
|
||||||
"execa": "8.0.1",
|
"execa": "8.0.1",
|
||||||
|
"fkill": "^9.0.0",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-mock": "29.7.0",
|
"jest-mock": "29.7.0",
|
||||||
"nodemon": "3.0.2",
|
"nodemon": "3.0.2",
|
||||||
|
"pid-port": "^1.0.0",
|
||||||
"simple-oauth2": "5.0.0"
|
"simple-oauth2": "5.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { setTimeout } from 'node:timers/promises';
|
|
||||||
import { Global, Inject, Module } from '@nestjs/common';
|
import { Global, Inject, Module } from '@nestjs/common';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
|
@ -12,6 +11,7 @@ import { DI } from './di-symbols.js';
|
||||||
import { Config, loadConfig } from './config.js';
|
import { Config, loadConfig } from './config.js';
|
||||||
import { createPostgresDataSource } from './postgres.js';
|
import { createPostgresDataSource } from './postgres.js';
|
||||||
import { RepositoryModule } from './models/RepositoryModule.js';
|
import { RepositoryModule } from './models/RepositoryModule.js';
|
||||||
|
import { allSettled } from './misc/promise-tracker.js';
|
||||||
import type { Provider, OnApplicationShutdown } from '@nestjs/common';
|
import type { Provider, OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
|
||||||
const $config: Provider = {
|
const $config: Provider = {
|
||||||
|
@ -33,7 +33,7 @@ const $meilisearch: Provider = {
|
||||||
useFactory: (config: Config) => {
|
useFactory: (config: Config) => {
|
||||||
if (config.meilisearch) {
|
if (config.meilisearch) {
|
||||||
return new MeiliSearch({
|
return new MeiliSearch({
|
||||||
host: `${config.meilisearch.ssl ? 'https' : 'http' }://${config.meilisearch.host}:${config.meilisearch.port}`,
|
host: `${config.meilisearch.ssl ? 'https' : 'http'}://${config.meilisearch.host}:${config.meilisearch.port}`,
|
||||||
apiKey: config.meilisearch.apiKey,
|
apiKey: config.meilisearch.apiKey,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -91,17 +91,12 @@ export class GlobalModule implements OnApplicationShutdown {
|
||||||
@Inject(DI.redisForPub) private redisForPub: Redis.Redis,
|
@Inject(DI.redisForPub) private redisForPub: Redis.Redis,
|
||||||
@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
|
@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
|
||||||
@Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis,
|
@Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
public async dispose(): Promise<void> {
|
public async dispose(): Promise<void> {
|
||||||
if (process.env.NODE_ENV === 'test') {
|
// Wait for all potential DB queries
|
||||||
// XXX:
|
await allSettled();
|
||||||
// Shutting down the existing connections causes errors on Jest as
|
// And then disconnect from DB
|
||||||
// Misskey has asynchronous postgres/redis connections that are not
|
|
||||||
// awaited.
|
|
||||||
// Let's wait for some random time for them to finish.
|
|
||||||
await setTimeout(5000);
|
|
||||||
}
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.db.destroy(),
|
this.db.destroy(),
|
||||||
this.redisClient.disconnect(),
|
this.redisClient.disconnect(),
|
||||||
|
|
|
@ -87,6 +87,8 @@ export const ACHIEVEMENT_TYPES = [
|
||||||
'brainDiver',
|
'brainDiver',
|
||||||
'smashTestNotificationButton',
|
'smashTestNotificationButton',
|
||||||
'tutorialCompleted',
|
'tutorialCompleted',
|
||||||
|
'bubbleGameExplodingHead',
|
||||||
|
'bubbleGameDoubleExplodingHead',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
|
@ -655,7 +655,7 @@ export class DriveService {
|
||||||
public async updateFile(file: MiDriveFile, values: Partial<MiDriveFile>, updater: MiUser) {
|
public async updateFile(file: MiDriveFile, values: Partial<MiDriveFile>, updater: MiUser) {
|
||||||
const alwaysMarkNsfw = (await this.roleService.getUserPolicies(file.userId)).alwaysMarkNsfw;
|
const alwaysMarkNsfw = (await this.roleService.getUserPolicies(file.userId)).alwaysMarkNsfw;
|
||||||
|
|
||||||
if (values.name && !this.driveFileEntityService.validateFileName(file.name)) {
|
if (values.name != null && !this.driveFileEntityService.validateFileName(values.name)) {
|
||||||
throw new DriveService.InvalidFileNameError();
|
throw new DriveService.InvalidFileNameError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,7 @@ import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||||
import { isReply } from '@/misc/is-reply.js';
|
import { isReply } from '@/misc/is-reply.js';
|
||||||
|
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||||
|
|
||||||
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
||||||
|
|
||||||
|
@ -644,7 +645,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
this.relayService.deliverToRelays(user, noteActivity);
|
this.relayService.deliverToRelays(user, noteActivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
dm.execute();
|
trackPromise(dm.execute());
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { IdService } from '@/core/IdService.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import type { NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository } from '@/models/_.js';
|
import type { NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository } from '@/models/_.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NoteReadService implements OnApplicationShutdown {
|
export class NoteReadService implements OnApplicationShutdown {
|
||||||
|
@ -107,7 +108,7 @@ export class NoteReadService implements OnApplicationShutdown {
|
||||||
|
|
||||||
// TODO: ↓まとめてクエリしたい
|
// TODO: ↓まとめてクエリしたい
|
||||||
|
|
||||||
this.noteUnreadsRepository.countBy({
|
trackPromise(this.noteUnreadsRepository.countBy({
|
||||||
userId: userId,
|
userId: userId,
|
||||||
isMentioned: true,
|
isMentioned: true,
|
||||||
}).then(mentionsCount => {
|
}).then(mentionsCount => {
|
||||||
|
@ -115,9 +116,9 @@ export class NoteReadService implements OnApplicationShutdown {
|
||||||
// 全て既読になったイベントを発行
|
// 全て既読になったイベントを発行
|
||||||
this.globalEventService.publishMainStream(userId, 'readAllUnreadMentions');
|
this.globalEventService.publishMainStream(userId, 'readAllUnreadMentions');
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.noteUnreadsRepository.countBy({
|
trackPromise(this.noteUnreadsRepository.countBy({
|
||||||
userId: userId,
|
userId: userId,
|
||||||
isSpecified: true,
|
isSpecified: true,
|
||||||
}).then(specifiedCount => {
|
}).then(specifiedCount => {
|
||||||
|
@ -125,7 +126,7 @@ export class NoteReadService implements OnApplicationShutdown {
|
||||||
// 全て既読になったイベントを発行
|
// 全て既読になったイベントを発行
|
||||||
this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
|
this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { CacheService } from '@/core/CacheService.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { UserListService } from '@/core/UserListService.js';
|
import { UserListService } from '@/core/UserListService.js';
|
||||||
import type { FilterUnionByProperty } from '@/types.js';
|
import type { FilterUnionByProperty } from '@/types.js';
|
||||||
|
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NotificationService implements OnApplicationShutdown {
|
export class NotificationService implements OnApplicationShutdown {
|
||||||
|
@ -74,7 +75,18 @@ export class NotificationService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async createNotification<T extends MiNotification['type']>(
|
public createNotification<T extends MiNotification['type']>(
|
||||||
|
notifieeId: MiUser['id'],
|
||||||
|
type: T,
|
||||||
|
data: Omit<FilterUnionByProperty<MiNotification, 'type', T>, 'type' | 'id' | 'createdAt' | 'notifierId'>,
|
||||||
|
notifierId?: MiUser['id'] | null,
|
||||||
|
) {
|
||||||
|
trackPromise(
|
||||||
|
this.#createNotificationInternal(notifieeId, type, data, notifierId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async #createNotificationInternal<T extends MiNotification['type']>(
|
||||||
notifieeId: MiUser['id'],
|
notifieeId: MiUser['id'],
|
||||||
type: T,
|
type: T,
|
||||||
data: Omit<FilterUnionByProperty<MiNotification, 'type', T>, 'type' | 'id' | 'createdAt' | 'notifierId'>,
|
data: Omit<FilterUnionByProperty<MiNotification, 'type', T>, 'type' | 'id' | 'createdAt' | 'notifierId'>,
|
||||||
|
|
|
@ -3,12 +3,12 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { setTimeout } from 'node:timers/promises';
|
|
||||||
import { Inject, Module, OnApplicationShutdown } from '@nestjs/common';
|
import { Inject, Module, OnApplicationShutdown } from '@nestjs/common';
|
||||||
import * as Bull from 'bullmq';
|
import * as Bull from 'bullmq';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { QUEUE, baseQueueOptions } from '@/queue/const.js';
|
import { QUEUE, baseQueueOptions } from '@/queue/const.js';
|
||||||
|
import { allSettled } from '@/misc/promise-tracker.js';
|
||||||
import type { Provider } from '@nestjs/common';
|
import type { Provider } from '@nestjs/common';
|
||||||
import type { DeliverJobData, InboxJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData, ScheduleNotePostJobData } from '../queue/types.js';
|
import type { DeliverJobData, InboxJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData, ScheduleNotePostJobData } from '../queue/types.js';
|
||||||
|
|
||||||
|
@ -116,14 +116,9 @@ export class QueueModule implements OnApplicationShutdown {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async dispose(): Promise<void> {
|
public async dispose(): Promise<void> {
|
||||||
if (process.env.NODE_ENV === 'test') {
|
// Wait for all potential queue jobs
|
||||||
// XXX:
|
await allSettled();
|
||||||
// Shutting down the existing connections causes errors on Jest as
|
// And then close all queues
|
||||||
// Misskey has asynchronous postgres/redis connections that are not
|
|
||||||
// awaited.
|
|
||||||
// Let's wait for some random time for them to finish.
|
|
||||||
await setTimeout(5000);
|
|
||||||
}
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.systemQueue.close(),
|
this.systemQueue.close(),
|
||||||
this.endedPollNotificationQueue.close(),
|
this.endedPollNotificationQueue.close(),
|
||||||
|
|
|
@ -28,6 +28,7 @@ import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||||
|
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||||
|
|
||||||
const FALLBACK = '❤';
|
const FALLBACK = '❤';
|
||||||
const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
|
const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
|
||||||
|
@ -268,7 +269,7 @@ export class ReactionService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dm.execute();
|
trackPromise(dm.execute());
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
}
|
}
|
||||||
|
@ -316,7 +317,7 @@ export class ReactionService {
|
||||||
dm.addDirectRecipe(reactee as MiRemoteUser);
|
dm.addDirectRecipe(reactee as MiRemoteUser);
|
||||||
}
|
}
|
||||||
dm.addFollowersRecipe();
|
dm.addFollowersRecipe();
|
||||||
dm.execute();
|
trackPromise(dm.execute());
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,7 +144,7 @@ class DeliverManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// deliver
|
// deliver
|
||||||
this.queueService.deliverMany(this.actor, this.activity, inboxes);
|
await this.queueService.deliverMany(this.actor, this.activity, inboxes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,5 +80,6 @@ export const DI = {
|
||||||
flashsRepository: Symbol('flashsRepository'),
|
flashsRepository: Symbol('flashsRepository'),
|
||||||
flashLikesRepository: Symbol('flashLikesRepository'),
|
flashLikesRepository: Symbol('flashLikesRepository'),
|
||||||
userMemosRepository: Symbol('userMemosRepository'),
|
userMemosRepository: Symbol('userMemosRepository'),
|
||||||
|
bubbleGameRecordsRepository: Symbol('bubbleGameRecordsRepository'),
|
||||||
//#endregion
|
//#endregion
|
||||||
};
|
};
|
||||||
|
|
23
packages/backend/src/misc/promise-tracker.ts
Normal file
23
packages/backend/src/misc/promise-tracker.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
const promiseRefs: Set<WeakRef<Promise<unknown>>> = new Set();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tracks promises that other modules decided not to wait for,
|
||||||
|
* and makes sure they are all settled before fully closing down the server.
|
||||||
|
*/
|
||||||
|
export function trackPromise(promise: Promise<unknown>) {
|
||||||
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ref = new WeakRef(promise);
|
||||||
|
promiseRefs.add(ref);
|
||||||
|
promise.finally(() => promiseRefs.delete(ref));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function allSettled(): Promise<void> {
|
||||||
|
await Promise.allSettled([...promiseRefs].map(r => r.deref()));
|
||||||
|
}
|
57
packages/backend/src/models/BubbleGameRecord.ts
Normal file
57
packages/backend/src/models/BubbleGameRecord.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||||
|
import { id } from './util/id.js';
|
||||||
|
import { MiUser } from './User.js';
|
||||||
|
|
||||||
|
@Entity('bubble_game_record')
|
||||||
|
export class MiBubbleGameRecord {
|
||||||
|
@PrimaryColumn(id())
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({
|
||||||
|
...id(),
|
||||||
|
})
|
||||||
|
public userId: MiUser['id'];
|
||||||
|
|
||||||
|
@ManyToOne(type => MiUser, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public user: MiUser | null;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column('timestamp with time zone')
|
||||||
|
public seededAt: Date;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024,
|
||||||
|
})
|
||||||
|
public seed: string;
|
||||||
|
|
||||||
|
@Column('integer')
|
||||||
|
public gameVersion: number;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 128,
|
||||||
|
})
|
||||||
|
public gameMode: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column('integer')
|
||||||
|
public score: number;
|
||||||
|
|
||||||
|
@Column('jsonb', {
|
||||||
|
default: [],
|
||||||
|
})
|
||||||
|
public logs: any[];
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public isVerified: boolean;
|
||||||
|
}
|
|
@ -72,6 +72,7 @@ import {
|
||||||
MiUserSecurityKey,
|
MiUserSecurityKey,
|
||||||
MiWebhook,
|
MiWebhook,
|
||||||
MiScheduledNote,
|
MiScheduledNote,
|
||||||
|
MiBubbleGameRecord
|
||||||
} from './_.js';
|
} from './_.js';
|
||||||
import type { DataSource } from 'typeorm';
|
import type { DataSource } from 'typeorm';
|
||||||
import type { Provider } from '@nestjs/common';
|
import type { Provider } from '@nestjs/common';
|
||||||
|
@ -478,6 +479,12 @@ const $userMemosRepository: Provider = {
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const $bubbleGameRecordsRepository: Provider = {
|
||||||
|
provide: DI.bubbleGameRecordsRepository,
|
||||||
|
useFactory: (db: DataSource) => db.getRepository(MiBubbleGameRecord),
|
||||||
|
inject: [DI.db],
|
||||||
|
};
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
],
|
],
|
||||||
|
@ -549,6 +556,7 @@ const $userMemosRepository: Provider = {
|
||||||
$flashsRepository,
|
$flashsRepository,
|
||||||
$flashLikesRepository,
|
$flashLikesRepository,
|
||||||
$userMemosRepository,
|
$userMemosRepository,
|
||||||
|
$bubbleGameRecordsRepository,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
$usersRepository,
|
$usersRepository,
|
||||||
|
@ -618,6 +626,7 @@ const $userMemosRepository: Provider = {
|
||||||
$flashsRepository,
|
$flashsRepository,
|
||||||
$flashLikesRepository,
|
$flashLikesRepository,
|
||||||
$userMemosRepository,
|
$userMemosRepository,
|
||||||
|
$bubbleGameRecordsRepository,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class RepositoryModule {}
|
export class RepositoryModule {}
|
||||||
|
|
|
@ -69,6 +69,7 @@ import { MiRoleAssignment } from '@/models/RoleAssignment.js';
|
||||||
import { MiFlash } from '@/models/Flash.js';
|
import { MiFlash } from '@/models/Flash.js';
|
||||||
import { MiFlashLike } from '@/models/FlashLike.js';
|
import { MiFlashLike } from '@/models/FlashLike.js';
|
||||||
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
|
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
|
||||||
|
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
|
||||||
import { MiScheduledNote } from './ScheduledNote.js';
|
import { MiScheduledNote } from './ScheduledNote.js';
|
||||||
import type { Repository } from 'typeorm';
|
import type { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
@ -140,6 +141,7 @@ export {
|
||||||
MiFlash,
|
MiFlash,
|
||||||
MiFlashLike,
|
MiFlashLike,
|
||||||
MiUserMemo,
|
MiUserMemo,
|
||||||
|
MiBubbleGameRecord,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AbuseUserReportsRepository = Repository<MiAbuseUserReport>;
|
export type AbuseUserReportsRepository = Repository<MiAbuseUserReport>;
|
||||||
|
@ -209,3 +211,4 @@ export type RoleAssignmentsRepository = Repository<MiRoleAssignment>;
|
||||||
export type FlashsRepository = Repository<MiFlash>;
|
export type FlashsRepository = Repository<MiFlash>;
|
||||||
export type FlashLikesRepository = Repository<MiFlashLike>;
|
export type FlashLikesRepository = Repository<MiFlashLike>;
|
||||||
export type UserMemoRepository = Repository<MiUserMemo>;
|
export type UserMemoRepository = Repository<MiUserMemo>;
|
||||||
|
export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord>;
|
||||||
|
|
|
@ -78,6 +78,7 @@ import { MiFlash } from '@/models/Flash.js';
|
||||||
import { MiFlashLike } from '@/models/FlashLike.js';
|
import { MiFlashLike } from '@/models/FlashLike.js';
|
||||||
import { MiUserMemo } from '@/models/UserMemo.js';
|
import { MiUserMemo } from '@/models/UserMemo.js';
|
||||||
import { MiScheduledNote } from '@/models/ScheduledNote.js';
|
import { MiScheduledNote } from '@/models/ScheduledNote.js';
|
||||||
|
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
|
||||||
|
|
||||||
import { Config } from '@/config.js';
|
import { Config } from '@/config.js';
|
||||||
import MisskeyLogger from '@/logger.js';
|
import MisskeyLogger from '@/logger.js';
|
||||||
|
@ -194,6 +195,7 @@ export const entities = [
|
||||||
MiFlash,
|
MiFlash,
|
||||||
MiFlashLike,
|
MiFlashLike,
|
||||||
MiUserMemo,
|
MiUserMemo,
|
||||||
|
MiBubbleGameRecord,
|
||||||
...charts,
|
...charts,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -374,6 +374,8 @@ import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
|
||||||
import * as ep___fetchRss from './endpoints/fetch-rss.js';
|
import * as ep___fetchRss from './endpoints/fetch-rss.js';
|
||||||
import * as ep___fetchExternalResources from './endpoints/fetch-external-resources.js';
|
import * as ep___fetchExternalResources from './endpoints/fetch-external-resources.js';
|
||||||
import * as ep___retention from './endpoints/retention.js';
|
import * as ep___retention from './endpoints/retention.js';
|
||||||
|
import * as ep___bubbleGame_register from './endpoints/bubble-game/register.js';
|
||||||
|
import * as ep___bubbleGame_ranking from './endpoints/bubble-game/ranking.js';
|
||||||
import { GetterService } from './GetterService.js';
|
import { GetterService } from './GetterService.js';
|
||||||
import { ApiLoggerService } from './ApiLoggerService.js';
|
import { ApiLoggerService } from './ApiLoggerService.js';
|
||||||
import type { Provider } from '@nestjs/common';
|
import type { Provider } from '@nestjs/common';
|
||||||
|
@ -746,6 +748,8 @@ const $users_updateMemo: Provider = { provide: 'ep:users/update-memo', useClass:
|
||||||
const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.default };
|
const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.default };
|
||||||
const $fetchExternalResources: Provider = { provide: 'ep:fetch-external-resources', useClass: ep___fetchExternalResources.default };
|
const $fetchExternalResources: Provider = { provide: 'ep:fetch-external-resources', useClass: ep___fetchExternalResources.default };
|
||||||
const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention.default };
|
const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention.default };
|
||||||
|
const $bubbleGame_register: Provider = { provide: 'ep:bubble-game/register', useClass: ep___bubbleGame_register.default };
|
||||||
|
const $bubbleGame_ranking: Provider = { provide: 'ep:bubble-game/ranking', useClass: ep___bubbleGame_ranking.default };
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -1122,6 +1126,8 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||||
$fetchRss,
|
$fetchRss,
|
||||||
$fetchExternalResources,
|
$fetchExternalResources,
|
||||||
$retention,
|
$retention,
|
||||||
|
$bubbleGame_register,
|
||||||
|
$bubbleGame_ranking,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
$admin_meta,
|
$admin_meta,
|
||||||
|
@ -1489,6 +1495,8 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||||
$fetchRss,
|
$fetchRss,
|
||||||
$fetchExternalResources,
|
$fetchExternalResources,
|
||||||
$retention,
|
$retention,
|
||||||
|
$bubbleGame_register,
|
||||||
|
$bubbleGame_ranking,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class EndpointsModule {}
|
export class EndpointsModule {}
|
||||||
|
|
|
@ -374,6 +374,8 @@ import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
|
||||||
import * as ep___fetchRss from './endpoints/fetch-rss.js';
|
import * as ep___fetchRss from './endpoints/fetch-rss.js';
|
||||||
import * as ep___fetchExternalResources from './endpoints/fetch-external-resources.js';
|
import * as ep___fetchExternalResources from './endpoints/fetch-external-resources.js';
|
||||||
import * as ep___retention from './endpoints/retention.js';
|
import * as ep___retention from './endpoints/retention.js';
|
||||||
|
import * as ep___bubbleGame_register from './endpoints/bubble-game/register.js';
|
||||||
|
import * as ep___bubbleGame_ranking from './endpoints/bubble-game/ranking.js';
|
||||||
|
|
||||||
const eps = [
|
const eps = [
|
||||||
['admin/meta', ep___admin_meta],
|
['admin/meta', ep___admin_meta],
|
||||||
|
@ -744,6 +746,8 @@ const eps = [
|
||||||
['fetch-rss', ep___fetchRss],
|
['fetch-rss', ep___fetchRss],
|
||||||
['fetch-external-resources', ep___fetchExternalResources],
|
['fetch-external-resources', ep___fetchExternalResources],
|
||||||
['retention', ep___retention],
|
['retention', ep___retention],
|
||||||
|
['bubble-game/register', ep___bubbleGame_register],
|
||||||
|
['bubble-game/ranking', ep___bubbleGame_ranking],
|
||||||
];
|
];
|
||||||
|
|
||||||
interface IEndpointMetaBase {
|
interface IEndpointMetaBase {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
|
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -92,7 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
antenna.isActive = true;
|
antenna.isActive = true;
|
||||||
antenna.lastUsedAt = new Date();
|
antenna.lastUsedAt = new Date();
|
||||||
this.antennasRepository.update(antenna.id, antenna);
|
trackPromise(this.antennasRepository.update(antenna.id, antenna));
|
||||||
|
|
||||||
if (needPublishEvent) {
|
if (needPublishEvent) {
|
||||||
this.globalEventService.publishInternalEvent('antennaUpdated', antenna);
|
this.globalEventService.publishInternalEvent('antennaUpdated', antenna);
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { MoreThan } from 'typeorm';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import type { BubbleGameRecordsRepository } from '@/models/_.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: [],
|
||||||
|
|
||||||
|
allowGet: true,
|
||||||
|
cacheSec: 60,
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
},
|
||||||
|
|
||||||
|
res: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string', format: 'misskey:id' },
|
||||||
|
score: { type: 'integer' },
|
||||||
|
user: { ref: 'UserLite' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
gameMode: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['gameMode'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.bubbleGameRecordsRepository)
|
||||||
|
private bubbleGameRecordsRepository: BubbleGameRecordsRepository,
|
||||||
|
|
||||||
|
private userEntityService: UserEntityService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps) => {
|
||||||
|
const records = await this.bubbleGameRecordsRepository.find({
|
||||||
|
where: {
|
||||||
|
gameMode: ps.gameMode,
|
||||||
|
seededAt: MoreThan(new Date(Date.now() - 1000 * 60 * 60 * 24 * 7)),
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
score: 'DESC',
|
||||||
|
},
|
||||||
|
take: 10,
|
||||||
|
relations: ['user'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const users = await this.userEntityService.packMany(records.map(r => r.user!), null, { detail: false });
|
||||||
|
|
||||||
|
return records.map(r => ({
|
||||||
|
id: r.id,
|
||||||
|
score: r.score,
|
||||||
|
user: users.find(u => u.id === r.user!.id),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import ms from 'ms';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import type { BubbleGameRecordsRepository } from '@/models/_.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: [],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
|
||||||
|
kind: 'write:account',
|
||||||
|
|
||||||
|
limit: {
|
||||||
|
duration: ms('1hour'),
|
||||||
|
max: 120,
|
||||||
|
minInterval: ms('30sec'),
|
||||||
|
},
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
invalidSeed: {
|
||||||
|
message: 'Provided seed is invalid.',
|
||||||
|
code: 'INVALID_SEED',
|
||||||
|
id: 'eb627bc7-574b-4a52-a860-3c3eae772b88',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
res: {
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
score: { type: 'integer', minimum: 0 },
|
||||||
|
seed: { type: 'string', minLength: 1, maxLength: 1024 },
|
||||||
|
logs: { type: 'array' },
|
||||||
|
gameMode: { type: 'string' },
|
||||||
|
gameVersion: { type: 'integer' },
|
||||||
|
},
|
||||||
|
required: ['score', 'seed', 'logs', 'gameMode', 'gameVersion'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.bubbleGameRecordsRepository)
|
||||||
|
private bubbleGameRecordsRepository: BubbleGameRecordsRepository,
|
||||||
|
|
||||||
|
private idService: IdService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const seedDate = new Date(parseInt(ps.seed, 10));
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
// シードが未来なのは通常のプレイではありえないので弾く
|
||||||
|
if (seedDate.getTime() > now.getTime()) {
|
||||||
|
throw new ApiError(meta.errors.invalidSeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// シードが古すぎる(1時間以上前)のも弾く
|
||||||
|
if (seedDate.getTime() < now.getTime() - 1000 * 60 * 60) {
|
||||||
|
throw new ApiError(meta.errors.invalidSeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.bubbleGameRecordsRepository.insert({
|
||||||
|
id: this.idService.gen(now.getTime()),
|
||||||
|
seed: ps.seed,
|
||||||
|
seededAt: seedDate,
|
||||||
|
userId: me.id,
|
||||||
|
score: ps.score,
|
||||||
|
logs: ps.logs,
|
||||||
|
gameMode: ps.gameMode,
|
||||||
|
gameVersion: ps.gameVersion,
|
||||||
|
isVerified: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
32
packages/backend/test-server/.eslintrc.cjs
Normal file
32
packages/backend/test-server/.eslintrc.cjs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
module.exports = {
|
||||||
|
parserOptions: {
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
project: ['./tsconfig.json'],
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'../../shared/.eslintrc.js',
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
'import/order': ['warn', {
|
||||||
|
'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
|
||||||
|
'pathGroups': [
|
||||||
|
{
|
||||||
|
'pattern': '@/**',
|
||||||
|
'group': 'external',
|
||||||
|
'position': 'after'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
'no-restricted-globals': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'name': '__dirname',
|
||||||
|
'message': 'Not in ESModule. Use `import.meta.url` instead.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': '__filename',
|
||||||
|
'message': 'Not in ESModule. Use `import.meta.url` instead.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
};
|
23
packages/backend/test-server/.swcrc
Normal file
23
packages/backend/test-server/.swcrc
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/swcrc",
|
||||||
|
"jsc": {
|
||||||
|
"parser": {
|
||||||
|
"syntax": "typescript",
|
||||||
|
"dynamicImport": true,
|
||||||
|
"decorators": true
|
||||||
|
},
|
||||||
|
"transform": {
|
||||||
|
"legacyDecorator": true,
|
||||||
|
"decoratorMetadata": true
|
||||||
|
},
|
||||||
|
"experimental": {
|
||||||
|
"keepImportAssertions": true
|
||||||
|
},
|
||||||
|
"baseUrl": "../built",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["*"]
|
||||||
|
},
|
||||||
|
"target": "es2022"
|
||||||
|
},
|
||||||
|
"minify": false
|
||||||
|
}
|
80
packages/backend/test-server/entry.ts
Normal file
80
packages/backend/test-server/entry.ts
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import { portToPid } from 'pid-port';
|
||||||
|
import fkill from 'fkill';
|
||||||
|
import Fastify from 'fastify';
|
||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { MainModule } from '@/MainModule.js';
|
||||||
|
import { ServerService } from '@/server/ServerService.js';
|
||||||
|
import { loadConfig } from '@/config.js';
|
||||||
|
import { NestLogger } from '@/NestLogger.js';
|
||||||
|
|
||||||
|
const config = loadConfig();
|
||||||
|
const originEnv = JSON.stringify(process.env);
|
||||||
|
|
||||||
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* テスト用のサーバインスタンスを起動する
|
||||||
|
*/
|
||||||
|
async function launch() {
|
||||||
|
await killTestServer();
|
||||||
|
|
||||||
|
console.log('starting application...');
|
||||||
|
|
||||||
|
const app = await NestFactory.createApplicationContext(MainModule, {
|
||||||
|
logger: new NestLogger(),
|
||||||
|
});
|
||||||
|
const serverService = app.get(ServerService);
|
||||||
|
await serverService.launch();
|
||||||
|
|
||||||
|
await startControllerEndpoints();
|
||||||
|
|
||||||
|
// ジョブキューは必要な時にテストコード側で起動する
|
||||||
|
// ジョブキューが動くとテスト結果の確認に支障が出ることがあるので意図的に動かさないでいる
|
||||||
|
|
||||||
|
console.log('application initialized.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 既に重複したポートで待ち受けしているサーバがある場合はkillする
|
||||||
|
*/
|
||||||
|
async function killTestServer() {
|
||||||
|
//
|
||||||
|
try {
|
||||||
|
const pid = await portToPid(config.port);
|
||||||
|
if (pid) {
|
||||||
|
await fkill(pid, { force: true });
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// NOP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 別プロセスに切り離してしまったが故に出来なくなった環境変数の書き換え等を実現するためのエンドポイントを作る
|
||||||
|
* @param port
|
||||||
|
*/
|
||||||
|
async function startControllerEndpoints(port = config.port + 1000) {
|
||||||
|
const fastify = Fastify();
|
||||||
|
|
||||||
|
fastify.post<{ Body: { key?: string, value?: string } }>('/env', async (req, res) => {
|
||||||
|
console.log(req.body);
|
||||||
|
const key = req.body['key'];
|
||||||
|
if (!key) {
|
||||||
|
res.code(400).send({ success: false });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.env[key] = req.body['value'];
|
||||||
|
|
||||||
|
res.code(200).send({ success: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
fastify.post<{ Body: { key?: string, value?: string } }>('/env-reset', async (req, res) => {
|
||||||
|
process.env = JSON.parse(originEnv);
|
||||||
|
res.code(200).send({ success: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
await fastify.listen({ port: port, host: 'localhost' });
|
||||||
|
}
|
||||||
|
|
||||||
|
export default launch;
|
52
packages/backend/test-server/tsconfig.json
Normal file
52
packages/backend/test-server/tsconfig.json
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"noEmitOnError": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"declaration": false,
|
||||||
|
"sourceMap": true,
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "nodenext",
|
||||||
|
"moduleResolution": "nodenext",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"removeComments": false,
|
||||||
|
"noLib": false,
|
||||||
|
"strict": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"strictPropertyInitialization": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"rootDir": "../src",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["../src/*"]
|
||||||
|
},
|
||||||
|
"outDir": "../built-test",
|
||||||
|
"types": [
|
||||||
|
"node"
|
||||||
|
],
|
||||||
|
"typeRoots": [
|
||||||
|
"../src/@types",
|
||||||
|
"../node_modules/@types",
|
||||||
|
"../node_modules"
|
||||||
|
],
|
||||||
|
"lib": [
|
||||||
|
"esnext"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"compileOnSave": false,
|
||||||
|
"include": [
|
||||||
|
"./**/*.ts",
|
||||||
|
"../src/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"../src/**/*.test.ts"
|
||||||
|
]
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ import * as crypto from 'node:crypto';
|
||||||
import cbor from 'cbor';
|
import cbor from 'cbor';
|
||||||
import * as OTPAuth from 'otpauth';
|
import * as OTPAuth from 'otpauth';
|
||||||
import { loadConfig } from '@/config.js';
|
import { loadConfig } from '@/config.js';
|
||||||
import { api, signup, startServer } from '../utils.js';
|
import { api, signup } from '../utils.js';
|
||||||
import type {
|
import type {
|
||||||
AuthenticationResponseJSON,
|
AuthenticationResponseJSON,
|
||||||
AuthenticatorAssertionResponseJSON,
|
AuthenticatorAssertionResponseJSON,
|
||||||
|
@ -19,11 +19,9 @@ import type {
|
||||||
PublicKeyCredentialRequestOptionsJSON,
|
PublicKeyCredentialRequestOptionsJSON,
|
||||||
RegistrationResponseJSON,
|
RegistrationResponseJSON,
|
||||||
} from '@simplewebauthn/typescript-types';
|
} from '@simplewebauthn/typescript-types';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('2要素認証', () => {
|
describe('2要素認証', () => {
|
||||||
let app: INestApplicationContext;
|
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
|
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
|
@ -185,14 +183,9 @@ describe('2要素認証', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
|
||||||
alice = await signup({ username, password });
|
alice = await signup({ username, password });
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('が設定でき、OTPでログインできる。', async () => {
|
test('が設定でき、OTPでログインできる。', async () => {
|
||||||
const registerResponse = await api('/i/2fa/register', {
|
const registerResponse = await api('/i/2fa/register', {
|
||||||
password,
|
password,
|
||||||
|
|
|
@ -6,24 +6,20 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { inspect } from 'node:util';
|
|
||||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
import {
|
import {
|
||||||
signup,
|
|
||||||
post,
|
|
||||||
userList,
|
|
||||||
page,
|
|
||||||
role,
|
|
||||||
startServer,
|
|
||||||
api,
|
api,
|
||||||
successfulApiCall,
|
|
||||||
failedApiCall,
|
failedApiCall,
|
||||||
uploadFile,
|
post,
|
||||||
|
role,
|
||||||
|
signup,
|
||||||
|
successfulApiCall,
|
||||||
testPaginationConsistency,
|
testPaginationConsistency,
|
||||||
|
uploadFile,
|
||||||
|
userList,
|
||||||
} from '../utils.js';
|
} from '../utils.js';
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
|
|
||||||
const compareBy = <T extends { id: string }>(selector: (s: T) => string = (s: T): string => s.id) => (a: T, b: T): number => {
|
const compareBy = <T extends { id: string }>(selector: (s: T) => string = (s: T): string => s.id) => (a: T, b: T): number => {
|
||||||
return selector(a).localeCompare(selector(b));
|
return selector(a).localeCompare(selector(b));
|
||||||
|
@ -54,8 +50,6 @@ describe('アンテナ', () => {
|
||||||
withReplies: false,
|
withReplies: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let app: INestApplicationContext;
|
|
||||||
|
|
||||||
let root: User;
|
let root: User;
|
||||||
let alice: User;
|
let alice: User;
|
||||||
let bob: User;
|
let bob: User;
|
||||||
|
@ -79,10 +73,6 @@ describe('アンテナ', () => {
|
||||||
let userMutingAlice: User;
|
let userMutingAlice: User;
|
||||||
let userMutedByAlice: User;
|
let userMutedByAlice: User;
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
app = await startServer();
|
|
||||||
}, 1000 * 60 * 2);
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
root = await signup({ username: 'root' });
|
root = await signup({ username: 'root' });
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
|
@ -136,10 +126,6 @@ describe('アンテナ', () => {
|
||||||
await api('mute/create', { userId: userMutedByAlice.id }, alice);
|
await api('mute/create', { userId: userMutedByAlice.id }, alice);
|
||||||
}, 1000 * 60 * 10);
|
}, 1000 * 60 * 10);
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// テスト間で影響し合わないように毎回全部消す。
|
// テスト間で影響し合わないように毎回全部消す。
|
||||||
for (const user of [alice, bob]) {
|
for (const user of [alice, bob]) {
|
||||||
|
|
|
@ -6,21 +6,10 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { signup, api, post, startServer } from '../utils.js';
|
import { api, post, signup } from '../utils.js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('API visibility', () => {
|
describe('API visibility', () => {
|
||||||
let app: INestApplicationContext;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
app = await startServer();
|
|
||||||
}, 1000 * 60 * 2);
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Note visibility', () => {
|
describe('Note visibility', () => {
|
||||||
//#region vars
|
//#region vars
|
||||||
/** ヒロイン */
|
/** ヒロイン */
|
||||||
|
|
|
@ -7,27 +7,30 @@ process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { IncomingMessage } from 'http';
|
import { IncomingMessage } from 'http';
|
||||||
import { signup, api, startServer, successfulApiCall, failedApiCall, uploadFile, waitFire, connectStream, relativeFetch, createAppToken } from '../utils.js';
|
import {
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
api,
|
||||||
|
connectStream,
|
||||||
|
createAppToken,
|
||||||
|
failedApiCall,
|
||||||
|
relativeFetch,
|
||||||
|
signup,
|
||||||
|
successfulApiCall,
|
||||||
|
uploadFile,
|
||||||
|
waitFire,
|
||||||
|
} from '../utils.js';
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('API', () => {
|
describe('API', () => {
|
||||||
let app: INestApplicationContext;
|
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
let bob: misskey.entities.SignupResponse;
|
let bob: misskey.entities.SignupResponse;
|
||||||
let carol: misskey.entities.SignupResponse;
|
let carol: misskey.entities.SignupResponse;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
||||||
carol = await signup({ username: 'carol' });
|
carol = await signup({ username: 'carol' });
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('General validation', () => {
|
describe('General validation', () => {
|
||||||
test('wrong type', async () => {
|
test('wrong type', async () => {
|
||||||
const res = await api('/test', {
|
const res = await api('/test', {
|
||||||
|
|
|
@ -6,29 +6,21 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { signup, api, post, startServer } from '../utils.js';
|
import { api, post, signup } from '../utils.js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('Block', () => {
|
describe('Block', () => {
|
||||||
let app: INestApplicationContext;
|
|
||||||
|
|
||||||
// alice blocks bob
|
// alice blocks bob
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
let bob: misskey.entities.SignupResponse;
|
let bob: misskey.entities.SignupResponse;
|
||||||
let carol: misskey.entities.SignupResponse;
|
let carol: misskey.entities.SignupResponse;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
||||||
carol = await signup({ username: 'carol' });
|
carol = await signup({ username: 'carol' });
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Block作成', async () => {
|
test('Block作成', async () => {
|
||||||
const res = await api('/blocking/create', {
|
const res = await api('/blocking/create', {
|
||||||
userId: bob.id,
|
userId: bob.id,
|
||||||
|
|
|
@ -18,25 +18,13 @@ import { paramDef as UnfavoriteParamDef } from '@/server/api/endpoints/clips/unf
|
||||||
import { paramDef as AddNoteParamDef } from '@/server/api/endpoints/clips/add-note.js';
|
import { paramDef as AddNoteParamDef } from '@/server/api/endpoints/clips/add-note.js';
|
||||||
import { paramDef as RemoveNoteParamDef } from '@/server/api/endpoints/clips/remove-note.js';
|
import { paramDef as RemoveNoteParamDef } from '@/server/api/endpoints/clips/remove-note.js';
|
||||||
import { paramDef as NotesParamDef } from '@/server/api/endpoints/clips/notes.js';
|
import { paramDef as NotesParamDef } from '@/server/api/endpoints/clips/notes.js';
|
||||||
import {
|
import { api, ApiRequest, failedApiCall, hiddenNote, post, signup, successfulApiCall } from '../utils.js';
|
||||||
signup,
|
|
||||||
post,
|
|
||||||
startServer,
|
|
||||||
api,
|
|
||||||
successfulApiCall,
|
|
||||||
failedApiCall,
|
|
||||||
ApiRequest,
|
|
||||||
hiddenNote,
|
|
||||||
} from '../utils.js';
|
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
|
|
||||||
describe('クリップ', () => {
|
describe('クリップ', () => {
|
||||||
type User = Packed<'User'>;
|
type User = Packed<'User'>;
|
||||||
type Note = Packed<'Note'>;
|
type Note = Packed<'Note'>;
|
||||||
type Clip = Packed<'Clip'>;
|
type Clip = Packed<'Clip'>;
|
||||||
|
|
||||||
let app: INestApplicationContext;
|
|
||||||
|
|
||||||
let alice: User;
|
let alice: User;
|
||||||
let bob: User;
|
let bob: User;
|
||||||
let aliceNote: Note;
|
let aliceNote: Note;
|
||||||
|
@ -145,7 +133,6 @@ describe('クリップ', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
||||||
|
|
||||||
|
@ -160,10 +147,6 @@ describe('クリップ', () => {
|
||||||
bobSpecifiedNote = await post(bob, { text: 'specified only', visibility: 'specified' }) as any;
|
bobSpecifiedNote = await post(bob, { text: 'specified only', visibility: 'specified' }) as any;
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
// テスト間で影響し合わないように毎回全部消す。
|
// テスト間で影響し合わないように毎回全部消す。
|
||||||
for (const user of [alice, bob]) {
|
for (const user of [alice, bob]) {
|
||||||
|
|
|
@ -10,30 +10,22 @@ import * as assert from 'assert';
|
||||||
// https://github.com/node-fetch/node-fetch/pull/1664
|
// https://github.com/node-fetch/node-fetch/pull/1664
|
||||||
import { Blob } from 'node-fetch';
|
import { Blob } from 'node-fetch';
|
||||||
import { MiUser } from '@/models/_.js';
|
import { MiUser } from '@/models/_.js';
|
||||||
import { startServer, signup, post, api, uploadFile, simpleGet, initTestDb } from '../utils.js';
|
import { api, initTestDb, post, signup, simpleGet, uploadFile } from '../utils.js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('Endpoints', () => {
|
describe('Endpoints', () => {
|
||||||
let app: INestApplicationContext;
|
|
||||||
|
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
let bob: misskey.entities.SignupResponse;
|
let bob: misskey.entities.SignupResponse;
|
||||||
let carol: misskey.entities.SignupResponse;
|
let carol: misskey.entities.SignupResponse;
|
||||||
let dave: misskey.entities.SignupResponse;
|
let dave: misskey.entities.SignupResponse;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
||||||
carol = await signup({ username: 'carol' });
|
carol = await signup({ username: 'carol' });
|
||||||
dave = await signup({ username: 'dave' });
|
dave = await signup({ username: 'dave' });
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('signup', () => {
|
describe('signup', () => {
|
||||||
test('不正なユーザー名でアカウントが作成できない', async () => {
|
test('不正なユーザー名でアカウントが作成できない', async () => {
|
||||||
const res = await api('signup', {
|
const res = await api('signup', {
|
||||||
|
@ -710,6 +702,18 @@ describe('Endpoints', () => {
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('不正なファイル名で怒られる', async () => {
|
||||||
|
const file = (await uploadFile(alice)).body;
|
||||||
|
const newName = '';
|
||||||
|
|
||||||
|
const res = await api('/drive/files/update', {
|
||||||
|
fileId: file.id,
|
||||||
|
name: newName,
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(res.status, 400);
|
||||||
|
});
|
||||||
|
|
||||||
test('間違ったIDで怒られる', async () => {
|
test('間違ったIDで怒られる', async () => {
|
||||||
const res = await api('/drive/files/update', {
|
const res = await api('/drive/files/update', {
|
||||||
fileId: 'kyoppie',
|
fileId: 'kyoppie',
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { signup, api, startServer, startJobQueue, port, post } from '../utils.js';
|
import { api, port, post, signup, startJobQueue } from '../utils.js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
import type { INestApplicationContext } from '@nestjs/common';
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('export-clips', () => {
|
describe('export-clips', () => {
|
||||||
let app: INestApplicationContext;
|
let queue: INestApplicationContext;
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
let bob: misskey.entities.SignupResponse;
|
let bob: misskey.entities.SignupResponse;
|
||||||
|
|
||||||
|
@ -33,14 +33,13 @@ describe('export-clips', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
queue = await startJobQueue();
|
||||||
await startJobQueue();
|
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await app.close();
|
await queue.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
|
|
@ -6,9 +6,8 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { startServer, channel, clip, cookie, galleryPost, signup, page, play, post, simpleGet, uploadFile } from '../utils.js';
|
import { channel, clip, cookie, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js';
|
||||||
import type { SimpleGetResponse } from '../utils.js';
|
import type { SimpleGetResponse } from '../utils.js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
// Request Accept
|
// Request Accept
|
||||||
|
@ -23,8 +22,6 @@ const HTML = 'text/html; charset=utf-8';
|
||||||
const JSON_UTF8 = 'application/json; charset=utf-8';
|
const JSON_UTF8 = 'application/json; charset=utf-8';
|
||||||
|
|
||||||
describe('Webリソース', () => {
|
describe('Webリソース', () => {
|
||||||
let app: INestApplicationContext;
|
|
||||||
|
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
let aliceUploadedFile: any;
|
let aliceUploadedFile: any;
|
||||||
let alicesPost: any;
|
let alicesPost: any;
|
||||||
|
@ -79,7 +76,6 @@ describe('Webリソース', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
aliceUploadedFile = await uploadFile(alice);
|
aliceUploadedFile = await uploadFile(alice);
|
||||||
alicesPost = await post(alice, {
|
alicesPost = await post(alice, {
|
||||||
|
@ -96,10 +92,6 @@ describe('Webリソース', () => {
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe.each([
|
describe.each([
|
||||||
{ path: '/', type: HTML },
|
{ path: '/', type: HTML },
|
||||||
{ path: '/docs/ja-JP/about', type: HTML }, // "指定されたURLに該当するページはありませんでした。"
|
{ path: '/docs/ja-JP/about', type: HTML }, // "指定されたURLに該当するページはありませんでした。"
|
||||||
|
|
|
@ -6,26 +6,18 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { signup, api, startServer, simpleGet } from '../utils.js';
|
import { api, signup, simpleGet } from '../utils.js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('FF visibility', () => {
|
describe('FF visibility', () => {
|
||||||
let app: INestApplicationContext;
|
|
||||||
|
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
let bob: misskey.entities.SignupResponse;
|
let bob: misskey.entities.SignupResponse;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('followingVisibility, followersVisibility がともに public なユーザーのフォロー/フォロワーを誰でも見れる', async () => {
|
test('followingVisibility, followersVisibility がともに public なユーザーのフォロー/フォロワーを誰でも見れる', async () => {
|
||||||
await api('/i/update', {
|
await api('/i/update', {
|
||||||
followingVisibility: 'public',
|
followingVisibility: 'public',
|
||||||
|
|
|
@ -3,19 +3,19 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { INestApplicationContext } from '@nestjs/common';
|
||||||
|
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { loadConfig } from '@/config.js';
|
import { loadConfig } from '@/config.js';
|
||||||
import { MiUser, UsersRepository } from '@/models/_.js';
|
import { MiUser, UsersRepository } from '@/models/_.js';
|
||||||
import { jobQueue } from '@/boot/common.js';
|
|
||||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||||
import { uploadFile, signup, startServer, initTestDb, api, sleep, successfulApiCall } from '../utils.js';
|
import { jobQueue } from '@/boot/common.js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
import { api, initTestDb, signup, sleep, successfulApiCall, uploadFile } from '../utils.js';
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('Account Move', () => {
|
describe('Account Move', () => {
|
||||||
let app: INestApplicationContext;
|
|
||||||
let jq: INestApplicationContext;
|
let jq: INestApplicationContext;
|
||||||
let url: URL;
|
let url: URL;
|
||||||
|
|
||||||
|
@ -30,8 +30,8 @@ describe('Account Move', () => {
|
||||||
let Users: UsersRepository;
|
let Users: UsersRepository;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
|
||||||
jq = await jobQueue();
|
jq = await jobQueue();
|
||||||
|
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
url = new URL(config.url);
|
url = new URL(config.url);
|
||||||
const connection = await initTestDb(false);
|
const connection = await initTestDb(false);
|
||||||
|
@ -46,7 +46,7 @@ describe('Account Move', () => {
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await Promise.all([app.close(), jq.close()]);
|
await jq.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Create Alias', () => {
|
describe('Create Alias', () => {
|
||||||
|
|
|
@ -6,29 +6,21 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { signup, api, post, react, startServer, waitFire } from '../utils.js';
|
import { api, post, react, signup, waitFire } from '../utils.js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('Mute', () => {
|
describe('Mute', () => {
|
||||||
let app: INestApplicationContext;
|
|
||||||
|
|
||||||
// alice mutes carol
|
// alice mutes carol
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
let bob: misskey.entities.SignupResponse;
|
let bob: misskey.entities.SignupResponse;
|
||||||
let carol: misskey.entities.SignupResponse;
|
let carol: misskey.entities.SignupResponse;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
||||||
carol = await signup({ username: 'carol' });
|
carol = await signup({ username: 'carol' });
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('ミュート作成', async () => {
|
test('ミュート作成', async () => {
|
||||||
const res = await api('/mute/create', {
|
const res = await api('/mute/create', {
|
||||||
userId: carol.id,
|
userId: carol.id,
|
||||||
|
|
|
@ -6,20 +6,9 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { relativeFetch, startServer } from '../utils.js';
|
import { relativeFetch } from '../utils.js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
|
|
||||||
describe('nodeinfo', () => {
|
describe('nodeinfo', () => {
|
||||||
let app: INestApplicationContext;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
app = await startServer();
|
|
||||||
}, 1000 * 60 * 2);
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('nodeinfo 2.1', async () => {
|
test('nodeinfo 2.1', async () => {
|
||||||
const res = await relativeFetch('nodeinfo/2.1');
|
const res = await relativeFetch('nodeinfo/2.1');
|
||||||
assert.ok(res.ok);
|
assert.ok(res.ok);
|
||||||
|
|
|
@ -8,29 +8,22 @@ process.env.NODE_ENV = 'test';
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { MiNote } from '@/models/Note.js';
|
import { MiNote } from '@/models/Note.js';
|
||||||
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||||
import { signup, post, uploadUrl, startServer, initTestDb, api, uploadFile } from '../utils.js';
|
import { api, initTestDb, post, signup, uploadFile, uploadUrl } from '../utils.js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('Note', () => {
|
describe('Note', () => {
|
||||||
let app: INestApplicationContext;
|
|
||||||
let Notes: any;
|
let Notes: any;
|
||||||
|
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
let bob: misskey.entities.SignupResponse;
|
let bob: misskey.entities.SignupResponse;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
|
||||||
const connection = await initTestDb(true);
|
const connection = await initTestDb(true);
|
||||||
Notes = connection.getRepository(MiNote);
|
Notes = connection.getRepository(MiNote);
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('投稿できる', async () => {
|
test('投稿できる', async () => {
|
||||||
const post = {
|
const post = {
|
||||||
text: 'test',
|
text: 'test',
|
||||||
|
|
|
@ -11,13 +11,18 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { AuthorizationCode, ResourceOwnerPassword, type AuthorizationTokenConfig, ClientCredentials, ModuleOptions } from 'simple-oauth2';
|
import {
|
||||||
|
AuthorizationCode,
|
||||||
|
type AuthorizationTokenConfig,
|
||||||
|
ClientCredentials,
|
||||||
|
ModuleOptions,
|
||||||
|
ResourceOwnerPassword,
|
||||||
|
} from 'simple-oauth2';
|
||||||
import pkceChallenge from 'pkce-challenge';
|
import pkceChallenge from 'pkce-challenge';
|
||||||
import { JSDOM } from 'jsdom';
|
import { JSDOM } from 'jsdom';
|
||||||
import Fastify, { type FastifyReply, type FastifyInstance } from 'fastify';
|
import Fastify, { type FastifyInstance, type FastifyReply } from 'fastify';
|
||||||
import { api, port, signup, startServer } from '../utils.js';
|
import { api, port, sendEnvUpdateRequest, signup } from '../utils.js';
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
|
|
||||||
const host = `http://127.0.0.1:${port}`;
|
const host = `http://127.0.0.1:${port}`;
|
||||||
|
|
||||||
|
@ -147,7 +152,6 @@ async function assertDirectError(response: Response, status: number, error: stri
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('OAuth', () => {
|
describe('OAuth', () => {
|
||||||
let app: INestApplicationContext;
|
|
||||||
let fastify: FastifyInstance;
|
let fastify: FastifyInstance;
|
||||||
|
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
|
@ -156,7 +160,6 @@ describe('OAuth', () => {
|
||||||
let sender: (reply: FastifyReply) => void;
|
let sender: (reply: FastifyReply) => void;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
||||||
|
|
||||||
|
@ -168,7 +171,7 @@ describe('OAuth', () => {
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
process.env.MISSKEY_TEST_CHECK_IP_RANGE = '';
|
await sendEnvUpdateRequest({ key: 'MISSKEY_TEST_CHECK_IP_RANGE', value: '' });
|
||||||
sender = (reply): void => {
|
sender = (reply): void => {
|
||||||
reply.send(`
|
reply.send(`
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
@ -180,7 +183,6 @@ describe('OAuth', () => {
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await fastify.close();
|
await fastify.close();
|
||||||
await app.close();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Full flow', async () => {
|
test('Full flow', async () => {
|
||||||
|
@ -881,7 +883,7 @@ describe('OAuth', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Disallow loopback', async () => {
|
test('Disallow loopback', async () => {
|
||||||
process.env.MISSKEY_TEST_CHECK_IP_RANGE = '1';
|
await sendEnvUpdateRequest({ key: 'MISSKEY_TEST_CHECK_IP_RANGE', value: '1' });
|
||||||
|
|
||||||
const client = new AuthorizationCode(clientConfig);
|
const client = new AuthorizationCode(clientConfig);
|
||||||
const response = await fetch(client.authorizeURL({
|
const response = await fetch(client.authorizeURL({
|
||||||
|
|
|
@ -6,29 +6,21 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { signup, api, post, react, startServer, waitFire, sleep } from '../utils.js';
|
import { api, post, signup, sleep, waitFire } from '../utils.js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('Renote Mute', () => {
|
describe('Renote Mute', () => {
|
||||||
let app: INestApplicationContext;
|
|
||||||
|
|
||||||
// alice mutes carol
|
// alice mutes carol
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
let bob: misskey.entities.SignupResponse;
|
let bob: misskey.entities.SignupResponse;
|
||||||
let carol: misskey.entities.SignupResponse;
|
let carol: misskey.entities.SignupResponse;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
||||||
carol = await signup({ username: 'carol' });
|
carol = await signup({ username: 'carol' });
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('ミュート作成', async () => {
|
test('ミュート作成', async () => {
|
||||||
const res = await api('/renote-mute/create', {
|
const res = await api('/renote-mute/create', {
|
||||||
userId: carol.id,
|
userId: carol.id,
|
||||||
|
|
|
@ -8,12 +8,10 @@ process.env.NODE_ENV = 'test';
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { WebSocket } from 'ws';
|
import { WebSocket } from 'ws';
|
||||||
import { MiFollowing } from '@/models/Following.js';
|
import { MiFollowing } from '@/models/Following.js';
|
||||||
import { signup, api, post, startServer, initTestDb, waitFire, createAppToken, port } from '../utils.js';
|
import { api, createAppToken, initTestDb, port, post, signup, waitFire } from '../utils.js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('Streaming', () => {
|
describe('Streaming', () => {
|
||||||
let app: INestApplicationContext;
|
|
||||||
let Followings: any;
|
let Followings: any;
|
||||||
|
|
||||||
const follow = async (follower: any, followee: any) => {
|
const follow = async (follower: any, followee: any) => {
|
||||||
|
@ -48,7 +46,6 @@ describe('Streaming', () => {
|
||||||
let list: any;
|
let list: any;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
|
||||||
const connection = await initTestDb(true);
|
const connection = await initTestDb(true);
|
||||||
Followings = connection.getRepository(MiFollowing);
|
Followings = connection.getRepository(MiFollowing);
|
||||||
|
|
||||||
|
@ -95,10 +92,6 @@ describe('Streaming', () => {
|
||||||
}, chitose);
|
}, chitose);
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Events', () => {
|
describe('Events', () => {
|
||||||
test('mention event', async () => {
|
test('mention event', async () => {
|
||||||
const fired = await waitFire(
|
const fired = await waitFire(
|
||||||
|
|
|
@ -6,28 +6,20 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { signup, api, post, connectStream, startServer } from '../utils.js';
|
import { api, connectStream, post, signup } from '../utils.js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('Note thread mute', () => {
|
describe('Note thread mute', () => {
|
||||||
let app: INestApplicationContext;
|
|
||||||
|
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
let bob: misskey.entities.SignupResponse;
|
let bob: misskey.entities.SignupResponse;
|
||||||
let carol: misskey.entities.SignupResponse;
|
let carol: misskey.entities.SignupResponse;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
||||||
carol = await signup({ username: 'carol' });
|
carol = await signup({ username: 'carol' });
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('notes/mentions にミュートしているスレッドの投稿が含まれない', async () => {
|
test('notes/mentions にミュートしているスレッドの投稿が含まれない', async () => {
|
||||||
const bobNote = await post(bob, { text: '@alice @carol root note' });
|
const bobNote = await post(bob, { text: '@alice @carol root note' });
|
||||||
const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' });
|
const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' });
|
||||||
|
|
|
@ -6,12 +6,8 @@
|
||||||
// How to run:
|
// How to run:
|
||||||
// pnpm jest -- e2e/timelines.ts
|
// pnpm jest -- e2e/timelines.ts
|
||||||
|
|
||||||
process.env.NODE_ENV = 'test';
|
|
||||||
process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING = 'true';
|
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { api, post, randomString, signup, sleep, startServer, uploadUrl } from '../utils.js';
|
import { api, post, randomString, sendEnvUpdateRequest, signup, sleep, uploadUrl } from '../utils.js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
|
|
||||||
function genHost() {
|
function genHost() {
|
||||||
return randomString() + '.example.com';
|
return randomString() + '.example.com';
|
||||||
|
@ -21,16 +17,6 @@ function waitForPushToTl() {
|
||||||
return sleep(500);
|
return sleep(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
let app: INestApplicationContext;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
app = await startServer();
|
|
||||||
}, 1000 * 60 * 2);
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Timelines', () => {
|
describe('Timelines', () => {
|
||||||
describe('Home TL', () => {
|
describe('Home TL', () => {
|
||||||
test.concurrent('自分の visibility: followers なノートが含まれる', async () => {
|
test.concurrent('自分の visibility: followers なノートが含まれる', async () => {
|
||||||
|
@ -334,8 +320,9 @@ describe('Timelines', () => {
|
||||||
test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
|
test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
|
||||||
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
||||||
|
|
||||||
|
await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
|
||||||
await api('/following/create', { userId: bob.id }, alice);
|
await api('/following/create', { userId: bob.id }, alice);
|
||||||
await sleep(1000);
|
|
||||||
const bobNote = await post(bob, { text: 'hi' });
|
const bobNote = await post(bob, { text: 'hi' });
|
||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
@ -348,8 +335,9 @@ describe('Timelines', () => {
|
||||||
test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
|
test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
|
||||||
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
||||||
|
|
||||||
|
await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
|
||||||
await api('/following/create', { userId: bob.id }, alice);
|
await api('/following/create', { userId: bob.id }, alice);
|
||||||
await sleep(1000);
|
|
||||||
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
|
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
|
||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
@ -762,8 +750,9 @@ describe('Timelines', () => {
|
||||||
test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
|
test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
|
||||||
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
||||||
|
|
||||||
|
await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
|
||||||
await api('/following/create', { userId: bob.id }, alice);
|
await api('/following/create', { userId: bob.id }, alice);
|
||||||
await sleep(1000);
|
|
||||||
const bobNote = await post(bob, { text: 'hi' });
|
const bobNote = await post(bob, { text: 'hi' });
|
||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
@ -776,8 +765,9 @@ describe('Timelines', () => {
|
||||||
test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
|
test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
|
||||||
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
||||||
|
|
||||||
|
await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
|
||||||
await api('/following/create', { userId: bob.id }, alice);
|
await api('/following/create', { userId: bob.id }, alice);
|
||||||
await sleep(1000);
|
|
||||||
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
|
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
|
||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
|
@ -6,20 +6,16 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { signup, api, post, uploadUrl, startServer } from '../utils.js';
|
import { api, post, signup, uploadUrl } from '../utils.js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('users/notes', () => {
|
describe('users/notes', () => {
|
||||||
let app: INestApplicationContext;
|
|
||||||
|
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
let jpgNote: any;
|
let jpgNote: any;
|
||||||
let pngNote: any;
|
let pngNote: any;
|
||||||
let jpgPngNote: any;
|
let jpgPngNote: any;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
const jpg = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg');
|
const jpg = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg');
|
||||||
const png = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.png');
|
const png = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.png');
|
||||||
|
@ -34,10 +30,6 @@ describe('users/notes', () => {
|
||||||
});
|
});
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
afterAll(async() => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('withFiles', async () => {
|
test('withFiles', async () => {
|
||||||
const res = await api('/users/notes', {
|
const res = await api('/users/notes', {
|
||||||
userId: alice.id,
|
userId: alice.id,
|
||||||
|
|
|
@ -8,20 +8,8 @@ process.env.NODE_ENV = 'test';
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { inspect } from 'node:util';
|
import { inspect } from 'node:util';
|
||||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
import { api, page, post, role, signup, successfulApiCall, uploadFile } from '../utils.js';
|
||||||
import {
|
|
||||||
signup,
|
|
||||||
post,
|
|
||||||
page,
|
|
||||||
role,
|
|
||||||
startServer,
|
|
||||||
api,
|
|
||||||
successfulApiCall,
|
|
||||||
failedApiCall,
|
|
||||||
uploadFile,
|
|
||||||
} from '../utils.js';
|
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
|
|
||||||
describe('ユーザー', () => {
|
describe('ユーザー', () => {
|
||||||
// エンティティとしてのユーザーを主眼においたテストを記述する
|
// エンティティとしてのユーザーを主眼においたテストを記述する
|
||||||
|
@ -185,8 +173,6 @@ describe('ユーザー', () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let app: INestApplicationContext;
|
|
||||||
|
|
||||||
let root: User;
|
let root: User;
|
||||||
let alice: User;
|
let alice: User;
|
||||||
let aliceNote: misskey.entities.Note;
|
let aliceNote: misskey.entities.Note;
|
||||||
|
@ -230,10 +216,6 @@ describe('ユーザー', () => {
|
||||||
let userFollowRequesting: User;
|
let userFollowRequesting: User;
|
||||||
let userFollowRequested: User;
|
let userFollowRequested: User;
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
app = await startServer();
|
|
||||||
}, 1000 * 60 * 2);
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
root = await signup({ username: 'root' });
|
root = await signup({ username: 'root' });
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
|
@ -321,10 +303,6 @@ describe('ユーザー', () => {
|
||||||
await api('following/create', { userId: userFollowRequested.id }, userFollowRequesting);
|
await api('following/create', { userId: userFollowRequested.id }, userFollowRequesting);
|
||||||
}, 1000 * 60 * 10);
|
}, 1000 * 60 * 10);
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
alice = {
|
alice = {
|
||||||
...alice,
|
...alice,
|
||||||
|
|
|
@ -6,24 +6,16 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { host, origin, relativeFetch, signup, startServer } from '../utils.js';
|
import { host, origin, relativeFetch, signup } from '../utils.js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('.well-known', () => {
|
describe('.well-known', () => {
|
||||||
let app: INestApplicationContext;
|
|
||||||
let alice: misskey.entities.User;
|
let alice: misskey.entities.User;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
|
||||||
|
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('nodeinfo', async () => {
|
test('nodeinfo', async () => {
|
||||||
const res = await relativeFetch('.well-known/nodeinfo');
|
const res = await relativeFetch('.well-known/nodeinfo');
|
||||||
assert.ok(res.ok);
|
assert.ok(res.ok);
|
||||||
|
|
8
packages/backend/test/jest.setup.ts
Normal file
8
packages/backend/test/jest.setup.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { initTestDb, sendEnvResetRequest } from './utils.js';
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await Promise.all([
|
||||||
|
initTestDb(false),
|
||||||
|
sendEnvResetRequest(),
|
||||||
|
]);
|
||||||
|
});
|
|
@ -15,7 +15,13 @@ import type { LoggerService } from '@/core/LoggerService.js';
|
||||||
import type { MetaService } from '@/core/MetaService.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 { bindThis } from '@/decorators.js';
|
||||||
import type { NoteReactionsRepository, NotesRepository, PollsRepository, UsersRepository, FollowRequestsRepository } from '@/models/_.js';
|
import type {
|
||||||
|
FollowRequestsRepository,
|
||||||
|
NoteReactionsRepository,
|
||||||
|
NotesRepository,
|
||||||
|
PollsRepository,
|
||||||
|
UsersRepository,
|
||||||
|
} from '@/models/_.js';
|
||||||
|
|
||||||
type MockResponse = {
|
type MockResponse = {
|
||||||
type: string;
|
type: string;
|
||||||
|
|
|
@ -10,7 +10,13 @@ import { ModuleMocker } from 'jest-mock';
|
||||||
import { Test } from '@nestjs/testing';
|
import { Test } from '@nestjs/testing';
|
||||||
import { GlobalModule } from '@/GlobalModule.js';
|
import { GlobalModule } from '@/GlobalModule.js';
|
||||||
import { AnnouncementService } from '@/core/AnnouncementService.js';
|
import { AnnouncementService } from '@/core/AnnouncementService.js';
|
||||||
import type { MiAnnouncement, AnnouncementsRepository, AnnouncementReadsRepository, UsersRepository, MiUser } from '@/models/_.js';
|
import type {
|
||||||
|
AnnouncementReadsRepository,
|
||||||
|
AnnouncementsRepository,
|
||||||
|
MiAnnouncement,
|
||||||
|
MiUser,
|
||||||
|
UsersRepository,
|
||||||
|
} from '@/models/_.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { genAidx } from '@/misc/id/aidx.js';
|
import { genAidx } from '@/misc/id/aidx.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
|
|
|
@ -6,7 +6,13 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import { Test } from '@nestjs/testing';
|
import { Test } from '@nestjs/testing';
|
||||||
import { DeleteObjectCommandOutput, DeleteObjectCommand, NoSuchKey, InvalidObjectState, S3Client } from '@aws-sdk/client-s3';
|
import {
|
||||||
|
DeleteObjectCommand,
|
||||||
|
DeleteObjectCommandOutput,
|
||||||
|
InvalidObjectState,
|
||||||
|
NoSuchKey,
|
||||||
|
S3Client,
|
||||||
|
} from '@aws-sdk/client-s3';
|
||||||
import { mockClient } from 'aws-sdk-client-mock';
|
import { mockClient } from 'aws-sdk-client-mock';
|
||||||
import { GlobalModule } from '@/GlobalModule.js';
|
import { GlobalModule } from '@/GlobalModule.js';
|
||||||
import { DriveService } from '@/core/DriveService.js';
|
import { DriveService } from '@/core/DriveService.js';
|
||||||
|
|
|
@ -55,7 +55,8 @@ describe('FetchInstanceMetadataService', () => {
|
||||||
return { fetch: jest.fn() };
|
return { fetch: jest.fn() };
|
||||||
} else if (token === DI.redis) {
|
} else if (token === DI.redis) {
|
||||||
return mockRedis;
|
return mockRedis;
|
||||||
}})
|
}
|
||||||
|
})
|
||||||
.compile();
|
.compile();
|
||||||
|
|
||||||
app.enableShutdownHooks();
|
app.enableShutdownHooks();
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { fileURLToPath } from 'node:url';
|
||||||
import { dirname } from 'node:path';
|
import { dirname } from 'node:path';
|
||||||
import { ModuleMocker } from 'jest-mock';
|
import { ModuleMocker } from 'jest-mock';
|
||||||
import { Test } from '@nestjs/testing';
|
import { Test } from '@nestjs/testing';
|
||||||
import { describe, beforeAll, afterAll, test } from '@jest/globals';
|
import { afterAll, beforeAll, describe, test } from '@jest/globals';
|
||||||
import { GlobalModule } from '@/GlobalModule.js';
|
import { GlobalModule } from '@/GlobalModule.js';
|
||||||
import { FileInfoService } from '@/core/FileInfoService.js';
|
import { FileInfoService } from '@/core/FileInfoService.js';
|
||||||
//import { DI } from '@/di-symbols.js';
|
//import { DI } from '@/di-symbols.js';
|
||||||
|
|
|
@ -6,15 +6,13 @@
|
||||||
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 { GlobalModule } from '@/GlobalModule.js';
|
||||||
import type { MetasRepository } from '@/models/_.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { CoreModule } from '@/core/CoreModule.js';
|
import { CoreModule } from '@/core/CoreModule.js';
|
||||||
import type { DataSource } from 'typeorm';
|
|
||||||
import type { TestingModule } from '@nestjs/testing';
|
import type { TestingModule } from '@nestjs/testing';
|
||||||
|
import type { DataSource } from 'typeorm';
|
||||||
|
|
||||||
describe('MetaService', () => {
|
describe('MetaService', () => {
|
||||||
let app: TestingModule;
|
let app: TestingModule;
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { Test } from '@nestjs/testing';
|
||||||
import * as lolex from '@sinonjs/fake-timers';
|
import * as lolex from '@sinonjs/fake-timers';
|
||||||
import { GlobalModule } from '@/GlobalModule.js';
|
import { GlobalModule } from '@/GlobalModule.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import type { MiRole, RolesRepository, RoleAssignmentsRepository, UsersRepository, MiUser } from '@/models/_.js';
|
import type { MiRole, MiUser, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/_.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { genAidx } from '@/misc/id/aidx.js';
|
import { genAidx } from '@/misc/id/aidx.js';
|
||||||
|
|
|
@ -6,7 +6,13 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import { Test } from '@nestjs/testing';
|
import { Test } from '@nestjs/testing';
|
||||||
import { UploadPartCommand, CompleteMultipartUploadCommand, CreateMultipartUploadCommand, S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
|
import {
|
||||||
|
CompleteMultipartUploadCommand,
|
||||||
|
CreateMultipartUploadCommand,
|
||||||
|
PutObjectCommand,
|
||||||
|
S3Client,
|
||||||
|
UploadPartCommand,
|
||||||
|
} from '@aws-sdk/client-s3';
|
||||||
import { mockClient } from 'aws-sdk-client-mock';
|
import { mockClient } from 'aws-sdk-client-mock';
|
||||||
import { GlobalModule } from '@/GlobalModule.js';
|
import { GlobalModule } from '@/GlobalModule.js';
|
||||||
import { CoreModule } from '@/core/CoreModule.js';
|
import { CoreModule } from '@/core/CoreModule.js';
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ulid } from 'ulid';
|
import { ulid } from 'ulid';
|
||||||
import { describe, test, expect } from '@jest/globals';
|
import { describe, expect, test } from '@jest/globals';
|
||||||
import { aidRegExp, genAid, parseAid } from '@/misc/id/aid.js';
|
import { aidRegExp, genAid, parseAid } from '@/misc/id/aid.js';
|
||||||
import { aidxRegExp, genAidx, parseAidx } from '@/misc/id/aidx.js';
|
import { aidxRegExp, genAidx, parseAidx } from '@/misc/id/aidx.js';
|
||||||
import { genMeid, meidRegExp, parseMeid } from '@/misc/id/meid.js';
|
import { genMeid, meidRegExp, parseMeid } from '@/misc/id/meid.js';
|
||||||
import { genMeidg, meidgRegExp, parseMeidg } from '@/misc/id/meidg.js';
|
import { genMeidg, meidgRegExp, parseMeidg } from '@/misc/id/meidg.js';
|
||||||
import { genObjectId, objectIdRegExp, parseObjectId } from '@/misc/id/object-id.js';
|
import { genObjectId, objectIdRegExp, parseObjectId } from '@/misc/id/object-id.js';
|
||||||
import { ulidRegExp, parseUlid } from '@/misc/id/ulid.js';
|
import { parseUlid, ulidRegExp } from '@/misc/id/ulid.js';
|
||||||
|
|
||||||
describe('misc:id', () => {
|
describe('misc:id', () => {
|
||||||
test('aid', () => {
|
test('aid', () => {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, test, expect } from '@jest/globals';
|
import { describe, expect, test } from '@jest/globals';
|
||||||
import { contentDisposition } from '@/misc/content-disposition.js';
|
import { contentDisposition } from '@/misc/content-disposition.js';
|
||||||
|
|
||||||
describe('misc:content-disposition', () => {
|
describe('misc:content-disposition', () => {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import * as assert from 'node:assert';
|
import * as assert from 'node:assert';
|
||||||
import { readFile } from 'node:fs/promises';
|
import { readFile } from 'node:fs/promises';
|
||||||
import { isAbsolute, basename } from 'node:path';
|
import { basename, isAbsolute } from 'node:path';
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import { inspect } from 'node:util';
|
import { inspect } from 'node:util';
|
||||||
import WebSocket, { ClientOptions } from 'ws';
|
import WebSocket, { ClientOptions } from 'ws';
|
||||||
|
@ -68,7 +68,11 @@ export const failedApiCall = async <T, >(request: ApiRequest, assertion: {
|
||||||
return res.body;
|
return res.body;
|
||||||
};
|
};
|
||||||
|
|
||||||
const request = async (path: string, params: any, me?: UserToken): Promise<{ status: number, headers: Headers, body: any }> => {
|
const request = async (path: string, params: any, me?: UserToken): Promise<{
|
||||||
|
status: number,
|
||||||
|
headers: Headers,
|
||||||
|
body: any
|
||||||
|
}> => {
|
||||||
const bodyAuth: Record<string, string> = {};
|
const bodyAuth: Record<string, string> = {};
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
@ -275,7 +279,11 @@ interface UploadOptions {
|
||||||
* Upload file
|
* Upload file
|
||||||
* @param user User
|
* @param user User
|
||||||
*/
|
*/
|
||||||
export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadOptions = {}): Promise<{ status: number, headers: Headers, body: misskey.Endpoints['drive/files/create']['res'] | null }> => {
|
export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadOptions = {}): Promise<{
|
||||||
|
status: number,
|
||||||
|
headers: Headers,
|
||||||
|
body: misskey.Endpoints['drive/files/create']['res'] | null
|
||||||
|
}> => {
|
||||||
const absPath = path == null
|
const absPath = path == null
|
||||||
? new URL('resources/Lenna.jpg', import.meta.url)
|
? new URL('resources/Lenna.jpg', import.meta.url)
|
||||||
: isAbsolute(path.toString())
|
: isAbsolute(path.toString())
|
||||||
|
@ -557,3 +565,34 @@ export function sleep(msec: number) {
|
||||||
}, msec);
|
}, msec);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function sendEnvUpdateRequest(params: { key: string, value?: string }) {
|
||||||
|
const res = await fetch(
|
||||||
|
`http://localhost:${port + 1000}/env`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(params),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error('server env update failed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sendEnvResetRequest() {
|
||||||
|
const res = await fetch(
|
||||||
|
`http://localhost:${port + 1000}/env-reset`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error('server env update failed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
BIN
packages/frontend/assets/drop-and-fusion/bgm_1.mp3
Normal file
BIN
packages/frontend/assets/drop-and-fusion/bgm_1.mp3
Normal file
Binary file not shown.
BIN
packages/frontend/assets/drop-and-fusion/bubble2.mp3
Normal file
BIN
packages/frontend/assets/drop-and-fusion/bubble2.mp3
Normal file
Binary file not shown.
BIN
packages/frontend/assets/drop-and-fusion/click.mp3
Normal file
BIN
packages/frontend/assets/drop-and-fusion/click.mp3
Normal file
Binary file not shown.
BIN
packages/frontend/assets/drop-and-fusion/gameover.mp3
Normal file
BIN
packages/frontend/assets/drop-and-fusion/gameover.mp3
Normal file
Binary file not shown.
BIN
packages/frontend/assets/drop-and-fusion/hold.mp3
Normal file
BIN
packages/frontend/assets/drop-and-fusion/hold.mp3
Normal file
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 248 KiB |
BIN
packages/frontend/assets/drop-and-fusion/poi1.mp3
Normal file
BIN
packages/frontend/assets/drop-and-fusion/poi1.mp3
Normal file
Binary file not shown.
BIN
packages/frontend/assets/drop-and-fusion/poi2.mp3
Normal file
BIN
packages/frontend/assets/drop-and-fusion/poi2.mp3
Normal file
Binary file not shown.
|
@ -4,7 +4,7 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"watch": "vite",
|
"watch": "vite",
|
||||||
"dev": "vite --config vite.config.local-dev.ts",
|
"dev": "vite --config vite.config.local-dev.ts --debug hmr",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"",
|
"storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"",
|
||||||
"build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js",
|
"build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js",
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
"@rollup/plugin-json": "6.1.0",
|
"@rollup/plugin-json": "6.1.0",
|
||||||
"@rollup/plugin-replace": "5.0.5",
|
"@rollup/plugin-replace": "5.0.5",
|
||||||
"@rollup/pluginutils": "5.1.0",
|
"@rollup/pluginutils": "5.1.0",
|
||||||
"@syuilo/aiscript": "0.16.0",
|
"@syuilo/aiscript": "0.17.0",
|
||||||
"@tabler/icons-webfont": "2.44.0",
|
"@tabler/icons-webfont": "2.44.0",
|
||||||
"@twemoji/parser": "15.0.0",
|
"@twemoji/parser": "15.0.0",
|
||||||
"@vitejs/plugin-vue": "5.0.2",
|
"@vitejs/plugin-vue": "5.0.2",
|
||||||
|
@ -58,6 +58,7 @@
|
||||||
"rollup": "4.9.1",
|
"rollup": "4.9.1",
|
||||||
"sanitize-html": "2.11.0",
|
"sanitize-html": "2.11.0",
|
||||||
"sass": "1.69.5",
|
"sass": "1.69.5",
|
||||||
|
"seedrandom": "^3.0.5",
|
||||||
"shiki": "0.14.7",
|
"shiki": "0.14.7",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { getAccountFromId } from '@/scripts/get-account-from-id.js';
|
||||||
import { deckStore } from '@/ui/deck/deck-store.js';
|
import { deckStore } from '@/ui/deck/deck-store.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { fetchCustomEmojis } from '@/custom-emojis.js';
|
import { fetchCustomEmojis } from '@/custom-emojis.js';
|
||||||
|
import { setupRouter } from '@/global/router/definition.js';
|
||||||
|
|
||||||
export async function common(createVue: () => App<Element>) {
|
export async function common(createVue: () => App<Element>) {
|
||||||
console.info(`Misskey v${version}`);
|
console.info(`Misskey v${version}`);
|
||||||
|
@ -241,6 +242,8 @@ export async function common(createVue: () => App<Element>) {
|
||||||
|
|
||||||
const app = createVue();
|
const app = createVue();
|
||||||
|
|
||||||
|
setupRouter(app);
|
||||||
|
|
||||||
if (_DEV_) {
|
if (_DEV_) {
|
||||||
app.config.performance = true;
|
app.config.performance = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,23 +3,23 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createApp, markRaw, defineAsyncComponent } from 'vue';
|
import { createApp, defineAsyncComponent, markRaw } from 'vue';
|
||||||
import { common } from './common.js';
|
import { common } from './common.js';
|
||||||
import { ui } from '@/config.js';
|
import { ui } from '@/config.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { confirm, alert, post, popup, toast } from '@/os.js';
|
import { alert, confirm, popup, post, toast } from '@/os.js';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
import * as sound from '@/scripts/sound.js';
|
import * as sound from '@/scripts/sound.js';
|
||||||
import { $i, updateAccount, signout } from '@/account.js';
|
import { $i, signout, updateAccount } from '@/account.js';
|
||||||
import { defaultStore, ColdDeviceStorage } from '@/store.js';
|
import { ColdDeviceStorage, defaultStore } from '@/store.js';
|
||||||
import { makeHotkey } from '@/scripts/hotkey.js';
|
import { makeHotkey } from '@/scripts/hotkey.js';
|
||||||
import { reactionPicker } from '@/scripts/reaction-picker.js';
|
import { reactionPicker } from '@/scripts/reaction-picker.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js';
|
import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js';
|
||||||
import { mainRouter } from '@/router.js';
|
|
||||||
import { initializeSw } from '@/scripts/initialize-sw.js';
|
import { initializeSw } from '@/scripts/initialize-sw.js';
|
||||||
import { deckStore } from '@/ui/deck/deck-store.js';
|
import { deckStore } from '@/ui/deck/deck-store.js';
|
||||||
import { emojiPicker } from '@/scripts/emoji-picker.js';
|
import { emojiPicker } from '@/scripts/emoji-picker.js';
|
||||||
|
import { mainRouter } from '@/global/router/main.js';
|
||||||
|
|
||||||
export async function mainBoot() {
|
export async function mainBoot() {
|
||||||
const { isClientUpdated } = await common(() => createApp(
|
const { isClientUpdated } = await common(() => createApp(
|
||||||
|
@ -271,7 +271,7 @@ export async function mainBoot() {
|
||||||
|
|
||||||
main.on('unreadAntenna', () => {
|
main.on('unreadAntenna', () => {
|
||||||
updateAccount({ hasUnreadAntenna: true });
|
updateAccount({ hasUnreadAntenna: true });
|
||||||
sound.play('antenna');
|
sound.playMisskeySfx('antenna');
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on('readAllAnnouncements', () => {
|
main.on('readAllAnnouncements', () => {
|
||||||
|
|
|
@ -266,14 +266,23 @@ function emojiAutoComplete(query: string | null, emojiDb: EmojiDef[], max = 30):
|
||||||
}
|
}
|
||||||
|
|
||||||
const matched = new Map<string, EmojiScore>();
|
const matched = new Map<string, EmojiScore>();
|
||||||
|
// 完全一致(エイリアス込み)
|
||||||
|
emojiDb.some(x => {
|
||||||
|
if (x.name === query && !matched.has(x.aliasOf ?? x.name)) {
|
||||||
|
matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length + 2 });
|
||||||
|
}
|
||||||
|
return matched.size === max;
|
||||||
|
});
|
||||||
|
|
||||||
// 前方一致(エイリアスなし)
|
// 前方一致(エイリアスなし)
|
||||||
|
if (matched.size < max) {
|
||||||
emojiDb.some(x => {
|
emojiDb.some(x => {
|
||||||
if (x.name.startsWith(query) && !x.aliasOf) {
|
if (x.name.startsWith(query) && !x.aliasOf) {
|
||||||
matched.set(x.name, { emoji: x, score: query.length + 1 });
|
matched.set(x.name, { emoji: x, score: query.length + 1 });
|
||||||
}
|
}
|
||||||
return matched.size === max;
|
return matched.size === max;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 前方一致(エイリアス込み)
|
// 前方一致(エイリアス込み)
|
||||||
if (matched.size < max) {
|
if (matched.size < max) {
|
||||||
|
|
|
@ -45,10 +45,10 @@ import bytes from '@/filters/bytes.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
import { useRouter } from '@/router.js';
|
|
||||||
import { getDriveFileMenu, getDriveMultiFileMenu } from '@/scripts/get-drive-file-menu.js';
|
import { getDriveFileMenu, getDriveMultiFileMenu } from '@/scripts/get-drive-file-menu.js';
|
||||||
import { isTouchUsing } from '@/scripts/touch.js';
|
import { isTouchUsing } from '@/scripts/touch.js';
|
||||||
import { deviceKind } from '@/scripts/device-kind.js';
|
import { deviceKind } from '@/scripts/device-kind.js';
|
||||||
|
import { useRouter } from '@/global/router/supplier.js';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
|
@ -226,7 +226,18 @@ watch(q, () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (customEmojisMap.has(newQ)) {
|
||||||
|
matches.add(customEmojisMap.get(newQ)!);
|
||||||
|
}
|
||||||
|
if (matches.size >= max) return matches;
|
||||||
|
|
||||||
for (const emoji of emojis) {
|
for (const emoji of emojis) {
|
||||||
|
if (emoji.aliases.some(alias => alias === newQ)) {
|
||||||
|
matches.add(emoji);
|
||||||
|
if (matches.size >= max) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matches.size >= max) return matches;for (const emoji of emojis) {
|
||||||
if (emoji.name.startsWith(newQ)) {
|
if (emoji.name.startsWith(newQ)) {
|
||||||
matches.add(emoji);
|
matches.add(emoji);
|
||||||
if (matches.size >= max) break;
|
if (matches.size >= max) break;
|
||||||
|
|
|
@ -352,7 +352,7 @@ function react(viaKeyboard = false): void {
|
||||||
pleaseLogin();
|
pleaseLogin();
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
if (appearNote.value.reactionAcceptance === 'likeOnly') {
|
if (appearNote.value.reactionAcceptance === 'likeOnly') {
|
||||||
sound.play('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
|
|
||||||
if (props.mock) {
|
if (props.mock) {
|
||||||
return;
|
return;
|
||||||
|
@ -372,7 +372,7 @@ function react(viaKeyboard = false): void {
|
||||||
} else {
|
} else {
|
||||||
blur();
|
blur();
|
||||||
reactionPicker.show(reactButton.value, reaction => {
|
reactionPicker.show(reactButton.value, reaction => {
|
||||||
sound.play('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
|
|
||||||
if (props.mock) {
|
if (props.mock) {
|
||||||
emit('reaction', reaction);
|
emit('reaction', reaction);
|
||||||
|
|
|
@ -396,7 +396,7 @@ function react(viaKeyboard = false): void {
|
||||||
pleaseLogin();
|
pleaseLogin();
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
if (appearNote.value.reactionAcceptance === 'likeOnly') {
|
if (appearNote.value.reactionAcceptance === 'likeOnly') {
|
||||||
sound.play('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
|
|
||||||
misskeyApi('notes/reactions/create', {
|
misskeyApi('notes/reactions/create', {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
|
@ -412,7 +412,7 @@ function react(viaKeyboard = false): void {
|
||||||
} else {
|
} else {
|
||||||
blur();
|
blur();
|
||||||
reactionPicker.show(reactButton.value, reaction => {
|
reactionPicker.show(reactButton.value, reaction => {
|
||||||
sound.play('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
|
|
||||||
misskeyApi('notes/reactions/create', {
|
misskeyApi('notes/reactions/create', {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
|
|
|
@ -23,26 +23,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div ref="contents" :class="$style.root" style="container-type: inline-size;">
|
<div ref="contents" :class="$style.root" style="container-type: inline-size;">
|
||||||
<RouterView :key="reloadCount" :router="router"/>
|
<RouterView :key="reloadCount" :router="windowRouter"/>
|
||||||
</div>
|
</div>
|
||||||
</MkWindow>
|
</MkWindow>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ComputedRef, onMounted, onUnmounted, provide, shallowRef, ref, computed } from 'vue';
|
import { computed, ComputedRef, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
|
||||||
import RouterView from '@/components/global/RouterView.vue';
|
import RouterView from '@/components/global/RouterView.vue';
|
||||||
import MkWindow from '@/components/MkWindow.vue';
|
import MkWindow from '@/components/MkWindow.vue';
|
||||||
import { popout as _popout } from '@/scripts/popout.js';
|
import { popout as _popout } from '@/scripts/popout.js';
|
||||||
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
||||||
import { url } from '@/config.js';
|
import { url } from '@/config.js';
|
||||||
import { mainRouter, routes, page } from '@/router.js';
|
import { useScrollPositionManager } from '@/nirax.js';
|
||||||
import { $i } from '@/account.js';
|
|
||||||
import { Router, useScrollPositionManager } from '@/nirax.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
|
import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
|
||||||
import { openingWindowsCount } from '@/os.js';
|
import { openingWindowsCount } from '@/os.js';
|
||||||
import { claimAchievement } from '@/scripts/achievements.js';
|
import { claimAchievement } from '@/scripts/achievements.js';
|
||||||
import { getScrollContainer } from '@/scripts/scroll.js';
|
import { getScrollContainer } from '@/scripts/scroll.js';
|
||||||
|
import { useRouterFactory } from '@/global/router/supplier.js';
|
||||||
|
import { mainRouter } from '@/global/router/main.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
initialPath: string;
|
initialPath: string;
|
||||||
|
@ -52,14 +52,15 @@ defineEmits<{
|
||||||
(ev: 'closed'): void;
|
(ev: 'closed'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const router = new Router(routes, props.initialPath, !!$i, page(() => import('@/pages/not-found.vue')));
|
const routerFactory = useRouterFactory();
|
||||||
|
const windowRouter = routerFactory(props.initialPath);
|
||||||
|
|
||||||
const contents = shallowRef<HTMLElement>();
|
const contents = shallowRef<HTMLElement>();
|
||||||
const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
|
const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
|
||||||
const windowEl = shallowRef<InstanceType<typeof MkWindow>>();
|
const windowEl = shallowRef<InstanceType<typeof MkWindow>>();
|
||||||
const history = ref<{ path: string; key: any; }[]>([{
|
const history = ref<{ path: string; key: any; }[]>([{
|
||||||
path: router.getCurrentPath(),
|
path: windowRouter.getCurrentPath(),
|
||||||
key: router.getCurrentKey(),
|
key: windowRouter.getCurrentKey(),
|
||||||
}]);
|
}]);
|
||||||
const buttonsLeft = computed(() => {
|
const buttonsLeft = computed(() => {
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
|
@ -88,11 +89,11 @@ const buttonsRight = computed(() => {
|
||||||
});
|
});
|
||||||
const reloadCount = ref(0);
|
const reloadCount = ref(0);
|
||||||
|
|
||||||
router.addListener('push', ctx => {
|
windowRouter.addListener('push', ctx => {
|
||||||
history.value.push({ path: ctx.path, key: ctx.key });
|
history.value.push({ path: ctx.path, key: ctx.key });
|
||||||
});
|
});
|
||||||
|
|
||||||
provide('router', router);
|
provide('router', windowRouter);
|
||||||
provideMetadataReceiver((info) => {
|
provideMetadataReceiver((info) => {
|
||||||
pageMetadata.value = info;
|
pageMetadata.value = info;
|
||||||
});
|
});
|
||||||
|
@ -112,20 +113,20 @@ const contextmenu = computed(() => ([{
|
||||||
icon: 'ti ti-external-link',
|
icon: 'ti ti-external-link',
|
||||||
text: i18n.ts.openInNewTab,
|
text: i18n.ts.openInNewTab,
|
||||||
action: () => {
|
action: () => {
|
||||||
window.open(url + router.getCurrentPath(), '_blank', 'noopener');
|
window.open(url + windowRouter.getCurrentPath(), '_blank', 'noopener');
|
||||||
windowEl.value.close();
|
windowEl.value.close();
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
icon: 'ti ti-link',
|
icon: 'ti ti-link',
|
||||||
text: i18n.ts.copyLink,
|
text: i18n.ts.copyLink,
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(url + router.getCurrentPath());
|
copyToClipboard(url + windowRouter.getCurrentPath());
|
||||||
},
|
},
|
||||||
}]));
|
}]));
|
||||||
|
|
||||||
function back() {
|
function back() {
|
||||||
history.value.pop();
|
history.value.pop();
|
||||||
router.replace(history.value.at(-1)!.path, history.value.at(-1)!.key);
|
windowRouter.replace(history.value.at(-1)!.path, history.value.at(-1)!.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
function reload() {
|
function reload() {
|
||||||
|
@ -137,16 +138,16 @@ function close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function expand() {
|
function expand() {
|
||||||
mainRouter.push(router.getCurrentPath(), 'forcePage');
|
mainRouter.push(windowRouter.getCurrentPath(), 'forcePage');
|
||||||
windowEl.value.close();
|
windowEl.value.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function popout() {
|
function popout() {
|
||||||
_popout(router.getCurrentPath(), windowEl.value.$el);
|
_popout(windowRouter.getCurrentPath(), windowEl.value.$el);
|
||||||
windowEl.value.close();
|
windowEl.value.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
useScrollPositionManager(() => getScrollContainer(contents.value), router);
|
useScrollPositionManager(() => getScrollContainer(contents.value), windowRouter);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
openingWindowsCount.value++;
|
openingWindowsCount.value++;
|
||||||
|
|
|
@ -47,6 +47,7 @@ const props = withDefaults(defineProps<{
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'update:modelValue', value: number): void;
|
(ev: 'update:modelValue', value: number): void;
|
||||||
|
(ev: 'dragEnded', value: number): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const containerEl = shallowRef<HTMLElement>();
|
const containerEl = shallowRef<HTMLElement>();
|
||||||
|
@ -147,6 +148,7 @@ const onMousedown = (ev: MouseEvent | TouchEvent) => {
|
||||||
// 値が変わってたら通知
|
// 値が変わってたら通知
|
||||||
if (beforeValue !== finalValue.value) {
|
if (beforeValue !== finalValue.value) {
|
||||||
emit('update:modelValue', finalValue.value);
|
emit('update:modelValue', finalValue.value);
|
||||||
|
emit('dragEnded', finalValue.value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ async function toggleReaction() {
|
||||||
if (confirm.canceled) return;
|
if (confirm.canceled) return;
|
||||||
|
|
||||||
if (oldReaction !== props.reaction) {
|
if (oldReaction !== props.reaction) {
|
||||||
sound.play('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mock) {
|
if (mock) {
|
||||||
|
@ -83,7 +83,7 @@ async function toggleReaction() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
sound.play('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
|
|
||||||
if (mock) {
|
if (mock) {
|
||||||
emit('reactionToggled', props.reaction, (props.count + 1));
|
emit('reactionToggled', props.reaction, (props.count + 1));
|
||||||
|
|
|
@ -81,7 +81,7 @@ function prepend(note) {
|
||||||
emit('note');
|
emit('note');
|
||||||
|
|
||||||
if (props.sound) {
|
if (props.sound) {
|
||||||
sound.play($i && (note.userId === $i.id) ? 'noteMy' : 'note');
|
sound.playMisskeySfx($i && (note.userId === $i.id) ? 'noteMy' : 'note');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton>
|
<MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkSwitch v-for="kind in Object.keys(permissions)" :key="kind" v-model="permissions[kind]">{{ i18n.t(`_permissions.${kind}`) }}</MkSwitch>
|
<MkSwitch v-for="kind in Object.keys(permissionSwitches)" :key="kind" v-model="permissionSwitches[kind]">{{ i18n.t(`_permissions.${kind}`) }}</MkSwitch>
|
||||||
|
</div>
|
||||||
|
<div v-if="iAmAdmin" :class="$style.adminPermissions">
|
||||||
|
<div :class="$style.adminPermissionsHeader"><b>{{ i18n.ts.adminPermission }}</b></div>
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<MkSwitch v-for="kind in Object.keys(permissionSwitchesForAdmin)" :key="kind" v-model="permissionSwitchesForAdmin[kind]">{{ i18n.t(`_permissions.${kind}`) }}</MkSwitch>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
@ -49,6 +55,7 @@ import MkButton from './MkButton.vue';
|
||||||
import MkInfo from './MkInfo.vue';
|
import MkInfo from './MkInfo.vue';
|
||||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { iAmAdmin } from '@/account.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
title?: string | null;
|
title?: string | null;
|
||||||
|
@ -68,37 +75,76 @@ const emit = defineEmits<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const defaultPermissions = Misskey.permissions.filter(p => !p.startsWith('read:admin') && !p.startsWith('write:admin'));
|
const defaultPermissions = Misskey.permissions.filter(p => !p.startsWith('read:admin') && !p.startsWith('write:admin'));
|
||||||
|
const adminPermissions = Misskey.permissions.filter(p => p.startsWith('read:admin') || p.startsWith('write:admin'));
|
||||||
|
|
||||||
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
|
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||||
const name = ref(props.initialName);
|
const name = ref(props.initialName);
|
||||||
const permissions = ref(<Record<(typeof Misskey.permissions)[number], boolean>>{});
|
const permissionSwitches = ref(<Record<(typeof Misskey.permissions)[number], boolean>>{});
|
||||||
|
const permissionSwitchesForAdmin = ref(<Record<(typeof Misskey.permissions)[number], boolean>>{});
|
||||||
|
|
||||||
if (props.initialPermissions) {
|
if (props.initialPermissions) {
|
||||||
for (const kind of props.initialPermissions) {
|
for (const kind of props.initialPermissions) {
|
||||||
permissions.value[kind] = true;
|
permissionSwitches.value[kind] = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const kind of defaultPermissions) {
|
for (const kind of defaultPermissions) {
|
||||||
permissions.value[kind] = false;
|
permissionSwitches.value[kind] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iAmAdmin) {
|
||||||
|
for (const kind of adminPermissions) {
|
||||||
|
permissionSwitchesForAdmin.value[kind] = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ok(): void {
|
function ok(): void {
|
||||||
emit('done', {
|
emit('done', {
|
||||||
name: name.value,
|
name: name.value,
|
||||||
permissions: Object.keys(permissions.value).filter(p => permissions.value[p]),
|
permissions: [
|
||||||
|
...Object.keys(permissionSwitches.value).filter(p => permissionSwitches.value[p]),
|
||||||
|
...(iAmAdmin ? Object.keys(permissionSwitchesForAdmin.value).filter(p => permissionSwitchesForAdmin.value[p]) : []),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
dialog.value?.close();
|
dialog.value?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableAll(): void {
|
function disableAll(): void {
|
||||||
for (const p in permissions.value) {
|
for (const p in permissionSwitches.value) {
|
||||||
permissions.value[p] = false;
|
permissionSwitches.value[p] = false;
|
||||||
|
}
|
||||||
|
if (iAmAdmin) {
|
||||||
|
for (const p in permissionSwitchesForAdmin.value) {
|
||||||
|
permissionSwitchesForAdmin.value[p] = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function enableAll(): void {
|
function enableAll(): void {
|
||||||
for (const p in permissions.value) {
|
for (const p in permissionSwitches.value) {
|
||||||
permissions.value[p] = true;
|
permissionSwitches.value[p] = true;
|
||||||
|
}
|
||||||
|
if (iAmAdmin) {
|
||||||
|
for (const p in permissionSwitchesForAdmin.value) {
|
||||||
|
permissionSwitchesForAdmin.value[p] = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style module lang="scss">
|
||||||
|
.adminPermissions {
|
||||||
|
margin: 8px -6px 0;
|
||||||
|
padding: 24px 6px 6px;
|
||||||
|
border: 2px solid var(--error);
|
||||||
|
border-radius: calc(var(--radius) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.adminPermissionsHeader {
|
||||||
|
margin: -34px 0 6px 12px;
|
||||||
|
padding: 0 4px;
|
||||||
|
width: fit-content;
|
||||||
|
color: var(--error);
|
||||||
|
background: var(--panel);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -15,7 +15,7 @@ import * as os from '@/os.js';
|
||||||
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
||||||
import { url } from '@/config.js';
|
import { url } from '@/config.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { useRouter } from '@/router.js';
|
import { useRouter } from '@/global/router/supplier.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
to: string;
|
to: string;
|
||||||
|
|
|
@ -99,7 +99,7 @@ function onClick(ev: MouseEvent) {
|
||||||
icon: 'ti ti-plus',
|
icon: 'ti ti-plus',
|
||||||
action: () => {
|
action: () => {
|
||||||
react(`:${props.name}:`);
|
react(`:${props.name}:`);
|
||||||
sound.play('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
},
|
},
|
||||||
}] : [])], ev.currentTarget ?? ev.target);
|
}] : [])], ev.currentTarget ?? ev.target);
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ function onClick(ev: MouseEvent) {
|
||||||
icon: 'ti ti-plus',
|
icon: 'ti ti-plus',
|
||||||
action: () => {
|
action: () => {
|
||||||
react(props.emoji);
|
react(props.emoji);
|
||||||
sound.play('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
},
|
},
|
||||||
}] : [])], ev.currentTarget ?? ev.target);
|
}] : [])], ev.currentTarget ?? ev.target);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { inject, onBeforeUnmount, provide, shallowRef, ref } from 'vue';
|
import { inject, onBeforeUnmount, provide, ref, shallowRef } from 'vue';
|
||||||
import { Resolved, Router } from '@/nirax.js';
|
import { IRouter, Resolved } from '@/nirax.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
router?: Router;
|
router?: IRouter;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const router = props.router ?? inject('router');
|
const router = props.router ?? inject('router');
|
||||||
|
|
571
packages/frontend/src/global/router/definition.ts
Normal file
571
packages/frontend/src/global/router/definition.ts
Normal file
|
@ -0,0 +1,571 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { App, AsyncComponentLoader, defineAsyncComponent, provide } from 'vue';
|
||||||
|
import { IRouter, Router } from '@/nirax.js';
|
||||||
|
import { $i, iAmModerator } from '@/account.js';
|
||||||
|
import MkLoading from '@/pages/_loading_.vue';
|
||||||
|
import MkError from '@/pages/_error_.vue';
|
||||||
|
import { setMainRouter } from '@/global/router/main.js';
|
||||||
|
|
||||||
|
const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({
|
||||||
|
loader: loader,
|
||||||
|
loadingComponent: MkLoading,
|
||||||
|
errorComponent: MkError,
|
||||||
|
});
|
||||||
|
const routes = [{
|
||||||
|
path: '/@:initUser/pages/:initPageName/view-source',
|
||||||
|
component: page(() => import('@/pages/page-editor/page-editor.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/@:username/pages/:pageName',
|
||||||
|
component: page(() => import('@/pages/page.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/@:acct/following',
|
||||||
|
component: page(() => import('@/pages/user/following.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/@:acct/followers',
|
||||||
|
component: page(() => import('@/pages/user/followers.vue')),
|
||||||
|
}, {
|
||||||
|
name: 'user',
|
||||||
|
path: '/@:acct/:page?',
|
||||||
|
component: page(() => import('@/pages/user/index.vue')),
|
||||||
|
}, {
|
||||||
|
name: 'note',
|
||||||
|
path: '/notes/:noteId',
|
||||||
|
component: page(() => import('@/pages/note.vue')),
|
||||||
|
}, {
|
||||||
|
name: 'list',
|
||||||
|
path: '/list/:listId',
|
||||||
|
component: page(() => import('@/pages/list.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/clips/:clipId',
|
||||||
|
component: page(() => import('@/pages/clip.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/instance-info/:host',
|
||||||
|
component: page(() => import('@/pages/instance-info.vue')),
|
||||||
|
}, {
|
||||||
|
name: 'settings',
|
||||||
|
path: '/settings',
|
||||||
|
component: page(() => import('@/pages/settings/index.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
children: [{
|
||||||
|
path: '/profile',
|
||||||
|
name: 'profile',
|
||||||
|
component: page(() => import('@/pages/settings/profile.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/avatar-decoration',
|
||||||
|
name: 'avatarDecoration',
|
||||||
|
component: page(() => import('@/pages/settings/avatar-decoration.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/roles',
|
||||||
|
name: 'roles',
|
||||||
|
component: page(() => import('@/pages/settings/roles.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/privacy',
|
||||||
|
name: 'privacy',
|
||||||
|
component: page(() => import('@/pages/settings/privacy.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/emoji-picker',
|
||||||
|
name: 'emojiPicker',
|
||||||
|
component: page(() => import('@/pages/settings/emoji-picker.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/drive',
|
||||||
|
name: 'drive',
|
||||||
|
component: page(() => import('@/pages/settings/drive.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/drive/cleaner',
|
||||||
|
name: 'drive',
|
||||||
|
component: page(() => import('@/pages/settings/drive-cleaner.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/notifications',
|
||||||
|
name: 'notifications',
|
||||||
|
component: page(() => import('@/pages/settings/notifications.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/email',
|
||||||
|
name: 'email',
|
||||||
|
component: page(() => import('@/pages/settings/email.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/security',
|
||||||
|
name: 'security',
|
||||||
|
component: page(() => import('@/pages/settings/security.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/general',
|
||||||
|
name: 'general',
|
||||||
|
component: page(() => import('@/pages/settings/general.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/theme/install',
|
||||||
|
name: 'theme',
|
||||||
|
component: page(() => import('@/pages/settings/theme.install.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/theme/manage',
|
||||||
|
name: 'theme',
|
||||||
|
component: page(() => import('@/pages/settings/theme.manage.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/theme',
|
||||||
|
name: 'theme',
|
||||||
|
component: page(() => import('@/pages/settings/theme.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/navbar',
|
||||||
|
name: 'navbar',
|
||||||
|
component: page(() => import('@/pages/settings/navbar.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/statusbar',
|
||||||
|
name: 'statusbar',
|
||||||
|
component: page(() => import('@/pages/settings/statusbar.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/sounds',
|
||||||
|
name: 'sounds',
|
||||||
|
component: page(() => import('@/pages/settings/sounds.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/plugin/install',
|
||||||
|
name: 'plugin',
|
||||||
|
component: page(() => import('@/pages/settings/plugin.install.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/plugin',
|
||||||
|
name: 'plugin',
|
||||||
|
component: page(() => import('@/pages/settings/plugin.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/import-export',
|
||||||
|
name: 'import-export',
|
||||||
|
component: page(() => import('@/pages/settings/import-export.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/mute-block',
|
||||||
|
name: 'mute-block',
|
||||||
|
component: page(() => import('@/pages/settings/mute-block.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/api',
|
||||||
|
name: 'api',
|
||||||
|
component: page(() => import('@/pages/settings/api.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/apps',
|
||||||
|
name: 'api',
|
||||||
|
component: page(() => import('@/pages/settings/apps.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/webhook/edit/:webhookId',
|
||||||
|
name: 'webhook',
|
||||||
|
component: page(() => import('@/pages/settings/webhook.edit.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/webhook/new',
|
||||||
|
name: 'webhook',
|
||||||
|
component: page(() => import('@/pages/settings/webhook.new.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/webhook',
|
||||||
|
name: 'webhook',
|
||||||
|
component: page(() => import('@/pages/settings/webhook.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/deck',
|
||||||
|
name: 'deck',
|
||||||
|
component: page(() => import('@/pages/settings/deck.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/preferences-backups',
|
||||||
|
name: 'preferences-backups',
|
||||||
|
component: page(() => import('@/pages/settings/preferences-backups.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/migration',
|
||||||
|
name: 'migration',
|
||||||
|
component: page(() => import('@/pages/settings/migration.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/custom-css',
|
||||||
|
name: 'general',
|
||||||
|
component: page(() => import('@/pages/settings/custom-css.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/accounts',
|
||||||
|
name: 'profile',
|
||||||
|
component: page(() => import('@/pages/settings/accounts.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/other',
|
||||||
|
name: 'other',
|
||||||
|
component: page(() => import('@/pages/settings/other.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/',
|
||||||
|
component: page(() => import('@/pages/_empty_.vue')),
|
||||||
|
}],
|
||||||
|
}, {
|
||||||
|
path: '/reset-password/:token?',
|
||||||
|
component: page(() => import('@/pages/reset-password.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/signup-complete/:code',
|
||||||
|
component: page(() => import('@/pages/signup-complete.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/announcements',
|
||||||
|
component: page(() => import('@/pages/announcements.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/about',
|
||||||
|
component: page(() => import('@/pages/about.vue')),
|
||||||
|
hash: 'initialTab',
|
||||||
|
}, {
|
||||||
|
path: '/about-misskey',
|
||||||
|
component: page(() => import('@/pages/about-misskey.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/invite',
|
||||||
|
name: 'invite',
|
||||||
|
component: page(() => import('@/pages/invite.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/ads',
|
||||||
|
component: page(() => import('@/pages/ads.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/theme-editor',
|
||||||
|
component: page(() => import('@/pages/theme-editor.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/roles/:role',
|
||||||
|
component: page(() => import('@/pages/role.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/user-tags/:tag',
|
||||||
|
component: page(() => import('@/pages/user-tag.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/explore',
|
||||||
|
component: page(() => import('@/pages/explore.vue')),
|
||||||
|
hash: 'initialTab',
|
||||||
|
}, {
|
||||||
|
path: '/search',
|
||||||
|
component: page(() => import('@/pages/search.vue')),
|
||||||
|
query: {
|
||||||
|
q: 'query',
|
||||||
|
channel: 'channel',
|
||||||
|
type: 'type',
|
||||||
|
origin: 'origin',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
path: '/authorize-follow',
|
||||||
|
component: page(() => import('@/pages/follow.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/share',
|
||||||
|
component: page(() => import('@/pages/share.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/api-console',
|
||||||
|
component: page(() => import('@/pages/api-console.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/scratchpad',
|
||||||
|
component: page(() => import('@/pages/scratchpad.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/auth/:token',
|
||||||
|
component: page(() => import('@/pages/auth.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/miauth/:session',
|
||||||
|
component: page(() => import('@/pages/miauth.vue')),
|
||||||
|
query: {
|
||||||
|
callback: 'callback',
|
||||||
|
name: 'name',
|
||||||
|
icon: 'icon',
|
||||||
|
permission: 'permission',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
path: '/oauth/authorize',
|
||||||
|
component: page(() => import('@/pages/oauth.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/tags/:tag',
|
||||||
|
component: page(() => import('@/pages/tag.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/pages/new',
|
||||||
|
component: page(() => import('@/pages/page-editor/page-editor.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/pages/edit/:initPageId',
|
||||||
|
component: page(() => import('@/pages/page-editor/page-editor.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/pages',
|
||||||
|
component: page(() => import('@/pages/pages.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/play/:id/edit',
|
||||||
|
component: page(() => import('@/pages/flash/flash-edit.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/play/new',
|
||||||
|
component: page(() => import('@/pages/flash/flash-edit.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/play/:id',
|
||||||
|
component: page(() => import('@/pages/flash/flash.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/play',
|
||||||
|
component: page(() => import('@/pages/flash/flash-index.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/gallery/:postId/edit',
|
||||||
|
component: page(() => import('@/pages/gallery/edit.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/gallery/new',
|
||||||
|
component: page(() => import('@/pages/gallery/edit.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/gallery/:postId',
|
||||||
|
component: page(() => import('@/pages/gallery/post.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/gallery',
|
||||||
|
component: page(() => import('@/pages/gallery/index.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/channels/:channelId/edit',
|
||||||
|
component: page(() => import('@/pages/channel-editor.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/channels/new',
|
||||||
|
component: page(() => import('@/pages/channel-editor.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/channels/:channelId',
|
||||||
|
component: page(() => import('@/pages/channel.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/channels',
|
||||||
|
component: page(() => import('@/pages/channels.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/custom-emojis-manager',
|
||||||
|
component: page(() => import('@/pages/custom-emojis-manager.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/avatar-decorations',
|
||||||
|
name: 'avatarDecorations',
|
||||||
|
component: page(() => import('@/pages/avatar-decorations.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/registry/keys/:domain/:path(*)?',
|
||||||
|
component: page(() => import('@/pages/registry.keys.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/registry/value/:domain/:path(*)?',
|
||||||
|
component: page(() => import('@/pages/registry.value.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/registry',
|
||||||
|
component: page(() => import('@/pages/registry.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/install-extentions',
|
||||||
|
component: page(() => import('@/pages/install-extentions.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/admin/user/:userId',
|
||||||
|
component: iAmModerator ? page(() => import('@/pages/admin-user.vue')) : page(() => import('@/pages/not-found.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/admin/file/:fileId',
|
||||||
|
component: iAmModerator ? page(() => import('@/pages/admin-file.vue')) : page(() => import('@/pages/not-found.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/admin',
|
||||||
|
component: iAmModerator ? page(() => import('@/pages/admin/index.vue')) : page(() => import('@/pages/not-found.vue')),
|
||||||
|
children: [{
|
||||||
|
path: '/overview',
|
||||||
|
name: 'overview',
|
||||||
|
component: page(() => import('@/pages/admin/overview.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/users',
|
||||||
|
name: 'users',
|
||||||
|
component: page(() => import('@/pages/admin/users.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/emojis',
|
||||||
|
name: 'emojis',
|
||||||
|
component: page(() => import('@/pages/custom-emojis-manager.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/avatar-decorations',
|
||||||
|
name: 'avatarDecorations',
|
||||||
|
component: page(() => import('@/pages/avatar-decorations.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/queue',
|
||||||
|
name: 'queue',
|
||||||
|
component: page(() => import('@/pages/admin/queue.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/files',
|
||||||
|
name: 'files',
|
||||||
|
component: page(() => import('@/pages/admin/files.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/federation',
|
||||||
|
name: 'federation',
|
||||||
|
component: page(() => import('@/pages/admin/federation.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/announcements',
|
||||||
|
name: 'announcements',
|
||||||
|
component: page(() => import('@/pages/admin/announcements.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/ads',
|
||||||
|
name: 'ads',
|
||||||
|
component: page(() => import('@/pages/admin/ads.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/roles/:id/edit',
|
||||||
|
name: 'roles',
|
||||||
|
component: page(() => import('@/pages/admin/roles.edit.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/roles/new',
|
||||||
|
name: 'roles',
|
||||||
|
component: page(() => import('@/pages/admin/roles.edit.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/roles/:id',
|
||||||
|
name: 'roles',
|
||||||
|
component: page(() => import('@/pages/admin/roles.role.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/roles',
|
||||||
|
name: 'roles',
|
||||||
|
component: page(() => import('@/pages/admin/roles.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/database',
|
||||||
|
name: 'database',
|
||||||
|
component: page(() => import('@/pages/admin/database.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/abuses',
|
||||||
|
name: 'abuses',
|
||||||
|
component: page(() => import('@/pages/admin/abuses.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/modlog',
|
||||||
|
name: 'modlog',
|
||||||
|
component: page(() => import('@/pages/admin/modlog.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/settings',
|
||||||
|
name: 'settings',
|
||||||
|
component: page(() => import('@/pages/admin/settings.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/branding',
|
||||||
|
name: 'branding',
|
||||||
|
component: page(() => import('@/pages/admin/branding.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/moderation',
|
||||||
|
name: 'moderation',
|
||||||
|
component: page(() => import('@/pages/admin/moderation.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/email-settings',
|
||||||
|
name: 'email-settings',
|
||||||
|
component: page(() => import('@/pages/admin/email-settings.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/object-storage',
|
||||||
|
name: 'object-storage',
|
||||||
|
component: page(() => import('@/pages/admin/object-storage.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/security',
|
||||||
|
name: 'security',
|
||||||
|
component: page(() => import('@/pages/admin/security.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/relays',
|
||||||
|
name: 'relays',
|
||||||
|
component: page(() => import('@/pages/admin/relays.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/instance-block',
|
||||||
|
name: 'instance-block',
|
||||||
|
component: page(() => import('@/pages/admin/instance-block.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/proxy-account',
|
||||||
|
name: 'proxy-account',
|
||||||
|
component: page(() => import('@/pages/admin/proxy-account.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/external-services',
|
||||||
|
name: 'external-services',
|
||||||
|
component: page(() => import('@/pages/admin/external-services.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/other-settings',
|
||||||
|
name: 'other-settings',
|
||||||
|
component: page(() => import('@/pages/admin/other-settings.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/server-rules',
|
||||||
|
name: 'server-rules',
|
||||||
|
component: page(() => import('@/pages/admin/server-rules.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/invites',
|
||||||
|
name: 'invites',
|
||||||
|
component: page(() => import('@/pages/admin/invites.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/',
|
||||||
|
component: page(() => import('@/pages/_empty_.vue')),
|
||||||
|
}],
|
||||||
|
}, {
|
||||||
|
path: '/my/notifications',
|
||||||
|
component: page(() => import('@/pages/notifications.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/my/favorites',
|
||||||
|
component: page(() => import('@/pages/favorites.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/my/achievements',
|
||||||
|
component: page(() => import('@/pages/achievements.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/my/drive/folder/:folder',
|
||||||
|
component: page(() => import('@/pages/drive.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/my/drive',
|
||||||
|
component: page(() => import('@/pages/drive.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/my/drive/file/:fileId',
|
||||||
|
component: page(() => import('@/pages/drive.file.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/my/follow-requests',
|
||||||
|
component: page(() => import('@/pages/follow-requests.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/my/lists/:listId',
|
||||||
|
component: page(() => import('@/pages/my-lists/list.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/my/lists',
|
||||||
|
component: page(() => import('@/pages/my-lists/index.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/my/clips',
|
||||||
|
component: page(() => import('@/pages/my-clips/index.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/my/antennas/create',
|
||||||
|
component: page(() => import('@/pages/my-antennas/create.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/my/antennas/:antennaId',
|
||||||
|
component: page(() => import('@/pages/my-antennas/edit.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/my/antennas',
|
||||||
|
component: page(() => import('@/pages/my-antennas/index.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/timeline/list/:listId',
|
||||||
|
component: page(() => import('@/pages/user-list-timeline.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/timeline/antenna/:antennaId',
|
||||||
|
component: page(() => import('@/pages/antenna-timeline.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/clicker',
|
||||||
|
component: page(() => import('@/pages/clicker.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/bubble-game',
|
||||||
|
component: page(() => import('@/pages/drop-and-fusion.vue')),
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/timeline',
|
||||||
|
component: page(() => import('@/pages/timeline.vue')),
|
||||||
|
}, {
|
||||||
|
name: 'index',
|
||||||
|
path: '/',
|
||||||
|
component: $i ? page(() => import('@/pages/timeline.vue')) : page(() => import('@/pages/welcome.vue')),
|
||||||
|
globalCacheKey: 'index',
|
||||||
|
}, {
|
||||||
|
path: '/:(*)',
|
||||||
|
component: page(() => import('@/pages/not-found.vue')),
|
||||||
|
}];
|
||||||
|
|
||||||
|
function createRouterImpl(path: string): IRouter {
|
||||||
|
return new Router(routes, path, !!$i, page(() => import('@/pages/not-found.vue')));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Router}による画面遷移を可能とするために{@link mainRouter}をセットアップする。
|
||||||
|
* また、{@link Router}のインスタンスを作成するためのファクトリも{@link provide}経由で公開する(`routerFactory`というキーで取得可能)
|
||||||
|
*/
|
||||||
|
export function setupRouter(app: App) {
|
||||||
|
app.provide('routerFactory', createRouterImpl);
|
||||||
|
|
||||||
|
const mainRouter = createRouterImpl(location.pathname + location.search + location.hash);
|
||||||
|
|
||||||
|
window.history.replaceState({ key: mainRouter.getCurrentKey() }, '', location.href);
|
||||||
|
|
||||||
|
window.addEventListener('popstate', (event) => {
|
||||||
|
mainRouter.replace(location.pathname + location.search + location.hash, event.state?.key);
|
||||||
|
});
|
||||||
|
|
||||||
|
mainRouter.addListener('push', ctx => {
|
||||||
|
window.history.pushState({ key: ctx.key }, '', ctx.path);
|
||||||
|
});
|
||||||
|
|
||||||
|
setMainRouter(mainRouter);
|
||||||
|
}
|
163
packages/frontend/src/global/router/main.ts
Normal file
163
packages/frontend/src/global/router/main.ts
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ShallowRef } from 'vue';
|
||||||
|
import { EventEmitter } from 'eventemitter3';
|
||||||
|
import { IRouter, Resolved, RouteDef, RouterEvent } from '@/nirax.js';
|
||||||
|
|
||||||
|
function getMainRouter(): IRouter {
|
||||||
|
const router = mainRouterHolder;
|
||||||
|
if (!router) {
|
||||||
|
throw new Error('mainRouter is not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* メインルータを設定する。一度設定すると、それ以降は変更できない。
|
||||||
|
* {@link setupRouter}から呼び出されることのみを想定している。
|
||||||
|
*/
|
||||||
|
export function setMainRouter(router: IRouter) {
|
||||||
|
if (mainRouterHolder) {
|
||||||
|
throw new Error('mainRouter is already exists.');
|
||||||
|
}
|
||||||
|
|
||||||
|
mainRouterHolder = router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link mainRouter}用のプロキシ実装。
|
||||||
|
* {@link mainRouter}は起動シーケンスの一部にて初期化されるため、僅かにundefinedになる期間がある。
|
||||||
|
* その僅かな期間のためだけに型をundefined込みにしたくないのでこのクラスを緩衝材として使用する。
|
||||||
|
*/
|
||||||
|
class MainRouterProxy implements IRouter {
|
||||||
|
private supplier: () => IRouter;
|
||||||
|
|
||||||
|
constructor(supplier: () => IRouter) {
|
||||||
|
this.supplier = supplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
get current(): Resolved {
|
||||||
|
return this.supplier().current;
|
||||||
|
}
|
||||||
|
|
||||||
|
get currentRef(): ShallowRef<Resolved> {
|
||||||
|
return this.supplier().currentRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
get currentRoute(): ShallowRef<RouteDef> {
|
||||||
|
return this.supplier().currentRoute;
|
||||||
|
}
|
||||||
|
|
||||||
|
get navHook(): ((path: string, flag?: any) => boolean) | null {
|
||||||
|
return this.supplier().navHook;
|
||||||
|
}
|
||||||
|
|
||||||
|
set navHook(value) {
|
||||||
|
this.supplier().navHook = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentKey(): string {
|
||||||
|
return this.supplier().getCurrentKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentPath(): any {
|
||||||
|
return this.supplier().getCurrentPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
push(path: string, flag?: any): void {
|
||||||
|
this.supplier().push(path, flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
replace(path: string, key?: string | null): void {
|
||||||
|
this.supplier().replace(path, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(path: string): Resolved | null {
|
||||||
|
return this.supplier().resolve(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
eventNames(): Array<EventEmitter.EventNames<RouterEvent>> {
|
||||||
|
return this.supplier().eventNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
listeners<T extends EventEmitter.EventNames<RouterEvent>>(
|
||||||
|
event: T,
|
||||||
|
): Array<EventEmitter.EventListener<RouterEvent, T>> {
|
||||||
|
return this.supplier().listeners(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
listenerCount(
|
||||||
|
event: EventEmitter.EventNames<RouterEvent>,
|
||||||
|
): number {
|
||||||
|
return this.supplier().listenerCount(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit<T extends EventEmitter.EventNames<RouterEvent>>(
|
||||||
|
event: T,
|
||||||
|
...args: EventEmitter.EventArgs<RouterEvent, T>
|
||||||
|
): boolean {
|
||||||
|
return this.supplier().emit(event, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
on<T extends EventEmitter.EventNames<RouterEvent>>(
|
||||||
|
event: T,
|
||||||
|
fn: EventEmitter.EventListener<RouterEvent, T>,
|
||||||
|
context?: any,
|
||||||
|
): this {
|
||||||
|
this.supplier().on(event, fn, context);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
addListener<T extends EventEmitter.EventNames<RouterEvent>>(
|
||||||
|
event: T,
|
||||||
|
fn: EventEmitter.EventListener<RouterEvent, T>,
|
||||||
|
context?: any,
|
||||||
|
): this {
|
||||||
|
this.supplier().addListener(event, fn, context);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
once<T extends EventEmitter.EventNames<RouterEvent>>(
|
||||||
|
event: T,
|
||||||
|
fn: EventEmitter.EventListener<RouterEvent, T>,
|
||||||
|
context?: any,
|
||||||
|
): this {
|
||||||
|
this.supplier().once(event, fn, context);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeListener<T extends EventEmitter.EventNames<RouterEvent>>(
|
||||||
|
event: T,
|
||||||
|
fn?: EventEmitter.EventListener<RouterEvent, T>,
|
||||||
|
context?: any,
|
||||||
|
once?: boolean,
|
||||||
|
): this {
|
||||||
|
this.supplier().removeListener(event, fn, context, once);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
off<T extends EventEmitter.EventNames<RouterEvent>>(
|
||||||
|
event: T,
|
||||||
|
fn?: EventEmitter.EventListener<RouterEvent, T>,
|
||||||
|
context?: any,
|
||||||
|
once?: boolean,
|
||||||
|
): this {
|
||||||
|
this.supplier().off(event, fn, context, once);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAllListeners(
|
||||||
|
event?: EventEmitter.EventNames<RouterEvent>,
|
||||||
|
): this {
|
||||||
|
this.supplier().removeAllListeners(event);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mainRouterHolder: IRouter | null = null;
|
||||||
|
|
||||||
|
export const mainRouter: IRouter = new MainRouterProxy(getMainRouter);
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue