summaryrefslogtreecommitdiff
path: root/packages/backend
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend')
-rw-r--r--packages/backend/package.json4
-rw-r--r--packages/backend/src/core/AntennaService.ts12
-rw-r--r--packages/backend/src/core/MetaService.ts5
-rw-r--r--packages/backend/src/core/ReversiService.ts325
-rw-r--r--packages/backend/src/core/RoleService.ts4
-rw-r--r--packages/backend/src/core/WebhookService.ts9
-rw-r--r--packages/backend/src/core/entities/ReversiGameEntityService.ts35
-rw-r--r--packages/backend/src/models/json-schema/reversi-game.ts20
-rw-r--r--packages/backend/src/server/api/endpoints/reversi/games.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/reversi/match.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/reversi/show-game.ts2
-rw-r--r--packages/backend/src/server/api/stream/channels/reversi-game.ts8
-rw-r--r--packages/backend/src/server/web/ClientServerService.ts26
-rw-r--r--packages/backend/src/server/web/views/reversi-game.pug20
-rw-r--r--packages/backend/src/types.ts6
15 files changed, 253 insertions, 231 deletions
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 2064d7f2eb..4ca4f85c5e 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -8,7 +8,7 @@
},
"scripts": {
"start": "node ./built/boot/entry.js",
- "start:test": "NODE_ENV=test node ./built/boot/entry.js",
+ "start:test": "cross-env NODE_ENV=test node ./built/boot/entry.js",
"migrate": "pnpm typeorm migration:run -d ormconfig.js",
"revert": "pnpm typeorm migration:revert -d ormconfig.js",
"check:connect": "node ./check_connect.js",
@@ -31,7 +31,7 @@
"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
"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": "pnpm build && node ./generate_api_json.js"
},
"optionalDependencies": {
"@swc/core-android-arm64": "1.3.11",
diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts
index 2c27a02559..2959153070 100644
--- a/packages/backend/src/core/AntennaService.ts
+++ b/packages/backend/src/core/AntennaService.ts
@@ -55,23 +55,29 @@ export class AntennaService implements OnApplicationShutdown {
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
switch (type) {
case 'antennaCreated':
- this.antennas.push({
+ this.antennas.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
...body,
lastUsedAt: new Date(body.lastUsedAt),
+ user: null, // joinなカラムは通常取ってこないので
+ userList: null, // joinなカラムは通常取ってこないので
});
break;
case 'antennaUpdated': {
const idx = this.antennas.findIndex(a => a.id === body.id);
if (idx >= 0) {
- this.antennas[idx] = {
+ this.antennas[idx] = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
...body,
lastUsedAt: new Date(body.lastUsedAt),
+ user: null, // joinなカラムは通常取ってこないので
+ userList: null, // joinなカラムは通常取ってこないので
};
} else {
// サーバ起動時にactiveじゃなかった場合、リストに持っていないので追加する必要あり
- this.antennas.push({
+ this.antennas.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
...body,
lastUsedAt: new Date(body.lastUsedAt),
+ user: null, // joinなカラムは通常取ってこないので
+ userList: null, // joinなカラムは通常取ってこないので
});
}
}
diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts
index 80e8020961..8e945e3d89 100644
--- a/packages/backend/src/core/MetaService.ts
+++ b/packages/backend/src/core/MetaService.ts
@@ -51,7 +51,10 @@ export class MetaService implements OnApplicationShutdown {
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
switch (type) {
case 'metaUpdated': {
- this.cache = body;
+ this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
+ ...body,
+ proxyAccount: null, // joinなカラムは通常取ってこないので
+ };
break;
}
default:
diff --git a/packages/backend/src/core/ReversiService.ts b/packages/backend/src/core/ReversiService.ts
index f97f71eb43..0d5f989c11 100644
--- a/packages/backend/src/core/ReversiService.ts
+++ b/packages/backend/src/core/ReversiService.ts
@@ -12,18 +12,14 @@ import { IsNull } from 'typeorm';
import type {
MiReversiGame,
ReversiGamesRepository,
- UsersRepository,
} from '@/models/_.js';
import type { MiUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
-import { MetaService } from '@/core/MetaService.js';
import { CacheService } from '@/core/CacheService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import type { GlobalEvents } from '@/core/GlobalEventService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { IdService } from '@/core/IdService.js';
-import type { Packed } from '@/misc/json-schema.js';
import { NotificationService } from '@/core/NotificationService.js';
import { Serialized } from '@/types.js';
import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js';
@@ -58,7 +54,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
@bindThis
private async cacheGame(game: MiReversiGame) {
- await this.redisClient.setex(`reversi:game:cache:${game.id}`, 60 * 3, JSON.stringify(game));
+ await this.redisClient.setex(`reversi:game:cache:${game.id}`, 60 * 60, JSON.stringify(game));
}
@bindThis
@@ -67,6 +63,33 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
}
@bindThis
+ private getBakeProps(game: MiReversiGame) {
+ return {
+ startedAt: game.startedAt,
+ endedAt: game.endedAt,
+ // ゲームの途中からユーザーが変わることは無いので
+ //user1Id: game.user1Id,
+ //user2Id: game.user2Id,
+ user1Ready: game.user1Ready,
+ user2Ready: game.user2Ready,
+ black: game.black,
+ isStarted: game.isStarted,
+ isEnded: game.isEnded,
+ winnerId: game.winnerId,
+ surrenderedUserId: game.surrenderedUserId,
+ timeoutUserId: game.timeoutUserId,
+ isLlotheo: game.isLlotheo,
+ canPutEverywhere: game.canPutEverywhere,
+ loopedBoard: game.loopedBoard,
+ timeLimitForEachTurn: game.timeLimitForEachTurn,
+ logs: game.logs,
+ map: game.map,
+ bw: game.bw,
+ crc32: game.crc32,
+ } satisfies Partial<MiReversiGame>;
+ }
+
+ @bindThis
public async matchSpecificUser(me: MiUser, targetUser: MiUser): Promise<MiReversiGame | null> {
if (targetUser.id === me.id) {
throw new Error('You cannot match yourself.');
@@ -81,23 +104,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
if (invitations.includes(targetUser.id)) {
await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, targetUser.id);
- const game = await this.reversiGamesRepository.insert({
- id: this.idService.gen(),
- user1Id: targetUser.id,
- user2Id: me.id,
- user1Ready: false,
- user2Ready: false,
- isStarted: false,
- isEnded: false,
- logs: [],
- map: Reversi.maps.eighteight.data,
- bw: 'random',
- isLlotheo: false,
- }).then(x => this.reversiGamesRepository.findOneByOrFail(x.identifiers[0]));
- this.cacheGame(game);
-
- const packed = await this.reversiGameEntityService.packDetail(game, { id: targetUser.id });
- this.globalEventService.publishReversiStream(targetUser.id, 'matched', { game: packed });
+ const game = await this.matched(targetUser.id, me.id);
return game;
} else {
@@ -124,23 +131,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
const invitorId = invitations[Math.floor(Math.random() * invitations.length)];
await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, invitorId);
- const game = await this.reversiGamesRepository.insert({
- id: this.idService.gen(),
- user1Id: invitorId,
- user2Id: me.id,
- user1Ready: false,
- user2Ready: false,
- isStarted: false,
- isEnded: false,
- logs: [],
- map: Reversi.maps.eighteight.data,
- bw: 'random',
- isLlotheo: false,
- }).then(x => this.reversiGamesRepository.findOneByOrFail(x.identifiers[0]));
- this.cacheGame(game);
-
- const packed = await this.reversiGameEntityService.packDetail(game, { id: invitorId });
- this.globalEventService.publishReversiStream(invitorId, 'matched', { game: packed });
+ const game = await this.matched(invitorId, me.id);
return game;
}
@@ -160,23 +151,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
await this.redisClient.zrem('reversi:matchAny', me.id, matchedUserId);
- const game = await this.reversiGamesRepository.insert({
- id: this.idService.gen(),
- user1Id: matchedUserId,
- user2Id: me.id,
- user1Ready: false,
- user2Ready: false,
- isStarted: false,
- isEnded: false,
- logs: [],
- map: Reversi.maps.eighteight.data,
- bw: 'random',
- isLlotheo: false,
- }).then(x => this.reversiGamesRepository.findOneByOrFail(x.identifiers[0]));
- this.cacheGame(game);
-
- const packed = await this.reversiGameEntityService.packDetail(game, { id: matchedUserId });
- this.globalEventService.publishReversiStream(matchedUserId, 'matched', { game: packed });
+ const game = await this.matched(matchedUserId, me.id);
return game;
} else {
@@ -204,14 +179,10 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
let isBothReady = false;
if (game.user1Id === user.id) {
- const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
- .set({
- user1Ready: ready,
- })
- .where('id = :id', { id: game.id })
- .returning('*')
- .execute()
- .then((response) => response.raw[0]);
+ const updatedGame = {
+ ...game,
+ user1Ready: ready,
+ };
this.cacheGame(updatedGame);
this.globalEventService.publishReversiGameStream(game.id, 'changeReadyStates', {
@@ -221,14 +192,10 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
if (ready && updatedGame.user2Ready) isBothReady = true;
} else if (game.user2Id === user.id) {
- const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
- .set({
- user2Ready: ready,
- })
- .where('id = :id', { id: game.id })
- .returning('*')
- .execute()
- .then((response) => response.raw[0]);
+ const updatedGame = {
+ ...game,
+ user2Ready: ready,
+ };
this.cacheGame(updatedGame);
this.globalEventService.publishReversiGameStream(game.id, 'changeReadyStates', {
@@ -254,6 +221,32 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
}
@bindThis
+ private async matched(parentId: MiUser['id'], childId: MiUser['id']): Promise<MiReversiGame> {
+ const game = await this.reversiGamesRepository.insert({
+ id: this.idService.gen(),
+ user1Id: parentId,
+ user2Id: childId,
+ user1Ready: false,
+ user2Ready: false,
+ isStarted: false,
+ isEnded: false,
+ logs: [],
+ map: Reversi.maps.eighteight.data,
+ bw: 'random',
+ isLlotheo: false,
+ }).then(x => this.reversiGamesRepository.findOneOrFail({
+ where: { id: x.identifiers[0].id },
+ relations: ['user1', 'user2'],
+ }));
+ this.cacheGame(game);
+
+ const packed = await this.reversiGameEntityService.packDetail(game);
+ this.globalEventService.publishReversiStream(parentId, 'matched', { game: packed });
+
+ return game;
+ }
+
+ @bindThis
private async startGame(game: MiReversiGame) {
let bw: number;
if (game.bw === 'random') {
@@ -262,63 +255,44 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
bw = parseInt(game.bw, 10);
}
- function getRandomMap() {
- const mapCount = Object.entries(Reversi.maps).length;
- const rnd = Math.floor(Math.random() * mapCount);
- return Object.values(Reversi.maps)[rnd].data;
- }
-
- const map = game.map != null ? game.map : getRandomMap();
-
const crc32 = CRC32.str(JSON.stringify(game.logs)).toString();
const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
.set({
+ ...this.getBakeProps(game),
startedAt: new Date(),
isStarted: true,
black: bw,
- map: map,
+ map: game.map,
crc32,
})
.where('id = :id', { id: game.id })
.returning('*')
.execute()
.then((response) => response.raw[0]);
+ // キャッシュ効率化のためにユーザー情報は再利用
+ updatedGame.user1 = game.user1;
+ updatedGame.user2 = game.user2;
this.cacheGame(updatedGame);
//#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理
- const engine = new Reversi.Game(map, {
- isLlotheo: game.isLlotheo,
- canPutEverywhere: game.canPutEverywhere,
- loopedBoard: game.loopedBoard,
+ const engine = new Reversi.Game(updatedGame.map, {
+ isLlotheo: updatedGame.isLlotheo,
+ canPutEverywhere: updatedGame.canPutEverywhere,
+ loopedBoard: updatedGame.loopedBoard,
});
if (engine.isEnded) {
- let winner;
+ let winnerId;
if (engine.winner === true) {
- winner = bw === 1 ? game.user1Id : game.user2Id;
+ winnerId = bw === 1 ? updatedGame.user1Id : updatedGame.user2Id;
} else if (engine.winner === false) {
- winner = bw === 1 ? game.user2Id : game.user1Id;
+ winnerId = bw === 1 ? updatedGame.user2Id : updatedGame.user1Id;
} else {
- winner = null;
+ winnerId = null;
}
- const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
- .set({
- isEnded: true,
- endedAt: new Date(),
- winnerId: winner,
- })
- .where('id = :id', { id: game.id })
- .returning('*')
- .execute()
- .then((response) => response.raw[0]);
- this.cacheGame(updatedGame);
-
- this.globalEventService.publishReversiGameStream(game.id, 'ended', {
- winnerId: winner,
- game: await this.reversiGameEntityService.packDetail(game.id),
- });
+ await this.endGame(updatedGame, winnerId, null);
return;
}
@@ -327,7 +301,33 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
this.redisClient.setex(`reversi:game:turnTimer:${game.id}:1`, updatedGame.timeLimitForEachTurn, '');
this.globalEventService.publishReversiGameStream(game.id, 'started', {
- game: await this.reversiGameEntityService.packDetail(game.id),
+ game: await this.reversiGameEntityService.packDetail(updatedGame),
+ });
+ }
+
+ @bindThis
+ private async endGame(game: MiReversiGame, winnerId: MiUser['id'] | null, reason: 'surrender' | 'timeout' | null) {
+ const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
+ .set({
+ ...this.getBakeProps(game),
+ isEnded: true,
+ endedAt: new Date(),
+ winnerId: winnerId,
+ surrenderedUserId: reason === 'surrender' ? (winnerId === game.user1Id ? game.user2Id : game.user1Id) : null,
+ timeoutUserId: reason === 'timeout' ? (winnerId === game.user1Id ? game.user2Id : game.user1Id) : null,
+ })
+ .where('id = :id', { id: game.id })
+ .returning('*')
+ .execute()
+ .then((response) => response.raw[0]);
+ // キャッシュ効率化のためにユーザー情報は再利用
+ updatedGame.user1 = game.user1;
+ updatedGame.user2 = game.user2;
+ this.cacheGame(updatedGame);
+
+ this.globalEventService.publishReversiGameStream(game.id, 'ended', {
+ winnerId: winnerId,
+ game: await this.reversiGameEntityService.packDetail(updatedGame),
});
}
@@ -354,14 +354,10 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
// TODO: より厳格なバリデーション
- const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
- .set({
- [key]: value,
- })
- .where('id = :id', { id: game.id })
- .returning('*')
- .execute()
- .then((response) => response.raw[0]);
+ const updatedGame = {
+ ...game,
+ [key]: value,
+ };
this.cacheGame(updatedGame);
this.globalEventService.publishReversiGameStream(game.id, 'updateSettings', {
@@ -397,17 +393,6 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
engine.putStone(pos);
- let winner;
- if (engine.isEnded) {
- if (engine.winner === true) {
- winner = game.black === 1 ? game.user1Id : game.user2Id;
- } else if (engine.winner === false) {
- winner = game.black === 1 ? game.user2Id : game.user1Id;
- } else {
- winner = null;
- }
- }
-
const logs = Reversi.Serializer.deserializeLogs(game.logs);
const log = {
@@ -423,17 +408,11 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
const crc32 = CRC32.str(JSON.stringify(serializeLogs)).toString();
- const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
- .set({
- crc32,
- isEnded: engine.isEnded,
- winnerId: winner,
- logs: serializeLogs,
- })
- .where('id = :id', { id: game.id })
- .returning('*')
- .execute()
- .then((response) => response.raw[0]);
+ const updatedGame = {
+ ...game,
+ crc32,
+ logs: serializeLogs,
+ };
this.cacheGame(updatedGame);
this.globalEventService.publishReversiGameStream(game.id, 'log', {
@@ -442,10 +421,16 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
});
if (engine.isEnded) {
- this.globalEventService.publishReversiGameStream(game.id, 'ended', {
- winnerId: winner ?? null,
- game: await this.reversiGameEntityService.packDetail(game.id),
- });
+ let winnerId;
+ if (engine.winner === true) {
+ winnerId = game.black === 1 ? game.user1Id : game.user2Id;
+ } else if (engine.winner === false) {
+ winnerId = game.black === 1 ? game.user2Id : game.user1Id;
+ } else {
+ winnerId = null;
+ }
+
+ await this.endGame(updatedGame, winnerId, null);
} else {
this.redisClient.setex(`reversi:game:turnTimer:${game.id}:${engine.turn ? '1' : '0'}`, updatedGame.timeLimitForEachTurn, '');
}
@@ -460,23 +445,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
const winnerId = game.user1Id === user.id ? game.user2Id : game.user1Id;
- const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
- .set({
- isEnded: true,
- endedAt: new Date(),
- winnerId: winnerId,
- surrenderedUserId: user.id,
- })
- .where('id = :id', { id: game.id })
- .returning('*')
- .execute()
- .then((response) => response.raw[0]);
- this.cacheGame(updatedGame);
-
- this.globalEventService.publishReversiGameStream(game.id, 'ended', {
- winnerId: winnerId,
- game: await this.reversiGameEntityService.packDetail(game.id),
- });
+ await this.endGame(game, winnerId, 'surrender');
}
@bindThis
@@ -500,23 +469,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
if (timer === 0) {
const winnerId = engine.turn ? (game.black === 1 ? game.user2Id : game.user1Id) : (game.black === 1 ? game.user1Id : game.user2Id);
- const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
- .set({
- isEnded: true,
- endedAt: new Date(),
- winnerId: winnerId,
- timeoutUserId: engine.turn ? (game.black === 1 ? game.user1Id : game.user2Id) : (game.black === 1 ? game.user2Id : game.user1Id),
- })
- .where('id = :id', { id: game.id })
- .returning('*')
- .execute()
- .then((response) => response.raw[0]);
- this.cacheGame(updatedGame);
-
- this.globalEventService.publishReversiGameStream(game.id, 'ended', {
- winnerId: winnerId,
- game: await this.reversiGameEntityService.packDetail(game.id),
- });
+ await this.endGame(game, winnerId, 'timeout');
}
}
@@ -539,14 +492,36 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
public async get(id: MiReversiGame['id']): Promise<MiReversiGame | null> {
const cached = await this.redisClient.get(`reversi:game:cache:${id}`);
if (cached != null) {
+ // TODO: この辺りのデシリアライズ処理をどこか別のサービスに切り出したい
const parsed = JSON.parse(cached) as Serialized<MiReversiGame>;
return {
...parsed,
startedAt: parsed.startedAt != null ? new Date(parsed.startedAt) : null,
endedAt: parsed.endedAt != null ? new Date(parsed.endedAt) : null,
+ user1: parsed.user1 != null ? {
+ ...parsed.user1,
+ avatar: null,
+ banner: null,
+ updatedAt: parsed.user1.updatedAt != null ? new Date(parsed.user1.updatedAt) : null,
+ lastActiveDate: parsed.user1.lastActiveDate != null ? new Date(parsed.user1.lastActiveDate) : null,
+ lastFetchedAt: parsed.user1.lastFetchedAt != null ? new Date(parsed.user1.lastFetchedAt) : null,
+ movedAt: parsed.user1.movedAt != null ? new Date(parsed.user1.movedAt) : null,
+ } : null,
+ user2: parsed.user2 != null ? {
+ ...parsed.user2,
+ avatar: null,
+ banner: null,
+ updatedAt: parsed.user2.updatedAt != null ? new Date(parsed.user2.updatedAt) : null,
+ lastActiveDate: parsed.user2.lastActiveDate != null ? new Date(parsed.user2.lastActiveDate) : null,
+ lastFetchedAt: parsed.user2.lastFetchedAt != null ? new Date(parsed.user2.lastFetchedAt) : null,
+ movedAt: parsed.user2.movedAt != null ? new Date(parsed.user2.movedAt) : null,
+ } : null,
};
} else {
- const game = await this.reversiGamesRepository.findOneBy({ id });
+ const game = await this.reversiGamesRepository.findOne({
+ where: { id },
+ relations: ['user1', 'user2'],
+ });
if (game == null) return null;
this.cacheGame(game);
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index dcd9d7399f..c37b24e3fa 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -181,9 +181,11 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
case 'userRoleAssigned': {
const cached = this.roleAssignmentByUserIdCache.get(body.userId);
if (cached) {
- cached.push({
+ cached.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
...body,
expiresAt: body.expiresAt ? new Date(body.expiresAt) : null,
+ user: null, // joinなカラムは通常取ってこないので
+ role: null, // joinなカラムは通常取ってこないので
});
}
break;
diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts
index 930e6ef64a..5937bf89b1 100644
--- a/packages/backend/src/core/WebhookService.ts
+++ b/packages/backend/src/core/WebhookService.ts
@@ -49,9 +49,10 @@ export class WebhookService implements OnApplicationShutdown {
switch (type) {
case 'webhookCreated':
if (body.active) {
- this.webhooks.push({
+ this.webhooks.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
...body,
latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
+ user: null, // joinなカラムは通常取ってこないので
});
}
break;
@@ -59,14 +60,16 @@ export class WebhookService implements OnApplicationShutdown {
if (body.active) {
const i = this.webhooks.findIndex(a => a.id === body.id);
if (i > -1) {
- this.webhooks[i] = {
+ this.webhooks[i] = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
...body,
latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
+ user: null, // joinなカラムは通常取ってこないので
};
} else {
- this.webhooks.push({
+ this.webhooks.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
...body,
latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
+ user: null, // joinなカラムは通常取ってこないので
});
}
} else {
diff --git a/packages/backend/src/core/entities/ReversiGameEntityService.ts b/packages/backend/src/core/entities/ReversiGameEntityService.ts
index bcb0fd5a6f..6c89a70599 100644
--- a/packages/backend/src/core/entities/ReversiGameEntityService.ts
+++ b/packages/backend/src/core/entities/ReversiGameEntityService.ts
@@ -9,7 +9,6 @@ import type { ReversiGamesRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
import type { } from '@/models/Blocking.js';
-import type { MiUser } from '@/models/User.js';
import type { MiReversiGame } from '@/models/ReversiGame.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
@@ -29,10 +28,14 @@ export class ReversiGameEntityService {
@bindThis
public async packDetail(
src: MiReversiGame['id'] | MiReversiGame,
- me?: { id: MiUser['id'] } | null | undefined,
): Promise<Packed<'ReversiGameDetailed'>> {
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
+ const users = await Promise.all([
+ this.userEntityService.pack(game.user1 ?? game.user1Id),
+ this.userEntityService.pack(game.user2 ?? game.user2Id),
+ ]);
+
return await awaitAll({
id: game.id,
createdAt: this.idService.parse(game.id).date.toISOString(),
@@ -46,10 +49,10 @@ export class ReversiGameEntityService {
user2Ready: game.user2Ready,
user1Id: game.user1Id,
user2Id: game.user2Id,
- user1: this.userEntityService.pack(game.user1Id, me),
- user2: this.userEntityService.pack(game.user2Id, me),
+ user1: users[0],
+ user2: users[1],
winnerId: game.winnerId,
- winner: game.winnerId ? this.userEntityService.pack(game.winnerId, me) : null,
+ winner: game.winnerId ? users.find(u => u.id === game.winnerId)! : null,
surrenderedUserId: game.surrenderedUserId,
timeoutUserId: game.timeoutUserId,
black: game.black,
@@ -66,18 +69,21 @@ export class ReversiGameEntityService {
@bindThis
public packDetailMany(
xs: MiReversiGame[],
- me?: { id: MiUser['id'] } | null | undefined,
) {
- return Promise.all(xs.map(x => this.packDetail(x, me)));
+ return Promise.all(xs.map(x => this.packDetail(x)));
}
@bindThis
public async packLite(
src: MiReversiGame['id'] | MiReversiGame,
- me?: { id: MiUser['id'] } | null | undefined,
): Promise<Packed<'ReversiGameLite'>> {
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
+ const users = await Promise.all([
+ this.userEntityService.pack(game.user1 ?? game.user1Id),
+ this.userEntityService.pack(game.user2 ?? game.user2Id),
+ ]);
+
return await awaitAll({
id: game.id,
createdAt: this.idService.parse(game.id).date.toISOString(),
@@ -85,16 +91,12 @@ export class ReversiGameEntityService {
endedAt: game.endedAt && game.endedAt.toISOString(),
isStarted: game.isStarted,
isEnded: game.isEnded,
- form1: game.form1,
- form2: game.form2,
- user1Ready: game.user1Ready,
- user2Ready: game.user2Ready,
user1Id: game.user1Id,
user2Id: game.user2Id,
- user1: this.userEntityService.pack(game.user1Id, me),
- user2: this.userEntityService.pack(game.user2Id, me),
+ user1: users[0],
+ user2: users[1],
winnerId: game.winnerId,
- winner: game.winnerId ? this.userEntityService.pack(game.winnerId, me) : null,
+ winner: game.winnerId ? users.find(u => u.id === game.winnerId)! : null,
surrenderedUserId: game.surrenderedUserId,
timeoutUserId: game.timeoutUserId,
black: game.black,
@@ -109,9 +111,8 @@ export class ReversiGameEntityService {
@bindThis
public packLiteMany(
xs: MiReversiGame[],
- me?: { id: MiUser['id'] } | null | undefined,
) {
- return Promise.all(xs.map(x => this.packLite(x, me)));
+ return Promise.all(xs.map(x => this.packLite(x)));
}
}
diff --git a/packages/backend/src/models/json-schema/reversi-game.ts b/packages/backend/src/models/json-schema/reversi-game.ts
index 4ac4d165d8..f8a5e7451c 100644
--- a/packages/backend/src/models/json-schema/reversi-game.ts
+++ b/packages/backend/src/models/json-schema/reversi-game.ts
@@ -34,22 +34,6 @@ export const packedReversiGameLiteSchema = {
type: 'boolean',
optional: false, nullable: false,
},
- form1: {
- type: 'any',
- optional: false, nullable: true,
- },
- form2: {
- type: 'any',
- optional: false, nullable: true,
- },
- user1Ready: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- user2Ready: {
- type: 'boolean',
- optional: false, nullable: false,
- },
user1Id: {
type: 'string',
optional: false, nullable: false,
@@ -149,11 +133,11 @@ export const packedReversiGameDetailedSchema = {
optional: false, nullable: false,
},
form1: {
- type: 'any',
+ type: 'object',
optional: false, nullable: true,
},
form2: {
- type: 'any',
+ type: 'object',
optional: false, nullable: true,
},
user1Ready: {
diff --git a/packages/backend/src/server/api/endpoints/reversi/games.ts b/packages/backend/src/server/api/endpoints/reversi/games.ts
index 5322cd0987..f28fe5d987 100644
--- a/packages/backend/src/server/api/endpoints/reversi/games.ts
+++ b/packages/backend/src/server/api/endpoints/reversi/games.ts
@@ -43,7 +43,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.reversiGamesRepository.createQueryBuilder('game'), ps.sinceId, ps.untilId)
- .andWhere('game.isStarted = TRUE');
+ .andWhere('game.isStarted = TRUE')
+ .innerJoinAndSelect('game.user1', 'user1')
+ .innerJoinAndSelect('game.user2', 'user2');
if (ps.my && me) {
query.andWhere(new Brackets(qb => {
@@ -55,7 +57,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const games = await query.take(ps.limit).getMany();
- return await this.reversiGameEntityService.packLiteMany(games, me);
+ return await this.reversiGameEntityService.packLiteMany(games);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/reversi/match.ts b/packages/backend/src/server/api/endpoints/reversi/match.ts
index da5a3409ef..1065ce5a89 100644
--- a/packages/backend/src/server/api/endpoints/reversi/match.ts
+++ b/packages/backend/src/server/api/endpoints/reversi/match.ts
@@ -60,7 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (game == null) return;
- return await this.reversiGameEntityService.packDetail(game, me);
+ return await this.reversiGameEntityService.packDetail(game);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/reversi/show-game.ts b/packages/backend/src/server/api/endpoints/reversi/show-game.ts
index de571053e1..86645ea4b4 100644
--- a/packages/backend/src/server/api/endpoints/reversi/show-game.ts
+++ b/packages/backend/src/server/api/endpoints/reversi/show-game.ts
@@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.noSuchGame);
}
- return await this.reversiGameEntityService.packDetail(game, me);
+ return await this.reversiGameEntityService.packDetail(game);
});
}
}
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 df92137f51..820c80006b 100644
--- a/packages/backend/src/server/api/stream/channels/reversi-game.ts
+++ b/packages/backend/src/server/api/stream/channels/reversi-game.ts
@@ -42,7 +42,7 @@ class ReversiGameChannel extends Channel {
case 'updateSettings': this.updateSettings(body.key, body.value); break;
case 'cancel': this.cancelGame(); break;
case 'putStone': this.putStone(body.pos, body.id); break;
- case 'checkState': this.checkState(body.crc32); break;
+ case 'resync': this.resync(body.crc32); break;
case 'claimTimeIsUp': this.claimTimeIsUp(); break;
}
}
@@ -76,12 +76,10 @@ class ReversiGameChannel extends Channel {
}
@bindThis
- private async checkState(crc32: string | number) {
- if (crc32 != null) return;
-
+ private async resync(crc32: string | number) {
const game = await this.reversiService.checkCrc(this.gameId!, crc32);
if (game) {
- this.send('rescue', game);
+ this.send('resynced', game);
}
}
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index 500ad523e9..9fdfafb737 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -31,12 +31,13 @@ import { PageEntityService } from '@/core/entities/PageEntityService.js';
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
-import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, MiMeta, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, MiMeta, NotesRepository, PagesRepository, ReversiGamesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
import type Logger from '@/logger.js';
import { deepClone } from '@/misc/clone.js';
import { bindThis } from '@/decorators.js';
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
import { RoleService } from '@/core/RoleService.js';
+import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
import { FeedService } from './FeedService.js';
import { UrlPreviewService } from './UrlPreviewService.js';
import { ClientLoggerService } from './ClientLoggerService.js';
@@ -83,6 +84,9 @@ export class ClientServerService {
@Inject(DI.flashsRepository)
private flashsRepository: FlashsRepository,
+ @Inject(DI.reversiGamesRepository)
+ private reversiGamesRepository: ReversiGamesRepository,
+
private flashEntityService: FlashEntityService,
private userEntityService: UserEntityService,
private noteEntityService: NoteEntityService,
@@ -90,6 +94,7 @@ export class ClientServerService {
private galleryPostEntityService: GalleryPostEntityService,
private clipEntityService: ClipEntityService,
private channelEntityService: ChannelEntityService,
+ private reversiGameEntityService: ReversiGameEntityService,
private metaService: MetaService,
private urlPreviewService: UrlPreviewService,
private feedService: FeedService,
@@ -704,6 +709,25 @@ export class ClientServerService {
return await renderBase(reply);
}
});
+
+ // Reversi game
+ fastify.get<{ Params: { game: string; } }>('/reversi/g/:game', async (request, reply) => {
+ const game = await this.reversiGamesRepository.findOneBy({
+ id: request.params.game,
+ });
+
+ if (game) {
+ const _game = await this.reversiGameEntityService.packDetail(game);
+ const meta = await this.metaService.fetch();
+ reply.header('Cache-Control', 'public, max-age=3600');
+ return await reply.view('reversi-game', {
+ game: _game,
+ ...this.generateCommonPugData(meta),
+ });
+ } else {
+ return await renderBase(reply);
+ }
+ });
//#endregion
fastify.get('/_info_card_', async (request, reply) => {
diff --git a/packages/backend/src/server/web/views/reversi-game.pug b/packages/backend/src/server/web/views/reversi-game.pug
new file mode 100644
index 0000000000..0b5ffb2bb0
--- /dev/null
+++ b/packages/backend/src/server/web/views/reversi-game.pug
@@ -0,0 +1,20 @@
+extends ./base
+
+block vars
+ - const user1 = game.user1;
+ - const user2 = game.user2;
+ - const title = `${user1.username} vs ${user2.username}`;
+ - const url = `${config.url}/reversi/g/${game.id}`;
+
+block title
+ = `${title} | ${instanceName}`
+
+block desc
+ meta(name='description' content='⚫⚪Misskey Reversi⚪⚫')
+
+block og
+ meta(property='og:type' content='article')
+ meta(property='og:title' content= title)
+ meta(property='og:description' content='⚫⚪Misskey Reversi⚪⚫')
+ meta(property='og:url' content= url)
+ meta(property='twitter:card' content='summary')
diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts
index e55952f296..8ff791ef90 100644
--- a/packages/backend/src/types.ts
+++ b/packages/backend/src/types.ts
@@ -283,7 +283,11 @@ export type Serialized<T> = {
? (string | null)
: T[K] extends Record<string, any>
? Serialized<T[K]>
- : T[K];
+ : T[K] extends (Record<string, any> | null)
+ ? (Serialized<T[K]> | null)
+ : T[K] extends (Record<string, any> | undefined)
+ ? (Serialized<T[K]> | undefined)
+ : T[K];
};
export type FilterUnionByProperty<