Skip to content

Commit

Permalink
Can join games
Browse files Browse the repository at this point in the history
  • Loading branch information
jarrod-lowe committed Sep 1, 2024
1 parent ed7256f commit 53f2a92
Show file tree
Hide file tree
Showing 18 changed files with 936 additions and 53 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ terraform/environment/*/output.json
graphql/node_modules
graphql/mutation/*/appsync.js
graphql/query/*/appsync.js
graphql/function/*/appsync.js
graphql/coverage/
graphql/environment.json

Expand Down
27 changes: 19 additions & 8 deletions appsync/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,35 +34,46 @@ export type Game = {
gameDescription?: Maybe<Scalars['String']['output']>;
gameId: Scalars['ID']['output'];
gameName: Scalars['String']['output'];
joinToken?: Maybe<Scalars['String']['output']>;
players?: Maybe<Array<Scalars['ID']['output']>>;
privateNotes?: Maybe<Scalars['String']['output']>;
publicNotes?: Maybe<Scalars['String']['output']>;
type: Scalars['String']['output'];
updatedAt: Scalars['AWSDateTime']['output'];
};

export type GameSummary = {
__typename?: 'GameSummary';
gameDescription: Scalars['String']['output'];
gameId: Scalars['ID']['output'];
gameName: Scalars['String']['output'];
type: Scalars['String']['output'];
export type JoinGameInput = {
gameId: Scalars['ID']['input'];
joinToken: Scalars['ID']['input'];
};

export type Mutation = {
__typename?: 'Mutation';
createGame: Game;
joinGame: Game;
};


export type MutationCreateGameArgs = {
input: CreateGameInput;
};


export type MutationJoinGameArgs = {
input: JoinGameInput;
};

export type PlayerSheetSummary = {
__typename?: 'PlayerSheetSummary';
gameDescription: Scalars['String']['output'];
gameId: Scalars['ID']['output'];
gameName: Scalars['String']['output'];
type: Scalars['String']['output'];
};

export type Query = {
__typename?: 'Query';
getGame: Game;
getGames?: Maybe<Array<GameSummary>>;
getGames?: Maybe<Array<PlayerSheetSummary>>;
};


Expand Down
16 changes: 12 additions & 4 deletions appsync/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
export const getGameQuery = `
query getGame($id: ID!) {
getGame(id: $id) {
gameId gameName gameDescription publicNotes privateNotes fireflyUserId players createdAt updatedAt type
gameId gameName gameDescription publicNotes fireflyUserId players joinToken createdAt updatedAt type
}
}
`;

export const getGamesQuery = `
query getGames {
getGames {
query getGames() {
getGames() {
gameId gameName gameDescription type
}
}
Expand All @@ -21,7 +21,15 @@
export const createGameMutation = `
mutation createGame($input: CreateGameInput!) {
createGame(input: $input) {
gameId gameName gameDescription publicNotes privateNotes fireflyUserId players createdAt updatedAt type
gameId gameName gameDescription publicNotes fireflyUserId players joinToken createdAt updatedAt type
}
}
`;

export const joinGameMutation = `
mutation joinGame($input: JoinGameInput!) {
joinGame(input: $input) {
gameId gameName gameDescription publicNotes fireflyUserId players joinToken createdAt updatedAt type
}
}
`;
Expand Down
2 changes: 1 addition & 1 deletion design/PLANNING.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ Assistant:
* `PK`: `GAME#<gameId>`
* `SK`: `METADATA`
* `gameId`: gameId
* Other fields: `gameName`, `gameDescription`, `publicNotes`, `privateNotes`, `fireflyUserId`, `createdAt`, `updatedAt`, `GSI1PK`
* Other fields: `gameName`, `gameDescription`, `publicNotes`, `fireflyUserId`, `createdAt`, `updatedAt`, `GSI1PK`

3. **GM Info**
* `PK`: `GAME#<gameId>`
Expand Down
56 changes: 56 additions & 0 deletions graphql/function/fnGetGameWithToken/appsync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { util, Context, AppSyncIdentityCognito } from "@aws-appsync/utils";
import type { DynamoDBGetItemRequest } from "@aws-appsync/utils/lib/resolver-return-types";
import type { Game, JoinGameInput } from "../../../appsync/graphql";

export function request(
context: Context<{ input: JoinGameInput }>,
): DynamoDBGetItemRequest {
if (!context.identity) {
util.error("Unauthorized: Identity information is missing." as string);
}

const identity = context.identity as AppSyncIdentityCognito;
if (!identity.sub) {
util.error("Unauthorized: User ID is missing." as string);
}

const id = context.arguments.input.gameId;
const key = {
PK: "GAME#" + id,
SK: "GAME",
};

return {
operation: "GetItem",
key: util.dynamodb.toMapValues(key),
};
}

export function response(
context: Context<{ input: JoinGameInput }>,
): Game | undefined {
if (context.error) {
util.error(context.error.message, context.error.type, context.result);
}

if (
context.result === null ||
context.result.joinToken !== context.arguments.input.joinToken
) {
util.error(
"Game not found or invalid token" as string,
"AccessDeniedException",
);
}

const identity = context.identity as AppSyncIdentityCognito;
if (identity.sub === context.result.fireflyUserId) {
util.error("You cannot join your own game" as string, "Conflict");
}

if (context.result.players?.includes(identity.sub)) {
util.error("You are already a player in this game" as string, "Conflict");
}

return context.result;
}
74 changes: 74 additions & 0 deletions graphql/function/fnJoinGame/appsync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { util, Context, AppSyncIdentityCognito } from "@aws-appsync/utils";
import type { PutItemInputAttributeMap } from "@aws-appsync/utils/lib/resolver-return-types";
import environment from "../../environment.json";
import type { Game, JoinGameInput } from "../../../appsync/graphql";

export function request(context: Context<{ input: JoinGameInput }>): unknown {
if (!context.identity) {
util.error("Unauthorized: Identity information is missing." as string);
}

const identity = context.identity as AppSyncIdentityCognito;
if (!identity.sub) {
util.error("Unauthorized: User ID is missing." as string);
}

const id = context.arguments.input.gameId;
const timestamp = util.time.nowISO8601();

const gameItem = {
operation: "UpdateItem",
table: "Wildsea-" + environment.name,
key: util.dynamodb.toMapValues({
PK: "GAME#" + id,
SK: "GAME",
}),
update: {
expression: "ADD #players :player SET #updatedAt = :updatedAt",
expressionNames: {
"#players": "players",
"#updatedAt": "updatedAt",
},
expressionValues: {
":player": { SS: [identity.sub] },
":updatedAt": { S: timestamp },
},
},
} as PutItemInputAttributeMap;

const playerItem = {
operation: "PutItem",
table: "Wildsea-" + environment.name,
key: util.dynamodb.toMapValues({
PK: "GAME#" + id,
SK: "PLAYER#PC#" + identity.sub,
}),
attributeValues: util.dynamodb.toMapValues({
gameId: id,
userId: identity.sub,
GSI1PK: "USER#" + identity.sub,
gameName: context.prev.result.gameName,
gameDescription: context.prev.result.gameDescription,
characterName: "Unnamed Character",
type: "CHARACTER",
createdAt: timestamp,
updatedAt: timestamp,
}),
} as PutItemInputAttributeMap;

return {
operation: "TransactWriteItems",
transactItems: [gameItem, playerItem],
};
}

export function response(
context: Context<{ input: JoinGameInput }>,
): Game | undefined {
if (context.error) {
util.error(context.error.message, context.error.type, context.result);
}

context.prev.result.joinToken = null;
return context.prev.result;
}
19 changes: 5 additions & 14 deletions graphql/mutation/createGame/appsync.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
import { util, Context, AppSyncIdentityCognito } from "@aws-appsync/utils";
import type { PutItemInputAttributeMap } from "@aws-appsync/utils/lib/resolver-return-types";
import environment from "../../environment.json";
import { Game } from "../../../appsync/graphql";

/**
* A CreateGameInput creates a Game.
* They are stored in DynamoDB with a PK of `GAME#<id>` and an SK of `GAME`.
* The ID is a UUID
* The fireflyUserId is the Cognito ID of the user
*/

interface CreateGameInput {
name: string;
description?: string;
}
import { Game, CreateGameInput } from "../../../appsync/graphql";

export function request(context: Context<{ input: CreateGameInput }>): unknown {
if (!context.identity) {
Expand All @@ -27,6 +15,7 @@ export function request(context: Context<{ input: CreateGameInput }>): unknown {

const input = context.arguments.input;
const id = util.autoId();
const joinToken = util.autoId();
const timestamp = util.time.nowISO8601();

context.stash.record = {
Expand All @@ -35,6 +24,7 @@ export function request(context: Context<{ input: CreateGameInput }>): unknown {
gameId: id,
fireflyUserId: identity.sub,
// players: no value yet
joinToken: joinToken,
createdAt: timestamp,
updatedAt: timestamp,
type: "GAME",
Expand All @@ -61,10 +51,11 @@ export function request(context: Context<{ input: CreateGameInput }>): unknown {
gameId: id,
gameName: input.name,
gameDescription: input.description,
characterName: "Firefly",
GSI1PK: "USER#" + identity.sub,
createdAt: timestamp,
updatedAt: timestamp,
type: "CHARACTER",
type: "FIREFLY",
}) as PutItemInputAttributeMap,
};

Expand Down
6 changes: 3 additions & 3 deletions graphql/query/getGames/appsync.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { util, Context, AppSyncIdentityCognito } from "@aws-appsync/utils";
import type { DynamoDBQueryRequest } from "@aws-appsync/utils/lib/resolver-return-types";
import type { GameSummary } from "../../../appsync/graphql";
import type { PlayerSheetSummary } from "../../../appsync/graphql";

export function request(context: Context): DynamoDBQueryRequest {
if (!context.identity) {
Expand All @@ -27,10 +27,10 @@ export function request(context: Context): DynamoDBQueryRequest {
};
}

export function response(context: Context): GameSummary[] {
export function response(context: Context): PlayerSheetSummary[] {
if (context.error) {
util.appendError(context.error.message, context.error.type, context.result);
return [];
}
return context.result.items as GameSummary[];
return context.result.items as PlayerSheetSummary[];
}
15 changes: 9 additions & 6 deletions graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,38 @@ directive @aws_cognito_user_pools(

type Mutation {
createGame(input: CreateGameInput!): Game! @aws_cognito_user_pools
joinGame(input: JoinGameInput!): Game! @aws_cognito_user_pools
}

type Query {
getGame(id: ID!): Game! @aws_cognito_user_pools
getGames: [GameSummary!] @aws_cognito_user_pools
getGames: [PlayerSheetSummary!] @aws_cognito_user_pools
}

input CreateGameInput {
name: String!
description: String
}

input JoinGameInput {
gameId: ID!
joinToken: ID!
}

type Game @aws_cognito_user_pools {
gameId: ID!
gameName: String!
gameDescription: String
publicNotes: String
privateNotes: String
fireflyUserId: ID!
players: [ID!]
# playerSheets: [PlayerSheet!]!
# shipSheet: ShipSheet
# clocks: [Clock!]!
joinToken: String
createdAt: AWSDateTime!
updatedAt: AWSDateTime!
type: String!
}

type GameSummary @aws_cognito_user_pools {
type PlayerSheetSummary @aws_cognito_user_pools {
gameId: ID!
gameName: String!
gameDescription: String!
Expand Down
9 changes: 7 additions & 2 deletions graphql/tests/createGame.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,12 @@ describe("request function", () => {
};

const mockId = "unique-id";
const mockJoinToken = "unique-join-token";
const mockTimestamp = "2024-08-17T00:00:00Z";

(util.autoId as jest.Mock).mockReturnValue(mockId);
(util.autoId as jest.Mock)
.mockReturnValueOnce(mockId)
.mockReturnValueOnce(mockJoinToken);
(util.time.nowISO8601 as jest.Mock).mockReturnValue(mockTimestamp);

// Act
Expand All @@ -82,6 +85,7 @@ describe("request function", () => {
gameDescription: { S: "Test Description" },
gameId: { S: mockId },
fireflyUserId: { S: "1234-5678-91011" },
joinToken: { S: mockJoinToken },
createdAt: { S: mockTimestamp },
updatedAt: { S: mockTimestamp },
type: { S: "GAME" },
Expand All @@ -102,7 +106,8 @@ describe("request function", () => {
GSI1PK: { S: "USER#1234-5678-91011" },
createdAt: { S: mockTimestamp },
updatedAt: { S: mockTimestamp },
type: { S: "CHARACTER" },
type: { S: "FIREFLY" },
characterName: { S: "Firefly" },
},
},
],
Expand Down
Loading

0 comments on commit 53f2a92

Please sign in to comment.