diff --git a/server/src/entity/ProlificStudyParticipant.ts b/server/src/entity/ProlificStudyParticipant.ts index 2854a4674..6c514978b 100644 --- a/server/src/entity/ProlificStudyParticipant.ts +++ b/server/src/entity/ProlificStudyParticipant.ts @@ -2,6 +2,7 @@ import { Column, Entity, JoinColumn, ManyToOne, OneToOne, PrimaryGeneratedColumn import { ProlificStudy } from "./ProlificStudy"; import { User } from "./User"; import { SoloGameTreatment } from "./SoloGameTreatment"; +import { SoloPlayer } from "./SoloPlayer"; @Entity() export class ProlificStudyParticipant { @@ -35,4 +36,18 @@ export class ProlificStudyParticipant { @Column() prolificVariableTreatmentId!: number; + + @OneToOne(type => SoloPlayer, { nullable: true }) + @JoinColumn() + prolificBaselinePlayer!: SoloPlayer; + + @Column({ nullable: true }) + prolificBaselinePlayerId!: number; + + @OneToOne(type => SoloPlayer, { nullable: true }) + @JoinColumn() + prolificVariablePlayer!: SoloPlayer; + + @Column({ nullable: true }) + prolificVariablePlayerId!: number; } diff --git a/server/src/migration/1727891252710-AddProlificParticipantPlayer.ts b/server/src/migration/1727891252710-AddProlificParticipantPlayer.ts new file mode 100644 index 000000000..6be973a8f --- /dev/null +++ b/server/src/migration/1727891252710-AddProlificParticipantPlayer.ts @@ -0,0 +1,24 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddProlificParticipantPlayer1727891252710 implements MigrationInterface { + name = 'AddProlificParticipantPlayer1727891252710' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "prolific_study_participant" ADD "prolificBaselinePlayerId" integer`); + await queryRunner.query(`ALTER TABLE "prolific_study_participant" ADD CONSTRAINT "UQ_da2b1f49f29eef7941ab06eca11" UNIQUE ("prolificBaselinePlayerId")`); + await queryRunner.query(`ALTER TABLE "prolific_study_participant" ADD "prolificVariablePlayerId" integer`); + await queryRunner.query(`ALTER TABLE "prolific_study_participant" ADD CONSTRAINT "UQ_b5a15c60ff91e4b754305c64137" UNIQUE ("prolificVariablePlayerId")`); + await queryRunner.query(`ALTER TABLE "prolific_study_participant" ADD CONSTRAINT "FK_da2b1f49f29eef7941ab06eca11" FOREIGN KEY ("prolificBaselinePlayerId") REFERENCES "solo_player"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "prolific_study_participant" ADD CONSTRAINT "FK_b5a15c60ff91e4b754305c64137" FOREIGN KEY ("prolificVariablePlayerId") REFERENCES "solo_player"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "prolific_study_participant" DROP CONSTRAINT "FK_b5a15c60ff91e4b754305c64137"`); + await queryRunner.query(`ALTER TABLE "prolific_study_participant" DROP CONSTRAINT "FK_da2b1f49f29eef7941ab06eca11"`); + await queryRunner.query(`ALTER TABLE "prolific_study_participant" DROP CONSTRAINT "UQ_b5a15c60ff91e4b754305c64137"`); + await queryRunner.query(`ALTER TABLE "prolific_study_participant" DROP COLUMN "prolificVariablePlayerId"`); + await queryRunner.query(`ALTER TABLE "prolific_study_participant" DROP CONSTRAINT "UQ_da2b1f49f29eef7941ab06eca11"`); + await queryRunner.query(`ALTER TABLE "prolific_study_participant" DROP COLUMN "prolificBaselinePlayerId"`); + } + +} diff --git a/server/src/rooms/sologame/commands.ts b/server/src/rooms/sologame/commands.ts index 19adecca1..6cacdeca0 100644 --- a/server/src/rooms/sologame/commands.ts +++ b/server/src/rooms/sologame/commands.ts @@ -92,8 +92,11 @@ export class SetGameParamsCmd extends CmdWithoutPayload { export class PersistGameCmd extends CmdWithoutPayload { async execute() { - const { sologame: service } = getServices(); - const game = await service.createGame(this.state); + const { sologame, study } = getServices(); + const game = await sologame.createGame(this.state); + if (this.state.type === "prolificVariable" || this.state.type === "prolificBaseline") { + await study.setProlificParticipantPlayer(this.state.type, game.player); + } this.state.gameId = game.id; // keep track of deck card db ids after persisting the deck this.state.eventCardDeck.forEach((card, index) => { diff --git a/server/src/services/sologame.ts b/server/src/services/sologame.ts index 4857af06b..3d6486f63 100644 --- a/server/src/services/sologame.ts +++ b/server/src/services/sologame.ts @@ -130,6 +130,7 @@ export class SoloGameService extends BaseService { return gameRepo.findOneOrFail({ where: { id: game.id }, relations: { + player: true, deck: { cards: true, }, diff --git a/server/src/services/study.ts b/server/src/services/study.ts index fabe9a35b..2c3a1632f 100644 --- a/server/src/services/study.ts +++ b/server/src/services/study.ts @@ -139,6 +139,72 @@ export class StudyService extends BaseService { return participant; } + async setProlificParticipantPlayer( + gameType: Extract, + player: SoloPlayer + ): Promise { + const participant = await this.getParticipantRepository().findOneOrFail({ + where: { userId: player.userId }, + relations: [`${gameType}Player`], + }); + if (gameType === "prolificBaseline") { + participant.prolificBaselinePlayer = player; + } else { + participant.prolificVariablePlayer = player; + } + return this.getParticipantRepository().save(participant); + } + + async getAllParticipantPoints( + studyId: string + ): Promise> { + const study = await this.getProlificStudy(studyId); + if (!study) { + throw new ServerError({ + code: 404, + message: `Invalid study ID: ${studyId}`, + displayMessage: `Invalid study ID: ${studyId}`, + }); + } + const participants = await this.getParticipantRepository().find({ + where: { studyId: study.id }, + relations: { + prolificBaselinePlayer: { + game: true, + }, + prolificVariablePlayer: { + game: true, + }, + }, + }); + return ( + participants + // filter out participants who haven't fully completed both games + .filter( + p => + p.prolificBaselinePlayer?.game && + p.prolificVariablePlayer?.game && + p.prolificBaselinePlayer.points && + p.prolificVariablePlayer.points + ) + // return an array of prolificId and total points earned, if they lost they get 0 points + .map(p => { + const prolificBaselinePoints = + p.prolificBaselinePlayer.game.status === "victory" + ? p.prolificBaselinePlayer.points! + : 0; + const prolificVariablePoints = + p.prolificVariablePlayer.game.status === "victory" + ? p.prolificVariablePlayer.points! + : 0; + return { + prolificId: p.prolificId, + points: prolificBaselinePoints + prolificVariablePoints, + }; + }) + ); + } + async getRandomTreatmentFor( gameType: Extract ): Promise {