mirror of
https://github.com/djhackersdev/minime.git
synced 2026-04-24 15:37:12 -05:00
idz: Add TeamMemberRepository
This commit is contained in:
parent
c6a67bd647
commit
be48619a4a
|
|
@ -9,6 +9,7 @@ import { SqlProfileRepository } from "./profile";
|
|||
import { SqlSettingsRepository } from "./settings";
|
||||
import { SqlStoryRepository } from "./story";
|
||||
import { SqlTeamRepository } from "./team";
|
||||
import { SqlTeamMemberRepository } from "./teamMember";
|
||||
import { SqlTicketsRepository } from "./tickets";
|
||||
import { SqlTimeAttackRepository } from "./timeAttack";
|
||||
import { SqlTitlesRepository } from "./titles";
|
||||
|
|
@ -56,6 +57,10 @@ class TransactionImpl implements Repo.Transaction {
|
|||
return new SqlTeamRepository(this._conn);
|
||||
}
|
||||
|
||||
teamMembers(): Repo.TeamMemberRepository {
|
||||
return new SqlTeamMemberRepository(this._conn);
|
||||
}
|
||||
|
||||
tickets(): Repo.FacetRepository<Model.Tickets> {
|
||||
return new SqlTicketsRepository(this._conn);
|
||||
}
|
||||
|
|
|
|||
153
src/idz/db/teamMember.ts
Normal file
153
src/idz/db/teamMember.ts
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
import * as sql from "sql-bricks-postgres";
|
||||
import { ClientBase } from "pg";
|
||||
|
||||
import { Profile } from "../model/profile";
|
||||
import { Team, TeamMember } from "../model/team";
|
||||
import { TeamMemberRepository } from "../repo";
|
||||
import { Id, generateId } from "../../db";
|
||||
import { _extractProfile } from "./profile";
|
||||
import { _extractChara } from "./chara";
|
||||
|
||||
export class SqlTeamMemberRepository implements TeamMemberRepository {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
|
||||
async findTeam(profileId: Id<Profile>): Promise<Id<Team> | undefined> {
|
||||
const findSql = sql
|
||||
.select("tm.team_id")
|
||||
.from("idz.team_member tm")
|
||||
.where("tm.id", profileId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(findSql);
|
||||
const row = rows[0];
|
||||
|
||||
if (row === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return row.team_id;
|
||||
}
|
||||
|
||||
async findLeader(teamId: Id<Team>): Promise<Id<Profile> | undefined> {
|
||||
const findSql = sql
|
||||
.select("tm.id")
|
||||
.from("idz.team_member tm")
|
||||
.where("tm.team_id", teamId)
|
||||
.where("tm.leader", true)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(findSql);
|
||||
const row = rows[0];
|
||||
|
||||
if (row === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return row.id;
|
||||
}
|
||||
|
||||
async loadRoster(teamId: Id<Team>): Promise<TeamMember[]> {
|
||||
const loadSql = sql
|
||||
.select("tm.*", "p.*", "c.*", "r.ext_id as aime_id")
|
||||
.from("idz.team_member tm")
|
||||
.join("idz.profile p", { "tm.id": "p.id" })
|
||||
.join("idz.chara c", { "tm.id": "c.id" })
|
||||
.join("aime.player r", { "p.player_id": "r.id" })
|
||||
.where("tm.team_id", teamId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
|
||||
return rows.map((row: any) => ({
|
||||
profile: _extractProfile(row),
|
||||
chara: _extractChara(row),
|
||||
leader: row.leader,
|
||||
joinTime: new Date(row.join_time),
|
||||
}));
|
||||
}
|
||||
|
||||
async join(
|
||||
teamId: Id<Team>,
|
||||
profileId: Id<Profile>,
|
||||
timestamp: Date
|
||||
): Promise<void> {
|
||||
// Lock the team record to avoid race conditions. This way
|
||||
|
||||
const lockSql = sql
|
||||
.select("id")
|
||||
.from("idz.team")
|
||||
.where("id", teamId)
|
||||
.forUpdate()
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(lockSql);
|
||||
|
||||
// Double-check (with lock held) that there is room to join this team.
|
||||
// If this fails then the error will propagate to the client and it will
|
||||
// retry, and, assuming we have a race between two new registrations to
|
||||
// take up the last slot in the current auto-team, hopefully succeed.
|
||||
//
|
||||
// There is arguably some business logic pollution here, since we have a
|
||||
// hard-coded maximum team size imposed by the protocol. This is why a
|
||||
// three-layered server would be better than our two-layered server.
|
||||
|
||||
const countSql = sql
|
||||
.select("count(*) as count")
|
||||
.from("idz.team_member")
|
||||
.where("team_id", teamId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(countSql);
|
||||
const row = rows[0];
|
||||
|
||||
if (row.count >= 6) {
|
||||
throw new Error(`Team ${teamId} is full`);
|
||||
}
|
||||
|
||||
// Do upsert
|
||||
|
||||
const joinSql = sql
|
||||
.insert("idz.team_member", {
|
||||
id: profileId,
|
||||
team_id: teamId,
|
||||
leader: false,
|
||||
join_time: timestamp,
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["team_id", "leader", "join_time"])
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(joinSql);
|
||||
}
|
||||
|
||||
async leave(teamId: Id<Team>, profileId: Id<Profile>): Promise<void> {
|
||||
const leaveSql = sql
|
||||
.delete("idz.team_member")
|
||||
.where("team_id", teamId)
|
||||
.where("id", profileId)
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(leaveSql);
|
||||
}
|
||||
|
||||
async makeLeader(teamId: Id<Team>, profileId: Id<Profile>): Promise<void> {
|
||||
const clearSql = sql
|
||||
.update("idz.team_member", {
|
||||
leader: false,
|
||||
})
|
||||
.where("team_id", teamId)
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(clearSql);
|
||||
|
||||
const setSql = sql
|
||||
.update("idz.team_member", {
|
||||
leader: true,
|
||||
})
|
||||
.where("id", profileId)
|
||||
.where("team_id", teamId)
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(setSql);
|
||||
}
|
||||
}
|
||||
|
|
@ -69,6 +69,27 @@ export interface TeamRepository {
|
|||
delete(id: Id<Model.Team>): Promise<void>;
|
||||
}
|
||||
|
||||
export interface TeamMemberRepository {
|
||||
findTeam(profileId: Id<Model.Profile>): Promise<Id<Model.Team> | undefined>;
|
||||
|
||||
findLeader(teamId: Id<Model.Team>): Promise<Id<Model.Profile> | undefined>;
|
||||
|
||||
loadRoster(id: Id<Model.Team>): Promise<Model.TeamMember[]>;
|
||||
|
||||
join(
|
||||
teamId: Id<Model.Team>,
|
||||
profileId: Id<Model.Profile>,
|
||||
timestamp: Date
|
||||
): Promise<void>;
|
||||
|
||||
leave(teamId: Id<Model.Team>, profileId: Id<Model.Profile>): Promise<void>;
|
||||
|
||||
makeLeader(
|
||||
team: Id<Model.Team>,
|
||||
profileId: Id<Model.Profile>
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
// TODO extend and factorize
|
||||
export interface TopTenResult {
|
||||
driverName: string;
|
||||
|
|
@ -110,6 +131,8 @@ export interface Repositories {
|
|||
|
||||
teams(): TeamRepository;
|
||||
|
||||
teamMembers(): TeamMemberRepository;
|
||||
|
||||
tickets(): FacetRepository<Model.Tickets>;
|
||||
|
||||
timeAttack(): TimeAttackRepository;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user