diff --git a/Makefile b/Makefile index c3b48f211..56d2b49b1 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,6 @@ DATA_DUMP_PATH=docker/dump LOG_DATA_PATH=docker/logs DB_PASSWORD_PATH=keys/pom_db_password REDIS_SETTINGS_PATH=keys/settings.json -ORMCONFIG_PATH=keys/ormconfig.json SERVER_ENV_TEMPLATE=server/.env.template SERVER_ENV=server/.env PGPASS_PATH=keys/.pgpass @@ -15,7 +14,7 @@ SECRET_KEY_PATH=keys/secret_key SENTRY_DSN_PATH=keys/sentry_dsn SENTRY_DSN=$(shell cat $(SENTRY_DSN_PATH)) MAIL_API_KEY_PATH=keys/mail_api_key -SECRETS=$(MAIL_API_KEY_PATH) $(DB_PASSWORD_PATH) $(ORMCONFIG_PATH) $(PGPASS_PATH) $(SENTRY_DSN_PATH) $(SECRET_KEY_PATH) +SECRETS=$(MAIL_API_KEY_PATH) $(DB_PASSWORD_PATH) $(PGPASS_PATH) $(SENTRY_DSN_PATH) $(SECRET_KEY_PATH) SHARED_CONFIG_PATH=shared/src/assets/config.ts BUILD_ID=$(shell git describe --tags --abbrev=1) GA_TAG_PATH=keys/ga_tag @@ -68,12 +67,9 @@ $(DATA_DUMP_PATH): $(REDIS_SETTINGS_PATH): server/deploy/settings.template.json | keys cp server/deploy/settings.template.json $(REDIS_SETTINGS_PATH) -$(ORMCONFIG_PATH): server/ormconfig.template.json $(DB_PASSWORD_PATH) - DB_PASSWORD=$$(cat $(DB_PASSWORD_PATH)); \ - sed "s|DB_PASSWORD|$$DB_PASSWORD|g" server/ormconfig.template.json > $(ORMCONFIG_PATH) - $(SERVER_ENV): $(SERVER_ENV_TEMPLATE) $(SECRETS) POM_BASE_URL=${POM_BASE_URL} \ + DB_PASSWORD=$$(cat $(DB_PASSWORD_PATH)); \ envsubst < $(SERVER_ENV_TEMPLATE) > $(SERVER_ENV) $(PGPASS_PATH): $(DB_PASSWORD_PATH) server/deploy/pgpass.template | keys @@ -110,7 +106,7 @@ settings: $(SENTRY_DSN_PATH) $(SECRET_KEY_PATH) | keys initialize: build docker compose run --rm server npm run initdb -docker-compose.yml: base.yml $(ENVIR).yml config.mk $(DB_DATA_PATH) $(DATA_DUMP_PATH) $(LOG_DATA_PATH) $(REDIS_SETTINGS_PATH) $(ORMCONFIG_PATH) $(NUXT_ORMCONFIG_PATH) $(PGPASS_PATH) $(SERVER_ENV) settings +docker-compose.yml: base.yml $(ENVIR).yml config.mk $(DB_DATA_PATH) $(DATA_DUMP_PATH) $(LOG_DATA_PATH) $(REDIS_SETTINGS_PATH) $(PGPASS_PATH) $(SERVER_ENV) settings case "$(ENVIR)" in \ dev|staging|prod) docker compose -f base.yml -f "$(ENVIR).yml" config > docker-compose.yml;; \ *) echo "invalid environment. must be either dev, staging or prod" 1>&2; exit 1;; \ @@ -118,7 +114,7 @@ docker-compose.yml: base.yml $(ENVIR).yml config.mk $(DB_DATA_PATH) $(DATA_DUMP_ .PHONY: test-setup test-setup: docker-compose.yml - docker compose run --rm server bash -c "dropdb --if-exists -h db -U ${DB_USER} ${TEST_DB_NAME} && createdb -h db -U ${DB_USER} ${TEST_DB_NAME} && npm run typeorm -- schema:sync -c test && npm run load-fixtures -- ./fixtures/sologame -cn test" + docker compose run --rm server bash -c "dropdb --if-exists -h db -U ${DB_USER} ${TEST_DB_NAME} && createdb -h db -U ${DB_USER} ${TEST_DB_NAME} && npm run typeorm -- schema:sync && npm run load-fixtures ./fixtures/sologame" .PHONY: test test: test-setup diff --git a/base.yml b/base.yml index 880fef79a..a1a095dad 100644 --- a/base.yml +++ b/base.yml @@ -12,7 +12,6 @@ services: volumes: - ./docker/dump:/dump - ./docker/logs:/var/log/port-of-mars - - ./keys/ormconfig.json:/code/server/ormconfig.json - ./keys/.pgpass:/root/.pgpass - ./keys:/run/secrets - ./scripts:/scripts diff --git a/server/.env.template b/server/.env.template index 947611450..474395cc3 100644 --- a/server/.env.template +++ b/server/.env.template @@ -1,5 +1,7 @@ BASE_URL=${POM_BASE_URL} +DB_PASSWORD=${DB_PASSWORD} + GOOGLE_CLIENT_ID=changeme GOOGLE_CLIENT_SECRET=changeme diff --git a/server/ormconfig.template.json b/server/ormconfig.template.json deleted file mode 100644 index 8bc909c2d..000000000 --- a/server/ormconfig.template.json +++ /dev/null @@ -1,52 +0,0 @@ -[ - { - "name": "default", - "type": "postgres", - "host": "db", - "port": 5432, - "username": "marsmadness", - "database": "port_of_mars", - "password": "DB_PASSWORD", - "synchronize": false, - "logging": false, - "entities": [ - "src/entity/**/*.{js,ts}" - ], - "migrations": [ - "src/migration/**/*.{js,ts}" - ], - "subscribers": [ - "src/subscriber/**/*.{js,ts}" - ], - "cli": { - "entitiesDir": "src/entity", - "migrationsDir": "src/migration", - "subscribersDir": "src/subscriber" - } - }, - { - "name": "test", - "type": "postgres", - "host": "db", - "port": 5432, - "username": "marsmadness", - "database": "pom_testing", - "password": "DB_PASSWORD", - "synchronize": false, - "logging": false, - "entities": [ - "src/entity/**/*.{js,ts}" - ], - "migrations": [ - "src/migration/**/*.{js,ts}" - ], - "subscribers": [ - "src/subscriber/**/*.{js,ts}" - ], - "cli": { - "entitiesDir": "src/entity", - "migrationsDir": "src/migration", - "subscribersDir": "src/subscriber" - } - } -] diff --git a/server/package.json b/server/package.json index 6baaeb802..0867490eb 100644 --- a/server/package.json +++ b/server/package.json @@ -12,7 +12,7 @@ "cli:prod": "node -r tsconfig-paths/register src/cli.js", "start:prod": "node -r tsconfig-paths/register src/index.js | tee -a /var/log/port-of-mars/index.log", "start": "ts-node-dev -r tsconfig-paths/register src/index.ts | tee -a /var/log/port-of-mars/index.log", - "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js", + "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js --dataSource src/datasource.ts", "dangerously-dropdb": "dropdb -U marsmadness -h db port_of_mars", "initdb": "createdb -U marsmadness -h db port_of_mars ; npm run typeorm migration:run && /scripts/freeplay/setup.sh ; npm run load-fixtures ./fixtures/sologame", "dangerously-loaddb": "psql -h db -U marsmadness port_of_mars < pom-db.sql", @@ -25,7 +25,7 @@ "lint:fix": "eslint --fix -c .eslintrc.js ./ ../shared --ext .ts", "style": "prettier --config ../.prettierrc --check './**/*.ts' '../shared/**/*.ts'", "style:fix": "prettier --config ../.prettierrc --write './**/*.ts' '../shared/**/*.ts'", - "load-fixtures": "fixtures --config ormconfig.json --require ts-node/register --require tsconfig-paths/register" + "load-fixtures": "fixtures load --require ts-node/register --require tsconfig-paths/register -d src/datasource.ts" }, "author": "Center for Behavior, Institutions, and the Environment (https://cbie.asu.edu)", "license": "MIT", diff --git a/server/src/cli.ts b/server/src/cli.ts index 7ec851f16..d412f45cb 100644 --- a/server/src/cli.ts +++ b/server/src/cli.ts @@ -25,10 +25,11 @@ import { } from "@port-of-mars/server/entity"; import { DYNAMIC_SETTINGS_PATH, RedisSettings } from "@port-of-mars/server/services/settings"; import { generateUsername } from "@port-of-mars/server/util"; +import appDataSource from "@port-of-mars/server/datasource"; import { program } from "commander"; import { mkdir, readFile, writeFile } from "fs/promises"; -import { createConnection, EntityManager } from "typeorm"; +import { EntityManager } from "typeorm"; /* import { promisify } from "util"; @@ -43,16 +44,6 @@ function getRandomInt(min: number, max: number) { return Math.floor(Math.random() * (max - min) + min); } -async function withConnection(f: (em: EntityManager) => Promise): Promise { - const conn = await createConnection("default"); - const em = conn.createEntityManager(); - try { - await f(em); - } finally { - await conn.close(); - } -} - async function exportSoloData(em: EntityManager, start?: string, end?: string) { const soloGameService = getServices().sologame; await mkdir("/dump/solo", { recursive: true }); @@ -483,7 +474,9 @@ program .option("--tournamentId ", "ID of the tournament", customParseInt) .description("link existing Treatments to a given Tournament") .action(async cmd => { - await withConnection(em => addTreatments(em, cmd.treatmentIds, cmd.tournamentId)); + await appDataSource.transaction(async em => + addTreatments(em, cmd.treatmentIds, cmd.tournamentId) + ); }) ) .addCommand( @@ -498,7 +491,7 @@ program .option("--tournamentId ", "ID of the tournament", customParseInt) .description("add a Treatment (set of mars event overrides) to a Tournament") .action(async cmd => { - await withConnection(em => + await appDataSource.transaction(async em => createTournamentTreatment( em, cmd.name, @@ -529,7 +522,7 @@ program ) .description("add a TournamentRoundDate for the given date") .action(async cmd => { - await withConnection(em => + await appDataSource.transaction(async em => createTournamentRoundDate(em, cmd.date, cmd.tournamentRoundId) ); }) @@ -544,7 +537,9 @@ program ) .description("report emails for all users in the given tournament round") .action(async cmd => { - await withConnection(em => exportTournamentRoundEmails(em, cmd.tournamentRoundId)); + await appDataSource.transaction(async em => + exportTournamentRoundEmails(em, cmd.tournamentRoundId) + ); }) ) .addCommand( @@ -567,7 +562,7 @@ program ) .description("create invitations for the given users in the given tournament round") .action(async cmd => { - await withConnection(em => + await appDataSource.transaction(async em => createTournamentRoundInvites( em, cmd.tournamentRoundId, @@ -597,7 +592,7 @@ program .option("--announcement ", "Tournament Round announcement message", "") .description("create a tournament round") .action(async cmd => { - await withConnection(em => + await appDataSource.transaction(async em => createRound( em, cmd.open, @@ -627,7 +622,7 @@ program .option("--description ", "Description of the tournament") .description("create a tournament") .action(async cmd => { - await withConnection(em => + await appDataSource.transaction(async em => createTournament( em, cmd.tournamentName, @@ -650,7 +645,7 @@ program .description("set a user as an administrator") .requiredOption("--username ", "username of the user") .action(async cmd => { - await withConnection(em => setAdminUser(em, cmd.username)); + await appDataSource.transaction(async em => setAdminUser(em, cmd.username)); }) ) .addCommand( @@ -660,7 +655,9 @@ program .requiredOption("--startId ", "initial user ID in range", customParseInt, 1) .requiredOption("--endId ", "end user ID in range", customParseInt, 1942) .action(async cmd => { - await withConnection(em => anonymizeUsernames(em, cmd.startId, cmd.endId)); + await appDataSource.transaction(async em => + anonymizeUsernames(em, cmd.startId, cmd.endId) + ); }) ) ) @@ -674,7 +671,7 @@ program .description("finalize a game that wasn't finalized properly") .requiredOption("--gameId ", "id of game", customParseInt) .action(async cmd => { - await withConnection(em => finalize(em, cmd.gameId)); + await appDataSource.transaction(async em => finalize(em, cmd.gameId)); }) ) .addCommand( @@ -685,7 +682,7 @@ program ) .requiredOption("--gameId ", "id of game", customParseInt) .action(async cmd => { - await withConnection(em => validate(em, cmd.gameId)); + await appDataSource.transaction(async em => validate(em, cmd.gameId)); }) ) ) @@ -701,7 +698,7 @@ program .description("dump game data for a given tournament round id to a pile of CSV files") .requiredOption("--tournamentId ", "tournament id", customParseInt) .action(async cmd => { - await withConnection(em => exportTournament(em, cmd.tournamentId)); + await appDataSource.transaction(async em => exportTournament(em, cmd.tournamentId)); }) ) .addCommand( @@ -720,7 +717,9 @@ program [] as Array ) .action(async cmd => { - await withConnection(em => exportTournamentRound(em, cmd.tournamentId, cmd.gids)); + await appDataSource.transaction(async em => + exportTournamentRound(em, cmd.tournamentId, cmd.gids) + ); }) ) .addCommand( @@ -730,7 +729,7 @@ program .option("-s, --start ", "Start date (YYYY-MM-DD)") .option("-e, --end ", "End date (YYYY-MM-DD)") .action(async cmd => { - await withConnection(em => exportSoloData(em, cmd.start, cmd.end)); + await appDataSource.transaction(async em => exportSoloData(em, cmd.start, cmd.end)); }) ) ) @@ -745,7 +744,7 @@ program [] as Array ) .action(async cmd => { - await withConnection(em => checkQuizCompletion(em, cmd.ids)); + await appDataSource.transaction(async em => checkQuizCompletion(em, cmd.ids)); }) ) .addCommand( @@ -759,7 +758,7 @@ program [] as Array ) .action(async cmd => { - await withConnection(em => completeQuizCompletion(em, cmd.ids)); + await appDataSource.transaction(async em => completeQuizCompletion(em, cmd.ids)); }) ) .addCommand( @@ -776,7 +775,9 @@ program "generate a CSV for mailchimp import of all active users with a valid email address" ) .action(async cmd => { - await withConnection(em => exportActiveEmails(em, cmd.after, cmd.enableAmdf)); + await appDataSource.transaction(async em => + exportActiveEmails(em, cmd.after, cmd.enableAmdf) + ); }) ) .addCommand( @@ -789,7 +790,7 @@ program ) .description("Deactivate users who have unsubscribed from emails (currently from mailchimp).") .action(async cmd => { - await withConnection(em => deactivateUsers(em, cmd.filename)); + await appDataSource.transaction(async em => deactivateUsers(em, cmd.filename)); }) ) .addCommand( diff --git a/server/src/datasource.ts b/server/src/datasource.ts new file mode 100644 index 000000000..aa5761194 --- /dev/null +++ b/server/src/datasource.ts @@ -0,0 +1,29 @@ +import { DataSourceOptions, DataSource } from "typeorm"; +import * as dotenv from "dotenv"; + +dotenv.config(); + +const postgresConnectionOptions: DataSourceOptions = { + type: "postgres", + host: "db", + port: 5432, + username: "marsmadness", + password: process.env.DB_PASSWORD, + synchronize: false, + logging: false, + entities: ["src/entity/**/*.{js,ts}"], + migrations: ["src/migration/**/*.{js,ts}"], +}; + +const appDataSource = new DataSource({ + ...postgresConnectionOptions, + database: "port_of_mars", +}); + +const testDataSource = new DataSource({ + ...postgresConnectionOptions, + database: "pom_testing", +}); + +const dataSource = process.env.NODE_ENV === "test" ? testDataSource : appDataSource; +export default dataSource; diff --git a/server/src/index.ts b/server/src/index.ts index 757a987cb..9f2c22151 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,4 +1,3 @@ -import { createConnection } from "typeorm"; import http from "http"; import express, { Response } from "express"; import helmet from "helmet"; @@ -35,11 +34,11 @@ import { statusRouter, statsRouter, } from "@port-of-mars/server/routes"; -import { ServerError } from "./util"; +import { ServerError } from "@port-of-mars/server/util"; +import dataSource from "@port-of-mars/server/datasource"; const logger = settings.logging.getLogger(__filename); const NODE_ENV = process.env.NODE_ENV || "development"; -const CONNECTION_NAME = NODE_ENV === "test" ? "test" : "default"; const RedisStore = connectRedis(session); const store = new RedisStore({ host: "redis", client: getRedis() }); @@ -251,12 +250,14 @@ async function createApp() { // connect to the database and start the server, retrying if the connection fails pRetry( async () => { - await createConnection(CONNECTION_NAME); + await dataSource.initialize(); await createApp(); }, { onFailedAttempt: error => { - logger.warn(`Connection to db failed on attempt number ${error.attemptNumber}, retrying...`); + logger.warn( + `Connection to db failed on attempt number ${error.attemptNumber}, retrying ${error.retriesLeft} more times...` + ); }, retries: 10, minTimeout: 1 * 1000, diff --git a/server/src/migration/1701805620516-ComputeSoloRoundInitialValues.ts b/server/src/migration/1701805620516-ComputeSoloRoundInitialValues.ts index 7e868c46f..ca3612e96 100644 --- a/server/src/migration/1701805620516-ComputeSoloRoundInitialValues.ts +++ b/server/src/migration/1701805620516-ComputeSoloRoundInitialValues.ts @@ -51,7 +51,12 @@ export class ComputeSoloRoundInitialValues1701805620516 implements MigrationInte private async computeMissingInitialValues(queryRunner: QueryRunner): Promise { // fetch all existing games with rounds, cards, decisions const games = await queryRunner.manager.find(SoloGame, { - relations: ["rounds", "rounds.cards", "rounds.decision"], + relations: { + rounds: { + cards: true, + decision: true, + }, + }, }); for (const game of games) { let initialSystemHealth = MAX_SYSTEM_HEALTH; diff --git a/server/src/routes/quiz.ts b/server/src/routes/quiz.ts index 492a07cf2..d59945b3e 100644 --- a/server/src/routes/quiz.ts +++ b/server/src/routes/quiz.ts @@ -24,12 +24,16 @@ quizRouter.get("/submission/:id", async (req: Request, res: Response, next: Next const id = parseInt(req.params.id); let submission = await quizService.findQuizSubmission(id, { where: { userId: user.id }, - relations: ["quiz", "quiz.questions"], + relations: { + quiz: { + questions: true, + }, + }, }); let quiz = submission?.quiz; let statusCode = 200; if (!submission) { - quiz = await quizService.getDefaultQuiz({ relations: ["questions"] }); + quiz = await quizService.getDefaultQuiz({ relations: { questions: true } }); logger.debug("created new submission after looking up id %d", id); submission = await quizService.createQuizSubmission(user.id, quiz.id); statusCode = 201; @@ -47,7 +51,7 @@ quizRouter.post("/submission", async (req: Request, res: Response, next: NextFun const user = req.user as User; if (user) { const userId = user!.id; - const quiz = await quizService.getDefaultQuiz({ relations: ["questions"] }); + const quiz = await quizService.getDefaultQuiz({ relations: { questions: true } }); const quizId = quiz!.id; const quizQuestions = quiz.questions; const submission = await quizService.createQuizSubmission(userId, quizId); diff --git a/server/src/services/account.ts b/server/src/services/account.ts index e3b4cdb41..80b6e6168 100644 --- a/server/src/services/account.ts +++ b/server/src/services/account.ts @@ -137,7 +137,12 @@ export class AccountService extends BaseService { async getActiveUsers(after: Date): Promise> { return await this.getRepository().find({ - select: ["name", "email", "username", "dateCreated"], + select: { + name: true, + email: true, + username: true, + dateCreated: true, + }, where: { isActive: true, email: Not(IsNull()), diff --git a/server/src/services/index.ts b/server/src/services/index.ts index 1f4200c8e..b9e61d179 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -6,11 +6,11 @@ import { TournamentService } from "@port-of-mars/server/services/tournament"; import { QuizService } from "@port-of-mars/server/services/quiz"; import { SurveyService } from "@port-of-mars/server/services/survey"; import { StatsService } from "@port-of-mars/server/services/stats"; -import { getConnection } from "@port-of-mars/server/util"; import { TimeService } from "@port-of-mars/server/services/time"; import { GameService } from "@port-of-mars/server/services/game"; import { SoloGameService } from "@port-of-mars/server/services/sologame"; import { RedisSettings } from "@port-of-mars/server/services/settings"; +import dataSource from "@port-of-mars/server/datasource"; import { createClient, RedisClient } from "redis"; export class ServiceProvider { @@ -114,5 +114,5 @@ export function getRedis(): RedisClient { } export function getServices(em?: EntityManager) { - return new ServiceProvider(em ?? getConnection().manager); + return new ServiceProvider(em ?? dataSource.manager); } diff --git a/server/src/services/persistence.ts b/server/src/services/persistence.ts index 6fa7e55dd..f38b0ec49 100644 --- a/server/src/services/persistence.ts +++ b/server/src/services/persistence.ts @@ -49,7 +49,11 @@ export class DBPersister implements Persister { async selectUsersByUsername(em: EntityManager, usernames: Array) { const userRepo = em.getRepository(User); const rawUsers = await userRepo.find({ - select: ["id", "username", "lastPlayerIp"], + select: { + id: true, + username: true, + lastPlayerIp: true, + }, where: { username: In(usernames), }, diff --git a/server/src/services/quiz.ts b/server/src/services/quiz.ts index 63620b91b..42b0a5f27 100644 --- a/server/src/services/quiz.ts +++ b/server/src/services/quiz.ts @@ -97,7 +97,7 @@ export class QuizService extends BaseService { async getQuizResponses(submissionId: number): Promise> { return await this.em .getRepository(QuestionResponse) - .find({ where: { submissionId }, relations: ["question"] }); + .find({ where: { submissionId }, relations: { question: true } }); } async getIncompleteQuizUsers(): Promise> { @@ -114,8 +114,8 @@ export class QuizService extends BaseService { */ async checkQuizCompletion(userId: number, quizId?: number): Promise> { const quiz: Quiz = quizId - ? await this.getQuizById(quizId, { relations: ["questions"] }) - : await this.getDefaultQuiz({ relations: ["questions"] }); + ? await this.getQuizById(quizId, { relations: { questions: true } }) + : await this.getDefaultQuiz({ relations: { questions: true } }); const quizSubmission = await this.getLatestQuizSubmission(userId, quiz.id); const questionIds = quiz.questions.map(q => q.id); if (!quizSubmission) { diff --git a/server/src/services/sologame.ts b/server/src/services/sologame.ts index 45863e574..de3b8f9bd 100644 --- a/server/src/services/sologame.ts +++ b/server/src/services/sologame.ts @@ -105,7 +105,11 @@ export class SoloGameService extends BaseService { await playerRepo.save(player); return gameRepo.findOneOrFail({ where: { id: game.id }, - relations: ["deck", "deck.cards"], + relations: { + deck: { + cards: true, + }, + }, }); } diff --git a/server/src/services/stats.ts b/server/src/services/stats.ts index 746bf8d76..31ff49fb8 100644 --- a/server/src/services/stats.ts +++ b/server/src/services/stats.ts @@ -126,7 +126,11 @@ export class StatsService extends BaseService { const pointsPerRound = points / maxRound; const game = await this.em.getRepository(SoloGame).findOneOrFail({ where: { id: gameId }, - relations: ["player", "player.user"], + relations: { + player: { + user: true, + }, + }, }); const user = game.player.user; let highscore = await highscoreRepo.findOneBy({ user }); diff --git a/server/src/services/survey.ts b/server/src/services/survey.ts index e2bfe28eb..33bb3c70a 100644 --- a/server/src/services/survey.ts +++ b/server/src/services/survey.ts @@ -45,7 +45,10 @@ export class SurveyService extends BaseService { async setSurveyComplete(data: { inviteId: number; surveyId: string }) { const invite = await this.em.getRepository(TournamentRoundInvite).findOneOrFail({ where: { id: data.inviteId }, - relations: ["user", "tournamentRound"], + relations: { + user: true, + tournamentRound: true, + }, }); const tournamentRound = invite.tournamentRound; const introSurveyUrl = tournamentRound.introSurveyUrl; diff --git a/server/src/services/tournament.ts b/server/src/services/tournament.ts index 25d144865..e11f2cb0c 100644 --- a/server/src/services/tournament.ts +++ b/server/src/services/tournament.ts @@ -45,7 +45,7 @@ export class TournamentService extends BaseService { where: { id: id, }, - relations: ["rounds"], + relations: { rounds: true }, }); } return this.em.getRepository(Tournament).findOneOrFail({ @@ -80,7 +80,7 @@ export class TournamentService extends BaseService { async getCurrentTournamentRound(tournamentId?: number): Promise { tournamentId = (await this.getTournament(tournamentId)).id; return await this.em.getRepository(TournamentRound).findOneOrFail({ - relations: ["tournament"], + relations: { tournament: true }, where: { tournamentId }, order: { roundNumber: "DESC" }, }); @@ -89,7 +89,7 @@ export class TournamentService extends BaseService { async getFreePlayTournamentRound(): Promise { const tournamentId = (await this.getFreePlayTournament()).id; return await this.em.getRepository(TournamentRound).findOneOrFail({ - relations: ["tournament"], + relations: { tournament: true }, where: { tournamentId }, order: { roundNumber: "DESC" }, }); @@ -243,7 +243,7 @@ export class TournamentService extends BaseService { if (!afterOffset) afterOffset = await this.getAfterOffset(); const offsetTime = new Date().getTime() - afterOffset; const schedule = await this.em.getRepository(TournamentRoundDate).find({ - select: ["date"], + select: { date: true }, where: { tournamentRoundId: tournamentRound.id, date: MoreThanOrEqual(new Date(offsetTime)) }, order: { date: "ASC" }, }); @@ -484,7 +484,7 @@ export class TournamentService extends BaseService { const tournament = await this.em.getRepository(Tournament).findOne({ where: tournamentId ? { id: tournamentId } : { active: true, name: Not("freeplay") }, - relations: ["treatments"], + relations: { treatments: true }, order: { id: "DESC", }, diff --git a/server/src/util.ts b/server/src/util.ts index 255ab4b6e..5d814f45b 100644 --- a/server/src/util.ts +++ b/server/src/util.ts @@ -15,11 +15,6 @@ import { ClientSafeUser } from "@port-of-mars/shared/types"; const logger = getLogger(__filename); -export function getConnection(): to.Connection { - const connectionName = process.env.NODE_ENV === "test" ? "test" : "default"; - return to.getConnection(connectionName); -} - export function toUrl(page: Page): string { const pagePath = getPagePath(page); // getPagePath returns a string with initial slash diff --git a/server/tests/common.ts b/server/tests/common.ts index e5a36950f..6737bee32 100644 --- a/server/tests/common.ts +++ b/server/tests/common.ts @@ -1,21 +1,23 @@ -import { Connection, createConnection, EntityManager, QueryRunner } from "typeorm"; +import { EntityManager, QueryRunner } from "typeorm"; import { ServiceProvider } from "@port-of-mars/server/services"; import { Tournament, User } from "@port-of-mars/server/entity"; import { Client } from "colyseus"; import EventEmitter from "events"; +import testDataSource from "@port-of-mars/server/datasource"; -export async function initTransaction(): Promise<[Connection, QueryRunner, EntityManager]> { - const conn = await createConnection("test"); - const qs = conn.createQueryRunner(); - await qs.startTransaction(); - const manager = qs.manager; - return [conn, qs, manager]; +export async function initTransaction(): Promise<[QueryRunner, EntityManager]> { + await testDataSource.initialize(); + await testDataSource.synchronize(); + const qr = testDataSource.createQueryRunner(); + await qr.startTransaction(); + const manager = qr.manager; + return [qr, manager]; } -export async function rollbackTransaction(conn: Connection, qs: QueryRunner) { - await qs.rollbackTransaction(); - await qs.release(); - await conn.close(); +export async function rollbackTransaction(qr: QueryRunner) { + await qr.rollbackTransaction(); + await qr.release(); + await testDataSource.destroy(); } export async function createUsers( diff --git a/server/tests/rooms/sologame.test.ts b/server/tests/rooms/sologame.test.ts index f377c9622..e61c23191 100644 --- a/server/tests/rooms/sologame.test.ts +++ b/server/tests/rooms/sologame.test.ts @@ -10,7 +10,7 @@ import { User, } from "@port-of-mars/server/entity"; import { SoloGameState } from "@port-of-mars/server/rooms/sologame/state"; -import { Connection, EntityManager, QueryRunner } from "typeorm"; +import { EntityManager, QueryRunner } from "typeorm"; import { ServiceProvider } from "@port-of-mars/server/services"; import { createUsers, initTransaction, rollbackTransaction } from "../common"; import { @@ -34,7 +34,6 @@ const logger = getLogger(__filename); */ describe("a solo game", () => { - let conn: Connection; let qr: QueryRunner; let manager: EntityManager; let sp: ServiceProvider; @@ -43,13 +42,13 @@ describe("a solo game", () => { let user3: User; beforeAll(async () => { - [conn, qr, manager] = await initTransaction(); + [qr, manager] = await initTransaction(); sp = new ServiceProvider(qr.manager); // create users paul1 paul2 paul3 [user1, user2, user3] = await createUsers(manager, "paul", [1, 2, 3]); }); - afterAll(async () => await rollbackTransaction(conn, qr)); + afterAll(async () => await rollbackTransaction(qr)); describe("a solo game room", () => { let room: SoloGameRoom; diff --git a/server/tests/services/account.test.ts b/server/tests/services/account.test.ts index c3466e10f..2d13accca 100644 --- a/server/tests/services/account.test.ts +++ b/server/tests/services/account.test.ts @@ -1,14 +1,13 @@ import { AccountService } from "@port-of-mars/server/services/account"; import { User, Tournament } from "@port-of-mars/server/entity"; import { settings } from "@port-of-mars/server/settings"; -import { Connection, EntityManager, QueryRunner } from "typeorm"; +import { EntityManager, QueryRunner } from "typeorm"; import { ServiceProvider } from "@port-of-mars/server/services"; import { ServerError } from "@port-of-mars/server/util"; import { createTournament, initTransaction, rollbackTransaction } from "../common"; describe("a potential user", () => { const username = "ahacker"; - let conn: Connection; let qr: QueryRunner; let manager: EntityManager; let sp: ServiceProvider; @@ -17,7 +16,7 @@ describe("a potential user", () => { let accountService: AccountService; beforeAll(async () => { - [conn, qr, manager] = await initTransaction(); + [qr, manager] = await initTransaction(); sp = new ServiceProvider(qr.manager); t = await createTournament(sp); // tr = await createRound(sp, { tournamentId: t.id }); @@ -107,5 +106,5 @@ describe("a potential user", () => { accountService.verifyUnregisteredUser(u, "invalid-registration-token") ).rejects.toThrowError(ServerError); }); - afterAll(async () => rollbackTransaction(conn, qr)); + afterAll(async () => rollbackTransaction(qr)); }); diff --git a/server/tests/services/admin.test.ts b/server/tests/services/admin.test.ts index 48b980074..8e0e3efbd 100644 --- a/server/tests/services/admin.test.ts +++ b/server/tests/services/admin.test.ts @@ -1,5 +1,5 @@ import { Tournament, TournamentRound, Game } from "@port-of-mars/server/entity"; -import { Connection, EntityManager, QueryRunner } from "typeorm"; +import { EntityManager, QueryRunner } from "typeorm"; import { ServiceProvider } from "@port-of-mars/server/services"; import { createRound, @@ -11,7 +11,6 @@ import { import { BAN, ModerationActionType, MUTE } from "@port-of-mars/shared/types"; describe("users in a game", () => { - let conn: Connection; let qr: QueryRunner; let manager: EntityManager; let sp: ServiceProvider; @@ -19,7 +18,7 @@ describe("users in a game", () => { let tr: TournamentRound; beforeAll(async () => { - [conn, qr, manager] = await initTransaction(); + [qr, manager] = await initTransaction(); sp = new ServiceProvider(qr.manager); t = await createTournament(sp, { name: "freeplay" }); tr = await createRound(sp, { tournamentId: t.id }); @@ -160,7 +159,7 @@ describe("users in a game", () => { expect(user.muteStrikes).toBe(1); }); }); - afterAll(async () => await rollbackTransaction(conn, qr)); + afterAll(async () => await rollbackTransaction(qr)); }); const createChatReports = async (sp: ServiceProvider, usernames: Array) => { diff --git a/server/tests/services/replay.test.ts b/server/tests/services/replay.test.ts index ff286701a..a413cffc3 100644 --- a/server/tests/services/replay.test.ts +++ b/server/tests/services/replay.test.ts @@ -35,7 +35,7 @@ import { import { DBPersister, toDBGameEvent } from "@port-of-mars/server/services/persistence"; import { GameReplayer, MarsEventSummarizer } from "@port-of-mars/server/services/replay"; import { GameEvent } from "@port-of-mars/server/rooms/game/events/types"; -import { Connection, EntityManager, QueryRunner } from "typeorm"; +import { EntityManager, QueryRunner } from "typeorm"; import { Persister } from "@port-of-mars/server/rooms/game/types"; import { ServiceProvider } from "@port-of-mars/server/services"; import { Player, Tournament, TournamentRound, User } from "@port-of-mars/server/entity"; @@ -302,7 +302,6 @@ describe("a game", () => { ]; describe("event stream", () => { - let conn: Connection; let manager: EntityManager; let persister: Persister; let qs: QueryRunner; @@ -313,7 +312,7 @@ describe("a game", () => { let users: Array; beforeAll(async () => { - [conn, qs, manager] = await initTransaction(); + [qs, manager] = await initTransaction(); sp = new ServiceProvider(manager); persister = new DBPersister(sp); t = await createTournament(sp); @@ -351,7 +350,7 @@ describe("a game", () => { } }); - afterAll(async () => rollbackTransaction(conn, qs)); + afterAll(async () => rollbackTransaction(qs)); }); it("correctly isolates all mars events", () => { diff --git a/server/tests/services/tournament.test.ts b/server/tests/services/tournament.test.ts index 414e7e8cd..fef7316bf 100644 --- a/server/tests/services/tournament.test.ts +++ b/server/tests/services/tournament.test.ts @@ -1,6 +1,6 @@ import { TournamentRound } from "@port-of-mars/server/entity/TournamentRound"; import { Tournament } from "@port-of-mars/server/entity/Tournament"; -import { Connection, EntityManager, QueryRunner } from "typeorm"; +import { EntityManager, QueryRunner } from "typeorm"; import { ServiceProvider } from "@port-of-mars/server/services"; import { createRound, @@ -17,7 +17,6 @@ import { settings } from "@port-of-mars/server/settings"; import { TournamentRoundSignup } from "@port-of-mars/server/entity/TournamentRoundSignup"; describe("a tournament", () => { - let conn: Connection; let qr: QueryRunner; let manager: EntityManager; let services: ServiceProvider; @@ -34,7 +33,7 @@ describe("a tournament", () => { const afterOffset = 30 * 60 * 1000; beforeAll(async () => { - [conn, qr, manager] = await initTransaction(); + [qr, manager] = await initTransaction(); services = new ServiceProvider(qr.manager); // create special freeplay tournament const freeplayTournament = await createTournament(services, { name: "freeplay" }); @@ -330,5 +329,5 @@ describe("a tournament", () => { }); }); - afterAll(async () => rollbackTransaction(conn, qr)); + afterAll(async () => rollbackTransaction(qr)); });