Merge remote-tracking branch 'misskey-original/develop' into develop

# Conflicts:
#	package.json
#	packages/frontend/src/components/MkEmojiEditDialog.vue
#	packages/frontend/src/components/MkMenu.vue
#	packages/frontend/src/components/MkNote.vue
#	packages/frontend/src/pages/timeline.vue
This commit is contained in:
mattyatea 2024-01-23 12:38:56 +09:00
commit 78a34d3de3
115 changed files with 4837 additions and 2932 deletions

View file

@ -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なカラムは通常取ってこないので
});
}
}

View file

@ -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:

View file

@ -5,25 +5,20 @@
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import CRC32 from 'crc-32';
import { ModuleRef } from '@nestjs/core';
import * as Reversi from 'misskey-reversi';
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 +53,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
@ -66,6 +61,33 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
await this.redisClient.del(`reversi:game:cache:${gameId}`);
}
@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) {
@ -81,23 +103,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 +130,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 +150,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 +178,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 +191,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', {
@ -253,6 +219,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;
@ -262,63 +254,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 engine = new Reversi.Game(game.map, {
isLlotheo: game.isLlotheo,
canPutEverywhere: game.canPutEverywhere,
loopedBoard: game.loopedBoard,
});
const map = game.map != null ? game.map : getRandomMap();
const crc32 = CRC32.str(JSON.stringify(game.logs)).toString();
const crc32 = engine.calcCrc32().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,
});
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 +300,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 +353,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 +392,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 = {
@ -421,19 +405,13 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
const serializeLogs = Reversi.Serializer.serializeLogs(logs);
const crc32 = CRC32.str(JSON.stringify(serializeLogs)).toString();
const crc32 = engine.calcCrc32().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 +420,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 +444,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 +468,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 +491,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);
@ -561,7 +535,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
if (game == null) throw new Error('game not found');
if (crc32.toString() !== game.crc32) {
return await this.reversiGameEntityService.packDetail(game);
return game;
} else {
return null;
}

View file

@ -185,9 +185,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;

View file

@ -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 {

View file

@ -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)));
}
}