Skip to content

Commit

Permalink
Can add and edit your sheet sections (#108)
Browse files Browse the repository at this point in the history
  • Loading branch information
jarrod-lowe authored Sep 21, 2024
1 parent 016df0b commit e8e7cba
Show file tree
Hide file tree
Showing 25 changed files with 292 additions and 106 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ clean:
rm -f terraform/environment/*/plan.tfplan
rm -f graphql/mutation/*/appsync.js
rm -f graphql/query/*/appsync.js
rm -f graphql/functions/*/appsync.js
rm -rf graphql/node_modules
rm -rf graphql/coverage
rm -rf appsync/node_modules
Expand Down
15 changes: 11 additions & 4 deletions appsync/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ export type CreateGameInput = {
name: Scalars['String']['input'];
};

export type CreateSectionInput = {
content?: InputMaybe<Scalars['AWSJSON']['input']>;
gameId: Scalars['ID']['input'];
sectionName: Scalars['String']['input'];
sectionType: Scalars['String']['input'];
};

export type Game = {
__typename?: 'Game';
createdAt: Scalars['AWSDateTime']['output'];
Expand Down Expand Up @@ -59,7 +66,7 @@ export type MutationCreateGameArgs = {


export type MutationCreateSectionArgs = {
input: UpdateSectionInput;
input: CreateSectionInput;
};


Expand Down Expand Up @@ -106,7 +113,7 @@ export type QueryGetGameArgs = {

export type SheetSection = {
__typename?: 'SheetSection';
content: Scalars['String']['output'];
content: Scalars['AWSJSON']['output'];
createdAt: Scalars['AWSDateTime']['output'];
gameId: Scalars['ID']['output'];
sectionId: Scalars['ID']['output'];
Expand All @@ -118,9 +125,9 @@ export type SheetSection = {
};

export type UpdateSectionInput = {
content?: InputMaybe<Scalars['String']['input']>;
content: Scalars['AWSJSON']['input'];
gameId: Scalars['ID']['input'];
sectionId: Scalars['ID']['input'];
sectionName: Scalars['String']['input'];
sectionType: Scalars['String']['input'];
userId: Scalars['ID']['input'];
};
2 changes: 1 addition & 1 deletion appsync/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
`;

export const createSectionMutation = `
mutation createSection($input: UpdateSectionInput!) {
mutation createSection($input: CreateSectionInput!) {
createSection(input: $input) {
userId gameId sectionId type sectionName sectionType content createdAt updatedAt
}
Expand Down
76 changes: 14 additions & 62 deletions design/PLANNING.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,80 +299,32 @@ Assistant:
* `gameId`: gameId
* Other fields: `gameName`, `gameDescription`, `fireflyUserId`, `createdAt`, `updatedAt`, `GSI1PK`

1. **GM Info**
1. **Player Sheet** (Includes GM Sheet)
* `PK`: `GAME#<gameId>`
* `SK`: `PLAYER#GM#<userId>`
* `SK`: `PLAYER#<userId>`
* `GSI1PK`: `USER#<userId>`
* `characterName`
* `userId`: userId
* `gameId`: gameId
* gameName and gameDescription: Duplicated from game
* Other fields: `createdAt`, `updatedAt`

1. **Player Sheet**
1. **Player Sheet Sections**
* `PK`: `GAME#<gameId>`
* `SK`: `PLAYER#PC#<userId>`
* `GSI1PK`: `USER#<userId>`
* `SK`: `SECTION#<userId>#<section>`
* `userId`: userId
* `gameId`: gameId

1. **Player Sheet Sections**
* `PK`: `GAME#<gameId>`
* `SK`: `PLAYER#SECTION#<userId>#<section>#<item>`
* `dataType`: The type of the data (key/value, ticks, clock, ...)
* Each section is one of: (TODO: Modify)
* `notes` - text
* `publicNotes` (String)
* `privateNotes` (String)
* `secretNotes` (String)
* `fireflyNotes` (String)
* `strings` - key/value pairs
* `characterName` (String)
* `pronouns` (String)
* `bloodline` (String)
* `origin` (String)
* `post` (String)
* `milestones` (List of Maps)
* Each map in the list represents a milestone and contains fields like `type` (String: "Major" or "Minor"), `description` (String)
* `drives` (List of Strings)
* `mires` (List of Maps)
* Each map in the list represents a mire and contains fields like `description` (String), `notes` (String), `level` (Number: 0, 1, or 2)
* `edges` (List of Maps)
* Each map in the list represents an edge and contains fields like `name` (String), `description` (String)
* `skills` (List of Maps)
* Each map in the list represents a skill and contains fields like `name` (String), `description` (String), `level` (Number: 0, 1, 2, or 3)
* `languages` (List of Maps)
* Each map in the list represents a language and contains fields like `name` (String), `description` (String), `level` (Number: 0, 1, 2, or 3)
* `resources` (List of Maps)
* Each map in the list represents a resource and contains fields like `type` (String: "Salvage", "Specimens", "Whispers", or "Charts"), `description` (String), `tags` (List of Strings)
* `aspects` (List of Maps)
* Each map in the list represents an aspect and contains fields like `name` (String), `type` (String: "Trait", "Gear", or "Companion"), `track` (Map with fields like `length` (Number) and `currentValue` (Number)), `description` (String)
* `temporaryTracks` (List of Maps)
* Each map in the list represents a temporary track and contains fields like `name` (String), `type` (String: "Benefit", "Injury", or "Track"), `track` (Map with fields like `length` (Number) and `currentValue` (Number)), `description` (String)
* `sectionType`: The type of the data (key/value, ticks, clock, ...)
* `sectionName`
* `content` - sectionType-specific JSON
* Other fields: `createdAt`, `updatedAt`
* gameName and gameDescription: Duplicated from game

1. **Ship Sheet**
* `PK`: `GAME#<gameId>`
* `SK`: `SHIP#<shipId>`
* `characterName` (String)
* Each section is one of: (TODO: Modify)
* `publicNotes` (String)
* `gmNotes` (String)
* `conditions` (List of Strings)
* `stakesUsed` (Number)
* `stakesTotal` (Number)
* `ratings` (List of Maps)
* Each map in the list represents a rating and contains fields like `name` (String: "Armour", "Seals", "Speed", "Saws", "Stealth", or "Tilt"), `track` (Map with fields like `length` (Number) and `currentValue` (Number))
* `design` (Map)
* Contains fields like `size` (String), `frame` (String), `hull` (String), `bite` (String), `engine` (String)
* `fittings` (Map)
* Contains fields like `name` (String), `description` (String) and `type` (String)
* The possible types will be in `SEED#FITTINGSTYPES`
* `undercrew` (List of Maps)
* Each map in the list represents an undercrew and contains fields like `name` (String), `type` (String: "Officer", "Gang", or "Pack"), `track` (Map with fields like `length` (Number) and `currentValue` (Number)), `description` (String)
* `reputation` (List of Maps)
* Each map in the list represents a reputation and contains fields like `name` (String), `track` (Map with fields like `length` (Number) and `currentValue` (Number))
* `cargo` (List of Strings)
* `passengers` (List of Strings)
* `SK`: `PLAYER#SHIP#<shipId>`
* `GSI1PK`: `SHIP#<userId>`
* `characterName`
* `userId`: userId
* `gameId`: gameId
* Other fields: `createdAt`, `updatedAt`

1. **Clock**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { util, Context, AppSyncIdentityCognito } from "@aws-appsync/utils";
import type { DynamoDBGetItemRequest } from "@aws-appsync/utils/lib/resolver-return-types";
import type { DataPlayerSheet } from "../../lib/dataTypes";
import type { MutationCreateSectionArgs } from "../../../appsync/graphql";

export function request(
context: Context<MutationCreateSectionArgs>,
): 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: "PLAYER#" + identity.sub,
};

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

type ResponseContext = Context<
MutationCreateSectionArgs,
Record<string, unknown>,
undefined,
undefined,
DataPlayerSheet
>;

export function response(
context: ResponseContext,
): DataPlayerSheet | undefined {
if (context.error) {
util.appendError(context.error.message, context.error.type, context.result);
return;
}

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

if (identity.sub != context.result.userId) {
util.appendError(
"Unauthorized: User does not have access to the player sheet." as string,
);
return;
}

return context.result;
}
51 changes: 51 additions & 0 deletions graphql/function/fnCreateSection/createSection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { util, Context, AppSyncIdentityCognito } from "@aws-appsync/utils";
import type {
DynamoDBPutItemRequest,
PutItemInputAttributeMap,
} from "@aws-appsync/utils/lib/resolver-return-types";
import { Game, CreateSectionInput } from "../../../appsync/graphql";
import { TypeSection } from "../../lib/constants";

export function request(
context: Context<{ input: CreateSectionInput }>,
): DynamoDBPutItemRequest {
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 input = context.arguments.input;
const sectionId = util.autoId();
const timestamp = util.time.nowISO8601();

return {
operation: "PutItem",
key: util.dynamodb.toMapValues({
PK: "GAME#" + input.gameId,
SK: TypeSection + "#" + sectionId,
}),
attributeValues: util.dynamodb.toMapValues({
gameId: input.gameId,
userId: identity.sub,
sectionId: sectionId,
sectionName: input.sectionName,
sectionType: input.sectionType,
content: input.content,
createdAt: timestamp,
updatedAt: timestamp,
type: TypeSection,
}) as PutItemInputAttributeMap,
};
}

export function response(context: Context): Game | null {
if (context.error) {
util.appendError(context.error.message, context.error.type, context.result);
return null;
}
return context.result;
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function request(context: Context<{ input: JoinGameInput }>): unknown {
table: "Wildsea-" + environment.name,
key: util.dynamodb.toMapValues({
PK: "GAME#" + id,
SK: "PLAYER#PC#" + identity.sub,
SK: "PLAYER#" + identity.sub,
}),
attributeValues: util.dynamodb.toMapValues({
gameId: id,
Expand Down
10 changes: 6 additions & 4 deletions graphql/graphql.mk
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
graphql/%/appsync.js: graphql/node_modules graphql/%/appsync.ts appsync/graphql.ts appsync/schema.ts graphql/environment.json
graphql/%/appsync.js: graphql/node_modules graphql/%/*.ts appsync/graphql.ts appsync/schema.ts graphql/environment.json
echo $(GRAPHQL_TS)
echo $(GRAPHQL_JS)
cd graphql && \
esbuild $*/*.ts \
--bundle \
Expand All @@ -8,7 +10,7 @@ graphql/%/appsync.js: graphql/node_modules graphql/%/appsync.ts appsync/graphql.
--target=esnext \
--sourcemap=inline \
--sources-content=false \
--outdir=$*
--outfile=$*/appsync.js

.PRECIOUS: graphql/environment.json
graphql/environment.json:
Expand All @@ -17,8 +19,8 @@ graphql/environment.json:
graphql/node_modules: graphql/package.json
cd graphql && npm install && npm ci \

GRAPHQL_TS := $(wildcard graphql/*/*/appsync.ts)
GRAPHQL_JS := $(patsubst %.ts,%.js,$(GRAPHQL_TS))
GRAPHQL_TS := $(wildcard graphql/function/*/*.ts) $(wildcard graphql/mutation/*/*.ts) $(wildcard graphql/query/*/*.ts)
GRAPHQL_JS := $(foreach file,$(GRAPHQL_TS),$(dir $(file))appsync.js)
GRAPHQL_DEV := graphql-eslint graphql-test

.PHONY: graphql
Expand Down
4 changes: 2 additions & 2 deletions graphql/lib/dataTypes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { AppGame, PlayerSheet, SheetSection } from "../../appsync/graphql";
import type { Game, PlayerSheet, SheetSection } from "../../appsync/graphql";

// define a type DataGame, like Game, except players -> playerSheets (PlayerSheet[]), and fireflyUserId -> fireflySheet (PlayerSheet)

export type DataGame = Omit<AppGame, "playerSheets"> & {
export type DataGame = Omit<Game, "playerSheets"> & {
players: string[];
fireflyUserId: string;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function request(context: Context<{ input: CreateGameInput }>): unknown {
const fireflyItem = {
key: util.dynamodb.toMapValues({
PK: "GAME#" + id,
SK: "PLAYER#GM#" + identity.sub,
SK: "PLAYER#" + identity.sub,
}),
operation: "PutItem",
table: "Wildsea-" + environment.name,
Expand Down
62 changes: 62 additions & 0 deletions graphql/mutation/updateSection/updateSection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { util, Context, AppSyncIdentityCognito } from "@aws-appsync/utils";
import type { DynamoDBUpdateItemRequest } from "@aws-appsync/utils/lib/resolver-return-types";
import { SheetSection, UpdateSectionInput } from "../../../appsync/graphql";
import { TypeSection } from "../../lib/constants";

export function request(
context: Context<{ input: UpdateSectionInput }>,
): DynamoDBUpdateItemRequest {
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 input = context.arguments.input;
const timestamp = util.time.nowISO8601();
const sk = TypeSection + "#" + input.sectionId;

return {
operation: "UpdateItem",
key: util.dynamodb.toMapValues({
PK: "GAME#" + input.gameId,
SK: sk,
}),
condition: {
expression: "#SK = :SK AND #userId = :userId",
expressionNames: {
"#SK": "SK",
"#userId": "userId",
},
expressionValues: util.dynamodb.toMapValues({
":SK": sk,
":userId": identity.sub,
}),
},
update: {
expression:
"SET #sectionName = :sectionName, #content = :content, #updatedAt = :updatedAt",
expressionNames: {
"#updatedAt": "updatedAt",
"#content": "content",
"#sectionName": "sectionName",
},
expressionValues: util.dynamodb.toMapValues({
":updatedAt": timestamp,
":content": input.content,
":sectionName": input.sectionName,
}),
},
};
}

export function response(context: Context): SheetSection | null {
if (context.error) {
util.appendError(context.error.message, context.error.type, context.result);
return null;
}
return context.result;
}
File renamed without changes.
Loading

0 comments on commit e8e7cba

Please sign in to comment.