};
type ObjectSchemaTypeDef =
p['ref'] extends keyof typeof refs ? Packed
:
@@ -232,6 +235,12 @@ export type SchemaTypeDef
=
p['items']['allOf'] extends ReadonlyArray ? UnionToIntersection>>[] :
never
) :
+ p['prefixItems'] extends ReadonlyArray ? (
+ p['items'] extends NonNullable ? [...ArrayToTuple, ...SchemaType
[]] :
+ p['items'] extends false ? ArrayToTuple
:
+ p['unevaluatedItems'] extends false ? ArrayToTuple
:
+ [...ArrayToTuple
, ...unknown[]]
+ ) :
p['items'] extends NonNullable ? SchemaType[] :
any[]
) :
diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts
index df88b99636..87d8c16cb3 100644
--- a/packages/backend/src/models/Notification.ts
+++ b/packages/backend/src/models/Notification.ts
@@ -85,7 +85,7 @@ export type MiNotification = {
/**
* アプリ通知のbody
*/
- customBody: string | null;
+ customBody: string;
/**
* アプリ通知のheader
diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts
index 3bcf9cac92..99feeaa7d7 100644
--- a/packages/backend/src/models/json-schema/meta.ts
+++ b/packages/backend/src/models/json-schema/meta.ts
@@ -253,6 +253,10 @@ export const packedMetaLiteSchema = {
optional: false, nullable: false,
default: 'local',
},
+ maxFileSize: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
},
} as const;
diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts
index b4c4442758..b05ec8b762 100644
--- a/packages/backend/src/models/json-schema/notification.ts
+++ b/packages/backend/src/models/json-schema/notification.ts
@@ -3,6 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
+import { ACHIEVEMENT_TYPES } from '@/core/AchievementService.js';
import { notificationTypes } from '@/types.js';
const baseSchema = {
@@ -294,6 +295,7 @@ export const packedNotificationSchema = {
achievement: {
type: 'string',
optional: false, nullable: false,
+ enum: ACHIEVEMENT_TYPES,
},
},
}, {
@@ -311,11 +313,11 @@ export const packedNotificationSchema = {
},
header: {
type: 'string',
- optional: false, nullable: false,
+ optional: false, nullable: true,
},
icon: {
type: 'string',
- optional: false, nullable: false,
+ optional: false, nullable: true,
},
},
}, {
diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts
index d665945861..4076e9da90 100644
--- a/packages/backend/src/queue/processors/DeliverProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts
@@ -45,7 +45,7 @@ export class DeliverProcessorService {
private queueLoggerService: QueueLoggerService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('deliver');
- this.suspendedHostsCache = new MemorySingleCache(1000 * 60 * 60);
+ this.suspendedHostsCache = new MemorySingleCache(1000 * 60 * 60); // 1h
}
@bindThis
diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts
index cc18997fdc..9a641007ee 100644
--- a/packages/backend/src/server/NodeinfoServerService.ts
+++ b/packages/backend/src/server/NodeinfoServerService.ts
@@ -134,7 +134,7 @@ export class NodeinfoServerService {
return document;
};
- const cache = new MemorySingleCache>>(1000 * 60 * 10);
+ const cache = new MemorySingleCache>>(1000 * 60 * 10); // 10m
fastify.get(nodeinfo2_1path, async (request, reply) => {
const base = await cache.fetch(() => nodeinfo2(21));
diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts
index 47f64f6609..f95c272757 100644
--- a/packages/backend/src/server/api/ApiCallService.ts
+++ b/packages/backend/src/server/api/ApiCallService.ts
@@ -199,9 +199,18 @@ export class ApiCallService implements OnApplicationShutdown {
return;
}
- const [path] = await createTemp();
+ const [path, cleanup] = await createTemp();
await stream.pipeline(multipartData.file, fs.createWriteStream(path));
+ // ファイルサイズが制限を超えていた場合
+ // なお truncated はストリームを読み切ってからでないと機能しないため、stream.pipeline より後にある必要がある
+ if (multipartData.file.truncated) {
+ cleanup();
+ reply.code(413);
+ reply.send();
+ return;
+ }
+
const fields = {} as Record;
for (const [k, v] of Object.entries(multipartData.fields)) {
fields[k] = typeof v === 'object' && 'value' in v ? v.value : undefined;
diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts
index 4a5935f930..13cbdfc3be 100644
--- a/packages/backend/src/server/api/ApiServerService.ts
+++ b/packages/backend/src/server/api/ApiServerService.ts
@@ -49,7 +49,7 @@ export class ApiServerService {
fastify.register(multipart, {
limits: {
- fileSize: this.config.maxFileSize ?? 262144000,
+ fileSize: this.config.maxFileSize,
files: 1,
},
});
diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts
index ddef8db987..690ff2e022 100644
--- a/packages/backend/src/server/api/AuthenticateService.ts
+++ b/packages/backend/src/server/api/AuthenticateService.ts
@@ -37,7 +37,7 @@ export class AuthenticateService implements OnApplicationShutdown {
private cacheService: CacheService,
) {
- this.appCache = new MemoryKVCache(Infinity);
+ this.appCache = new MemoryKVCache(1000 * 60 * 60 * 24 * 7); // 1w
}
@bindThis
diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts
index 4074e416b8..01dea703a3 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts
@@ -7,9 +7,9 @@ import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UsersRepository } from '@/models/_.js';
import { QueueService } from '@/core/QueueService.js';
-import { UserSuspendService } from '@/core/UserSuspendService.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { DeleteAccountService } from '@/core/DeleteAccountService.js';
export const meta = {
tags: ['admin'],
@@ -33,9 +33,7 @@ export default class extends Endpoint { // eslint-
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
- private userEntityService: UserEntityService,
- private queueService: QueueService,
- private userSuspendService: UserSuspendService,
+ private deleteAccoountService: DeleteAccountService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
@@ -48,22 +46,7 @@ export default class extends Endpoint { // eslint-
throw new Error('cannot delete a root account');
}
- if (this.userEntityService.isLocalUser(user)) {
- // 物理削除する前にDelete activityを送信する
- await this.userSuspendService.doPostSuspend(user).catch(err => {});
-
- this.queueService.createDeleteAccountJob(user, {
- soft: false,
- });
- } else {
- this.queueService.createDeleteAccountJob(user, {
- soft: true, // リモートユーザーの削除は、完全にDBから物理削除してしまうと再度連合してきてアカウントが復活する可能性があるため、soft指定する
- });
- }
-
- await this.usersRepository.update(user.id, {
- isDeleted: true,
- });
+ await this.deleteAccoountService.deleteAccount(user);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
index 7a3410ffa7..f3e440b4cb 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
@@ -21,16 +21,15 @@ export const meta = {
items: {
type: 'array',
optional: false, nullable: false,
- items: {
- anyOf: [
- {
- type: 'string',
- },
- {
- type: 'number',
- },
- ],
- },
+ prefixItems: [
+ {
+ type: 'string',
+ },
+ {
+ type: 'number',
+ },
+ ],
+ unevaluatedItems: false,
},
example: [[
'example.com',
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
index 305ae1af1d..e7589cba81 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
@@ -21,16 +21,15 @@ export const meta = {
items: {
type: 'array',
optional: false, nullable: false,
- items: {
- anyOf: [
- {
- type: 'string',
- },
- {
- type: 'number',
- },
- ],
- },
+ prefixItems: [
+ {
+ type: 'string',
+ },
+ {
+ type: 'number',
+ },
+ ],
+ unevaluatedItems: false,
},
example: [[
'example.com',
diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update-default-policies.ts b/packages/backend/src/server/api/endpoints/admin/roles/update-default-policies.ts
index d7209965db..5cf49670be 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/update-default-policies.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/update-default-policies.ts
@@ -7,6 +7,7 @@ import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { MetaService } from '@/core/MetaService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin', 'role'],
@@ -33,12 +34,22 @@ export default class extends Endpoint { // eslint-
constructor(
private metaService: MetaService,
private globalEventService: GlobalEventService,
+ private moderationLogService: ModerationLogService,
) {
- super(meta, paramDef, async (ps) => {
+ super(meta, paramDef, async (ps, me) => {
+ const before = await this.metaService.fetch(true);
+
await this.metaService.update({
policies: ps.policies,
});
- this.globalEventService.publishInternalEvent('policiesUpdated', ps.policies);
+
+ const after = await this.metaService.fetch(true);
+
+ this.globalEventService.publishInternalEvent('policiesUpdated', after.policies);
+ this.moderationLogService.log(me, 'updateServerSettings', {
+ before: before.policies,
+ after: after.policies,
+ });
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
index 8a946405cc..bea1bdc4ed 100644
--- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
@@ -3,18 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { IsNull, Not } from 'typeorm';
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { UsersRepository, FollowingsRepository } from '@/models/_.js';
-import type { MiUser } from '@/models/User.js';
-import type { RelationshipJobData } from '@/queue/types.js';
-import { ModerationLogService } from '@/core/ModerationLogService.js';
+import type { UsersRepository } from '@/models/_.js';
import { UserSuspendService } from '@/core/UserSuspendService.js';
import { DI } from '@/di-symbols.js';
-import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
-import { QueueService } from '@/core/QueueService.js';
export const meta = {
tags: ['admin'],
@@ -38,13 +32,8 @@ export default class extends Endpoint { // eslint-
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
- @Inject(DI.followingsRepository)
- private followingsRepository: FollowingsRepository,
-
private userSuspendService: UserSuspendService,
private roleService: RoleService,
- private moderationLogService: ModerationLogService,
- private queueService: QueueService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
@@ -57,42 +46,7 @@ export default class extends Endpoint { // eslint-
throw new Error('cannot suspend moderator account');
}
- await this.usersRepository.update(user.id, {
- isSuspended: true,
- });
-
- this.moderationLogService.log(me, 'suspend', {
- userId: user.id,
- userUsername: user.username,
- userHost: user.host,
- });
-
- (async () => {
- await this.userSuspendService.doPostSuspend(user).catch(e => {});
- await this.unFollowAll(user).catch(e => {});
- })();
+ await this.userSuspendService.suspend(user, me);
});
}
-
- @bindThis
- private async unFollowAll(follower: MiUser) {
- const followings = await this.followingsRepository.find({
- where: {
- followerId: follower.id,
- followeeId: Not(IsNull()),
- },
- });
-
- const jobs: RelationshipJobData[] = [];
- for (const following of followings) {
- if (following.followeeId && following.followerId) {
- jobs.push({
- from: { id: following.followerId },
- to: { id: following.followeeId },
- silent: true,
- });
- }
- }
- this.queueService.createUnfollowJob(jobs);
- }
}
diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts
index 2c2b1bf6f5..b52c638cdb 100644
--- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts
@@ -6,7 +6,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UsersRepository } from '@/models/_.js';
-import { ModerationLogService } from '@/core/ModerationLogService.js';
import { UserSuspendService } from '@/core/UserSuspendService.js';
import { DI } from '@/di-symbols.js';
@@ -33,7 +32,6 @@ export default class extends Endpoint { // eslint-
private usersRepository: UsersRepository,
private userSuspendService: UserSuspendService,
- private moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
@@ -42,17 +40,7 @@ export default class extends Endpoint { // eslint-
throw new Error('user not found');
}
- await this.usersRepository.update(user.id, {
- isSuspended: false,
- });
-
- this.moderationLogService.log(me, 'unsuspend', {
- userId: user.id,
- userUsername: user.username,
- userHost: user.host,
- });
-
- this.userSuspendService.doPostUnsuspend(user);
+ await this.userSuspendService.unsuspend(user, me);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/flash/delete.ts b/packages/backend/src/server/api/endpoints/flash/delete.ts
index d3d47e5deb..6912450abf 100644
--- a/packages/backend/src/server/api/endpoints/flash/delete.ts
+++ b/packages/backend/src/server/api/endpoints/flash/delete.ts
@@ -4,9 +4,11 @@
*/
import { Inject, Injectable } from '@nestjs/common';
-import type { FlashsRepository } from '@/models/_.js';
+import type { FlashsRepository, UsersRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { RoleService } from '@/core/RoleService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -44,17 +46,35 @@ export default class extends Endpoint { // eslint-
constructor(
@Inject(DI.flashsRepository)
private flashsRepository: FlashsRepository,
+
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
+
+ private moderationLogService: ModerationLogService,
+ private roleService: RoleService,
) {
super(meta, paramDef, async (ps, me) => {
const flash = await this.flashsRepository.findOneBy({ id: ps.flashId });
+
if (flash == null) {
throw new ApiError(meta.errors.noSuchFlash);
}
- if (flash.userId !== me.id) {
+
+ if (!await this.roleService.isModerator(me) && flash.userId !== me.id) {
throw new ApiError(meta.errors.accessDenied);
}
await this.flashsRepository.delete(flash.id);
+
+ if (flash.userId !== me.id) {
+ const user = await this.usersRepository.findOneByOrFail({ id: flash.userId });
+ this.moderationLogService.log(me, 'deleteFlash', {
+ flashId: flash.id,
+ flashUserId: flash.userId,
+ flashUserUsername: user.username,
+ flash,
+ });
+ }
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts
index 527e3fb52d..b6b94db161 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts
@@ -5,8 +5,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { GalleryPostsRepository } from '@/models/_.js';
+import type { GalleryPostsRepository, UsersRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { RoleService } from '@/core/RoleService.js';
import { ApiError } from '../../../error.js';
export const meta = {
@@ -22,6 +24,12 @@ export const meta = {
code: 'NO_SUCH_POST',
id: 'ae52f367-4bd7-4ecd-afc6-5672fff427f5',
},
+
+ accessDenied: {
+ message: 'Access denied.',
+ code: 'ACCESS_DENIED',
+ id: 'c86e09de-1c48-43ac-a435-1c7e42ed4496',
+ },
},
} as const;
@@ -38,18 +46,35 @@ export default class extends Endpoint { // eslint-
constructor(
@Inject(DI.galleryPostsRepository)
private galleryPostsRepository: GalleryPostsRepository,
+
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
+
+ private moderationLogService: ModerationLogService,
+ private roleService: RoleService,
) {
super(meta, paramDef, async (ps, me) => {
- const post = await this.galleryPostsRepository.findOneBy({
- id: ps.postId,
- userId: me.id,
- });
+ const post = await this.galleryPostsRepository.findOneBy({ id: ps.postId });
if (post == null) {
throw new ApiError(meta.errors.noSuchPost);
}
+ if (!await this.roleService.isModerator(me) && post.userId !== me.id) {
+ throw new ApiError(meta.errors.accessDenied);
+ }
+
await this.galleryPostsRepository.delete(post.id);
+
+ if (post.userId !== me.id) {
+ const user = await this.usersRepository.findOneByOrFail({ id: post.userId });
+ this.moderationLogService.log(me, 'deleteGalleryPost', {
+ postId: post.id,
+ postUserId: post.userId,
+ postUserUsername: user.username,
+ post,
+ });
+ }
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts
index aa2ba75a41..f2bc946788 100644
--- a/packages/backend/src/server/api/endpoints/pages/delete.ts
+++ b/packages/backend/src/server/api/endpoints/pages/delete.ts
@@ -4,9 +4,11 @@
*/
import { Inject, Injectable } from '@nestjs/common';
-import type { PagesRepository } from '@/models/_.js';
+import type { PagesRepository, UsersRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { RoleService } from '@/core/RoleService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -44,17 +46,35 @@ export default class extends Endpoint { // eslint-
constructor(
@Inject(DI.pagesRepository)
private pagesRepository: PagesRepository,
+
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
+
+ private moderationLogService: ModerationLogService,
+ private roleService: RoleService,
) {
super(meta, paramDef, async (ps, me) => {
const page = await this.pagesRepository.findOneBy({ id: ps.pageId });
+
if (page == null) {
throw new ApiError(meta.errors.noSuchPage);
}
- if (page.userId !== me.id) {
+
+ if (!await this.roleService.isModerator(me) && page.userId !== me.id) {
throw new ApiError(meta.errors.accessDenied);
}
await this.pagesRepository.delete(page.id);
+
+ if (page.userId !== me.id) {
+ const user = await this.usersRepository.findOneByOrFail({ id: page.userId });
+ this.moderationLogService.log(me, 'deletePage', {
+ pageId: page.id,
+ pageUserId: page.userId,
+ pageUserUsername: user.username,
+ page,
+ });
+ }
});
}
}
diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts
index 7773150b74..0fb5238c78 100644
--- a/packages/backend/src/server/api/stream/Connection.ts
+++ b/packages/backend/src/server/api/stream/Connection.ts
@@ -20,6 +20,8 @@ import type { ChannelsService } from './ChannelsService.js';
import type { EventEmitter } from 'events';
import type Channel from './channel.js';
+const MAX_CHANNELS_PER_CONNECTION = 32;
+
/**
* Main stream connection
*/
@@ -255,6 +257,10 @@ export default class Connection {
*/
@bindThis
public connectChannel(id: string, params: JsonObject | undefined, channel: string, pong = false) {
+ if (this.channels.length >= MAX_CHANNELS_PER_CONNECTION) {
+ return;
+ }
+
const channelService = this.channelsService.getChannelService(channel);
if (channelService.requireCredential && this.user == null) {
diff --git a/packages/backend/src/server/api/stream/channels/reversi-game.ts b/packages/backend/src/server/api/stream/channels/reversi-game.ts
index c6f4a4ae3b..7597a1cfa3 100644
--- a/packages/backend/src/server/api/stream/channels/reversi-game.ts
+++ b/packages/backend/src/server/api/stream/channels/reversi-game.ts
@@ -12,6 +12,7 @@ import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityServi
import { isJsonObject } from '@/misc/json-value.js';
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
+import { reversiUpdateKeys } from 'misskey-js';
class ReversiGameChannel extends Channel {
public readonly chName = 'reversiGame';
@@ -46,8 +47,9 @@ class ReversiGameChannel extends Channel {
break;
case 'updateSettings':
if (!isJsonObject(body)) return;
- if (typeof body.key !== 'string') return;
- if (!isJsonObject(body.value)) return;
+ if (!this.reversiService.isValidReversiUpdateKey(body.key)) return;
+ if (!this.reversiService.isValidReversiUpdateValue(body.key, body.value)) return;
+
this.updateSettings(body.key, body.value);
break;
case 'cancel':
@@ -64,7 +66,7 @@ class ReversiGameChannel extends Channel {
}
@bindThis
- private async updateSettings(key: string, value: JsonObject) {
+ private async updateSettings(key: K, value: MiReversiGame[K]) {
if (this.user == null) return;
this.reversiService.updateSettings(this.gameId!, this.user, key, value);
diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js
index 4275dc9527..5283596316 100644
--- a/packages/backend/src/server/web/boot.js
+++ b/packages/backend/src/server/web/boot.js
@@ -166,7 +166,7 @@
if (!errorsElement) {
document.body.innerHTML = `
-