Merge tag '2024.3.1-io.4b' into host

This commit is contained in:
まっちゃとーにゅ 2024-03-30 17:31:59 +09:00
commit 1c111aeab7
No known key found for this signature in database
GPG key ID: 6AFBBF529601C1DB
19 changed files with 1154 additions and 1159 deletions

View file

@ -1,12 +1,12 @@
{ {
"name": "misskey", "name": "misskey",
"version": "2024.3.1-host.4a", "version": "2024.3.1-host.4b",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/MisskeyIO/misskey.git" "url": "https://github.com/MisskeyIO/misskey.git"
}, },
"packageManager": "pnpm@8.15.4", "packageManager": "pnpm@8.15.5",
"workspaces": [ "workspaces": [
"packages/frontend", "packages/frontend",
"packages/backend", "packages/backend",
@ -49,20 +49,20 @@
"@tensorflow/tfjs-core": "4.17.0", "@tensorflow/tfjs-core": "4.17.0",
"chokidar": "3.6.0", "chokidar": "3.6.0",
"lodash": "4.17.21", "lodash": "4.17.21",
"sharp": "0.33.2" "sharp": "0.33.3"
}, },
"dependencies": { "dependencies": {
"cssnano": "6.1.1", "cssnano": "6.1.2",
"execa": "8.0.1", "execa": "8.0.1",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"postcss": "8.4.38", "postcss": "8.4.38",
"terser": "5.29.2", "terser": "5.30.0",
"typescript": "5.4.3" "typescript": "5.4.3"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "20.11.30", "@types/node": "20.12.2",
"@typescript-eslint/eslint-plugin": "7.3.1", "@typescript-eslint/eslint-plugin": "7.4.0",
"@typescript-eslint/parser": "7.3.1", "@typescript-eslint/parser": "7.4.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "13.7.1", "cypress": "13.7.1",
"eslint": "8.57.0", "eslint": "8.57.0",

View file

@ -66,12 +66,12 @@
}, },
"dependencies": { "dependencies": {
"@authenio/samlify-node-xmllint": "2.0.0", "@authenio/samlify-node-xmllint": "2.0.0",
"@aws-sdk/client-s3": "3.537.0", "@aws-sdk/client-s3": "3.540.0",
"@aws-sdk/lib-storage": "3.537.0", "@aws-sdk/lib-storage": "3.540.0",
"@bull-board/api": "5.15.1", "@bull-board/api": "5.15.3",
"@bull-board/fastify": "5.15.1", "@bull-board/fastify": "5.15.3",
"@bull-board/ui": "5.15.1", "@bull-board/ui": "5.15.3",
"@discordapp/twemoji": "15.0.2", "@discordapp/twemoji": "15.0.3",
"@fastify/accepts": "4.3.0", "@fastify/accepts": "4.3.0",
"@fastify/cookie": "9.3.1", "@fastify/cookie": "9.3.1",
"@fastify/cors": "9.0.1", "@fastify/cors": "9.0.1",
@ -79,20 +79,20 @@
"@fastify/formbody": "7.4.0", "@fastify/formbody": "7.4.0",
"@fastify/http-proxy": "9.5.0", "@fastify/http-proxy": "9.5.0",
"@fastify/multipart": "8.2.0", "@fastify/multipart": "8.2.0",
"@fastify/static": "7.0.1", "@fastify/static": "7.0.2",
"@fastify/view": "9.0.0", "@fastify/view": "9.0.0",
"@misskey-dev/sharp-read-bmp": "1.2.0", "@misskey-dev/sharp-read-bmp": "1.2.0",
"@misskey-dev/summaly": "5.1.0", "@misskey-dev/summaly": "5.1.0",
"@nestjs/common": "10.3.4", "@nestjs/common": "10.3.7",
"@nestjs/core": "10.3.4", "@nestjs/core": "10.3.7",
"@nestjs/testing": "10.3.4", "@nestjs/testing": "10.3.7",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@simplewebauthn/server": "9.0.3", "@simplewebauthn/server": "9.0.3",
"@sinonjs/fake-timers": "11.2.2", "@sinonjs/fake-timers": "11.2.2",
"@smithy/node-http-handler": "2.5.0", "@smithy/node-http-handler": "2.5.0",
"@swc/cli": "0.1.65", "@swc/cli": "0.1.65",
"@swc/core": "1.3.107", "@swc/core": "1.3.107",
"@twemoji/parser": "15.0.0", "@twemoji/parser": "15.1.0",
"accepts": "1.3.8", "accepts": "1.3.8",
"ajv": "8.12.0", "ajv": "8.12.0",
"archiver": "6.0.1", "archiver": "6.0.1",
@ -100,7 +100,7 @@
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "2.0.5", "blurhash": "2.0.5",
"body-parser": "1.20.2", "body-parser": "1.20.2",
"bullmq": "5.4.4", "bullmq": "5.4.6",
"cacheable-lookup": "7.0.0", "cacheable-lookup": "7.0.0",
"cbor": "9.0.2", "cbor": "9.0.2",
"chalk": "5.3.0", "chalk": "5.3.0",
@ -166,14 +166,14 @@
"ratelimiter": "3.4.1", "ratelimiter": "3.4.1",
"re2": "1.20.10", "re2": "1.20.10",
"redis-lock": "0.1.4", "redis-lock": "0.1.4",
"reflect-metadata": "0.2.1", "reflect-metadata": "0.2.2",
"rename": "1.0.4", "rename": "1.0.4",
"rss-parser": "3.13.0", "rss-parser": "3.13.0",
"rxjs": "7.8.1", "rxjs": "7.8.1",
"samlify": "2.8.11", "samlify": "2.8.11",
"sanitize-html": "2.13.0", "sanitize-html": "2.13.0",
"secure-json-parse": "2.7.0", "secure-json-parse": "2.7.0",
"sharp": "0.33.2", "sharp": "0.33.3",
"slacc": "0.0.10", "slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
@ -194,7 +194,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.4", "@nestjs/platform-express": "10.3.7",
"@simplewebauthn/types": "9.0.1", "@simplewebauthn/types": "9.0.1",
"@swc/jest": "0.2.36", "@swc/jest": "0.2.36",
"@types/accepts": "1.3.7", "@types/accepts": "1.3.7",
@ -213,11 +213,11 @@
"@types/jsrsasign": "10.5.13", "@types/jsrsasign": "10.5.13",
"@types/mime-types": "2.1.4", "@types/mime-types": "2.1.4",
"@types/ms": "0.7.34", "@types/ms": "0.7.34",
"@types/node": "20.11.30", "@types/node": "20.12.2",
"@types/node-forge": "1.3.11", "@types/node-forge": "1.3.11",
"@types/nodemailer": "6.4.14", "@types/nodemailer": "6.4.14",
"@types/oauth": "0.9.4", "@types/oauth": "0.9.4",
"@types/oauth2orize": "1.11.4", "@types/oauth2orize": "1.11.5",
"@types/oauth2orize-pkce": "0.1.2", "@types/oauth2orize-pkce": "0.1.2",
"@types/pg": "8.11.4", "@types/pg": "8.11.4",
"@types/pug": "2.0.10", "@types/pug": "2.0.10",
@ -235,9 +235,9 @@
"@types/vary": "1.1.3", "@types/vary": "1.1.3",
"@types/web-push": "3.6.3", "@types/web-push": "3.6.3",
"@types/ws": "8.5.10", "@types/ws": "8.5.10",
"@typescript-eslint/eslint-plugin": "7.3.1", "@typescript-eslint/eslint-plugin": "7.4.0",
"@typescript-eslint/parser": "7.3.1", "@typescript-eslint/parser": "7.4.0",
"aws-sdk-client-mock": "3.0.1", "aws-sdk-client-mock": "4.0.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "8.57.0", "eslint": "8.57.0",
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.29.1",

View file

@ -4,6 +4,7 @@
*/ */
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
@ -20,6 +21,8 @@ export class DeleteAccountService {
public logger: Logger; public logger: Logger;
constructor( constructor(
@Inject(DI.redis)
private redisClient: Redis.Redis,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -29,7 +32,7 @@ export class DeleteAccountService {
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private loggerService: LoggerService, private loggerService: LoggerService,
) { ) {
this.logger = this.loggerService.getLogger('delete-account'); this.logger = this.loggerService.getLogger('account:delete');
} }
@bindThis @bindThis
@ -39,19 +42,38 @@ export class DeleteAccountService {
const _user = await this.usersRepository.findOneByOrFail({ id: user.id }); const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
if (_user.isRoot) throw new Error('cannot delete a root account'); if (_user.isRoot) throw new Error('cannot delete a root account');
// 物理削除する前にDelete activityを送信する // 5分間の間に同じアカウントに対して削除リクエストが複数回来た場合、最初のリクエストのみを処理する
await this.userSuspendService.doPostSuspend(user).catch(err => this.logger.error(err)); const lock = await this.redisClient.set(`account:delete:lock:${user.id}`, Date.now(), 'EX', 60 * 5, 'NX');
if (lock === null) {
this.logger.warn(`Delete account is already in progress for ${user.id}`);
return;
}
this.queueService.createDeleteAccountJob(user, { // noinspection ES6MissingAwait APIで呼び出される際にタイムアウトされないように
force: me ? await this.roleService.isModerator(me) : false, (async () => {
soft: soft, try {
}); // 物理削除する前にDelete activityを送信する
await this.userSuspendService.doPostSuspend(user).catch(err => this.logger.error(err));
await this.usersRepository.update(user.id, { // noinspection ES6MissingAwait
isDeleted: true, this.queueService.createDeleteAccountJob(user, {
}); force: me ? await this.roleService.isModerator(me) : false,
soft: soft,
});
this.globalEventService.publishInternalEvent('userChangeDeletedState', { id: user.id, isDeleted: true }); await this.usersRepository.update(user.id, {
isDeleted: true,
});
this.globalEventService.publishInternalEvent('userChangeDeletedState', { id: user.id, isDeleted: true });
} catch (err) {
this.logger.error(`Failed to delete account ${user.id}, request by ${me ? me.id : 'remote'} (soft: ${soft})`, { error: err });
// すでにcallstackから離れてるので、ここでエラーをthrowしても意味がない
} finally {
// 成功・失敗に関わらずロックを解除
await this.redisClient.unlink(`account:delete:lock:${user.id}`);
}
})();
} }
@bindThis @bindThis

View file

@ -41,11 +41,12 @@ export class FetchInstanceMetadataService {
private logger: Logger; private logger: Logger;
constructor( constructor(
@Inject(DI.redis)
private redisClient: Redis.Redis,
private httpRequestService: HttpRequestService, private httpRequestService: HttpRequestService,
private loggerService: LoggerService, private loggerService: LoggerService,
private federatedInstanceService: FederatedInstanceService, private federatedInstanceService: FederatedInstanceService,
@Inject(DI.redis)
private redisClient: Redis.Redis,
) { ) {
this.logger = this.loggerService.getLogger('metadata', 'cyan'); this.logger = this.loggerService.getLogger('metadata', 'cyan');
} }

View file

@ -6,27 +6,34 @@
import { generateKeyPair } from 'node:crypto'; import { generateKeyPair } from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import * as Redis from 'ioredis';
import { DataSource, IsNull } from 'typeorm'; import { DataSource, IsNull } from 'typeorm';
import { bindThis } from '@/decorators.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type Logger from '@/logger.js';
import generateUserToken from '@/misc/generate-native-user-token.js';
import type { UsedUsernamesRepository, UsersRepository } from '@/models/_.js'; import type { UsedUsernamesRepository, UsersRepository } from '@/models/_.js';
import { MiUser } from '@/models/User.js'; import { MiUser } from '@/models/User.js';
import { MiUserProfile } from '@/models/UserProfile.js'; import { MiUserProfile } from '@/models/UserProfile.js';
import { IdService } from '@/core/IdService.js';
import { MiUserKeypair } from '@/models/UserKeypair.js'; import { MiUserKeypair } from '@/models/UserKeypair.js';
import { MiUsedUsername } from '@/models/UsedUsername.js'; import { MiUsedUsername } from '@/models/UsedUsername.js';
import generateUserToken from '@/misc/generate-native-user-token.js'; import { IdService } from '@/core/IdService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { InstanceActorService } from '@/core/InstanceActorService.js';
import { bindThis } from '@/decorators.js';
import UsersChart from '@/core/chart/charts/users.js';
import { UtilityService } from '@/core/UtilityService.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { LoggerService } from '@/core/LoggerService.js';
import { InstanceActorService } from '@/core/InstanceActorService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import UsersChart from '@/core/chart/charts/users.js';
@Injectable() @Injectable()
export class SignupService { export class SignupService {
public logger: Logger;
constructor( constructor(
@Inject(DI.db) @Inject(DI.db)
private db: DataSource, private db: DataSource,
@Inject(DI.redis)
private redisClient: Redis.Redis,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -34,13 +41,15 @@ export class SignupService {
@Inject(DI.usedUsernamesRepository) @Inject(DI.usedUsernamesRepository)
private usedUsernamesRepository: UsedUsernamesRepository, private usedUsernamesRepository: UsedUsernamesRepository,
private utilityService: UtilityService,
private userEntityService: UserEntityService,
private idService: IdService, private idService: IdService,
private metaService: MetaService, private metaService: MetaService,
private utilityService: UtilityService,
private loggerService: LoggerService,
private instanceActorService: InstanceActorService, private instanceActorService: InstanceActorService,
private userEntityService: UserEntityService,
private usersChart: UsersChart, private usersChart: UsersChart,
) { ) {
this.logger = this.loggerService.getLogger('account:create');
} }
@bindThis @bindThis
@ -110,47 +119,61 @@ export class SignupService {
err ? rej(err) : res([publicKey, privateKey]), err ? rej(err) : res([publicKey, privateKey]),
)); ));
let account!: MiUser; // 5分間のロックを取得
const lock = await this.redisClient.set(`account:create:lock:${username.toLowerCase()}`, Date.now(), 'EX', 60 * 5, 'NX');
if (lock === null) {
throw new Error('ALREADY_IN_PROGRESS');
}
// Start transaction try {
await this.db.transaction(async transactionalEntityManager => { let account!: MiUser;
const exist = await transactionalEntityManager.findOneBy(MiUser, {
usernameLower: username.toLowerCase(), // Start transaction
host: IsNull(), await this.db.transaction(async transactionalEntityManager => {
const exist = await transactionalEntityManager.findOneBy(MiUser, {
usernameLower: username.toLowerCase(),
host: IsNull(),
});
if (exist) throw new Error(' the username is already used');
account = await transactionalEntityManager.save(new MiUser({
id: this.idService.gen(),
username: username,
usernameLower: username.toLowerCase(),
host: this.utilityService.toPunyNullable(host),
token: secret,
isRoot: isTheFirstUser,
}));
await transactionalEntityManager.save(new MiUserKeypair({
publicKey: keyPair[0],
privateKey: keyPair[1],
userId: account.id,
}));
await transactionalEntityManager.save(new MiUserProfile({
userId: account.id,
autoAcceptFollowed: true,
password: hash,
}));
await transactionalEntityManager.save(new MiUsedUsername({
createdAt: new Date(),
username: username.toLowerCase(),
}));
}); });
if (exist) throw new Error(' the username is already used'); this.usersChart.update(account, true);
account = await transactionalEntityManager.save(new MiUser({ return { account, secret };
id: this.idService.gen(), } catch (err) {
username: username, this.logger.error(`Failed to create account ${username}`, { error: err });
usernameLower: username.toLowerCase(), throw err;
host: this.utilityService.toPunyNullable(host), } finally {
token: secret, // 成功・失敗に関わらずロックを解除
isRoot: isTheFirstUser, await this.redisClient.unlink(`account:create:lock:${username.toLowerCase()}`);
})); }
await transactionalEntityManager.save(new MiUserKeypair({
publicKey: keyPair[0],
privateKey: keyPair[1],
userId: account.id,
}));
await transactionalEntityManager.save(new MiUserProfile({
userId: account.id,
autoAcceptFollowed: true,
password: hash,
}));
await transactionalEntityManager.save(new MiUsedUsername({
createdAt: new Date(),
username: username.toLowerCase(),
}));
});
this.usersChart.update(account, true);
return { account, secret };
} }
} }

View file

@ -37,6 +37,7 @@ export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
userId: { type: 'string', format: 'misskey:id' }, userId: { type: 'string', format: 'misskey:id' },
soft: { type: 'boolean', default: true, description: 'Since deletion by an administrator is a moderation action, the default is to soft delete.' },
}, },
required: ['userId'], required: ['userId'],
} as const; } as const;
@ -56,8 +57,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (user == null) throw new ApiError(meta.errors.userNotFound); if (user == null) throw new ApiError(meta.errors.userNotFound);
if (await this.roleService.isModerator(user)) throw new ApiError(meta.errors.cannotDeleteModerator); if (await this.roleService.isModerator(user)) throw new ApiError(meta.errors.cannotDeleteModerator);
// 管理者からの削除ということはモデレーション行為なので、soft delete にする await this.deleteAccountService.deleteAccount(user, ps.soft, me);
await this.deleteAccountService.deleteAccount(user, true, me);
}); });
} }
} }

View file

@ -36,7 +36,7 @@ html
link(rel='prefetch' href=infoImageUrl) link(rel='prefetch' href=infoImageUrl)
link(rel='prefetch' href=notFoundImageUrl) link(rel='prefetch' href=notFoundImageUrl)
//- https://github.com/misskey-dev/misskey/issues/9842 //- https://github.com/misskey-dev/misskey/issues/9842
link(rel='stylesheet' href=`/assets/tabler-icons.${version}/tabler-icons.min.css`) link(rel='stylesheet' href=`/assets/tabler-icons.${version}/dist/tabler-icons.min.css`)
link(rel='modulepreload' href=`/vite/${clientEntry.file}`) link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
if !config.clientManifestExists if !config.clientManifestExists

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true" as="image" type="image/png" crossorigin="anonymous"> <link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true" as="image" type="image/png" crossorigin="anonymous">
<link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true" as="image" type="image/jpeg" crossorigin="anonymous"> <link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true" as="image" type="image/jpeg" crossorigin="anonymous">
<link rel="stylesheet" href="https://unpkg.com/@tabler/icons-webfont@2.46.0/tabler-icons.min.css"> <link rel="stylesheet" href="https://unpkg.com/@tabler/icons-webfont@3.1.0/dist/tabler-icons.min.css">
<link rel="stylesheet" href="https://unpkg.com/@fontsource/m-plus-rounded-1c/index.css"> <link rel="stylesheet" href="https://unpkg.com/@fontsource/m-plus-rounded-1c/index.css">
<style> <style>
html { html {

View file

@ -17,7 +17,7 @@
"lint": "pnpm typecheck && pnpm eslint" "lint": "pnpm typecheck && pnpm eslint"
}, },
"dependencies": { "dependencies": {
"@discordapp/twemoji": "15.0.2", "@discordapp/twemoji": "15.0.3",
"@github/webauthn-json": "2.1.1", "@github/webauthn-json": "2.1.1",
"@isaacs/ttlcache": "1.4.1", "@isaacs/ttlcache": "1.4.1",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
@ -27,8 +27,8 @@
"@rollup/plugin-typescript": "11.1.6", "@rollup/plugin-typescript": "11.1.6",
"@rollup/pluginutils": "5.1.0", "@rollup/pluginutils": "5.1.0",
"@syuilo/aiscript": "0.17.0", "@syuilo/aiscript": "0.17.0",
"@tabler/icons-webfont": "2.47.0", "@tabler/icons-webfont": "3.1.0",
"@twemoji/parser": "15.0.0", "@twemoji/parser": "15.1.0",
"@vitejs/plugin-vue": "5.0.4", "@vitejs/plugin-vue": "5.0.4",
"@vue/compiler-sfc": "3.4.15", "@vue/compiler-sfc": "3.4.15",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.2", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.2",
@ -41,7 +41,7 @@
"chartjs-chart-matrix": "2.0.1", "chartjs-chart-matrix": "2.0.1",
"chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.0.1", "chartjs-plugin-zoom": "2.0.1",
"chromatic": "11.2.0", "chromatic": "11.3.0",
"compare-versions": "6.1.0", "compare-versions": "6.1.0",
"cropperjs": "2.0.0-beta.4", "cropperjs": "2.0.0-beta.4",
"date-fns": "3.6.0", "date-fns": "3.6.0",
@ -59,13 +59,13 @@
"misskey-reversi": "workspace:*", "misskey-reversi": "workspace:*",
"photoswipe": "5.4.3", "photoswipe": "5.4.3",
"punycode": "2.3.1", "punycode": "2.3.1",
"rollup": "4.13.0", "rollup": "4.13.2",
"sanitize-html": "2.13.0", "sanitize-html": "2.13.0",
"sass": "1.72.0", "sass": "1.72.0",
"shiki": "1.2.0", "shiki": "1.2.1",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.162.0", "three": "0.163.0",
"throttle-debounce": "5.0.0", "throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tsc-alias": "1.8.8", "tsc-alias": "1.8.8",
@ -73,45 +73,45 @@
"typescript": "5.4.3", "typescript": "5.4.3",
"uuid": "9.0.1", "uuid": "9.0.1",
"v-code-diff": "1.11.0", "v-code-diff": "1.11.0",
"vite": "5.2.2", "vite": "5.2.7",
"vue": "3.4.15", "vue": "3.4.15",
"vuedraggable": "next" "vuedraggable": "next"
}, },
"devDependencies": { "devDependencies": {
"@misskey-dev/eslint-plugin": "1.0.0", "@misskey-dev/eslint-plugin": "1.0.0",
"@misskey-dev/summaly": "5.1.0", "@misskey-dev/summaly": "5.1.0",
"@storybook/addon-actions": "8.0.4", "@storybook/addon-actions": "8.0.5",
"@storybook/addon-essentials": "8.0.4", "@storybook/addon-essentials": "8.0.5",
"@storybook/addon-interactions": "8.0.4", "@storybook/addon-interactions": "8.0.5",
"@storybook/addon-links": "8.0.4", "@storybook/addon-links": "8.0.5",
"@storybook/addon-mdx-gfm": "8.0.4", "@storybook/addon-mdx-gfm": "8.0.5",
"@storybook/addon-storysource": "8.0.4", "@storybook/addon-storysource": "8.0.5",
"@storybook/blocks": "8.0.4", "@storybook/blocks": "8.0.5",
"@storybook/components": "8.0.4", "@storybook/components": "8.0.5",
"@storybook/core-events": "8.0.4", "@storybook/core-events": "8.0.5",
"@storybook/manager-api": "8.0.4", "@storybook/manager-api": "8.0.5",
"@storybook/preview-api": "8.0.4", "@storybook/preview-api": "8.0.5",
"@storybook/react": "8.0.4", "@storybook/react": "8.0.5",
"@storybook/react-vite": "8.0.4", "@storybook/react-vite": "8.0.5",
"@storybook/test": "8.0.4", "@storybook/test": "8.0.5",
"@storybook/theming": "8.0.4", "@storybook/theming": "8.0.5",
"@storybook/types": "8.0.4", "@storybook/types": "8.0.5",
"@storybook/vue3": "8.0.4", "@storybook/vue3": "8.0.5",
"@storybook/vue3-vite": "8.0.4", "@storybook/vue3-vite": "8.0.5",
"@testing-library/vue": "8.0.3", "@testing-library/vue": "8.0.3",
"@types/escape-regexp": "0.0.3", "@types/escape-regexp": "0.0.3",
"@types/estree": "1.0.5", "@types/estree": "1.0.5",
"@types/matter-js": "0.19.6", "@types/matter-js": "0.19.6",
"@types/micromatch": "4.0.6", "@types/micromatch": "4.0.6",
"@types/node": "20.11.30", "@types/node": "20.12.2",
"@types/punycode": "2.1.4", "@types/punycode": "2.1.4",
"@types/sanitize-html": "2.11.0", "@types/sanitize-html": "2.11.0",
"@types/throttle-debounce": "5.0.2", "@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6", "@types/tinycolor2": "1.4.6",
"@types/uuid": "9.0.8", "@types/uuid": "9.0.8",
"@types/ws": "8.5.10", "@types/ws": "8.5.10",
"@typescript-eslint/eslint-plugin": "7.3.1", "@typescript-eslint/eslint-plugin": "7.4.0",
"@typescript-eslint/parser": "7.3.1", "@typescript-eslint/parser": "7.4.0",
"@vitest/coverage-v8": "0.34.6", "@vitest/coverage-v8": "0.34.6",
"@vue/runtime-core": "3.4.15", "@vue/runtime-core": "3.4.15",
"acorn": "8.11.3", "acorn": "8.11.3",
@ -119,19 +119,19 @@
"cypress": "13.7.1", "cypress": "13.7.1",
"eslint": "8.57.0", "eslint": "8.57.0",
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.29.1",
"eslint-plugin-vue": "9.23.0", "eslint-plugin-vue": "9.24.0",
"fast-glob": "3.3.2", "fast-glob": "3.3.2",
"happy-dom": "13.6.2", "happy-dom": "13.6.2",
"intersection-observer": "0.12.2", "intersection-observer": "0.12.2",
"micromatch": "4.0.5", "micromatch": "4.0.5",
"msw": "2.2.9", "msw": "2.2.13",
"msw-storybook-addon": "2.0.0-beta.1", "msw-storybook-addon": "2.0.0-beta.1",
"nodemon": "3.1.0", "nodemon": "3.1.0",
"prettier": "3.2.5", "prettier": "3.2.5",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"start-server-and-test": "2.0.3", "start-server-and-test": "2.0.3",
"storybook": "8.0.4", "storybook": "8.0.5",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"vite-plugin-turbosnap": "1.0.3", "vite-plugin-turbosnap": "1.0.3",
"vitest": "0.34.6", "vitest": "0.34.6",

View file

@ -6,7 +6,7 @@
// devモードで起動される際index.htmlを使うときはrouterが暴発してしまってうまく読み込めない。 // devモードで起動される際index.htmlを使うときはrouterが暴発してしまってうまく読み込めない。
// よって、devモードとして起動されるときはビルド時に組み込む形としておく。 // よって、devモードとして起動されるときはビルド時に組み込む形としておく。
// (pnpm start時はpugファイルの中で静的リソースとして読み込むようになっており、この問題は起こっていない) // (pnpm start時はpugファイルの中で静的リソースとして読み込むようになっており、この問題は起こっていない)
import '@tabler/icons-webfont/tabler-icons.scss'; import '@tabler/icons-webfont/dist/tabler-icons.scss';
await main(); await main();

View file

@ -58,11 +58,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<FormSection> <FormSection>
<div class="_gaps"> <div class="_gaps">
<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch> <MkFolder v-if="iAmModerator" defaultOpen>
<template #icon><i class="ti ti-shield"></i></template>
<div> <template #label>{{ i18n.ts.moderation }}</template>
<MkButton v-if="user.host == null" inline style="margin-right: 8px;" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton> <div class="_gaps">
</div> <MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
<MkButton v-if="user.host == null" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton>
<MkButton inline danger @click="unsetUserAvatar"><i class="ti ti-user-circle"></i> {{ i18n.ts.unsetUserAvatar }}</MkButton>
<MkButton inline danger @click="unsetUserBanner"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserBanner }}</MkButton>
</div>
</MkFolder>
<MkFolder> <MkFolder>
<template #icon><i class="ti ti-license"></i></template> <template #icon><i class="ti ti-license"></i></template>
@ -87,11 +92,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
</MkFolder> </MkFolder>
<div class="_buttons"> <MkFolder v-if="$i.isAdmin">
<MkButton v-if="iAmModerator" inline danger @click="unsetUserAvatar"><i class="ti ti-user-circle"></i> {{ i18n.ts.unsetUserAvatar }}</MkButton> <template #icon><i class="ti ti-user-x"></i></template>
<MkButton v-if="iAmModerator" inline danger @click="unsetUserBanner"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserBanner }}</MkButton> <template #label>{{ i18n.ts.deleteAccount }}</template>
</div> <div class="_gaps">
<MkButton v-if="$i.isAdmin" inline danger @click="deleteAccount">{{ i18n.ts.deleteAccount }}</MkButton> <MkButton inline danger @click="deleteAccount(true)"><i class="ti ti-user-x"></i> {{ i18n.ts.deleteAccount }}</MkButton>
<MkButton inline danger @click="deleteAccount(false)"><i class="ti ti-file-shredder"></i> {{ i18n.ts.deleteAccount }} ({{ i18n.ts.all }})</MkButton>
</div>
</MkFolder>
</div> </div>
</FormSection> </FormSection>
</div> </div>
@ -380,7 +388,7 @@ async function deleteAllFiles() {
} }
} }
async function deleteAccount() { async function deleteAccount(soft: boolean) {
const confirm = await os.confirm({ const confirm = await os.confirm({
type: 'warning', type: 'warning',
text: i18n.ts.deleteAccountConfirm, text: i18n.ts.deleteAccountConfirm,
@ -395,6 +403,7 @@ async function deleteAccount() {
if (typed.result === user.value?.username) { if (typed.result === user.value?.username) {
await os.apiWithDialog('admin/accounts/delete', { await os.apiWithDialog('admin/accounts/delete', {
userId: user.value.id, userId: user.value.id,
soft,
}).then(refreshUser); }).then(refreshUser);
} else { } else {
os.alert({ os.alert({

View file

@ -20,13 +20,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #default="{ items: users }"> <template #default="{ items: users }">
<MkDateSeparatedList v-slot="{ item }" :items="toMisskeyEntity(users)" :noGap="true"> <MkDateSeparatedList v-slot="{ item }" :items="toMisskeyEntity(users)" :noGap="true">
<div v-if="item.user" :key="item.id" style="display: flex; gap: 8px; padding-right: 16px"> <div v-if="item.user" :key="item.id" style="display: grid; grid-template-columns: auto 56px; grid-column-gap: 8px;">
<MkA :to="userPage(item.user)" style="flex-grow: 1;"> <MkA :to="userPage(item.user)" style="overflow: hidden;">
<MkUserCardMini :user="item.user" :withChart="false" style="background: inherit; border-radius: unset;"/> <MkUserCardMini :user="item.user" :withChart="false" style="text-overflow: ellipsis; background: inherit; border-radius: unset;"/>
</MkA> </MkA>
<button v-tooltip.noDelay="i18n.ts.note" class="_button" :class="$style.post" @click="os.post({initialText: `@${item.user.username}${item.user.host ? `@${item.user.host}` : ''} `})"> <div style="display: flex; margin-right: 16px;">
<i class="ti-fw ti ti-confetti" :class="$style.postIcon"></i> <button v-tooltip.noDelay="i18n.ts.note" class="_button" :class="$style.post" @click="os.post({initialText: `@${item.user.username}${item.user.host ? `@${item.user.host}` : ''} `})">
</button> <i class="ti-fw ti ti-confetti" :class="$style.postIcon"></i>
</button>
</div>
</div> </div>
</MkDateSeparatedList> </MkDateSeparatedList>
</template> </template>

View file

@ -26,10 +26,10 @@
"devDependencies": { "devDependencies": {
"@misskey-dev/eslint-plugin": "1.0.0", "@misskey-dev/eslint-plugin": "1.0.0",
"@types/matter-js": "0.19.6", "@types/matter-js": "0.19.6",
"@types/node": "20.11.30", "@types/node": "20.12.2",
"@types/seedrandom": "3.0.8", "@types/seedrandom": "3.0.8",
"@typescript-eslint/eslint-plugin": "7.3.1", "@typescript-eslint/eslint-plugin": "7.4.0",
"@typescript-eslint/parser": "7.3.1", "@typescript-eslint/parser": "7.4.0",
"eslint": "8.57.0", "eslint": "8.57.0",
"nodemon": "3.1.0", "nodemon": "3.1.0",
"typescript": "5.4.3" "typescript": "5.4.3"
@ -40,7 +40,7 @@
"dependencies": { "dependencies": {
"esbuild": "0.20.2", "esbuild": "0.20.2",
"eventemitter3": "5.0.1", "eventemitter3": "5.0.1",
"glob": "^10.3.10", "glob": "^10.3.12",
"matter-js": "0.19.0", "matter-js": "0.19.0",
"seedrandom": "3.0.5" "seedrandom": "3.0.5"
} }

View file

@ -9,9 +9,9 @@
"devDependencies": { "devDependencies": {
"@misskey-dev/eslint-plugin": "^1.0.0", "@misskey-dev/eslint-plugin": "^1.0.0",
"@readme/openapi-parser": "2.5.0", "@readme/openapi-parser": "2.5.0",
"@types/node": "20.11.30", "@types/node": "20.12.2",
"@typescript-eslint/eslint-plugin": "7.3.1", "@typescript-eslint/eslint-plugin": "7.4.0",
"@typescript-eslint/parser": "7.3.1", "@typescript-eslint/parser": "7.4.0",
"eslint": "8.57.0", "eslint": "8.57.0",
"openapi-types": "12.1.3", "openapi-types": "12.1.3",
"openapi-typescript": "6.7.5", "openapi-typescript": "6.7.5",

View file

@ -1,7 +1,7 @@
{ {
"type": "module", "type": "module",
"name": "misskey-js", "name": "misskey-js",
"version": "2024.3.1-host.4a", "version": "2024.3.1-host.4b",
"description": "Misskey SDK for JavaScript", "description": "Misskey SDK for JavaScript",
"types": "./built/dts/index.d.ts", "types": "./built/dts/index.d.ts",
"exports": { "exports": {
@ -39,9 +39,9 @@
"@misskey-dev/eslint-plugin": "1.0.0", "@misskey-dev/eslint-plugin": "1.0.0",
"@swc/jest": "0.2.36", "@swc/jest": "0.2.36",
"@types/jest": "29.5.12", "@types/jest": "29.5.12",
"@types/node": "20.11.30", "@types/node": "20.12.2",
"@typescript-eslint/eslint-plugin": "7.3.1", "@typescript-eslint/eslint-plugin": "7.4.0",
"@typescript-eslint/parser": "7.3.1", "@typescript-eslint/parser": "7.4.0",
"eslint": "8.57.0", "eslint": "8.57.0",
"jest": "29.7.0", "jest": "29.7.0",
"jest-fetch-mock": "3.0.3", "jest-fetch-mock": "3.0.3",

View file

@ -5423,6 +5423,11 @@ export type operations = {
'application/json': { 'application/json': {
/** Format: misskey:id */ /** Format: misskey:id */
userId: string; userId: string;
/**
* @description Since deletion by an administrator is a moderation action, the default is to soft delete.
* @default true
*/
soft?: boolean;
}; };
}; };
}; };

View file

@ -25,9 +25,9 @@
}, },
"devDependencies": { "devDependencies": {
"@misskey-dev/eslint-plugin": "1.0.0", "@misskey-dev/eslint-plugin": "1.0.0",
"@types/node": "20.11.30", "@types/node": "20.12.2",
"@typescript-eslint/eslint-plugin": "7.3.1", "@typescript-eslint/eslint-plugin": "7.4.0",
"@typescript-eslint/parser": "7.3.1", "@typescript-eslint/parser": "7.4.0",
"eslint": "8.57.0", "eslint": "8.57.0",
"nodemon": "3.1.0", "nodemon": "3.1.0",
"typescript": "5.4.3" "typescript": "5.4.3"
@ -35,7 +35,7 @@
"dependencies": { "dependencies": {
"crc-32": "1.2.2", "crc-32": "1.2.2",
"esbuild": "0.20.2", "esbuild": "0.20.2",
"glob": "10.3.10" "glob": "10.3.12"
}, },
"files": [ "files": [
"built" "built"

View file

@ -16,7 +16,7 @@
"devDependencies": { "devDependencies": {
"@misskey-dev/eslint-plugin": "1.0.0", "@misskey-dev/eslint-plugin": "1.0.0",
"@types/serviceworker": "0.0.84", "@types/serviceworker": "0.0.84",
"@typescript-eslint/parser": "7.3.1", "@typescript-eslint/parser": "7.4.0",
"eslint": "8.57.0", "eslint": "8.57.0",
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.29.1",
"nodemon": "3.1.0", "nodemon": "3.1.0",

1937
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff