Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/backend/src/lib/graphql/resolvers/mutations/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { teamArrivedResolver } from './team-arrived';
import { teamNotArrivedResolver } from './team-not-arrived';
import { disqualifyTeamResolver } from './disqualify-team';
import { assignPersonalAwardResolver } from './assign-personal-award';
import {
Expand Down Expand Up @@ -53,6 +54,7 @@ import { updatePracticeTableAssignmentResolver } from './practice-tables';

export const mutationResolvers = {
teamArrived: teamArrivedResolver,
teamNotArrived: teamNotArrivedResolver,
disqualifyTeam: disqualifyTeamResolver,
assignPersonalAward: assignPersonalAwardResolver,
startJudgingSession: startJudgingSessionResolver,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const teamArrivedResolver: GraphQLFieldResolver<
.execute();

const pubSub = getRedisPubSub();
await pubSub.publish(divisionId, RedisEventTypes.TEAM_ARRIVED, { teamId });
await pubSub.publish(divisionId, RedisEventTypes.TEAM_ARRIVED, { teamId, arrived: true });

return { teamId };
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { GraphQLFieldResolver } from 'graphql';
import { RedisEventTypes } from '@lems/types/api/lems/redis';
import { MutationError, MutationErrorCode } from '@lems/types/api/lems';
import type { GraphQLContext } from '../../apollo-server';
import db from '../../../database';
import { getRedisPubSub } from '../../../redis/redis-pubsub';

interface TeamNotArrivedArgs {
teamId: string;
divisionId: string;
}

interface TeamEvent {
teamId: string;
}

/**
* Resolver for Mutation.teamNotArrived
* Marks that a team has not arrived at a division (reverses arrival) and publishes an event.
*/
export const teamNotArrivedResolver: GraphQLFieldResolver<
unknown,
GraphQLContext,
TeamNotArrivedArgs,
Promise<TeamEvent>
> = async (_root, { teamId, divisionId }, context) => {
try {
if (!context.user) {
throw new MutationError(MutationErrorCode.UNAUTHORIZED, 'Authentication required');
}

if (context.user.role !== 'pit-admin') {
throw new MutationError(
MutationErrorCode.FORBIDDEN,
'User does not have permission to mark teams as not arrived'
);
}

if (!context.user.divisions.includes(divisionId)) {
throw new MutationError(
MutationErrorCode.FORBIDDEN,
'User does not have access to this division'
);
}

const existing = await db.raw.sql
.selectFrom('team_divisions')
.select(['pk', 'arrived'])
.where('team_id', '=', teamId)
.where('division_id', '=', divisionId)
.executeTakeFirst();

if (!existing) {
throw new MutationError(
MutationErrorCode.NOT_FOUND,
`Team #${teamId} is not registered in this division`
);
}

if (!existing.arrived) {
throw new MutationError(
MutationErrorCode.CONFLICT,
`Team #${teamId} has not arrived at this division yet`
);
}

const update: Record<string, unknown> = { arrived: false, arrived_at: null };
await db.raw.sql
.updateTable('team_divisions')
.set(update)
.where('team_id', '=', teamId)
.where('division_id', '=', divisionId)
.execute();

const pubSub = getRedisPubSub();
await pubSub.publish(divisionId, RedisEventTypes.TEAM_ARRIVED, { teamId, arrived: false });

return { teamId };
} catch (error) {
console.error(
'Error updating team arrival status for team:',
teamId,
'in division:',
divisionId,
error
);
throw error;
}
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { RedisEventTypes } from '@lems/types/api/lems/redis';
import { getRedisPubSub } from '../../../redis/redis-pubsub';

interface TeamEvent {
interface TeamArrivalEvent {
teamId: string;
arrived: boolean;
}

interface TeamArrivalUpdatedSubscribeArgs {
Expand All @@ -22,19 +23,21 @@ const teamArrivalUpdatedSubscribe = (
};

/**
* Transforms raw Redis events into minimal TeamEvent objects
* Transforms raw Redis events into TeamArrivalEvent objects
*/
const processTeamArrivalEvent = async (
event: Record<string, unknown>
): Promise<TeamEvent | null> => {
const teamId = ((event.data as Record<string, unknown>).teamId as string) || '';
): Promise<TeamArrivalEvent | null> => {
const data = event.data as Record<string, unknown>;
const teamId = (data.teamId as string) || '';

if (!teamId) {
return null;
}

const result: TeamEvent = {
teamId
const result: TeamArrivalEvent = {
teamId,
arrived: data.arrived !== false
};

return result;
Expand Down
4 changes: 3 additions & 1 deletion apps/frontend/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@
"arrived": "Arrived",
"all-arrived": "All teams arrived! Great job 🎉",
"waiting-for": "Waiting for {count, plural, one {# team} other {# teams}}",
"teams-pending": "{count, plural, one {# team pending} other {# teams pending}}"
"teams-pending": "{count, plural, one {# team pending} other {# teams pending}}",
"arrived-teams": "{count, plural, one {# Arrived Team} other {# Arrived Teams}}",
"mark-not-arrived": "Mark as not arrived"
},
"team-input": {
"title": "Register Team Arrival",
Expand Down
4 changes: 3 additions & 1 deletion apps/frontend/locale/he.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@
"arrived": "הגיעו",
"all-arrived": "כל הקבוצות הגיעו! עבודה מעולה 🎉",
"waiting-for": "-מחכים ל {count, plural, one {קבוצה} other {# קבוצות}}",
"teams-pending": "{count, plural, one {קבוצה טרם הגיעו} other {# קבוצות טרם הגיעו}}"
"teams-pending": "{count, plural, one {קבוצה טרם הגיעו} other {# קבוצות טרם הגיעו}}",
"arrived-teams": "{count, plural, one {קבוצה הגיעה} other {# קבוצות הגיעו}}",
"mark-not-arrived": "סמן כלא הגיעה"
},
"team-input": {
"title": "רישום הגעת קבוצה",
Expand Down
4 changes: 3 additions & 1 deletion apps/frontend/locale/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@
"arrived": "Przybyli",
"all-arrived": "Wszystkie drużyny przybyły! Świetna robota 🎉",
"waiting-for": "Oczekiwanie na {count, plural, one {# drużynę} other {# drużyny}}",
"teams-pending": "{count, plural, one {# drużyna w oczekiwaniu} other {# drużyny w oczekiwaniu}}"
"teams-pending": "{count, plural, one {# drużyna w oczekiwaniu} other {# drużyny w oczekiwaniu}}",
"arrived-teams": "{count, plural, one {# drużyna przybyła} other {# drużyny przybyły}}",
"mark-not-arrived": "Oznacz jako nieprzybyłą"
},
"team-input": {
"title": "Zarejestruj przybycie drużyny",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { SubscriptionVars, QueryData, JudgingSession } from '../types';

interface TeamEvent {
teamId: string;
arrived: boolean;
}

interface TeamArrivalSubscriptionData {
Expand All @@ -18,6 +19,7 @@ export const TEAM_ARRIVAL_UPDATED_SUBSCRIPTION: TypedDocumentNode<
subscription TeamArrivalUpdated($divisionId: String!) {
teamArrivalUpdated(divisionId: $divisionId) {
teamId
arrived
}
}
`;
Expand Down Expand Up @@ -73,13 +75,13 @@ export function createTeamArrivalSubscription(
const updateQuery = (prev: QueryData, { data }: { data?: unknown }) => {
if (!data) return prev;

const { teamId } = (data as TeamArrivalSubscriptionData).teamArrivalUpdated;
const { teamId, arrived } = (data as TeamArrivalSubscriptionData).teamArrivalUpdated;

return updateJudgingSessions(prev, sessions =>
updateInArray(
sessions,
session => session.team.id === teamId,
session => merge(session, { team: { arrived: true } })
session => merge(session, { team: { arrived } })
)
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { SubscriptionVars, QueryData, JudgingSession } from '../types';

interface TeamEvent {
teamId: string;
arrived: boolean;
}

interface TeamArrivalSubscriptionData {
Expand All @@ -18,6 +19,7 @@ export const TEAM_ARRIVAL_UPDATED_SUBSCRIPTION: TypedDocumentNode<
subscription TeamArrivalUpdated($divisionId: String!) {
teamArrivalUpdated(divisionId: $divisionId) {
teamId
arrived
}
}
`;
Expand Down Expand Up @@ -74,13 +76,13 @@ export function createTeamArrivalSubscription(
const updateQuery = (prev: QueryData, { data }: { data?: unknown }) => {
if (!data) return prev;

const { teamId } = (data as TeamArrivalSubscriptionData).teamArrivalUpdated;
const { teamId, arrived } = (data as TeamArrivalSubscriptionData).teamArrivalUpdated;

return updateJudgingSessions(prev, sessions =>
updateInArray(
sessions,
session => session.team.id === teamId,
session => merge(session, { team: { arrived: true } })
session => merge(session, { team: { arrived } })
)
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { SubscriptionVars, QueryData, JudgingSession } from '../types';

interface TeamEvent {
teamId: string;
arrived: boolean;
}

interface TeamArrivalSubscriptionData {
Expand All @@ -18,6 +19,7 @@ export const TEAM_ARRIVAL_UPDATED_SUBSCRIPTION: TypedDocumentNode<
subscription TeamArrivalUpdated($divisionId: String!) {
teamArrivalUpdated(divisionId: $divisionId) {
teamId
arrived
}
}
`;
Expand Down Expand Up @@ -74,13 +76,13 @@ export function createTeamArrivalSubscription(
const updateQuery = (prev: QueryData, { data }: { data?: unknown }) => {
if (!data) return prev;

const { teamId } = (data as TeamArrivalSubscriptionData).teamArrivalUpdated;
const { teamId, arrived } = (data as TeamArrivalSubscriptionData).teamArrivalUpdated;

return updateJudgingSessions(prev, sessions =>
updateInArray(
sessions,
session => session.team.id === teamId,
session => merge(session, { team: { arrived: true } })
session => merge(session, { team: { arrived } })
)
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import type { SubscriptionConfig } from '../../../../hooks/use-page-data';
import type { McData } from '../types';

interface TeamArrivedEvent {
matchId: string;
teamId: string;
arrived: boolean;
}

interface TeamArrivedSubscriptionData {
teamArrived: TeamArrivedEvent;
teamArrivalUpdated: TeamArrivedEvent;
}

interface SubscriptionVars {
Expand All @@ -23,7 +22,6 @@ export const TEAM_ARRIVED_SUBSCRIPTION: TypedDocumentNode<
> = gql`
subscription TeamArrived($divisionId: String!) {
teamArrivalUpdated(divisionId: $divisionId) {
matchId
teamId
arrived
}
Expand All @@ -36,22 +34,18 @@ export function createTeamArrivedSubscription(divisionId: string) {
subscriptionVariables: { divisionId },
updateQuery: (prev: McData, { data }: { data?: unknown }) => {
if (!prev.division?.field || !data) return prev;
const event = (data as TeamArrivedSubscriptionData).teamArrived;
const event = (data as TeamArrivedSubscriptionData).teamArrivalUpdated;
return merge(prev, {
division: {
field: {
matches: prev.division.field.matches.map(_match =>
_match.id === event.matchId
? {
..._match,
participants: _match.participants.map(p =>
p.team?.id === event.teamId
? { ...p, team: p.team ? { ...p.team, arrived: event.arrived } : null }
: p
)
}
: _match
)
matches: prev.division.field.matches.map(_match => ({
..._match,
participants: _match.participants.map(p =>
p.team?.id === event.teamId
? { ...p, team: { ...p.team, arrived: event.arrived } }
: p
)
}))
}
}
});
Expand Down
Loading
Loading