diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 3e387825..2240dbbd 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,9 +1,9 @@ -FROM mcr.microsoft.com/devcontainers/javascript-node:18 +FROM mcr.microsoft.com/devcontainers/javascript-node:22 # [Optional] Uncomment this section to install additional OS packages. # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # && apt-get -y install --no-install-recommends -RUN curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null && echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | sudo tee /etc/apt/sources.list.d/ngrok.list && sudo apt update && sudo apt install ngrok +# RUN curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null && echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | sudo tee /etc/apt/sources.list.d/ngrok.list && sudo apt update && sudo apt install ngrok # [Optional] Uncomment if you want to install an additional version of node using nvm # ARG EXTRA_NODE_VERSION=10 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d960e700..41ff9fbd 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -45,19 +45,16 @@ "containerEnv": { "HOME": "/home/node", - "DB_HOST": "localhost", - "DB_PORT": "5432", - "DB_NAME": "danceblue", - "DB_UNAME": "danceblue", - "DB_PWD": "danceblue", "DATABASE_URL": "postgres://danceblue:danceblue@localhost:5432/danceblue?schema=danceblue", "NODE_ENV": "development", "APPLICATION_HOST": "localhost", "APPLICATION_PORT": "8000", "MS_OIDC_URL": "https://login.microsoftonline.com/2b30530b-69b6-4457-b818-481cb53d42ae/v2.0/.well-known/openid-configuration", + "SERVE_ORIGIN": "http://localhost:8000", "SERVE_PATH": "/workspaces/monorepo/local-uploads", "UPLOAD_PATH": "/workspaces/monorepo/local-uploads", - "MAX_FILE_SIZE": "200" + "MAX_FILE_SIZE": "200", + "LOG_DIR": "/workspaces/monorepo/packages/server/logs" }, "customizations": { diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 0a63aef1..2ae97194 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -19,7 +19,7 @@ env: SERVER_IMAGE_NAME: ukdanceblue/app-server jobs: - build-and-push-portal-image: + build-and-push-images: runs-on: ubuntu-latest permissions: contents: read @@ -27,126 +27,54 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Enable Corepack before setting up Node - run: corepack enable - - name: Set up node 18.x - uses: actions/setup-node@v4.0.2 - with: - node-version: 18 - cache: yarn - - name: Install dependencies - working-directory: ./packages/portal - run: - yarn workspaces focus @ukdanceblue/common @ukdanceblue/portal - @ukdanceblue/monorepo - - name: Build GraphQL - run: yarn run gql:build - working-directory: . - - name: Build common - run: yarn run build - working-directory: ./packages/common - - name: Build portal - run: yarn run build - working-directory: ./packages/portal # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. - name: Log in to the Container registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 + id: portalMeta + uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.PORTAL_IMAGE_NAME }} - # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. - # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. - # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. - - name: Build and push Docker image - uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 - with: - context: ./packages/portal - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - build-and-push-server-image: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - name: Enable Corepack before setting up Node - run: corepack enable - - name: Set up node 18.x - uses: actions/setup-node@v4.0.2 - with: - node-version: 18 - cache: yarn - - name: Install dependencies - working-directory: ./packages/server - run: - yarn workspaces focus @ukdanceblue/common @ukdanceblue/server - @ukdanceblue/monorepo - - name: Build common - run: yarn run build - working-directory: ./packages/common - - name: Generate prisma client - run: yarn dlx prisma generate - working-directory: ./packages/server - - name: Build server - run: yarn run build - working-directory: ./packages/server - - name: Focus on server - run: - yarn workspaces focus --production @ukdanceblue/server - @ukdanceblue/common - # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. - - name: Log in to the Container registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 + id: serverMeta + uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.SERVER_IMAGE_NAME }} # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. - - name: Build and push Docker image - uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 + - name: Build and push Portal image + uses: docker/build-push-action@v6 with: context: . - file: ./packages/server/Dockerfile push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - trigger-recreate-release: - runs-on: ubuntu-latest - needs: [build-and-push-portal-image, build-and-push-server-image] - if: github.ref == 'refs/heads/release' - steps: - - name: Trigger container recreate - shell: bash - run: | - ((curl --insecure -X POST ${{ secrets.DANCEBLUE_APP_PORTAL_RECREATE_WEBHOOK }} -H "CF-Access-Client-Id: ${{ secrets.CLOUDFLARE_ACCESS_SERVICE_TOKEN_CLIENT_ID }}" -H "CF-Access-Client-Secret: ${{ secrets.CLOUDFLARE_ACCESS_SERVICE_TOKEN_CLIENT_SECRET }}") || exit 1) - ((curl --insecure -X POST ${{ secrets.DANCEBLUE_APP_SERVER_RECREATE_WEBHOOK }} -H "CF-Access-Client-Id: ${{ secrets.CLOUDFLARE_ACCESS_SERVICE_TOKEN_CLIENT_ID }}" -H "CF-Access-Client-Secret: ${{ secrets.CLOUDFLARE_ACCESS_SERVICE_TOKEN_CLIENT_SECRET }}") || exit 1) - trigger-recreate-main: - runs-on: ubuntu-latest - needs: [build-and-push-portal-image, build-and-push-server-image] - if: github.ref == 'refs/heads/main' - steps: - - name: Trigger container recreate - shell: bash - run: | - ((curl --insecure -X POST ${{ secrets.DANCEBLUE_APP_DEV_PORTAL_RECREATE_WEBHOOK }} -H "CF-Access-Client-Id: ${{ secrets.CLOUDFLARE_ACCESS_SERVICE_TOKEN_CLIENT_ID }}" -H "CF-Access-Client-Secret: ${{ secrets.CLOUDFLARE_ACCESS_SERVICE_TOKEN_CLIENT_SECRET }}") || exit 1) - ((curl --insecure -X POST ${{ secrets.DANCEBLUE_APP_DEV_SERVER_RECREATE_WEBHOOK }} -H "CF-Access-Client-Id: ${{ secrets.CLOUDFLARE_ACCESS_SERVICE_TOKEN_CLIENT_ID }}" -H "CF-Access-Client-Secret: ${{ secrets.CLOUDFLARE_ACCESS_SERVICE_TOKEN_CLIENT_SECRET }}") || exit 1) + tags: ${{ steps.portalMeta.outputs.tags }} + labels: ${{ steps.portalMeta.outputs.labels }} + target: portal + - name: Build and push Server image + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.serverMeta.outputs.tags }} + labels: ${{ steps.serverMeta.outputs.labels }} + target: server + - name: Remove old Portal images + uses: actions/delete-package-versions@v5 + with: + package-name: ${{ env.PORTAL_IMAGE_NAME }} + package-type: "container" + min-versions-to-keep: 2 + delete-only-untagged-versions: "true" + - name: Remove old Server images + uses: actions/delete-package-versions@v5 + with: + package-name: ${{ env.SERVER_IMAGE_NAME }} + package-type: "container" + min-versions-to-keep: 2 + delete-only-untagged-versions: "true" diff --git a/.github/workflows/main-to-release.yml b/.github/workflows/main-to-release.yml deleted file mode 100644 index 0acd1889..00000000 --- a/.github/workflows/main-to-release.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Open pull request to release branch - -on: - push: - branches: - - main - -jobs: - release: - runs-on: ubuntu-latest - steps: - # First, get all the open pull requests that target the release branch using the GitHub CLI - - name: Get open pull requests - id: get_prs - run: | - gh pr list --state open --base release --json number,title -R ukdanceblue/monorepo > prs.json - echo "prs=$(cat prs.json)" >> $GITHUB_OUTPUT - env: - GH_TOKEN: ${{ github.token }} - # If the array is empty, there are no open pull requests, so we can open one - - name: Open pull request - if: steps.get_prs.outputs.prs == '[]' - run: | - gh pr create --title "Update Release Branch" --body "This pull request was automatically opened by a GitHub Action." --base release --head main --draft -R ukdanceblue/monorepo - env: - GH_TOKEN: ${{ github.token }} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..8c77820b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,25 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "msjsdiag.vscode-react-native", + "Prisma.prisma", + "esbenp.prettier-vscode", + "christian-kohler.npm-intellisense", + "VisualStudioExptTeam.vscodeintellicode", + "VisualStudioExptTeam.intellicode-api-usage-examples", + "cmstead.js-codeformer", + "cmstead.jsrefactor", + "ecmel.vscode-html-css", + "GraphQL.vscode-graphql-syntax", + "GraphQL.vscode-graphql", + "eamodio.gitlens", + "github.vscode-github-actions", + "expo.vscode-expo-tools", + "ms-azuretools.vscode-docker", + "naumovs.color-highlight", + "streetsidesoftware.code-spell-checker", + "vitest.explorer", + "GitHub.copilot", + "GitHub.vscode-pull-request-github" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index e8cbed29..b21907db 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "eslint.workingDirectories": ["."], - "eslint.experimental.useFlatConfig": true, + "eslint.useFlatConfig": true, "typescript.tsdk": "node_modules/typescript/lib", "typescript.enablePromptUseWorkspaceTsdk": true, "typescript.tsserver.experimental.enableProjectDiagnostics": true, @@ -8,7 +8,7 @@ "editor.formatOnSave": true, "editor.tabSize": 2, "editor.codeActionsOnSave": { - "source.organizeImports": "explicit", + "source.organizeImports": "never", "source.fixAll": "explicit" }, "search.exclude": { @@ -17,8 +17,14 @@ }, "remote.localPortHost": "allInterfaces", "typescript.inlayHints.parameterNames.enabled": "all", - "typescript.preferences.preferTypeOnlyAutoImports": true, "javascript.inlayHints.parameterNames.enabled": "all", - "cSpell.words": ["cooldown", "Spinnable", "Whatwg"], + "cSpell.words": [ + "Catchable", + "cooldown", + "Errorable", + "Leaderboards", + "Spinnable", + "Whatwg" + ], "eslint.codeActionsOnSave.mode": "problems" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 57931db0..9265cab8 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -104,7 +104,7 @@ "problemMatcher": [], "label": "Server: Build", "detail": "yarn workspace @ukdanceblue/server run build", - "dependsOn": ["Server: Prisma Generate", "Common: Build"] + "dependsOn": ["Common: Build"] } ] } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..c67a25e6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,66 @@ +# syntax=docker/dockerfile:1.7-labs +FROM node:22.4.1 as build + +ADD --link --exclude=packages/mobile . /builddir + +WORKDIR /builddir + +RUN corepack yarn install + +RUN corepack yarn run gql:build + +WORKDIR /builddir/packages/common + +RUN corepack yarn build + +# Server build +FROM build as server-build + +WORKDIR /builddir/packages/server + +RUN corepack yarn prisma generate + +RUN corepack yarn build + +RUN corepack yarn workspaces focus --production -A + +# Portal build +FROM build as portal-build + +WORKDIR /builddir/packages/portal + +ENV VITE_API_BASE_URL="" + +RUN corepack yarn build + +# Server +FROM node:22.4.1 as server + +ENV MS_OIDC_URL="https://login.microsoftonline.com/2b30530b-69b6-4457-b818-481cb53d42ae/v2.0/.well-known/openid-configuration" +ENV NODE_ENV="production" +ENV APPLICATION_PORT="8000" +ENV APPLICATION_HOST="0.0.0.0" + +RUN mkdir -p /app/packages/server +RUN mkdir -p /app/packages/common +RUN mkdir -p /app/.yarn +RUN mkdir -p /app/node_modules + +COPY --from=server-build /builddir/packages/server/dist /app/packages/server/dist +COPY --from=server-build /builddir/packages/server/node_modules /app/packages/server/node_modules +COPY --from=server-build /builddir/packages/server/prisma /app/packages/server/prisma +COPY --from=server-build /builddir/packages/server/package.json /app/packages/server/package.json +COPY --from=server-build /builddir/packages/common/dist /app/packages/common/dist +COPY --from=server-build /builddir/packages/common/package.json /app/packages/common/package.json +COPY --from=server-build /builddir/node_modules /app/node_modules +COPY --from=server-build /builddir/package.json /app/package.json +COPY --from=server-build /builddir/yarn.lock /app/yarn.lock + +WORKDIR /app/packages/server + +CMD corepack yarn dlx prisma migrate deploy && node . + +# Portal +FROM nginx:stable-alpine as portal + +COPY --from=portal-build /builddir/packages/portal/dist /usr/share/nginx/html diff --git a/README.md b/README.md index 0e8876b8..674360af 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,9 @@ Project Link: DB22, DB23, DB24 - [Jackson Huse](https://github.com/jphuse) - App Design Coordinator - DB23, DB24 +- [Skyler Trowel](https://github.com/smtrowel) - App Development Coordinator - + DB25 +- [Camille Dyer](https://github.com/cdyer8) - App Design Coordinator - DB25 - [Everyone on the DanceBlue committee](http://www.danceblue.org/meet-the-team)

(back to top)

diff --git a/codegen.ts b/codegen.ts index 91dfcbec..106799f9 100644 --- a/codegen.ts +++ b/codegen.ts @@ -1,3 +1,4 @@ +import { readdirSync } from "fs"; import type { CodegenConfig } from "@graphql-codegen/cli"; import type { ClientPresetConfig } from "@graphql-codegen/client-preset"; import type { TypeScriptPluginConfig } from "@graphql-codegen/typescript"; @@ -34,8 +35,8 @@ const config: TypeScriptPluginConfig = { AuthSource: "../index.js#AuthSource", // AccessLevel: "../index.js#AccessLevel", DbRole: "../index.js#DbRole", - CommitteeRole: "../index.js#CommitteeRole", - CommitteeIdentifier: "../index.js#CommitteeIdentifier", + // CommitteeRole: "../index.js#CommitteeRole", + // CommitteeIdentifier: "../index.js#CommitteeIdentifier", // ErrorCode: "../index.js#ErrorCode", MembershipPositionType: "../index.js#MembershipPositionType", TeamLegacyStatus: "../index.js#TeamLegacyStatus", @@ -48,12 +49,37 @@ const config: TypeScriptPluginConfig = { }, scalars: { LuxonDateRange: "string", - LuxonDateTime: "string", LuxonDuration: "string", + GlobalId: "string", ...graphqlScalarsClientDefs, }, strictScalars: true, }; + +const generates: CodegenConfig["generates"] = {}; +const packages = readdirSync("./packages"); +if (packages.includes("mobile")) { + generates["./packages/common/lib/graphql-client-mobile/"] = { + preset: "client", + presetConfig, + config, + documents: [ + "./packages/mobile/src/**/*.ts", + "./packages/mobile/src/**/*.tsx", + ], + }; +} +if (packages.includes("portal")) { + generates["./packages/common/lib/graphql-client-portal/"] = { + preset: "client", + presetConfig, + config, + documents: [ + "./packages/portal/src/**/*.ts", + "./packages/portal/src/**/*.tsx", + ], + }; +} const codegenConfig: CodegenConfig = { schema: "schema.graphql", hooks: { @@ -62,26 +88,7 @@ const codegenConfig: CodegenConfig = { }, }, emitLegacyCommonJSImports: false, - generates: { - "./packages/common/lib/graphql-client-public/": { - preset: "client", - presetConfig, - config, - documents: [ - "./packages/mobile/src/**/*.ts", - "./packages/mobile/src/**/*.tsx", - ], - }, - "./packages/common/lib/graphql-client-admin/": { - preset: "client", - presetConfig, - config, - documents: [ - "./packages/portal/src/**/*.ts", - "./packages/portal/src/**/*.tsx", - ], - }, - }, + generates, }; export default codegenConfig; diff --git a/compose.yaml b/compose.yaml index 751eff10..848dc7ef 100644 --- a/compose.yaml +++ b/compose.yaml @@ -2,37 +2,120 @@ services: postgres: - image: postgres:latest + container_name: danceblue-database-${SUBDOMAIN:?error} + image: postgres:16 + networks: + - database environment: POSTGRES_USER: danceblue POSTGRES_PASSWORD: danceblue POSTGRES_DB: danceblue - networks: - - danceblue attach: false volumes: - - ./compose-volumes/postgres:/var/lib/postgresql/data + - database:/var/lib/postgresql/data server: - container_name: server + container_name: danceblue-server-${SUBDOMAIN:?error} build: context: . - dockerfile: ./packages/server/Dockerfile - ports: - - 8080:8000 + target: server + image: ghcr.io/ukdanceblue/app-server:${VERSION:-latest} networks: - - danceblue + - proxy + - database depends_on: - postgres - env_file: ./packages/server/.env environment: - DB_HOST: postgres - DB_NAME: danceblue - DB_UNAME: danceblue - DB_PWD: danceblue - NODE_ENV: development - APPLICATION_HOST: server - DATABASE_URL: postgres://danceblue:danceblue@postgres:5432/danceblue + NODE_ENV: ${NODE_ENV:-production} + SERVE_PATH: /data/local-uploads + UPLOAD_PATH: /data/local-uploads + MAX_FILE_SIZE: 200 + LOG_DIR: /data/logs + DATABASE_URL: postgres://danceblue:danceblue@danceblue-database-${SUBDOMAIN:?error}:5432/danceblue + SERVE_ORIGIN: http://localhost:8000 + secrets: + - application_port + - application_host + - cookie_secret + - jwt_secret + - db_host + - db_port + - db_uname + - db_pwd + - db_name + - database_url + - ms_client_id + - ms_client_secret + - expo_access_token + - serve_origin + - logging_level + - dbfunds_api_origin + - dbfunds_api_key + - log_dir + volumes: + - content:/data/local-uploads + portal: + container_name: danceblue-portal-${SUBDOMAIN:?error} + image: ghcr.io/ukdanceblue/app-portal:${VERSION:-latest} + networks: + - proxy + build: + context: . + target: portal networks: - danceblue: - driver: bridge + database: + internal: true + proxy: + external: true + name: proxy + +volumes: + content: + driver: local + name: danceblue-server-${SUBDOMAIN:?error}-content + database: + driver: local + name: danceblue-server-${SUBDOMAIN:?error}-database + +# Secrets from environment variables (i.e. var: environment: VAR) +# secrets: +# token: +# environment: "OAUTH_TOKEN" + +secrets: + application_port: + environment: "APPLICATION_PORT" + application_host: + environment: "APPLICATION_HOST" + cookie_secret: + environment: "COOKIE_SECRET" + jwt_secret: + environment: "JWT_SECRET" + db_host: + environment: "DB_HOST" + db_port: + environment: "DB_PORT" + db_uname: + environment: "DB_UNAME" + db_pwd: + environment: "DB_PWD" + db_name: + environment: "DB_NAME" + database_url: + environment: "DATABASE_URL" + ms_client_id: + environment: "MS_CLIENT_ID" + ms_client_secret: + environment: "MS_CLIENT_SECRET" + expo_access_token: + environment: "EXPO_ACCESS_TOKEN" + serve_origin: + environment: "SERVE_ORIGIN" + logging_level: + environment: "LOGGING_LEVEL" + dbfunds_api_origin: + environment: "DBFUNDS_API_ORIGIN" + dbfunds_api_key: + environment: "DBFUNDS_API_KEY" + log_dir: + environment: "LOG_DIR" diff --git a/eslint/base.ts b/eslint/base.ts index 655104dd..4ecdc3f1 100644 --- a/eslint/base.ts +++ b/eslint/base.ts @@ -28,8 +28,14 @@ const rules: Linter.RulesRecord = { "prefer-destructuring": [ "error", { - array: false, - object: true, + VariableDeclarator: { + array: false, + object: true, + }, + AssignmentExpression: { + array: false, + object: false, + }, }, { enforceForRenamedProperties: false, @@ -56,12 +62,21 @@ const rules: Linter.RulesRecord = { "import/order": [ "error", { - "groups": ["builtin", "external", "parent", "sibling", "index"], + "groups": [ + "index", + "sibling", + "parent", + "internal", + "external", + "builtin", + "object", + "type", + ], "pathGroups": [ { - pattern: "@custom-lib/**", - group: "external", - position: "after", + pattern: "#**", + group: "internal", + position: "before", }, ], "pathGroupsExcludedImportTypes": ["builtin"], @@ -71,13 +86,7 @@ const rules: Linter.RulesRecord = { "newlines-between": "always", }, ], - "sort-imports": [ - "error", - { - allowSeparatedGroups: true, - ignoreDeclarationSort: true, - }, - ], + "sort-imports": ["off"], // Unicorn Plugin "unicorn/better-regex": "error", "unicorn/catch-error-name": "error", diff --git a/eslint/out/base.js b/eslint/out/base.js index dc8f84e8..ccbc6a03 100644 --- a/eslint/out/base.js +++ b/eslint/out/base.js @@ -26,8 +26,14 @@ const rules = { "prefer-destructuring": [ "error", { - array: false, - object: true, + VariableDeclarator: { + array: false, + object: true, + }, + AssignmentExpression: { + array: false, + object: false, + }, }, { enforceForRenamedProperties: false, @@ -54,28 +60,21 @@ const rules = { "import/order": [ "error", { - "groups": ["builtin", "external", "parent", "sibling", "index"], - "pathGroups": [ + groups: ["builtin", "external", "parent", "sibling", "index"], + pathGroups: [ { pattern: "@custom-lib/**", group: "external", position: "after", }, ], - "pathGroupsExcludedImportTypes": ["builtin"], - "alphabetize": { + pathGroupsExcludedImportTypes: ["builtin"], + alphabetize: { order: "asc", }, - "newlines-between": "always", - }, - ], - "sort-imports": [ - "error", - { - allowSeparatedGroups: true, - ignoreDeclarationSort: true, }, ], + "sort-imports": ["off"], // Unicorn Plugin "unicorn/better-regex": "error", "unicorn/catch-error-name": "error", diff --git a/package.json b/package.json index a3f69a28..4301bfc6 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "server:bs": "yarn workspace @ukdanceblue/server run bs", "server:build": "yarn workspace @ukdanceblue/server run build", "server:start": "yarn workspace @ukdanceblue/server run start", + "server:dev": "yarn workspace @ukdanceblue/server run dev", "server:prisma": "yarn workspace @ukdanceblue/server prisma", "server:prisma-generate": "yarn workspace @ukdanceblue/server prisma generate", "portal:dev": "yarn workspace @ukdanceblue/portal dev", @@ -55,7 +56,7 @@ "graphql": "^16.8.1", "graphql-scalars": "^1.23.0", "prettier": "^3.2.5", - "typescript": "^5.4.3", + "typescript": "^5.5.3", "vitest": "^1.4.0" }, "workspaces": [ diff --git a/packages/common/lib/api/filtering/ListQueryTypes.ts b/packages/common/lib/api/filtering/ListQueryTypes.ts index 3cbfd8f5..9c4da7c5 100644 --- a/packages/common/lib/api/filtering/ListQueryTypes.ts +++ b/packages/common/lib/api/filtering/ListQueryTypes.ts @@ -68,8 +68,8 @@ export interface PaginationOptions { } export const SortDirection = { - ASCENDING: "ASCENDING", - DESCENDING: "DESCENDING", + asc: "asc", + desc: "desc", } as const; export type SortDirection = (typeof SortDirection)[keyof typeof SortDirection]; diff --git a/packages/common/lib/api/filtering/list-query-args/FilterItem.ts b/packages/common/lib/api/filtering/list-query-args/FilterItem.ts index ea3d1a33..7600b38a 100644 --- a/packages/common/lib/api/filtering/list-query-args/FilterItem.ts +++ b/packages/common/lib/api/filtering/list-query-args/FilterItem.ts @@ -7,8 +7,7 @@ import type { OneOfFilterItemInterface, StringFilterItemInterface, } from "@ukdanceblue/common"; -import { DateTimeScalar } from "@ukdanceblue/common"; -import { VoidResolver } from "graphql-scalars"; +import { DateTimeISOResolver, VoidResolver } from "graphql-scalars"; import { Field, InputType } from "type-graphql"; import type { Comparator } from "../ListQueryTypes.js"; @@ -104,7 +103,7 @@ export abstract class AbstractDateFilterItem extends FilterItem implements DateFilterItemInterface { - @Field(() => DateTimeScalar) + @Field(() => DateTimeISOResolver) value!: string; @Field(() => NumericComparator, { diff --git a/packages/common/lib/api/filtering/list-query-args/UnfilteredListQueryArgs.ts b/packages/common/lib/api/filtering/list-query-args/UnfilteredListQueryArgs.ts index 63e69562..10163703 100644 --- a/packages/common/lib/api/filtering/list-query-args/UnfilteredListQueryArgs.ts +++ b/packages/common/lib/api/filtering/list-query-args/UnfilteredListQueryArgs.ts @@ -1,4 +1,4 @@ -import type { OptionalToNullable, Resource } from "@ukdanceblue/common"; +import type { Node, OptionalToNullable } from "@ukdanceblue/common"; import { ArgsType, Field, Int } from "type-graphql"; import type { ListQueryType } from "../ListQueryTypes.js"; @@ -8,7 +8,7 @@ import { DEFAULT_PAGE_SIZE, FIRST_PAGE } from "./common.js"; @ArgsType() export class UnfilteredListQueryArgs - implements OptionalToNullable>> + implements OptionalToNullable>> { @Field(() => Boolean, { nullable: true, diff --git a/packages/common/lib/api/relay.ts b/packages/common/lib/api/relay.ts new file mode 100644 index 00000000..bc8314c3 --- /dev/null +++ b/packages/common/lib/api/relay.ts @@ -0,0 +1,125 @@ +import { Field, InterfaceType, ObjectType } from "type-graphql"; + +import { Errorable, ResourceError } from "./resourceError.js"; +import { CursorScalar } from "./scalars/Cursor.js"; +import type { GlobalId } from "./scalars/GlobalId.js"; +import { GlobalIdScalar } from "./scalars/GlobalId.js"; + +@ObjectType() +export class PageInfo { + @Field(() => Boolean, { + name: "hasPreviousPage", + description: + "hasPreviousPage is used to indicate whether more edges exist prior to the set defined by the clients arguments. If the client is paginating with last/before, then the server must return true if prior edges exist, otherwise false. If the client is paginating with first/after, then the client may return true if edges prior to after exist, if it can do so efficiently, otherwise may return false.", + }) + hasPreviousPage!: boolean; + + @Field(() => Boolean, { + name: "hasNextPage", + description: + "hasNextPage is used to indicate whether more edges exist following the set defined by the clients arguments. If the client is paginating with first/after, then the server must return true if further edges exist, otherwise false. If the client is paginating with last/before, then the client may return true if edges further from before exist, if it can do so efficiently, otherwise may return false.", + }) + hasNextPage!: boolean; + + @Field(() => CursorScalar, { + name: "startCursor", + description: + "startCursor is simply an opaque value that refers to the first position in a connection. It is used by the client to request the first set of edges in a connection. The server must return the cursor that corresponds to the first element in the connection.", + }) + startCursor!: string; + + @Field(() => CursorScalar, { + name: "endCursor", + description: + "endCursor is simply an opaque value that refers to the last position in a connection. It is used by the client to request the last set of edges in a connection. The server must return the cursor that corresponds to the last element in the connection.", + }) + endCursor!: string; +} + +@InterfaceType() +export abstract class Node { + @Field(() => GlobalIdScalar) + id!: GlobalId; +} +@InterfaceType() +export abstract class Edge { + @Field(() => CursorScalar) + cursor!: string; + + @Field(() => Node) + node!: N; +} +@InterfaceType({ implements: [Errorable] }) +export abstract class Connection implements Errorable { + @Field(() => Number, { name: "totalCount" }) + totalCount!: number; + + @Field(() => [Edge], { name: "edges" }) + edges!: E[]; + + @Field(() => PageInfo, { name: "pageInfo" }) + pageInfo!: PageInfo; + + @Field(() => [ResourceError], { name: "errors" }) + errors!: ResourceError[]; +} +@InterfaceType({ implements: [Errorable] }) +export abstract class Resource implements Errorable { + @Field(() => Node, { name: "node" }) + node!: N; + + @Field(() => [ResourceError], { name: "errors" }) + errors!: ResourceError[]; +} +@InterfaceType({ implements: [Errorable] }) +export abstract class Result implements Errorable { + @Field(() => Node, { name: "node", nullable: true }) + node?: N; + + @Field(() => [ResourceError], { name: "errors" }) + errors!: ResourceError[]; +} + +export function createNodeClasses( + cls: new () => T, + name: Name +) { + const edgeClassName = `${name}Edge` as const; + @ObjectType(edgeClassName, { implements: Edge }) + class EdgeClass extends Edge { + @Field(() => cls) + node!: T; + } + + const connectionClassName = `${name}Connection` as const; + @ObjectType(connectionClassName, { implements: Connection }) + class ConnectionClass extends Connection { + @Field(() => [EdgeClass]) + edges!: EdgeClass[]; + } + + const resultClassName = `${name}Result` as const; + @ObjectType(resultClassName, { implements: Result }) + class ResultClass extends Result { + @Field(() => cls, { nullable: true }) + node?: T; + } + + return { + [edgeClassName]: EdgeClass, + [connectionClassName]: ConnectionClass, + [resultClassName]: ResultClass, + } as { + // Type magic to let autocomplete work + [K in + | `${Name}Edge` + | `${Name}Connection` + | `${Name}Result`]: K extends `${Name}Edge` + ? typeof EdgeClass + : K extends `${Name}Connection` + ? typeof ConnectionClass + : K extends `${Name}Result` + ? typeof ResultClass + : never; + }; +} diff --git a/packages/common/lib/api/resourceError.ts b/packages/common/lib/api/resourceError.ts new file mode 100644 index 00000000..971a7ec8 --- /dev/null +++ b/packages/common/lib/api/resourceError.ts @@ -0,0 +1,46 @@ +import { + Field, + InterfaceType, + ObjectType, + registerEnumType, +} from "type-graphql"; + +export const ResourceErrorCode = { + InternalError: "InternalError", +} as const; +export type ResourceErrorCode = + (typeof ResourceErrorCode)[keyof typeof ResourceErrorCode]; + +registerEnumType(ResourceErrorCode, { + name: "ResourceErrorCode", +}); + +@ObjectType("ResourceError") +export class ResourceError { + @Field(() => ResourceErrorCode, { + description: "A specific error code to identify the error", + }) + code!: ResourceErrorCode; + + @Field(() => String, { description: "A human readable error message" }) + message!: string; + + @Field(() => String, { + nullable: true, + description: "A message that should be presented to the user", + }) + alert?: string; + + @Field(() => String, { + nullable: true, + description: + "Development information about the error. should not be sent to untrusted clients (i.e. production environments)", + }) + debugInfo?: string; +} + +@InterfaceType() +export abstract class Errorable { + @Field(() => [ResourceError]) + errors!: ResourceError[]; +} diff --git a/packages/common/lib/api/resources/Committee.ts b/packages/common/lib/api/resources/Committee.ts new file mode 100644 index 00000000..5ea0d78d --- /dev/null +++ b/packages/common/lib/api/resources/Committee.ts @@ -0,0 +1,30 @@ +import { Field, ObjectType } from "type-graphql"; + +import { CommitteeIdentifier } from "../../authorization/structures.js"; +import { Node } from "../relay.js"; +import type { GlobalId } from "../scalars/GlobalId.js"; +import { GlobalIdScalar } from "../scalars/GlobalId.js"; + +import { TimestampedResource } from "./Resource.js"; + +@ObjectType({ implements: [Node] }) +export class CommitteeNode extends TimestampedResource implements Node { + @Field(() => GlobalIdScalar) + id!: GlobalId; + + @Field(() => CommitteeIdentifier) + identifier!: CommitteeIdentifier; + + static init(init: { + id: string; + identifier: CommitteeIdentifier; + updatedAt?: Date | null; + createdAt?: Date | null; + }) { + return this.createInstance().withValues(init); + } + + public getUniqueId(): string { + return this.id.id; + } +} diff --git a/packages/common/lib/api/resources/Configuration.ts b/packages/common/lib/api/resources/Configuration.ts index 265bebf2..a93127fd 100644 --- a/packages/common/lib/api/resources/Configuration.ts +++ b/packages/common/lib/api/resources/Configuration.ts @@ -1,10 +1,13 @@ -import type { DateTime } from "luxon"; -import { Field, ID, ObjectType } from "type-graphql"; +import { DateTimeISOResolver } from "graphql-scalars"; +import { DateTime } from "luxon"; +import { Field, ObjectType } from "type-graphql"; -import { DateTimeScalar } from "../scalars/DateTimeScalar.js"; +import { dateTimeFromSomething } from "../../utility/time/intervalTools.js"; +import { Node, createNodeClasses } from "../relay.js"; +import type { GlobalId } from "../scalars/GlobalId.js"; +import { GlobalIdScalar } from "../scalars/GlobalId.js"; import { TimestampedResource } from "./Resource.js"; - /* The way configurations work is that there can be n number of configurations, each with it's own UUID. When multiple configurations are created with the @@ -16,10 +19,12 @@ This also means we have some of the logic we need for a configuration to have additional validation logic in the future. */ -@ObjectType() -export class ConfigurationResource extends TimestampedResource { - @Field(() => ID) - uuid!: string; +@ObjectType({ + implements: [Node], +}) +export class ConfigurationNode extends TimestampedResource implements Node { + @Field(() => GlobalIdScalar) + id!: GlobalId; @Field(() => String) key!: string; @@ -27,17 +32,37 @@ export class ConfigurationResource extends TimestampedResource { @Field(() => String) value!: string; - @Field(() => DateTimeScalar, { nullable: true }) - validAfter!: DateTime | null; + @Field(() => DateTimeISOResolver, { nullable: true }) + validAfter?: Date | null; + get validAfterDateTime(): DateTime | null { + return dateTimeFromSomething(this.validAfter ?? null); + } - @Field(() => DateTimeScalar, { nullable: true }) - validUntil!: DateTime | null; + @Field(() => DateTimeISOResolver, { nullable: true }) + validUntil?: Date | null; + get validUntilDateTime(): DateTime | null { + return dateTimeFromSomething(this.validUntil ?? null); + } public getUniqueId(): string { return this.key; } - public static init(init: Partial) { - return ConfigurationResource.doInit(init); + public static init(init: { + id: string; + key: string; + value: string; + validAfter?: Date | null; + validUntil?: Date | null; + createdAt?: Date | null; + updatedAt?: Date | null; + }) { + return this.createInstance().withValues(init); } } + +export const { + ConfigurationConnection, + ConfigurationEdge, + ConfigurationResult, +} = createNodeClasses(ConfigurationNode, "Configuration"); diff --git a/packages/common/lib/api/resources/Device.ts b/packages/common/lib/api/resources/Device.ts index f9327115..ea641a52 100644 --- a/packages/common/lib/api/resources/Device.ts +++ b/packages/common/lib/api/resources/Device.ts @@ -1,24 +1,39 @@ +import { DateTimeISOResolver } from "graphql-scalars"; import type { DateTime } from "luxon"; -import { Field, ID, ObjectType } from "type-graphql"; +import { Field, ObjectType } from "type-graphql"; -import { DateTimeScalar } from "../scalars/DateTimeScalar.js"; +import { dateTimeFromSomething } from "../../utility/time/intervalTools.js"; +import { Node, createNodeClasses } from "../relay.js"; +import type { GlobalId } from "../scalars/GlobalId.js"; +import { GlobalIdScalar } from "../scalars/GlobalId.js"; import { TimestampedResource } from "./Resource.js"; +@ObjectType({ implements: [Node] }) +export class DeviceNode extends TimestampedResource implements Node { + @Field(() => GlobalIdScalar) + id!: GlobalId; -@ObjectType() -export class DeviceResource extends TimestampedResource { - @Field(() => ID) - uuid!: string; - @Field(() => String, { nullable: true }) - public expoPushToken!: string | null; - @Field(() => DateTimeScalar, { nullable: true }) - public lastLogin!: DateTime | null; + @Field(() => DateTimeISOResolver, { nullable: true }) + public lastLogin?: Date | null; + get lastLoginDateTime(): DateTime | null { + return dateTimeFromSomething(this.lastLogin ?? null); + } public getUniqueId(): string { - return this.uuid; + return this.id.id; } - public static init(init: Partial) { - return DeviceResource.doInit(init); + public static init(init: { + id: string; + lastLogin?: Date | null; + createdAt: Date; + updatedAt: Date; + }) { + return this.createInstance().withValues(init); } } + +export const { DeviceConnection, DeviceEdge, DeviceResult } = createNodeClasses( + DeviceNode, + "Device" +); diff --git a/packages/common/lib/api/resources/Event.ts b/packages/common/lib/api/resources/Event.ts index 7e422986..d1286df8 100644 --- a/packages/common/lib/api/resources/Event.ts +++ b/packages/common/lib/api/resources/Event.ts @@ -1,16 +1,20 @@ -import { Interval } from "luxon"; import { Field, ID, ObjectType } from "type-graphql"; -import { DateRangeScalar } from "../scalars/DateRangeScalar.js"; +import { Node, createNodeClasses } from "../relay.js"; +import type { GlobalId } from "../scalars/GlobalId.js"; +import { GlobalIdScalar } from "../scalars/GlobalId.js"; +import { IntervalISO } from "../types/IntervalISO.js"; import { Resource, TimestampedResource } from "./Resource.js"; -@ObjectType() -export class EventResource extends TimestampedResource { - @Field(() => ID) - uuid!: string; - @Field(() => [EventOccurrenceResource]) - occurrences!: EventOccurrenceResource[]; +@ObjectType({ + implements: [Node], +}) +export class EventNode extends TimestampedResource implements Node { + @Field(() => GlobalIdScalar) + id!: GlobalId; + @Field(() => [EventOccurrenceNode]) + occurrences!: EventOccurrenceNode[]; @Field(() => String) title!: string; @Field(() => String, { nullable: true }) @@ -21,28 +25,52 @@ export class EventResource extends TimestampedResource { location!: string | null; public getUniqueId(): string { - return this.uuid; + return this.id.id; } - public static init(init: Partial) { - return EventResource.doInit(init); + public static init(init: { + id: string; + title: string; + summary?: string | null; + description?: string | null; + location?: string | null; + updatedAt?: Date | null; + createdAt?: Date | null; + occurrences: EventOccurrenceNode[]; + }) { + return this.createInstance().withValues(init); } } -@ObjectType() -export class EventOccurrenceResource extends Resource { +@ObjectType({ + implements: [], +}) +export class EventOccurrenceNode extends Resource { @Field(() => ID) - uuid!: string; - @Field(() => DateRangeScalar) - interval!: Interval; + id!: string; + @Field(() => IntervalISO) + interval!: IntervalISO; @Field(() => Boolean) fullDay!: boolean; public getUniqueId(): string { - return this.uuid; + return this.id; } - public static init(init: Partial) { - return EventOccurrenceResource.doInit(init); + public static init(init: { + id: string; + interval: IntervalISO; + fullDay: boolean; + }) { + const resource = this.createInstance(); + resource.id = init.id; + resource.interval = init.interval; + resource.fullDay = init.fullDay; + return resource; } } + +export const { EventConnection, EventEdge, EventResult } = createNodeClasses( + EventNode, + "Event" +); diff --git a/packages/common/lib/api/resources/FeedResource.ts b/packages/common/lib/api/resources/Feed.ts similarity index 57% rename from packages/common/lib/api/resources/FeedResource.ts rename to packages/common/lib/api/resources/Feed.ts index 68c2289c..e143f2e3 100644 --- a/packages/common/lib/api/resources/FeedResource.ts +++ b/packages/common/lib/api/resources/Feed.ts @@ -1,7 +1,10 @@ -import { Field, ID, ObjectType } from "type-graphql"; +import { Field, ObjectType } from "type-graphql"; -import { TimestampedResource } from "./Resource.js"; +import { Node, createNodeClasses } from "../relay.js"; +import type { GlobalId } from "../scalars/GlobalId.js"; +import { GlobalIdScalar } from "../scalars/GlobalId.js"; +import { TimestampedResource } from "./Resource.js"; // TODO: Expand this to include more types of feed items // export const FeedResourceType = { // FeaturedImage: "FeaturedImage", @@ -13,10 +16,12 @@ import { TimestampedResource } from "./Resource.js"; // description: "Dictates how to interpret the resource link", // }); -@ObjectType() -export class FeedResource extends TimestampedResource { - @Field(() => ID) - uuid!: string; +@ObjectType({ + implements: [Node], +}) +export class FeedNode extends TimestampedResource implements Node { + @Field(() => GlobalIdScalar) + id!: GlobalId; @Field(() => String) title!: string; @@ -25,16 +30,21 @@ export class FeedResource extends TimestampedResource { textContent?: string | null | undefined; public getUniqueId(): string { - return this.uuid; + return this.id.id; } public static init(init: { - uuid: string; + id: string; title: string; textContent?: string | null | undefined; createdAt?: Date; updatedAt?: Date; }) { - return FeedResource.doInit(init); + return FeedNode.createInstance().withValues(init); } } + +export const { FeedConnection, FeedEdge, FeedResult } = createNodeClasses( + FeedNode, + "Feed" +); diff --git a/packages/common/lib/api/resources/Fundraising.ts b/packages/common/lib/api/resources/Fundraising.ts new file mode 100644 index 00000000..80c0c93d --- /dev/null +++ b/packages/common/lib/api/resources/Fundraising.ts @@ -0,0 +1,118 @@ +import { DateTimeISOResolver } from "graphql-scalars"; +import type { DateTime } from "luxon"; +import { None, Option, Some } from "ts-results-es"; +import { Field, Float, ObjectType } from "type-graphql"; + +import { dateTimeFromSomething } from "../../utility/time/intervalTools.js"; +import { Node, createNodeClasses } from "../relay.js"; +import type { GlobalId } from "../scalars/GlobalId.js"; +import { GlobalIdScalar } from "../scalars/GlobalId.js"; + +import { TimestampedResource } from "./Resource.js"; + +@ObjectType({ + implements: [Node], +}) +export class FundraisingEntryNode extends TimestampedResource implements Node { + @Field(() => GlobalIdScalar) + id!: GlobalId; + @Field(() => String, { nullable: true, name: "donatedByText" }) + private _donatedByText!: string | null; + get donatedByText(): Option { + return this._donatedByText ? Some(this._donatedByText) : None; + } + set donatedByText(value: Option) { + this._donatedByText = value.unwrapOr(null); + } + @Field(() => String, { nullable: true, name: "donatedToText" }) + private _donatedToText!: string | null; + get donatedToText(): Option { + return this._donatedToText ? Some(this._donatedToText) : None; + } + set donatedToText(value: Option) { + this._donatedToText = value.unwrapOr(null); + } + @Field(() => DateTimeISOResolver) + donatedOn!: Date; + get donatedOnDateTime(): DateTime { + return dateTimeFromSomething(this.donatedOn); + } + @Field(() => Float) + amount!: number; + + public getUniqueId(): string { + return this.id.id; + } + + public static init(init: { + id: string; + donatedByText: Option | string | null; + donatedToText: Option | string | null; + donatedOn: Date; + amount: number; + createdAt: Date; + updatedAt: Date; + }) { + const node = new FundraisingEntryNode(); + node.id = { + id: init.id, + typename: "FundraisingEntryNode", + }; + node.donatedByText = + init.donatedByText == null + ? None + : typeof init.donatedByText === "string" + ? Some(init.donatedByText) + : init.donatedByText; + node.donatedToText = + init.donatedToText == null + ? None + : typeof init.donatedToText === "string" + ? Some(init.donatedToText) + : init.donatedToText; + node.donatedOn = init.donatedOn; + node.amount = init.amount; + node.createdAt = init.createdAt; + node.updatedAt = init.updatedAt; + + return node; + } +} + +export const { + FundraisingEntryConnection, + FundraisingEntryEdge, + FundraisingEntryResult, +} = createNodeClasses(FundraisingEntryNode, "FundraisingEntry"); + +@ObjectType({ + implements: [Node], +}) +export class FundraisingAssignmentNode + extends TimestampedResource + implements Node +{ + @Field(() => GlobalIdScalar) + id!: GlobalId; + @Field(() => Float) + amount!: number; + + public getUniqueId(): string { + return this.id.id; + } + + public static init(init: { + id: string; + amount: number; + createdAt: Date; + updatedAt: Date; + }) { + return FundraisingAssignmentNode.createInstance().withValues(init); + } +} + +export const { + FundraisingAssignmentConnection, + FundraisingAssignmentEdge, + FundraisingAssignmentResult, +} = createNodeClasses(FundraisingAssignmentNode, "FundraisingAssignment"); diff --git a/packages/common/lib/api/resources/Image.ts b/packages/common/lib/api/resources/Image.ts index e904e2b1..ee02444e 100644 --- a/packages/common/lib/api/resources/Image.ts +++ b/packages/common/lib/api/resources/Image.ts @@ -1,12 +1,17 @@ import { URLResolver } from "graphql-scalars"; -import { Field, ID, Int, ObjectType } from "type-graphql"; +import { Field, Int, ObjectType } from "type-graphql"; -import { TimestampedResource } from "./Resource.js"; +import { Node, createNodeClasses } from "../relay.js"; +import type { GlobalId } from "../scalars/GlobalId.js"; +import { GlobalIdScalar } from "../scalars/GlobalId.js"; -@ObjectType() -export class ImageResource extends TimestampedResource { - @Field(() => ID) - uuid!: string; +import { TimestampedResource } from "./Resource.js"; +@ObjectType({ + implements: [Node], +}) +export class ImageNode extends TimestampedResource implements Node { + @Field(() => GlobalIdScalar) + id!: GlobalId; @Field(() => URLResolver, { nullable: true }) url!: URL | null; @@ -27,10 +32,25 @@ export class ImageResource extends TimestampedResource { height!: number; public getUniqueId(): string { - return this.uuid; + return this.id.id; } - public static init(init: Partial) { - return ImageResource.doInit(init); + public static init(init: { + id: string; + url?: URL | null; + mimeType: string; + thumbHash?: string | null; + alt?: string | null; + width: number; + height: number; + updatedAt?: Date | null; + createdAt?: Date | null; + }) { + return this.createInstance().withValues(init); } } + +export const { ImageConnection, ImageEdge, ImageResult } = createNodeClasses( + ImageNode, + "Image" +); diff --git a/packages/common/lib/api/resources/LoginFlowSession.ts b/packages/common/lib/api/resources/LoginFlowSession.ts deleted file mode 100644 index 4345aab7..00000000 --- a/packages/common/lib/api/resources/LoginFlowSession.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { URLResolver } from "graphql-scalars"; -import { DateTime } from "luxon"; -import { Field, ID, ObjectType } from "type-graphql"; - -import { DateTimeScalar } from "../scalars/DateTimeScalar.js"; - -import { TimestampedResource } from "./Resource.js"; - -// TODO: Maybe remove - -@ObjectType() -export class LoginFlowSessionResource extends TimestampedResource { - @Field(() => ID) - uuid!: string; - @Field(() => String) - codeVerifier!: string; - @Field(() => DateTimeScalar) - creationDate!: DateTime; - @Field(() => URLResolver, { nullable: true }) - redirectToAfterLogin!: URL | null; - - public getUniqueId(): string { - return this.uuid; - } - - public static init(init: Partial) { - return LoginFlowSessionResource.doInit(init); - } -} diff --git a/packages/common/lib/api/resources/Marathon.ts b/packages/common/lib/api/resources/Marathon.ts index 10076863..7dfcc267 100644 --- a/packages/common/lib/api/resources/Marathon.ts +++ b/packages/common/lib/api/resources/Marathon.ts @@ -1,29 +1,49 @@ import { DateTimeISOResolver } from "graphql-scalars"; -import { Field, ID, ObjectType } from "type-graphql"; +import type { DateTime } from "luxon"; +import { Field, ObjectType } from "type-graphql"; -import { TimestampedResource } from "./Resource.js"; +import { dateTimeFromSomething } from "../../utility/time/intervalTools.js"; +import { Node, createNodeClasses } from "../relay.js"; +import type { GlobalId } from "../scalars/GlobalId.js"; +import { GlobalIdScalar } from "../scalars/GlobalId.js"; -@ObjectType() -export class MarathonResource extends TimestampedResource { - @Field(() => ID) - uuid!: string; +import { TimestampedResource } from "./Resource.js"; +@ObjectType({ + implements: [Node], +}) +export class MarathonNode extends TimestampedResource implements Node { + @Field(() => GlobalIdScalar) + id!: GlobalId; @Field(() => String) year!: string; - @Field(() => DateTimeISOResolver) - startDate!: string; - @Field(() => DateTimeISOResolver) - endDate!: string; + @Field(() => DateTimeISOResolver, { nullable: true }) + startDate?: Date | undefined | null; + get startDateDateTime(): DateTime | null { + return dateTimeFromSomething(this.startDate) ?? null; + } + @Field(() => DateTimeISOResolver, { nullable: true }) + endDate?: Date | undefined | null; + get endDateDateTime(): DateTime | null { + return dateTimeFromSomething(this.endDate) ?? null; + } static init({ - uuid, + id: id, year, startDate, endDate, createdAt, updatedAt, - }: Omit): MarathonResource { - return this.doInit({ - uuid, + }: { + id: string; + year: string; + startDate?: Date | null; + endDate?: Date | null; + createdAt?: Date | null; + updatedAt?: Date | null; + }): MarathonNode { + return this.createInstance().withValues({ + id, year, startDate, endDate, @@ -33,6 +53,9 @@ export class MarathonResource extends TimestampedResource { } public getUniqueId(): string { - return this.uuid; + return this.id.id; } } + +export const { MarathonConnection, MarathonEdge, MarathonResult } = + createNodeClasses(MarathonNode, "Marathon"); diff --git a/packages/common/lib/api/resources/MarathonHour.ts b/packages/common/lib/api/resources/MarathonHour.ts index 35a8dd6b..324cc866 100644 --- a/packages/common/lib/api/resources/MarathonHour.ts +++ b/packages/common/lib/api/resources/MarathonHour.ts @@ -1,32 +1,50 @@ import { DateTimeISOResolver } from "graphql-scalars"; -import { Field, ID, ObjectType } from "type-graphql"; +import type { DateTime } from "luxon"; +import { Field, ObjectType } from "type-graphql"; -import { TimestampedResource } from "./Resource.js"; +import { dateTimeFromSomething } from "../../utility/time/intervalTools.js"; +import { Node, createNodeClasses } from "../relay.js"; +import type { GlobalId } from "../scalars/GlobalId.js"; +import { GlobalIdScalar } from "../scalars/GlobalId.js"; -@ObjectType() -export class MarathonHourResource extends TimestampedResource { - @Field(() => ID) - uuid!: string; +import { TimestampedResource } from "./Resource.js"; +@ObjectType({ + implements: [Node], +}) +export class MarathonHourNode extends TimestampedResource implements Node { + @Field(() => GlobalIdScalar) + id!: GlobalId; @Field(() => String) title!: string; @Field(() => String, { nullable: true }) details?: string | null; @Field(() => DateTimeISOResolver) - shownStartingAt!: string; + shownStartingAt!: Date; + get shownStartingAtDateTime(): DateTime { + return dateTimeFromSomething(this.shownStartingAt); + } @Field(() => String) durationInfo!: string; static init({ - uuid, + id, title, details, shownStartingAt, durationInfo, createdAt, updatedAt, - }: Omit): MarathonHourResource { - return this.doInit({ - uuid, + }: { + id: string; + title: string; + details?: string | null; + shownStartingAt: Date; + durationInfo: string; + createdAt?: Date | null; + updatedAt?: Date | null; + }): MarathonHourNode { + return this.createInstance().withValues({ + id, title, details, shownStartingAt, @@ -37,6 +55,9 @@ export class MarathonHourResource extends TimestampedResource { } public getUniqueId(): string { - return this.uuid; + return this.id.id; } } + +export const { MarathonHourConnection, MarathonHourEdge, MarathonHourResult } = + createNodeClasses(MarathonHourNode, "MarathonHour"); diff --git a/packages/common/lib/api/resources/Membership.ts b/packages/common/lib/api/resources/Membership.ts index b68ae9e0..6b0ce6e3 100644 --- a/packages/common/lib/api/resources/Membership.ts +++ b/packages/common/lib/api/resources/Membership.ts @@ -1,7 +1,14 @@ -import { Field, ID, ObjectType, registerEnumType } from "type-graphql"; +import { Field, ObjectType, registerEnumType } from "type-graphql"; -import { TimestampedResource } from "./Resource.js"; +import { + CommitteeIdentifier, + CommitteeRole, +} from "../../authorization/structures.js"; +import { Node, createNodeClasses } from "../relay.js"; +import type { GlobalId } from "../scalars/GlobalId.js"; +import { GlobalIdScalar } from "../scalars/GlobalId.js"; +import { TimestampedResource } from "./Resource.js"; export const MembershipPositionType = { Member: "Member", Captain: "Captain", @@ -14,19 +21,56 @@ registerEnumType(MembershipPositionType, { description: "The position of a member on a team", }); -@ObjectType() -export class MembershipResource extends TimestampedResource { - @Field(() => ID) - uuid!: string; +@ObjectType({ + implements: [Node], +}) +export class MembershipNode extends TimestampedResource implements Node { + @Field(() => GlobalIdScalar) + id!: GlobalId; @Field(() => MembershipPositionType) position!: MembershipPositionType; public getUniqueId(): string { - return this.uuid; + return this.id.id; + } + + public static init(init: { + id: string; + position: MembershipPositionType; + createdAt?: Date | null; + updatedAt?: Date | null; + }) { + return MembershipNode.createInstance().withValues(init); } +} + +@ObjectType({ + implements: [Node], +}) +export class CommitteeMembershipNode extends MembershipNode implements Node { + @Field(() => CommitteeRole) + role!: CommitteeRole; - public static init(init: Partial) { - return MembershipResource.doInit(init); + @Field(() => CommitteeIdentifier) + identifier!: CommitteeIdentifier; + + public static init(init: { + id: string; + position: MembershipPositionType; + identifier: CommitteeIdentifier; + role: CommitteeRole; + createdAt?: Date | null; + updatedAt?: Date | null; + }) { + return CommitteeMembershipNode.createInstance().withValues(init); } } + +export const { MembershipConnection, MembershipEdge, MembershipResult } = + createNodeClasses(MembershipNode, "Membership"); +export const { + CommitteeMembershipConnection, + CommitteeMembershipEdge, + CommitteeMembershipResult, +} = createNodeClasses(CommitteeMembershipNode, "CommitteeMembership"); diff --git a/packages/common/lib/api/resources/Notification.ts b/packages/common/lib/api/resources/Notification.ts index e8d971bd..9eff5e7f 100644 --- a/packages/common/lib/api/resources/Notification.ts +++ b/packages/common/lib/api/resources/Notification.ts @@ -1,15 +1,21 @@ -import { URLResolver } from "graphql-scalars"; -import { Field, ID, ObjectType } from "type-graphql"; +import { DateTimeISOResolver, URLResolver } from "graphql-scalars"; +import type { DateTime } from "luxon"; +import { Field, ObjectType } from "type-graphql"; import { AccessControl } from "../../authorization/accessControl.js"; -import { AccessLevel } from "../../index.js"; +import { AccessLevel } from "../../authorization/structures.js"; +import { dateTimeFromSomething } from "../../utility/time/intervalTools.js"; +import { Node, createNodeClasses } from "../relay.js"; +import type { GlobalId } from "../scalars/GlobalId.js"; +import { GlobalIdScalar } from "../scalars/GlobalId.js"; import { TimestampedResource } from "./Resource.js"; - -@ObjectType() -export class NotificationResource extends TimestampedResource { - @Field(() => ID) - uuid!: string; +@ObjectType({ + implements: [Node], +}) +export class NotificationNode extends TimestampedResource implements Node { + @Field(() => GlobalIdScalar) + id!: GlobalId; @Field(() => String) title!: string; @@ -24,36 +30,64 @@ export class NotificationResource extends TimestampedResource { @AccessControl({ accessLevel: AccessLevel.CommitteeChairOrCoordinator }) deliveryIssue?: string | null; - @Field(() => Date, { nullable: true }) + @Field(() => DateTimeISOResolver, { nullable: true }) @AccessControl({ accessLevel: AccessLevel.CommitteeChairOrCoordinator }) deliveryIssueAcknowledgedAt?: Date | null; + get deliveryIssueAcknowledgedAtDateTime(): DateTime | null { + return dateTimeFromSomething(this.deliveryIssueAcknowledgedAt ?? null); + } - @Field(() => Date, { + @Field(() => DateTimeISOResolver, { nullable: true, description: "The time the notification is scheduled to be sent, if null it is either already sent or unscheduled.", }) sendAt?: Date | null; + get sendAtDateTime(): DateTime | null { + return dateTimeFromSomething(this.sendAt ?? null); + } - @Field(() => Date, { + @Field(() => DateTimeISOResolver, { nullable: true, description: "The time the server started sending the notification.", }) startedSendingAt?: Date | null; + get startedSendingAtDateTime(): DateTime | null { + return dateTimeFromSomething(this.startedSendingAt ?? null); + } public getUniqueId(): string { - return this.uuid; + return this.id.id; } - public static init(init: Partial) { - return NotificationResource.doInit(init); + public static init(init: { + id: string; + title: string; + body: string; + url?: URL | null; + deliveryIssue?: string | null; + deliveryIssueAcknowledgedAt?: Date | null; + sendAt?: Date | null; + startedSendingAt?: Date | null; + createdAt: Date; + updatedAt: Date; + }) { + return NotificationNode.createInstance().withValues(init); } } -@ObjectType() -export class NotificationDeliveryResource extends TimestampedResource { - @Field(() => ID) - uuid!: string; +export const { NotificationConnection, NotificationEdge, NotificationResult } = + createNodeClasses(NotificationNode, "Notification"); + +@ObjectType({ + implements: [Node], +}) +export class NotificationDeliveryNode + extends TimestampedResource + implements Node +{ + @Field(() => GlobalIdScalar) + id!: GlobalId; @Field(() => Date, { nullable: true, @@ -87,10 +121,24 @@ export class NotificationDeliveryResource extends TimestampedResource { deliveryError?: string | null; public getUniqueId(): string { - return this.uuid; + return this.id.id; } - public static init(init: Partial) { - return NotificationDeliveryResource.doInit(init); + public static init(init: { + id: string; + sentAt?: Date | null; + receiptCheckedAt?: Date | null; + chunkUuid?: string | null; + deliveryError?: string | null; + createdAt: Date; + updatedAt: Date; + }) { + return NotificationDeliveryNode.createInstance().withValues(init); } } + +export const { + NotificationDeliveryConnection, + NotificationDeliveryEdge, + NotificationDeliveryResult, +} = createNodeClasses(NotificationDeliveryNode, "NotificationDelivery"); diff --git a/packages/common/lib/api/resources/Person.ts b/packages/common/lib/api/resources/Person.ts index 6e91d23d..e10ae760 100644 --- a/packages/common/lib/api/resources/Person.ts +++ b/packages/common/lib/api/resources/Person.ts @@ -1,52 +1,45 @@ -import { Field, ID, ObjectType } from "type-graphql"; +import { Field, ObjectType } from "type-graphql"; + +import { DbRole } from "../../authorization/structures.js"; +import { Node, createNodeClasses } from "../relay.js"; +import type { GlobalId } from "../scalars/GlobalId.js"; +import { GlobalIdScalar } from "../scalars/GlobalId.js"; -import { AuthIdPairResource } from "./AuthIdPair.js"; import { TimestampedResource } from "./Resource.js"; -import { RoleResource, defaultRole } from "./Role.js"; -@ObjectType() -export class PersonResource extends TimestampedResource { - @Field(() => ID) - uuid!: string; - @Field(() => [AuthIdPairResource], { - deprecationReason: "This is now provided on the AuthIdPair resource.", - }) - authIds!: AuthIdPairResource[]; +@ObjectType({ + implements: [Node], +}) +export class PersonNode extends TimestampedResource implements Node { + @Field(() => GlobalIdScalar) + id!: GlobalId; @Field(() => String, { nullable: true }) name!: string | null; @Field(() => String) email!: string; @Field(() => String, { nullable: true }) linkblue!: string | null; - @Field(() => RoleResource) - role!: RoleResource; + @Field(() => DbRole) + dbRole!: DbRole; public getUniqueId(): string { - return this.uuid; + return this.id.id; } public static init(init: { - uuid: string; - authIds?: AuthIdPairResource[] | null; + id: string; name?: string | null; email: string; linkblue?: string | null; - role?: RoleResource | null; + dbRole?: DbRole | null; createdAt?: Date | null; updatedAt?: Date | null; }) { - const resource = PersonResource.doInit({ - uuid: init.uuid, - email: init.email, - }); - - resource.authIds = init.authIds ?? []; - resource.name = init.name ?? null; - resource.linkblue = init.linkblue ?? null; - resource.role = init.role ?? defaultRole; - resource.createdAt = init.createdAt ?? null; - resource.updatedAt = init.updatedAt ?? null; - - return resource; + return this.createInstance().withValues(init); } } + +export const { PersonConnection, PersonEdge, PersonResult } = createNodeClasses( + PersonNode, + "Person" +); diff --git a/packages/common/lib/api/resources/PointEntry.ts b/packages/common/lib/api/resources/PointEntry.ts index 8d83d56d..e1703f49 100644 --- a/packages/common/lib/api/resources/PointEntry.ts +++ b/packages/common/lib/api/resources/PointEntry.ts @@ -1,21 +1,35 @@ -import { Field, ID, Int, ObjectType } from "type-graphql"; +import { Field, Int, ObjectType } from "type-graphql"; -import { TimestampedResource } from "./Resource.js"; +import { Node, createNodeClasses } from "../relay.js"; +import type { GlobalId } from "../scalars/GlobalId.js"; +import { GlobalIdScalar } from "../scalars/GlobalId.js"; -@ObjectType() -export class PointEntryResource extends TimestampedResource { - @Field(() => ID) - uuid!: string; +import { TimestampedResource } from "./Resource.js"; +@ObjectType({ + implements: [Node], +}) +export class PointEntryNode extends TimestampedResource implements Node { + @Field(() => GlobalIdScalar) + id!: GlobalId; @Field(() => String, { nullable: true }) comment!: string | null; @Field(() => Int) points!: number; public getUniqueId(): string { - return this.uuid; + return this.id.id; } - public static init(init: Partial) { - return PointEntryResource.doInit(init); + public static init(init: { + id: string; + comment?: string | null; + points: number; + createdAt?: Date | null; + updatedAt?: Date | null; + }) { + return this.createInstance().withValues(init); } } + +export const { PointEntryConnection, PointEntryEdge, PointEntryResult } = + createNodeClasses(PointEntryNode, "PointEntry"); diff --git a/packages/common/lib/api/resources/PointOpportunity.ts b/packages/common/lib/api/resources/PointOpportunity.ts index 47a67828..a8ec9894 100644 --- a/packages/common/lib/api/resources/PointOpportunity.ts +++ b/packages/common/lib/api/resources/PointOpportunity.ts @@ -1,27 +1,43 @@ +import { DateTimeISOResolver } from "graphql-scalars"; import type { DateTime } from "luxon"; -import { Field, ID, ObjectType } from "type-graphql"; +import { Field, ObjectType } from "type-graphql"; -import { DateTimeScalar } from "../scalars/DateTimeScalar.js"; +import { dateTimeFromSomething } from "../../utility/time/intervalTools.js"; +import { Node } from "../relay.js"; +import type { GlobalId } from "../scalars/GlobalId.js"; +import { GlobalIdScalar } from "../scalars/GlobalId.js"; import { TimestampedResource } from "./Resource.js"; import { TeamType } from "./Team.js"; -@ObjectType() -export class PointOpportunityResource extends TimestampedResource { - @Field(() => ID) - uuid!: string; +@ObjectType({ + implements: [Node], +}) +export class PointOpportunityNode extends TimestampedResource implements Node { + @Field(() => GlobalIdScalar) + id!: GlobalId; @Field(() => String) name!: string; @Field(() => TeamType) type!: TeamType; - @Field(() => DateTimeScalar, { nullable: true }) - opportunityDate!: DateTime | null; + @Field(() => DateTimeISOResolver, { nullable: true }) + opportunityDate!: Date | null; + get opportunityDateTime(): DateTime | null { + return dateTimeFromSomething(this.opportunityDate ?? null); + } public getUniqueId(): string { - return this.uuid; + return this.id.id; } - public static init(init: Partial) { - return PointOpportunityResource.doInit(init); + public static init(init: { + id: string; + name: string; + type: TeamType; + opportunityDate: Date | null; + createdAt: Date; + updatedAt: Date; + }) { + return this.createInstance().withValues(init); } } diff --git a/packages/common/lib/api/resources/Resource.ts b/packages/common/lib/api/resources/Resource.ts index 86648601..82f7a94e 100644 --- a/packages/common/lib/api/resources/Resource.ts +++ b/packages/common/lib/api/resources/Resource.ts @@ -1,6 +1,10 @@ +import type { DateTime } from "luxon"; import { Field, ObjectType } from "type-graphql"; import type { Class } from "utility-types"; +import { dateTimeFromSomething } from "../../utility/time/intervalTools.js"; +import { GlobalId } from "../scalars/GlobalId.js"; + @ObjectType() export abstract class Resource { /** @@ -16,17 +20,30 @@ export abstract class Resource { throw new Error(`Method not implemented by subclass.`); } - protected static doInit(this: Class, init: object): R { - const instance = new this(); - Object.assign(instance, init); - return instance; + protected static createInstance(this: Class): R { + return new this(); + } + + protected withValues( + this: R, + values: D + ): R { + this.id = { id: values.id, typename: this.constructor.name }; + return this; } } @ObjectType() export abstract class TimestampedResource extends Resource { @Field(() => Date, { nullable: true }) - createdAt?: Date | null; + createdAt!: Date; + get createdAtDateTime(): DateTime { + return dateTimeFromSomething(this.createdAt); + } + @Field(() => Date, { nullable: true }) - updatedAt?: Date | null; + updatedAt!: Date; + get updatedAtDateTime(): DateTime { + return dateTimeFromSomething(this.updatedAt); + } } diff --git a/packages/common/lib/api/resources/Role.ts b/packages/common/lib/api/resources/Role.ts deleted file mode 100644 index 5f4a9cdb..00000000 --- a/packages/common/lib/api/resources/Role.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Field, InputType, ObjectType } from "type-graphql"; - -import type { - AccessLevel, - Authorization, -} from "../../authorization/structures.js"; -import { - CommitteeIdentifier, - CommitteeRole, - DbRole, - defaultAuthorization, - isCommitteeIdentifier, -} from "../../authorization/structures.js"; -import { roleToAccessLevel, roleToAuthorization } from "../../index.js"; - -import { Resource } from "./Resource.js"; - -@InputType("RoleResourceInput") -@ObjectType() -export class RoleResource extends Resource { - @Field(() => DbRole, { defaultValue: DbRole.None }) - dbRole!: DbRole; - @Field(() => CommitteeRole, { nullable: true }) - committeeRole!: CommitteeRole | null; - @Field(() => CommitteeIdentifier, { nullable: true }) - committeeIdentifier!: CommitteeIdentifier | null; - - public static init(init: Partial) { - return RoleResource.doInit(init); - } - - static fromAuthorization( - authorization: Readonly - ): RoleResource { - const partial: Partial = {}; - partial.dbRole = authorization.dbRole; - partial.committeeRole = authorization.committeeRole ?? null; - if (isCommitteeIdentifier(authorization.committeeIdentifier)) { - partial.committeeIdentifier = authorization.committeeIdentifier; - } - - return this.init(partial); - } - - toAuthorization(): Authorization { - return roleToAuthorization(this); - } - - toAccessLevel(): AccessLevel { - return roleToAccessLevel(this); - } -} - -export const defaultRole = RoleResource.fromAuthorization(defaultAuthorization); diff --git a/packages/common/lib/api/resources/Team.ts b/packages/common/lib/api/resources/Team.ts index 46a7d436..7fcfdd40 100644 --- a/packages/common/lib/api/resources/Team.ts +++ b/packages/common/lib/api/resources/Team.ts @@ -1,11 +1,10 @@ -import { Field, ID, ObjectType, registerEnumType } from "type-graphql"; +import { Field, ObjectType, registerEnumType } from "type-graphql"; -import { AccessControl } from "../../authorization/accessControl.js"; -import { AccessLevel } from "../../authorization/structures.js"; -import * as SimpleTypes from "../../utility/primitive/SimpleTypes.js"; +import { Node, createNodeClasses } from "../relay.js"; +import type { GlobalId } from "../scalars/GlobalId.js"; +import { GlobalIdScalar } from "../scalars/GlobalId.js"; import { TimestampedResource } from "./Resource.js"; - export const TeamType = { Spirit: "Spirit", Morale: "Morale", @@ -33,28 +32,36 @@ registerEnumType(TeamLegacyStatus, { description: "New Team vs Returning Team", }); -@ObjectType() -export class TeamResource extends TimestampedResource { - @Field(() => ID) - uuid!: string; +@ObjectType({ + implements: [Node], +}) +export class TeamNode extends TimestampedResource implements Node { + @Field(() => GlobalIdScalar) + id!: GlobalId; @Field(() => String) name!: string; @Field(() => TeamType) type!: TeamType; @Field(() => TeamLegacyStatus) legacyStatus!: TeamLegacyStatus; - @Field(() => String) - marathonYear!: SimpleTypes.MarathonYearString; - - @AccessControl({ accessLevel: AccessLevel.Committee }) - @Field(() => String, { nullable: true }) - persistentIdentifier!: string | null; public getUniqueId(): string { - return this.uuid; + return this.id.id; } - public static init(init: Partial) { - return TeamResource.doInit(init); + public static init(init: { + id: string; + name: string; + type: TeamType; + legacyStatus: TeamLegacyStatus; + createdAt?: Date | null; + updatedAt?: Date | null; + }) { + return TeamNode.createInstance().withValues(init); } } + +export const { TeamConnection, TeamEdge, TeamResult } = createNodeClasses( + TeamNode, + "Team" +); diff --git a/packages/common/lib/api/resources/authorization.test.ts b/packages/common/lib/api/resources/authorization.test.ts index f7854c3f..360c0275 100644 --- a/packages/common/lib/api/resources/authorization.test.ts +++ b/packages/common/lib/api/resources/authorization.test.ts @@ -9,196 +9,239 @@ import { DbRole, } from "../../index.js"; - const techChair: Authorization = { accessLevel: AccessLevel.Admin, dbRole: DbRole.Committee, - committeeIdentifier: CommitteeIdentifier.techCommittee, - committeeRole: CommitteeRole.Chair, + committees: [ + { + identifier: CommitteeIdentifier.techCommittee, + role: CommitteeRole.Chair, + }, + ], }; const techCoordinator: Authorization = { accessLevel: AccessLevel.Admin, dbRole: DbRole.Committee, - committeeIdentifier: CommitteeIdentifier.techCommittee, - committeeRole: CommitteeRole.Coordinator, + committees: [ + { + identifier: CommitteeIdentifier.techCommittee, + role: CommitteeRole.Coordinator, + }, + ], }; const techMember: Authorization = { accessLevel: AccessLevel.Admin, dbRole: DbRole.Committee, - committeeIdentifier: CommitteeIdentifier.techCommittee, - committeeRole: CommitteeRole.Member, + committees: [ + { + identifier: CommitteeIdentifier.techCommittee, + role: CommitteeRole.Member, + }, + ], }; const overallChair: Authorization = { accessLevel: AccessLevel.CommitteeChairOrCoordinator, dbRole: DbRole.Committee, - committeeIdentifier: CommitteeIdentifier.viceCommittee, - committeeRole: CommitteeRole.Chair, + committees: [ + { + identifier: CommitteeIdentifier.techCommittee, + role: CommitteeRole.Chair, + }, + { + identifier: CommitteeIdentifier.viceCommittee, + role: CommitteeRole.Chair, + }, + ], }; const dancerRelationsChair: Authorization = { accessLevel: AccessLevel.CommitteeChairOrCoordinator, dbRole: DbRole.Committee, - committeeIdentifier: CommitteeIdentifier.dancerRelationsCommittee, - committeeRole: CommitteeRole.Chair, + committees: [ + { + identifier: CommitteeIdentifier.dancerRelationsCommittee, + role: CommitteeRole.Chair, + }, + ], }; const dancerRelationsCoordinator: Authorization = { accessLevel: AccessLevel.CommitteeChairOrCoordinator, dbRole: DbRole.Committee, - committeeIdentifier: CommitteeIdentifier.dancerRelationsCommittee, - committeeRole: CommitteeRole.Coordinator, + committees: [ + { + identifier: CommitteeIdentifier.dancerRelationsCommittee, + role: CommitteeRole.Coordinator, + }, + ], }; const dancerRelationsMember: Authorization = { accessLevel: AccessLevel.Committee, dbRole: DbRole.Committee, - committeeIdentifier: CommitteeIdentifier.dancerRelationsCommittee, - committeeRole: CommitteeRole.Member, + committees: [ + { + identifier: CommitteeIdentifier.dancerRelationsCommittee, + role: CommitteeRole.Member, + }, + ], }; const member: Authorization = { accessLevel: AccessLevel.UKY, dbRole: DbRole.UKY, + committees: [], }; const publicAuth: Authorization = { accessLevel: AccessLevel.Public, dbRole: DbRole.Public, + committees: [], }; const none: Authorization = { accessLevel: AccessLevel.None, dbRole: DbRole.None, + committees: [], }; describe("checkAuthorization", () => { - it("should return true when the user's access level matches the required access level", () => { - expect( + it("should return true when the user's access level matches the required access level", async () => { + expect.hasAssertions(); + + await expect( checkAuthorization({ accessLevel: AccessLevel.Admin }, techChair) - ).toBe(true); - expect( + ).resolves.toBe(true); + await expect( checkAuthorization({ accessLevel: AccessLevel.Admin }, techCoordinator) - ).toBe(true); - expect( + ).resolves.toBe(true); + await expect( checkAuthorization({ accessLevel: AccessLevel.Admin }, techMember) - ).toBe(true); - expect( + ).resolves.toBe(true); + await expect( checkAuthorization( { accessLevel: AccessLevel.CommitteeChairOrCoordinator }, overallChair ) - ).toBe(true); - expect( + ).resolves.toBe(true); + await expect( checkAuthorization( { accessLevel: AccessLevel.CommitteeChairOrCoordinator }, dancerRelationsChair ) - ).toBe(true); - expect( + ).resolves.toBe(true); + await expect( checkAuthorization( { accessLevel: AccessLevel.CommitteeChairOrCoordinator }, dancerRelationsCoordinator ) - ).toBe(true); - expect( + ).resolves.toBe(true); + await expect( checkAuthorization( { accessLevel: AccessLevel.Committee }, dancerRelationsMember ) - ).toBe(true); - expect(checkAuthorization({ accessLevel: AccessLevel.UKY }, member)).toBe( - true - ); - expect( + ).resolves.toBe(true); + await expect( + checkAuthorization({ accessLevel: AccessLevel.UKY }, member) + ).resolves.toBe(true); + await expect( checkAuthorization({ accessLevel: AccessLevel.Public }, publicAuth) - ).toBe(true); - expect(checkAuthorization({ accessLevel: AccessLevel.None }, none)).toBe( - true - ); + ).resolves.toBe(true); + await expect( + checkAuthorization({ accessLevel: AccessLevel.None }, none) + ).resolves.toBe(true); }); - it("should return true when the user's access level is higher than the required access level", () => { - expect( + // TODO: Make the rest of these async + + it("should return true when the user's access level is higher than the required access level", async () => { + expect.assertions(3); + await expect( checkAuthorization({ accessLevel: AccessLevel.Committee }, techChair) - ).toBe(true); - expect( + ).resolves.toBe(true); + await expect( checkAuthorization( { accessLevel: AccessLevel.Committee }, techCoordinator ) - ).toBe(true); - expect( + ).resolves.toBe(true); + await expect( checkAuthorization({ accessLevel: AccessLevel.Committee }, techMember) - ).toBe(true); + ).resolves.toBe(true); }); - it("should return false when the user's access level is lower than the required access level", () => { - expect( + it("should return false when the user's access level is lower than the required access level", async () => { + expect.assertions(4); + await expect( checkAuthorization( { accessLevel: AccessLevel.CommitteeChairOrCoordinator }, dancerRelationsMember ) - ).toBe(false); - expect( + ).resolves.toBe(false); + await expect( checkAuthorization({ accessLevel: AccessLevel.UKY }, publicAuth) - ).toBe(false); - expect(checkAuthorization({ accessLevel: AccessLevel.Public }, none)).toBe( - false - ); - expect( + ).resolves.toBe(false); + await expect( + checkAuthorization({ accessLevel: AccessLevel.Public }, none) + ).resolves.toBe(false); + await expect( checkAuthorization( { accessLevel: AccessLevel.CommitteeChairOrCoordinator }, none ) - ).toBe(false); + ).resolves.toBe(false); }); - it("should work with a custom check function", () => { - expect( + it("should work with a custom check function", async () => { + expect.assertions(2); + await expect( checkAuthorization( { custom() { return true; }, }, - { accessLevel: AccessLevel.None, dbRole: DbRole.None } + { accessLevel: AccessLevel.None, dbRole: DbRole.None, committees: [] } ) - ).toBe(true); - expect( + ).resolves.toBe(true); + await expect( checkAuthorization( { custom() { return false; }, }, - { accessLevel: AccessLevel.None, dbRole: DbRole.None } + { accessLevel: AccessLevel.None, dbRole: DbRole.None, committees: [] } ) - ).toBe(false); + ).resolves.toBe(false); }); - it("should work with committeeIdentifier matching", () => { - expect( + it("should work with committeeIdentifier matching", async () => { + expect.assertions(2); + await expect( checkAuthorization( { committeeIdentifier: CommitteeIdentifier.techCommittee, }, techChair ) - ).toBe(true); - expect( + ).resolves.toBe(true); + await expect( checkAuthorization( { committeeIdentifier: CommitteeIdentifier.techCommittee, }, none ) - ).toBe(false); + ).resolves.toBe(false); }); - it("should work with committeeIdentifiers matching", () => { - expect( + it("should work with committeeIdentifiers matching", async () => { + expect.assertions(2); + await expect( checkAuthorization( { committeeIdentifiers: [ @@ -208,8 +251,8 @@ describe("checkAuthorization", () => { }, techChair ) - ).toBe(true); - expect( + ).resolves.toBe(true); + await expect( checkAuthorization( { committeeIdentifiers: [ @@ -219,98 +262,82 @@ describe("checkAuthorization", () => { }, none ) - ).toBe(false); + ).resolves.toBe(false); }); - it("should work with exact dbRole matching", () => { - expect( + it("should work with exact dbRole matching", async () => { + expect.assertions(2); + await expect( checkAuthorization( { dbRole: DbRole.Committee, }, techChair ) - ).toBe(true); - expect( + ).resolves.toBe(true); + await expect( checkAuthorization( { dbRole: DbRole.Committee, }, none ) - ).toBe(false); - }); - - it("should work with exact committeeRole matching", () => { - expect( - checkAuthorization( - { - committeeRole: CommitteeRole.Chair, - }, - techChair - ) - ).toBe(true); - expect( - checkAuthorization( - { - committeeRole: CommitteeRole.Chair, - }, - none - ) - ).toBe(false); + ).resolves.toBe(false); }); - it("should work with minimum dbRole matching", () => { - expect( + it("should work with minimum dbRole matching", async () => { + expect.assertions(1); + await expect( checkAuthorization( { minDbRole: DbRole.Committee, }, techChair ) - ).toBe(true); + ).resolves.toBe(true); }); - it("should work with minimum committeeRole matching", () => { - expect( + it("should work with minimum committeeRole matching", async () => { + expect.assertions(3); + await expect( checkAuthorization( { minCommitteeRole: CommitteeRole.Chair, }, techChair ) - ).toBe(true); - expect( + ).resolves.toBe(true); + await expect( checkAuthorization( { minCommitteeRole: CommitteeRole.Coordinator, }, techChair ) - ).toBe(true); - expect( + ).resolves.toBe(true); + await expect( checkAuthorization( { minCommitteeRole: CommitteeRole.Coordinator, }, none ) - ).toBe(false); + ).resolves.toBe(false); }); - it("should work with all of the above", () => { - expect( + it("should work with all of the above", async () => { + expect.assertions(3); + await expect( checkAuthorization( { accessLevel: AccessLevel.Admin, dbRole: DbRole.Committee, committeeIdentifier: CommitteeIdentifier.techCommittee, - committeeRole: CommitteeRole.Chair, }, techChair ) - ).toBe(true); - expect( + ).resolves.toBe(true); + await expect( checkAuthorization( { accessLevel: AccessLevel.Admin, @@ -320,8 +347,8 @@ describe("checkAuthorization", () => { }, techChair ) - ).toBe(true); - expect( + ).resolves.toBe(true); + await expect( checkAuthorization( { accessLevel: AccessLevel.Admin, @@ -330,6 +357,6 @@ describe("checkAuthorization", () => { }, none ) - ).toBe(false); + ).resolves.toBe(false); }); }); diff --git a/packages/common/lib/api/resources/index.ts b/packages/common/lib/api/resources/index.ts index ced6bad1..4627557a 100644 --- a/packages/common/lib/api/resources/index.ts +++ b/packages/common/lib/api/resources/index.ts @@ -1,20 +1,31 @@ -export { AuthIdPairResource } from "./AuthIdPair.js"; -export { ConfigurationResource } from "./Configuration.js"; -export { DeviceResource } from "./Device.js"; -export { EventOccurrenceResource, EventResource } from "./Event.js"; -export { FeedResource } from "./FeedResource.js"; -export { ImageResource } from "./Image.js"; -export { LoginFlowSessionResource } from "./LoginFlowSession.js"; +export * from "../scalars/GlobalId.js"; +export { AuthIdPairResource as AuthIdPairNode } from "../types/AuthIdPair.js"; +export { EffectiveCommitteeRole } from "../types/EffectiveCommitteeRole.js"; +export { IntervalISO } from "../types/IntervalISO.js"; + +export * from "./Committee.js"; +export * from "./Configuration.js"; +export * from "./Device.js"; +export * from "./Event.js"; +export * from "./Feed.js"; +export * from "./Fundraising.js"; +export * from "./Image.js"; export * from "./Marathon.js"; export * from "./MarathonHour.js"; -export { MembershipPositionType, MembershipResource } from "./Membership.js"; +export * from "./Membership.js"; +export * from "./Notification.js"; +export * from "./Person.js"; +export * from "./PointEntry.js"; +export * from "./PointOpportunity.js"; +export * from "./Resource.js"; +export * from "./Team.js"; + export { - NotificationDeliveryResource, - NotificationResource, -} from "./Notification.js"; -export { PersonResource } from "./Person.js"; -export { PointEntryResource } from "./PointEntry.js"; -export { PointOpportunityResource } from "./PointOpportunity.js"; -export { Resource, TimestampedResource } from "./Resource.js"; -export { RoleResource, defaultRole } from "./Role.js"; -export { TeamLegacyStatus, TeamResource, TeamType } from "./Team.js"; + Connection, + Edge, + Node, + PageInfo, + Resource, + Result, +} from "../relay.js"; +export * from "../resourceError.js"; diff --git a/packages/common/lib/api/scalars/Cursor.ts b/packages/common/lib/api/scalars/Cursor.ts new file mode 100644 index 00000000..be6e04e2 --- /dev/null +++ b/packages/common/lib/api/scalars/Cursor.ts @@ -0,0 +1,27 @@ +import { GraphQLScalarType, Kind } from "graphql"; + +export const CursorScalar = new GraphQLScalarType({ + name: "Cursor", + description: "Cursor custom scalar type", + parseValue(value): string { + if (typeof value === "string") { + return value; + } else { + throw new TypeError("CursorScalar can only parse strings"); + } + }, + serialize(value): string { + if (typeof value === "string") { + return value; + } else { + throw new TypeError("CursorScalar can only serialize strings"); + } + }, + parseLiteral(ast): string { + if (ast.kind === Kind.STRING) { + return ast.value; + } else { + throw new TypeError("CursorScalar can only parse literal string values"); + } + }, +}); diff --git a/packages/common/lib/api/scalars/DateRangeScalar.ts b/packages/common/lib/api/scalars/DateRangeScalar.ts deleted file mode 100644 index c6a7a2d4..00000000 --- a/packages/common/lib/api/scalars/DateRangeScalar.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { GraphQLScalarType, Kind } from "graphql"; -import { DateTime, Duration, Interval } from "luxon"; - -import { LuxonError } from "../../utility/errors/validation.js"; - -export const DateRangeScalar = new GraphQLScalarType({ - name: "LuxonDateRange", - description: "Date range custom scalar type (just an ISO 8601 interval)", - parseValue(value): Interval { - if (value == null) { - throw new TypeError("DateRangeScalar cannot parse nullish values"); - } - if (typeof value === "string") { - const interval = Interval.fromISO(value); - if (!interval.isValid) { - throw new LuxonError(interval); - } - return interval; - } - if (Interval.isInterval(value)) { - return value; - } - if (Array.isArray(value) && value.length === 2) { - if (DateTime.isDateTime(value[0])) { - if (DateTime.isDateTime(value[1])) { - return Interval.fromDateTimes(value[0], value[1]); - } - if (Duration.isDuration(value[1])) { - return Interval.after(value[0], value[1]); - } - } - if (value[0] instanceof Date && value[1] instanceof Date) { - if ( - value[0].toString() === "Invalid Date" || - value[1].toString() === "Invalid Date" - ) { - throw new TypeError( - "DateRangeScalar cannot parse tuples of [Date, Date] with invalid dates" - ); - } - return Interval.fromDateTimes( - DateTime.fromJSDate(value[0]), - DateTime.fromJSDate(value[1]) - ); - } - throw new TypeError( - "DateRangeScalar can only parse tuples of [DateTime, DateTime], [Date, Date] or [DateTime, Duration]" - ); - } - throw new TypeError( - "DateRangeScalar can only parse strings, Luxon intervals, or tuples of [DateTime, DateTime], [Date, Date] or [DateTime, Duration]" - ); - }, - parseLiteral(ast): Interval { - if (ast.kind === Kind.STRING) { - const interval = Interval.fromISO(ast.value); - if (!interval.isValid) { - throw new LuxonError(interval); - } - return interval; - } - throw new TypeError("DateRangeScalar can only parse literal string values"); - }, - specifiedByURL: "https://www.iso.org/iso-8601-date-and-time-format.html", - serialize(value): string { - if (Interval.isInterval(value) && value.isValid) { - return value.toISO(); - } else if (typeof value === "string") { - const interval = Interval.fromISO(value); - if (interval.isValid) { - return interval.toISO(); - } else { - throw new TypeError( - "DateRangeScalar can only serialize strings that are valid ISO 8601 intervals", - { - cause: new LuxonError(interval), - } - ); - } - } - throw new TypeError( - "DateRangeScalar can only serialize Luxon Interval objects" - ); - }, -}); diff --git a/packages/common/lib/api/scalars/DateTimeScalar.ts b/packages/common/lib/api/scalars/DateTimeScalar.ts deleted file mode 100644 index 68b23407..00000000 --- a/packages/common/lib/api/scalars/DateTimeScalar.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { GraphQLScalarType, Kind } from "graphql"; -import { DateTime } from "luxon"; - -export const DateTimeScalar = new GraphQLScalarType({ - name: "LuxonDateTime", - description: "Luxon DateTime custom scalar type", - parseValue(value): DateTime { - if (typeof value === "string") { - return DateTime.fromISO(value); - } else if (DateTime.isDateTime(value)) { - return value; - } else if (typeof value === "number") { - return DateTime.fromMillis(value); - } else if (value instanceof Date) { - return DateTime.fromJSDate(value); - } else if (value && typeof value === "object") { - const dateTimeFromObject = DateTime.fromObject(value); - if (dateTimeFromObject.isValid) { - return dateTimeFromObject; - } else { - throw new TypeError( - "DateTimeScalar can only parse objects that are valid Luxon DateTime objects" - ); - } - } else { - throw new TypeError( - "DateTimeScalar can only parse strings, numbers, Date objects, or Luxon DateTime objects" - ); - } - }, - serialize(value): string { - if (typeof value === "string") { - const dateTime = DateTime.fromISO(value); - if (dateTime.isValid) { - return dateTime.toISO(); - } else { - throw new TypeError( - "DateTimeScalar can only serialize strings that are valid ISO 8601 dates" - ); - } - } else if (DateTime.isDateTime(value) && value.isValid) { - return value.toISO(); - } else { - throw new TypeError( - "DateTimeScalar can only serialize strings or Luxon DateTime objects" - ); - } - }, - parseLiteral(ast): DateTime { - if (ast.kind === Kind.STRING) { - return DateTime.fromISO(ast.value); - } else { - throw new TypeError( - "DateTimeScalar can only parse literal string values" - ); - } - }, -}); diff --git a/packages/common/lib/api/scalars/DurationScalar.ts b/packages/common/lib/api/scalars/DurationScalar.ts deleted file mode 100644 index 4697aa74..00000000 --- a/packages/common/lib/api/scalars/DurationScalar.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { GraphQLScalarType, Kind } from "graphql"; -import { Duration } from "luxon"; - -export const DurationScalar = new GraphQLScalarType({ - name: "LuxonDuration", - description: "Luxon Duration custom scalar type", - parseValue(value): Duration { - if (typeof value === "string") { - return Duration.fromISO(value); - } else if (Duration.isDuration(value)) { - return value; - } else if (typeof value === "number") { - return Duration.fromMillis(value); - } else if (value && typeof value === "object") { - const durationFromObject = Duration.fromObject(value); - if (durationFromObject.isValid as boolean) { - return durationFromObject; - } else { - throw new TypeError( - "DurationScalar can only parse objects that are valid Luxon Duration objects" - ); - } - } else { - throw new TypeError( - "DurationScalar can only parse strings, numbers, or Luxon Duration objects" - ); - } - }, - serialize(value): string { - if (typeof value === "string") { - const duration = Duration.fromISO(value); - if (duration.isValid) { - return duration.toISO(); - } else { - throw new TypeError( - "DurationScalar can only serialize strings that are valid ISO 8601 durations" - ); - } - } else if (Duration.isDuration(value) && value.isValid) { - return value.toISO(); - } else { - throw new TypeError( - "DurationScalar can only serialize strings or Luxon Duration objects" - ); - } - }, - parseLiteral(ast): Duration { - if (ast.kind === Kind.STRING) { - return Duration.fromISO(ast.value); - } else { - throw new TypeError( - "DurationScalar can only parse literal string values" - ); - } - }, -}); diff --git a/packages/common/lib/api/scalars/GlobalId.ts b/packages/common/lib/api/scalars/GlobalId.ts new file mode 100644 index 00000000..8a202ea3 --- /dev/null +++ b/packages/common/lib/api/scalars/GlobalId.ts @@ -0,0 +1,105 @@ +import { GraphQLScalarType, Kind } from "graphql"; + +import { + UTF8ArrToStr, + arrayToBase64String, + base64StringToArray, + strToUTF8Arr, +} from "../../utility/primitive/base64.js"; + +export interface GlobalId { + typename: string; + id: string; +} + +export const GlobalIdScalar = new GraphQLScalarType< + { typename: string; id: string }, + string +>({ + name: "GlobalId", + description: "GlobalId custom scalar type", + extensions: {}, + parseValue(value): { typename: string; id: string } { + if (typeof value === "object") { + if ( + value && + typeof value === "object" && + "typename" in value && + "id" in value + ) { + const { typename, id } = value; + if (typeof typename !== "string" || typeof id !== "string") { + throw new TypeError( + "GlobalIdScalar can only parse objects with typename and id as strings" + ); + } + return { typename, id }; + } else { + throw new TypeError( + "GlobalIdScalar can only parse objects with typename and id" + ); + } + } else if (typeof value === "string") { + const plain = UTF8ArrToStr(base64StringToArray(value)); + const [typename, id, ...rest] = plain.split(":"); + if (rest.length > 0) { + throw new TypeError( + "GlobalIdScalar can only parse strings with one colon" + ); + } + if (!typename || !id) { + throw new TypeError( + "GlobalIdScalar can only parse strings with a colon" + ); + } + return { typename, id }; + } else { + throw new TypeError("GlobalIdScalar can only parse strings or objects"); + } + }, + serialize(value): string { + if (typeof value === "object") { + if ( + value && + typeof value === "object" && + "typename" in value && + "id" in value + ) { + const { typename, id } = value; + if (typeof typename !== "string" || typeof id !== "string") { + throw new TypeError( + "GlobalIdScalar can only serialize objects with typename and id as strings" + ); + } + return arrayToBase64String(strToUTF8Arr(`${typename}:${id}`)); + } else { + throw new TypeError( + "GlobalIdScalar can only serialize objects with typename and id" + ); + } + } else { + throw new TypeError("GlobalIdScalar can only serialize objects"); + } + }, + parseLiteral(ast): { typename: string; id: string } { + if (ast.kind === Kind.STRING) { + const plain = UTF8ArrToStr(base64StringToArray(ast.value)); + const [typename, id, ...rest] = plain.split(":"); + if (rest.length > 0) { + throw new TypeError( + "GlobalIdScalar can only parse strings with one colon" + ); + } + if (!typename || !id) { + throw new TypeError( + "GlobalIdScalar can only parse strings with a colon" + ); + } + return { typename, id }; + } else { + throw new TypeError( + "GlobalIdScalar can only parse literal string values" + ); + } + }, +}); diff --git a/packages/common/lib/api/resources/AuthIdPair.ts b/packages/common/lib/api/types/AuthIdPair.ts similarity index 89% rename from packages/common/lib/api/resources/AuthIdPair.ts rename to packages/common/lib/api/types/AuthIdPair.ts index a0253ad8..f2e1ae9f 100644 --- a/packages/common/lib/api/resources/AuthIdPair.ts +++ b/packages/common/lib/api/types/AuthIdPair.ts @@ -1,8 +1,7 @@ import { Field, ObjectType } from "type-graphql"; import { AuthSource } from "../../authorization/structures.js"; - -import { Resource } from "./Resource.js"; +import { Resource } from "../resources/Resource.js"; @ObjectType() export class AuthIdPairResource< diff --git a/packages/common/lib/api/types/EffectiveCommitteeRole.ts b/packages/common/lib/api/types/EffectiveCommitteeRole.ts new file mode 100644 index 00000000..45a76e62 --- /dev/null +++ b/packages/common/lib/api/types/EffectiveCommitteeRole.ts @@ -0,0 +1,25 @@ +import { Field, ObjectType } from "type-graphql"; + +import { + CommitteeIdentifier, + CommitteeRole, +} from "../../authorization/structures.js"; + +@ObjectType("EffectiveCommitteeRole") +export class EffectiveCommitteeRole { + @Field(() => CommitteeIdentifier) + identifier!: CommitteeIdentifier; + + @Field(() => CommitteeRole) + role!: CommitteeRole; + + public static init( + identifier: CommitteeIdentifier, + role: CommitteeRole + ): EffectiveCommitteeRole { + const effectiveCommitteeRole = new this(); + effectiveCommitteeRole.identifier = identifier; + effectiveCommitteeRole.role = role; + return effectiveCommitteeRole; + } +} diff --git a/packages/common/lib/api/types/IntervalISO.ts b/packages/common/lib/api/types/IntervalISO.ts new file mode 100644 index 00000000..568aa846 --- /dev/null +++ b/packages/common/lib/api/types/IntervalISO.ts @@ -0,0 +1,40 @@ +import { DateTimeISOResolver } from "graphql-scalars"; +import { DateTime, Interval } from "luxon"; +import { Field, InputType, ObjectType } from "type-graphql"; + +import { dateTimeFromSomething } from "../../utility/time/intervalTools.js"; + +@ObjectType() +@InputType("IntervalISOInput") +export class IntervalISO { + @Field(() => DateTimeISOResolver) + start!: Date; + get startDateTime(): DateTime { + return dateTimeFromSomething(this.start); + } + + @Field(() => DateTimeISOResolver) + end!: Date; + get endDateTime(): DateTime { + return dateTimeFromSomething(this.end); + } + + get interval(): Interval { + return Interval.fromDateTimes(this.startDateTime, this.endDateTime); + } + + static init(start: Date, end: Date): IntervalISO { + const self = new IntervalISO(); + self.start = start; + self.end = end; + return self; + } + + static fromDateTimes(start: DateTime, end: DateTime): IntervalISO { + return this.init(start.toJSDate(), end.toJSDate()); + } + + static fromInterval(interval: Interval): IntervalISO { + return this.fromDateTimes(interval.start, interval.end); + } +} diff --git a/packages/common/lib/authentication/jwt.ts b/packages/common/lib/authentication/jwt.ts index 26125cf9..c939b698 100644 --- a/packages/common/lib/authentication/jwt.ts +++ b/packages/common/lib/authentication/jwt.ts @@ -1,37 +1,17 @@ -import type { PersonResource } from "../api/resources/Person.js"; -import { roleToAccessLevel } from "../authorization/role.js"; -import type { - AccessLevel, - AuthSource, - Authorization, - CommitteeRole, - DbRole, -} from "../authorization/structures.js"; +import type { PersonNode } from "../api/resources/Person.js"; +import type { AuthSource } from "../authorization/structures.js"; export interface UserData { - auth: Authorization; userId?: string; - teamIds?: string[]; - captainOfTeamIds?: string[]; authSource: AuthSource; } export function makeUserData( - person: PersonResource, - authSource: AuthSource, - teamIds?: string[], - captainOfTeamIds?: string[] + person: PersonNode, + authSource: AuthSource ): UserData { return { - auth: { - dbRole: person.role.dbRole, - committeeRole: person.role.committeeRole ?? undefined, - committeeIdentifier: person.role.committeeIdentifier ?? undefined, - accessLevel: roleToAccessLevel(person.role), - }, - userId: person.uuid, - teamIds, - captainOfTeamIds, + userId: person.id.id, authSource, }; } @@ -40,11 +20,4 @@ export interface JwtPayload { sub?: string; // The type of authentication used to log in (e.g. "uky-linkblue" or "anonymous") auth_source: AuthSource; - // TODO: Replace these fields with either "roles" or "groups" (these are specified in the RFC 7643 Section 4.1.2) - dbRole: DbRole; - committee_role?: CommitteeRole; - committee?: string; - access_level: AccessLevel; - team_ids?: string[]; - captain_of_team_ids?: string[]; } diff --git a/packages/common/lib/authorization/accessControl.ts b/packages/common/lib/authorization/accessControl.ts index c6556e2e..e9256544 100644 --- a/packages/common/lib/authorization/accessControl.ts +++ b/packages/common/lib/authorization/accessControl.ts @@ -3,21 +3,27 @@ import { UseMiddleware } from "type-graphql"; import type { Primitive } from "utility-types"; import type { - AccessLevel, Authorization, CommitteeRole, DbRole, - PersonResource, + MembershipPositionType, + PersonNode, + TeamType, UserData, } from "../index.js"; import { + AccessLevel, DetailedError, ErrorCode, compareCommitteeRole, compareDbRole, } from "../index.js"; -export interface AuthorizationRule extends Partial { +export interface AuthorizationRule { + /** + * The minimum access level required to access this resource + */ + accessLevel?: AccessLevel; /** * Exact DanceBlue role, cannot be used with minDbRole */ @@ -27,10 +33,10 @@ export interface AuthorizationRule extends Partial { * Committee > TeamCaptain > TeamMember > Public > None */ minDbRole?: DbRole; - /** - * Exact committee role, cannot be used with minCommitteeRole - */ - committeeRole?: CommitteeRole; + // /** + // * Exact committee role, cannot be used with minCommitteeRole + // */ + // committeeRole?: CommitteeRole; /** * Minimum committee role, cannot be used with committeeRole */ @@ -53,22 +59,62 @@ export interface AuthorizationRule extends Partial { * * Should usually be avoided, but can be used for more complex authorization rules */ - custom?: (authorization: Authorization) => boolean; + custom?: (authorization: Authorization) => boolean | Promise; +} + +export function prettyPrintAuthorizationRule(rule: AuthorizationRule): string { + const parts: string[] = []; + if (rule.accessLevel != null) { + parts.push(`accessLevel >= ${rule.accessLevel}`); + } + if (rule.dbRole != null) { + parts.push(`dbRole === ${rule.dbRole}`); + } + if (rule.minDbRole != null) { + parts.push(`dbRole >= ${rule.minDbRole}`); + } + // if (rule.committeeRole != null) { + // parts.push(`committeeRole === ${rule.committeeRole}`); + // } + if (rule.minCommitteeRole != null) { + parts.push(`committeeRole >= ${rule.minCommitteeRole}`); + } + if (rule.committeeIdentifier != null) { + parts.push(`committeeIdentifier === ${rule.committeeIdentifier}`); + } + if (rule.committeeIdentifiers != null) { + parts.push( + `committeeIdentifier in ${rule.committeeIdentifiers.join(", ")}` + ); + } + if (rule.custom != null) { + parts.push(`[custom]`); + } + return parts.join(", "); } -export function checkAuthorization( - role: AuthorizationRule, +export async function checkAuthorization( + { + accessLevel, + committeeIdentifier, + committeeIdentifiers, + // committeeRole, + custom, + dbRole, + minCommitteeRole, + minDbRole, + }: AuthorizationRule, authorization: Authorization ) { - if (role.minDbRole != null && role.dbRole != null) { + if (minDbRole != null && dbRole != null) { throw new TypeError(`Cannot specify both dbRole and minDbRole.`); } - if (role.minCommitteeRole != null && role.committeeRole != null) { - throw new TypeError( - `Cannot specify both committeeRole and minCommitteeRole.` - ); - } - if (role.committeeIdentifier != null && role.committeeIdentifiers != null) { + // if (minCommitteeRole != null && committeeRole != null) { + // throw new TypeError( + // `Cannot specify both committeeRole and minCommitteeRole.` + // ); + // } + if (committeeIdentifier != null && committeeIdentifiers != null) { throw new TypeError( `Cannot specify both committeeIdentifier and committeeIdentifiers.` ); @@ -77,116 +123,161 @@ export function checkAuthorization( let matches = true; // Access Level - if (role.accessLevel != null) { - matches &&= authorization.accessLevel >= role.accessLevel; + if (accessLevel != null) { + matches &&= authorization.accessLevel >= accessLevel; } // DB role - if (role.dbRole != null) { - matches &&= authorization.dbRole === role.dbRole; + if (dbRole != null) { + matches &&= authorization.dbRole === dbRole; } - if (role.minDbRole != null) { - matches &&= compareDbRole(authorization.dbRole, role.minDbRole) >= 0; + if (minDbRole != null) { + matches &&= compareDbRole(authorization.dbRole, minDbRole) >= 0; } // Committee role - if (role.committeeRole != null) { - matches &&= authorization.committeeRole === role.committeeRole; - } - if (role.minCommitteeRole != null) { - if (authorization.committeeRole == null) { + // if (committeeRole != null) { + // matches &&= authorization.committees.some( + // (committee) => + // committee.role === committeeRole && + // committee.identifier === committeeIdentifier + // ); + // } + if (minCommitteeRole != null) { + if (authorization.committees.length === 0) { matches = false; } else { - matches &&= - compareCommitteeRole( - authorization.committeeRole, - role.minCommitteeRole - ) >= 0; + matches &&= authorization.committees.some( + (committee) => + compareCommitteeRole(committee.role, minCommitteeRole) >= 0 + ); } } // Committee identifier(s) - if (role.committeeIdentifier != null) { - matches &&= authorization.committeeIdentifier === role.committeeIdentifier; + if (committeeIdentifier != null) { + matches &&= authorization.committees.some( + (committee) => committee.identifier === committeeIdentifier + ); } - if (role.committeeIdentifiers != null) { - matches &&= role.committeeIdentifiers.includes( - String(authorization.committeeIdentifier) + if (committeeIdentifiers != null) { + matches &&= authorization.committees.some((committee) => + committeeIdentifiers.includes(committee.identifier) ); } // Custom auth checker - if (role.custom != null) { - matches &&= role.custom(authorization); + if (custom != null) { + matches &&= await custom(authorization); } return matches; } +interface ExtractorData { + authenticatedUser: PersonNode | null; + teamMemberships: SimpleTeamMembership[]; + userData: UserData; + authorization: Authorization; +} + /** * An AccessControlParam accepts a user if: * * 1. The user's access level is greater than or equal to the access level specified (AccessLevel.None by default) * 2. The user's role matches one of the specified authorization rules * 3. The resolver arguments match ALL of the specified argument matchers + * 4. The root object matches ALL of the specified root matchers + * 5. The custom authorization rule returns true */ -export interface AccessControlParam< - RootType extends Record = Record, -> { - authRules?: readonly AuthorizationRule[]; +export interface AccessControlParam { + authRules?: + | readonly AuthorizationRule[] + | ((root: RootType) => readonly AuthorizationRule[]); accessLevel?: AccessLevel; argumentMatch?: { argument: string | ((args: ArgsDictionary) => Primitive | Primitive[]); - extractor: ( - userData: UserData, - person: PersonResource | null - ) => Primitive | Primitive[]; + extractor: (param: ExtractorData) => Primitive | Primitive[]; }[]; rootMatch?: { root: string | ((root: RootType) => Primitive | Primitive[]); - extractor: ( - userData: UserData, - person: PersonResource | null - ) => Primitive | Primitive[]; + extractor: (param: ExtractorData) => Primitive | Primitive[]; }[]; + /** + * Custom authorization rule + * + * Should usually be avoided, but can be used for more complex authorization rules + * + * If the custom rule returns a boolean the user is allowed access if the rule returns true and an error is thrown if the rule returns false. + * If the custom rule returns null the field is set to null (make sure the field is nullable in the schema) + * If one param returns false and another returns null, an error will be thrown and the null ignored. + */ + custom?: ( + root: RootType, + context: ExtractorData, + result: ResultType + ) => boolean | null | Promise; +} + +export interface SimpleTeamMembership { + teamType: TeamType; + teamId: string; + position: MembershipPositionType; } export interface AuthorizationContext { - authenticatedUser: PersonResource | null; + authenticatedUser: PersonNode | null; + teamMemberships: SimpleTeamMembership[]; userData: UserData; + authorization: Authorization; } export function AccessControl< - RootType extends Record = Record, + RootType extends object = never, + ResultType extends object = never, >( - ...params: AccessControlParam[] + ...params: AccessControlParam[] ): MethodDecorator & PropertyDecorator { - const middleware: MiddlewareFn = ( + const middleware: MiddlewareFn = async ( resolverData, next ) => { const { context, args } = resolverData; const root = resolverData.root as RootType; - const { userData, authenticatedUser } = context; + const { authorization } = context; + + if (authorization.accessLevel === AccessLevel.SuperAdmin) { + // Super admins have access to everything + return next(); + } let ok = false; for (const rule of params) { if (rule.accessLevel != null) { - if (rule.accessLevel > userData.auth.accessLevel) { + if (rule.accessLevel > authorization.accessLevel) { continue; } } if (rule.authRules != null) { - if (rule.authRules.length === 0) { + const authRules = + typeof rule.authRules === "function" + ? rule.authRules(root) + : rule.authRules; + if (authRules.length === 0) { throw new DetailedError( ErrorCode.InternalFailure, "Resource has no allowed authorization rules." ); } - const matches = rule.authRules.some((rule) => - checkAuthorization(rule, userData.auth) - ); + let matches = false; + for (const authRule of authRules) { + // eslint-disable-next-line no-await-in-loop + matches = await checkAuthorization(authRule, authorization); + if (matches) { + break; + } + } if (!matches) { continue; } @@ -204,7 +295,7 @@ export function AccessControl< "FieldMatchAuthorized argument is null or undefined." ); } - const expectedValue = match.extractor(userData, authenticatedUser); + const expectedValue = match.extractor(context); if (Array.isArray(expectedValue)) { if (Array.isArray(argValue)) { @@ -225,6 +316,7 @@ export function AccessControl< } if (rule.rootMatch != null) { + let shouldContinue = false; for (const match of rule.rootMatch) { const rootValue = typeof match.root === "string" @@ -236,24 +328,31 @@ export function AccessControl< "FieldMatchAuthorized root is null or undefined." ); } - const expectedValue = match.extractor(userData, authenticatedUser); + const expectedValue = match.extractor(context); if (Array.isArray(expectedValue)) { if (Array.isArray(rootValue)) { if (!rootValue.some((v) => expectedValue.includes(v))) { - continue; + shouldContinue = true; + break; } } else if (!expectedValue.includes(rootValue)) { - continue; + shouldContinue = true; + break; } } else if (Array.isArray(rootValue)) { if (!rootValue.includes(expectedValue)) { - continue; + shouldContinue = true; + break; } } else if (rootValue !== expectedValue) { - continue; + shouldContinue = true; + break; } } + if (shouldContinue) { + continue; + } } ok = true; @@ -265,9 +364,31 @@ export function AccessControl< ErrorCode.Unauthorized, "You are not authorized to access this resource." ); - } else { - return next(); } + + const result = (await next()) as ResultType; + + let customResult: boolean | null = true; + for (const rule of params) { + if (rule.custom != null) { + // eslint-disable-next-line no-await-in-loop + customResult = await rule.custom(root, context, result); + if (customResult === true) { + break; + } + } + } + + if (customResult === false) { + throw new DetailedError( + ErrorCode.Unauthorized, + "You are not authorized to access this resource." + ); + } else if (customResult === null) { + return null; + } + + return result; }; return UseMiddleware(middleware); diff --git a/packages/common/lib/authorization/role.test.ts b/packages/common/lib/authorization/role.test.ts index b8178492..0ea006e3 100644 --- a/packages/common/lib/authorization/role.test.ts +++ b/packages/common/lib/authorization/role.test.ts @@ -5,121 +5,84 @@ import { CommitteeIdentifier, CommitteeRole, DbRole, - RoleResource, } from "../index.js"; -import { roleToAccessLevel, roleToAuthorization } from "./role.js"; +import { roleToAccessLevel } from "./role.js"; + +// TODO test the committee hierarchy system (i.e. overall and vice roles vs other committees) describe("roleToAccessLevel", () => { it("returns the correct access level for a given role normally", () => { - const chairRole = RoleResource.init({ + const chairRole = { dbRole: DbRole.Committee, committeeRole: CommitteeRole.Chair, committeeIdentifier: CommitteeIdentifier.dancerRelationsCommittee, - }); + }; expect(roleToAccessLevel(chairRole)).toBe( AccessLevel.CommitteeChairOrCoordinator ); - const coordinatorRole = RoleResource.init({ + const coordinatorRole = { dbRole: DbRole.Committee, committeeRole: CommitteeRole.Coordinator, committeeIdentifier: CommitteeIdentifier.dancerRelationsCommittee, - }); + }; expect(roleToAccessLevel(coordinatorRole)).toBe( AccessLevel.CommitteeChairOrCoordinator ); - const memberRole = RoleResource.init({ + const memberRole = { dbRole: DbRole.Committee, committeeRole: CommitteeRole.Member, committeeIdentifier: CommitteeIdentifier.dancerRelationsCommittee, - }); + }; expect(roleToAccessLevel(memberRole)).toBe(AccessLevel.Committee); - const teamMemberRole = RoleResource.init({ + const teamMemberRole = { dbRole: DbRole.UKY, - }); + }; expect(roleToAccessLevel(teamMemberRole)).toBe(AccessLevel.UKY); - const publicRole = RoleResource.init({ + const publicRole = { dbRole: DbRole.Public, - }); + }; expect(roleToAccessLevel(publicRole)).toBe(AccessLevel.Public); - const noneRole = RoleResource.init({ + const noneRole = { dbRole: DbRole.None, - }); + }; expect(roleToAccessLevel(noneRole)).toBe(AccessLevel.None); }); it("grants any member of the tech committee admin access", () => { - const chairRole = RoleResource.init({ + const chairRole = { dbRole: DbRole.Committee, committeeRole: CommitteeRole.Chair, committeeIdentifier: CommitteeIdentifier.techCommittee, - }); + }; expect(roleToAccessLevel(chairRole)).toBe(AccessLevel.Admin); - const coordinatorRole = RoleResource.init({ + const coordinatorRole = { dbRole: DbRole.Committee, committeeRole: CommitteeRole.Coordinator, committeeIdentifier: CommitteeIdentifier.techCommittee, - }); + }; expect(roleToAccessLevel(coordinatorRole)).toBe(AccessLevel.Admin); - const memberRole = RoleResource.init({ + const memberRole = { dbRole: DbRole.Committee, committeeRole: CommitteeRole.Member, committeeIdentifier: CommitteeIdentifier.techCommittee, - }); + }; expect(roleToAccessLevel(memberRole)).toBe(AccessLevel.Admin); }); it("throws an error for an illegal role", () => { - const illegalRole = RoleResource.init({ + const illegalRole = { dbRole: "illegal" as DbRole, - }); + }; expect(() => roleToAccessLevel(illegalRole)).toThrow( "Illegal DbRole: [Parsing of 'illegal' failed]" ); }); }); - -describe("roleToAuthorization", () => { - it("converts a role to an authorization object", () => { - const role = RoleResource.init({ - dbRole: DbRole.UKY, - }); - expect(roleToAuthorization(role)).toStrictEqual({ - dbRole: DbRole.UKY, - accessLevel: AccessLevel.UKY, - }); - }); - - it("converts a role with a committee set to an authorization object", () => { - const role = RoleResource.init({ - dbRole: DbRole.Committee, - committeeRole: CommitteeRole.Member, - committeeIdentifier: CommitteeIdentifier.dancerRelationsCommittee, - }); - expect(roleToAuthorization(role)).toStrictEqual({ - dbRole: DbRole.Committee, - accessLevel: AccessLevel.Committee, - committeeRole: CommitteeRole.Member, - committeeIdentifier: CommitteeIdentifier.dancerRelationsCommittee, - }); - }); - - it("throws an error if one of committee or committeeRole is set without the other", ({ - expect, - }) => { - const role = RoleResource.init({ - dbRole: DbRole.Committee, - committeeRole: CommitteeRole.Chair, - }); - expect(() => roleToAuthorization(role)).toThrowError( - "Cannot have a committee role without a committee or vice versa" - ); - }); -}); diff --git a/packages/common/lib/authorization/role.ts b/packages/common/lib/authorization/role.ts index 050329d9..26aaba28 100644 --- a/packages/common/lib/authorization/role.ts +++ b/packages/common/lib/authorization/role.ts @@ -1,6 +1,3 @@ -import type { RoleResource } from "../api/resources/Role.js"; - -import type { Authorization } from "./structures.js"; import { AccessLevel, CommitteeIdentifier, @@ -15,12 +12,14 @@ import { * @return The equivalent AccessLevel * @throws Error if the DbRole is not a valid member of the DbRole enum */ -export function roleToAccessLevel(role: { +export function roleToAccessLevel({ + dbRole, + committees, +}: { dbRole: DbRole; - committeeRole?: CommitteeRole | null; - committeeIdentifier?: CommitteeIdentifier | null; + committees?: { identifier: CommitteeIdentifier; role: CommitteeRole }[]; }): AccessLevel { - switch (role.dbRole) { + switch (dbRole) { case DbRole.None: { return AccessLevel.None; } @@ -31,50 +30,39 @@ export function roleToAccessLevel(role: { return AccessLevel.UKY; } case DbRole.Committee: { - if (role.committeeIdentifier === CommitteeIdentifier.techCommittee) { - return AccessLevel.Admin; - } else if ( - role.committeeRole === CommitteeRole.Coordinator || - role.committeeRole === CommitteeRole.Chair - ) { - return AccessLevel.CommitteeChairOrCoordinator; - } else { - return AccessLevel.Committee; + let maxLevel: AccessLevel | null = null; + for (const committee of committees ?? []) { + let thisLevel: AccessLevel; + + if (committee.identifier === CommitteeIdentifier.techCommittee) { + thisLevel = AccessLevel.Admin; + } else if ( + committee.role === CommitteeRole.Coordinator || + committee.role === CommitteeRole.Chair + ) { + thisLevel = AccessLevel.CommitteeChairOrCoordinator; + } else { + thisLevel = AccessLevel.Committee; + } + + if (maxLevel === null || thisLevel > maxLevel) { + maxLevel = thisLevel; + } } + if (maxLevel === null) { + throw new Error("No committee roles found when DbRole was Committee"); + } + return maxLevel; } default: { + dbRole satisfies never; try { - throw new Error(`Illegal DbRole: ${JSON.stringify(role.dbRole)}`); + throw new Error(`Illegal DbRole: ${JSON.stringify(dbRole)}`); } catch (error) { throw new Error( - `Illegal DbRole: [Parsing of '${String(role.dbRole)}' failed]` + `Illegal DbRole: [Parsing of '${String(dbRole)}' failed]` ); } } } } - -/** - * Convert a RoleResource to an Authorization object - * - * @param role A full RoleResource object - * @return An Authorization object representing the same role - * @throws Error if one of committee or committeeRole is set without the other - */ -export function roleToAuthorization(role: RoleResource): Authorization { - const auth: Authorization = { - dbRole: role.dbRole, - accessLevel: roleToAccessLevel(role), - }; - - if (role.committeeRole && role.committeeIdentifier) { - auth.committeeRole = role.committeeRole; - auth.committeeIdentifier = role.committeeIdentifier; - } else if (role.committeeIdentifier || role.committeeRole) { - throw new Error( - "Cannot have a committee role without a committee or vice versa" - ); - } - - return auth; -} diff --git a/packages/common/lib/authorization/structures.ts b/packages/common/lib/authorization/structures.ts index a248348c..dff5849b 100644 --- a/packages/common/lib/authorization/structures.ts +++ b/packages/common/lib/authorization/structures.ts @@ -1,5 +1,7 @@ import { registerEnumType } from "type-graphql"; +import type { EffectiveCommitteeRole } from "../api/types/EffectiveCommitteeRole.js"; + export const AuthSource = { LinkBlue: "LinkBlue", Anonymous: "Anonymous", @@ -28,7 +30,8 @@ export const AccessLevel = { UKY: 1, Committee: 3, CommitteeChairOrCoordinator: 3.5, - Admin: 4, // Tech committee + Admin: 4, + SuperAdmin: 5, // App & Web Coordinators and Tech chair - master override access } as const; export type AccessLevel = (typeof AccessLevel)[keyof typeof AccessLevel]; @@ -79,6 +82,9 @@ export function stringifyAccessLevel(val: unknown): string { case AccessLevel.Admin: { return "Admin"; } + case AccessLevel.SuperAdmin: { + return "God Emperor of DanceBlue"; + } } } @@ -230,6 +236,7 @@ export const CommitteeIdentifier = { corporateCommittee: "corporateCommittee", miniMarathonsCommittee: "miniMarathonsCommittee", viceCommittee: "viceCommittee", + overallCommittee: "overallCommittee", } as const; export type CommitteeIdentifier = (typeof CommitteeIdentifier)[keyof typeof CommitteeIdentifier]; @@ -257,12 +264,12 @@ export const committeeNames: Record = { corporateCommittee: "Corporate Committee", miniMarathonsCommittee: "Mini Marathons Committee", viceCommittee: "Vice Committee", + overallCommittee: "Overall Committee", }; export interface Authorization { dbRole: DbRole; - committeeRole?: CommitteeRole; - committeeIdentifier?: string; + committees: EffectiveCommitteeRole[]; accessLevel: AccessLevel; } @@ -274,6 +281,7 @@ export interface Authorization { export const defaultAuthorization = { dbRole: DbRole.None, accessLevel: AccessLevel.None, + committees: [], } satisfies Authorization; // Registering the enum types with TypeGraphQL diff --git a/packages/common/lib/client-parsers/event.ts b/packages/common/lib/client-parsers/event.ts index 22e55fe4..577f9f16 100644 --- a/packages/common/lib/client-parsers/event.ts +++ b/packages/common/lib/client-parsers/event.ts @@ -1,8 +1,10 @@ -import { Interval } from "luxon"; +import type { Interval } from "luxon"; + +import { intervalFromSomething } from "../utility/time/intervalTools.js"; interface EventOccurrence { fullDay: boolean; - interval: string; + interval: { readonly start: Date | string; readonly end: Date | string }; } interface ParsedEventOccurrence { @@ -15,7 +17,7 @@ export function parseEventOccurrence( ): ParsedEventOccurrence { return { fullDay: occurrence.fullDay, - interval: Interval.fromISO(occurrence.interval), + interval: intervalFromSomething(occurrence.interval), }; } diff --git a/packages/common/lib/error/composite.ts b/packages/common/lib/error/composite.ts new file mode 100644 index 00000000..26b0e981 --- /dev/null +++ b/packages/common/lib/error/composite.ts @@ -0,0 +1,31 @@ +import { ConcreteError } from "./error.js"; + +const CompositeErrorTag = Symbol("CompositeError"); +type CompositeErrorTag = typeof CompositeErrorTag; +export class CompositeError extends ConcreteError { + readonly errors: E[]; + + constructor(errors: E[]) { + super(); + this.errors = errors; + } + + get message(): string { + return this.errors.map((error) => error.message).join(", "); + } + + get detailedMessage(): string { + return this.errors.map((error) => error.detailedMessage).join(", "); + } + + get expose(): boolean { + return this.errors.every((error) => error.expose); + } + + static get Tag() { + return CompositeErrorTag; + } + get tag(): CompositeErrorTag { + return CompositeErrorTag; + } +} diff --git a/packages/common/lib/error/control.ts b/packages/common/lib/error/control.ts new file mode 100644 index 00000000..d49b8f0e --- /dev/null +++ b/packages/common/lib/error/control.ts @@ -0,0 +1,81 @@ +import type { AuthorizationRule } from "@ukdanceblue/common"; +import { prettyPrintAuthorizationRule } from "@ukdanceblue/common"; + +import { ConcreteError } from "./error.js"; + +/** + * These errors are caused when the server can do something, but doesn't want to. For example + * when a user tries to do something they aren't allowed to do, or when a notification is cancelled + * after it's already been sent. + * + * Exposed by default. + */ +export abstract class ControlError extends ConcreteError { + abstract get message(): string; + get detailedMessage(): string { + return this.message; + } + get expose() { + return true; + } + get stack(): string | undefined { + return undefined; + } +} + +const UnauthorizedErrorTag = Symbol("UnauthorizedError"); +type UnauthorizedErrorTag = typeof UnauthorizedErrorTag; +export class UnauthorizedError extends ControlError { + get message() { + return "Unauthorized"; + } + + constructor(protected readonly requiredAuthorization: AuthorizationRule[]) { + super(); + } + + get detailedMessage() { + return `Unauthorized: ${this.requiredAuthorization.map(prettyPrintAuthorizationRule).join(", ")}`; + } + + static get Tag(): UnauthorizedErrorTag { + return UnauthorizedErrorTag; + } + get tag(): UnauthorizedErrorTag { + return UnauthorizedErrorTag; + } +} + +const UnauthenticatedErrorTag = Symbol("UnauthenticatedError"); +type UnauthenticatedErrorTag = typeof UnauthenticatedErrorTag; +export class UnauthenticatedError extends ControlError { + get message() { + return "Unauthenticated"; + } + + static get Tag(): UnauthenticatedErrorTag { + return UnauthenticatedErrorTag; + } + get tag(): UnauthenticatedErrorTag { + return UnauthenticatedErrorTag; + } +} + +const ActionDeniedErrorTag = Symbol("ActionDeniedError"); +type ActionDeniedErrorTag = typeof ActionDeniedErrorTag; +export class ActionDeniedError extends ControlError { + constructor(protected readonly action: string) { + super(); + } + + get message() { + return `Action denied: ${this.action}`; + } + + static get Tag(): ActionDeniedErrorTag { + return ActionDeniedErrorTag; + } + get tag(): ActionDeniedErrorTag { + return ActionDeniedErrorTag; + } +} diff --git a/packages/common/lib/error/direct.ts b/packages/common/lib/error/direct.ts new file mode 100644 index 00000000..acbd429e --- /dev/null +++ b/packages/common/lib/error/direct.ts @@ -0,0 +1,187 @@ +import type { Option } from "ts-results-es"; + +import { ConcreteError } from "./error.js"; +import { optionOf } from "./option.js"; + +const NotFoundErrorTag = Symbol("NotFoundError"); +type NotFoundErrorTag = typeof NotFoundErrorTag; +export class NotFoundError extends ConcreteError { + readonly #what: Option; + readonly #where: Option; + readonly #why: Option; + readonly #sensitive: boolean; + + constructor({ + what, + where, + why, + sensitive = true, + }: { + what?: string; + where?: string; + why?: string; + sensitive?: boolean; + }) { + super(); + this.#what = optionOf(what); + this.#where = optionOf(where); + this.#why = optionOf(why); + this.#sensitive = sensitive; + } + + get message(): string { + return this.#what.mapOr("Not found", (what) => `Not found: ${what}`); + } + + get detailedMessage(): string { + const what = this.#what.unwrapOr("unknown"); + const where = this.#where.mapOr("", (where) => ` at ${where}`); + const why = this.#why.mapOr("", (why) => ` because ${why}`); + return `Not found: ${what}${where}${why}`; + } + + get expose(): boolean { + return !this.#sensitive; + } + + get stack(): string | undefined { + return undefined; + } + + static get Tag(): NotFoundErrorTag { + return NotFoundErrorTag; + } + get tag(): NotFoundErrorTag { + return NotFoundErrorTag; + } +} + +const TimeoutErrorTag = Symbol("TimeoutError"); +type TimeoutErrorTag = typeof TimeoutErrorTag; +export class TimeoutError extends ConcreteError { + readonly #what: string | null; + + constructor(what?: string) { + super(); + this.#what = what ?? null; + } + + get message(): string { + return `${this.#what ?? "A task"} took too long`; + } + + get expose() { + return false; + } + + static get Tag(): TimeoutErrorTag { + return TimeoutErrorTag; + } + get tag(): TimeoutErrorTag { + return TimeoutErrorTag; + } +} + +const NotImplementedErrorTag = Symbol("NotImplementedError"); +type NotImplementedErrorTag = typeof NotImplementedErrorTag; +export class InvalidOperationError extends ConcreteError { + readonly #what: string; + + constructor(what: string) { + super(); + this.#what = what; + } + + get message(): string { + return `Invalid operation: ${this.#what}`; + } + + get expose() { + return false; + } + + static get Tag(): NotImplementedErrorTag { + return NotImplementedErrorTag; + } + get tag(): NotImplementedErrorTag { + return NotImplementedErrorTag; + } +} + +const InvalidArgumentErrorTag = Symbol("InvalidArgumentError"); +type InvalidArgumentErrorTag = typeof InvalidArgumentErrorTag; +export class InvalidArgumentError extends ConcreteError { + readonly #what: string; + + constructor(what: string) { + super(); + this.#what = what; + } + + get message(): string { + return `Invalid argument: ${this.#what}`; + } + + get expose() { + return false; + } + + static get Tag(): InvalidArgumentErrorTag { + return InvalidArgumentErrorTag; + } + get tag(): InvalidArgumentErrorTag { + return InvalidArgumentErrorTag; + } +} + +const InvalidStateErrorTag = Symbol("InvalidStateError"); +type InvalidStateErrorTag = typeof InvalidStateErrorTag; +export class InvalidStateError extends ConcreteError { + readonly #what: string; + + constructor(what: string) { + super(); + this.#what = what; + } + + get message(): string { + return `Invalid state: ${this.#what}`; + } + + get expose() { + return false; + } + + static get Tag(): InvalidStateErrorTag { + return InvalidStateErrorTag; + } + get tag(): InvalidStateErrorTag { + return InvalidStateErrorTag; + } +} + +const InvariantErrorTag = Symbol("InvariantError"); +type InvariantErrorTag = typeof InvariantErrorTag; +export class InvariantError extends ConcreteError { + readonly #what: string; + + constructor(what: string) { + super(); + this.#what = what; + } + + get message(): string { + return `Invariant violation: ${this.#what}`; + } + + get expose() { + return false; + } + + static get Tag(): InvariantErrorTag { + return InvariantErrorTag; + } + get tag(): InvariantErrorTag { + return InvariantErrorTag; + } +} diff --git a/packages/common/lib/error/error.ts b/packages/common/lib/error/error.ts new file mode 100644 index 00000000..968025ed --- /dev/null +++ b/packages/common/lib/error/error.ts @@ -0,0 +1,77 @@ +export abstract class ConcreteError { + abstract get message(): string; + get detailedMessage(): string { + return this.message; + } + abstract get expose(): boolean; + get stack(): string | undefined { + return undefined; + } + abstract get tag(): unknown; +} + +const JsErrorTag = Symbol("JsError"); +type JsErrorTag = typeof JsErrorTag; +export class JsError extends ConcreteError { + readonly error: Error; + + constructor(error: Error) { + super(); + this.error = error; + } + + get message(): string { + return this.error.message; + } + + get expose(): boolean { + return false; + } + + get stack(): string | undefined { + return this.error.stack; + } + + static get Tag(): JsErrorTag { + return JsErrorTag; + } + + get tag(): JsErrorTag { + return JsErrorTag; + } +} + +const UnknownErrorTag = Symbol("UnknownError"); +type UnknownErrorTag = typeof UnknownErrorTag; +export class UnknownError extends ConcreteError { + readonly #message: string = "Unknown error"; + + // We use a rest parameter here to detect when undefined is passed. If we just allowed an optional parameter, we wouldn't be able to distinguish between `new UnknownError()` and `new UnknownError(undefined)`. + constructor(...message: unknown[]) { + super(); + if (message.length > 0) { + this.#message = String(message[0]); + } + } + + get message(): string { + return this.#message; + } + + get expose(): boolean { + return false; + } + + static get Tag(): UnknownErrorTag { + return UnknownErrorTag; + } + get tag(): UnknownErrorTag { + return UnknownErrorTag; + } +} + +export type BasicError = JsError | UnknownError; + +export function toBasicError(error: unknown): BasicError { + return error instanceof Error ? new JsError(error) : new UnknownError(error); +} diff --git a/packages/common/lib/error/http.ts b/packages/common/lib/error/http.ts new file mode 100644 index 00000000..e8cdd889 --- /dev/null +++ b/packages/common/lib/error/http.ts @@ -0,0 +1,29 @@ +import type { StatusCodes } from "http-status-codes"; +import { getReasonPhrase } from "http-status-codes"; + +import { ConcreteError } from "./error.js"; + +const HttpErrorTag = Symbol("HttpError"); +type HttpErrorTag = typeof HttpErrorTag; +export class HttpError< + Code extends StatusCodes = StatusCodes, +> extends ConcreteError { + constructor(readonly code: Code) { + super(); + } + + get message(): string { + return getReasonPhrase(this.code); + } + + get expose(): boolean { + return true; + } + + static get Tag(): HttpErrorTag { + return HttpErrorTag; + } + get tag(): HttpErrorTag { + return HttpErrorTag; + } +} diff --git a/packages/common/lib/error/index.ts b/packages/common/lib/error/index.ts new file mode 100644 index 00000000..5bceea01 --- /dev/null +++ b/packages/common/lib/error/index.ts @@ -0,0 +1,7 @@ +export * from "./composite.js"; +export * from "./control.js"; +export * from "./direct.js"; +export * from "./error.js"; +export * from "./http.js"; +export * from "./option.js"; +export * from "./result.js"; diff --git a/packages/common/lib/error/option.ts b/packages/common/lib/error/option.ts new file mode 100644 index 00000000..cb66f64e --- /dev/null +++ b/packages/common/lib/error/option.ts @@ -0,0 +1,6 @@ +import type { Option } from "ts-results-es"; +import { None, Some } from "ts-results-es"; + +export function optionOf(value: T | null | undefined): Option { + return value == null ? None : Some(value); +} diff --git a/packages/common/lib/error/result.ts b/packages/common/lib/error/result.ts new file mode 100644 index 00000000..7ca66799 --- /dev/null +++ b/packages/common/lib/error/result.ts @@ -0,0 +1,13 @@ +import type { Result } from "ts-results-es"; + +import type { ConcreteError, JsError, UnknownError } from "./error.js"; + +export type ConcreteResult = Result< + T, + E +>; + +export type JsResult = ConcreteResult< + T, + E | JsError | UnknownError +>; diff --git a/packages/common/lib/graphql-client-admin/fragment-masking.ts b/packages/common/lib/graphql-client-mobile/fragment-masking.ts similarity index 100% rename from packages/common/lib/graphql-client-admin/fragment-masking.ts rename to packages/common/lib/graphql-client-mobile/fragment-masking.ts diff --git a/packages/common/lib/graphql-client-mobile/gql.ts b/packages/common/lib/graphql-client-mobile/gql.ts new file mode 100644 index 00000000..b7710966 --- /dev/null +++ b/packages/common/lib/graphql-client-mobile/gql.ts @@ -0,0 +1,167 @@ +/* eslint-disable */ +import * as types from './graphql.js'; +import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'; + +/** + * Map of all GraphQL operations in the project. + * + * This map has several performance disadvantages: + * 1. It is not tree-shakeable, so it will include all operations in the project. + * 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle. + * 3. It does not support dead code elimination, so it will add unused operations. + * + * Therefore it is highly recommended to use the babel or swc plugin for production. + */ +const documents = { + "\n fragment ImageViewFragment on ImageNode {\n id\n url\n thumbHash\n alt\n width\n height\n mimeType\n }\n": types.ImageViewFragmentFragmentDoc, + "\n fragment SimpleConfig on ConfigurationNode {\n id\n key\n value\n }\n": types.SimpleConfigFragmentDoc, + "\n fragment FullConfig on ConfigurationNode {\n ...SimpleConfig\n validAfter\n validUntil\n createdAt\n }\n": types.FullConfigFragmentDoc, + "\n fragment NotificationFragment on NotificationNode {\n id\n title\n body\n url\n }\n": types.NotificationFragmentFragmentDoc, + "\n fragment NotificationDeliveryFragment on NotificationDeliveryNode {\n id\n sentAt\n notification {\n ...NotificationFragment\n }\n }\n": types.NotificationDeliveryFragmentFragmentDoc, + "\n query useAllowedLoginTypes {\n activeConfiguration(key: \"ALLOWED_LOGIN_TYPES\") {\n data {\n ...SimpleConfig\n }\n }\n }\n": types.UseAllowedLoginTypesDocument, + "\n query MarathonTime {\n latestMarathon {\n startDate\n endDate\n }\n }\n": types.MarathonTimeDocument, + "\n query useTabBarConfig {\n activeConfiguration(key: \"TAB_BAR_CONFIG\") {\n data {\n ...SimpleConfig\n }\n }\n me {\n linkblue\n }\n }\n": types.UseTabBarConfigDocument, + "\n query TriviaCrack {\n activeConfiguration(key: \"TRIVIA_CRACK\") {\n data {\n ...SimpleConfig\n }\n }\n\n me {\n teams {\n team {\n type\n name\n }\n }\n }\n }\n ": types.TriviaCrackDocument, + "\n query AuthState {\n me {\n id\n }\n loginState {\n dbRole\n loggedIn\n authSource\n }\n }\n": types.AuthStateDocument, + "\n mutation SetDevice($input: RegisterDeviceInput!) {\n registerDevice(input: $input) {\n ok\n }\n }\n": types.SetDeviceDocument, + "\n fragment EventScreenFragment on EventNode {\n id\n title\n summary\n description\n location\n occurrences {\n id\n interval {\n start\n end\n }\n fullDay\n }\n images {\n thumbHash\n url\n height\n width\n alt\n mimeType\n }\n }\n": types.EventScreenFragmentFragmentDoc, + "\n query DeviceNotifications(\n $deviceUuid: GlobalId!\n $page: Int\n $pageSize: Int\n $verifier: String!\n ) {\n device(uuid: $deviceUuid) {\n data {\n notificationDeliveries(\n pageSize: $pageSize\n page: $page\n verifier: $verifier\n ) {\n ...NotificationDeliveryFragment\n }\n }\n }\n }\n": types.DeviceNotificationsDocument, + "\n fragment ProfileScreenAuthFragment on LoginState {\n dbRole\n authSource\n }\n": types.ProfileScreenAuthFragmentFragmentDoc, + "\n fragment ProfileScreenUserFragment on PersonNode {\n name\n linkblue\n teams {\n position\n team {\n name\n }\n }\n primaryCommittee {\n identifier\n role\n }\n }\n": types.ProfileScreenUserFragmentFragmentDoc, + "\n query RootScreenDocument {\n loginState {\n ...ProfileScreenAuthFragment\n ...RootScreenAuthFragment\n }\n me {\n ...ProfileScreenUserFragment\n }\n }\n": types.RootScreenDocumentDocument, + "\n fragment RootScreenAuthFragment on LoginState {\n dbRole\n }\n": types.RootScreenAuthFragmentFragmentDoc, + "\n query Events(\n $earliestTimestamp: DateTimeISO!\n $lastTimestamp: DateTimeISO!\n ) {\n events(\n dateFilters: [\n {\n comparison: GREATER_THAN_OR_EQUAL_TO\n field: occurrenceStart\n value: $earliestTimestamp\n }\n {\n comparison: LESS_THAN_OR_EQUAL_TO\n field: occurrenceStart\n value: $lastTimestamp\n }\n ]\n sortDirection: asc\n sortBy: \"occurrence\"\n ) {\n data {\n ...EventScreenFragment\n }\n }\n }\n ": types.EventsDocument, + "\n query ServerFeed {\n feed(limit: 20) {\n id\n title\n createdAt\n textContent\n image {\n url\n alt\n width\n height\n thumbHash\n }\n }\n }\n": types.ServerFeedDocument, + "\n fragment HourScreenFragment on MarathonHourNode {\n id\n title\n details\n durationInfo\n mapImages {\n ...ImageViewFragment\n }\n }\n": types.HourScreenFragmentFragmentDoc, + "\n query MarathonScreen {\n currentMarathonHour {\n ...HourScreenFragment\n }\n latestMarathon {\n startDate\n endDate\n hours {\n ...HourScreenFragment\n }\n }\n }\n": types.MarathonScreenDocument, + "\n fragment ScoreBoardFragment on TeamNode {\n id\n name\n totalPoints\n legacyStatus\n type\n }\n": types.ScoreBoardFragmentFragmentDoc, + "\n fragment HighlightedTeamFragment on TeamNode {\n id\n name\n legacyStatus\n type\n }\n": types.HighlightedTeamFragmentFragmentDoc, + "\n query ScoreBoardDocument($type: [TeamType!]) {\n me {\n id\n teams {\n team {\n ...HighlightedTeamFragment\n ...MyTeamFragment\n }\n }\n }\n teams(\n sendAll: true\n sortBy: [\"totalPoints\", \"name\"]\n sortDirection: [desc, asc]\n type: $type\n ) {\n data {\n ...ScoreBoardFragment\n }\n }\n }\n": types.ScoreBoardDocumentDocument, + "\n query ActiveMarathonDocument {\n currentMarathon {\n id\n }\n }\n": types.ActiveMarathonDocumentDocument, + "\n fragment MyTeamFragment on TeamNode {\n id\n name\n totalPoints\n pointEntries {\n personFrom {\n id\n name\n linkblue\n }\n points\n }\n members {\n position\n person {\n linkblue\n name\n }\n }\n }\n": types.MyTeamFragmentFragmentDoc, +}; + +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + * + * + * @example + * ```ts + * const query = graphql(`query GetUser($id: ID!) { user(id: $id) { name } }`); + * ``` + * + * The query argument is unknown! + * Please regenerate the types. + */ +export function graphql(source: string): unknown; + +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment ImageViewFragment on ImageNode {\n id\n url\n thumbHash\n alt\n width\n height\n mimeType\n }\n"): (typeof documents)["\n fragment ImageViewFragment on ImageNode {\n id\n url\n thumbHash\n alt\n width\n height\n mimeType\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment SimpleConfig on ConfigurationNode {\n id\n key\n value\n }\n"): (typeof documents)["\n fragment SimpleConfig on ConfigurationNode {\n id\n key\n value\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment FullConfig on ConfigurationNode {\n ...SimpleConfig\n validAfter\n validUntil\n createdAt\n }\n"): (typeof documents)["\n fragment FullConfig on ConfigurationNode {\n ...SimpleConfig\n validAfter\n validUntil\n createdAt\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment NotificationFragment on NotificationNode {\n id\n title\n body\n url\n }\n"): (typeof documents)["\n fragment NotificationFragment on NotificationNode {\n id\n title\n body\n url\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment NotificationDeliveryFragment on NotificationDeliveryNode {\n id\n sentAt\n notification {\n ...NotificationFragment\n }\n }\n"): (typeof documents)["\n fragment NotificationDeliveryFragment on NotificationDeliveryNode {\n id\n sentAt\n notification {\n ...NotificationFragment\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query useAllowedLoginTypes {\n activeConfiguration(key: \"ALLOWED_LOGIN_TYPES\") {\n data {\n ...SimpleConfig\n }\n }\n }\n"): (typeof documents)["\n query useAllowedLoginTypes {\n activeConfiguration(key: \"ALLOWED_LOGIN_TYPES\") {\n data {\n ...SimpleConfig\n }\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query MarathonTime {\n latestMarathon {\n startDate\n endDate\n }\n }\n"): (typeof documents)["\n query MarathonTime {\n latestMarathon {\n startDate\n endDate\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query useTabBarConfig {\n activeConfiguration(key: \"TAB_BAR_CONFIG\") {\n data {\n ...SimpleConfig\n }\n }\n me {\n linkblue\n }\n }\n"): (typeof documents)["\n query useTabBarConfig {\n activeConfiguration(key: \"TAB_BAR_CONFIG\") {\n data {\n ...SimpleConfig\n }\n }\n me {\n linkblue\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query TriviaCrack {\n activeConfiguration(key: \"TRIVIA_CRACK\") {\n data {\n ...SimpleConfig\n }\n }\n\n me {\n teams {\n team {\n type\n name\n }\n }\n }\n }\n "): (typeof documents)["\n query TriviaCrack {\n activeConfiguration(key: \"TRIVIA_CRACK\") {\n data {\n ...SimpleConfig\n }\n }\n\n me {\n teams {\n team {\n type\n name\n }\n }\n }\n }\n "]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query AuthState {\n me {\n id\n }\n loginState {\n dbRole\n loggedIn\n authSource\n }\n }\n"): (typeof documents)["\n query AuthState {\n me {\n id\n }\n loginState {\n dbRole\n loggedIn\n authSource\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n mutation SetDevice($input: RegisterDeviceInput!) {\n registerDevice(input: $input) {\n ok\n }\n }\n"): (typeof documents)["\n mutation SetDevice($input: RegisterDeviceInput!) {\n registerDevice(input: $input) {\n ok\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment EventScreenFragment on EventNode {\n id\n title\n summary\n description\n location\n occurrences {\n id\n interval {\n start\n end\n }\n fullDay\n }\n images {\n thumbHash\n url\n height\n width\n alt\n mimeType\n }\n }\n"): (typeof documents)["\n fragment EventScreenFragment on EventNode {\n id\n title\n summary\n description\n location\n occurrences {\n id\n interval {\n start\n end\n }\n fullDay\n }\n images {\n thumbHash\n url\n height\n width\n alt\n mimeType\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query DeviceNotifications(\n $deviceUuid: GlobalId!\n $page: Int\n $pageSize: Int\n $verifier: String!\n ) {\n device(uuid: $deviceUuid) {\n data {\n notificationDeliveries(\n pageSize: $pageSize\n page: $page\n verifier: $verifier\n ) {\n ...NotificationDeliveryFragment\n }\n }\n }\n }\n"): (typeof documents)["\n query DeviceNotifications(\n $deviceUuid: GlobalId!\n $page: Int\n $pageSize: Int\n $verifier: String!\n ) {\n device(uuid: $deviceUuid) {\n data {\n notificationDeliveries(\n pageSize: $pageSize\n page: $page\n verifier: $verifier\n ) {\n ...NotificationDeliveryFragment\n }\n }\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment ProfileScreenAuthFragment on LoginState {\n dbRole\n authSource\n }\n"): (typeof documents)["\n fragment ProfileScreenAuthFragment on LoginState {\n dbRole\n authSource\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment ProfileScreenUserFragment on PersonNode {\n name\n linkblue\n teams {\n position\n team {\n name\n }\n }\n primaryCommittee {\n identifier\n role\n }\n }\n"): (typeof documents)["\n fragment ProfileScreenUserFragment on PersonNode {\n name\n linkblue\n teams {\n position\n team {\n name\n }\n }\n primaryCommittee {\n identifier\n role\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query RootScreenDocument {\n loginState {\n ...ProfileScreenAuthFragment\n ...RootScreenAuthFragment\n }\n me {\n ...ProfileScreenUserFragment\n }\n }\n"): (typeof documents)["\n query RootScreenDocument {\n loginState {\n ...ProfileScreenAuthFragment\n ...RootScreenAuthFragment\n }\n me {\n ...ProfileScreenUserFragment\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment RootScreenAuthFragment on LoginState {\n dbRole\n }\n"): (typeof documents)["\n fragment RootScreenAuthFragment on LoginState {\n dbRole\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query Events(\n $earliestTimestamp: DateTimeISO!\n $lastTimestamp: DateTimeISO!\n ) {\n events(\n dateFilters: [\n {\n comparison: GREATER_THAN_OR_EQUAL_TO\n field: occurrenceStart\n value: $earliestTimestamp\n }\n {\n comparison: LESS_THAN_OR_EQUAL_TO\n field: occurrenceStart\n value: $lastTimestamp\n }\n ]\n sortDirection: asc\n sortBy: \"occurrence\"\n ) {\n data {\n ...EventScreenFragment\n }\n }\n }\n "): (typeof documents)["\n query Events(\n $earliestTimestamp: DateTimeISO!\n $lastTimestamp: DateTimeISO!\n ) {\n events(\n dateFilters: [\n {\n comparison: GREATER_THAN_OR_EQUAL_TO\n field: occurrenceStart\n value: $earliestTimestamp\n }\n {\n comparison: LESS_THAN_OR_EQUAL_TO\n field: occurrenceStart\n value: $lastTimestamp\n }\n ]\n sortDirection: asc\n sortBy: \"occurrence\"\n ) {\n data {\n ...EventScreenFragment\n }\n }\n }\n "]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query ServerFeed {\n feed(limit: 20) {\n id\n title\n createdAt\n textContent\n image {\n url\n alt\n width\n height\n thumbHash\n }\n }\n }\n"): (typeof documents)["\n query ServerFeed {\n feed(limit: 20) {\n id\n title\n createdAt\n textContent\n image {\n url\n alt\n width\n height\n thumbHash\n }\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment HourScreenFragment on MarathonHourNode {\n id\n title\n details\n durationInfo\n mapImages {\n ...ImageViewFragment\n }\n }\n"): (typeof documents)["\n fragment HourScreenFragment on MarathonHourNode {\n id\n title\n details\n durationInfo\n mapImages {\n ...ImageViewFragment\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query MarathonScreen {\n currentMarathonHour {\n ...HourScreenFragment\n }\n latestMarathon {\n startDate\n endDate\n hours {\n ...HourScreenFragment\n }\n }\n }\n"): (typeof documents)["\n query MarathonScreen {\n currentMarathonHour {\n ...HourScreenFragment\n }\n latestMarathon {\n startDate\n endDate\n hours {\n ...HourScreenFragment\n }\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment ScoreBoardFragment on TeamNode {\n id\n name\n totalPoints\n legacyStatus\n type\n }\n"): (typeof documents)["\n fragment ScoreBoardFragment on TeamNode {\n id\n name\n totalPoints\n legacyStatus\n type\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment HighlightedTeamFragment on TeamNode {\n id\n name\n legacyStatus\n type\n }\n"): (typeof documents)["\n fragment HighlightedTeamFragment on TeamNode {\n id\n name\n legacyStatus\n type\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query ScoreBoardDocument($type: [TeamType!]) {\n me {\n id\n teams {\n team {\n ...HighlightedTeamFragment\n ...MyTeamFragment\n }\n }\n }\n teams(\n sendAll: true\n sortBy: [\"totalPoints\", \"name\"]\n sortDirection: [desc, asc]\n type: $type\n ) {\n data {\n ...ScoreBoardFragment\n }\n }\n }\n"): (typeof documents)["\n query ScoreBoardDocument($type: [TeamType!]) {\n me {\n id\n teams {\n team {\n ...HighlightedTeamFragment\n ...MyTeamFragment\n }\n }\n }\n teams(\n sendAll: true\n sortBy: [\"totalPoints\", \"name\"]\n sortDirection: [desc, asc]\n type: $type\n ) {\n data {\n ...ScoreBoardFragment\n }\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query ActiveMarathonDocument {\n currentMarathon {\n id\n }\n }\n"): (typeof documents)["\n query ActiveMarathonDocument {\n currentMarathon {\n id\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment MyTeamFragment on TeamNode {\n id\n name\n totalPoints\n pointEntries {\n personFrom {\n id\n name\n linkblue\n }\n points\n }\n members {\n position\n person {\n linkblue\n name\n }\n }\n }\n"): (typeof documents)["\n fragment MyTeamFragment on TeamNode {\n id\n name\n totalPoints\n pointEntries {\n personFrom {\n id\n name\n linkblue\n }\n points\n }\n members {\n position\n person {\n linkblue\n name\n }\n }\n }\n"]; + +export function graphql(source: string) { + return (documents as any)[source] ?? {}; +} + +export type DocumentType> = TDocumentNode extends DocumentNode< infer TType, any> ? TType : never; \ No newline at end of file diff --git a/packages/common/lib/graphql-client-public/graphql.ts b/packages/common/lib/graphql-client-mobile/graphql.ts similarity index 61% rename from packages/common/lib/graphql-client-public/graphql.ts rename to packages/common/lib/graphql-client-mobile/graphql.ts index 02f60264..87aa9b9d 100644 --- a/packages/common/lib/graphql-client-public/graphql.ts +++ b/packages/common/lib/graphql-client-mobile/graphql.ts @@ -1,8 +1,6 @@ /* eslint-disable */ import type { AuthSource } from '../index.js'; import type { DbRole } from '../index.js'; -import type { CommitteeRole } from '../index.js'; -import type { CommitteeIdentifier } from '../index.js'; import type { MembershipPositionType } from '../index.js'; import type { TeamLegacyStatus } from '../index.js'; import type { TeamType } from '../index.js'; @@ -28,10 +26,8 @@ export type Scalars = { DateTimeISO: { input: Date | string; output: Date | string; } /** A field whose value conforms to the standard internet email address format as specified in HTML Spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address. */ EmailAddress: { input: string; output: string; } - /** Date range custom scalar type (just an ISO 8601 interval) */ - LuxonDateRange: { input: string; output: string; } - /** Luxon DateTime custom scalar type */ - LuxonDateTime: { input: string; output: string; } + /** GlobalId custom scalar type */ + GlobalId: { input: string; output: string; } /** Integers that will have a value of 0 or more. */ NonNegativeInt: { input: number; output: number; } /** Integers that will have a value greater than 0. */ @@ -83,43 +79,82 @@ export type AcknowledgeDeliveryIssueResponse = AbstractGraphQlOkResponse & Graph export type AddEventImageResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'AddEventImageResponse'; - readonly data: ImageResource; + readonly data: ImageNode; readonly ok: Scalars['Boolean']['output']; }; -export type AuthIdPairResource = { - readonly __typename?: 'AuthIdPairResource'; - readonly source: AuthSource; - readonly value: Scalars['String']['output']; +export type AssignEntryToPersonInput = { + readonly amount: Scalars['Float']['input']; }; export { AuthSource }; -export { CommitteeIdentifier }; +/** The identifier for a committee */ +export const CommitteeIdentifier = { + CommunityDevelopmentCommittee: 'communityDevelopmentCommittee', + CorporateCommittee: 'corporateCommittee', + DancerRelationsCommittee: 'dancerRelationsCommittee', + FamilyRelationsCommittee: 'familyRelationsCommittee', + FundraisingCommittee: 'fundraisingCommittee', + MarketingCommittee: 'marketingCommittee', + MiniMarathonsCommittee: 'miniMarathonsCommittee', + OperationsCommittee: 'operationsCommittee', + OverallCommittee: 'overallCommittee', + ProgrammingCommittee: 'programmingCommittee', + TechCommittee: 'techCommittee', + ViceCommittee: 'viceCommittee' +} as const; -export { CommitteeRole }; +export type CommitteeIdentifier = typeof CommitteeIdentifier[keyof typeof CommitteeIdentifier]; +export type CommitteeMembershipNode = Node & { + readonly __typename?: 'CommitteeMembershipNode'; + readonly createdAt?: Maybe; + readonly id: Scalars['GlobalId']['output']; + readonly identifier: CommitteeIdentifier; + readonly person: PersonNode; + readonly position: MembershipPositionType; + readonly role: CommitteeRole; + readonly team: TeamNode; + readonly updatedAt?: Maybe; +}; -export type ConfigurationResource = { - readonly __typename?: 'ConfigurationResource'; +export type CommitteeNode = Node & { + readonly __typename?: 'CommitteeNode'; readonly createdAt?: Maybe; + readonly id: Scalars['GlobalId']['output']; + readonly identifier: CommitteeIdentifier; + readonly updatedAt?: Maybe; +}; + +/** Roles within a committee */ +export const CommitteeRole = { + Chair: 'Chair', + Coordinator: 'Coordinator', + Member: 'Member' +} as const; + +export type CommitteeRole = typeof CommitteeRole[keyof typeof CommitteeRole]; +export type ConfigurationNode = Node & { + readonly __typename?: 'ConfigurationNode'; + readonly createdAt?: Maybe; + readonly id: Scalars['GlobalId']['output']; readonly key: Scalars['String']['output']; readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; - readonly validAfter?: Maybe; - readonly validUntil?: Maybe; + readonly validAfter?: Maybe; + readonly validUntil?: Maybe; readonly value: Scalars['String']['output']; }; export type CreateConfigurationInput = { readonly key: Scalars['String']['input']; - readonly validAfter?: InputMaybe; - readonly validUntil?: InputMaybe; + readonly validAfter?: InputMaybe; + readonly validUntil?: InputMaybe; readonly value: Scalars['String']['input']; }; export type CreateConfigurationResponse = AbstractGraphQlCreatedResponse & AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'CreateConfigurationResponse'; - readonly data: ConfigurationResource; + readonly data: ConfigurationNode; readonly ok: Scalars['Boolean']['output']; readonly uuid: Scalars['String']['output']; }; @@ -134,12 +169,12 @@ export type CreateEventInput = { export type CreateEventOccurrenceInput = { readonly fullDay: Scalars['Boolean']['input']; - readonly interval: Scalars['LuxonDateRange']['input']; + readonly interval: IntervalIsoInput; }; export type CreateEventResponse = AbstractGraphQlCreatedResponse & AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'CreateEventResponse'; - readonly data: EventResource; + readonly data: EventNode; readonly ok: Scalars['Boolean']['output']; readonly uuid: Scalars['String']['output']; }; @@ -170,18 +205,11 @@ export type CreateMarathonInput = { export type CreatePersonInput = { readonly captainOf?: ReadonlyArray; + readonly dbRole?: InputMaybe; readonly email: Scalars['EmailAddress']['input']; readonly linkblue?: InputMaybe; readonly memberOf?: ReadonlyArray; readonly name?: InputMaybe; - readonly role?: InputMaybe; -}; - -export type CreatePersonResponse = AbstractGraphQlCreatedResponse & AbstractGraphQlOkResponse & GraphQlBaseResponse & { - readonly __typename?: 'CreatePersonResponse'; - readonly data: PersonResource; - readonly ok: Scalars['Boolean']['output']; - readonly uuid: Scalars['String']['output']; }; export type CreatePointEntryInput = { @@ -194,7 +222,7 @@ export type CreatePointEntryInput = { export type CreatePointEntryResponse = AbstractGraphQlCreatedResponse & AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'CreatePointEntryResponse'; - readonly data: PointEntryResource; + readonly data: PointEntryNode; readonly ok: Scalars['Boolean']['output']; readonly uuid: Scalars['String']['output']; }; @@ -202,32 +230,37 @@ export type CreatePointEntryResponse = AbstractGraphQlCreatedResponse & Abstract export type CreatePointOpportunityInput = { readonly eventUuid?: InputMaybe; readonly name: Scalars['String']['input']; - readonly opportunityDate?: InputMaybe; + readonly opportunityDate?: InputMaybe; readonly type: TeamType; }; export type CreatePointOpportunityResponse = AbstractGraphQlCreatedResponse & AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'CreatePointOpportunityResponse'; - readonly data: PointOpportunityResource; + readonly data: PointOpportunityNode; readonly ok: Scalars['Boolean']['output']; readonly uuid: Scalars['String']['output']; }; export type CreateTeamInput = { readonly legacyStatus: TeamLegacyStatus; - readonly marathonYear: Scalars['String']['input']; readonly name: Scalars['String']['input']; - readonly persistentIdentifier?: InputMaybe; readonly type: TeamType; }; export type CreateTeamResponse = AbstractGraphQlCreatedResponse & AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'CreateTeamResponse'; - readonly data: TeamResource; + readonly data: TeamNode; readonly ok: Scalars['Boolean']['output']; readonly uuid: Scalars['String']['output']; }; +export type DbFundsTeamInfo = Node & { + readonly __typename?: 'DbFundsTeamInfo'; + readonly dbNum: Scalars['Int']['output']; + readonly id: Scalars['GlobalId']['output']; + readonly name: Scalars['String']['output']; +}; + export { DbRole }; export type DeleteConfigurationResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { @@ -256,11 +289,6 @@ export type DeleteNotificationResponse = AbstractGraphQlOkResponse & GraphQlBase readonly ok: Scalars['Boolean']['output']; }; -export type DeletePersonResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { - readonly __typename?: 'DeletePersonResponse'; - readonly ok: Scalars['Boolean']['output']; -}; - export type DeletePointEntryResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'DeletePointEntryResponse'; readonly ok: Scalars['Boolean']['output']; @@ -276,6 +304,23 @@ export type DeleteTeamResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse readonly ok: Scalars['Boolean']['output']; }; +export type DeviceNode = Node & { + readonly __typename?: 'DeviceNode'; + readonly createdAt?: Maybe; + readonly id: Scalars['GlobalId']['output']; + readonly lastLoggedInUser?: Maybe; + readonly lastLogin?: Maybe; + readonly notificationDeliveries: ReadonlyArray; + readonly updatedAt?: Maybe; +}; + + +export type DeviceNodeNotificationDeliveriesArgs = { + page?: InputMaybe; + pageSize?: InputMaybe; + verifier?: InputMaybe; +}; + export const DeviceResolverAllKeys = { CreatedAt: 'createdAt', ExpoPushToken: 'expoPushToken', @@ -298,7 +343,7 @@ export type DeviceResolverKeyedDateFilterItem = { readonly field: DeviceResolverDateFilterKeys; /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ readonly negate?: InputMaybe; - readonly value: Scalars['LuxonDateTime']['input']; + readonly value: Scalars['DateTimeISO']['input']; }; export type DeviceResolverKeyedIsNullFilterItem = { @@ -331,29 +376,30 @@ export const DeviceResolverStringFilterKeys = { } as const; export type DeviceResolverStringFilterKeys = typeof DeviceResolverStringFilterKeys[keyof typeof DeviceResolverStringFilterKeys]; -export type DeviceResource = { - readonly __typename?: 'DeviceResource'; - readonly createdAt?: Maybe; - readonly expoPushToken?: Maybe; - readonly lastLoggedInUser?: Maybe; - readonly lastLogin?: Maybe; - readonly notificationDeliveries: ReadonlyArray; - readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; +export type EffectiveCommitteeRole = { + readonly __typename?: 'EffectiveCommitteeRole'; + readonly identifier: CommitteeIdentifier; + readonly role: CommitteeRole; }; - -export type DeviceResourceNotificationDeliveriesArgs = { - page?: InputMaybe; - pageSize?: InputMaybe; - verifier?: InputMaybe; +export type EventNode = Node & { + readonly __typename?: 'EventNode'; + readonly createdAt?: Maybe; + readonly description?: Maybe; + readonly id: Scalars['GlobalId']['output']; + readonly images: ReadonlyArray; + readonly location?: Maybe; + readonly occurrences: ReadonlyArray; + readonly summary?: Maybe; + readonly title: Scalars['String']['output']; + readonly updatedAt?: Maybe; }; -export type EventOccurrenceResource = { - readonly __typename?: 'EventOccurrenceResource'; +export type EventOccurrenceNode = { + readonly __typename?: 'EventOccurrenceNode'; readonly fullDay: Scalars['Boolean']['output']; - readonly interval: Scalars['LuxonDateRange']['output']; - readonly uuid: Scalars['ID']['output']; + readonly id: Scalars['ID']['output']; + readonly interval: IntervalIso; }; export const EventResolverAllKeys = { @@ -385,7 +431,7 @@ export type EventResolverKeyedDateFilterItem = { readonly field: EventResolverDateFilterKeys; /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ readonly negate?: InputMaybe; - readonly value: Scalars['LuxonDateTime']['input']; + readonly value: Scalars['DateTimeISO']['input']; }; export type EventResolverKeyedIsNullFilterItem = { @@ -421,80 +467,156 @@ export const EventResolverStringFilterKeys = { } as const; export type EventResolverStringFilterKeys = typeof EventResolverStringFilterKeys[keyof typeof EventResolverStringFilterKeys]; -export type EventResource = { - readonly __typename?: 'EventResource'; +export type FeedNode = Node & { + readonly __typename?: 'FeedNode'; readonly createdAt?: Maybe; - readonly description?: Maybe; - readonly images: ReadonlyArray; - readonly location?: Maybe; - readonly occurrences: ReadonlyArray; - readonly summary?: Maybe; + readonly id: Scalars['GlobalId']['output']; + readonly image?: Maybe; + readonly textContent?: Maybe; readonly title: Scalars['String']['output']; readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; }; -export type FeedResource = { - readonly __typename?: 'FeedResource'; +export type FundraisingAssignmentNode = Node & { + readonly __typename?: 'FundraisingAssignmentNode'; + readonly amount: Scalars['Float']['output']; readonly createdAt?: Maybe; - readonly image?: Maybe; - readonly textContent?: Maybe; - readonly title: Scalars['String']['output']; + readonly entry: FundraisingEntryNode; + readonly id: Scalars['GlobalId']['output']; + /** The person assigned to this assignment, only null when access is denied */ + readonly person?: Maybe; readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; }; +export type FundraisingEntryNode = Node & { + readonly __typename?: 'FundraisingEntryNode'; + readonly amount: Scalars['Float']['output']; + readonly assignments: ReadonlyArray; + readonly createdAt?: Maybe; + readonly donatedByText?: Maybe; + readonly donatedOn: Scalars['DateTimeISO']['output']; + readonly donatedToText?: Maybe; + readonly id: Scalars['GlobalId']['output']; + readonly updatedAt?: Maybe; +}; + +export const FundraisingEntryResolverAllKeys = { + Amount: 'amount', + CreatedAt: 'createdAt', + DonatedBy: 'donatedBy', + DonatedOn: 'donatedOn', + DonatedTo: 'donatedTo', + UpdatedAt: 'updatedAt' +} as const; + +export type FundraisingEntryResolverAllKeys = typeof FundraisingEntryResolverAllKeys[keyof typeof FundraisingEntryResolverAllKeys]; +export const FundraisingEntryResolverDateFilterKeys = { + CreatedAt: 'createdAt', + DonatedOn: 'donatedOn', + UpdatedAt: 'updatedAt' +} as const; + +export type FundraisingEntryResolverDateFilterKeys = typeof FundraisingEntryResolverDateFilterKeys[keyof typeof FundraisingEntryResolverDateFilterKeys]; +export type FundraisingEntryResolverKeyedDateFilterItem = { + /** The comparator to use for the filter */ + readonly comparison: NumericComparator; + /** The field to filter on */ + readonly field: FundraisingEntryResolverDateFilterKeys; + /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ + readonly negate?: InputMaybe; + readonly value: Scalars['DateTimeISO']['input']; +}; + +export type FundraisingEntryResolverKeyedIsNullFilterItem = { + /** The field to filter on */ + readonly field: FundraisingEntryResolverAllKeys; + /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ + readonly negate?: InputMaybe; +}; + +export type FundraisingEntryResolverKeyedNumericFilterItem = { + /** The comparator to use for the filter */ + readonly comparison: NumericComparator; + /** The field to filter on */ + readonly field: FundraisingEntryResolverNumericFilterKeys; + /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ + readonly negate?: InputMaybe; + readonly value: Scalars['Float']['input']; +}; + +export type FundraisingEntryResolverKeyedOneOfFilterItem = { + /** The field to filter on */ + readonly field: FundraisingEntryResolverOneOfFilterKeys; + /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ + readonly negate?: InputMaybe; + readonly value: ReadonlyArray; +}; + +export type FundraisingEntryResolverKeyedStringFilterItem = { + /** The comparator to use for the filter */ + readonly comparison: StringComparator; + /** The field to filter on */ + readonly field: FundraisingEntryResolverStringFilterKeys; + /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ + readonly negate?: InputMaybe; + readonly value: Scalars['String']['input']; +}; + +export const FundraisingEntryResolverNumericFilterKeys = { + Amount: 'amount' +} as const; + +export type FundraisingEntryResolverNumericFilterKeys = typeof FundraisingEntryResolverNumericFilterKeys[keyof typeof FundraisingEntryResolverNumericFilterKeys]; +export const FundraisingEntryResolverOneOfFilterKeys = { + TeamId: 'teamId' +} as const; + +export type FundraisingEntryResolverOneOfFilterKeys = typeof FundraisingEntryResolverOneOfFilterKeys[keyof typeof FundraisingEntryResolverOneOfFilterKeys]; +export const FundraisingEntryResolverStringFilterKeys = { + DonatedBy: 'donatedBy', + DonatedTo: 'donatedTo' +} as const; + +export type FundraisingEntryResolverStringFilterKeys = typeof FundraisingEntryResolverStringFilterKeys[keyof typeof FundraisingEntryResolverStringFilterKeys]; export type GetAllConfigurationsResponse = AbstractGraphQlArrayOkResponse & GraphQlBaseResponse & { readonly __typename?: 'GetAllConfigurationsResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; }; export type GetConfigurationByUuidResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'GetConfigurationByUuidResponse'; - readonly data: ConfigurationResource; + readonly data: ConfigurationNode; readonly ok: Scalars['Boolean']['output']; }; export type GetDeviceByUuidResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'GetDeviceByUuidResponse'; - readonly data: DeviceResource; + readonly data: DeviceNode; readonly ok: Scalars['Boolean']['output']; }; export type GetEventByUuidResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'GetEventByUuidResponse'; - readonly data: EventResource; + readonly data: EventNode; readonly ok: Scalars['Boolean']['output']; }; export type GetImageByUuidResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'GetImageByUuidResponse'; - readonly data: ImageResource; + readonly data: ImageNode; readonly ok: Scalars['Boolean']['output']; }; export type GetNotificationByUuidResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'GetNotificationByUuidResponse'; - readonly data: NotificationResource; - readonly ok: Scalars['Boolean']['output']; -}; - -export type GetPeopleResponse = AbstractGraphQlArrayOkResponse & GraphQlBaseResponse & { - readonly __typename?: 'GetPeopleResponse'; - readonly data: ReadonlyArray; - readonly ok: Scalars['Boolean']['output']; -}; - -export type GetPersonResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { - readonly __typename?: 'GetPersonResponse'; - readonly data?: Maybe; + readonly data: NotificationNode; readonly ok: Scalars['Boolean']['output']; }; export type GetPointEntryByUuidResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'GetPointEntryByUuidResponse'; - readonly data: PointEntryResource; + readonly data: PointEntryNode; readonly ok: Scalars['Boolean']['output']; }; @@ -503,6 +625,19 @@ export type GraphQlBaseResponse = { readonly ok: Scalars['Boolean']['output']; }; +export type ImageNode = Node & { + readonly __typename?: 'ImageNode'; + readonly alt?: Maybe; + readonly createdAt?: Maybe; + readonly height: Scalars['Int']['output']; + readonly id: Scalars['GlobalId']['output']; + readonly mimeType: Scalars['String']['output']; + readonly thumbHash?: Maybe; + readonly updatedAt?: Maybe; + readonly url?: Maybe; + readonly width: Scalars['Int']['output']; +}; + export const ImageResolverAllKeys = { Alt: 'alt', CreatedAt: 'createdAt', @@ -525,7 +660,7 @@ export type ImageResolverKeyedDateFilterItem = { readonly field: ImageResolverDateFilterKeys; /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ readonly negate?: InputMaybe; - readonly value: Scalars['LuxonDateTime']['input']; + readonly value: Scalars['DateTimeISO']['input']; }; export type ImageResolverKeyedIsNullFilterItem = { @@ -574,22 +709,20 @@ export const ImageResolverStringFilterKeys = { } as const; export type ImageResolverStringFilterKeys = typeof ImageResolverStringFilterKeys[keyof typeof ImageResolverStringFilterKeys]; -export type ImageResource = { - readonly __typename?: 'ImageResource'; - readonly alt?: Maybe; - readonly createdAt?: Maybe; - readonly height: Scalars['Int']['output']; - readonly mimeType: Scalars['String']['output']; - readonly thumbHash?: Maybe; - readonly updatedAt?: Maybe; - readonly url?: Maybe; - readonly uuid: Scalars['ID']['output']; - readonly width: Scalars['Int']['output']; +export type IntervalIso = { + readonly __typename?: 'IntervalISO'; + readonly end: Scalars['DateTimeISO']['output']; + readonly start: Scalars['DateTimeISO']['output']; +}; + +export type IntervalIsoInput = { + readonly end: Scalars['DateTimeISO']['input']; + readonly start: Scalars['DateTimeISO']['input']; }; export type ListDevicesResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListDevicesResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -601,7 +734,19 @@ export type ListDevicesResponse = AbstractGraphQlArrayOkResponse & AbstractGraph export type ListEventsResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListEventsResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; + readonly ok: Scalars['Boolean']['output']; + /** The current page number (1-indexed) */ + readonly page: Scalars['PositiveInt']['output']; + /** The number of items per page */ + readonly pageSize: Scalars['NonNegativeInt']['output']; + /** The total number of items */ + readonly total: Scalars['NonNegativeInt']['output']; +}; + +export type ListFundraisingEntriesResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { + readonly __typename?: 'ListFundraisingEntriesResponse'; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -613,7 +758,7 @@ export type ListEventsResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQ export type ListImagesResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListImagesResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -625,7 +770,7 @@ export type ListImagesResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQ export type ListMarathonsResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListMarathonsResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -637,7 +782,7 @@ export type ListMarathonsResponse = AbstractGraphQlArrayOkResponse & AbstractGra export type ListNotificationDeliveriesResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListNotificationDeliveriesResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -649,7 +794,7 @@ export type ListNotificationDeliveriesResponse = AbstractGraphQlArrayOkResponse export type ListNotificationsResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListNotificationsResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -661,7 +806,7 @@ export type ListNotificationsResponse = AbstractGraphQlArrayOkResponse & Abstrac export type ListPeopleResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListPeopleResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -673,7 +818,7 @@ export type ListPeopleResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQ export type ListPointEntriesResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListPointEntriesResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -685,7 +830,7 @@ export type ListPointEntriesResponse = AbstractGraphQlArrayOkResponse & Abstract export type ListPointOpportunitiesResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListPointOpportunitiesResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -697,7 +842,7 @@ export type ListPointOpportunitiesResponse = AbstractGraphQlArrayOkResponse & Ab export type ListTeamsResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListTeamsResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -710,20 +855,44 @@ export type ListTeamsResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQl export type LoginState = { readonly __typename?: 'LoginState'; readonly authSource: AuthSource; + readonly dbRole: DbRole; + readonly effectiveCommitteeRoles: ReadonlyArray; readonly loggedIn: Scalars['Boolean']['output']; - readonly role: RoleResource; }; -export type MarathonHourResource = { - readonly __typename?: 'MarathonHourResource'; +export type MarathonHourNode = Node & { + readonly __typename?: 'MarathonHourNode'; readonly createdAt?: Maybe; readonly details?: Maybe; readonly durationInfo: Scalars['String']['output']; - readonly mapImages: ReadonlyArray; + readonly id: Scalars['GlobalId']['output']; + readonly mapImages: ReadonlyArray; readonly shownStartingAt: Scalars['DateTimeISO']['output']; readonly title: Scalars['String']['output']; readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; +}; + +export type MarathonNode = Node & { + readonly __typename?: 'MarathonNode'; + readonly communityDevelopmentCommitteeTeam: TeamNode; + readonly corporateCommitteeTeam: TeamNode; + readonly createdAt?: Maybe; + readonly dancerRelationsCommitteeTeam: TeamNode; + readonly endDate?: Maybe; + readonly familyRelationsCommitteeTeam: TeamNode; + readonly fundraisingCommitteeTeam: TeamNode; + readonly hours: ReadonlyArray; + readonly id: Scalars['GlobalId']['output']; + readonly marketingCommitteeTeam: TeamNode; + readonly miniMarathonsCommitteeTeam: TeamNode; + readonly operationsCommitteeTeam: TeamNode; + readonly overallCommitteeTeam: TeamNode; + readonly programmingCommitteeTeam: TeamNode; + readonly startDate?: Maybe; + readonly techCommitteeTeam: TeamNode; + readonly updatedAt?: Maybe; + readonly viceCommitteeTeam: TeamNode; + readonly year: Scalars['String']['output']; }; export const MarathonResolverAllKeys = { @@ -750,7 +919,7 @@ export type MarathonResolverKeyedDateFilterItem = { readonly field: MarathonResolverDateFilterKeys; /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ readonly negate?: InputMaybe; - readonly value: Scalars['LuxonDateTime']['input']; + readonly value: Scalars['DateTimeISO']['input']; }; export type MarathonResolverKeyedIsNullFilterItem = { @@ -760,44 +929,36 @@ export type MarathonResolverKeyedIsNullFilterItem = { readonly negate?: InputMaybe; }; -export type MarathonResource = { - readonly __typename?: 'MarathonResource'; +export type MembershipNode = Node & { + readonly __typename?: 'MembershipNode'; readonly createdAt?: Maybe; - readonly endDate: Scalars['DateTimeISO']['output']; - readonly hours: ReadonlyArray; - readonly startDate: Scalars['DateTimeISO']['output']; + readonly id: Scalars['GlobalId']['output']; + readonly person: PersonNode; + readonly position: MembershipPositionType; + readonly team: TeamNode; readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; - readonly year: Scalars['String']['output']; }; export { MembershipPositionType }; -export type MembershipResource = { - readonly __typename?: 'MembershipResource'; - readonly createdAt?: Maybe; - readonly person: PersonResource; - readonly position: MembershipPositionType; - readonly team: TeamResource; - readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; -}; - export type Mutation = { readonly __typename?: 'Mutation'; readonly abortScheduledNotification: AbortScheduledNotificationResponse; readonly acknowledgeDeliveryIssue: AcknowledgeDeliveryIssueResponse; readonly addExistingImageToEvent: AddEventImageResponse; - readonly addMap: MarathonHourResource; - readonly attachImageToFeedItem: FeedResource; + readonly addMap: MarathonHourNode; + readonly addPersonToTeam: MembershipNode; + readonly assignEntryToPerson: FundraisingAssignmentNode; + readonly assignTeamToDbFundsTeam: Scalars['Void']['output']; + readonly attachImageToFeedItem: FeedNode; readonly createConfiguration: CreateConfigurationResponse; readonly createConfigurations: CreateConfigurationResponse; readonly createEvent: CreateEventResponse; - readonly createFeedItem: FeedResource; - readonly createImage: ImageResource; - readonly createMarathon: MarathonResource; - readonly createMarathonHour: MarathonHourResource; - readonly createPerson: CreatePersonResponse; + readonly createFeedItem: FeedNode; + readonly createImage: ImageNode; + readonly createMarathon: MarathonNode; + readonly createMarathonHour: MarathonHourNode; + readonly createPerson: PersonNode; readonly createPointEntry: CreatePointEntryResponse; readonly createPointOpportunity: CreatePointOpportunityResponse; readonly createTeam: CreateTeamResponse; @@ -805,41 +966,43 @@ export type Mutation = { readonly deleteDevice: DeleteDeviceResponse; readonly deleteEvent: DeleteEventResponse; readonly deleteFeedItem: Scalars['Boolean']['output']; + readonly deleteFundraisingAssignment: FundraisingAssignmentNode; readonly deleteImage: DeleteImageResponse; readonly deleteMarathon: Scalars['Void']['output']; readonly deleteMarathonHour: Scalars['Void']['output']; readonly deleteNotification: DeleteNotificationResponse; - readonly deletePerson: DeletePersonResponse; + readonly deletePerson: PersonNode; readonly deletePointEntry: DeletePointEntryResponse; readonly deletePointOpportunity: DeletePointOpportunityResponse; readonly deleteTeam: DeleteTeamResponse; readonly registerDevice: RegisterDeviceResponse; readonly removeImageFromEvent: RemoveEventImageResponse; - readonly removeImageFromFeedItem: FeedResource; + readonly removeImageFromFeedItem: FeedNode; readonly removeMap: Scalars['Void']['output']; readonly scheduleNotification: ScheduleNotificationResponse; /** Send a notification immediately. */ readonly sendNotification: SendNotificationResponse; readonly setEvent: SetEventResponse; - readonly setFeedItem: FeedResource; - readonly setImageAltText: ImageResource; - readonly setImageUrl: ImageResource; - readonly setMarathon: MarathonResource; - readonly setMarathonHour: MarathonHourResource; - readonly setPerson: GetPersonResponse; + readonly setFeedItem: FeedNode; + readonly setImageAltText: ImageNode; + readonly setImageUrl: ImageNode; + readonly setMarathon: MarathonNode; + readonly setMarathonHour: MarathonHourNode; + readonly setPerson: PersonNode; readonly setPointOpportunity: SinglePointOpportunityResponse; readonly setTeam: SingleTeamResponse; readonly stageNotification: StageNotificationResponse; + readonly updateFundraisingAssignment: FundraisingAssignmentNode; }; export type MutationAbortScheduledNotificationArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationAcknowledgeDeliveryIssueArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -851,7 +1014,26 @@ export type MutationAddExistingImageToEventArgs = { export type MutationAddMapArgs = { imageUuid: Scalars['String']['input']; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; +}; + + +export type MutationAddPersonToTeamArgs = { + personUuid: Scalars['String']['input']; + teamUuid: Scalars['String']['input']; +}; + + +export type MutationAssignEntryToPersonArgs = { + entryId: Scalars['String']['input']; + input: AssignEntryToPersonInput; + personId: Scalars['String']['input']; +}; + + +export type MutationAssignTeamToDbFundsTeamArgs = { + dbFundsTeamId: Scalars['Float']['input']; + teamId: Scalars['String']['input']; }; @@ -914,21 +1096,22 @@ export type MutationCreatePointOpportunityArgs = { export type MutationCreateTeamArgs = { input: CreateTeamInput; + marathon: Scalars['String']['input']; }; export type MutationDeleteConfigurationArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeleteDeviceArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeleteEventArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -937,44 +1120,49 @@ export type MutationDeleteFeedItemArgs = { }; +export type MutationDeleteFundraisingAssignmentArgs = { + id: Scalars['GlobalId']['input']; +}; + + export type MutationDeleteImageArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeleteMarathonArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeleteMarathonHourArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeleteNotificationArgs = { force?: InputMaybe; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeletePersonArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeletePointEntryArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeletePointOpportunityArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeleteTeamArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -996,24 +1184,24 @@ export type MutationRemoveImageFromFeedItemArgs = { export type MutationRemoveMapArgs = { imageUuid: Scalars['String']['input']; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationScheduleNotificationArgs = { sendAt: Scalars['DateTimeISO']['input']; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationSendNotificationArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationSetEventArgs = { input: SetEventInput; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1025,42 +1213,42 @@ export type MutationSetFeedItemArgs = { export type MutationSetImageAltTextArgs = { alt: Scalars['String']['input']; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationSetImageUrlArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationSetMarathonArgs = { input: SetMarathonInput; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationSetMarathonHourArgs = { input: SetMarathonHourInput; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationSetPersonArgs = { input: SetPersonInput; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationSetPointOpportunityArgs = { input: SetPointOpportunityInput; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationSetTeamArgs = { input: SetTeamInput; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1071,6 +1259,16 @@ export type MutationStageNotificationArgs = { url?: InputMaybe; }; + +export type MutationUpdateFundraisingAssignmentArgs = { + id: Scalars['GlobalId']['input']; + input: UpdateFundraisingAssignmentInput; +}; + +export type Node = { + readonly id: Scalars['GlobalId']['output']; +}; + export type NotificationAudienceInput = { readonly all?: InputMaybe; readonly memberOfTeamType?: InputMaybe; @@ -1089,6 +1287,22 @@ export type NotificationDeliveryIssueCount = { readonly Unknown: Scalars['Int']['output']; }; +export type NotificationDeliveryNode = Node & { + readonly __typename?: 'NotificationDeliveryNode'; + /** A unique identifier corresponding the group of notifications this was sent to Expo with. */ + readonly chunkUuid?: Maybe; + readonly createdAt?: Maybe; + /** Any error message returned by Expo when sending the notification. */ + readonly deliveryError?: Maybe; + readonly id: Scalars['GlobalId']['output']; + readonly notification: NotificationNode; + /** The time the server received a delivery receipt from the user. */ + readonly receiptCheckedAt?: Maybe; + /** The time the server sent the notification to Expo for delivery. */ + readonly sentAt?: Maybe; + readonly updatedAt?: Maybe; +}; + export const NotificationDeliveryResolverAllKeys = { CreatedAt: 'createdAt', DeliveryError: 'deliveryError', @@ -1113,7 +1327,7 @@ export type NotificationDeliveryResolverKeyedDateFilterItem = { readonly field: NotificationDeliveryResolverDateFilterKeys; /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ readonly negate?: InputMaybe; - readonly value: Scalars['LuxonDateTime']['input']; + readonly value: Scalars['DateTimeISO']['input']; }; export type NotificationDeliveryResolverKeyedIsNullFilterItem = { @@ -1123,20 +1337,22 @@ export type NotificationDeliveryResolverKeyedIsNullFilterItem = { readonly negate?: InputMaybe; }; -export type NotificationDeliveryResource = { - readonly __typename?: 'NotificationDeliveryResource'; - /** A unique identifier corresponding the group of notifications this was sent to Expo with. */ - readonly chunkUuid?: Maybe; +export type NotificationNode = Node & { + readonly __typename?: 'NotificationNode'; + readonly body: Scalars['String']['output']; readonly createdAt?: Maybe; - /** Any error message returned by Expo when sending the notification. */ - readonly deliveryError?: Maybe; - readonly notification: NotificationResource; - /** The time the server received a delivery receipt from the user. */ - readonly receiptCheckedAt?: Maybe; - /** The time the server sent the notification to Expo for delivery. */ - readonly sentAt?: Maybe; + readonly deliveryCount: Scalars['Int']['output']; + readonly deliveryIssue?: Maybe; + readonly deliveryIssueAcknowledgedAt?: Maybe; + readonly deliveryIssueCount: NotificationDeliveryIssueCount; + readonly id: Scalars['GlobalId']['output']; + /** The time the notification is scheduled to be sent, if null it is either already sent or unscheduled. */ + readonly sendAt?: Maybe; + /** The time the server started sending the notification. */ + readonly startedSendingAt?: Maybe; + readonly title: Scalars['String']['output']; readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; + readonly url?: Maybe; }; export const NotificationResolverAllKeys = { @@ -1165,7 +1381,7 @@ export type NotificationResolverKeyedDateFilterItem = { readonly field: NotificationResolverDateFilterKeys; /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ readonly negate?: InputMaybe; - readonly value: Scalars['LuxonDateTime']['input']; + readonly value: Scalars['DateTimeISO']['input']; }; export type NotificationResolverKeyedIsNullFilterItem = { @@ -1204,25 +1420,40 @@ export const NotificationResolverStringFilterKeys = { } as const; export type NotificationResolverStringFilterKeys = typeof NotificationResolverStringFilterKeys[keyof typeof NotificationResolverStringFilterKeys]; -export type NotificationResource = { - readonly __typename?: 'NotificationResource'; - readonly body: Scalars['String']['output']; +export { NumericComparator }; + +export type PersonNode = Node & { + readonly __typename?: 'PersonNode'; + readonly assignedDonationEntries?: Maybe; + readonly committees: ReadonlyArray; readonly createdAt?: Maybe; - readonly deliveryCount: Scalars['Int']['output']; - readonly deliveryIssue?: Maybe; - readonly deliveryIssueAcknowledgedAt?: Maybe; - readonly deliveryIssueCount: NotificationDeliveryIssueCount; - /** The time the notification is scheduled to be sent, if null it is either already sent or unscheduled. */ - readonly sendAt?: Maybe; - /** The time the server started sending the notification. */ - readonly startedSendingAt?: Maybe; - readonly title: Scalars['String']['output']; + readonly dbRole: DbRole; + readonly email: Scalars['String']['output']; + readonly fundraisingAssignments: ReadonlyArray; + readonly id: Scalars['GlobalId']['output']; + readonly linkblue?: Maybe; + readonly moraleTeams: ReadonlyArray; + readonly name?: Maybe; + readonly primaryCommittee?: Maybe; + readonly teams: ReadonlyArray; readonly updatedAt?: Maybe; - readonly url?: Maybe; - readonly uuid: Scalars['ID']['output']; }; -export { NumericComparator }; + +export type PersonNodeAssignedDonationEntriesArgs = { + booleanFilters?: InputMaybe; + dateFilters?: InputMaybe>; + includeDeleted?: InputMaybe; + isNullFilters?: InputMaybe>; + numericFilters?: InputMaybe>; + oneOfFilters?: InputMaybe>; + page?: InputMaybe; + pageSize?: InputMaybe; + sendAll?: InputMaybe; + sortBy?: InputMaybe>; + sortDirection?: InputMaybe>; + stringFilters?: InputMaybe>; +}; export const PersonResolverAllKeys = { CommitteeName: 'committeeName', @@ -1273,20 +1504,16 @@ export const PersonResolverStringFilterKeys = { } as const; export type PersonResolverStringFilterKeys = typeof PersonResolverStringFilterKeys[keyof typeof PersonResolverStringFilterKeys]; -export type PersonResource = { - readonly __typename?: 'PersonResource'; - /** @deprecated This is now provided on the AuthIdPair resource. */ - readonly authIds: ReadonlyArray; - /** @deprecated Use teams instead and filter by position */ - readonly captaincies: ReadonlyArray; +export type PointEntryNode = Node & { + readonly __typename?: 'PointEntryNode'; + readonly comment?: Maybe; readonly createdAt?: Maybe; - readonly email: Scalars['String']['output']; - readonly linkblue?: Maybe; - readonly name?: Maybe; - readonly role: RoleResource; - readonly teams: ReadonlyArray; + readonly id: Scalars['GlobalId']['output']; + readonly personFrom?: Maybe; + readonly pointOpportunity?: Maybe; + readonly points: Scalars['Int']['output']; + readonly team: TeamNode; readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; }; export const PointEntryResolverAllKeys = { @@ -1308,7 +1535,7 @@ export type PointEntryResolverKeyedDateFilterItem = { readonly field: PointEntryResolverDateFilterKeys; /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ readonly negate?: InputMaybe; - readonly value: Scalars['LuxonDateTime']['input']; + readonly value: Scalars['DateTimeISO']['input']; }; export type PointEntryResolverKeyedIsNullFilterItem = { @@ -1318,16 +1545,15 @@ export type PointEntryResolverKeyedIsNullFilterItem = { readonly negate?: InputMaybe; }; -export type PointEntryResource = { - readonly __typename?: 'PointEntryResource'; - readonly comment?: Maybe; +export type PointOpportunityNode = Node & { + readonly __typename?: 'PointOpportunityNode'; readonly createdAt?: Maybe; - readonly personFrom?: Maybe; - readonly pointOpportunity?: Maybe; - readonly points: Scalars['Int']['output']; - readonly team: TeamResource; + readonly event?: Maybe; + readonly id: Scalars['GlobalId']['output']; + readonly name: Scalars['String']['output']; + readonly opportunityDate?: Maybe; + readonly type: TeamType; readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; }; export const PointOpportunityResolverAllKeys = { @@ -1353,7 +1579,7 @@ export type PointOpportunityResolverKeyedDateFilterItem = { readonly field: PointOpportunityResolverDateFilterKeys; /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ readonly negate?: InputMaybe; - readonly value: Scalars['LuxonDateTime']['input']; + readonly value: Scalars['DateTimeISO']['input']; }; export type PointOpportunityResolverKeyedIsNullFilterItem = { @@ -1391,48 +1617,43 @@ export const PointOpportunityResolverStringFilterKeys = { } as const; export type PointOpportunityResolverStringFilterKeys = typeof PointOpportunityResolverStringFilterKeys[keyof typeof PointOpportunityResolverStringFilterKeys]; -export type PointOpportunityResource = { - readonly __typename?: 'PointOpportunityResource'; - readonly createdAt?: Maybe; - readonly event?: Maybe; - readonly name: Scalars['String']['output']; - readonly opportunityDate?: Maybe; - readonly type: TeamType; - readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; -}; - export type Query = { readonly __typename?: 'Query'; readonly activeConfiguration: GetConfigurationByUuidResponse; readonly allConfigurations: GetAllConfigurationsResponse; - readonly currentMarathon?: Maybe; - readonly currentMarathonHour?: Maybe; + readonly configuration: GetConfigurationByUuidResponse; + readonly currentMarathon?: Maybe; + readonly currentMarathonHour?: Maybe; + readonly dbFundsTeams: ReadonlyArray; readonly device: GetDeviceByUuidResponse; readonly devices: ListDevicesResponse; readonly event: GetEventByUuidResponse; readonly events: ListEventsResponse; - readonly feed: ReadonlyArray; + readonly feed: ReadonlyArray; + readonly fundraisingAssignment: FundraisingAssignmentNode; + readonly fundraisingEntries: ListFundraisingEntriesResponse; + readonly fundraisingEntry: FundraisingEntryNode; readonly image: GetImageByUuidResponse; readonly images: ListImagesResponse; + readonly latestMarathon?: Maybe; readonly listPeople: ListPeopleResponse; readonly loginState: LoginState; - readonly marathon: MarathonResource; - readonly marathonForYear: MarathonResource; - readonly marathonHour: MarathonHourResource; + readonly marathon: MarathonNode; + readonly marathonForYear: MarathonNode; + readonly marathonHour: MarathonHourNode; readonly marathons: ListMarathonsResponse; - readonly me: GetPersonResponse; - readonly nextMarathon?: Maybe; + readonly me?: Maybe; + readonly node: Node; readonly notification: GetNotificationByUuidResponse; readonly notificationDeliveries: ListNotificationDeliveriesResponse; readonly notifications: ListNotificationsResponse; - readonly person: GetPersonResponse; - readonly personByLinkBlue: GetPersonResponse; + readonly person: PersonNode; + readonly personByLinkBlue: PersonNode; readonly pointEntries: ListPointEntriesResponse; readonly pointEntry: GetPointEntryByUuidResponse; readonly pointOpportunities: ListPointOpportunitiesResponse; readonly pointOpportunity: SinglePointOpportunityResponse; - readonly searchPeopleByName: GetPeopleResponse; + readonly searchPeopleByName: ReadonlyArray; readonly team: SingleTeamResponse; readonly teams: ListTeamsResponse; }; @@ -1443,8 +1664,18 @@ export type QueryActiveConfigurationArgs = { }; +export type QueryConfigurationArgs = { + id: Scalars['GlobalId']['input']; +}; + + +export type QueryDbFundsTeamsArgs = { + search: Scalars['String']['input']; +}; + + export type QueryDeviceArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1465,7 +1696,7 @@ export type QueryDevicesArgs = { export type QueryEventArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1490,8 +1721,34 @@ export type QueryFeedArgs = { }; +export type QueryFundraisingAssignmentArgs = { + id: Scalars['GlobalId']['input']; +}; + + +export type QueryFundraisingEntriesArgs = { + booleanFilters?: InputMaybe; + dateFilters?: InputMaybe>; + includeDeleted?: InputMaybe; + isNullFilters?: InputMaybe>; + numericFilters?: InputMaybe>; + oneOfFilters?: InputMaybe>; + page?: InputMaybe; + pageSize?: InputMaybe; + sendAll?: InputMaybe; + sortBy?: InputMaybe>; + sortDirection?: InputMaybe>; + stringFilters?: InputMaybe>; +}; + + +export type QueryFundraisingEntryArgs = { + id: Scalars['GlobalId']['input']; +}; + + export type QueryImageArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1528,7 +1785,7 @@ export type QueryListPeopleArgs = { export type QueryMarathonArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1538,7 +1795,7 @@ export type QueryMarathonForYearArgs = { export type QueryMarathonHourArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1558,8 +1815,13 @@ export type QueryMarathonsArgs = { }; +export type QueryNodeArgs = { + id: Scalars['GlobalId']['input']; +}; + + export type QueryNotificationArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1597,7 +1859,7 @@ export type QueryNotificationsArgs = { export type QueryPersonArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1623,7 +1885,7 @@ export type QueryPointEntriesArgs = { export type QueryPointEntryArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1644,7 +1906,7 @@ export type QueryPointOpportunitiesArgs = { export type QueryPointOpportunityArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1654,7 +1916,7 @@ export type QuerySearchPeopleByNameArgs = { export type QueryTeamArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1664,7 +1926,7 @@ export type QueryTeamsArgs = { includeDeleted?: InputMaybe; isNullFilters?: InputMaybe>; legacyStatus?: InputMaybe>; - marathonYear?: InputMaybe>; + marathonId?: InputMaybe>; numericFilters?: InputMaybe; oneOfFilters?: InputMaybe>; page?: InputMaybe; @@ -1689,7 +1951,7 @@ export type RegisterDeviceInput = { export type RegisterDeviceResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'RegisterDeviceResponse'; - readonly data: DeviceResource; + readonly data: DeviceNode; readonly ok: Scalars['Boolean']['output']; }; @@ -1699,19 +1961,6 @@ export type RemoveEventImageResponse = AbstractGraphQlOkResponse & GraphQlBaseRe readonly ok: Scalars['Boolean']['output']; }; -export type RoleResource = { - readonly __typename?: 'RoleResource'; - readonly committeeIdentifier?: Maybe; - readonly committeeRole?: Maybe; - readonly dbRole: DbRole; -}; - -export type RoleResourceInput = { - readonly committeeIdentifier?: InputMaybe; - readonly committeeRole?: InputMaybe; - readonly dbRole?: DbRole; -}; - export type ScheduleNotificationResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'ScheduleNotificationResponse'; readonly data: Scalars['Boolean']['output']; @@ -1734,14 +1983,14 @@ export type SetEventInput = { export type SetEventOccurrenceInput = { readonly fullDay: Scalars['Boolean']['input']; - readonly interval: Scalars['LuxonDateRange']['input']; + readonly interval: IntervalIsoInput; /** If updating an existing occurrence, the UUID of the occurrence to update */ readonly uuid?: InputMaybe; }; export type SetEventResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'SetEventResponse'; - readonly data: EventResource; + readonly data: EventNode; readonly ok: Scalars['Boolean']['output']; }; @@ -1769,19 +2018,17 @@ export type SetPersonInput = { readonly linkblue?: InputMaybe; readonly memberOf?: InputMaybe>; readonly name?: InputMaybe; - readonly role?: InputMaybe; }; export type SetPointOpportunityInput = { readonly eventUuid?: InputMaybe; readonly name?: InputMaybe; - readonly opportunityDate?: InputMaybe; + readonly opportunityDate?: InputMaybe; readonly type?: InputMaybe; }; export type SetTeamInput = { readonly legacyStatus?: InputMaybe; - readonly marathonYear?: InputMaybe; readonly name?: InputMaybe; readonly persistentIdentifier?: InputMaybe; readonly type?: InputMaybe; @@ -1789,13 +2036,13 @@ export type SetTeamInput = { export type SinglePointOpportunityResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'SinglePointOpportunityResponse'; - readonly data: PointOpportunityResource; + readonly data: PointOpportunityNode; readonly ok: Scalars['Boolean']['output']; }; export type SingleTeamResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'SingleTeamResponse'; - readonly data: TeamResource; + readonly data: TeamNode; readonly ok: Scalars['Boolean']['output']; }; @@ -1803,7 +2050,7 @@ export { SortDirection }; export type StageNotificationResponse = AbstractGraphQlCreatedResponse & AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'StageNotificationResponse'; - readonly data: NotificationResource; + readonly data: NotificationNode; readonly ok: Scalars['Boolean']['output']; readonly uuid: Scalars['String']['output']; }; @@ -1812,9 +2059,42 @@ export { StringComparator }; export { TeamLegacyStatus }; +export type TeamNode = Node & { + readonly __typename?: 'TeamNode'; + /** @deprecated Just query the members field and filter by role */ + readonly captains: ReadonlyArray; + readonly createdAt?: Maybe; + readonly fundraisingEntries: ListFundraisingEntriesResponse; + readonly id: Scalars['GlobalId']['output']; + readonly legacyStatus: TeamLegacyStatus; + readonly marathon: MarathonNode; + readonly members: ReadonlyArray; + readonly name: Scalars['String']['output']; + readonly pointEntries: ReadonlyArray; + readonly totalPoints: Scalars['Int']['output']; + readonly type: TeamType; + readonly updatedAt?: Maybe; +}; + + +export type TeamNodeFundraisingEntriesArgs = { + booleanFilters?: InputMaybe; + dateFilters?: InputMaybe>; + includeDeleted?: InputMaybe; + isNullFilters?: InputMaybe>; + numericFilters?: InputMaybe>; + oneOfFilters?: InputMaybe>; + page?: InputMaybe; + pageSize?: InputMaybe; + sendAll?: InputMaybe; + sortBy?: InputMaybe>; + sortDirection?: InputMaybe>; + stringFilters?: InputMaybe>; +}; + export const TeamResolverAllKeys = { LegacyStatus: 'legacyStatus', - MarathonYear: 'marathonYear', + MarathonId: 'marathonId', Name: 'name', Type: 'type' } as const; @@ -1847,7 +2127,7 @@ export type TeamResolverKeyedStringFilterItem = { export const TeamResolverOneOfFilterKeys = { LegacyStatus: 'legacyStatus', - MarathonYear: 'marathonYear', + MarathonId: 'marathonId', Type: 'type' } as const; @@ -1857,38 +2137,25 @@ export const TeamResolverStringFilterKeys = { } as const; export type TeamResolverStringFilterKeys = typeof TeamResolverStringFilterKeys[keyof typeof TeamResolverStringFilterKeys]; -export type TeamResource = { - readonly __typename?: 'TeamResource'; - /** @deprecated Just query the members field and filter by role */ - readonly captains: ReadonlyArray; - readonly createdAt?: Maybe; - readonly legacyStatus: TeamLegacyStatus; - readonly marathonYear: Scalars['String']['output']; - readonly members: ReadonlyArray; - readonly name: Scalars['String']['output']; - readonly persistentIdentifier?: Maybe; - readonly pointEntries: ReadonlyArray; - readonly totalPoints: Scalars['Int']['output']; - readonly type: TeamType; - readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; -}; - export { TeamType }; -export type ImageViewFragmentFragment = { readonly __typename?: 'ImageResource', readonly uuid: string, readonly url?: URL | string | null, readonly thumbHash?: string | null, readonly alt?: string | null, readonly width: number, readonly height: number, readonly mimeType: string } & { ' $fragmentName'?: 'ImageViewFragmentFragment' }; +export type UpdateFundraisingAssignmentInput = { + readonly amount: Scalars['Float']['input']; +}; + +export type ImageViewFragmentFragment = { readonly __typename?: 'ImageNode', readonly id: string, readonly url?: URL | string | null, readonly thumbHash?: string | null, readonly alt?: string | null, readonly width: number, readonly height: number, readonly mimeType: string } & { ' $fragmentName'?: 'ImageViewFragmentFragment' }; -export type SimpleConfigFragment = { readonly __typename?: 'ConfigurationResource', readonly uuid: string, readonly key: string, readonly value: string } & { ' $fragmentName'?: 'SimpleConfigFragment' }; +export type SimpleConfigFragment = { readonly __typename?: 'ConfigurationNode', readonly id: string, readonly key: string, readonly value: string } & { ' $fragmentName'?: 'SimpleConfigFragment' }; export type FullConfigFragment = ( - { readonly __typename?: 'ConfigurationResource', readonly validAfter?: string | null, readonly validUntil?: string | null, readonly createdAt?: Date | string | null } + { readonly __typename?: 'ConfigurationNode', readonly validAfter?: Date | string | null, readonly validUntil?: Date | string | null, readonly createdAt?: Date | string | null } & { ' $fragmentRefs'?: { 'SimpleConfigFragment': SimpleConfigFragment } } ) & { ' $fragmentName'?: 'FullConfigFragment' }; -export type NotificationFragmentFragment = { readonly __typename?: 'NotificationResource', readonly uuid: string, readonly title: string, readonly body: string, readonly url?: URL | string | null } & { ' $fragmentName'?: 'NotificationFragmentFragment' }; +export type NotificationFragmentFragment = { readonly __typename?: 'NotificationNode', readonly id: string, readonly title: string, readonly body: string, readonly url?: URL | string | null } & { ' $fragmentName'?: 'NotificationFragmentFragment' }; -export type NotificationDeliveryFragmentFragment = { readonly __typename?: 'NotificationDeliveryResource', readonly uuid: string, readonly sentAt?: Date | string | null, readonly notification: ( - { readonly __typename?: 'NotificationResource' } +export type NotificationDeliveryFragmentFragment = { readonly __typename?: 'NotificationDeliveryNode', readonly id: string, readonly sentAt?: Date | string | null, readonly notification: ( + { readonly __typename?: 'NotificationNode' } & { ' $fragmentRefs'?: { 'NotificationFragmentFragment': NotificationFragmentFragment } } ) } & { ' $fragmentName'?: 'NotificationDeliveryFragmentFragment' }; @@ -1896,35 +2163,35 @@ export type UseAllowedLoginTypesQueryVariables = Exact<{ [key: string]: never; } export type UseAllowedLoginTypesQuery = { readonly __typename?: 'Query', readonly activeConfiguration: { readonly __typename?: 'GetConfigurationByUuidResponse', readonly data: ( - { readonly __typename?: 'ConfigurationResource' } + { readonly __typename?: 'ConfigurationNode' } & { ' $fragmentRefs'?: { 'SimpleConfigFragment': SimpleConfigFragment } } ) } }; export type MarathonTimeQueryVariables = Exact<{ [key: string]: never; }>; -export type MarathonTimeQuery = { readonly __typename?: 'Query', readonly nextMarathon?: { readonly __typename?: 'MarathonResource', readonly startDate: Date | string, readonly endDate: Date | string } | null }; +export type MarathonTimeQuery = { readonly __typename?: 'Query', readonly latestMarathon?: { readonly __typename?: 'MarathonNode', readonly startDate?: Date | string | null, readonly endDate?: Date | string | null } | null }; export type UseTabBarConfigQueryVariables = Exact<{ [key: string]: never; }>; export type UseTabBarConfigQuery = { readonly __typename?: 'Query', readonly activeConfiguration: { readonly __typename?: 'GetConfigurationByUuidResponse', readonly data: ( - { readonly __typename?: 'ConfigurationResource' } + { readonly __typename?: 'ConfigurationNode' } & { ' $fragmentRefs'?: { 'SimpleConfigFragment': SimpleConfigFragment } } - ) }, readonly me: { readonly __typename?: 'GetPersonResponse', readonly data?: { readonly __typename?: 'PersonResource', readonly linkblue?: string | null } | null } }; + ) }, readonly me?: { readonly __typename?: 'PersonNode', readonly linkblue?: string | null } | null }; export type TriviaCrackQueryVariables = Exact<{ [key: string]: never; }>; export type TriviaCrackQuery = { readonly __typename?: 'Query', readonly activeConfiguration: { readonly __typename?: 'GetConfigurationByUuidResponse', readonly data: ( - { readonly __typename?: 'ConfigurationResource' } + { readonly __typename?: 'ConfigurationNode' } & { ' $fragmentRefs'?: { 'SimpleConfigFragment': SimpleConfigFragment } } - ) }, readonly me: { readonly __typename?: 'GetPersonResponse', readonly data?: { readonly __typename?: 'PersonResource', readonly teams: ReadonlyArray<{ readonly __typename?: 'MembershipResource', readonly team: { readonly __typename?: 'TeamResource', readonly type: TeamType, readonly name: string } }> } | null } }; + ) }, readonly me?: { readonly __typename?: 'PersonNode', readonly teams: ReadonlyArray<{ readonly __typename?: 'MembershipNode', readonly team: { readonly __typename?: 'TeamNode', readonly type: TeamType, readonly name: string } }> } | null }; export type AuthStateQueryVariables = Exact<{ [key: string]: never; }>; -export type AuthStateQuery = { readonly __typename?: 'Query', readonly me: { readonly __typename?: 'GetPersonResponse', readonly data?: { readonly __typename?: 'PersonResource', readonly uuid: string } | null }, readonly loginState: { readonly __typename?: 'LoginState', readonly loggedIn: boolean, readonly authSource: AuthSource, readonly role: { readonly __typename?: 'RoleResource', readonly dbRole: DbRole, readonly committeeIdentifier?: CommitteeIdentifier | null, readonly committeeRole?: CommitteeRole | null } } }; +export type AuthStateQuery = { readonly __typename?: 'Query', readonly me?: { readonly __typename?: 'PersonNode', readonly id: string } | null, readonly loginState: { readonly __typename?: 'LoginState', readonly dbRole: DbRole, readonly loggedIn: boolean, readonly authSource: AuthSource } }; export type SetDeviceMutationVariables = Exact<{ input: RegisterDeviceInput; @@ -1933,24 +2200,24 @@ export type SetDeviceMutationVariables = Exact<{ export type SetDeviceMutation = { readonly __typename?: 'Mutation', readonly registerDevice: { readonly __typename?: 'RegisterDeviceResponse', readonly ok: boolean } }; -export type EventScreenFragmentFragment = { readonly __typename?: 'EventResource', readonly uuid: string, readonly title: string, readonly summary?: string | null, readonly description?: string | null, readonly location?: string | null, readonly occurrences: ReadonlyArray<{ readonly __typename?: 'EventOccurrenceResource', readonly uuid: string, readonly interval: string, readonly fullDay: boolean }>, readonly images: ReadonlyArray<{ readonly __typename?: 'ImageResource', readonly thumbHash?: string | null, readonly url?: URL | string | null, readonly height: number, readonly width: number, readonly alt?: string | null, readonly mimeType: string }> } & { ' $fragmentName'?: 'EventScreenFragmentFragment' }; +export type EventScreenFragmentFragment = { readonly __typename?: 'EventNode', readonly id: string, readonly title: string, readonly summary?: string | null, readonly description?: string | null, readonly location?: string | null, readonly occurrences: ReadonlyArray<{ readonly __typename?: 'EventOccurrenceNode', readonly id: string, readonly fullDay: boolean, readonly interval: { readonly __typename?: 'IntervalISO', readonly start: Date | string, readonly end: Date | string } }>, readonly images: ReadonlyArray<{ readonly __typename?: 'ImageNode', readonly thumbHash?: string | null, readonly url?: URL | string | null, readonly height: number, readonly width: number, readonly alt?: string | null, readonly mimeType: string }> } & { ' $fragmentName'?: 'EventScreenFragmentFragment' }; export type DeviceNotificationsQueryVariables = Exact<{ - deviceUuid: Scalars['String']['input']; + deviceUuid: Scalars['GlobalId']['input']; page?: InputMaybe; pageSize?: InputMaybe; verifier: Scalars['String']['input']; }>; -export type DeviceNotificationsQuery = { readonly __typename?: 'Query', readonly device: { readonly __typename?: 'GetDeviceByUuidResponse', readonly data: { readonly __typename?: 'DeviceResource', readonly notificationDeliveries: ReadonlyArray<( - { readonly __typename?: 'NotificationDeliveryResource' } +export type DeviceNotificationsQuery = { readonly __typename?: 'Query', readonly device: { readonly __typename?: 'GetDeviceByUuidResponse', readonly data: { readonly __typename?: 'DeviceNode', readonly notificationDeliveries: ReadonlyArray<( + { readonly __typename?: 'NotificationDeliveryNode' } & { ' $fragmentRefs'?: { 'NotificationDeliveryFragmentFragment': NotificationDeliveryFragmentFragment } } )> } } }; -export type ProfileScreenAuthFragmentFragment = { readonly __typename?: 'LoginState', readonly authSource: AuthSource, readonly role: { readonly __typename?: 'RoleResource', readonly committeeIdentifier?: CommitteeIdentifier | null, readonly committeeRole?: CommitteeRole | null, readonly dbRole: DbRole } } & { ' $fragmentName'?: 'ProfileScreenAuthFragmentFragment' }; +export type ProfileScreenAuthFragmentFragment = { readonly __typename?: 'LoginState', readonly dbRole: DbRole, readonly authSource: AuthSource } & { ' $fragmentName'?: 'ProfileScreenAuthFragmentFragment' }; -export type ProfileScreenUserFragmentFragment = { readonly __typename?: 'PersonResource', readonly name?: string | null, readonly linkblue?: string | null, readonly teams: ReadonlyArray<{ readonly __typename?: 'MembershipResource', readonly position: MembershipPositionType, readonly team: { readonly __typename?: 'TeamResource', readonly name: string } }> } & { ' $fragmentName'?: 'ProfileScreenUserFragmentFragment' }; +export type ProfileScreenUserFragmentFragment = { readonly __typename?: 'PersonNode', readonly name?: string | null, readonly linkblue?: string | null, readonly teams: ReadonlyArray<{ readonly __typename?: 'MembershipNode', readonly position: MembershipPositionType, readonly team: { readonly __typename?: 'TeamNode', readonly name: string } }>, readonly primaryCommittee?: { readonly __typename?: 'CommitteeMembershipNode', readonly identifier: CommitteeIdentifier, readonly role: CommitteeRole } | null } & { ' $fragmentName'?: 'ProfileScreenUserFragmentFragment' }; export type RootScreenDocumentQueryVariables = Exact<{ [key: string]: never; }>; @@ -1958,31 +2225,31 @@ export type RootScreenDocumentQueryVariables = Exact<{ [key: string]: never; }>; export type RootScreenDocumentQuery = { readonly __typename?: 'Query', readonly loginState: ( { readonly __typename?: 'LoginState' } & { ' $fragmentRefs'?: { 'ProfileScreenAuthFragmentFragment': ProfileScreenAuthFragmentFragment;'RootScreenAuthFragmentFragment': RootScreenAuthFragmentFragment } } - ), readonly me: { readonly __typename?: 'GetPersonResponse', readonly data?: ( - { readonly __typename?: 'PersonResource' } - & { ' $fragmentRefs'?: { 'ProfileScreenUserFragmentFragment': ProfileScreenUserFragmentFragment } } - ) | null } }; + ), readonly me?: ( + { readonly __typename?: 'PersonNode' } + & { ' $fragmentRefs'?: { 'ProfileScreenUserFragmentFragment': ProfileScreenUserFragmentFragment } } + ) | null }; -export type RootScreenAuthFragmentFragment = { readonly __typename?: 'LoginState', readonly role: { readonly __typename?: 'RoleResource', readonly dbRole: DbRole } } & { ' $fragmentName'?: 'RootScreenAuthFragmentFragment' }; +export type RootScreenAuthFragmentFragment = { readonly __typename?: 'LoginState', readonly dbRole: DbRole } & { ' $fragmentName'?: 'RootScreenAuthFragmentFragment' }; export type EventsQueryVariables = Exact<{ - earliestTimestamp: Scalars['LuxonDateTime']['input']; - lastTimestamp: Scalars['LuxonDateTime']['input']; + earliestTimestamp: Scalars['DateTimeISO']['input']; + lastTimestamp: Scalars['DateTimeISO']['input']; }>; export type EventsQuery = { readonly __typename?: 'Query', readonly events: { readonly __typename?: 'ListEventsResponse', readonly data: ReadonlyArray<( - { readonly __typename?: 'EventResource' } + { readonly __typename?: 'EventNode' } & { ' $fragmentRefs'?: { 'EventScreenFragmentFragment': EventScreenFragmentFragment } } )> } }; export type ServerFeedQueryVariables = Exact<{ [key: string]: never; }>; -export type ServerFeedQuery = { readonly __typename?: 'Query', readonly feed: ReadonlyArray<{ readonly __typename?: 'FeedResource', readonly uuid: string, readonly title: string, readonly createdAt?: Date | string | null, readonly textContent?: string | null, readonly image?: { readonly __typename?: 'ImageResource', readonly url?: URL | string | null, readonly alt?: string | null, readonly width: number, readonly height: number, readonly thumbHash?: string | null } | null }> }; +export type ServerFeedQuery = { readonly __typename?: 'Query', readonly feed: ReadonlyArray<{ readonly __typename?: 'FeedNode', readonly id: string, readonly title: string, readonly createdAt?: Date | string | null, readonly textContent?: string | null, readonly image?: { readonly __typename?: 'ImageNode', readonly url?: URL | string | null, readonly alt?: string | null, readonly width: number, readonly height: number, readonly thumbHash?: string | null } | null }> }; -export type HourScreenFragmentFragment = { readonly __typename?: 'MarathonHourResource', readonly uuid: string, readonly title: string, readonly details?: string | null, readonly durationInfo: string, readonly mapImages: ReadonlyArray<( - { readonly __typename?: 'ImageResource' } +export type HourScreenFragmentFragment = { readonly __typename?: 'MarathonHourNode', readonly id: string, readonly title: string, readonly details?: string | null, readonly durationInfo: string, readonly mapImages: ReadonlyArray<( + { readonly __typename?: 'ImageNode' } & { ' $fragmentRefs'?: { 'ImageViewFragmentFragment': ImageViewFragmentFragment } } )> } & { ' $fragmentName'?: 'HourScreenFragmentFragment' }; @@ -1990,60 +2257,60 @@ export type MarathonScreenQueryVariables = Exact<{ [key: string]: never; }>; export type MarathonScreenQuery = { readonly __typename?: 'Query', readonly currentMarathonHour?: ( - { readonly __typename?: 'MarathonHourResource' } + { readonly __typename?: 'MarathonHourNode' } & { ' $fragmentRefs'?: { 'HourScreenFragmentFragment': HourScreenFragmentFragment } } - ) | null, readonly nextMarathon?: { readonly __typename?: 'MarathonResource', readonly startDate: Date | string, readonly endDate: Date | string, readonly hours: ReadonlyArray<( - { readonly __typename?: 'MarathonHourResource' } + ) | null, readonly latestMarathon?: { readonly __typename?: 'MarathonNode', readonly startDate?: Date | string | null, readonly endDate?: Date | string | null, readonly hours: ReadonlyArray<( + { readonly __typename?: 'MarathonHourNode' } & { ' $fragmentRefs'?: { 'HourScreenFragmentFragment': HourScreenFragmentFragment } } )> } | null }; -export type ScoreBoardFragmentFragment = { readonly __typename?: 'TeamResource', readonly uuid: string, readonly name: string, readonly totalPoints: number, readonly legacyStatus: TeamLegacyStatus, readonly type: TeamType } & { ' $fragmentName'?: 'ScoreBoardFragmentFragment' }; +export type ScoreBoardFragmentFragment = { readonly __typename?: 'TeamNode', readonly id: string, readonly name: string, readonly totalPoints: number, readonly legacyStatus: TeamLegacyStatus, readonly type: TeamType } & { ' $fragmentName'?: 'ScoreBoardFragmentFragment' }; -export type HighlightedTeamFragmentFragment = { readonly __typename?: 'TeamResource', readonly uuid: string, readonly name: string, readonly legacyStatus: TeamLegacyStatus, readonly type: TeamType } & { ' $fragmentName'?: 'HighlightedTeamFragmentFragment' }; +export type HighlightedTeamFragmentFragment = { readonly __typename?: 'TeamNode', readonly id: string, readonly name: string, readonly legacyStatus: TeamLegacyStatus, readonly type: TeamType } & { ' $fragmentName'?: 'HighlightedTeamFragmentFragment' }; export type ScoreBoardDocumentQueryVariables = Exact<{ type?: InputMaybe | TeamType>; }>; -export type ScoreBoardDocumentQuery = { readonly __typename?: 'Query', readonly me: { readonly __typename?: 'GetPersonResponse', readonly data?: { readonly __typename?: 'PersonResource', readonly uuid: string, readonly teams: ReadonlyArray<{ readonly __typename?: 'MembershipResource', readonly team: ( - { readonly __typename?: 'TeamResource' } - & { ' $fragmentRefs'?: { 'HighlightedTeamFragmentFragment': HighlightedTeamFragmentFragment;'MyTeamFragmentFragment': MyTeamFragmentFragment } } - ) }> } | null }, readonly teams: { readonly __typename?: 'ListTeamsResponse', readonly data: ReadonlyArray<( - { readonly __typename?: 'TeamResource' } +export type ScoreBoardDocumentQuery = { readonly __typename?: 'Query', readonly me?: { readonly __typename?: 'PersonNode', readonly id: string, readonly teams: ReadonlyArray<{ readonly __typename?: 'MembershipNode', readonly team: ( + { readonly __typename?: 'TeamNode' } + & { ' $fragmentRefs'?: { 'HighlightedTeamFragmentFragment': HighlightedTeamFragmentFragment;'MyTeamFragmentFragment': MyTeamFragmentFragment } } + ) }> } | null, readonly teams: { readonly __typename?: 'ListTeamsResponse', readonly data: ReadonlyArray<( + { readonly __typename?: 'TeamNode' } & { ' $fragmentRefs'?: { 'ScoreBoardFragmentFragment': ScoreBoardFragmentFragment } } )> } }; export type ActiveMarathonDocumentQueryVariables = Exact<{ [key: string]: never; }>; -export type ActiveMarathonDocumentQuery = { readonly __typename?: 'Query', readonly currentMarathon?: { readonly __typename?: 'MarathonResource', readonly uuid: string } | null }; - -export type MyTeamFragmentFragment = { readonly __typename?: 'TeamResource', readonly uuid: string, readonly name: string, readonly totalPoints: number, readonly pointEntries: ReadonlyArray<{ readonly __typename?: 'PointEntryResource', readonly points: number, readonly personFrom?: { readonly __typename?: 'PersonResource', readonly uuid: string, readonly name?: string | null, readonly linkblue?: string | null } | null }>, readonly members: ReadonlyArray<{ readonly __typename?: 'MembershipResource', readonly position: MembershipPositionType, readonly person: { readonly __typename?: 'PersonResource', readonly linkblue?: string | null, readonly name?: string | null } }> } & { ' $fragmentName'?: 'MyTeamFragmentFragment' }; - -export const SimpleConfigFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SimpleConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ConfigurationResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]} as unknown as DocumentNode; -export const FullConfigFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FullConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ConfigurationResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SimpleConfig"}},{"kind":"Field","name":{"kind":"Name","value":"validAfter"}},{"kind":"Field","name":{"kind":"Name","value":"validUntil"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SimpleConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ConfigurationResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]} as unknown as DocumentNode; -export const NotificationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]} as unknown as DocumentNode; -export const NotificationDeliveryFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationDeliveryFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationDeliveryResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"sentAt"}},{"kind":"Field","name":{"kind":"Name","value":"notification"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]} as unknown as DocumentNode; -export const EventScreenFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventScreenFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"summary"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"interval"}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"mimeType"}}]}}]}}]} as unknown as DocumentNode; -export const ProfileScreenAuthFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProfileScreenAuthFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LoginState"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"committeeIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"committeeRole"}},{"kind":"Field","name":{"kind":"Name","value":"dbRole"}}]}},{"kind":"Field","name":{"kind":"Name","value":"authSource"}}]}}]} as unknown as DocumentNode; -export const ProfileScreenUserFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProfileScreenUserFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; -export const RootScreenAuthFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RootScreenAuthFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LoginState"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dbRole"}}]}}]}}]} as unknown as DocumentNode; -export const ImageViewFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ImageViewFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ImageResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"mimeType"}}]}}]} as unknown as DocumentNode; -export const HourScreenFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"HourScreenFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarathonHourResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"details"}},{"kind":"Field","name":{"kind":"Name","value":"durationInfo"}},{"kind":"Field","name":{"kind":"Name","value":"mapImages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ImageViewFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ImageViewFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ImageResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"mimeType"}}]}}]} as unknown as DocumentNode; -export const ScoreBoardFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ScoreBoardFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"totalPoints"}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"type"}}]}}]} as unknown as DocumentNode; -export const HighlightedTeamFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"HighlightedTeamFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"type"}}]}}]} as unknown as DocumentNode; -export const MyTeamFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MyTeamFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"totalPoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointEntries"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personFrom"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}},{"kind":"Field","name":{"kind":"Name","value":"points"}}]}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"person"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; -export const UseAllowedLoginTypesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"useAllowedLoginTypes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeConfiguration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"StringValue","value":"ALLOWED_LOGIN_TYPES","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SimpleConfig"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SimpleConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ConfigurationResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]} as unknown as DocumentNode; -export const MarathonTimeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MarathonTime"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nextMarathon"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}}]}}]}}]} as unknown as DocumentNode; -export const UseTabBarConfigDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"useTabBarConfig"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeConfiguration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"StringValue","value":"TAB_BAR_CONFIG","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SimpleConfig"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SimpleConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ConfigurationResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]} as unknown as DocumentNode; -export const TriviaCrackDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"TriviaCrack"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeConfiguration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"StringValue","value":"TRIVIA_CRACK","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SimpleConfig"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"teams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SimpleConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ConfigurationResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]} as unknown as DocumentNode; -export const AuthStateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AuthState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"loginState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dbRole"}},{"kind":"Field","name":{"kind":"Name","value":"committeeIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"committeeRole"}}]}},{"kind":"Field","name":{"kind":"Name","value":"loggedIn"}},{"kind":"Field","name":{"kind":"Name","value":"authSource"}}]}}]}}]} as unknown as DocumentNode; +export type ActiveMarathonDocumentQuery = { readonly __typename?: 'Query', readonly currentMarathon?: { readonly __typename?: 'MarathonNode', readonly id: string } | null }; + +export type MyTeamFragmentFragment = { readonly __typename?: 'TeamNode', readonly id: string, readonly name: string, readonly totalPoints: number, readonly pointEntries: ReadonlyArray<{ readonly __typename?: 'PointEntryNode', readonly points: number, readonly personFrom?: { readonly __typename?: 'PersonNode', readonly id: string, readonly name?: string | null, readonly linkblue?: string | null } | null }>, readonly members: ReadonlyArray<{ readonly __typename?: 'MembershipNode', readonly position: MembershipPositionType, readonly person: { readonly __typename?: 'PersonNode', readonly linkblue?: string | null, readonly name?: string | null } }> } & { ' $fragmentName'?: 'MyTeamFragmentFragment' }; + +export const SimpleConfigFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SimpleConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ConfigurationNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]} as unknown as DocumentNode; +export const FullConfigFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FullConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ConfigurationNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SimpleConfig"}},{"kind":"Field","name":{"kind":"Name","value":"validAfter"}},{"kind":"Field","name":{"kind":"Name","value":"validUntil"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SimpleConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ConfigurationNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]} as unknown as DocumentNode; +export const NotificationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]} as unknown as DocumentNode; +export const NotificationDeliveryFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationDeliveryFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationDeliveryNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"sentAt"}},{"kind":"Field","name":{"kind":"Name","value":"notification"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]} as unknown as DocumentNode; +export const EventScreenFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventScreenFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"summary"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"interval"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"end"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"mimeType"}}]}}]}}]} as unknown as DocumentNode; +export const ProfileScreenAuthFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProfileScreenAuthFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LoginState"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dbRole"}},{"kind":"Field","name":{"kind":"Name","value":"authSource"}}]}}]} as unknown as DocumentNode; +export const ProfileScreenUserFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProfileScreenUserFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"primaryCommittee"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]} as unknown as DocumentNode; +export const RootScreenAuthFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RootScreenAuthFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LoginState"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dbRole"}}]}}]} as unknown as DocumentNode; +export const ImageViewFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ImageViewFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ImageNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"mimeType"}}]}}]} as unknown as DocumentNode; +export const HourScreenFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"HourScreenFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarathonHourNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"details"}},{"kind":"Field","name":{"kind":"Name","value":"durationInfo"}},{"kind":"Field","name":{"kind":"Name","value":"mapImages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ImageViewFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ImageViewFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ImageNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"mimeType"}}]}}]} as unknown as DocumentNode; +export const ScoreBoardFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ScoreBoardFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"totalPoints"}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"type"}}]}}]} as unknown as DocumentNode; +export const HighlightedTeamFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"HighlightedTeamFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"type"}}]}}]} as unknown as DocumentNode; +export const MyTeamFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MyTeamFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"totalPoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointEntries"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personFrom"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}},{"kind":"Field","name":{"kind":"Name","value":"points"}}]}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"person"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; +export const UseAllowedLoginTypesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"useAllowedLoginTypes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeConfiguration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"StringValue","value":"ALLOWED_LOGIN_TYPES","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SimpleConfig"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SimpleConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ConfigurationNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]} as unknown as DocumentNode; +export const MarathonTimeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MarathonTime"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"latestMarathon"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}}]}}]}}]} as unknown as DocumentNode; +export const UseTabBarConfigDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"useTabBarConfig"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeConfiguration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"StringValue","value":"TAB_BAR_CONFIG","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SimpleConfig"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SimpleConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ConfigurationNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]} as unknown as DocumentNode; +export const TriviaCrackDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"TriviaCrack"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeConfiguration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"StringValue","value":"TRIVIA_CRACK","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SimpleConfig"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"teams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SimpleConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ConfigurationNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]} as unknown as DocumentNode; +export const AuthStateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AuthState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"loginState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dbRole"}},{"kind":"Field","name":{"kind":"Name","value":"loggedIn"}},{"kind":"Field","name":{"kind":"Name","value":"authSource"}}]}}]}}]} as unknown as DocumentNode; export const SetDeviceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SetDevice"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RegisterDeviceInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"registerDevice"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; -export const DeviceNotificationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"DeviceNotifications"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"deviceUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"verifier"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"device"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"deviceUuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notificationDeliveries"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"verifier"},"value":{"kind":"Variable","name":{"kind":"Name","value":"verifier"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationDeliveryFragment"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationDeliveryFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationDeliveryResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"sentAt"}},{"kind":"Field","name":{"kind":"Name","value":"notification"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationFragment"}}]}}]}}]} as unknown as DocumentNode; -export const RootScreenDocumentDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"RootScreenDocument"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"loginState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProfileScreenAuthFragment"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"RootScreenAuthFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProfileScreenUserFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProfileScreenAuthFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LoginState"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"committeeIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"committeeRole"}},{"kind":"Field","name":{"kind":"Name","value":"dbRole"}}]}},{"kind":"Field","name":{"kind":"Name","value":"authSource"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RootScreenAuthFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LoginState"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dbRole"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProfileScreenUserFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; -export const EventsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Events"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"earliestTimestamp"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"LuxonDateTime"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"lastTimestamp"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"LuxonDateTime"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"events"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"dateFilters"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"comparison"},"value":{"kind":"EnumValue","value":"GREATER_THAN_OR_EQUAL_TO"}},{"kind":"ObjectField","name":{"kind":"Name","value":"field"},"value":{"kind":"EnumValue","value":"occurrenceStart"}},{"kind":"ObjectField","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"earliestTimestamp"}}}]},{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"comparison"},"value":{"kind":"EnumValue","value":"LESS_THAN_OR_EQUAL_TO"}},{"kind":"ObjectField","name":{"kind":"Name","value":"field"},"value":{"kind":"EnumValue","value":"occurrenceStart"}},{"kind":"ObjectField","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"lastTimestamp"}}}]}]}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"EnumValue","value":"ASCENDING"}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"StringValue","value":"occurrence","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"EventScreenFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventScreenFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"summary"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"interval"}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"mimeType"}}]}}]}}]} as unknown as DocumentNode; -export const ServerFeedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ServerFeed"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"feed"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"20"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"textContent"}},{"kind":"Field","name":{"kind":"Name","value":"image"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}}]}}]}}]}}]} as unknown as DocumentNode; -export const MarathonScreenDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MarathonScreen"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"currentMarathonHour"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"HourScreenFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"nextMarathon"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}},{"kind":"Field","name":{"kind":"Name","value":"hours"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"HourScreenFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ImageViewFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ImageResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"mimeType"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"HourScreenFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarathonHourResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"details"}},{"kind":"Field","name":{"kind":"Name","value":"durationInfo"}},{"kind":"Field","name":{"kind":"Name","value":"mapImages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ImageViewFragment"}}]}}]}}]} as unknown as DocumentNode; -export const ScoreBoardDocumentDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ScoreBoardDocument"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"type"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"TeamType"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"HighlightedTeamFragment"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MyTeamFragment"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"sendAll"},"value":{"kind":"BooleanValue","value":true}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"ListValue","values":[{"kind":"StringValue","value":"totalPoints","block":false},{"kind":"StringValue","value":"name","block":false}]}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"ListValue","values":[{"kind":"EnumValue","value":"DESCENDING"},{"kind":"EnumValue","value":"ASCENDING"}]}},{"kind":"Argument","name":{"kind":"Name","value":"type"},"value":{"kind":"Variable","name":{"kind":"Name","value":"type"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ScoreBoardFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"HighlightedTeamFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"type"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MyTeamFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"totalPoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointEntries"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personFrom"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}},{"kind":"Field","name":{"kind":"Name","value":"points"}}]}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"person"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ScoreBoardFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"totalPoints"}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"type"}}]}}]} as unknown as DocumentNode; -export const ActiveMarathonDocumentDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ActiveMarathonDocument"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"currentMarathon"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file +export const DeviceNotificationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"DeviceNotifications"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"deviceUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"verifier"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"device"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"deviceUuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notificationDeliveries"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"verifier"},"value":{"kind":"Variable","name":{"kind":"Name","value":"verifier"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationDeliveryFragment"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationDeliveryFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationDeliveryNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"sentAt"}},{"kind":"Field","name":{"kind":"Name","value":"notification"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationFragment"}}]}}]}}]} as unknown as DocumentNode; +export const RootScreenDocumentDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"RootScreenDocument"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"loginState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProfileScreenAuthFragment"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"RootScreenAuthFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProfileScreenUserFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProfileScreenAuthFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LoginState"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dbRole"}},{"kind":"Field","name":{"kind":"Name","value":"authSource"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RootScreenAuthFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LoginState"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dbRole"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProfileScreenUserFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"primaryCommittee"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]} as unknown as DocumentNode; +export const EventsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Events"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"earliestTimestamp"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DateTimeISO"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"lastTimestamp"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DateTimeISO"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"events"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"dateFilters"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"comparison"},"value":{"kind":"EnumValue","value":"GREATER_THAN_OR_EQUAL_TO"}},{"kind":"ObjectField","name":{"kind":"Name","value":"field"},"value":{"kind":"EnumValue","value":"occurrenceStart"}},{"kind":"ObjectField","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"earliestTimestamp"}}}]},{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"comparison"},"value":{"kind":"EnumValue","value":"LESS_THAN_OR_EQUAL_TO"}},{"kind":"ObjectField","name":{"kind":"Name","value":"field"},"value":{"kind":"EnumValue","value":"occurrenceStart"}},{"kind":"ObjectField","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"lastTimestamp"}}}]}]}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"EnumValue","value":"asc"}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"StringValue","value":"occurrence","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"EventScreenFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventScreenFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"summary"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"interval"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"end"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"mimeType"}}]}}]}}]} as unknown as DocumentNode; +export const ServerFeedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ServerFeed"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"feed"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"20"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"textContent"}},{"kind":"Field","name":{"kind":"Name","value":"image"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}}]}}]}}]}}]} as unknown as DocumentNode; +export const MarathonScreenDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MarathonScreen"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"currentMarathonHour"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"HourScreenFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"latestMarathon"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}},{"kind":"Field","name":{"kind":"Name","value":"hours"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"HourScreenFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ImageViewFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ImageNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"mimeType"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"HourScreenFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarathonHourNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"details"}},{"kind":"Field","name":{"kind":"Name","value":"durationInfo"}},{"kind":"Field","name":{"kind":"Name","value":"mapImages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ImageViewFragment"}}]}}]}}]} as unknown as DocumentNode; +export const ScoreBoardDocumentDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ScoreBoardDocument"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"type"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"TeamType"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"HighlightedTeamFragment"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MyTeamFragment"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"sendAll"},"value":{"kind":"BooleanValue","value":true}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"ListValue","values":[{"kind":"StringValue","value":"totalPoints","block":false},{"kind":"StringValue","value":"name","block":false}]}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"ListValue","values":[{"kind":"EnumValue","value":"desc"},{"kind":"EnumValue","value":"asc"}]}},{"kind":"Argument","name":{"kind":"Name","value":"type"},"value":{"kind":"Variable","name":{"kind":"Name","value":"type"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ScoreBoardFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"HighlightedTeamFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"type"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MyTeamFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"totalPoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointEntries"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personFrom"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}},{"kind":"Field","name":{"kind":"Name","value":"points"}}]}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"person"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ScoreBoardFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"totalPoints"}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"type"}}]}}]} as unknown as DocumentNode; +export const ActiveMarathonDocumentDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ActiveMarathonDocument"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"currentMarathon"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/packages/common/lib/graphql-client-admin/index.ts b/packages/common/lib/graphql-client-mobile/index.ts similarity index 100% rename from packages/common/lib/graphql-client-admin/index.ts rename to packages/common/lib/graphql-client-mobile/index.ts diff --git a/packages/common/lib/graphql-client-public/fragment-masking.ts b/packages/common/lib/graphql-client-portal/fragment-masking.ts similarity index 100% rename from packages/common/lib/graphql-client-public/fragment-masking.ts rename to packages/common/lib/graphql-client-portal/fragment-masking.ts diff --git a/packages/common/lib/graphql-client-admin/gql.ts b/packages/common/lib/graphql-client-portal/gql.ts similarity index 50% rename from packages/common/lib/graphql-client-admin/gql.ts rename to packages/common/lib/graphql-client-portal/gql.ts index f3260128..3edfdcde 100644 --- a/packages/common/lib/graphql-client-admin/gql.ts +++ b/packages/common/lib/graphql-client-portal/gql.ts @@ -13,78 +13,81 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document- * Therefore it is highly recommended to use the babel or swc plugin for production. */ const documents = { - "\n query ImagePicker($stringFilters: [ImageResolverKeyedStringFilterItem!]) {\n images(stringFilters: $stringFilters, pageSize: 9) {\n data {\n uuid\n alt\n url\n }\n }\n }\n": types.ImagePickerDocument, - "\n query PersonSearch($search: String!) {\n searchPeopleByName(name: $search) {\n data {\n uuid\n name\n linkblue\n }\n }\n personByLinkBlue(linkBlueId: $search) {\n data {\n uuid\n name\n linkblue\n }\n }\n }\n": types.PersonSearchDocument, - "\n fragment SingleNotificationFragment on NotificationResource {\n uuid\n title\n body\n deliveryIssue\n deliveryIssueAcknowledgedAt\n sendAt\n startedSendingAt\n createdAt\n deliveryCount\n deliveryIssueCount {\n DeviceNotRegistered\n InvalidCredentials\n MessageRateExceeded\n MessageTooBig\n MismatchSenderId\n Unknown\n }\n }\n": types.SingleNotificationFragmentFragmentDoc, + "\n query ActiveMarathon {\n latestMarathon {\n id\n year\n startDate\n endDate\n }\n marathons(sendAll: true) {\n data {\n id\n year\n }\n }\n }\n": types.ActiveMarathonDocument, + "\n query SelectedMarathon($marathonId: GlobalId!) {\n marathon(uuid: $marathonId) {\n id\n year\n startDate\n endDate\n }\n }\n": types.SelectedMarathonDocument, + "\n query ImagePicker($stringFilters: [ImageResolverKeyedStringFilterItem!]) {\n images(stringFilters: $stringFilters, pageSize: 9) {\n data {\n id\n alt\n url\n }\n }\n }\n": types.ImagePickerDocument, + "\n query PersonSearch($search: String!) {\n searchPeopleByName(name: $search) {\n id\n name\n linkblue\n }\n personByLinkBlue(linkBlueId: $search) {\n id\n name\n linkblue\n }\n }\n": types.PersonSearchDocument, + "\n fragment SingleNotificationFragment on NotificationNode {\n id\n title\n body\n deliveryIssue\n deliveryIssueAcknowledgedAt\n sendAt\n startedSendingAt\n createdAt\n deliveryCount\n deliveryIssueCount {\n DeviceNotRegistered\n InvalidCredentials\n MessageRateExceeded\n MessageTooBig\n MismatchSenderId\n Unknown\n }\n }\n": types.SingleNotificationFragmentFragmentDoc, "\n mutation CreateNotification(\n $title: String!\n $body: String!\n $audience: NotificationAudienceInput!\n $url: String\n ) {\n stageNotification(\n title: $title\n body: $body\n audience: $audience\n url: $url\n ) {\n uuid\n }\n }\n": types.CreateNotificationDocument, - "\n mutation CancelNotificationSchedule($uuid: String!) {\n abortScheduledNotification(uuid: $uuid) {\n ok\n }\n }\n": types.CancelNotificationScheduleDocument, - "\n mutation DeleteNotification($uuid: String!, $force: Boolean) {\n deleteNotification(uuid: $uuid, force: $force) {\n ok\n }\n }\n": types.DeleteNotificationDocument, - "\n mutation SendNotification($uuid: String!) {\n sendNotification(uuid: $uuid) {\n ok\n }\n }\n": types.SendNotificationDocument, - "\n mutation ScheduleNotification($uuid: String!, $sendAt: DateTimeISO!) {\n scheduleNotification(uuid: $uuid, sendAt: $sendAt) {\n ok\n }\n }\n": types.ScheduleNotificationDocument, - "\n fragment TeamNameFragment on TeamResource {\n uuid\n name\n }\n": types.TeamNameFragmentFragmentDoc, - "\n mutation PersonCreator($input: CreatePersonInput!) {\n createPerson(input: $input) {\n ok\n uuid\n }\n }\n": types.PersonCreatorDocument, - "\n fragment PersonEditorFragment on PersonResource {\n uuid\n name\n linkblue\n email\n role {\n committeeRole\n committeeIdentifier\n }\n teams {\n position\n team {\n uuid\n name\n }\n }\n }\n": types.PersonEditorFragmentFragmentDoc, - "\n mutation PersonEditor($uuid: String!, $input: SetPersonInput!) {\n setPerson(uuid: $uuid, input: $input) {\n ok\n }\n }\n": types.PersonEditorDocument, - "\n mutation CreatePointEntry($input: CreatePointEntryInput!) {\n createPointEntry(input: $input) {\n data {\n uuid\n }\n }\n }\n": types.CreatePointEntryDocument, - "\n query GetPersonByUuid($uuid: String!) {\n person(uuid: $uuid) {\n data {\n uuid\n name\n linkblue\n }\n }\n }\n": types.GetPersonByUuidDocument, - "\n query GetPersonByLinkBlue($linkBlue: String!) {\n personByLinkBlue(linkBlueId: $linkBlue) {\n data {\n uuid\n name\n }\n }\n }\n": types.GetPersonByLinkBlueDocument, - "\n query SearchPersonByName($name: String!) {\n searchPeopleByName(name: $name) {\n data {\n uuid\n name\n }\n }\n }\n": types.SearchPersonByNameDocument, - "\n mutation CreatePersonByLinkBlue(\n $linkBlue: String!\n $email: EmailAddress!\n $teamUuid: String!\n ) {\n createPerson(\n input: { email: $email, linkblue: $linkBlue, memberOf: [$teamUuid] }\n ) {\n uuid\n }\n }\n": types.CreatePersonByLinkBlueDocument, - "\n query PointEntryOpportunityLookup($name: String!) {\n pointOpportunities(\n stringFilters: { field: name, comparison: SUBSTRING, value: $name }\n sendAll: true\n ) {\n data {\n name\n uuid\n }\n }\n }\n": types.PointEntryOpportunityLookupDocument, + "\n mutation CancelNotificationSchedule($uuid: GlobalId!) {\n abortScheduledNotification(uuid: $uuid) {\n ok\n }\n }\n": types.CancelNotificationScheduleDocument, + "\n mutation DeleteNotification($uuid: GlobalId!, $force: Boolean) {\n deleteNotification(uuid: $uuid, force: $force) {\n ok\n }\n }\n": types.DeleteNotificationDocument, + "\n mutation SendNotification($uuid: GlobalId!) {\n sendNotification(uuid: $uuid) {\n ok\n }\n }\n": types.SendNotificationDocument, + "\n mutation ScheduleNotification($uuid: GlobalId!, $sendAt: DateTimeISO!) {\n scheduleNotification(uuid: $uuid, sendAt: $sendAt) {\n ok\n }\n }\n": types.ScheduleNotificationDocument, + "\n fragment TeamNameFragment on TeamNode {\n id\n name\n }\n": types.TeamNameFragmentFragmentDoc, + "\n mutation PersonCreator($input: CreatePersonInput!) {\n createPerson(input: $input) {\n id\n }\n }\n": types.PersonCreatorDocument, + "\n fragment PersonEditorFragment on PersonNode {\n id\n name\n linkblue\n email\n teams {\n position\n team {\n id\n name\n }\n }\n }\n": types.PersonEditorFragmentFragmentDoc, + "\n mutation PersonEditor($uuid: GlobalId!, $input: SetPersonInput!) {\n setPerson(uuid: $uuid, input: $input) {\n id\n }\n }\n": types.PersonEditorDocument, + "\n mutation CreatePointEntry($input: CreatePointEntryInput!) {\n createPointEntry(input: $input) {\n data {\n id\n }\n }\n }\n": types.CreatePointEntryDocument, + "\n query GetPersonByUuid($uuid: GlobalId!) {\n person(uuid: $uuid) {\n id\n name\n linkblue\n }\n }\n": types.GetPersonByUuidDocument, + "\n query GetPersonByLinkBlue($linkBlue: String!) {\n personByLinkBlue(linkBlueId: $linkBlue) {\n id\n name\n }\n }\n": types.GetPersonByLinkBlueDocument, + "\n query SearchPersonByName($name: String!) {\n searchPeopleByName(name: $name) {\n id\n name\n }\n }\n": types.SearchPersonByNameDocument, + "\n mutation CreatePersonByLinkBlue(\n $linkBlue: String!\n $email: EmailAddress!\n $teamUuid: String!\n ) {\n createPerson(\n input: { email: $email, linkblue: $linkBlue, memberOf: [$teamUuid] }\n ) {\n id\n }\n }\n": types.CreatePersonByLinkBlueDocument, + "\n query PointEntryOpportunityLookup($name: String!) {\n pointOpportunities(\n stringFilters: { field: name, comparison: SUBSTRING, value: $name }\n sendAll: true\n ) {\n data {\n name\n id\n }\n }\n }\n": types.PointEntryOpportunityLookupDocument, "\n mutation CreatePointOpportunity($input: CreatePointOpportunityInput!) {\n createPointOpportunity(input: $input) {\n uuid\n }\n }\n": types.CreatePointOpportunityDocument, - "\n mutation TeamCreator($input: CreateTeamInput!) {\n createTeam(input: $input) {\n ok\n uuid\n }\n }\n": types.TeamCreatorDocument, - "\n fragment TeamEditorFragment on TeamResource {\n uuid\n name\n marathonYear\n legacyStatus\n persistentIdentifier\n type\n }\n": types.TeamEditorFragmentFragmentDoc, - "\n mutation TeamEditor($uuid: String!, $input: SetTeamInput!) {\n setTeam(uuid: $uuid, input: $input) {\n ok\n }\n }\n": types.TeamEditorDocument, - "\n fragment PeopleTableFragment on PersonResource {\n uuid\n name\n linkblue\n email\n role {\n dbRole\n committeeRole\n committeeIdentifier\n }\n }\n": types.PeopleTableFragmentFragmentDoc, + "\n mutation TeamCreator($input: CreateTeamInput!, $marathonUuid: String!) {\n createTeam(input: $input, marathon: $marathonUuid) {\n ok\n uuid\n }\n }\n": types.TeamCreatorDocument, + "\n fragment TeamEditorFragment on TeamNode {\n id\n name\n marathon {\n id\n year\n }\n legacyStatus\n type\n }\n": types.TeamEditorFragmentFragmentDoc, + "\n mutation TeamEditor($uuid: GlobalId!, $input: SetTeamInput!) {\n setTeam(uuid: $uuid, input: $input) {\n ok\n }\n }\n": types.TeamEditorDocument, + "\n fragment PeopleTableFragment on PersonNode {\n id\n name\n linkblue\n email\n dbRole\n primaryCommittee {\n identifier\n role\n }\n }\n": types.PeopleTableFragmentFragmentDoc, "\n query PeopleTable(\n $page: Int\n $pageSize: Int\n $sortBy: [String!]\n $sortDirection: [SortDirection!]\n $isNullFilters: [PersonResolverKeyedIsNullFilterItem!]\n $oneOfFilters: [PersonResolverKeyedOneOfFilterItem!]\n $stringFilters: [PersonResolverKeyedStringFilterItem!]\n ) {\n listPeople(\n page: $page\n pageSize: $pageSize\n sortBy: $sortBy\n sortDirection: $sortDirection\n isNullFilters: $isNullFilters\n oneOfFilters: $oneOfFilters\n stringFilters: $stringFilters\n ) {\n page\n pageSize\n total\n data {\n ...PeopleTableFragment\n }\n }\n }\n": types.PeopleTableDocument, "\n query TeamsTable(\n $page: Int\n $pageSize: Int\n $sortBy: [String!]\n $sortDirection: [SortDirection!]\n $isNullFilters: [TeamResolverKeyedIsNullFilterItem!]\n $oneOfFilters: [TeamResolverKeyedOneOfFilterItem!]\n $stringFilters: [TeamResolverKeyedStringFilterItem!]\n ) {\n teams(\n page: $page\n pageSize: $pageSize\n sortBy: $sortBy\n sortDirection: $sortDirection\n isNullFilters: $isNullFilters\n oneOfFilters: $oneOfFilters\n stringFilters: $stringFilters\n ) {\n page\n pageSize\n total\n data {\n ...TeamsTableFragment\n }\n }\n }\n": types.TeamsTableDocument, - "\n fragment TeamsTableFragment on TeamResource {\n uuid\n type\n name\n legacyStatus\n marathonYear\n totalPoints\n }\n": types.TeamsTableFragmentFragmentDoc, - "\n fragment NotificationDeliveriesTableFragment on NotificationDeliveryResource {\n uuid\n deliveryError\n receiptCheckedAt\n sentAt\n }\n": types.NotificationDeliveriesTableFragmentFragmentDoc, + "\n fragment TeamsTableFragment on TeamNode {\n id\n type\n name\n legacyStatus\n totalPoints\n }\n": types.TeamsTableFragmentFragmentDoc, + "\n fragment NotificationDeliveriesTableFragment on NotificationDeliveryNode {\n id\n deliveryError\n receiptCheckedAt\n sentAt\n }\n": types.NotificationDeliveriesTableFragmentFragmentDoc, "\n query NotificationDeliveriesTableQuery(\n $notificationId: String!\n $page: Int\n $pageSize: Int\n $sortBy: [String!]\n $sortDirection: [SortDirection!]\n $dateFilters: [NotificationDeliveryResolverKeyedDateFilterItem!]\n $isNullFilters: [NotificationDeliveryResolverKeyedIsNullFilterItem!]\n ) {\n notificationDeliveries(\n notificationUuid: $notificationId\n page: $page\n pageSize: $pageSize\n sortBy: $sortBy\n sortDirection: $sortDirection\n dateFilters: $dateFilters\n isNullFilters: $isNullFilters\n ) {\n page\n pageSize\n total\n data {\n ...NotificationDeliveriesTableFragment\n }\n }\n }\n": types.NotificationDeliveriesTableQueryDocument, - "\n fragment NotificationsTableFragment on NotificationResource {\n uuid\n title\n body\n deliveryIssue\n deliveryIssueAcknowledgedAt\n sendAt\n startedSendingAt\n }\n": types.NotificationsTableFragmentFragmentDoc, + "\n fragment NotificationsTableFragment on NotificationNode {\n id\n title\n body\n deliveryIssue\n deliveryIssueAcknowledgedAt\n sendAt\n startedSendingAt\n }\n": types.NotificationsTableFragmentFragmentDoc, "\n query NotificationsTableQuery(\n $page: Int\n $pageSize: Int\n $sortBy: [String!]\n $sortDirection: [SortDirection!]\n $dateFilters: [NotificationResolverKeyedDateFilterItem!]\n $isNullFilters: [NotificationResolverKeyedIsNullFilterItem!]\n $oneOfFilters: [NotificationResolverKeyedOneOfFilterItem!]\n $stringFilters: [NotificationResolverKeyedStringFilterItem!]\n ) {\n notifications(\n page: $page\n pageSize: $pageSize\n sortBy: $sortBy\n sortDirection: $sortDirection\n dateFilters: $dateFilters\n isNullFilters: $isNullFilters\n oneOfFilters: $oneOfFilters\n stringFilters: $stringFilters\n ) {\n page\n pageSize\n total\n data {\n ...NotificationsTableFragment\n }\n }\n }\n": types.NotificationsTableQueryDocument, - "\n mutation DeletePointEntry($uuid: String!) {\n deletePointEntry(uuid: $uuid) {\n ok\n }\n }\n": types.DeletePointEntryDocument, - "\n fragment PointEntryTableFragment on PointEntryResource {\n uuid\n personFrom {\n name\n linkblue\n }\n points\n pointOpportunity {\n name\n opportunityDate\n }\n comment\n }\n": types.PointEntryTableFragmentFragmentDoc, - "\n mutation DeletePerson($uuid: String!) {\n deletePerson(uuid: $uuid) {\n ok\n }\n }\n": types.DeletePersonDocument, - "\n fragment PersonViewerFragment on PersonResource {\n uuid\n name\n linkblue\n email\n role {\n dbRole\n committeeRole\n committeeIdentifier\n }\n teams {\n position\n team {\n uuid\n name\n }\n }\n }\n": types.PersonViewerFragmentFragmentDoc, - "\n mutation DeleteTeam($uuid: String!) {\n deleteTeam(uuid: $uuid) {\n ok\n }\n }\n": types.DeleteTeamDocument, - "\n fragment TeamViewerFragment on TeamResource {\n uuid\n name\n marathonYear\n legacyStatus\n totalPoints\n type\n members {\n person {\n uuid\n name\n linkblue\n }\n }\n captains {\n person {\n uuid\n name\n linkblue\n }\n }\n }\n": types.TeamViewerFragmentFragmentDoc, - "\n query LoginState {\n loginState {\n loggedIn\n role {\n dbRole\n committeeRole\n committeeIdentifier\n }\n }\n }\n": types.LoginStateDocument, + "\n mutation DeletePointEntry($uuid: GlobalId!) {\n deletePointEntry(uuid: $uuid) {\n ok\n }\n }\n": types.DeletePointEntryDocument, + "\n fragment PointEntryTableFragment on PointEntryNode {\n id\n personFrom {\n name\n linkblue\n }\n points\n pointOpportunity {\n name\n opportunityDate\n }\n comment\n }\n": types.PointEntryTableFragmentFragmentDoc, + "\n mutation DeletePerson($uuid: GlobalId!) {\n deletePerson(uuid: $uuid) {\n id\n }\n }\n": types.DeletePersonDocument, + "\n fragment PersonViewerFragment on PersonNode {\n id\n name\n linkblue\n email\n dbRole\n teams {\n position\n team {\n id\n name\n }\n }\n committees {\n identifier\n role\n }\n }\n": types.PersonViewerFragmentFragmentDoc, + "\n mutation DeleteTeam($uuid: GlobalId!) {\n deleteTeam(uuid: $uuid) {\n ok\n }\n }\n": types.DeleteTeamDocument, + "\n fragment TeamViewerFragment on TeamNode {\n id\n name\n marathon {\n id\n year\n }\n legacyStatus\n totalPoints\n type\n members {\n person {\n id\n name\n linkblue\n }\n position\n }\n }\n": types.TeamViewerFragmentFragmentDoc, + "\n query LoginState {\n loginState {\n loggedIn\n dbRole\n effectiveCommitteeRoles {\n role\n identifier\n }\n }\n }\n": types.LoginStateDocument, "\n mutation CommitConfigChanges($changes: [CreateConfigurationInput!]!) {\n createConfigurations(input: $changes) {\n ok\n }\n }\n": types.CommitConfigChangesDocument, - "\n fragment ConfigFragment on ConfigurationResource {\n uuid\n key\n value\n validAfter\n validUntil\n createdAt\n }\n": types.ConfigFragmentFragmentDoc, + "\n fragment ConfigFragment on ConfigurationNode {\n id\n key\n value\n validAfter\n validUntil\n createdAt\n }\n": types.ConfigFragmentFragmentDoc, "\n query ConfigQuery {\n allConfigurations {\n data {\n ...ConfigFragment\n }\n }\n }\n ": types.ConfigQueryDocument, - "\n mutation CreateEvent($input: CreateEventInput!) {\n createEvent(input: $input) {\n data {\n uuid\n }\n }\n }\n": types.CreateEventDocument, - "\n fragment EventsTableFragment on EventResource {\n uuid\n title\n description\n occurrences {\n uuid\n interval\n fullDay\n }\n summary\n }\n": types.EventsTableFragmentFragmentDoc, + "\n mutation CreateEvent($input: CreateEventInput!) {\n createEvent(input: $input) {\n data {\n id\n }\n }\n }\n": types.CreateEventDocument, + "\n fragment EventsTableFragment on EventNode {\n id\n title\n description\n occurrences {\n id\n interval {\n start\n end\n }\n fullDay\n }\n summary\n }\n": types.EventsTableFragmentFragmentDoc, "\n query EventsTable(\n $page: Int\n $pageSize: Int\n $sortBy: [String!]\n $sortDirection: [SortDirection!]\n $dateFilters: [EventResolverKeyedDateFilterItem!]\n $isNullFilters: [EventResolverKeyedIsNullFilterItem!]\n $oneOfFilters: [EventResolverKeyedOneOfFilterItem!]\n $stringFilters: [EventResolverKeyedStringFilterItem!]\n ) {\n events(\n page: $page\n pageSize: $pageSize\n sortBy: $sortBy\n sortDirection: $sortDirection\n dateFilters: $dateFilters\n isNullFilters: $isNullFilters\n oneOfFilters: $oneOfFilters\n stringFilters: $stringFilters\n ) {\n page\n pageSize\n total\n data {\n ...EventsTableFragment\n }\n }\n }\n": types.EventsTableDocument, - "\n query EditEventPage($uuid: String!) {\n event(uuid: $uuid) {\n data {\n ...EventEditorFragment\n }\n }\n }\n": types.EditEventPageDocument, - "\n fragment EventEditorFragment on EventResource {\n uuid\n title\n summary\n description\n location\n occurrences {\n uuid\n interval\n fullDay\n }\n images {\n url\n width\n height\n thumbHash\n alt\n }\n }\n": types.EventEditorFragmentFragmentDoc, - "\n mutation SaveEvent($uuid: String!, $input: SetEventInput!) {\n setEvent(uuid: $uuid, input: $input) {\n data {\n ...EventEditorFragment\n }\n }\n }\n": types.SaveEventDocument, - "\n mutation DeleteEvent($uuid: String!) {\n deleteEvent(uuid: $uuid) {\n ok\n }\n }\n": types.DeleteEventDocument, - "\n fragment EventViewerFragment on EventResource {\n uuid\n title\n summary\n description\n location\n occurrences {\n interval\n fullDay\n }\n images {\n url\n width\n height\n thumbHash\n alt\n }\n createdAt\n updatedAt\n }\n": types.EventViewerFragmentFragmentDoc, - "\n query ViewEventPage($uuid: String!) {\n event(uuid: $uuid) {\n data {\n ...EventViewerFragment\n }\n }\n }\n": types.ViewEventPageDocument, - "\n query FeedPage {\n feed(limit: null) {\n uuid\n title\n createdAt\n textContent\n image {\n url\n alt\n }\n }\n }\n": types.FeedPageDocument, - "\n mutation CreateFeedItem($input: CreateFeedInput!) {\n createFeedItem(input: $input) {\n uuid\n }\n }\n": types.CreateFeedItemDocument, + "\n query EditEventPage($uuid: GlobalId!) {\n event(uuid: $uuid) {\n data {\n ...EventEditorFragment\n }\n }\n }\n": types.EditEventPageDocument, + "\n fragment EventEditorFragment on EventNode {\n id\n title\n summary\n description\n location\n occurrences {\n id\n interval {\n start\n end\n }\n fullDay\n }\n images {\n url\n width\n height\n thumbHash\n alt\n }\n }\n": types.EventEditorFragmentFragmentDoc, + "\n mutation SaveEvent($uuid: GlobalId!, $input: SetEventInput!) {\n setEvent(uuid: $uuid, input: $input) {\n data {\n ...EventEditorFragment\n }\n }\n }\n": types.SaveEventDocument, + "\n mutation DeleteEvent($uuid: GlobalId!) {\n deleteEvent(uuid: $uuid) {\n ok\n }\n }\n": types.DeleteEventDocument, + "\n fragment EventViewerFragment on EventNode {\n id\n title\n summary\n description\n location\n occurrences {\n interval {\n start\n end\n }\n fullDay\n }\n images {\n url\n width\n height\n thumbHash\n alt\n }\n createdAt\n updatedAt\n }\n": types.EventViewerFragmentFragmentDoc, + "\n query ViewEventPage($uuid: GlobalId!) {\n event(uuid: $uuid) {\n data {\n ...EventViewerFragment\n }\n }\n }\n": types.ViewEventPageDocument, + "\n query FeedPage {\n feed(limit: null) {\n id\n title\n createdAt\n textContent\n image {\n url\n alt\n }\n }\n }\n": types.FeedPageDocument, + "\n mutation CreateFeedItem($input: CreateFeedInput!) {\n createFeedItem(input: $input) {\n id\n }\n }\n": types.CreateFeedItemDocument, "\n mutation DeleteFeedItem($uuid: String!) {\n deleteFeedItem(feedItemUuid: $uuid)\n }\n": types.DeleteFeedItemDocument, - "\n mutation CreateImage($input: CreateImageInput!) {\n createImage(input: $input) {\n uuid\n }\n }\n": types.CreateImageDocument, - "\n fragment ImagesTableFragment on ImageResource {\n uuid\n url\n thumbHash\n height\n width\n alt\n mimeType\n createdAt\n }\n": types.ImagesTableFragmentFragmentDoc, + "\n mutation CreateImage($input: CreateImageInput!) {\n createImage(input: $input) {\n id\n }\n }\n": types.CreateImageDocument, + "\n fragment ImagesTableFragment on ImageNode {\n id\n url\n thumbHash\n height\n width\n alt\n mimeType\n createdAt\n }\n": types.ImagesTableFragmentFragmentDoc, "\n query ImagesTable(\n $page: Int\n $pageSize: Int\n $sortBy: [String!]\n $sortDirection: [SortDirection!]\n $dateFilters: [ImageResolverKeyedDateFilterItem!]\n $isNullFilters: [ImageResolverKeyedIsNullFilterItem!]\n $oneOfFilters: [ImageResolverKeyedOneOfFilterItem!]\n $stringFilters: [ImageResolverKeyedStringFilterItem!]\n $numericFilters: [ImageResolverKeyedNumericFilterItem!]\n ) {\n images(\n page: $page\n pageSize: $pageSize\n sortBy: $sortBy\n sortDirection: $sortDirection\n dateFilters: $dateFilters\n isNullFilters: $isNullFilters\n oneOfFilters: $oneOfFilters\n stringFilters: $stringFilters\n numericFilters: $numericFilters\n ) {\n page\n pageSize\n total\n data {\n ...ImagesTableFragment\n }\n }\n }\n": types.ImagesTableDocument, - "\n mutation CreateMarathon($input: CreateMarathonInput!) {\n createMarathon(input: $input) {\n uuid\n }\n }\n ": types.CreateMarathonDocument, - "\n query MarathonOverviewPage {\n nextMarathon {\n ...MarathonViewerFragment\n }\n marathons(sendAll: true) {\n data {\n ...MarathonTableFragment\n }\n }\n }\n": types.MarathonOverviewPageDocument, - "\n fragment MarathonTableFragment on MarathonResource {\n uuid\n year\n startDate\n endDate\n }\n": types.MarathonTableFragmentFragmentDoc, - "\n mutation EditMarathon($input: SetMarathonInput!, $marathonId: String!) {\n setMarathon(input: $input, uuid: $marathonId) {\n uuid\n }\n }\n ": types.EditMarathonDocument, - "\n query GetMarathon($marathonId: String!) {\n marathon(uuid: $marathonId) {\n year\n startDate\n endDate\n }\n }\n ": types.GetMarathonDocument, - "\n fragment MarathonViewerFragment on MarathonResource {\n uuid\n year\n startDate\n endDate\n hours {\n uuid\n shownStartingAt\n title\n }\n }\n": types.MarathonViewerFragmentFragmentDoc, - "\n query MarathonPage($marathonUuid: String!) {\n marathon(uuid: $marathonUuid) {\n ...MarathonViewerFragment\n }\n }\n": types.MarathonPageDocument, - "\n mutation AddMarathonHour(\n $input: CreateMarathonHourInput!\n $marathonUuid: String!\n ) {\n createMarathonHour(input: $input, marathonUuid: $marathonUuid) {\n uuid\n }\n }\n ": types.AddMarathonHourDocument, - "\n query EditMarathonHourData($marathonHourUuid: String!) {\n marathonHour(uuid: $marathonHourUuid) {\n details\n durationInfo\n shownStartingAt\n title\n }\n }\n": types.EditMarathonHourDataDocument, - "\n mutation EditMarathonHour($input: SetMarathonHourInput!, $uuid: String!) {\n setMarathonHour(input: $input, uuid: $uuid) {\n uuid\n }\n }\n": types.EditMarathonHourDocument, - "\n query NotificationManager($uuid: String!) {\n notification(uuid: $uuid) {\n data {\n ...SingleNotificationFragment\n }\n }\n }\n": types.NotificationManagerDocument, - "\n query NotificationViewer($uuid: String!) {\n notification(uuid: $uuid) {\n data {\n ...SingleNotificationFragment\n }\n }\n }\n": types.NotificationViewerDocument, - "\n query CreatePersonPage {\n teams(sendAll: true, sortBy: [\"name\"], sortDirection: [ASCENDING]) {\n data {\n ...TeamNameFragment\n }\n }\n }\n": types.CreatePersonPageDocument, - "\n query EditPersonPage($uuid: String!) {\n person(uuid: $uuid) {\n data {\n ...PersonEditorFragment\n }\n }\n teams(sendAll: true, sortBy: [\"name\"], sortDirection: [ASCENDING]) {\n data {\n ...TeamNameFragment\n }\n }\n }\n": types.EditPersonPageDocument, - "\n query ViewPersonPage($uuid: String!) {\n person(uuid: $uuid) {\n data {\n ...PersonViewerFragment\n }\n }\n }\n": types.ViewPersonPageDocument, - "\n query EditTeamPage($uuid: String!) {\n team(uuid: $uuid) {\n data {\n ...TeamEditorFragment\n }\n }\n }\n": types.EditTeamPageDocument, - "\n query ViewTeamPage($teamUuid: String!) {\n team(uuid: $teamUuid) {\n data {\n ...TeamViewerFragment\n pointEntries {\n ...PointEntryTableFragment\n }\n }\n }\n }\n": types.ViewTeamPageDocument, + "\n mutation CreateMarathon($input: CreateMarathonInput!) {\n createMarathon(input: $input) {\n id\n }\n }\n ": types.CreateMarathonDocument, + "\n query MarathonOverviewPage {\n latestMarathon {\n ...MarathonViewerFragment\n }\n marathons(sendAll: true) {\n data {\n ...MarathonTableFragment\n }\n }\n }\n": types.MarathonOverviewPageDocument, + "\n fragment MarathonTableFragment on MarathonNode {\n id\n year\n startDate\n endDate\n }\n": types.MarathonTableFragmentFragmentDoc, + "\n mutation EditMarathon(\n $input: SetMarathonInput!\n $marathonId: GlobalId!\n ) {\n setMarathon(input: $input, uuid: $marathonId) {\n id\n }\n }\n ": types.EditMarathonDocument, + "\n query GetMarathon($marathonId: GlobalId!) {\n marathon(uuid: $marathonId) {\n year\n startDate\n endDate\n }\n }\n ": types.GetMarathonDocument, + "\n fragment MarathonViewerFragment on MarathonNode {\n id\n year\n startDate\n endDate\n hours {\n id\n shownStartingAt\n title\n }\n }\n": types.MarathonViewerFragmentFragmentDoc, + "\n query MarathonPage($marathonUuid: GlobalId!) {\n marathon(uuid: $marathonUuid) {\n ...MarathonViewerFragment\n }\n }\n": types.MarathonPageDocument, + "\n mutation AddMarathonHour(\n $input: CreateMarathonHourInput!\n $marathonUuid: String!\n ) {\n createMarathonHour(input: $input, marathonUuid: $marathonUuid) {\n id\n }\n }\n ": types.AddMarathonHourDocument, + "\n query EditMarathonHourData($marathonHourUuid: GlobalId!) {\n marathonHour(uuid: $marathonHourUuid) {\n details\n durationInfo\n shownStartingAt\n title\n }\n }\n": types.EditMarathonHourDataDocument, + "\n mutation EditMarathonHour($input: SetMarathonHourInput!, $uuid: GlobalId!) {\n setMarathonHour(input: $input, uuid: $uuid) {\n id\n }\n }\n": types.EditMarathonHourDocument, + "\n query NotificationManager($uuid: GlobalId!) {\n notification(uuid: $uuid) {\n data {\n ...SingleNotificationFragment\n }\n }\n }\n": types.NotificationManagerDocument, + "\n query NotificationViewer($uuid: GlobalId!) {\n notification(uuid: $uuid) {\n data {\n ...SingleNotificationFragment\n }\n }\n }\n": types.NotificationViewerDocument, + "\n query CreatePersonPage {\n teams(sendAll: true, sortBy: [\"name\"], sortDirection: [asc]) {\n data {\n ...TeamNameFragment\n }\n }\n }\n": types.CreatePersonPageDocument, + "\n query EditPersonPage($uuid: GlobalId!) {\n person(uuid: $uuid) {\n ...PersonEditorFragment\n }\n teams(sendAll: true, sortBy: [\"name\"], sortDirection: [asc]) {\n data {\n ...TeamNameFragment\n }\n }\n }\n": types.EditPersonPageDocument, + "\n query ViewPersonPage($uuid: GlobalId!) {\n person(uuid: $uuid) {\n ...PersonViewerFragment\n }\n }\n": types.ViewPersonPageDocument, + "\n query EditTeamPage($uuid: GlobalId!) {\n team(uuid: $uuid) {\n data {\n ...TeamEditorFragment\n }\n }\n }\n": types.EditTeamPageDocument, + "\n query ViewTeamFundraisingDocument($teamUuid: GlobalId!) {\n team(uuid: $teamUuid) {\n data {\n # TODO: Add filtering and pagination\n fundraisingEntries(sendAll: true) {\n data {\n id\n amount\n donatedByText\n donatedToText\n donatedOn\n assignments {\n id\n amount\n person {\n name\n }\n }\n }\n }\n }\n }\n }\n": types.ViewTeamFundraisingDocumentDocument, + "\n query ViewTeamPage($teamUuid: GlobalId!) {\n team(uuid: $teamUuid) {\n data {\n ...TeamViewerFragment\n pointEntries {\n ...PointEntryTableFragment\n }\n }\n }\n }\n": types.ViewTeamPageDocument, }; /** @@ -104,15 +107,23 @@ export function graphql(source: string): unknown; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query ImagePicker($stringFilters: [ImageResolverKeyedStringFilterItem!]) {\n images(stringFilters: $stringFilters, pageSize: 9) {\n data {\n uuid\n alt\n url\n }\n }\n }\n"): (typeof documents)["\n query ImagePicker($stringFilters: [ImageResolverKeyedStringFilterItem!]) {\n images(stringFilters: $stringFilters, pageSize: 9) {\n data {\n uuid\n alt\n url\n }\n }\n }\n"]; +export function graphql(source: "\n query ActiveMarathon {\n latestMarathon {\n id\n year\n startDate\n endDate\n }\n marathons(sendAll: true) {\n data {\n id\n year\n }\n }\n }\n"): (typeof documents)["\n query ActiveMarathon {\n latestMarathon {\n id\n year\n startDate\n endDate\n }\n marathons(sendAll: true) {\n data {\n id\n year\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query PersonSearch($search: String!) {\n searchPeopleByName(name: $search) {\n data {\n uuid\n name\n linkblue\n }\n }\n personByLinkBlue(linkBlueId: $search) {\n data {\n uuid\n name\n linkblue\n }\n }\n }\n"): (typeof documents)["\n query PersonSearch($search: String!) {\n searchPeopleByName(name: $search) {\n data {\n uuid\n name\n linkblue\n }\n }\n personByLinkBlue(linkBlueId: $search) {\n data {\n uuid\n name\n linkblue\n }\n }\n }\n"]; +export function graphql(source: "\n query SelectedMarathon($marathonId: GlobalId!) {\n marathon(uuid: $marathonId) {\n id\n year\n startDate\n endDate\n }\n }\n"): (typeof documents)["\n query SelectedMarathon($marathonId: GlobalId!) {\n marathon(uuid: $marathonId) {\n id\n year\n startDate\n endDate\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment SingleNotificationFragment on NotificationResource {\n uuid\n title\n body\n deliveryIssue\n deliveryIssueAcknowledgedAt\n sendAt\n startedSendingAt\n createdAt\n deliveryCount\n deliveryIssueCount {\n DeviceNotRegistered\n InvalidCredentials\n MessageRateExceeded\n MessageTooBig\n MismatchSenderId\n Unknown\n }\n }\n"): (typeof documents)["\n fragment SingleNotificationFragment on NotificationResource {\n uuid\n title\n body\n deliveryIssue\n deliveryIssueAcknowledgedAt\n sendAt\n startedSendingAt\n createdAt\n deliveryCount\n deliveryIssueCount {\n DeviceNotRegistered\n InvalidCredentials\n MessageRateExceeded\n MessageTooBig\n MismatchSenderId\n Unknown\n }\n }\n"]; +export function graphql(source: "\n query ImagePicker($stringFilters: [ImageResolverKeyedStringFilterItem!]) {\n images(stringFilters: $stringFilters, pageSize: 9) {\n data {\n id\n alt\n url\n }\n }\n }\n"): (typeof documents)["\n query ImagePicker($stringFilters: [ImageResolverKeyedStringFilterItem!]) {\n images(stringFilters: $stringFilters, pageSize: 9) {\n data {\n id\n alt\n url\n }\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query PersonSearch($search: String!) {\n searchPeopleByName(name: $search) {\n id\n name\n linkblue\n }\n personByLinkBlue(linkBlueId: $search) {\n id\n name\n linkblue\n }\n }\n"): (typeof documents)["\n query PersonSearch($search: String!) {\n searchPeopleByName(name: $search) {\n id\n name\n linkblue\n }\n personByLinkBlue(linkBlueId: $search) {\n id\n name\n linkblue\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment SingleNotificationFragment on NotificationNode {\n id\n title\n body\n deliveryIssue\n deliveryIssueAcknowledgedAt\n sendAt\n startedSendingAt\n createdAt\n deliveryCount\n deliveryIssueCount {\n DeviceNotRegistered\n InvalidCredentials\n MessageRateExceeded\n MessageTooBig\n MismatchSenderId\n Unknown\n }\n }\n"): (typeof documents)["\n fragment SingleNotificationFragment on NotificationNode {\n id\n title\n body\n deliveryIssue\n deliveryIssueAcknowledgedAt\n sendAt\n startedSendingAt\n createdAt\n deliveryCount\n deliveryIssueCount {\n DeviceNotRegistered\n InvalidCredentials\n MessageRateExceeded\n MessageTooBig\n MismatchSenderId\n Unknown\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -120,59 +131,59 @@ export function graphql(source: "\n mutation CreateNotification(\n $title: S /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation CancelNotificationSchedule($uuid: String!) {\n abortScheduledNotification(uuid: $uuid) {\n ok\n }\n }\n"): (typeof documents)["\n mutation CancelNotificationSchedule($uuid: String!) {\n abortScheduledNotification(uuid: $uuid) {\n ok\n }\n }\n"]; +export function graphql(source: "\n mutation CancelNotificationSchedule($uuid: GlobalId!) {\n abortScheduledNotification(uuid: $uuid) {\n ok\n }\n }\n"): (typeof documents)["\n mutation CancelNotificationSchedule($uuid: GlobalId!) {\n abortScheduledNotification(uuid: $uuid) {\n ok\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation DeleteNotification($uuid: String!, $force: Boolean) {\n deleteNotification(uuid: $uuid, force: $force) {\n ok\n }\n }\n"): (typeof documents)["\n mutation DeleteNotification($uuid: String!, $force: Boolean) {\n deleteNotification(uuid: $uuid, force: $force) {\n ok\n }\n }\n"]; +export function graphql(source: "\n mutation DeleteNotification($uuid: GlobalId!, $force: Boolean) {\n deleteNotification(uuid: $uuid, force: $force) {\n ok\n }\n }\n"): (typeof documents)["\n mutation DeleteNotification($uuid: GlobalId!, $force: Boolean) {\n deleteNotification(uuid: $uuid, force: $force) {\n ok\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation SendNotification($uuid: String!) {\n sendNotification(uuid: $uuid) {\n ok\n }\n }\n"): (typeof documents)["\n mutation SendNotification($uuid: String!) {\n sendNotification(uuid: $uuid) {\n ok\n }\n }\n"]; +export function graphql(source: "\n mutation SendNotification($uuid: GlobalId!) {\n sendNotification(uuid: $uuid) {\n ok\n }\n }\n"): (typeof documents)["\n mutation SendNotification($uuid: GlobalId!) {\n sendNotification(uuid: $uuid) {\n ok\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation ScheduleNotification($uuid: String!, $sendAt: DateTimeISO!) {\n scheduleNotification(uuid: $uuid, sendAt: $sendAt) {\n ok\n }\n }\n"): (typeof documents)["\n mutation ScheduleNotification($uuid: String!, $sendAt: DateTimeISO!) {\n scheduleNotification(uuid: $uuid, sendAt: $sendAt) {\n ok\n }\n }\n"]; +export function graphql(source: "\n mutation ScheduleNotification($uuid: GlobalId!, $sendAt: DateTimeISO!) {\n scheduleNotification(uuid: $uuid, sendAt: $sendAt) {\n ok\n }\n }\n"): (typeof documents)["\n mutation ScheduleNotification($uuid: GlobalId!, $sendAt: DateTimeISO!) {\n scheduleNotification(uuid: $uuid, sendAt: $sendAt) {\n ok\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment TeamNameFragment on TeamResource {\n uuid\n name\n }\n"): (typeof documents)["\n fragment TeamNameFragment on TeamResource {\n uuid\n name\n }\n"]; +export function graphql(source: "\n fragment TeamNameFragment on TeamNode {\n id\n name\n }\n"): (typeof documents)["\n fragment TeamNameFragment on TeamNode {\n id\n name\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation PersonCreator($input: CreatePersonInput!) {\n createPerson(input: $input) {\n ok\n uuid\n }\n }\n"): (typeof documents)["\n mutation PersonCreator($input: CreatePersonInput!) {\n createPerson(input: $input) {\n ok\n uuid\n }\n }\n"]; +export function graphql(source: "\n mutation PersonCreator($input: CreatePersonInput!) {\n createPerson(input: $input) {\n id\n }\n }\n"): (typeof documents)["\n mutation PersonCreator($input: CreatePersonInput!) {\n createPerson(input: $input) {\n id\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment PersonEditorFragment on PersonResource {\n uuid\n name\n linkblue\n email\n role {\n committeeRole\n committeeIdentifier\n }\n teams {\n position\n team {\n uuid\n name\n }\n }\n }\n"): (typeof documents)["\n fragment PersonEditorFragment on PersonResource {\n uuid\n name\n linkblue\n email\n role {\n committeeRole\n committeeIdentifier\n }\n teams {\n position\n team {\n uuid\n name\n }\n }\n }\n"]; +export function graphql(source: "\n fragment PersonEditorFragment on PersonNode {\n id\n name\n linkblue\n email\n teams {\n position\n team {\n id\n name\n }\n }\n }\n"): (typeof documents)["\n fragment PersonEditorFragment on PersonNode {\n id\n name\n linkblue\n email\n teams {\n position\n team {\n id\n name\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation PersonEditor($uuid: String!, $input: SetPersonInput!) {\n setPerson(uuid: $uuid, input: $input) {\n ok\n }\n }\n"): (typeof documents)["\n mutation PersonEditor($uuid: String!, $input: SetPersonInput!) {\n setPerson(uuid: $uuid, input: $input) {\n ok\n }\n }\n"]; +export function graphql(source: "\n mutation PersonEditor($uuid: GlobalId!, $input: SetPersonInput!) {\n setPerson(uuid: $uuid, input: $input) {\n id\n }\n }\n"): (typeof documents)["\n mutation PersonEditor($uuid: GlobalId!, $input: SetPersonInput!) {\n setPerson(uuid: $uuid, input: $input) {\n id\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation CreatePointEntry($input: CreatePointEntryInput!) {\n createPointEntry(input: $input) {\n data {\n uuid\n }\n }\n }\n"): (typeof documents)["\n mutation CreatePointEntry($input: CreatePointEntryInput!) {\n createPointEntry(input: $input) {\n data {\n uuid\n }\n }\n }\n"]; +export function graphql(source: "\n mutation CreatePointEntry($input: CreatePointEntryInput!) {\n createPointEntry(input: $input) {\n data {\n id\n }\n }\n }\n"): (typeof documents)["\n mutation CreatePointEntry($input: CreatePointEntryInput!) {\n createPointEntry(input: $input) {\n data {\n id\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query GetPersonByUuid($uuid: String!) {\n person(uuid: $uuid) {\n data {\n uuid\n name\n linkblue\n }\n }\n }\n"): (typeof documents)["\n query GetPersonByUuid($uuid: String!) {\n person(uuid: $uuid) {\n data {\n uuid\n name\n linkblue\n }\n }\n }\n"]; +export function graphql(source: "\n query GetPersonByUuid($uuid: GlobalId!) {\n person(uuid: $uuid) {\n id\n name\n linkblue\n }\n }\n"): (typeof documents)["\n query GetPersonByUuid($uuid: GlobalId!) {\n person(uuid: $uuid) {\n id\n name\n linkblue\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query GetPersonByLinkBlue($linkBlue: String!) {\n personByLinkBlue(linkBlueId: $linkBlue) {\n data {\n uuid\n name\n }\n }\n }\n"): (typeof documents)["\n query GetPersonByLinkBlue($linkBlue: String!) {\n personByLinkBlue(linkBlueId: $linkBlue) {\n data {\n uuid\n name\n }\n }\n }\n"]; +export function graphql(source: "\n query GetPersonByLinkBlue($linkBlue: String!) {\n personByLinkBlue(linkBlueId: $linkBlue) {\n id\n name\n }\n }\n"): (typeof documents)["\n query GetPersonByLinkBlue($linkBlue: String!) {\n personByLinkBlue(linkBlueId: $linkBlue) {\n id\n name\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query SearchPersonByName($name: String!) {\n searchPeopleByName(name: $name) {\n data {\n uuid\n name\n }\n }\n }\n"): (typeof documents)["\n query SearchPersonByName($name: String!) {\n searchPeopleByName(name: $name) {\n data {\n uuid\n name\n }\n }\n }\n"]; +export function graphql(source: "\n query SearchPersonByName($name: String!) {\n searchPeopleByName(name: $name) {\n id\n name\n }\n }\n"): (typeof documents)["\n query SearchPersonByName($name: String!) {\n searchPeopleByName(name: $name) {\n id\n name\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation CreatePersonByLinkBlue(\n $linkBlue: String!\n $email: EmailAddress!\n $teamUuid: String!\n ) {\n createPerson(\n input: { email: $email, linkblue: $linkBlue, memberOf: [$teamUuid] }\n ) {\n uuid\n }\n }\n"): (typeof documents)["\n mutation CreatePersonByLinkBlue(\n $linkBlue: String!\n $email: EmailAddress!\n $teamUuid: String!\n ) {\n createPerson(\n input: { email: $email, linkblue: $linkBlue, memberOf: [$teamUuid] }\n ) {\n uuid\n }\n }\n"]; +export function graphql(source: "\n mutation CreatePersonByLinkBlue(\n $linkBlue: String!\n $email: EmailAddress!\n $teamUuid: String!\n ) {\n createPerson(\n input: { email: $email, linkblue: $linkBlue, memberOf: [$teamUuid] }\n ) {\n id\n }\n }\n"): (typeof documents)["\n mutation CreatePersonByLinkBlue(\n $linkBlue: String!\n $email: EmailAddress!\n $teamUuid: String!\n ) {\n createPerson(\n input: { email: $email, linkblue: $linkBlue, memberOf: [$teamUuid] }\n ) {\n id\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query PointEntryOpportunityLookup($name: String!) {\n pointOpportunities(\n stringFilters: { field: name, comparison: SUBSTRING, value: $name }\n sendAll: true\n ) {\n data {\n name\n uuid\n }\n }\n }\n"): (typeof documents)["\n query PointEntryOpportunityLookup($name: String!) {\n pointOpportunities(\n stringFilters: { field: name, comparison: SUBSTRING, value: $name }\n sendAll: true\n ) {\n data {\n name\n uuid\n }\n }\n }\n"]; +export function graphql(source: "\n query PointEntryOpportunityLookup($name: String!) {\n pointOpportunities(\n stringFilters: { field: name, comparison: SUBSTRING, value: $name }\n sendAll: true\n ) {\n data {\n name\n id\n }\n }\n }\n"): (typeof documents)["\n query PointEntryOpportunityLookup($name: String!) {\n pointOpportunities(\n stringFilters: { field: name, comparison: SUBSTRING, value: $name }\n sendAll: true\n ) {\n data {\n name\n id\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -180,19 +191,19 @@ export function graphql(source: "\n mutation CreatePointOpportunity($input: Cre /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation TeamCreator($input: CreateTeamInput!) {\n createTeam(input: $input) {\n ok\n uuid\n }\n }\n"): (typeof documents)["\n mutation TeamCreator($input: CreateTeamInput!) {\n createTeam(input: $input) {\n ok\n uuid\n }\n }\n"]; +export function graphql(source: "\n mutation TeamCreator($input: CreateTeamInput!, $marathonUuid: String!) {\n createTeam(input: $input, marathon: $marathonUuid) {\n ok\n uuid\n }\n }\n"): (typeof documents)["\n mutation TeamCreator($input: CreateTeamInput!, $marathonUuid: String!) {\n createTeam(input: $input, marathon: $marathonUuid) {\n ok\n uuid\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment TeamEditorFragment on TeamResource {\n uuid\n name\n marathonYear\n legacyStatus\n persistentIdentifier\n type\n }\n"): (typeof documents)["\n fragment TeamEditorFragment on TeamResource {\n uuid\n name\n marathonYear\n legacyStatus\n persistentIdentifier\n type\n }\n"]; +export function graphql(source: "\n fragment TeamEditorFragment on TeamNode {\n id\n name\n marathon {\n id\n year\n }\n legacyStatus\n type\n }\n"): (typeof documents)["\n fragment TeamEditorFragment on TeamNode {\n id\n name\n marathon {\n id\n year\n }\n legacyStatus\n type\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation TeamEditor($uuid: String!, $input: SetTeamInput!) {\n setTeam(uuid: $uuid, input: $input) {\n ok\n }\n }\n"): (typeof documents)["\n mutation TeamEditor($uuid: String!, $input: SetTeamInput!) {\n setTeam(uuid: $uuid, input: $input) {\n ok\n }\n }\n"]; +export function graphql(source: "\n mutation TeamEditor($uuid: GlobalId!, $input: SetTeamInput!) {\n setTeam(uuid: $uuid, input: $input) {\n ok\n }\n }\n"): (typeof documents)["\n mutation TeamEditor($uuid: GlobalId!, $input: SetTeamInput!) {\n setTeam(uuid: $uuid, input: $input) {\n ok\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment PeopleTableFragment on PersonResource {\n uuid\n name\n linkblue\n email\n role {\n dbRole\n committeeRole\n committeeIdentifier\n }\n }\n"): (typeof documents)["\n fragment PeopleTableFragment on PersonResource {\n uuid\n name\n linkblue\n email\n role {\n dbRole\n committeeRole\n committeeIdentifier\n }\n }\n"]; +export function graphql(source: "\n fragment PeopleTableFragment on PersonNode {\n id\n name\n linkblue\n email\n dbRole\n primaryCommittee {\n identifier\n role\n }\n }\n"): (typeof documents)["\n fragment PeopleTableFragment on PersonNode {\n id\n name\n linkblue\n email\n dbRole\n primaryCommittee {\n identifier\n role\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -204,11 +215,11 @@ export function graphql(source: "\n query TeamsTable(\n $page: Int\n $pag /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment TeamsTableFragment on TeamResource {\n uuid\n type\n name\n legacyStatus\n marathonYear\n totalPoints\n }\n"): (typeof documents)["\n fragment TeamsTableFragment on TeamResource {\n uuid\n type\n name\n legacyStatus\n marathonYear\n totalPoints\n }\n"]; +export function graphql(source: "\n fragment TeamsTableFragment on TeamNode {\n id\n type\n name\n legacyStatus\n totalPoints\n }\n"): (typeof documents)["\n fragment TeamsTableFragment on TeamNode {\n id\n type\n name\n legacyStatus\n totalPoints\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment NotificationDeliveriesTableFragment on NotificationDeliveryResource {\n uuid\n deliveryError\n receiptCheckedAt\n sentAt\n }\n"): (typeof documents)["\n fragment NotificationDeliveriesTableFragment on NotificationDeliveryResource {\n uuid\n deliveryError\n receiptCheckedAt\n sentAt\n }\n"]; +export function graphql(source: "\n fragment NotificationDeliveriesTableFragment on NotificationDeliveryNode {\n id\n deliveryError\n receiptCheckedAt\n sentAt\n }\n"): (typeof documents)["\n fragment NotificationDeliveriesTableFragment on NotificationDeliveryNode {\n id\n deliveryError\n receiptCheckedAt\n sentAt\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -216,7 +227,7 @@ export function graphql(source: "\n query NotificationDeliveriesTableQuery(\n /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment NotificationsTableFragment on NotificationResource {\n uuid\n title\n body\n deliveryIssue\n deliveryIssueAcknowledgedAt\n sendAt\n startedSendingAt\n }\n"): (typeof documents)["\n fragment NotificationsTableFragment on NotificationResource {\n uuid\n title\n body\n deliveryIssue\n deliveryIssueAcknowledgedAt\n sendAt\n startedSendingAt\n }\n"]; +export function graphql(source: "\n fragment NotificationsTableFragment on NotificationNode {\n id\n title\n body\n deliveryIssue\n deliveryIssueAcknowledgedAt\n sendAt\n startedSendingAt\n }\n"): (typeof documents)["\n fragment NotificationsTableFragment on NotificationNode {\n id\n title\n body\n deliveryIssue\n deliveryIssueAcknowledgedAt\n sendAt\n startedSendingAt\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -224,31 +235,31 @@ export function graphql(source: "\n query NotificationsTableQuery(\n $page: /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation DeletePointEntry($uuid: String!) {\n deletePointEntry(uuid: $uuid) {\n ok\n }\n }\n"): (typeof documents)["\n mutation DeletePointEntry($uuid: String!) {\n deletePointEntry(uuid: $uuid) {\n ok\n }\n }\n"]; +export function graphql(source: "\n mutation DeletePointEntry($uuid: GlobalId!) {\n deletePointEntry(uuid: $uuid) {\n ok\n }\n }\n"): (typeof documents)["\n mutation DeletePointEntry($uuid: GlobalId!) {\n deletePointEntry(uuid: $uuid) {\n ok\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment PointEntryTableFragment on PointEntryResource {\n uuid\n personFrom {\n name\n linkblue\n }\n points\n pointOpportunity {\n name\n opportunityDate\n }\n comment\n }\n"): (typeof documents)["\n fragment PointEntryTableFragment on PointEntryResource {\n uuid\n personFrom {\n name\n linkblue\n }\n points\n pointOpportunity {\n name\n opportunityDate\n }\n comment\n }\n"]; +export function graphql(source: "\n fragment PointEntryTableFragment on PointEntryNode {\n id\n personFrom {\n name\n linkblue\n }\n points\n pointOpportunity {\n name\n opportunityDate\n }\n comment\n }\n"): (typeof documents)["\n fragment PointEntryTableFragment on PointEntryNode {\n id\n personFrom {\n name\n linkblue\n }\n points\n pointOpportunity {\n name\n opportunityDate\n }\n comment\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation DeletePerson($uuid: String!) {\n deletePerson(uuid: $uuid) {\n ok\n }\n }\n"): (typeof documents)["\n mutation DeletePerson($uuid: String!) {\n deletePerson(uuid: $uuid) {\n ok\n }\n }\n"]; +export function graphql(source: "\n mutation DeletePerson($uuid: GlobalId!) {\n deletePerson(uuid: $uuid) {\n id\n }\n }\n"): (typeof documents)["\n mutation DeletePerson($uuid: GlobalId!) {\n deletePerson(uuid: $uuid) {\n id\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment PersonViewerFragment on PersonResource {\n uuid\n name\n linkblue\n email\n role {\n dbRole\n committeeRole\n committeeIdentifier\n }\n teams {\n position\n team {\n uuid\n name\n }\n }\n }\n"): (typeof documents)["\n fragment PersonViewerFragment on PersonResource {\n uuid\n name\n linkblue\n email\n role {\n dbRole\n committeeRole\n committeeIdentifier\n }\n teams {\n position\n team {\n uuid\n name\n }\n }\n }\n"]; +export function graphql(source: "\n fragment PersonViewerFragment on PersonNode {\n id\n name\n linkblue\n email\n dbRole\n teams {\n position\n team {\n id\n name\n }\n }\n committees {\n identifier\n role\n }\n }\n"): (typeof documents)["\n fragment PersonViewerFragment on PersonNode {\n id\n name\n linkblue\n email\n dbRole\n teams {\n position\n team {\n id\n name\n }\n }\n committees {\n identifier\n role\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation DeleteTeam($uuid: String!) {\n deleteTeam(uuid: $uuid) {\n ok\n }\n }\n"): (typeof documents)["\n mutation DeleteTeam($uuid: String!) {\n deleteTeam(uuid: $uuid) {\n ok\n }\n }\n"]; +export function graphql(source: "\n mutation DeleteTeam($uuid: GlobalId!) {\n deleteTeam(uuid: $uuid) {\n ok\n }\n }\n"): (typeof documents)["\n mutation DeleteTeam($uuid: GlobalId!) {\n deleteTeam(uuid: $uuid) {\n ok\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment TeamViewerFragment on TeamResource {\n uuid\n name\n marathonYear\n legacyStatus\n totalPoints\n type\n members {\n person {\n uuid\n name\n linkblue\n }\n }\n captains {\n person {\n uuid\n name\n linkblue\n }\n }\n }\n"): (typeof documents)["\n fragment TeamViewerFragment on TeamResource {\n uuid\n name\n marathonYear\n legacyStatus\n totalPoints\n type\n members {\n person {\n uuid\n name\n linkblue\n }\n }\n captains {\n person {\n uuid\n name\n linkblue\n }\n }\n }\n"]; +export function graphql(source: "\n fragment TeamViewerFragment on TeamNode {\n id\n name\n marathon {\n id\n year\n }\n legacyStatus\n totalPoints\n type\n members {\n person {\n id\n name\n linkblue\n }\n position\n }\n }\n"): (typeof documents)["\n fragment TeamViewerFragment on TeamNode {\n id\n name\n marathon {\n id\n year\n }\n legacyStatus\n totalPoints\n type\n members {\n person {\n id\n name\n linkblue\n }\n position\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query LoginState {\n loginState {\n loggedIn\n role {\n dbRole\n committeeRole\n committeeIdentifier\n }\n }\n }\n"): (typeof documents)["\n query LoginState {\n loginState {\n loggedIn\n role {\n dbRole\n committeeRole\n committeeIdentifier\n }\n }\n }\n"]; +export function graphql(source: "\n query LoginState {\n loginState {\n loggedIn\n dbRole\n effectiveCommitteeRoles {\n role\n identifier\n }\n }\n }\n"): (typeof documents)["\n query LoginState {\n loginState {\n loggedIn\n dbRole\n effectiveCommitteeRoles {\n role\n identifier\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -256,7 +267,7 @@ export function graphql(source: "\n mutation CommitConfigChanges($changes: [Cre /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment ConfigFragment on ConfigurationResource {\n uuid\n key\n value\n validAfter\n validUntil\n createdAt\n }\n"): (typeof documents)["\n fragment ConfigFragment on ConfigurationResource {\n uuid\n key\n value\n validAfter\n validUntil\n createdAt\n }\n"]; +export function graphql(source: "\n fragment ConfigFragment on ConfigurationNode {\n id\n key\n value\n validAfter\n validUntil\n createdAt\n }\n"): (typeof documents)["\n fragment ConfigFragment on ConfigurationNode {\n id\n key\n value\n validAfter\n validUntil\n createdAt\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -264,11 +275,11 @@ export function graphql(source: "\n query ConfigQuery {\n allConfigu /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation CreateEvent($input: CreateEventInput!) {\n createEvent(input: $input) {\n data {\n uuid\n }\n }\n }\n"): (typeof documents)["\n mutation CreateEvent($input: CreateEventInput!) {\n createEvent(input: $input) {\n data {\n uuid\n }\n }\n }\n"]; +export function graphql(source: "\n mutation CreateEvent($input: CreateEventInput!) {\n createEvent(input: $input) {\n data {\n id\n }\n }\n }\n"): (typeof documents)["\n mutation CreateEvent($input: CreateEventInput!) {\n createEvent(input: $input) {\n data {\n id\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment EventsTableFragment on EventResource {\n uuid\n title\n description\n occurrences {\n uuid\n interval\n fullDay\n }\n summary\n }\n"): (typeof documents)["\n fragment EventsTableFragment on EventResource {\n uuid\n title\n description\n occurrences {\n uuid\n interval\n fullDay\n }\n summary\n }\n"]; +export function graphql(source: "\n fragment EventsTableFragment on EventNode {\n id\n title\n description\n occurrences {\n id\n interval {\n start\n end\n }\n fullDay\n }\n summary\n }\n"): (typeof documents)["\n fragment EventsTableFragment on EventNode {\n id\n title\n description\n occurrences {\n id\n interval {\n start\n end\n }\n fullDay\n }\n summary\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -276,35 +287,35 @@ export function graphql(source: "\n query EventsTable(\n $page: Int\n $pa /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query EditEventPage($uuid: String!) {\n event(uuid: $uuid) {\n data {\n ...EventEditorFragment\n }\n }\n }\n"): (typeof documents)["\n query EditEventPage($uuid: String!) {\n event(uuid: $uuid) {\n data {\n ...EventEditorFragment\n }\n }\n }\n"]; +export function graphql(source: "\n query EditEventPage($uuid: GlobalId!) {\n event(uuid: $uuid) {\n data {\n ...EventEditorFragment\n }\n }\n }\n"): (typeof documents)["\n query EditEventPage($uuid: GlobalId!) {\n event(uuid: $uuid) {\n data {\n ...EventEditorFragment\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment EventEditorFragment on EventResource {\n uuid\n title\n summary\n description\n location\n occurrences {\n uuid\n interval\n fullDay\n }\n images {\n url\n width\n height\n thumbHash\n alt\n }\n }\n"): (typeof documents)["\n fragment EventEditorFragment on EventResource {\n uuid\n title\n summary\n description\n location\n occurrences {\n uuid\n interval\n fullDay\n }\n images {\n url\n width\n height\n thumbHash\n alt\n }\n }\n"]; +export function graphql(source: "\n fragment EventEditorFragment on EventNode {\n id\n title\n summary\n description\n location\n occurrences {\n id\n interval {\n start\n end\n }\n fullDay\n }\n images {\n url\n width\n height\n thumbHash\n alt\n }\n }\n"): (typeof documents)["\n fragment EventEditorFragment on EventNode {\n id\n title\n summary\n description\n location\n occurrences {\n id\n interval {\n start\n end\n }\n fullDay\n }\n images {\n url\n width\n height\n thumbHash\n alt\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation SaveEvent($uuid: String!, $input: SetEventInput!) {\n setEvent(uuid: $uuid, input: $input) {\n data {\n ...EventEditorFragment\n }\n }\n }\n"): (typeof documents)["\n mutation SaveEvent($uuid: String!, $input: SetEventInput!) {\n setEvent(uuid: $uuid, input: $input) {\n data {\n ...EventEditorFragment\n }\n }\n }\n"]; +export function graphql(source: "\n mutation SaveEvent($uuid: GlobalId!, $input: SetEventInput!) {\n setEvent(uuid: $uuid, input: $input) {\n data {\n ...EventEditorFragment\n }\n }\n }\n"): (typeof documents)["\n mutation SaveEvent($uuid: GlobalId!, $input: SetEventInput!) {\n setEvent(uuid: $uuid, input: $input) {\n data {\n ...EventEditorFragment\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation DeleteEvent($uuid: String!) {\n deleteEvent(uuid: $uuid) {\n ok\n }\n }\n"): (typeof documents)["\n mutation DeleteEvent($uuid: String!) {\n deleteEvent(uuid: $uuid) {\n ok\n }\n }\n"]; +export function graphql(source: "\n mutation DeleteEvent($uuid: GlobalId!) {\n deleteEvent(uuid: $uuid) {\n ok\n }\n }\n"): (typeof documents)["\n mutation DeleteEvent($uuid: GlobalId!) {\n deleteEvent(uuid: $uuid) {\n ok\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment EventViewerFragment on EventResource {\n uuid\n title\n summary\n description\n location\n occurrences {\n interval\n fullDay\n }\n images {\n url\n width\n height\n thumbHash\n alt\n }\n createdAt\n updatedAt\n }\n"): (typeof documents)["\n fragment EventViewerFragment on EventResource {\n uuid\n title\n summary\n description\n location\n occurrences {\n interval\n fullDay\n }\n images {\n url\n width\n height\n thumbHash\n alt\n }\n createdAt\n updatedAt\n }\n"]; +export function graphql(source: "\n fragment EventViewerFragment on EventNode {\n id\n title\n summary\n description\n location\n occurrences {\n interval {\n start\n end\n }\n fullDay\n }\n images {\n url\n width\n height\n thumbHash\n alt\n }\n createdAt\n updatedAt\n }\n"): (typeof documents)["\n fragment EventViewerFragment on EventNode {\n id\n title\n summary\n description\n location\n occurrences {\n interval {\n start\n end\n }\n fullDay\n }\n images {\n url\n width\n height\n thumbHash\n alt\n }\n createdAt\n updatedAt\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query ViewEventPage($uuid: String!) {\n event(uuid: $uuid) {\n data {\n ...EventViewerFragment\n }\n }\n }\n"): (typeof documents)["\n query ViewEventPage($uuid: String!) {\n event(uuid: $uuid) {\n data {\n ...EventViewerFragment\n }\n }\n }\n"]; +export function graphql(source: "\n query ViewEventPage($uuid: GlobalId!) {\n event(uuid: $uuid) {\n data {\n ...EventViewerFragment\n }\n }\n }\n"): (typeof documents)["\n query ViewEventPage($uuid: GlobalId!) {\n event(uuid: $uuid) {\n data {\n ...EventViewerFragment\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query FeedPage {\n feed(limit: null) {\n uuid\n title\n createdAt\n textContent\n image {\n url\n alt\n }\n }\n }\n"): (typeof documents)["\n query FeedPage {\n feed(limit: null) {\n uuid\n title\n createdAt\n textContent\n image {\n url\n alt\n }\n }\n }\n"]; +export function graphql(source: "\n query FeedPage {\n feed(limit: null) {\n id\n title\n createdAt\n textContent\n image {\n url\n alt\n }\n }\n }\n"): (typeof documents)["\n query FeedPage {\n feed(limit: null) {\n id\n title\n createdAt\n textContent\n image {\n url\n alt\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation CreateFeedItem($input: CreateFeedInput!) {\n createFeedItem(input: $input) {\n uuid\n }\n }\n"): (typeof documents)["\n mutation CreateFeedItem($input: CreateFeedInput!) {\n createFeedItem(input: $input) {\n uuid\n }\n }\n"]; +export function graphql(source: "\n mutation CreateFeedItem($input: CreateFeedInput!) {\n createFeedItem(input: $input) {\n id\n }\n }\n"): (typeof documents)["\n mutation CreateFeedItem($input: CreateFeedInput!) {\n createFeedItem(input: $input) {\n id\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -312,11 +323,11 @@ export function graphql(source: "\n mutation DeleteFeedItem($uuid: String!) {\n /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation CreateImage($input: CreateImageInput!) {\n createImage(input: $input) {\n uuid\n }\n }\n"): (typeof documents)["\n mutation CreateImage($input: CreateImageInput!) {\n createImage(input: $input) {\n uuid\n }\n }\n"]; +export function graphql(source: "\n mutation CreateImage($input: CreateImageInput!) {\n createImage(input: $input) {\n id\n }\n }\n"): (typeof documents)["\n mutation CreateImage($input: CreateImageInput!) {\n createImage(input: $input) {\n id\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment ImagesTableFragment on ImageResource {\n uuid\n url\n thumbHash\n height\n width\n alt\n mimeType\n createdAt\n }\n"): (typeof documents)["\n fragment ImagesTableFragment on ImageResource {\n uuid\n url\n thumbHash\n height\n width\n alt\n mimeType\n createdAt\n }\n"]; +export function graphql(source: "\n fragment ImagesTableFragment on ImageNode {\n id\n url\n thumbHash\n height\n width\n alt\n mimeType\n createdAt\n }\n"): (typeof documents)["\n fragment ImagesTableFragment on ImageNode {\n id\n url\n thumbHash\n height\n width\n alt\n mimeType\n createdAt\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -324,71 +335,75 @@ export function graphql(source: "\n query ImagesTable(\n $page: Int\n $pa /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation CreateMarathon($input: CreateMarathonInput!) {\n createMarathon(input: $input) {\n uuid\n }\n }\n "): (typeof documents)["\n mutation CreateMarathon($input: CreateMarathonInput!) {\n createMarathon(input: $input) {\n uuid\n }\n }\n "]; +export function graphql(source: "\n mutation CreateMarathon($input: CreateMarathonInput!) {\n createMarathon(input: $input) {\n id\n }\n }\n "): (typeof documents)["\n mutation CreateMarathon($input: CreateMarathonInput!) {\n createMarathon(input: $input) {\n id\n }\n }\n "]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query MarathonOverviewPage {\n latestMarathon {\n ...MarathonViewerFragment\n }\n marathons(sendAll: true) {\n data {\n ...MarathonTableFragment\n }\n }\n }\n"): (typeof documents)["\n query MarathonOverviewPage {\n latestMarathon {\n ...MarathonViewerFragment\n }\n marathons(sendAll: true) {\n data {\n ...MarathonTableFragment\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query MarathonOverviewPage {\n nextMarathon {\n ...MarathonViewerFragment\n }\n marathons(sendAll: true) {\n data {\n ...MarathonTableFragment\n }\n }\n }\n"): (typeof documents)["\n query MarathonOverviewPage {\n nextMarathon {\n ...MarathonViewerFragment\n }\n marathons(sendAll: true) {\n data {\n ...MarathonTableFragment\n }\n }\n }\n"]; +export function graphql(source: "\n fragment MarathonTableFragment on MarathonNode {\n id\n year\n startDate\n endDate\n }\n"): (typeof documents)["\n fragment MarathonTableFragment on MarathonNode {\n id\n year\n startDate\n endDate\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment MarathonTableFragment on MarathonResource {\n uuid\n year\n startDate\n endDate\n }\n"): (typeof documents)["\n fragment MarathonTableFragment on MarathonResource {\n uuid\n year\n startDate\n endDate\n }\n"]; +export function graphql(source: "\n mutation EditMarathon(\n $input: SetMarathonInput!\n $marathonId: GlobalId!\n ) {\n setMarathon(input: $input, uuid: $marathonId) {\n id\n }\n }\n "): (typeof documents)["\n mutation EditMarathon(\n $input: SetMarathonInput!\n $marathonId: GlobalId!\n ) {\n setMarathon(input: $input, uuid: $marathonId) {\n id\n }\n }\n "]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation EditMarathon($input: SetMarathonInput!, $marathonId: String!) {\n setMarathon(input: $input, uuid: $marathonId) {\n uuid\n }\n }\n "): (typeof documents)["\n mutation EditMarathon($input: SetMarathonInput!, $marathonId: String!) {\n setMarathon(input: $input, uuid: $marathonId) {\n uuid\n }\n }\n "]; +export function graphql(source: "\n query GetMarathon($marathonId: GlobalId!) {\n marathon(uuid: $marathonId) {\n year\n startDate\n endDate\n }\n }\n "): (typeof documents)["\n query GetMarathon($marathonId: GlobalId!) {\n marathon(uuid: $marathonId) {\n year\n startDate\n endDate\n }\n }\n "]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query GetMarathon($marathonId: String!) {\n marathon(uuid: $marathonId) {\n year\n startDate\n endDate\n }\n }\n "): (typeof documents)["\n query GetMarathon($marathonId: String!) {\n marathon(uuid: $marathonId) {\n year\n startDate\n endDate\n }\n }\n "]; +export function graphql(source: "\n fragment MarathonViewerFragment on MarathonNode {\n id\n year\n startDate\n endDate\n hours {\n id\n shownStartingAt\n title\n }\n }\n"): (typeof documents)["\n fragment MarathonViewerFragment on MarathonNode {\n id\n year\n startDate\n endDate\n hours {\n id\n shownStartingAt\n title\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment MarathonViewerFragment on MarathonResource {\n uuid\n year\n startDate\n endDate\n hours {\n uuid\n shownStartingAt\n title\n }\n }\n"): (typeof documents)["\n fragment MarathonViewerFragment on MarathonResource {\n uuid\n year\n startDate\n endDate\n hours {\n uuid\n shownStartingAt\n title\n }\n }\n"]; +export function graphql(source: "\n query MarathonPage($marathonUuid: GlobalId!) {\n marathon(uuid: $marathonUuid) {\n ...MarathonViewerFragment\n }\n }\n"): (typeof documents)["\n query MarathonPage($marathonUuid: GlobalId!) {\n marathon(uuid: $marathonUuid) {\n ...MarathonViewerFragment\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query MarathonPage($marathonUuid: String!) {\n marathon(uuid: $marathonUuid) {\n ...MarathonViewerFragment\n }\n }\n"): (typeof documents)["\n query MarathonPage($marathonUuid: String!) {\n marathon(uuid: $marathonUuid) {\n ...MarathonViewerFragment\n }\n }\n"]; +export function graphql(source: "\n mutation AddMarathonHour(\n $input: CreateMarathonHourInput!\n $marathonUuid: String!\n ) {\n createMarathonHour(input: $input, marathonUuid: $marathonUuid) {\n id\n }\n }\n "): (typeof documents)["\n mutation AddMarathonHour(\n $input: CreateMarathonHourInput!\n $marathonUuid: String!\n ) {\n createMarathonHour(input: $input, marathonUuid: $marathonUuid) {\n id\n }\n }\n "]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation AddMarathonHour(\n $input: CreateMarathonHourInput!\n $marathonUuid: String!\n ) {\n createMarathonHour(input: $input, marathonUuid: $marathonUuid) {\n uuid\n }\n }\n "): (typeof documents)["\n mutation AddMarathonHour(\n $input: CreateMarathonHourInput!\n $marathonUuid: String!\n ) {\n createMarathonHour(input: $input, marathonUuid: $marathonUuid) {\n uuid\n }\n }\n "]; +export function graphql(source: "\n query EditMarathonHourData($marathonHourUuid: GlobalId!) {\n marathonHour(uuid: $marathonHourUuid) {\n details\n durationInfo\n shownStartingAt\n title\n }\n }\n"): (typeof documents)["\n query EditMarathonHourData($marathonHourUuid: GlobalId!) {\n marathonHour(uuid: $marathonHourUuid) {\n details\n durationInfo\n shownStartingAt\n title\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query EditMarathonHourData($marathonHourUuid: String!) {\n marathonHour(uuid: $marathonHourUuid) {\n details\n durationInfo\n shownStartingAt\n title\n }\n }\n"): (typeof documents)["\n query EditMarathonHourData($marathonHourUuid: String!) {\n marathonHour(uuid: $marathonHourUuid) {\n details\n durationInfo\n shownStartingAt\n title\n }\n }\n"]; +export function graphql(source: "\n mutation EditMarathonHour($input: SetMarathonHourInput!, $uuid: GlobalId!) {\n setMarathonHour(input: $input, uuid: $uuid) {\n id\n }\n }\n"): (typeof documents)["\n mutation EditMarathonHour($input: SetMarathonHourInput!, $uuid: GlobalId!) {\n setMarathonHour(input: $input, uuid: $uuid) {\n id\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation EditMarathonHour($input: SetMarathonHourInput!, $uuid: String!) {\n setMarathonHour(input: $input, uuid: $uuid) {\n uuid\n }\n }\n"): (typeof documents)["\n mutation EditMarathonHour($input: SetMarathonHourInput!, $uuid: String!) {\n setMarathonHour(input: $input, uuid: $uuid) {\n uuid\n }\n }\n"]; +export function graphql(source: "\n query NotificationManager($uuid: GlobalId!) {\n notification(uuid: $uuid) {\n data {\n ...SingleNotificationFragment\n }\n }\n }\n"): (typeof documents)["\n query NotificationManager($uuid: GlobalId!) {\n notification(uuid: $uuid) {\n data {\n ...SingleNotificationFragment\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query NotificationManager($uuid: String!) {\n notification(uuid: $uuid) {\n data {\n ...SingleNotificationFragment\n }\n }\n }\n"): (typeof documents)["\n query NotificationManager($uuid: String!) {\n notification(uuid: $uuid) {\n data {\n ...SingleNotificationFragment\n }\n }\n }\n"]; +export function graphql(source: "\n query NotificationViewer($uuid: GlobalId!) {\n notification(uuid: $uuid) {\n data {\n ...SingleNotificationFragment\n }\n }\n }\n"): (typeof documents)["\n query NotificationViewer($uuid: GlobalId!) {\n notification(uuid: $uuid) {\n data {\n ...SingleNotificationFragment\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query NotificationViewer($uuid: String!) {\n notification(uuid: $uuid) {\n data {\n ...SingleNotificationFragment\n }\n }\n }\n"): (typeof documents)["\n query NotificationViewer($uuid: String!) {\n notification(uuid: $uuid) {\n data {\n ...SingleNotificationFragment\n }\n }\n }\n"]; +export function graphql(source: "\n query CreatePersonPage {\n teams(sendAll: true, sortBy: [\"name\"], sortDirection: [asc]) {\n data {\n ...TeamNameFragment\n }\n }\n }\n"): (typeof documents)["\n query CreatePersonPage {\n teams(sendAll: true, sortBy: [\"name\"], sortDirection: [asc]) {\n data {\n ...TeamNameFragment\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query CreatePersonPage {\n teams(sendAll: true, sortBy: [\"name\"], sortDirection: [ASCENDING]) {\n data {\n ...TeamNameFragment\n }\n }\n }\n"): (typeof documents)["\n query CreatePersonPage {\n teams(sendAll: true, sortBy: [\"name\"], sortDirection: [ASCENDING]) {\n data {\n ...TeamNameFragment\n }\n }\n }\n"]; +export function graphql(source: "\n query EditPersonPage($uuid: GlobalId!) {\n person(uuid: $uuid) {\n ...PersonEditorFragment\n }\n teams(sendAll: true, sortBy: [\"name\"], sortDirection: [asc]) {\n data {\n ...TeamNameFragment\n }\n }\n }\n"): (typeof documents)["\n query EditPersonPage($uuid: GlobalId!) {\n person(uuid: $uuid) {\n ...PersonEditorFragment\n }\n teams(sendAll: true, sortBy: [\"name\"], sortDirection: [asc]) {\n data {\n ...TeamNameFragment\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query EditPersonPage($uuid: String!) {\n person(uuid: $uuid) {\n data {\n ...PersonEditorFragment\n }\n }\n teams(sendAll: true, sortBy: [\"name\"], sortDirection: [ASCENDING]) {\n data {\n ...TeamNameFragment\n }\n }\n }\n"): (typeof documents)["\n query EditPersonPage($uuid: String!) {\n person(uuid: $uuid) {\n data {\n ...PersonEditorFragment\n }\n }\n teams(sendAll: true, sortBy: [\"name\"], sortDirection: [ASCENDING]) {\n data {\n ...TeamNameFragment\n }\n }\n }\n"]; +export function graphql(source: "\n query ViewPersonPage($uuid: GlobalId!) {\n person(uuid: $uuid) {\n ...PersonViewerFragment\n }\n }\n"): (typeof documents)["\n query ViewPersonPage($uuid: GlobalId!) {\n person(uuid: $uuid) {\n ...PersonViewerFragment\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query ViewPersonPage($uuid: String!) {\n person(uuid: $uuid) {\n data {\n ...PersonViewerFragment\n }\n }\n }\n"): (typeof documents)["\n query ViewPersonPage($uuid: String!) {\n person(uuid: $uuid) {\n data {\n ...PersonViewerFragment\n }\n }\n }\n"]; +export function graphql(source: "\n query EditTeamPage($uuid: GlobalId!) {\n team(uuid: $uuid) {\n data {\n ...TeamEditorFragment\n }\n }\n }\n"): (typeof documents)["\n query EditTeamPage($uuid: GlobalId!) {\n team(uuid: $uuid) {\n data {\n ...TeamEditorFragment\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query EditTeamPage($uuid: String!) {\n team(uuid: $uuid) {\n data {\n ...TeamEditorFragment\n }\n }\n }\n"): (typeof documents)["\n query EditTeamPage($uuid: String!) {\n team(uuid: $uuid) {\n data {\n ...TeamEditorFragment\n }\n }\n }\n"]; +export function graphql(source: "\n query ViewTeamFundraisingDocument($teamUuid: GlobalId!) {\n team(uuid: $teamUuid) {\n data {\n # TODO: Add filtering and pagination\n fundraisingEntries(sendAll: true) {\n data {\n id\n amount\n donatedByText\n donatedToText\n donatedOn\n assignments {\n id\n amount\n person {\n name\n }\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query ViewTeamFundraisingDocument($teamUuid: GlobalId!) {\n team(uuid: $teamUuid) {\n data {\n # TODO: Add filtering and pagination\n fundraisingEntries(sendAll: true) {\n data {\n id\n amount\n donatedByText\n donatedToText\n donatedOn\n assignments {\n id\n amount\n person {\n name\n }\n }\n }\n }\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query ViewTeamPage($teamUuid: String!) {\n team(uuid: $teamUuid) {\n data {\n ...TeamViewerFragment\n pointEntries {\n ...PointEntryTableFragment\n }\n }\n }\n }\n"): (typeof documents)["\n query ViewTeamPage($teamUuid: String!) {\n team(uuid: $teamUuid) {\n data {\n ...TeamViewerFragment\n pointEntries {\n ...PointEntryTableFragment\n }\n }\n }\n }\n"]; +export function graphql(source: "\n query ViewTeamPage($teamUuid: GlobalId!) {\n team(uuid: $teamUuid) {\n data {\n ...TeamViewerFragment\n pointEntries {\n ...PointEntryTableFragment\n }\n }\n }\n }\n"): (typeof documents)["\n query ViewTeamPage($teamUuid: GlobalId!) {\n team(uuid: $teamUuid) {\n data {\n ...TeamViewerFragment\n pointEntries {\n ...PointEntryTableFragment\n }\n }\n }\n }\n"]; export function graphql(source: string) { return (documents as any)[source] ?? {}; diff --git a/packages/common/lib/graphql-client-admin/graphql.ts b/packages/common/lib/graphql-client-portal/graphql.ts similarity index 62% rename from packages/common/lib/graphql-client-admin/graphql.ts rename to packages/common/lib/graphql-client-portal/graphql.ts index 4dd61f05..5469e760 100644 --- a/packages/common/lib/graphql-client-admin/graphql.ts +++ b/packages/common/lib/graphql-client-portal/graphql.ts @@ -1,8 +1,6 @@ /* eslint-disable */ import type { AuthSource } from '../index.js'; import type { DbRole } from '../index.js'; -import type { CommitteeRole } from '../index.js'; -import type { CommitteeIdentifier } from '../index.js'; import type { MembershipPositionType } from '../index.js'; import type { TeamLegacyStatus } from '../index.js'; import type { TeamType } from '../index.js'; @@ -28,10 +26,8 @@ export type Scalars = { DateTimeISO: { input: Date | string; output: Date | string; } /** A field whose value conforms to the standard internet email address format as specified in HTML Spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address. */ EmailAddress: { input: string; output: string; } - /** Date range custom scalar type (just an ISO 8601 interval) */ - LuxonDateRange: { input: string; output: string; } - /** Luxon DateTime custom scalar type */ - LuxonDateTime: { input: string; output: string; } + /** GlobalId custom scalar type */ + GlobalId: { input: string; output: string; } /** Integers that will have a value of 0 or more. */ NonNegativeInt: { input: number; output: number; } /** Integers that will have a value greater than 0. */ @@ -83,43 +79,82 @@ export type AcknowledgeDeliveryIssueResponse = AbstractGraphQlOkResponse & Graph export type AddEventImageResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'AddEventImageResponse'; - readonly data: ImageResource; + readonly data: ImageNode; readonly ok: Scalars['Boolean']['output']; }; -export type AuthIdPairResource = { - readonly __typename?: 'AuthIdPairResource'; - readonly source: AuthSource; - readonly value: Scalars['String']['output']; +export type AssignEntryToPersonInput = { + readonly amount: Scalars['Float']['input']; }; export { AuthSource }; -export { CommitteeIdentifier }; +/** The identifier for a committee */ +export const CommitteeIdentifier = { + CommunityDevelopmentCommittee: 'communityDevelopmentCommittee', + CorporateCommittee: 'corporateCommittee', + DancerRelationsCommittee: 'dancerRelationsCommittee', + FamilyRelationsCommittee: 'familyRelationsCommittee', + FundraisingCommittee: 'fundraisingCommittee', + MarketingCommittee: 'marketingCommittee', + MiniMarathonsCommittee: 'miniMarathonsCommittee', + OperationsCommittee: 'operationsCommittee', + OverallCommittee: 'overallCommittee', + ProgrammingCommittee: 'programmingCommittee', + TechCommittee: 'techCommittee', + ViceCommittee: 'viceCommittee' +} as const; + +export type CommitteeIdentifier = typeof CommitteeIdentifier[keyof typeof CommitteeIdentifier]; +export type CommitteeMembershipNode = Node & { + readonly __typename?: 'CommitteeMembershipNode'; + readonly createdAt?: Maybe; + readonly id: Scalars['GlobalId']['output']; + readonly identifier: CommitteeIdentifier; + readonly person: PersonNode; + readonly position: MembershipPositionType; + readonly role: CommitteeRole; + readonly team: TeamNode; + readonly updatedAt?: Maybe; +}; + +export type CommitteeNode = Node & { + readonly __typename?: 'CommitteeNode'; + readonly createdAt?: Maybe; + readonly id: Scalars['GlobalId']['output']; + readonly identifier: CommitteeIdentifier; + readonly updatedAt?: Maybe; +}; -export { CommitteeRole }; +/** Roles within a committee */ +export const CommitteeRole = { + Chair: 'Chair', + Coordinator: 'Coordinator', + Member: 'Member' +} as const; -export type ConfigurationResource = { - readonly __typename?: 'ConfigurationResource'; +export type CommitteeRole = typeof CommitteeRole[keyof typeof CommitteeRole]; +export type ConfigurationNode = Node & { + readonly __typename?: 'ConfigurationNode'; readonly createdAt?: Maybe; + readonly id: Scalars['GlobalId']['output']; readonly key: Scalars['String']['output']; readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; - readonly validAfter?: Maybe; - readonly validUntil?: Maybe; + readonly validAfter?: Maybe; + readonly validUntil?: Maybe; readonly value: Scalars['String']['output']; }; export type CreateConfigurationInput = { readonly key: Scalars['String']['input']; - readonly validAfter?: InputMaybe; - readonly validUntil?: InputMaybe; + readonly validAfter?: InputMaybe; + readonly validUntil?: InputMaybe; readonly value: Scalars['String']['input']; }; export type CreateConfigurationResponse = AbstractGraphQlCreatedResponse & AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'CreateConfigurationResponse'; - readonly data: ConfigurationResource; + readonly data: ConfigurationNode; readonly ok: Scalars['Boolean']['output']; readonly uuid: Scalars['String']['output']; }; @@ -134,12 +169,12 @@ export type CreateEventInput = { export type CreateEventOccurrenceInput = { readonly fullDay: Scalars['Boolean']['input']; - readonly interval: Scalars['LuxonDateRange']['input']; + readonly interval: IntervalIsoInput; }; export type CreateEventResponse = AbstractGraphQlCreatedResponse & AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'CreateEventResponse'; - readonly data: EventResource; + readonly data: EventNode; readonly ok: Scalars['Boolean']['output']; readonly uuid: Scalars['String']['output']; }; @@ -170,18 +205,11 @@ export type CreateMarathonInput = { export type CreatePersonInput = { readonly captainOf?: ReadonlyArray; + readonly dbRole?: InputMaybe; readonly email: Scalars['EmailAddress']['input']; readonly linkblue?: InputMaybe; readonly memberOf?: ReadonlyArray; readonly name?: InputMaybe; - readonly role?: InputMaybe; -}; - -export type CreatePersonResponse = AbstractGraphQlCreatedResponse & AbstractGraphQlOkResponse & GraphQlBaseResponse & { - readonly __typename?: 'CreatePersonResponse'; - readonly data: PersonResource; - readonly ok: Scalars['Boolean']['output']; - readonly uuid: Scalars['String']['output']; }; export type CreatePointEntryInput = { @@ -194,7 +222,7 @@ export type CreatePointEntryInput = { export type CreatePointEntryResponse = AbstractGraphQlCreatedResponse & AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'CreatePointEntryResponse'; - readonly data: PointEntryResource; + readonly data: PointEntryNode; readonly ok: Scalars['Boolean']['output']; readonly uuid: Scalars['String']['output']; }; @@ -202,32 +230,37 @@ export type CreatePointEntryResponse = AbstractGraphQlCreatedResponse & Abstract export type CreatePointOpportunityInput = { readonly eventUuid?: InputMaybe; readonly name: Scalars['String']['input']; - readonly opportunityDate?: InputMaybe; + readonly opportunityDate?: InputMaybe; readonly type: TeamType; }; export type CreatePointOpportunityResponse = AbstractGraphQlCreatedResponse & AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'CreatePointOpportunityResponse'; - readonly data: PointOpportunityResource; + readonly data: PointOpportunityNode; readonly ok: Scalars['Boolean']['output']; readonly uuid: Scalars['String']['output']; }; export type CreateTeamInput = { readonly legacyStatus: TeamLegacyStatus; - readonly marathonYear: Scalars['String']['input']; readonly name: Scalars['String']['input']; - readonly persistentIdentifier?: InputMaybe; readonly type: TeamType; }; export type CreateTeamResponse = AbstractGraphQlCreatedResponse & AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'CreateTeamResponse'; - readonly data: TeamResource; + readonly data: TeamNode; readonly ok: Scalars['Boolean']['output']; readonly uuid: Scalars['String']['output']; }; +export type DbFundsTeamInfo = Node & { + readonly __typename?: 'DbFundsTeamInfo'; + readonly dbNum: Scalars['Int']['output']; + readonly id: Scalars['GlobalId']['output']; + readonly name: Scalars['String']['output']; +}; + export { DbRole }; export type DeleteConfigurationResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { @@ -256,11 +289,6 @@ export type DeleteNotificationResponse = AbstractGraphQlOkResponse & GraphQlBase readonly ok: Scalars['Boolean']['output']; }; -export type DeletePersonResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { - readonly __typename?: 'DeletePersonResponse'; - readonly ok: Scalars['Boolean']['output']; -}; - export type DeletePointEntryResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'DeletePointEntryResponse'; readonly ok: Scalars['Boolean']['output']; @@ -276,6 +304,23 @@ export type DeleteTeamResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse readonly ok: Scalars['Boolean']['output']; }; +export type DeviceNode = Node & { + readonly __typename?: 'DeviceNode'; + readonly createdAt?: Maybe; + readonly id: Scalars['GlobalId']['output']; + readonly lastLoggedInUser?: Maybe; + readonly lastLogin?: Maybe; + readonly notificationDeliveries: ReadonlyArray; + readonly updatedAt?: Maybe; +}; + + +export type DeviceNodeNotificationDeliveriesArgs = { + page?: InputMaybe; + pageSize?: InputMaybe; + verifier?: InputMaybe; +}; + export const DeviceResolverAllKeys = { CreatedAt: 'createdAt', ExpoPushToken: 'expoPushToken', @@ -298,7 +343,7 @@ export type DeviceResolverKeyedDateFilterItem = { readonly field: DeviceResolverDateFilterKeys; /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ readonly negate?: InputMaybe; - readonly value: Scalars['LuxonDateTime']['input']; + readonly value: Scalars['DateTimeISO']['input']; }; export type DeviceResolverKeyedIsNullFilterItem = { @@ -331,29 +376,30 @@ export const DeviceResolverStringFilterKeys = { } as const; export type DeviceResolverStringFilterKeys = typeof DeviceResolverStringFilterKeys[keyof typeof DeviceResolverStringFilterKeys]; -export type DeviceResource = { - readonly __typename?: 'DeviceResource'; - readonly createdAt?: Maybe; - readonly expoPushToken?: Maybe; - readonly lastLoggedInUser?: Maybe; - readonly lastLogin?: Maybe; - readonly notificationDeliveries: ReadonlyArray; - readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; +export type EffectiveCommitteeRole = { + readonly __typename?: 'EffectiveCommitteeRole'; + readonly identifier: CommitteeIdentifier; + readonly role: CommitteeRole; }; - -export type DeviceResourceNotificationDeliveriesArgs = { - page?: InputMaybe; - pageSize?: InputMaybe; - verifier?: InputMaybe; +export type EventNode = Node & { + readonly __typename?: 'EventNode'; + readonly createdAt?: Maybe; + readonly description?: Maybe; + readonly id: Scalars['GlobalId']['output']; + readonly images: ReadonlyArray; + readonly location?: Maybe; + readonly occurrences: ReadonlyArray; + readonly summary?: Maybe; + readonly title: Scalars['String']['output']; + readonly updatedAt?: Maybe; }; -export type EventOccurrenceResource = { - readonly __typename?: 'EventOccurrenceResource'; +export type EventOccurrenceNode = { + readonly __typename?: 'EventOccurrenceNode'; readonly fullDay: Scalars['Boolean']['output']; - readonly interval: Scalars['LuxonDateRange']['output']; - readonly uuid: Scalars['ID']['output']; + readonly id: Scalars['ID']['output']; + readonly interval: IntervalIso; }; export const EventResolverAllKeys = { @@ -385,7 +431,7 @@ export type EventResolverKeyedDateFilterItem = { readonly field: EventResolverDateFilterKeys; /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ readonly negate?: InputMaybe; - readonly value: Scalars['LuxonDateTime']['input']; + readonly value: Scalars['DateTimeISO']['input']; }; export type EventResolverKeyedIsNullFilterItem = { @@ -421,80 +467,156 @@ export const EventResolverStringFilterKeys = { } as const; export type EventResolverStringFilterKeys = typeof EventResolverStringFilterKeys[keyof typeof EventResolverStringFilterKeys]; -export type EventResource = { - readonly __typename?: 'EventResource'; +export type FeedNode = Node & { + readonly __typename?: 'FeedNode'; readonly createdAt?: Maybe; - readonly description?: Maybe; - readonly images: ReadonlyArray; - readonly location?: Maybe; - readonly occurrences: ReadonlyArray; - readonly summary?: Maybe; + readonly id: Scalars['GlobalId']['output']; + readonly image?: Maybe; + readonly textContent?: Maybe; readonly title: Scalars['String']['output']; readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; }; -export type FeedResource = { - readonly __typename?: 'FeedResource'; +export type FundraisingAssignmentNode = Node & { + readonly __typename?: 'FundraisingAssignmentNode'; + readonly amount: Scalars['Float']['output']; readonly createdAt?: Maybe; - readonly image?: Maybe; - readonly textContent?: Maybe; - readonly title: Scalars['String']['output']; + readonly entry: FundraisingEntryNode; + readonly id: Scalars['GlobalId']['output']; + /** The person assigned to this assignment, only null when access is denied */ + readonly person?: Maybe; + readonly updatedAt?: Maybe; +}; + +export type FundraisingEntryNode = Node & { + readonly __typename?: 'FundraisingEntryNode'; + readonly amount: Scalars['Float']['output']; + readonly assignments: ReadonlyArray; + readonly createdAt?: Maybe; + readonly donatedByText?: Maybe; + readonly donatedOn: Scalars['DateTimeISO']['output']; + readonly donatedToText?: Maybe; + readonly id: Scalars['GlobalId']['output']; readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; }; +export const FundraisingEntryResolverAllKeys = { + Amount: 'amount', + CreatedAt: 'createdAt', + DonatedBy: 'donatedBy', + DonatedOn: 'donatedOn', + DonatedTo: 'donatedTo', + UpdatedAt: 'updatedAt' +} as const; + +export type FundraisingEntryResolverAllKeys = typeof FundraisingEntryResolverAllKeys[keyof typeof FundraisingEntryResolverAllKeys]; +export const FundraisingEntryResolverDateFilterKeys = { + CreatedAt: 'createdAt', + DonatedOn: 'donatedOn', + UpdatedAt: 'updatedAt' +} as const; + +export type FundraisingEntryResolverDateFilterKeys = typeof FundraisingEntryResolverDateFilterKeys[keyof typeof FundraisingEntryResolverDateFilterKeys]; +export type FundraisingEntryResolverKeyedDateFilterItem = { + /** The comparator to use for the filter */ + readonly comparison: NumericComparator; + /** The field to filter on */ + readonly field: FundraisingEntryResolverDateFilterKeys; + /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ + readonly negate?: InputMaybe; + readonly value: Scalars['DateTimeISO']['input']; +}; + +export type FundraisingEntryResolverKeyedIsNullFilterItem = { + /** The field to filter on */ + readonly field: FundraisingEntryResolverAllKeys; + /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ + readonly negate?: InputMaybe; +}; + +export type FundraisingEntryResolverKeyedNumericFilterItem = { + /** The comparator to use for the filter */ + readonly comparison: NumericComparator; + /** The field to filter on */ + readonly field: FundraisingEntryResolverNumericFilterKeys; + /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ + readonly negate?: InputMaybe; + readonly value: Scalars['Float']['input']; +}; + +export type FundraisingEntryResolverKeyedOneOfFilterItem = { + /** The field to filter on */ + readonly field: FundraisingEntryResolverOneOfFilterKeys; + /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ + readonly negate?: InputMaybe; + readonly value: ReadonlyArray; +}; + +export type FundraisingEntryResolverKeyedStringFilterItem = { + /** The comparator to use for the filter */ + readonly comparison: StringComparator; + /** The field to filter on */ + readonly field: FundraisingEntryResolverStringFilterKeys; + /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ + readonly negate?: InputMaybe; + readonly value: Scalars['String']['input']; +}; + +export const FundraisingEntryResolverNumericFilterKeys = { + Amount: 'amount' +} as const; + +export type FundraisingEntryResolverNumericFilterKeys = typeof FundraisingEntryResolverNumericFilterKeys[keyof typeof FundraisingEntryResolverNumericFilterKeys]; +export const FundraisingEntryResolverOneOfFilterKeys = { + TeamId: 'teamId' +} as const; + +export type FundraisingEntryResolverOneOfFilterKeys = typeof FundraisingEntryResolverOneOfFilterKeys[keyof typeof FundraisingEntryResolverOneOfFilterKeys]; +export const FundraisingEntryResolverStringFilterKeys = { + DonatedBy: 'donatedBy', + DonatedTo: 'donatedTo' +} as const; + +export type FundraisingEntryResolverStringFilterKeys = typeof FundraisingEntryResolverStringFilterKeys[keyof typeof FundraisingEntryResolverStringFilterKeys]; export type GetAllConfigurationsResponse = AbstractGraphQlArrayOkResponse & GraphQlBaseResponse & { readonly __typename?: 'GetAllConfigurationsResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; }; export type GetConfigurationByUuidResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'GetConfigurationByUuidResponse'; - readonly data: ConfigurationResource; + readonly data: ConfigurationNode; readonly ok: Scalars['Boolean']['output']; }; export type GetDeviceByUuidResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'GetDeviceByUuidResponse'; - readonly data: DeviceResource; + readonly data: DeviceNode; readonly ok: Scalars['Boolean']['output']; }; export type GetEventByUuidResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'GetEventByUuidResponse'; - readonly data: EventResource; + readonly data: EventNode; readonly ok: Scalars['Boolean']['output']; }; export type GetImageByUuidResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'GetImageByUuidResponse'; - readonly data: ImageResource; + readonly data: ImageNode; readonly ok: Scalars['Boolean']['output']; }; export type GetNotificationByUuidResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'GetNotificationByUuidResponse'; - readonly data: NotificationResource; - readonly ok: Scalars['Boolean']['output']; -}; - -export type GetPeopleResponse = AbstractGraphQlArrayOkResponse & GraphQlBaseResponse & { - readonly __typename?: 'GetPeopleResponse'; - readonly data: ReadonlyArray; - readonly ok: Scalars['Boolean']['output']; -}; - -export type GetPersonResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { - readonly __typename?: 'GetPersonResponse'; - readonly data?: Maybe; + readonly data: NotificationNode; readonly ok: Scalars['Boolean']['output']; }; export type GetPointEntryByUuidResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'GetPointEntryByUuidResponse'; - readonly data: PointEntryResource; + readonly data: PointEntryNode; readonly ok: Scalars['Boolean']['output']; }; @@ -503,6 +625,19 @@ export type GraphQlBaseResponse = { readonly ok: Scalars['Boolean']['output']; }; +export type ImageNode = Node & { + readonly __typename?: 'ImageNode'; + readonly alt?: Maybe; + readonly createdAt?: Maybe; + readonly height: Scalars['Int']['output']; + readonly id: Scalars['GlobalId']['output']; + readonly mimeType: Scalars['String']['output']; + readonly thumbHash?: Maybe; + readonly updatedAt?: Maybe; + readonly url?: Maybe; + readonly width: Scalars['Int']['output']; +}; + export const ImageResolverAllKeys = { Alt: 'alt', CreatedAt: 'createdAt', @@ -525,7 +660,7 @@ export type ImageResolverKeyedDateFilterItem = { readonly field: ImageResolverDateFilterKeys; /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ readonly negate?: InputMaybe; - readonly value: Scalars['LuxonDateTime']['input']; + readonly value: Scalars['DateTimeISO']['input']; }; export type ImageResolverKeyedIsNullFilterItem = { @@ -574,22 +709,20 @@ export const ImageResolverStringFilterKeys = { } as const; export type ImageResolverStringFilterKeys = typeof ImageResolverStringFilterKeys[keyof typeof ImageResolverStringFilterKeys]; -export type ImageResource = { - readonly __typename?: 'ImageResource'; - readonly alt?: Maybe; - readonly createdAt?: Maybe; - readonly height: Scalars['Int']['output']; - readonly mimeType: Scalars['String']['output']; - readonly thumbHash?: Maybe; - readonly updatedAt?: Maybe; - readonly url?: Maybe; - readonly uuid: Scalars['ID']['output']; - readonly width: Scalars['Int']['output']; +export type IntervalIso = { + readonly __typename?: 'IntervalISO'; + readonly end: Scalars['DateTimeISO']['output']; + readonly start: Scalars['DateTimeISO']['output']; +}; + +export type IntervalIsoInput = { + readonly end: Scalars['DateTimeISO']['input']; + readonly start: Scalars['DateTimeISO']['input']; }; export type ListDevicesResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListDevicesResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -601,7 +734,19 @@ export type ListDevicesResponse = AbstractGraphQlArrayOkResponse & AbstractGraph export type ListEventsResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListEventsResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; + readonly ok: Scalars['Boolean']['output']; + /** The current page number (1-indexed) */ + readonly page: Scalars['PositiveInt']['output']; + /** The number of items per page */ + readonly pageSize: Scalars['NonNegativeInt']['output']; + /** The total number of items */ + readonly total: Scalars['NonNegativeInt']['output']; +}; + +export type ListFundraisingEntriesResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { + readonly __typename?: 'ListFundraisingEntriesResponse'; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -613,7 +758,7 @@ export type ListEventsResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQ export type ListImagesResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListImagesResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -625,7 +770,7 @@ export type ListImagesResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQ export type ListMarathonsResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListMarathonsResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -637,7 +782,7 @@ export type ListMarathonsResponse = AbstractGraphQlArrayOkResponse & AbstractGra export type ListNotificationDeliveriesResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListNotificationDeliveriesResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -649,7 +794,7 @@ export type ListNotificationDeliveriesResponse = AbstractGraphQlArrayOkResponse export type ListNotificationsResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListNotificationsResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -661,7 +806,7 @@ export type ListNotificationsResponse = AbstractGraphQlArrayOkResponse & Abstrac export type ListPeopleResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListPeopleResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -673,7 +818,7 @@ export type ListPeopleResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQ export type ListPointEntriesResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListPointEntriesResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -685,7 +830,7 @@ export type ListPointEntriesResponse = AbstractGraphQlArrayOkResponse & Abstract export type ListPointOpportunitiesResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListPointOpportunitiesResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -697,7 +842,7 @@ export type ListPointOpportunitiesResponse = AbstractGraphQlArrayOkResponse & Ab export type ListTeamsResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQlPaginatedResponse & GraphQlBaseResponse & { readonly __typename?: 'ListTeamsResponse'; - readonly data: ReadonlyArray; + readonly data: ReadonlyArray; readonly ok: Scalars['Boolean']['output']; /** The current page number (1-indexed) */ readonly page: Scalars['PositiveInt']['output']; @@ -710,20 +855,44 @@ export type ListTeamsResponse = AbstractGraphQlArrayOkResponse & AbstractGraphQl export type LoginState = { readonly __typename?: 'LoginState'; readonly authSource: AuthSource; + readonly dbRole: DbRole; + readonly effectiveCommitteeRoles: ReadonlyArray; readonly loggedIn: Scalars['Boolean']['output']; - readonly role: RoleResource; }; -export type MarathonHourResource = { - readonly __typename?: 'MarathonHourResource'; +export type MarathonHourNode = Node & { + readonly __typename?: 'MarathonHourNode'; readonly createdAt?: Maybe; readonly details?: Maybe; readonly durationInfo: Scalars['String']['output']; - readonly mapImages: ReadonlyArray; + readonly id: Scalars['GlobalId']['output']; + readonly mapImages: ReadonlyArray; readonly shownStartingAt: Scalars['DateTimeISO']['output']; readonly title: Scalars['String']['output']; readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; +}; + +export type MarathonNode = Node & { + readonly __typename?: 'MarathonNode'; + readonly communityDevelopmentCommitteeTeam: TeamNode; + readonly corporateCommitteeTeam: TeamNode; + readonly createdAt?: Maybe; + readonly dancerRelationsCommitteeTeam: TeamNode; + readonly endDate?: Maybe; + readonly familyRelationsCommitteeTeam: TeamNode; + readonly fundraisingCommitteeTeam: TeamNode; + readonly hours: ReadonlyArray; + readonly id: Scalars['GlobalId']['output']; + readonly marketingCommitteeTeam: TeamNode; + readonly miniMarathonsCommitteeTeam: TeamNode; + readonly operationsCommitteeTeam: TeamNode; + readonly overallCommitteeTeam: TeamNode; + readonly programmingCommitteeTeam: TeamNode; + readonly startDate?: Maybe; + readonly techCommitteeTeam: TeamNode; + readonly updatedAt?: Maybe; + readonly viceCommitteeTeam: TeamNode; + readonly year: Scalars['String']['output']; }; export const MarathonResolverAllKeys = { @@ -750,7 +919,7 @@ export type MarathonResolverKeyedDateFilterItem = { readonly field: MarathonResolverDateFilterKeys; /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ readonly negate?: InputMaybe; - readonly value: Scalars['LuxonDateTime']['input']; + readonly value: Scalars['DateTimeISO']['input']; }; export type MarathonResolverKeyedIsNullFilterItem = { @@ -760,44 +929,36 @@ export type MarathonResolverKeyedIsNullFilterItem = { readonly negate?: InputMaybe; }; -export type MarathonResource = { - readonly __typename?: 'MarathonResource'; +export type MembershipNode = Node & { + readonly __typename?: 'MembershipNode'; readonly createdAt?: Maybe; - readonly endDate: Scalars['DateTimeISO']['output']; - readonly hours: ReadonlyArray; - readonly startDate: Scalars['DateTimeISO']['output']; + readonly id: Scalars['GlobalId']['output']; + readonly person: PersonNode; + readonly position: MembershipPositionType; + readonly team: TeamNode; readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; - readonly year: Scalars['String']['output']; }; export { MembershipPositionType }; -export type MembershipResource = { - readonly __typename?: 'MembershipResource'; - readonly createdAt?: Maybe; - readonly person: PersonResource; - readonly position: MembershipPositionType; - readonly team: TeamResource; - readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; -}; - export type Mutation = { readonly __typename?: 'Mutation'; readonly abortScheduledNotification: AbortScheduledNotificationResponse; readonly acknowledgeDeliveryIssue: AcknowledgeDeliveryIssueResponse; readonly addExistingImageToEvent: AddEventImageResponse; - readonly addMap: MarathonHourResource; - readonly attachImageToFeedItem: FeedResource; + readonly addMap: MarathonHourNode; + readonly addPersonToTeam: MembershipNode; + readonly assignEntryToPerson: FundraisingAssignmentNode; + readonly assignTeamToDbFundsTeam: Scalars['Void']['output']; + readonly attachImageToFeedItem: FeedNode; readonly createConfiguration: CreateConfigurationResponse; readonly createConfigurations: CreateConfigurationResponse; readonly createEvent: CreateEventResponse; - readonly createFeedItem: FeedResource; - readonly createImage: ImageResource; - readonly createMarathon: MarathonResource; - readonly createMarathonHour: MarathonHourResource; - readonly createPerson: CreatePersonResponse; + readonly createFeedItem: FeedNode; + readonly createImage: ImageNode; + readonly createMarathon: MarathonNode; + readonly createMarathonHour: MarathonHourNode; + readonly createPerson: PersonNode; readonly createPointEntry: CreatePointEntryResponse; readonly createPointOpportunity: CreatePointOpportunityResponse; readonly createTeam: CreateTeamResponse; @@ -805,41 +966,43 @@ export type Mutation = { readonly deleteDevice: DeleteDeviceResponse; readonly deleteEvent: DeleteEventResponse; readonly deleteFeedItem: Scalars['Boolean']['output']; + readonly deleteFundraisingAssignment: FundraisingAssignmentNode; readonly deleteImage: DeleteImageResponse; readonly deleteMarathon: Scalars['Void']['output']; readonly deleteMarathonHour: Scalars['Void']['output']; readonly deleteNotification: DeleteNotificationResponse; - readonly deletePerson: DeletePersonResponse; + readonly deletePerson: PersonNode; readonly deletePointEntry: DeletePointEntryResponse; readonly deletePointOpportunity: DeletePointOpportunityResponse; readonly deleteTeam: DeleteTeamResponse; readonly registerDevice: RegisterDeviceResponse; readonly removeImageFromEvent: RemoveEventImageResponse; - readonly removeImageFromFeedItem: FeedResource; + readonly removeImageFromFeedItem: FeedNode; readonly removeMap: Scalars['Void']['output']; readonly scheduleNotification: ScheduleNotificationResponse; /** Send a notification immediately. */ readonly sendNotification: SendNotificationResponse; readonly setEvent: SetEventResponse; - readonly setFeedItem: FeedResource; - readonly setImageAltText: ImageResource; - readonly setImageUrl: ImageResource; - readonly setMarathon: MarathonResource; - readonly setMarathonHour: MarathonHourResource; - readonly setPerson: GetPersonResponse; + readonly setFeedItem: FeedNode; + readonly setImageAltText: ImageNode; + readonly setImageUrl: ImageNode; + readonly setMarathon: MarathonNode; + readonly setMarathonHour: MarathonHourNode; + readonly setPerson: PersonNode; readonly setPointOpportunity: SinglePointOpportunityResponse; readonly setTeam: SingleTeamResponse; readonly stageNotification: StageNotificationResponse; + readonly updateFundraisingAssignment: FundraisingAssignmentNode; }; export type MutationAbortScheduledNotificationArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationAcknowledgeDeliveryIssueArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -851,7 +1014,26 @@ export type MutationAddExistingImageToEventArgs = { export type MutationAddMapArgs = { imageUuid: Scalars['String']['input']; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; +}; + + +export type MutationAddPersonToTeamArgs = { + personUuid: Scalars['String']['input']; + teamUuid: Scalars['String']['input']; +}; + + +export type MutationAssignEntryToPersonArgs = { + entryId: Scalars['String']['input']; + input: AssignEntryToPersonInput; + personId: Scalars['String']['input']; +}; + + +export type MutationAssignTeamToDbFundsTeamArgs = { + dbFundsTeamId: Scalars['Float']['input']; + teamId: Scalars['String']['input']; }; @@ -914,21 +1096,22 @@ export type MutationCreatePointOpportunityArgs = { export type MutationCreateTeamArgs = { input: CreateTeamInput; + marathon: Scalars['String']['input']; }; export type MutationDeleteConfigurationArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeleteDeviceArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeleteEventArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -937,44 +1120,49 @@ export type MutationDeleteFeedItemArgs = { }; +export type MutationDeleteFundraisingAssignmentArgs = { + id: Scalars['GlobalId']['input']; +}; + + export type MutationDeleteImageArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeleteMarathonArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeleteMarathonHourArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeleteNotificationArgs = { force?: InputMaybe; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeletePersonArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeletePointEntryArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeletePointOpportunityArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationDeleteTeamArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -996,24 +1184,24 @@ export type MutationRemoveImageFromFeedItemArgs = { export type MutationRemoveMapArgs = { imageUuid: Scalars['String']['input']; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationScheduleNotificationArgs = { sendAt: Scalars['DateTimeISO']['input']; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationSendNotificationArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationSetEventArgs = { input: SetEventInput; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1025,42 +1213,42 @@ export type MutationSetFeedItemArgs = { export type MutationSetImageAltTextArgs = { alt: Scalars['String']['input']; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationSetImageUrlArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationSetMarathonArgs = { input: SetMarathonInput; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationSetMarathonHourArgs = { input: SetMarathonHourInput; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationSetPersonArgs = { input: SetPersonInput; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationSetPointOpportunityArgs = { input: SetPointOpportunityInput; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; export type MutationSetTeamArgs = { input: SetTeamInput; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1071,6 +1259,16 @@ export type MutationStageNotificationArgs = { url?: InputMaybe; }; + +export type MutationUpdateFundraisingAssignmentArgs = { + id: Scalars['GlobalId']['input']; + input: UpdateFundraisingAssignmentInput; +}; + +export type Node = { + readonly id: Scalars['GlobalId']['output']; +}; + export type NotificationAudienceInput = { readonly all?: InputMaybe; readonly memberOfTeamType?: InputMaybe; @@ -1089,6 +1287,22 @@ export type NotificationDeliveryIssueCount = { readonly Unknown: Scalars['Int']['output']; }; +export type NotificationDeliveryNode = Node & { + readonly __typename?: 'NotificationDeliveryNode'; + /** A unique identifier corresponding the group of notifications this was sent to Expo with. */ + readonly chunkUuid?: Maybe; + readonly createdAt?: Maybe; + /** Any error message returned by Expo when sending the notification. */ + readonly deliveryError?: Maybe; + readonly id: Scalars['GlobalId']['output']; + readonly notification: NotificationNode; + /** The time the server received a delivery receipt from the user. */ + readonly receiptCheckedAt?: Maybe; + /** The time the server sent the notification to Expo for delivery. */ + readonly sentAt?: Maybe; + readonly updatedAt?: Maybe; +}; + export const NotificationDeliveryResolverAllKeys = { CreatedAt: 'createdAt', DeliveryError: 'deliveryError', @@ -1113,7 +1327,7 @@ export type NotificationDeliveryResolverKeyedDateFilterItem = { readonly field: NotificationDeliveryResolverDateFilterKeys; /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ readonly negate?: InputMaybe; - readonly value: Scalars['LuxonDateTime']['input']; + readonly value: Scalars['DateTimeISO']['input']; }; export type NotificationDeliveryResolverKeyedIsNullFilterItem = { @@ -1123,20 +1337,22 @@ export type NotificationDeliveryResolverKeyedIsNullFilterItem = { readonly negate?: InputMaybe; }; -export type NotificationDeliveryResource = { - readonly __typename?: 'NotificationDeliveryResource'; - /** A unique identifier corresponding the group of notifications this was sent to Expo with. */ - readonly chunkUuid?: Maybe; +export type NotificationNode = Node & { + readonly __typename?: 'NotificationNode'; + readonly body: Scalars['String']['output']; readonly createdAt?: Maybe; - /** Any error message returned by Expo when sending the notification. */ - readonly deliveryError?: Maybe; - readonly notification: NotificationResource; - /** The time the server received a delivery receipt from the user. */ - readonly receiptCheckedAt?: Maybe; - /** The time the server sent the notification to Expo for delivery. */ - readonly sentAt?: Maybe; + readonly deliveryCount: Scalars['Int']['output']; + readonly deliveryIssue?: Maybe; + readonly deliveryIssueAcknowledgedAt?: Maybe; + readonly deliveryIssueCount: NotificationDeliveryIssueCount; + readonly id: Scalars['GlobalId']['output']; + /** The time the notification is scheduled to be sent, if null it is either already sent or unscheduled. */ + readonly sendAt?: Maybe; + /** The time the server started sending the notification. */ + readonly startedSendingAt?: Maybe; + readonly title: Scalars['String']['output']; readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; + readonly url?: Maybe; }; export const NotificationResolverAllKeys = { @@ -1165,7 +1381,7 @@ export type NotificationResolverKeyedDateFilterItem = { readonly field: NotificationResolverDateFilterKeys; /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ readonly negate?: InputMaybe; - readonly value: Scalars['LuxonDateTime']['input']; + readonly value: Scalars['DateTimeISO']['input']; }; export type NotificationResolverKeyedIsNullFilterItem = { @@ -1204,25 +1420,40 @@ export const NotificationResolverStringFilterKeys = { } as const; export type NotificationResolverStringFilterKeys = typeof NotificationResolverStringFilterKeys[keyof typeof NotificationResolverStringFilterKeys]; -export type NotificationResource = { - readonly __typename?: 'NotificationResource'; - readonly body: Scalars['String']['output']; +export { NumericComparator }; + +export type PersonNode = Node & { + readonly __typename?: 'PersonNode'; + readonly assignedDonationEntries?: Maybe; + readonly committees: ReadonlyArray; readonly createdAt?: Maybe; - readonly deliveryCount: Scalars['Int']['output']; - readonly deliveryIssue?: Maybe; - readonly deliveryIssueAcknowledgedAt?: Maybe; - readonly deliveryIssueCount: NotificationDeliveryIssueCount; - /** The time the notification is scheduled to be sent, if null it is either already sent or unscheduled. */ - readonly sendAt?: Maybe; - /** The time the server started sending the notification. */ - readonly startedSendingAt?: Maybe; - readonly title: Scalars['String']['output']; + readonly dbRole: DbRole; + readonly email: Scalars['String']['output']; + readonly fundraisingAssignments: ReadonlyArray; + readonly id: Scalars['GlobalId']['output']; + readonly linkblue?: Maybe; + readonly moraleTeams: ReadonlyArray; + readonly name?: Maybe; + readonly primaryCommittee?: Maybe; + readonly teams: ReadonlyArray; readonly updatedAt?: Maybe; - readonly url?: Maybe; - readonly uuid: Scalars['ID']['output']; }; -export { NumericComparator }; + +export type PersonNodeAssignedDonationEntriesArgs = { + booleanFilters?: InputMaybe; + dateFilters?: InputMaybe>; + includeDeleted?: InputMaybe; + isNullFilters?: InputMaybe>; + numericFilters?: InputMaybe>; + oneOfFilters?: InputMaybe>; + page?: InputMaybe; + pageSize?: InputMaybe; + sendAll?: InputMaybe; + sortBy?: InputMaybe>; + sortDirection?: InputMaybe>; + stringFilters?: InputMaybe>; +}; export const PersonResolverAllKeys = { CommitteeName: 'committeeName', @@ -1273,20 +1504,16 @@ export const PersonResolverStringFilterKeys = { } as const; export type PersonResolverStringFilterKeys = typeof PersonResolverStringFilterKeys[keyof typeof PersonResolverStringFilterKeys]; -export type PersonResource = { - readonly __typename?: 'PersonResource'; - /** @deprecated This is now provided on the AuthIdPair resource. */ - readonly authIds: ReadonlyArray; - /** @deprecated Use teams instead and filter by position */ - readonly captaincies: ReadonlyArray; +export type PointEntryNode = Node & { + readonly __typename?: 'PointEntryNode'; + readonly comment?: Maybe; readonly createdAt?: Maybe; - readonly email: Scalars['String']['output']; - readonly linkblue?: Maybe; - readonly name?: Maybe; - readonly role: RoleResource; - readonly teams: ReadonlyArray; + readonly id: Scalars['GlobalId']['output']; + readonly personFrom?: Maybe; + readonly pointOpportunity?: Maybe; + readonly points: Scalars['Int']['output']; + readonly team: TeamNode; readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; }; export const PointEntryResolverAllKeys = { @@ -1308,7 +1535,7 @@ export type PointEntryResolverKeyedDateFilterItem = { readonly field: PointEntryResolverDateFilterKeys; /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ readonly negate?: InputMaybe; - readonly value: Scalars['LuxonDateTime']['input']; + readonly value: Scalars['DateTimeISO']['input']; }; export type PointEntryResolverKeyedIsNullFilterItem = { @@ -1318,16 +1545,15 @@ export type PointEntryResolverKeyedIsNullFilterItem = { readonly negate?: InputMaybe; }; -export type PointEntryResource = { - readonly __typename?: 'PointEntryResource'; - readonly comment?: Maybe; +export type PointOpportunityNode = Node & { + readonly __typename?: 'PointOpportunityNode'; readonly createdAt?: Maybe; - readonly personFrom?: Maybe; - readonly pointOpportunity?: Maybe; - readonly points: Scalars['Int']['output']; - readonly team: TeamResource; + readonly event?: Maybe; + readonly id: Scalars['GlobalId']['output']; + readonly name: Scalars['String']['output']; + readonly opportunityDate?: Maybe; + readonly type: TeamType; readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; }; export const PointOpportunityResolverAllKeys = { @@ -1353,7 +1579,7 @@ export type PointOpportunityResolverKeyedDateFilterItem = { readonly field: PointOpportunityResolverDateFilterKeys; /** Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. */ readonly negate?: InputMaybe; - readonly value: Scalars['LuxonDateTime']['input']; + readonly value: Scalars['DateTimeISO']['input']; }; export type PointOpportunityResolverKeyedIsNullFilterItem = { @@ -1391,48 +1617,43 @@ export const PointOpportunityResolverStringFilterKeys = { } as const; export type PointOpportunityResolverStringFilterKeys = typeof PointOpportunityResolverStringFilterKeys[keyof typeof PointOpportunityResolverStringFilterKeys]; -export type PointOpportunityResource = { - readonly __typename?: 'PointOpportunityResource'; - readonly createdAt?: Maybe; - readonly event?: Maybe; - readonly name: Scalars['String']['output']; - readonly opportunityDate?: Maybe; - readonly type: TeamType; - readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; -}; - export type Query = { readonly __typename?: 'Query'; readonly activeConfiguration: GetConfigurationByUuidResponse; readonly allConfigurations: GetAllConfigurationsResponse; - readonly currentMarathon?: Maybe; - readonly currentMarathonHour?: Maybe; + readonly configuration: GetConfigurationByUuidResponse; + readonly currentMarathon?: Maybe; + readonly currentMarathonHour?: Maybe; + readonly dbFundsTeams: ReadonlyArray; readonly device: GetDeviceByUuidResponse; readonly devices: ListDevicesResponse; readonly event: GetEventByUuidResponse; readonly events: ListEventsResponse; - readonly feed: ReadonlyArray; + readonly feed: ReadonlyArray; + readonly fundraisingAssignment: FundraisingAssignmentNode; + readonly fundraisingEntries: ListFundraisingEntriesResponse; + readonly fundraisingEntry: FundraisingEntryNode; readonly image: GetImageByUuidResponse; readonly images: ListImagesResponse; + readonly latestMarathon?: Maybe; readonly listPeople: ListPeopleResponse; readonly loginState: LoginState; - readonly marathon: MarathonResource; - readonly marathonForYear: MarathonResource; - readonly marathonHour: MarathonHourResource; + readonly marathon: MarathonNode; + readonly marathonForYear: MarathonNode; + readonly marathonHour: MarathonHourNode; readonly marathons: ListMarathonsResponse; - readonly me: GetPersonResponse; - readonly nextMarathon?: Maybe; + readonly me?: Maybe; + readonly node: Node; readonly notification: GetNotificationByUuidResponse; readonly notificationDeliveries: ListNotificationDeliveriesResponse; readonly notifications: ListNotificationsResponse; - readonly person: GetPersonResponse; - readonly personByLinkBlue: GetPersonResponse; + readonly person: PersonNode; + readonly personByLinkBlue: PersonNode; readonly pointEntries: ListPointEntriesResponse; readonly pointEntry: GetPointEntryByUuidResponse; readonly pointOpportunities: ListPointOpportunitiesResponse; readonly pointOpportunity: SinglePointOpportunityResponse; - readonly searchPeopleByName: GetPeopleResponse; + readonly searchPeopleByName: ReadonlyArray; readonly team: SingleTeamResponse; readonly teams: ListTeamsResponse; }; @@ -1443,8 +1664,18 @@ export type QueryActiveConfigurationArgs = { }; +export type QueryConfigurationArgs = { + id: Scalars['GlobalId']['input']; +}; + + +export type QueryDbFundsTeamsArgs = { + search: Scalars['String']['input']; +}; + + export type QueryDeviceArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1465,7 +1696,7 @@ export type QueryDevicesArgs = { export type QueryEventArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1490,8 +1721,34 @@ export type QueryFeedArgs = { }; +export type QueryFundraisingAssignmentArgs = { + id: Scalars['GlobalId']['input']; +}; + + +export type QueryFundraisingEntriesArgs = { + booleanFilters?: InputMaybe; + dateFilters?: InputMaybe>; + includeDeleted?: InputMaybe; + isNullFilters?: InputMaybe>; + numericFilters?: InputMaybe>; + oneOfFilters?: InputMaybe>; + page?: InputMaybe; + pageSize?: InputMaybe; + sendAll?: InputMaybe; + sortBy?: InputMaybe>; + sortDirection?: InputMaybe>; + stringFilters?: InputMaybe>; +}; + + +export type QueryFundraisingEntryArgs = { + id: Scalars['GlobalId']['input']; +}; + + export type QueryImageArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1528,7 +1785,7 @@ export type QueryListPeopleArgs = { export type QueryMarathonArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1538,7 +1795,7 @@ export type QueryMarathonForYearArgs = { export type QueryMarathonHourArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1558,8 +1815,13 @@ export type QueryMarathonsArgs = { }; +export type QueryNodeArgs = { + id: Scalars['GlobalId']['input']; +}; + + export type QueryNotificationArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1597,7 +1859,7 @@ export type QueryNotificationsArgs = { export type QueryPersonArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1623,7 +1885,7 @@ export type QueryPointEntriesArgs = { export type QueryPointEntryArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1644,7 +1906,7 @@ export type QueryPointOpportunitiesArgs = { export type QueryPointOpportunityArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1654,7 +1916,7 @@ export type QuerySearchPeopleByNameArgs = { export type QueryTeamArgs = { - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }; @@ -1664,7 +1926,7 @@ export type QueryTeamsArgs = { includeDeleted?: InputMaybe; isNullFilters?: InputMaybe>; legacyStatus?: InputMaybe>; - marathonYear?: InputMaybe>; + marathonId?: InputMaybe>; numericFilters?: InputMaybe; oneOfFilters?: InputMaybe>; page?: InputMaybe; @@ -1689,7 +1951,7 @@ export type RegisterDeviceInput = { export type RegisterDeviceResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'RegisterDeviceResponse'; - readonly data: DeviceResource; + readonly data: DeviceNode; readonly ok: Scalars['Boolean']['output']; }; @@ -1699,19 +1961,6 @@ export type RemoveEventImageResponse = AbstractGraphQlOkResponse & GraphQlBaseRe readonly ok: Scalars['Boolean']['output']; }; -export type RoleResource = { - readonly __typename?: 'RoleResource'; - readonly committeeIdentifier?: Maybe; - readonly committeeRole?: Maybe; - readonly dbRole: DbRole; -}; - -export type RoleResourceInput = { - readonly committeeIdentifier?: InputMaybe; - readonly committeeRole?: InputMaybe; - readonly dbRole?: DbRole; -}; - export type ScheduleNotificationResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'ScheduleNotificationResponse'; readonly data: Scalars['Boolean']['output']; @@ -1734,14 +1983,14 @@ export type SetEventInput = { export type SetEventOccurrenceInput = { readonly fullDay: Scalars['Boolean']['input']; - readonly interval: Scalars['LuxonDateRange']['input']; + readonly interval: IntervalIsoInput; /** If updating an existing occurrence, the UUID of the occurrence to update */ readonly uuid?: InputMaybe; }; export type SetEventResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'SetEventResponse'; - readonly data: EventResource; + readonly data: EventNode; readonly ok: Scalars['Boolean']['output']; }; @@ -1769,19 +2018,17 @@ export type SetPersonInput = { readonly linkblue?: InputMaybe; readonly memberOf?: InputMaybe>; readonly name?: InputMaybe; - readonly role?: InputMaybe; }; export type SetPointOpportunityInput = { readonly eventUuid?: InputMaybe; readonly name?: InputMaybe; - readonly opportunityDate?: InputMaybe; + readonly opportunityDate?: InputMaybe; readonly type?: InputMaybe; }; export type SetTeamInput = { readonly legacyStatus?: InputMaybe; - readonly marathonYear?: InputMaybe; readonly name?: InputMaybe; readonly persistentIdentifier?: InputMaybe; readonly type?: InputMaybe; @@ -1789,13 +2036,13 @@ export type SetTeamInput = { export type SinglePointOpportunityResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'SinglePointOpportunityResponse'; - readonly data: PointOpportunityResource; + readonly data: PointOpportunityNode; readonly ok: Scalars['Boolean']['output']; }; export type SingleTeamResponse = AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'SingleTeamResponse'; - readonly data: TeamResource; + readonly data: TeamNode; readonly ok: Scalars['Boolean']['output']; }; @@ -1803,7 +2050,7 @@ export { SortDirection }; export type StageNotificationResponse = AbstractGraphQlCreatedResponse & AbstractGraphQlOkResponse & GraphQlBaseResponse & { readonly __typename?: 'StageNotificationResponse'; - readonly data: NotificationResource; + readonly data: NotificationNode; readonly ok: Scalars['Boolean']['output']; readonly uuid: Scalars['String']['output']; }; @@ -1812,9 +2059,42 @@ export { StringComparator }; export { TeamLegacyStatus }; +export type TeamNode = Node & { + readonly __typename?: 'TeamNode'; + /** @deprecated Just query the members field and filter by role */ + readonly captains: ReadonlyArray; + readonly createdAt?: Maybe; + readonly fundraisingEntries: ListFundraisingEntriesResponse; + readonly id: Scalars['GlobalId']['output']; + readonly legacyStatus: TeamLegacyStatus; + readonly marathon: MarathonNode; + readonly members: ReadonlyArray; + readonly name: Scalars['String']['output']; + readonly pointEntries: ReadonlyArray; + readonly totalPoints: Scalars['Int']['output']; + readonly type: TeamType; + readonly updatedAt?: Maybe; +}; + + +export type TeamNodeFundraisingEntriesArgs = { + booleanFilters?: InputMaybe; + dateFilters?: InputMaybe>; + includeDeleted?: InputMaybe; + isNullFilters?: InputMaybe>; + numericFilters?: InputMaybe>; + oneOfFilters?: InputMaybe>; + page?: InputMaybe; + pageSize?: InputMaybe; + sendAll?: InputMaybe; + sortBy?: InputMaybe>; + sortDirection?: InputMaybe>; + stringFilters?: InputMaybe>; +}; + export const TeamResolverAllKeys = { LegacyStatus: 'legacyStatus', - MarathonYear: 'marathonYear', + MarathonId: 'marathonId', Name: 'name', Type: 'type' } as const; @@ -1847,7 +2127,7 @@ export type TeamResolverKeyedStringFilterItem = { export const TeamResolverOneOfFilterKeys = { LegacyStatus: 'legacyStatus', - MarathonYear: 'marathonYear', + MarathonId: 'marathonId', Type: 'type' } as const; @@ -1857,40 +2137,39 @@ export const TeamResolverStringFilterKeys = { } as const; export type TeamResolverStringFilterKeys = typeof TeamResolverStringFilterKeys[keyof typeof TeamResolverStringFilterKeys]; -export type TeamResource = { - readonly __typename?: 'TeamResource'; - /** @deprecated Just query the members field and filter by role */ - readonly captains: ReadonlyArray; - readonly createdAt?: Maybe; - readonly legacyStatus: TeamLegacyStatus; - readonly marathonYear: Scalars['String']['output']; - readonly members: ReadonlyArray; - readonly name: Scalars['String']['output']; - readonly persistentIdentifier?: Maybe; - readonly pointEntries: ReadonlyArray; - readonly totalPoints: Scalars['Int']['output']; - readonly type: TeamType; - readonly updatedAt?: Maybe; - readonly uuid: Scalars['ID']['output']; +export { TeamType }; + +export type UpdateFundraisingAssignmentInput = { + readonly amount: Scalars['Float']['input']; }; -export { TeamType }; +export type ActiveMarathonQueryVariables = Exact<{ [key: string]: never; }>; + + +export type ActiveMarathonQuery = { readonly __typename?: 'Query', readonly latestMarathon?: { readonly __typename?: 'MarathonNode', readonly id: string, readonly year: string, readonly startDate?: Date | string | null, readonly endDate?: Date | string | null } | null, readonly marathons: { readonly __typename?: 'ListMarathonsResponse', readonly data: ReadonlyArray<{ readonly __typename?: 'MarathonNode', readonly id: string, readonly year: string }> } }; + +export type SelectedMarathonQueryVariables = Exact<{ + marathonId: Scalars['GlobalId']['input']; +}>; + + +export type SelectedMarathonQuery = { readonly __typename?: 'Query', readonly marathon: { readonly __typename?: 'MarathonNode', readonly id: string, readonly year: string, readonly startDate?: Date | string | null, readonly endDate?: Date | string | null } }; export type ImagePickerQueryVariables = Exact<{ stringFilters?: InputMaybe | ImageResolverKeyedStringFilterItem>; }>; -export type ImagePickerQuery = { readonly __typename?: 'Query', readonly images: { readonly __typename?: 'ListImagesResponse', readonly data: ReadonlyArray<{ readonly __typename?: 'ImageResource', readonly uuid: string, readonly alt?: string | null, readonly url?: URL | string | null }> } }; +export type ImagePickerQuery = { readonly __typename?: 'Query', readonly images: { readonly __typename?: 'ListImagesResponse', readonly data: ReadonlyArray<{ readonly __typename?: 'ImageNode', readonly id: string, readonly alt?: string | null, readonly url?: URL | string | null }> } }; export type PersonSearchQueryVariables = Exact<{ search: Scalars['String']['input']; }>; -export type PersonSearchQuery = { readonly __typename?: 'Query', readonly searchPeopleByName: { readonly __typename?: 'GetPeopleResponse', readonly data: ReadonlyArray<{ readonly __typename?: 'PersonResource', readonly uuid: string, readonly name?: string | null, readonly linkblue?: string | null }> }, readonly personByLinkBlue: { readonly __typename?: 'GetPersonResponse', readonly data?: { readonly __typename?: 'PersonResource', readonly uuid: string, readonly name?: string | null, readonly linkblue?: string | null } | null } }; +export type PersonSearchQuery = { readonly __typename?: 'Query', readonly searchPeopleByName: ReadonlyArray<{ readonly __typename?: 'PersonNode', readonly id: string, readonly name?: string | null, readonly linkblue?: string | null }>, readonly personByLinkBlue: { readonly __typename?: 'PersonNode', readonly id: string, readonly name?: string | null, readonly linkblue?: string | null } }; -export type SingleNotificationFragmentFragment = { readonly __typename?: 'NotificationResource', readonly uuid: string, readonly title: string, readonly body: string, readonly deliveryIssue?: string | null, readonly deliveryIssueAcknowledgedAt?: Date | string | null, readonly sendAt?: Date | string | null, readonly startedSendingAt?: Date | string | null, readonly createdAt?: Date | string | null, readonly deliveryCount: number, readonly deliveryIssueCount: { readonly __typename?: 'NotificationDeliveryIssueCount', readonly DeviceNotRegistered: number, readonly InvalidCredentials: number, readonly MessageRateExceeded: number, readonly MessageTooBig: number, readonly MismatchSenderId: number, readonly Unknown: number } } & { ' $fragmentName'?: 'SingleNotificationFragmentFragment' }; +export type SingleNotificationFragmentFragment = { readonly __typename?: 'NotificationNode', readonly id: string, readonly title: string, readonly body: string, readonly deliveryIssue?: string | null, readonly deliveryIssueAcknowledgedAt?: Date | string | null, readonly sendAt?: Date | string | null, readonly startedSendingAt?: Date | string | null, readonly createdAt?: Date | string | null, readonly deliveryCount: number, readonly deliveryIssueCount: { readonly __typename?: 'NotificationDeliveryIssueCount', readonly DeviceNotRegistered: number, readonly InvalidCredentials: number, readonly MessageRateExceeded: number, readonly MessageTooBig: number, readonly MismatchSenderId: number, readonly Unknown: number } } & { ' $fragmentName'?: 'SingleNotificationFragmentFragment' }; export type CreateNotificationMutationVariables = Exact<{ title: Scalars['String']['input']; @@ -1903,14 +2182,14 @@ export type CreateNotificationMutationVariables = Exact<{ export type CreateNotificationMutation = { readonly __typename?: 'Mutation', readonly stageNotification: { readonly __typename?: 'StageNotificationResponse', readonly uuid: string } }; export type CancelNotificationScheduleMutationVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }>; export type CancelNotificationScheduleMutation = { readonly __typename?: 'Mutation', readonly abortScheduledNotification: { readonly __typename?: 'AbortScheduledNotificationResponse', readonly ok: boolean } }; export type DeleteNotificationMutationVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; force?: InputMaybe; }>; @@ -1918,66 +2197,66 @@ export type DeleteNotificationMutationVariables = Exact<{ export type DeleteNotificationMutation = { readonly __typename?: 'Mutation', readonly deleteNotification: { readonly __typename?: 'DeleteNotificationResponse', readonly ok: boolean } }; export type SendNotificationMutationVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }>; export type SendNotificationMutation = { readonly __typename?: 'Mutation', readonly sendNotification: { readonly __typename?: 'SendNotificationResponse', readonly ok: boolean } }; export type ScheduleNotificationMutationVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; sendAt: Scalars['DateTimeISO']['input']; }>; export type ScheduleNotificationMutation = { readonly __typename?: 'Mutation', readonly scheduleNotification: { readonly __typename?: 'ScheduleNotificationResponse', readonly ok: boolean } }; -export type TeamNameFragmentFragment = { readonly __typename?: 'TeamResource', readonly uuid: string, readonly name: string } & { ' $fragmentName'?: 'TeamNameFragmentFragment' }; +export type TeamNameFragmentFragment = { readonly __typename?: 'TeamNode', readonly id: string, readonly name: string } & { ' $fragmentName'?: 'TeamNameFragmentFragment' }; export type PersonCreatorMutationVariables = Exact<{ input: CreatePersonInput; }>; -export type PersonCreatorMutation = { readonly __typename?: 'Mutation', readonly createPerson: { readonly __typename?: 'CreatePersonResponse', readonly ok: boolean, readonly uuid: string } }; +export type PersonCreatorMutation = { readonly __typename?: 'Mutation', readonly createPerson: { readonly __typename?: 'PersonNode', readonly id: string } }; -export type PersonEditorFragmentFragment = { readonly __typename?: 'PersonResource', readonly uuid: string, readonly name?: string | null, readonly linkblue?: string | null, readonly email: string, readonly role: { readonly __typename?: 'RoleResource', readonly committeeRole?: CommitteeRole | null, readonly committeeIdentifier?: CommitteeIdentifier | null }, readonly teams: ReadonlyArray<{ readonly __typename?: 'MembershipResource', readonly position: MembershipPositionType, readonly team: { readonly __typename?: 'TeamResource', readonly uuid: string, readonly name: string } }> } & { ' $fragmentName'?: 'PersonEditorFragmentFragment' }; +export type PersonEditorFragmentFragment = { readonly __typename?: 'PersonNode', readonly id: string, readonly name?: string | null, readonly linkblue?: string | null, readonly email: string, readonly teams: ReadonlyArray<{ readonly __typename?: 'MembershipNode', readonly position: MembershipPositionType, readonly team: { readonly __typename?: 'TeamNode', readonly id: string, readonly name: string } }> } & { ' $fragmentName'?: 'PersonEditorFragmentFragment' }; export type PersonEditorMutationVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; input: SetPersonInput; }>; -export type PersonEditorMutation = { readonly __typename?: 'Mutation', readonly setPerson: { readonly __typename?: 'GetPersonResponse', readonly ok: boolean } }; +export type PersonEditorMutation = { readonly __typename?: 'Mutation', readonly setPerson: { readonly __typename?: 'PersonNode', readonly id: string } }; export type CreatePointEntryMutationVariables = Exact<{ input: CreatePointEntryInput; }>; -export type CreatePointEntryMutation = { readonly __typename?: 'Mutation', readonly createPointEntry: { readonly __typename?: 'CreatePointEntryResponse', readonly data: { readonly __typename?: 'PointEntryResource', readonly uuid: string } } }; +export type CreatePointEntryMutation = { readonly __typename?: 'Mutation', readonly createPointEntry: { readonly __typename?: 'CreatePointEntryResponse', readonly data: { readonly __typename?: 'PointEntryNode', readonly id: string } } }; export type GetPersonByUuidQueryVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }>; -export type GetPersonByUuidQuery = { readonly __typename?: 'Query', readonly person: { readonly __typename?: 'GetPersonResponse', readonly data?: { readonly __typename?: 'PersonResource', readonly uuid: string, readonly name?: string | null, readonly linkblue?: string | null } | null } }; +export type GetPersonByUuidQuery = { readonly __typename?: 'Query', readonly person: { readonly __typename?: 'PersonNode', readonly id: string, readonly name?: string | null, readonly linkblue?: string | null } }; export type GetPersonByLinkBlueQueryVariables = Exact<{ linkBlue: Scalars['String']['input']; }>; -export type GetPersonByLinkBlueQuery = { readonly __typename?: 'Query', readonly personByLinkBlue: { readonly __typename?: 'GetPersonResponse', readonly data?: { readonly __typename?: 'PersonResource', readonly uuid: string, readonly name?: string | null } | null } }; +export type GetPersonByLinkBlueQuery = { readonly __typename?: 'Query', readonly personByLinkBlue: { readonly __typename?: 'PersonNode', readonly id: string, readonly name?: string | null } }; export type SearchPersonByNameQueryVariables = Exact<{ name: Scalars['String']['input']; }>; -export type SearchPersonByNameQuery = { readonly __typename?: 'Query', readonly searchPeopleByName: { readonly __typename?: 'GetPeopleResponse', readonly data: ReadonlyArray<{ readonly __typename?: 'PersonResource', readonly uuid: string, readonly name?: string | null }> } }; +export type SearchPersonByNameQuery = { readonly __typename?: 'Query', readonly searchPeopleByName: ReadonlyArray<{ readonly __typename?: 'PersonNode', readonly id: string, readonly name?: string | null }> }; export type CreatePersonByLinkBlueMutationVariables = Exact<{ linkBlue: Scalars['String']['input']; @@ -1986,14 +2265,14 @@ export type CreatePersonByLinkBlueMutationVariables = Exact<{ }>; -export type CreatePersonByLinkBlueMutation = { readonly __typename?: 'Mutation', readonly createPerson: { readonly __typename?: 'CreatePersonResponse', readonly uuid: string } }; +export type CreatePersonByLinkBlueMutation = { readonly __typename?: 'Mutation', readonly createPerson: { readonly __typename?: 'PersonNode', readonly id: string } }; export type PointEntryOpportunityLookupQueryVariables = Exact<{ name: Scalars['String']['input']; }>; -export type PointEntryOpportunityLookupQuery = { readonly __typename?: 'Query', readonly pointOpportunities: { readonly __typename?: 'ListPointOpportunitiesResponse', readonly data: ReadonlyArray<{ readonly __typename?: 'PointOpportunityResource', readonly name: string, readonly uuid: string }> } }; +export type PointEntryOpportunityLookupQuery = { readonly __typename?: 'Query', readonly pointOpportunities: { readonly __typename?: 'ListPointOpportunitiesResponse', readonly data: ReadonlyArray<{ readonly __typename?: 'PointOpportunityNode', readonly name: string, readonly id: string }> } }; export type CreatePointOpportunityMutationVariables = Exact<{ input: CreatePointOpportunityInput; @@ -2004,22 +2283,23 @@ export type CreatePointOpportunityMutation = { readonly __typename?: 'Mutation', export type TeamCreatorMutationVariables = Exact<{ input: CreateTeamInput; + marathonUuid: Scalars['String']['input']; }>; export type TeamCreatorMutation = { readonly __typename?: 'Mutation', readonly createTeam: { readonly __typename?: 'CreateTeamResponse', readonly ok: boolean, readonly uuid: string } }; -export type TeamEditorFragmentFragment = { readonly __typename?: 'TeamResource', readonly uuid: string, readonly name: string, readonly marathonYear: string, readonly legacyStatus: TeamLegacyStatus, readonly persistentIdentifier?: string | null, readonly type: TeamType } & { ' $fragmentName'?: 'TeamEditorFragmentFragment' }; +export type TeamEditorFragmentFragment = { readonly __typename?: 'TeamNode', readonly id: string, readonly name: string, readonly legacyStatus: TeamLegacyStatus, readonly type: TeamType, readonly marathon: { readonly __typename?: 'MarathonNode', readonly id: string, readonly year: string } } & { ' $fragmentName'?: 'TeamEditorFragmentFragment' }; export type TeamEditorMutationVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; input: SetTeamInput; }>; export type TeamEditorMutation = { readonly __typename?: 'Mutation', readonly setTeam: { readonly __typename?: 'SingleTeamResponse', readonly ok: boolean } }; -export type PeopleTableFragmentFragment = { readonly __typename?: 'PersonResource', readonly uuid: string, readonly name?: string | null, readonly linkblue?: string | null, readonly email: string, readonly role: { readonly __typename?: 'RoleResource', readonly dbRole: DbRole, readonly committeeRole?: CommitteeRole | null, readonly committeeIdentifier?: CommitteeIdentifier | null } } & { ' $fragmentName'?: 'PeopleTableFragmentFragment' }; +export type PeopleTableFragmentFragment = { readonly __typename?: 'PersonNode', readonly id: string, readonly name?: string | null, readonly linkblue?: string | null, readonly email: string, readonly dbRole: DbRole, readonly primaryCommittee?: { readonly __typename?: 'CommitteeMembershipNode', readonly identifier: CommitteeIdentifier, readonly role: CommitteeRole } | null } & { ' $fragmentName'?: 'PeopleTableFragmentFragment' }; export type PeopleTableQueryVariables = Exact<{ page?: InputMaybe; @@ -2033,7 +2313,7 @@ export type PeopleTableQueryVariables = Exact<{ export type PeopleTableQuery = { readonly __typename?: 'Query', readonly listPeople: { readonly __typename?: 'ListPeopleResponse', readonly page: number, readonly pageSize: number, readonly total: number, readonly data: ReadonlyArray<( - { readonly __typename?: 'PersonResource' } + { readonly __typename?: 'PersonNode' } & { ' $fragmentRefs'?: { 'PeopleTableFragmentFragment': PeopleTableFragmentFragment } } )> } }; @@ -2049,13 +2329,13 @@ export type TeamsTableQueryVariables = Exact<{ export type TeamsTableQuery = { readonly __typename?: 'Query', readonly teams: { readonly __typename?: 'ListTeamsResponse', readonly page: number, readonly pageSize: number, readonly total: number, readonly data: ReadonlyArray<( - { readonly __typename?: 'TeamResource' } + { readonly __typename?: 'TeamNode' } & { ' $fragmentRefs'?: { 'TeamsTableFragmentFragment': TeamsTableFragmentFragment } } )> } }; -export type TeamsTableFragmentFragment = { readonly __typename?: 'TeamResource', readonly uuid: string, readonly type: TeamType, readonly name: string, readonly legacyStatus: TeamLegacyStatus, readonly marathonYear: string, readonly totalPoints: number } & { ' $fragmentName'?: 'TeamsTableFragmentFragment' }; +export type TeamsTableFragmentFragment = { readonly __typename?: 'TeamNode', readonly id: string, readonly type: TeamType, readonly name: string, readonly legacyStatus: TeamLegacyStatus, readonly totalPoints: number } & { ' $fragmentName'?: 'TeamsTableFragmentFragment' }; -export type NotificationDeliveriesTableFragmentFragment = { readonly __typename?: 'NotificationDeliveryResource', readonly uuid: string, readonly deliveryError?: string | null, readonly receiptCheckedAt?: Date | string | null, readonly sentAt?: Date | string | null } & { ' $fragmentName'?: 'NotificationDeliveriesTableFragmentFragment' }; +export type NotificationDeliveriesTableFragmentFragment = { readonly __typename?: 'NotificationDeliveryNode', readonly id: string, readonly deliveryError?: string | null, readonly receiptCheckedAt?: Date | string | null, readonly sentAt?: Date | string | null } & { ' $fragmentName'?: 'NotificationDeliveriesTableFragmentFragment' }; export type NotificationDeliveriesTableQueryQueryVariables = Exact<{ notificationId: Scalars['String']['input']; @@ -2069,11 +2349,11 @@ export type NotificationDeliveriesTableQueryQueryVariables = Exact<{ export type NotificationDeliveriesTableQueryQuery = { readonly __typename?: 'Query', readonly notificationDeliveries: { readonly __typename?: 'ListNotificationDeliveriesResponse', readonly page: number, readonly pageSize: number, readonly total: number, readonly data: ReadonlyArray<( - { readonly __typename?: 'NotificationDeliveryResource' } + { readonly __typename?: 'NotificationDeliveryNode' } & { ' $fragmentRefs'?: { 'NotificationDeliveriesTableFragmentFragment': NotificationDeliveriesTableFragmentFragment } } )> } }; -export type NotificationsTableFragmentFragment = { readonly __typename?: 'NotificationResource', readonly uuid: string, readonly title: string, readonly body: string, readonly deliveryIssue?: string | null, readonly deliveryIssueAcknowledgedAt?: Date | string | null, readonly sendAt?: Date | string | null, readonly startedSendingAt?: Date | string | null } & { ' $fragmentName'?: 'NotificationsTableFragmentFragment' }; +export type NotificationsTableFragmentFragment = { readonly __typename?: 'NotificationNode', readonly id: string, readonly title: string, readonly body: string, readonly deliveryIssue?: string | null, readonly deliveryIssueAcknowledgedAt?: Date | string | null, readonly sendAt?: Date | string | null, readonly startedSendingAt?: Date | string | null } & { ' $fragmentName'?: 'NotificationsTableFragmentFragment' }; export type NotificationsTableQueryQueryVariables = Exact<{ page?: InputMaybe; @@ -2088,41 +2368,41 @@ export type NotificationsTableQueryQueryVariables = Exact<{ export type NotificationsTableQueryQuery = { readonly __typename?: 'Query', readonly notifications: { readonly __typename?: 'ListNotificationsResponse', readonly page: number, readonly pageSize: number, readonly total: number, readonly data: ReadonlyArray<( - { readonly __typename?: 'NotificationResource' } + { readonly __typename?: 'NotificationNode' } & { ' $fragmentRefs'?: { 'NotificationsTableFragmentFragment': NotificationsTableFragmentFragment } } )> } }; export type DeletePointEntryMutationVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }>; export type DeletePointEntryMutation = { readonly __typename?: 'Mutation', readonly deletePointEntry: { readonly __typename?: 'DeletePointEntryResponse', readonly ok: boolean } }; -export type PointEntryTableFragmentFragment = { readonly __typename?: 'PointEntryResource', readonly uuid: string, readonly points: number, readonly comment?: string | null, readonly personFrom?: { readonly __typename?: 'PersonResource', readonly name?: string | null, readonly linkblue?: string | null } | null, readonly pointOpportunity?: { readonly __typename?: 'PointOpportunityResource', readonly name: string, readonly opportunityDate?: string | null } | null } & { ' $fragmentName'?: 'PointEntryTableFragmentFragment' }; +export type PointEntryTableFragmentFragment = { readonly __typename?: 'PointEntryNode', readonly id: string, readonly points: number, readonly comment?: string | null, readonly personFrom?: { readonly __typename?: 'PersonNode', readonly name?: string | null, readonly linkblue?: string | null } | null, readonly pointOpportunity?: { readonly __typename?: 'PointOpportunityNode', readonly name: string, readonly opportunityDate?: Date | string | null } | null } & { ' $fragmentName'?: 'PointEntryTableFragmentFragment' }; export type DeletePersonMutationVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }>; -export type DeletePersonMutation = { readonly __typename?: 'Mutation', readonly deletePerson: { readonly __typename?: 'DeletePersonResponse', readonly ok: boolean } }; +export type DeletePersonMutation = { readonly __typename?: 'Mutation', readonly deletePerson: { readonly __typename?: 'PersonNode', readonly id: string } }; -export type PersonViewerFragmentFragment = { readonly __typename?: 'PersonResource', readonly uuid: string, readonly name?: string | null, readonly linkblue?: string | null, readonly email: string, readonly role: { readonly __typename?: 'RoleResource', readonly dbRole: DbRole, readonly committeeRole?: CommitteeRole | null, readonly committeeIdentifier?: CommitteeIdentifier | null }, readonly teams: ReadonlyArray<{ readonly __typename?: 'MembershipResource', readonly position: MembershipPositionType, readonly team: { readonly __typename?: 'TeamResource', readonly uuid: string, readonly name: string } }> } & { ' $fragmentName'?: 'PersonViewerFragmentFragment' }; +export type PersonViewerFragmentFragment = { readonly __typename?: 'PersonNode', readonly id: string, readonly name?: string | null, readonly linkblue?: string | null, readonly email: string, readonly dbRole: DbRole, readonly teams: ReadonlyArray<{ readonly __typename?: 'MembershipNode', readonly position: MembershipPositionType, readonly team: { readonly __typename?: 'TeamNode', readonly id: string, readonly name: string } }>, readonly committees: ReadonlyArray<{ readonly __typename?: 'CommitteeMembershipNode', readonly identifier: CommitteeIdentifier, readonly role: CommitteeRole }> } & { ' $fragmentName'?: 'PersonViewerFragmentFragment' }; export type DeleteTeamMutationVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }>; export type DeleteTeamMutation = { readonly __typename?: 'Mutation', readonly deleteTeam: { readonly __typename?: 'DeleteTeamResponse', readonly ok: boolean } }; -export type TeamViewerFragmentFragment = { readonly __typename?: 'TeamResource', readonly uuid: string, readonly name: string, readonly marathonYear: string, readonly legacyStatus: TeamLegacyStatus, readonly totalPoints: number, readonly type: TeamType, readonly members: ReadonlyArray<{ readonly __typename?: 'MembershipResource', readonly person: { readonly __typename?: 'PersonResource', readonly uuid: string, readonly name?: string | null, readonly linkblue?: string | null } }>, readonly captains: ReadonlyArray<{ readonly __typename?: 'MembershipResource', readonly person: { readonly __typename?: 'PersonResource', readonly uuid: string, readonly name?: string | null, readonly linkblue?: string | null } }> } & { ' $fragmentName'?: 'TeamViewerFragmentFragment' }; +export type TeamViewerFragmentFragment = { readonly __typename?: 'TeamNode', readonly id: string, readonly name: string, readonly legacyStatus: TeamLegacyStatus, readonly totalPoints: number, readonly type: TeamType, readonly marathon: { readonly __typename?: 'MarathonNode', readonly id: string, readonly year: string }, readonly members: ReadonlyArray<{ readonly __typename?: 'MembershipNode', readonly position: MembershipPositionType, readonly person: { readonly __typename?: 'PersonNode', readonly id: string, readonly name?: string | null, readonly linkblue?: string | null } }> } & { ' $fragmentName'?: 'TeamViewerFragmentFragment' }; export type LoginStateQueryVariables = Exact<{ [key: string]: never; }>; -export type LoginStateQuery = { readonly __typename?: 'Query', readonly loginState: { readonly __typename?: 'LoginState', readonly loggedIn: boolean, readonly role: { readonly __typename?: 'RoleResource', readonly dbRole: DbRole, readonly committeeRole?: CommitteeRole | null, readonly committeeIdentifier?: CommitteeIdentifier | null } } }; +export type LoginStateQuery = { readonly __typename?: 'Query', readonly loginState: { readonly __typename?: 'LoginState', readonly loggedIn: boolean, readonly dbRole: DbRole, readonly effectiveCommitteeRoles: ReadonlyArray<{ readonly __typename?: 'EffectiveCommitteeRole', readonly role: CommitteeRole, readonly identifier: CommitteeIdentifier }> } }; export type CommitConfigChangesMutationVariables = Exact<{ changes: ReadonlyArray | CreateConfigurationInput; @@ -2131,13 +2411,13 @@ export type CommitConfigChangesMutationVariables = Exact<{ export type CommitConfigChangesMutation = { readonly __typename?: 'Mutation', readonly createConfigurations: { readonly __typename?: 'CreateConfigurationResponse', readonly ok: boolean } }; -export type ConfigFragmentFragment = { readonly __typename?: 'ConfigurationResource', readonly uuid: string, readonly key: string, readonly value: string, readonly validAfter?: string | null, readonly validUntil?: string | null, readonly createdAt?: Date | string | null } & { ' $fragmentName'?: 'ConfigFragmentFragment' }; +export type ConfigFragmentFragment = { readonly __typename?: 'ConfigurationNode', readonly id: string, readonly key: string, readonly value: string, readonly validAfter?: Date | string | null, readonly validUntil?: Date | string | null, readonly createdAt?: Date | string | null } & { ' $fragmentName'?: 'ConfigFragmentFragment' }; export type ConfigQueryQueryVariables = Exact<{ [key: string]: never; }>; export type ConfigQueryQuery = { readonly __typename?: 'Query', readonly allConfigurations: { readonly __typename?: 'GetAllConfigurationsResponse', readonly data: ReadonlyArray<( - { readonly __typename?: 'ConfigurationResource' } + { readonly __typename?: 'ConfigurationNode' } & { ' $fragmentRefs'?: { 'ConfigFragmentFragment': ConfigFragmentFragment } } )> } }; @@ -2146,9 +2426,9 @@ export type CreateEventMutationVariables = Exact<{ }>; -export type CreateEventMutation = { readonly __typename?: 'Mutation', readonly createEvent: { readonly __typename?: 'CreateEventResponse', readonly data: { readonly __typename?: 'EventResource', readonly uuid: string } } }; +export type CreateEventMutation = { readonly __typename?: 'Mutation', readonly createEvent: { readonly __typename?: 'CreateEventResponse', readonly data: { readonly __typename?: 'EventNode', readonly id: string } } }; -export type EventsTableFragmentFragment = { readonly __typename?: 'EventResource', readonly uuid: string, readonly title: string, readonly description?: string | null, readonly summary?: string | null, readonly occurrences: ReadonlyArray<{ readonly __typename?: 'EventOccurrenceResource', readonly uuid: string, readonly interval: string, readonly fullDay: boolean }> } & { ' $fragmentName'?: 'EventsTableFragmentFragment' }; +export type EventsTableFragmentFragment = { readonly __typename?: 'EventNode', readonly id: string, readonly title: string, readonly description?: string | null, readonly summary?: string | null, readonly occurrences: ReadonlyArray<{ readonly __typename?: 'EventOccurrenceNode', readonly id: string, readonly fullDay: boolean, readonly interval: { readonly __typename?: 'IntervalISO', readonly start: Date | string, readonly end: Date | string } }> } & { ' $fragmentName'?: 'EventsTableFragmentFragment' }; export type EventsTableQueryVariables = Exact<{ page?: InputMaybe; @@ -2163,63 +2443,63 @@ export type EventsTableQueryVariables = Exact<{ export type EventsTableQuery = { readonly __typename?: 'Query', readonly events: { readonly __typename?: 'ListEventsResponse', readonly page: number, readonly pageSize: number, readonly total: number, readonly data: ReadonlyArray<( - { readonly __typename?: 'EventResource' } + { readonly __typename?: 'EventNode' } & { ' $fragmentRefs'?: { 'EventsTableFragmentFragment': EventsTableFragmentFragment } } )> } }; export type EditEventPageQueryVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }>; export type EditEventPageQuery = { readonly __typename?: 'Query', readonly event: { readonly __typename?: 'GetEventByUuidResponse', readonly data: ( - { readonly __typename?: 'EventResource' } + { readonly __typename?: 'EventNode' } & { ' $fragmentRefs'?: { 'EventEditorFragmentFragment': EventEditorFragmentFragment } } ) } }; -export type EventEditorFragmentFragment = { readonly __typename?: 'EventResource', readonly uuid: string, readonly title: string, readonly summary?: string | null, readonly description?: string | null, readonly location?: string | null, readonly occurrences: ReadonlyArray<{ readonly __typename?: 'EventOccurrenceResource', readonly uuid: string, readonly interval: string, readonly fullDay: boolean }>, readonly images: ReadonlyArray<{ readonly __typename?: 'ImageResource', readonly url?: URL | string | null, readonly width: number, readonly height: number, readonly thumbHash?: string | null, readonly alt?: string | null }> } & { ' $fragmentName'?: 'EventEditorFragmentFragment' }; +export type EventEditorFragmentFragment = { readonly __typename?: 'EventNode', readonly id: string, readonly title: string, readonly summary?: string | null, readonly description?: string | null, readonly location?: string | null, readonly occurrences: ReadonlyArray<{ readonly __typename?: 'EventOccurrenceNode', readonly id: string, readonly fullDay: boolean, readonly interval: { readonly __typename?: 'IntervalISO', readonly start: Date | string, readonly end: Date | string } }>, readonly images: ReadonlyArray<{ readonly __typename?: 'ImageNode', readonly url?: URL | string | null, readonly width: number, readonly height: number, readonly thumbHash?: string | null, readonly alt?: string | null }> } & { ' $fragmentName'?: 'EventEditorFragmentFragment' }; export type SaveEventMutationVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; input: SetEventInput; }>; export type SaveEventMutation = { readonly __typename?: 'Mutation', readonly setEvent: { readonly __typename?: 'SetEventResponse', readonly data: ( - { readonly __typename?: 'EventResource' } + { readonly __typename?: 'EventNode' } & { ' $fragmentRefs'?: { 'EventEditorFragmentFragment': EventEditorFragmentFragment } } ) } }; export type DeleteEventMutationVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }>; export type DeleteEventMutation = { readonly __typename?: 'Mutation', readonly deleteEvent: { readonly __typename?: 'DeleteEventResponse', readonly ok: boolean } }; -export type EventViewerFragmentFragment = { readonly __typename?: 'EventResource', readonly uuid: string, readonly title: string, readonly summary?: string | null, readonly description?: string | null, readonly location?: string | null, readonly createdAt?: Date | string | null, readonly updatedAt?: Date | string | null, readonly occurrences: ReadonlyArray<{ readonly __typename?: 'EventOccurrenceResource', readonly interval: string, readonly fullDay: boolean }>, readonly images: ReadonlyArray<{ readonly __typename?: 'ImageResource', readonly url?: URL | string | null, readonly width: number, readonly height: number, readonly thumbHash?: string | null, readonly alt?: string | null }> } & { ' $fragmentName'?: 'EventViewerFragmentFragment' }; +export type EventViewerFragmentFragment = { readonly __typename?: 'EventNode', readonly id: string, readonly title: string, readonly summary?: string | null, readonly description?: string | null, readonly location?: string | null, readonly createdAt?: Date | string | null, readonly updatedAt?: Date | string | null, readonly occurrences: ReadonlyArray<{ readonly __typename?: 'EventOccurrenceNode', readonly fullDay: boolean, readonly interval: { readonly __typename?: 'IntervalISO', readonly start: Date | string, readonly end: Date | string } }>, readonly images: ReadonlyArray<{ readonly __typename?: 'ImageNode', readonly url?: URL | string | null, readonly width: number, readonly height: number, readonly thumbHash?: string | null, readonly alt?: string | null }> } & { ' $fragmentName'?: 'EventViewerFragmentFragment' }; export type ViewEventPageQueryVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }>; export type ViewEventPageQuery = { readonly __typename?: 'Query', readonly event: { readonly __typename?: 'GetEventByUuidResponse', readonly data: ( - { readonly __typename?: 'EventResource' } + { readonly __typename?: 'EventNode' } & { ' $fragmentRefs'?: { 'EventViewerFragmentFragment': EventViewerFragmentFragment } } ) } }; export type FeedPageQueryVariables = Exact<{ [key: string]: never; }>; -export type FeedPageQuery = { readonly __typename?: 'Query', readonly feed: ReadonlyArray<{ readonly __typename?: 'FeedResource', readonly uuid: string, readonly title: string, readonly createdAt?: Date | string | null, readonly textContent?: string | null, readonly image?: { readonly __typename?: 'ImageResource', readonly url?: URL | string | null, readonly alt?: string | null } | null }> }; +export type FeedPageQuery = { readonly __typename?: 'Query', readonly feed: ReadonlyArray<{ readonly __typename?: 'FeedNode', readonly id: string, readonly title: string, readonly createdAt?: Date | string | null, readonly textContent?: string | null, readonly image?: { readonly __typename?: 'ImageNode', readonly url?: URL | string | null, readonly alt?: string | null } | null }> }; export type CreateFeedItemMutationVariables = Exact<{ input: CreateFeedInput; }>; -export type CreateFeedItemMutation = { readonly __typename?: 'Mutation', readonly createFeedItem: { readonly __typename?: 'FeedResource', readonly uuid: string } }; +export type CreateFeedItemMutation = { readonly __typename?: 'Mutation', readonly createFeedItem: { readonly __typename?: 'FeedNode', readonly id: string } }; export type DeleteFeedItemMutationVariables = Exact<{ uuid: Scalars['String']['input']; @@ -2233,9 +2513,9 @@ export type CreateImageMutationVariables = Exact<{ }>; -export type CreateImageMutation = { readonly __typename?: 'Mutation', readonly createImage: { readonly __typename?: 'ImageResource', readonly uuid: string } }; +export type CreateImageMutation = { readonly __typename?: 'Mutation', readonly createImage: { readonly __typename?: 'ImageNode', readonly id: string } }; -export type ImagesTableFragmentFragment = { readonly __typename?: 'ImageResource', readonly uuid: string, readonly url?: URL | string | null, readonly thumbHash?: string | null, readonly height: number, readonly width: number, readonly alt?: string | null, readonly mimeType: string, readonly createdAt?: Date | string | null } & { ' $fragmentName'?: 'ImagesTableFragmentFragment' }; +export type ImagesTableFragmentFragment = { readonly __typename?: 'ImageNode', readonly id: string, readonly url?: URL | string | null, readonly thumbHash?: string | null, readonly height: number, readonly width: number, readonly alt?: string | null, readonly mimeType: string, readonly createdAt?: Date | string | null } & { ' $fragmentName'?: 'ImagesTableFragmentFragment' }; export type ImagesTableQueryVariables = Exact<{ page?: InputMaybe; @@ -2251,7 +2531,7 @@ export type ImagesTableQueryVariables = Exact<{ export type ImagesTableQuery = { readonly __typename?: 'Query', readonly images: { readonly __typename?: 'ListImagesResponse', readonly page: number, readonly pageSize: number, readonly total: number, readonly data: ReadonlyArray<( - { readonly __typename?: 'ImageResource' } + { readonly __typename?: 'ImageNode' } & { ' $fragmentRefs'?: { 'ImagesTableFragmentFragment': ImagesTableFragmentFragment } } )> } }; @@ -2260,45 +2540,45 @@ export type CreateMarathonMutationVariables = Exact<{ }>; -export type CreateMarathonMutation = { readonly __typename?: 'Mutation', readonly createMarathon: { readonly __typename?: 'MarathonResource', readonly uuid: string } }; +export type CreateMarathonMutation = { readonly __typename?: 'Mutation', readonly createMarathon: { readonly __typename?: 'MarathonNode', readonly id: string } }; export type MarathonOverviewPageQueryVariables = Exact<{ [key: string]: never; }>; -export type MarathonOverviewPageQuery = { readonly __typename?: 'Query', readonly nextMarathon?: ( - { readonly __typename?: 'MarathonResource' } +export type MarathonOverviewPageQuery = { readonly __typename?: 'Query', readonly latestMarathon?: ( + { readonly __typename?: 'MarathonNode' } & { ' $fragmentRefs'?: { 'MarathonViewerFragmentFragment': MarathonViewerFragmentFragment } } ) | null, readonly marathons: { readonly __typename?: 'ListMarathonsResponse', readonly data: ReadonlyArray<( - { readonly __typename?: 'MarathonResource' } + { readonly __typename?: 'MarathonNode' } & { ' $fragmentRefs'?: { 'MarathonTableFragmentFragment': MarathonTableFragmentFragment } } )> } }; -export type MarathonTableFragmentFragment = { readonly __typename?: 'MarathonResource', readonly uuid: string, readonly year: string, readonly startDate: Date | string, readonly endDate: Date | string } & { ' $fragmentName'?: 'MarathonTableFragmentFragment' }; +export type MarathonTableFragmentFragment = { readonly __typename?: 'MarathonNode', readonly id: string, readonly year: string, readonly startDate?: Date | string | null, readonly endDate?: Date | string | null } & { ' $fragmentName'?: 'MarathonTableFragmentFragment' }; export type EditMarathonMutationVariables = Exact<{ input: SetMarathonInput; - marathonId: Scalars['String']['input']; + marathonId: Scalars['GlobalId']['input']; }>; -export type EditMarathonMutation = { readonly __typename?: 'Mutation', readonly setMarathon: { readonly __typename?: 'MarathonResource', readonly uuid: string } }; +export type EditMarathonMutation = { readonly __typename?: 'Mutation', readonly setMarathon: { readonly __typename?: 'MarathonNode', readonly id: string } }; export type GetMarathonQueryVariables = Exact<{ - marathonId: Scalars['String']['input']; + marathonId: Scalars['GlobalId']['input']; }>; -export type GetMarathonQuery = { readonly __typename?: 'Query', readonly marathon: { readonly __typename?: 'MarathonResource', readonly year: string, readonly startDate: Date | string, readonly endDate: Date | string } }; +export type GetMarathonQuery = { readonly __typename?: 'Query', readonly marathon: { readonly __typename?: 'MarathonNode', readonly year: string, readonly startDate?: Date | string | null, readonly endDate?: Date | string | null } }; -export type MarathonViewerFragmentFragment = { readonly __typename?: 'MarathonResource', readonly uuid: string, readonly year: string, readonly startDate: Date | string, readonly endDate: Date | string, readonly hours: ReadonlyArray<{ readonly __typename?: 'MarathonHourResource', readonly uuid: string, readonly shownStartingAt: Date | string, readonly title: string }> } & { ' $fragmentName'?: 'MarathonViewerFragmentFragment' }; +export type MarathonViewerFragmentFragment = { readonly __typename?: 'MarathonNode', readonly id: string, readonly year: string, readonly startDate?: Date | string | null, readonly endDate?: Date | string | null, readonly hours: ReadonlyArray<{ readonly __typename?: 'MarathonHourNode', readonly id: string, readonly shownStartingAt: Date | string, readonly title: string }> } & { ' $fragmentName'?: 'MarathonViewerFragmentFragment' }; export type MarathonPageQueryVariables = Exact<{ - marathonUuid: Scalars['String']['input']; + marathonUuid: Scalars['GlobalId']['input']; }>; export type MarathonPageQuery = { readonly __typename?: 'Query', readonly marathon: ( - { readonly __typename?: 'MarathonResource' } + { readonly __typename?: 'MarathonNode' } & { ' $fragmentRefs'?: { 'MarathonViewerFragmentFragment': MarathonViewerFragmentFragment } } ) }; @@ -2308,40 +2588,40 @@ export type AddMarathonHourMutationVariables = Exact<{ }>; -export type AddMarathonHourMutation = { readonly __typename?: 'Mutation', readonly createMarathonHour: { readonly __typename?: 'MarathonHourResource', readonly uuid: string } }; +export type AddMarathonHourMutation = { readonly __typename?: 'Mutation', readonly createMarathonHour: { readonly __typename?: 'MarathonHourNode', readonly id: string } }; export type EditMarathonHourDataQueryVariables = Exact<{ - marathonHourUuid: Scalars['String']['input']; + marathonHourUuid: Scalars['GlobalId']['input']; }>; -export type EditMarathonHourDataQuery = { readonly __typename?: 'Query', readonly marathonHour: { readonly __typename?: 'MarathonHourResource', readonly details?: string | null, readonly durationInfo: string, readonly shownStartingAt: Date | string, readonly title: string } }; +export type EditMarathonHourDataQuery = { readonly __typename?: 'Query', readonly marathonHour: { readonly __typename?: 'MarathonHourNode', readonly details?: string | null, readonly durationInfo: string, readonly shownStartingAt: Date | string, readonly title: string } }; export type EditMarathonHourMutationVariables = Exact<{ input: SetMarathonHourInput; - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }>; -export type EditMarathonHourMutation = { readonly __typename?: 'Mutation', readonly setMarathonHour: { readonly __typename?: 'MarathonHourResource', readonly uuid: string } }; +export type EditMarathonHourMutation = { readonly __typename?: 'Mutation', readonly setMarathonHour: { readonly __typename?: 'MarathonHourNode', readonly id: string } }; export type NotificationManagerQueryVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }>; export type NotificationManagerQuery = { readonly __typename?: 'Query', readonly notification: { readonly __typename?: 'GetNotificationByUuidResponse', readonly data: ( - { readonly __typename?: 'NotificationResource' } + { readonly __typename?: 'NotificationNode' } & { ' $fragmentRefs'?: { 'SingleNotificationFragmentFragment': SingleNotificationFragmentFragment } } ) } }; export type NotificationViewerQueryVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }>; export type NotificationViewerQuery = { readonly __typename?: 'Query', readonly notification: { readonly __typename?: 'GetNotificationByUuidResponse', readonly data: ( - { readonly __typename?: 'NotificationResource' } + { readonly __typename?: 'NotificationNode' } & { ' $fragmentRefs'?: { 'SingleNotificationFragmentFragment': SingleNotificationFragmentFragment } } ) } }; @@ -2349,125 +2629,135 @@ export type CreatePersonPageQueryVariables = Exact<{ [key: string]: never; }>; export type CreatePersonPageQuery = { readonly __typename?: 'Query', readonly teams: { readonly __typename?: 'ListTeamsResponse', readonly data: ReadonlyArray<( - { readonly __typename?: 'TeamResource' } + { readonly __typename?: 'TeamNode' } & { ' $fragmentRefs'?: { 'TeamNameFragmentFragment': TeamNameFragmentFragment } } )> } }; export type EditPersonPageQueryVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }>; -export type EditPersonPageQuery = { readonly __typename?: 'Query', readonly person: { readonly __typename?: 'GetPersonResponse', readonly data?: ( - { readonly __typename?: 'PersonResource' } - & { ' $fragmentRefs'?: { 'PersonEditorFragmentFragment': PersonEditorFragmentFragment } } - ) | null }, readonly teams: { readonly __typename?: 'ListTeamsResponse', readonly data: ReadonlyArray<( - { readonly __typename?: 'TeamResource' } +export type EditPersonPageQuery = { readonly __typename?: 'Query', readonly person: ( + { readonly __typename?: 'PersonNode' } + & { ' $fragmentRefs'?: { 'PersonEditorFragmentFragment': PersonEditorFragmentFragment } } + ), readonly teams: { readonly __typename?: 'ListTeamsResponse', readonly data: ReadonlyArray<( + { readonly __typename?: 'TeamNode' } & { ' $fragmentRefs'?: { 'TeamNameFragmentFragment': TeamNameFragmentFragment } } )> } }; export type ViewPersonPageQueryVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }>; -export type ViewPersonPageQuery = { readonly __typename?: 'Query', readonly person: { readonly __typename?: 'GetPersonResponse', readonly data?: ( - { readonly __typename?: 'PersonResource' } - & { ' $fragmentRefs'?: { 'PersonViewerFragmentFragment': PersonViewerFragmentFragment } } - ) | null } }; +export type ViewPersonPageQuery = { readonly __typename?: 'Query', readonly person: ( + { readonly __typename?: 'PersonNode' } + & { ' $fragmentRefs'?: { 'PersonViewerFragmentFragment': PersonViewerFragmentFragment } } + ) }; export type EditTeamPageQueryVariables = Exact<{ - uuid: Scalars['String']['input']; + uuid: Scalars['GlobalId']['input']; }>; export type EditTeamPageQuery = { readonly __typename?: 'Query', readonly team: { readonly __typename?: 'SingleTeamResponse', readonly data: ( - { readonly __typename?: 'TeamResource' } + { readonly __typename?: 'TeamNode' } & { ' $fragmentRefs'?: { 'TeamEditorFragmentFragment': TeamEditorFragmentFragment } } ) } }; +export type ViewTeamFundraisingDocumentQueryVariables = Exact<{ + teamUuid: Scalars['GlobalId']['input']; +}>; + + +export type ViewTeamFundraisingDocumentQuery = { readonly __typename?: 'Query', readonly team: { readonly __typename?: 'SingleTeamResponse', readonly data: { readonly __typename?: 'TeamNode', readonly fundraisingEntries: { readonly __typename?: 'ListFundraisingEntriesResponse', readonly data: ReadonlyArray<{ readonly __typename?: 'FundraisingEntryNode', readonly id: string, readonly amount: number, readonly donatedByText?: string | null, readonly donatedToText?: string | null, readonly donatedOn: Date | string, readonly assignments: ReadonlyArray<{ readonly __typename?: 'FundraisingAssignmentNode', readonly id: string, readonly amount: number, readonly person?: { readonly __typename?: 'PersonNode', readonly name?: string | null } | null }> }> } } } }; + export type ViewTeamPageQueryVariables = Exact<{ - teamUuid: Scalars['String']['input']; + teamUuid: Scalars['GlobalId']['input']; }>; export type ViewTeamPageQuery = { readonly __typename?: 'Query', readonly team: { readonly __typename?: 'SingleTeamResponse', readonly data: ( - { readonly __typename?: 'TeamResource', readonly pointEntries: ReadonlyArray<( - { readonly __typename?: 'PointEntryResource' } + { readonly __typename?: 'TeamNode', readonly pointEntries: ReadonlyArray<( + { readonly __typename?: 'PointEntryNode' } & { ' $fragmentRefs'?: { 'PointEntryTableFragmentFragment': PointEntryTableFragmentFragment } } )> } & { ' $fragmentRefs'?: { 'TeamViewerFragmentFragment': TeamViewerFragmentFragment } } ) } }; -export const SingleNotificationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SingleNotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssue"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssueAcknowledgedAt"}},{"kind":"Field","name":{"kind":"Name","value":"sendAt"}},{"kind":"Field","name":{"kind":"Name","value":"startedSendingAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryCount"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssueCount"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"DeviceNotRegistered"}},{"kind":"Field","name":{"kind":"Name","value":"InvalidCredentials"}},{"kind":"Field","name":{"kind":"Name","value":"MessageRateExceeded"}},{"kind":"Field","name":{"kind":"Name","value":"MessageTooBig"}},{"kind":"Field","name":{"kind":"Name","value":"MismatchSenderId"}},{"kind":"Field","name":{"kind":"Name","value":"Unknown"}}]}}]}}]} as unknown as DocumentNode; -export const TeamNameFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamNameFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode; -export const PersonEditorFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PersonEditorFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"committeeRole"}},{"kind":"Field","name":{"kind":"Name","value":"committeeIdentifier"}}]}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; -export const TeamEditorFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamEditorFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"marathonYear"}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"persistentIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"type"}}]}}]} as unknown as DocumentNode; -export const PeopleTableFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PeopleTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dbRole"}},{"kind":"Field","name":{"kind":"Name","value":"committeeRole"}},{"kind":"Field","name":{"kind":"Name","value":"committeeIdentifier"}}]}}]}}]} as unknown as DocumentNode; -export const TeamsTableFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamsTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"marathonYear"}},{"kind":"Field","name":{"kind":"Name","value":"totalPoints"}}]}}]} as unknown as DocumentNode; -export const NotificationDeliveriesTableFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationDeliveriesTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationDeliveryResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryError"}},{"kind":"Field","name":{"kind":"Name","value":"receiptCheckedAt"}},{"kind":"Field","name":{"kind":"Name","value":"sentAt"}}]}}]} as unknown as DocumentNode; -export const NotificationsTableFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationsTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssue"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssueAcknowledgedAt"}},{"kind":"Field","name":{"kind":"Name","value":"sendAt"}},{"kind":"Field","name":{"kind":"Name","value":"startedSendingAt"}}]}}]} as unknown as DocumentNode; -export const PointEntryTableFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PointEntryTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PointEntryResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"personFrom"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"pointOpportunity"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"opportunityDate"}}]}},{"kind":"Field","name":{"kind":"Name","value":"comment"}}]}}]} as unknown as DocumentNode; -export const PersonViewerFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PersonViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dbRole"}},{"kind":"Field","name":{"kind":"Name","value":"committeeRole"}},{"kind":"Field","name":{"kind":"Name","value":"committeeIdentifier"}}]}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; -export const TeamViewerFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"marathonYear"}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"totalPoints"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"person"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"captains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"person"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}}]}}]}}]} as unknown as DocumentNode; -export const ConfigFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ConfigFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ConfigurationResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"validAfter"}},{"kind":"Field","name":{"kind":"Name","value":"validUntil"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; -export const EventsTableFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventsTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"interval"}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"summary"}}]}}]} as unknown as DocumentNode; -export const EventEditorFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventEditorFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"summary"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"interval"}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}}]}}]}}]} as unknown as DocumentNode; -export const EventViewerFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"summary"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"interval"}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; -export const ImagesTableFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ImagesTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ImageResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"mimeType"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; -export const MarathonTableFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MarathonTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarathonResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"year"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}}]}}]} as unknown as DocumentNode; -export const MarathonViewerFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MarathonViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarathonResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"year"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}},{"kind":"Field","name":{"kind":"Name","value":"hours"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"shownStartingAt"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]}}]} as unknown as DocumentNode; -export const ImagePickerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ImagePicker"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ImageResolverKeyedStringFilterItem"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"images"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"stringFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"IntValue","value":"9"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]}}]}}]} as unknown as DocumentNode; -export const PersonSearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PersonSearch"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"search"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"searchPeopleByName"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"search"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"personByLinkBlue"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"linkBlueId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"search"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}}]}}]}}]} as unknown as DocumentNode; +export const SingleNotificationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SingleNotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssue"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssueAcknowledgedAt"}},{"kind":"Field","name":{"kind":"Name","value":"sendAt"}},{"kind":"Field","name":{"kind":"Name","value":"startedSendingAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryCount"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssueCount"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"DeviceNotRegistered"}},{"kind":"Field","name":{"kind":"Name","value":"InvalidCredentials"}},{"kind":"Field","name":{"kind":"Name","value":"MessageRateExceeded"}},{"kind":"Field","name":{"kind":"Name","value":"MessageTooBig"}},{"kind":"Field","name":{"kind":"Name","value":"MismatchSenderId"}},{"kind":"Field","name":{"kind":"Name","value":"Unknown"}}]}}]}}]} as unknown as DocumentNode; +export const TeamNameFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamNameFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode; +export const PersonEditorFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PersonEditorFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; +export const TeamEditorFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamEditorFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"marathon"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"year"}}]}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"type"}}]}}]} as unknown as DocumentNode; +export const PeopleTableFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PeopleTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"dbRole"}},{"kind":"Field","name":{"kind":"Name","value":"primaryCommittee"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]} as unknown as DocumentNode; +export const TeamsTableFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamsTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"totalPoints"}}]}}]} as unknown as DocumentNode; +export const NotificationDeliveriesTableFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationDeliveriesTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationDeliveryNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryError"}},{"kind":"Field","name":{"kind":"Name","value":"receiptCheckedAt"}},{"kind":"Field","name":{"kind":"Name","value":"sentAt"}}]}}]} as unknown as DocumentNode; +export const NotificationsTableFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationsTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssue"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssueAcknowledgedAt"}},{"kind":"Field","name":{"kind":"Name","value":"sendAt"}},{"kind":"Field","name":{"kind":"Name","value":"startedSendingAt"}}]}}]} as unknown as DocumentNode; +export const PointEntryTableFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PointEntryTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PointEntryNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"personFrom"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"pointOpportunity"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"opportunityDate"}}]}},{"kind":"Field","name":{"kind":"Name","value":"comment"}}]}}]} as unknown as DocumentNode; +export const PersonViewerFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PersonViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"dbRole"}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"committees"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]} as unknown as DocumentNode; +export const TeamViewerFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"marathon"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"year"}}]}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"totalPoints"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"person"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}},{"kind":"Field","name":{"kind":"Name","value":"position"}}]}}]}}]} as unknown as DocumentNode; +export const ConfigFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ConfigFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ConfigurationNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"validAfter"}},{"kind":"Field","name":{"kind":"Name","value":"validUntil"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; +export const EventsTableFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventsTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"interval"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"end"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"summary"}}]}}]} as unknown as DocumentNode; +export const EventEditorFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventEditorFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"summary"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"interval"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"end"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}}]}}]}}]} as unknown as DocumentNode; +export const EventViewerFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"summary"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"interval"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"end"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; +export const ImagesTableFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ImagesTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ImageNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"mimeType"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; +export const MarathonTableFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MarathonTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarathonNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"year"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}}]}}]} as unknown as DocumentNode; +export const MarathonViewerFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MarathonViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarathonNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"year"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}},{"kind":"Field","name":{"kind":"Name","value":"hours"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"shownStartingAt"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]}}]} as unknown as DocumentNode; +export const ActiveMarathonDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ActiveMarathon"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"latestMarathon"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"year"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}}]}},{"kind":"Field","name":{"kind":"Name","value":"marathons"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"sendAll"},"value":{"kind":"BooleanValue","value":true}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"year"}}]}}]}}]}}]} as unknown as DocumentNode; +export const SelectedMarathonDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SelectedMarathon"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"marathonId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"marathon"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"marathonId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"year"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}}]}}]}}]} as unknown as DocumentNode; +export const ImagePickerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ImagePicker"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ImageResolverKeyedStringFilterItem"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"images"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"stringFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"IntValue","value":"9"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]}}]}}]} as unknown as DocumentNode; +export const PersonSearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PersonSearch"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"search"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"searchPeopleByName"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"search"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}},{"kind":"Field","name":{"kind":"Name","value":"personByLinkBlue"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"linkBlueId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"search"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}}]}}]} as unknown as DocumentNode; export const CreateNotificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateNotification"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"title"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"body"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"audience"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationAudienceInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"url"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stageNotification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"title"},"value":{"kind":"Variable","name":{"kind":"Name","value":"title"}}},{"kind":"Argument","name":{"kind":"Name","value":"body"},"value":{"kind":"Variable","name":{"kind":"Name","value":"body"}}},{"kind":"Argument","name":{"kind":"Name","value":"audience"},"value":{"kind":"Variable","name":{"kind":"Name","value":"audience"}}},{"kind":"Argument","name":{"kind":"Name","value":"url"},"value":{"kind":"Variable","name":{"kind":"Name","value":"url"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}}]} as unknown as DocumentNode; -export const CancelNotificationScheduleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CancelNotificationSchedule"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"abortScheduledNotification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; -export const DeleteNotificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteNotification"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"force"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteNotification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"force"},"value":{"kind":"Variable","name":{"kind":"Name","value":"force"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; -export const SendNotificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SendNotification"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sendNotification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; -export const ScheduleNotificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ScheduleNotification"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sendAt"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DateTimeISO"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"scheduleNotification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"sendAt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sendAt"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; -export const PersonCreatorDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"PersonCreator"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreatePersonInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createPerson"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}},{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}}]} as unknown as DocumentNode; -export const PersonEditorDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"PersonEditor"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SetPersonInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setPerson"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; -export const CreatePointEntryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreatePointEntry"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreatePointEntryInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createPointEntry"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}}]}}]} as unknown as DocumentNode; -export const GetPersonByUuidDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetPersonByUuid"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"person"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}}]}}]}}]} as unknown as DocumentNode; -export const GetPersonByLinkBlueDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetPersonByLinkBlue"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"linkBlue"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personByLinkBlue"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"linkBlueId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"linkBlue"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; -export const SearchPersonByNameDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SearchPersonByName"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"searchPeopleByName"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; -export const CreatePersonByLinkBlueDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreatePersonByLinkBlue"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"linkBlue"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"email"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EmailAddress"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"teamUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createPerson"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"email"},"value":{"kind":"Variable","name":{"kind":"Name","value":"email"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"linkblue"},"value":{"kind":"Variable","name":{"kind":"Name","value":"linkBlue"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"memberOf"},"value":{"kind":"ListValue","values":[{"kind":"Variable","name":{"kind":"Name","value":"teamUuid"}}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}}]} as unknown as DocumentNode; -export const PointEntryOpportunityLookupDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PointEntryOpportunityLookup"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pointOpportunities"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"stringFilters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"field"},"value":{"kind":"EnumValue","value":"name"}},{"kind":"ObjectField","name":{"kind":"Name","value":"comparison"},"value":{"kind":"EnumValue","value":"SUBSTRING"}},{"kind":"ObjectField","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}}]}},{"kind":"Argument","name":{"kind":"Name","value":"sendAll"},"value":{"kind":"BooleanValue","value":true}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}}]}}]} as unknown as DocumentNode; +export const CancelNotificationScheduleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CancelNotificationSchedule"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"abortScheduledNotification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; +export const DeleteNotificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteNotification"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"force"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteNotification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"force"},"value":{"kind":"Variable","name":{"kind":"Name","value":"force"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; +export const SendNotificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SendNotification"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sendNotification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; +export const ScheduleNotificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ScheduleNotification"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sendAt"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DateTimeISO"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"scheduleNotification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"sendAt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sendAt"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; +export const PersonCreatorDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"PersonCreator"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreatePersonInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createPerson"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; +export const PersonEditorDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"PersonEditor"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SetPersonInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setPerson"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; +export const CreatePointEntryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreatePointEntry"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreatePointEntryInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createPointEntry"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; +export const GetPersonByUuidDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetPersonByUuid"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"person"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}}]}}]} as unknown as DocumentNode; +export const GetPersonByLinkBlueDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetPersonByLinkBlue"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"linkBlue"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personByLinkBlue"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"linkBlueId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"linkBlue"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; +export const SearchPersonByNameDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SearchPersonByName"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"searchPeopleByName"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; +export const CreatePersonByLinkBlueDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreatePersonByLinkBlue"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"linkBlue"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"email"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EmailAddress"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"teamUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createPerson"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"email"},"value":{"kind":"Variable","name":{"kind":"Name","value":"email"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"linkblue"},"value":{"kind":"Variable","name":{"kind":"Name","value":"linkBlue"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"memberOf"},"value":{"kind":"ListValue","values":[{"kind":"Variable","name":{"kind":"Name","value":"teamUuid"}}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; +export const PointEntryOpportunityLookupDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PointEntryOpportunityLookup"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pointOpportunities"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"stringFilters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"field"},"value":{"kind":"EnumValue","value":"name"}},{"kind":"ObjectField","name":{"kind":"Name","value":"comparison"},"value":{"kind":"EnumValue","value":"SUBSTRING"}},{"kind":"ObjectField","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}}]}},{"kind":"Argument","name":{"kind":"Name","value":"sendAll"},"value":{"kind":"BooleanValue","value":true}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; export const CreatePointOpportunityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreatePointOpportunity"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreatePointOpportunityInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createPointOpportunity"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}}]} as unknown as DocumentNode; -export const TeamCreatorDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"TeamCreator"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateTeamInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createTeam"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}},{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}}]} as unknown as DocumentNode; -export const TeamEditorDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"TeamEditor"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SetTeamInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setTeam"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; -export const PeopleTableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PeopleTable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SortDirection"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PersonResolverKeyedIsNullFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PersonResolverKeyedOneOfFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PersonResolverKeyedStringFilterItem"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"listPeople"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}}},{"kind":"Argument","name":{"kind":"Name","value":"isNullFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"oneOfFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"stringFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"page"}},{"kind":"Field","name":{"kind":"Name","value":"pageSize"}},{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PeopleTableFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PeopleTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dbRole"}},{"kind":"Field","name":{"kind":"Name","value":"committeeRole"}},{"kind":"Field","name":{"kind":"Name","value":"committeeIdentifier"}}]}}]}}]} as unknown as DocumentNode; -export const TeamsTableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"TeamsTable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SortDirection"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResolverKeyedIsNullFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResolverKeyedOneOfFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResolverKeyedStringFilterItem"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"teams"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}}},{"kind":"Argument","name":{"kind":"Name","value":"isNullFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"oneOfFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"stringFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"page"}},{"kind":"Field","name":{"kind":"Name","value":"pageSize"}},{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TeamsTableFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamsTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"marathonYear"}},{"kind":"Field","name":{"kind":"Name","value":"totalPoints"}}]}}]} as unknown as DocumentNode; -export const NotificationDeliveriesTableQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"NotificationDeliveriesTableQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"notificationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SortDirection"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"dateFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationDeliveryResolverKeyedDateFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationDeliveryResolverKeyedIsNullFilterItem"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notificationDeliveries"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"notificationUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"notificationId"}}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}}},{"kind":"Argument","name":{"kind":"Name","value":"dateFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"dateFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"isNullFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"page"}},{"kind":"Field","name":{"kind":"Name","value":"pageSize"}},{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationDeliveriesTableFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationDeliveriesTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationDeliveryResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryError"}},{"kind":"Field","name":{"kind":"Name","value":"receiptCheckedAt"}},{"kind":"Field","name":{"kind":"Name","value":"sentAt"}}]}}]} as unknown as DocumentNode; -export const NotificationsTableQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"NotificationsTableQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SortDirection"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"dateFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationResolverKeyedDateFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationResolverKeyedIsNullFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationResolverKeyedOneOfFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationResolverKeyedStringFilterItem"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notifications"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}}},{"kind":"Argument","name":{"kind":"Name","value":"dateFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"dateFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"isNullFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"oneOfFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"stringFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"page"}},{"kind":"Field","name":{"kind":"Name","value":"pageSize"}},{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationsTableFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationsTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssue"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssueAcknowledgedAt"}},{"kind":"Field","name":{"kind":"Name","value":"sendAt"}},{"kind":"Field","name":{"kind":"Name","value":"startedSendingAt"}}]}}]} as unknown as DocumentNode; -export const DeletePointEntryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeletePointEntry"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deletePointEntry"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; -export const DeletePersonDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeletePerson"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deletePerson"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; -export const DeleteTeamDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteTeam"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteTeam"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; -export const LoginStateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"LoginState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"loginState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"loggedIn"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dbRole"}},{"kind":"Field","name":{"kind":"Name","value":"committeeRole"}},{"kind":"Field","name":{"kind":"Name","value":"committeeIdentifier"}}]}}]}}]}}]} as unknown as DocumentNode; +export const TeamCreatorDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"TeamCreator"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateTeamInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"marathonUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createTeam"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}},{"kind":"Argument","name":{"kind":"Name","value":"marathon"},"value":{"kind":"Variable","name":{"kind":"Name","value":"marathonUuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}},{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}}]} as unknown as DocumentNode; +export const TeamEditorDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"TeamEditor"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SetTeamInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setTeam"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; +export const PeopleTableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PeopleTable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SortDirection"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PersonResolverKeyedIsNullFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PersonResolverKeyedOneOfFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PersonResolverKeyedStringFilterItem"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"listPeople"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}}},{"kind":"Argument","name":{"kind":"Name","value":"isNullFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"oneOfFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"stringFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"page"}},{"kind":"Field","name":{"kind":"Name","value":"pageSize"}},{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PeopleTableFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PeopleTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"dbRole"}},{"kind":"Field","name":{"kind":"Name","value":"primaryCommittee"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]} as unknown as DocumentNode; +export const TeamsTableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"TeamsTable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SortDirection"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResolverKeyedIsNullFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResolverKeyedOneOfFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResolverKeyedStringFilterItem"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"teams"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}}},{"kind":"Argument","name":{"kind":"Name","value":"isNullFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"oneOfFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"stringFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"page"}},{"kind":"Field","name":{"kind":"Name","value":"pageSize"}},{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TeamsTableFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamsTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"totalPoints"}}]}}]} as unknown as DocumentNode; +export const NotificationDeliveriesTableQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"NotificationDeliveriesTableQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"notificationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SortDirection"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"dateFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationDeliveryResolverKeyedDateFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationDeliveryResolverKeyedIsNullFilterItem"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notificationDeliveries"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"notificationUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"notificationId"}}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}}},{"kind":"Argument","name":{"kind":"Name","value":"dateFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"dateFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"isNullFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"page"}},{"kind":"Field","name":{"kind":"Name","value":"pageSize"}},{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationDeliveriesTableFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationDeliveriesTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationDeliveryNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryError"}},{"kind":"Field","name":{"kind":"Name","value":"receiptCheckedAt"}},{"kind":"Field","name":{"kind":"Name","value":"sentAt"}}]}}]} as unknown as DocumentNode; +export const NotificationsTableQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"NotificationsTableQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SortDirection"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"dateFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationResolverKeyedDateFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationResolverKeyedIsNullFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationResolverKeyedOneOfFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationResolverKeyedStringFilterItem"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notifications"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}}},{"kind":"Argument","name":{"kind":"Name","value":"dateFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"dateFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"isNullFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"oneOfFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"stringFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"page"}},{"kind":"Field","name":{"kind":"Name","value":"pageSize"}},{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationsTableFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationsTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssue"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssueAcknowledgedAt"}},{"kind":"Field","name":{"kind":"Name","value":"sendAt"}},{"kind":"Field","name":{"kind":"Name","value":"startedSendingAt"}}]}}]} as unknown as DocumentNode; +export const DeletePointEntryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeletePointEntry"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deletePointEntry"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; +export const DeletePersonDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeletePerson"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deletePerson"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; +export const DeleteTeamDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteTeam"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteTeam"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; +export const LoginStateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"LoginState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"loginState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"loggedIn"}},{"kind":"Field","name":{"kind":"Name","value":"dbRole"}},{"kind":"Field","name":{"kind":"Name","value":"effectiveCommitteeRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}}]}}]}}]}}]} as unknown as DocumentNode; export const CommitConfigChangesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CommitConfigChanges"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"changes"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateConfigurationInput"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createConfigurations"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"changes"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; -export const ConfigQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ConfigQuery"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allConfigurations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ConfigFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ConfigFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ConfigurationResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"validAfter"}},{"kind":"Field","name":{"kind":"Name","value":"validUntil"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; -export const CreateEventDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateEvent"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateEventInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createEvent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}}]}}]} as unknown as DocumentNode; -export const EventsTableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EventsTable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SortDirection"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"dateFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EventResolverKeyedDateFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EventResolverKeyedIsNullFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EventResolverKeyedOneOfFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EventResolverKeyedStringFilterItem"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"events"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}}},{"kind":"Argument","name":{"kind":"Name","value":"dateFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"dateFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"isNullFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"oneOfFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"stringFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"page"}},{"kind":"Field","name":{"kind":"Name","value":"pageSize"}},{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"EventsTableFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventsTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"interval"}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"summary"}}]}}]} as unknown as DocumentNode; -export const EditEventPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EditEventPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"EventEditorFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventEditorFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"summary"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"interval"}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}}]}}]}}]} as unknown as DocumentNode; -export const SaveEventDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SaveEvent"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SetEventInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setEvent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"EventEditorFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventEditorFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"summary"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"interval"}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}}]}}]}}]} as unknown as DocumentNode; -export const DeleteEventDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteEvent"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteEvent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; -export const ViewEventPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ViewEventPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"EventViewerFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"summary"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"interval"}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; -export const FeedPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FeedPage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"feed"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"NullValue"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"textContent"}},{"kind":"Field","name":{"kind":"Name","value":"image"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}}]}}]}}]}}]} as unknown as DocumentNode; -export const CreateFeedItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateFeedItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateFeedInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createFeedItem"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}}]} as unknown as DocumentNode; +export const ConfigQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ConfigQuery"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allConfigurations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ConfigFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ConfigFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ConfigurationNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"validAfter"}},{"kind":"Field","name":{"kind":"Name","value":"validUntil"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; +export const CreateEventDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateEvent"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateEventInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createEvent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; +export const EventsTableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EventsTable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SortDirection"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"dateFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EventResolverKeyedDateFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EventResolverKeyedIsNullFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EventResolverKeyedOneOfFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EventResolverKeyedStringFilterItem"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"events"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}}},{"kind":"Argument","name":{"kind":"Name","value":"dateFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"dateFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"isNullFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"oneOfFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"stringFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"page"}},{"kind":"Field","name":{"kind":"Name","value":"pageSize"}},{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"EventsTableFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventsTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"interval"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"end"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"summary"}}]}}]} as unknown as DocumentNode; +export const EditEventPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EditEventPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"EventEditorFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventEditorFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"summary"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"interval"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"end"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}}]}}]}}]} as unknown as DocumentNode; +export const SaveEventDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SaveEvent"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SetEventInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setEvent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"EventEditorFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventEditorFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"summary"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"interval"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"end"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}}]}}]}}]} as unknown as DocumentNode; +export const DeleteEventDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteEvent"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteEvent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; +export const ViewEventPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ViewEventPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"EventViewerFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EventViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EventNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"summary"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"occurrences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"interval"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"end"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fullDay"}}]}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; +export const FeedPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FeedPage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"feed"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"NullValue"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"textContent"}},{"kind":"Field","name":{"kind":"Name","value":"image"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}}]}}]}}]}}]} as unknown as DocumentNode; +export const CreateFeedItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateFeedItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateFeedInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createFeedItem"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; export const DeleteFeedItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteFeedItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteFeedItem"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"feedItemUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}]}]}}]} as unknown as DocumentNode; -export const CreateImageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateImage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateImageInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createImage"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}}]} as unknown as DocumentNode; -export const ImagesTableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ImagesTable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SortDirection"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"dateFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ImageResolverKeyedDateFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ImageResolverKeyedIsNullFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ImageResolverKeyedOneOfFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ImageResolverKeyedStringFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"numericFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ImageResolverKeyedNumericFilterItem"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"images"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}}},{"kind":"Argument","name":{"kind":"Name","value":"dateFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"dateFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"isNullFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"oneOfFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"stringFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"numericFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"numericFilters"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"page"}},{"kind":"Field","name":{"kind":"Name","value":"pageSize"}},{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ImagesTableFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ImagesTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ImageResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"mimeType"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; -export const CreateMarathonDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateMarathon"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateMarathonInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createMarathon"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}}]} as unknown as DocumentNode; -export const MarathonOverviewPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MarathonOverviewPage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nextMarathon"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MarathonViewerFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"marathons"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"sendAll"},"value":{"kind":"BooleanValue","value":true}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MarathonTableFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MarathonViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarathonResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"year"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}},{"kind":"Field","name":{"kind":"Name","value":"hours"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"shownStartingAt"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MarathonTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarathonResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"year"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}}]}}]} as unknown as DocumentNode; -export const EditMarathonDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"EditMarathon"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SetMarathonInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"marathonId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setMarathon"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}},{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"marathonId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}}]} as unknown as DocumentNode; -export const GetMarathonDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetMarathon"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"marathonId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"marathon"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"marathonId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"year"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}}]}}]}}]} as unknown as DocumentNode; -export const MarathonPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MarathonPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"marathonUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"marathon"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"marathonUuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MarathonViewerFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MarathonViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarathonResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"year"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}},{"kind":"Field","name":{"kind":"Name","value":"hours"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"shownStartingAt"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]}}]} as unknown as DocumentNode; -export const AddMarathonHourDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddMarathonHour"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateMarathonHourInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"marathonUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createMarathonHour"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}},{"kind":"Argument","name":{"kind":"Name","value":"marathonUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"marathonUuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}}]} as unknown as DocumentNode; -export const EditMarathonHourDataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EditMarathonHourData"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"marathonHourUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"marathonHour"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"marathonHourUuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"details"}},{"kind":"Field","name":{"kind":"Name","value":"durationInfo"}},{"kind":"Field","name":{"kind":"Name","value":"shownStartingAt"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]}}]} as unknown as DocumentNode; -export const EditMarathonHourDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"EditMarathonHour"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SetMarathonHourInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setMarathonHour"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}},{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}}]} as unknown as DocumentNode; -export const NotificationManagerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"NotificationManager"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SingleNotificationFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SingleNotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssue"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssueAcknowledgedAt"}},{"kind":"Field","name":{"kind":"Name","value":"sendAt"}},{"kind":"Field","name":{"kind":"Name","value":"startedSendingAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryCount"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssueCount"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"DeviceNotRegistered"}},{"kind":"Field","name":{"kind":"Name","value":"InvalidCredentials"}},{"kind":"Field","name":{"kind":"Name","value":"MessageRateExceeded"}},{"kind":"Field","name":{"kind":"Name","value":"MessageTooBig"}},{"kind":"Field","name":{"kind":"Name","value":"MismatchSenderId"}},{"kind":"Field","name":{"kind":"Name","value":"Unknown"}}]}}]}}]} as unknown as DocumentNode; -export const NotificationViewerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"NotificationViewer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SingleNotificationFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SingleNotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssue"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssueAcknowledgedAt"}},{"kind":"Field","name":{"kind":"Name","value":"sendAt"}},{"kind":"Field","name":{"kind":"Name","value":"startedSendingAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryCount"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssueCount"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"DeviceNotRegistered"}},{"kind":"Field","name":{"kind":"Name","value":"InvalidCredentials"}},{"kind":"Field","name":{"kind":"Name","value":"MessageRateExceeded"}},{"kind":"Field","name":{"kind":"Name","value":"MessageTooBig"}},{"kind":"Field","name":{"kind":"Name","value":"MismatchSenderId"}},{"kind":"Field","name":{"kind":"Name","value":"Unknown"}}]}}]}}]} as unknown as DocumentNode; -export const CreatePersonPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CreatePersonPage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"teams"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"sendAll"},"value":{"kind":"BooleanValue","value":true}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"ListValue","values":[{"kind":"StringValue","value":"name","block":false}]}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"ListValue","values":[{"kind":"EnumValue","value":"ASCENDING"}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TeamNameFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamNameFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode; -export const EditPersonPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EditPersonPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"person"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PersonEditorFragment"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"sendAll"},"value":{"kind":"BooleanValue","value":true}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"ListValue","values":[{"kind":"StringValue","value":"name","block":false}]}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"ListValue","values":[{"kind":"EnumValue","value":"ASCENDING"}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TeamNameFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PersonEditorFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"committeeRole"}},{"kind":"Field","name":{"kind":"Name","value":"committeeIdentifier"}}]}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamNameFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode; -export const ViewPersonPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ViewPersonPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"person"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PersonViewerFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PersonViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dbRole"}},{"kind":"Field","name":{"kind":"Name","value":"committeeRole"}},{"kind":"Field","name":{"kind":"Name","value":"committeeIdentifier"}}]}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; -export const EditTeamPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EditTeamPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"team"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TeamEditorFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamEditorFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"marathonYear"}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"persistentIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"type"}}]}}]} as unknown as DocumentNode; -export const ViewTeamPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ViewTeamPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"teamUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"team"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"teamUuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TeamViewerFragment"}},{"kind":"Field","name":{"kind":"Name","value":"pointEntries"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PointEntryTableFragment"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"marathonYear"}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"totalPoints"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"person"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"captains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"person"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PointEntryTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PointEntryResource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"personFrom"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"pointOpportunity"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"opportunityDate"}}]}},{"kind":"Field","name":{"kind":"Name","value":"comment"}}]}}]} as unknown as DocumentNode; \ No newline at end of file +export const CreateImageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateImage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateImageInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createImage"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; +export const ImagesTableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ImagesTable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SortDirection"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"dateFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ImageResolverKeyedDateFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ImageResolverKeyedIsNullFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ImageResolverKeyedOneOfFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ImageResolverKeyedStringFilterItem"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"numericFilters"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ImageResolverKeyedNumericFilterItem"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"images"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortBy"}}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sortDirection"}}},{"kind":"Argument","name":{"kind":"Name","value":"dateFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"dateFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"isNullFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isNullFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"oneOfFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"oneOfFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"stringFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stringFilters"}}},{"kind":"Argument","name":{"kind":"Name","value":"numericFilters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"numericFilters"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"page"}},{"kind":"Field","name":{"kind":"Name","value":"pageSize"}},{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ImagesTableFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ImagesTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ImageNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"thumbHash"}},{"kind":"Field","name":{"kind":"Name","value":"height"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"alt"}},{"kind":"Field","name":{"kind":"Name","value":"mimeType"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; +export const CreateMarathonDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateMarathon"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateMarathonInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createMarathon"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; +export const MarathonOverviewPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MarathonOverviewPage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"latestMarathon"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MarathonViewerFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"marathons"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"sendAll"},"value":{"kind":"BooleanValue","value":true}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MarathonTableFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MarathonViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarathonNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"year"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}},{"kind":"Field","name":{"kind":"Name","value":"hours"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"shownStartingAt"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MarathonTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarathonNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"year"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}}]}}]} as unknown as DocumentNode; +export const EditMarathonDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"EditMarathon"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SetMarathonInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"marathonId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setMarathon"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}},{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"marathonId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; +export const GetMarathonDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetMarathon"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"marathonId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"marathon"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"marathonId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"year"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}}]}}]}}]} as unknown as DocumentNode; +export const MarathonPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MarathonPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"marathonUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"marathon"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"marathonUuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MarathonViewerFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MarathonViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarathonNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"year"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}},{"kind":"Field","name":{"kind":"Name","value":"hours"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"shownStartingAt"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]}}]} as unknown as DocumentNode; +export const AddMarathonHourDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddMarathonHour"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateMarathonHourInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"marathonUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createMarathonHour"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}},{"kind":"Argument","name":{"kind":"Name","value":"marathonUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"marathonUuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; +export const EditMarathonHourDataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EditMarathonHourData"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"marathonHourUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"marathonHour"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"marathonHourUuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"details"}},{"kind":"Field","name":{"kind":"Name","value":"durationInfo"}},{"kind":"Field","name":{"kind":"Name","value":"shownStartingAt"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]}}]} as unknown as DocumentNode; +export const EditMarathonHourDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"EditMarathonHour"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SetMarathonHourInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setMarathonHour"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}},{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; +export const NotificationManagerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"NotificationManager"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SingleNotificationFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SingleNotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssue"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssueAcknowledgedAt"}},{"kind":"Field","name":{"kind":"Name","value":"sendAt"}},{"kind":"Field","name":{"kind":"Name","value":"startedSendingAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryCount"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssueCount"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"DeviceNotRegistered"}},{"kind":"Field","name":{"kind":"Name","value":"InvalidCredentials"}},{"kind":"Field","name":{"kind":"Name","value":"MessageRateExceeded"}},{"kind":"Field","name":{"kind":"Name","value":"MessageTooBig"}},{"kind":"Field","name":{"kind":"Name","value":"MismatchSenderId"}},{"kind":"Field","name":{"kind":"Name","value":"Unknown"}}]}}]}}]} as unknown as DocumentNode; +export const NotificationViewerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"NotificationViewer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SingleNotificationFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SingleNotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssue"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssueAcknowledgedAt"}},{"kind":"Field","name":{"kind":"Name","value":"sendAt"}},{"kind":"Field","name":{"kind":"Name","value":"startedSendingAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryCount"}},{"kind":"Field","name":{"kind":"Name","value":"deliveryIssueCount"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"DeviceNotRegistered"}},{"kind":"Field","name":{"kind":"Name","value":"InvalidCredentials"}},{"kind":"Field","name":{"kind":"Name","value":"MessageRateExceeded"}},{"kind":"Field","name":{"kind":"Name","value":"MessageTooBig"}},{"kind":"Field","name":{"kind":"Name","value":"MismatchSenderId"}},{"kind":"Field","name":{"kind":"Name","value":"Unknown"}}]}}]}}]} as unknown as DocumentNode; +export const CreatePersonPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CreatePersonPage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"teams"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"sendAll"},"value":{"kind":"BooleanValue","value":true}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"ListValue","values":[{"kind":"StringValue","value":"name","block":false}]}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"ListValue","values":[{"kind":"EnumValue","value":"asc"}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TeamNameFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamNameFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode; +export const EditPersonPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EditPersonPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"person"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PersonEditorFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"sendAll"},"value":{"kind":"BooleanValue","value":true}},{"kind":"Argument","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"ListValue","values":[{"kind":"StringValue","value":"name","block":false}]}},{"kind":"Argument","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"ListValue","values":[{"kind":"EnumValue","value":"asc"}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TeamNameFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PersonEditorFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamNameFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode; +export const ViewPersonPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ViewPersonPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"person"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PersonViewerFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PersonViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"dbRole"}},{"kind":"Field","name":{"kind":"Name","value":"teams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"committees"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]} as unknown as DocumentNode; +export const EditTeamPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EditTeamPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"team"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TeamEditorFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamEditorFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"marathon"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"year"}}]}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"type"}}]}}]} as unknown as DocumentNode; +export const ViewTeamFundraisingDocumentDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ViewTeamFundraisingDocument"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"teamUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"team"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"teamUuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fundraisingEntries"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"sendAll"},"value":{"kind":"BooleanValue","value":true}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"donatedByText"}},{"kind":"Field","name":{"kind":"Name","value":"donatedToText"}},{"kind":"Field","name":{"kind":"Name","value":"donatedOn"}},{"kind":"Field","name":{"kind":"Name","value":"assignments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"person"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const ViewTeamPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ViewTeamPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"teamUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GlobalId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"team"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"teamUuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TeamViewerFragment"}},{"kind":"Field","name":{"kind":"Name","value":"pointEntries"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PointEntryTableFragment"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TeamViewerFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TeamNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"marathon"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"year"}}]}},{"kind":"Field","name":{"kind":"Name","value":"legacyStatus"}},{"kind":"Field","name":{"kind":"Name","value":"totalPoints"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"person"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}},{"kind":"Field","name":{"kind":"Name","value":"position"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PointEntryTableFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PointEntryNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"personFrom"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"linkblue"}}]}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"pointOpportunity"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"opportunityDate"}}]}},{"kind":"Field","name":{"kind":"Name","value":"comment"}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/packages/common/lib/graphql-client-public/index.ts b/packages/common/lib/graphql-client-portal/index.ts similarity index 100% rename from packages/common/lib/graphql-client-public/index.ts rename to packages/common/lib/graphql-client-portal/index.ts diff --git a/packages/common/lib/graphql-client-public/gql.ts b/packages/common/lib/graphql-client-public/gql.ts deleted file mode 100644 index 2c68b131..00000000 --- a/packages/common/lib/graphql-client-public/gql.ts +++ /dev/null @@ -1,167 +0,0 @@ -/* eslint-disable */ -import * as types from './graphql.js'; -import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'; - -/** - * Map of all GraphQL operations in the project. - * - * This map has several performance disadvantages: - * 1. It is not tree-shakeable, so it will include all operations in the project. - * 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle. - * 3. It does not support dead code elimination, so it will add unused operations. - * - * Therefore it is highly recommended to use the babel or swc plugin for production. - */ -const documents = { - "\n fragment ImageViewFragment on ImageResource {\n uuid\n url\n thumbHash\n alt\n width\n height\n mimeType\n }\n": types.ImageViewFragmentFragmentDoc, - "\n fragment SimpleConfig on ConfigurationResource {\n uuid\n key\n value\n }\n": types.SimpleConfigFragmentDoc, - "\n fragment FullConfig on ConfigurationResource {\n ...SimpleConfig\n validAfter\n validUntil\n createdAt\n }\n": types.FullConfigFragmentDoc, - "\n fragment NotificationFragment on NotificationResource {\n uuid\n title\n body\n url\n }\n": types.NotificationFragmentFragmentDoc, - "\n fragment NotificationDeliveryFragment on NotificationDeliveryResource {\n uuid\n sentAt\n notification {\n ...NotificationFragment\n }\n }\n": types.NotificationDeliveryFragmentFragmentDoc, - "\n query useAllowedLoginTypes {\n activeConfiguration(key: \"ALLOWED_LOGIN_TYPES\") {\n data {\n ...SimpleConfig\n }\n }\n }\n": types.UseAllowedLoginTypesDocument, - "\n query MarathonTime {\n nextMarathon {\n startDate\n endDate\n }\n }\n": types.MarathonTimeDocument, - "\n query useTabBarConfig {\n activeConfiguration(key: \"TAB_BAR_CONFIG\") {\n data {\n ...SimpleConfig\n }\n }\n me {\n data {\n linkblue\n }\n }\n }\n": types.UseTabBarConfigDocument, - "\n query TriviaCrack {\n activeConfiguration(key: \"TRIVIA_CRACK\") {\n data {\n ...SimpleConfig\n }\n }\n\n me {\n data {\n teams {\n team {\n type\n name\n }\n }\n }\n }\n }\n ": types.TriviaCrackDocument, - "\n query AuthState {\n me {\n data {\n uuid\n }\n }\n loginState {\n role {\n dbRole\n committeeIdentifier\n committeeRole\n }\n loggedIn\n authSource\n }\n }\n": types.AuthStateDocument, - "\n mutation SetDevice($input: RegisterDeviceInput!) {\n registerDevice(input: $input) {\n ok\n }\n }\n": types.SetDeviceDocument, - "\n fragment EventScreenFragment on EventResource {\n uuid\n title\n summary\n description\n location\n occurrences {\n uuid\n interval\n fullDay\n }\n images {\n thumbHash\n url\n height\n width\n alt\n mimeType\n }\n }\n": types.EventScreenFragmentFragmentDoc, - "\n query DeviceNotifications(\n $deviceUuid: String!\n $page: Int\n $pageSize: Int\n $verifier: String!\n ) {\n device(uuid: $deviceUuid) {\n data {\n notificationDeliveries(\n pageSize: $pageSize\n page: $page\n verifier: $verifier\n ) {\n ...NotificationDeliveryFragment\n }\n }\n }\n }\n": types.DeviceNotificationsDocument, - "\n fragment ProfileScreenAuthFragment on LoginState {\n role {\n committeeIdentifier\n committeeRole\n dbRole\n }\n authSource\n }\n": types.ProfileScreenAuthFragmentFragmentDoc, - "\n fragment ProfileScreenUserFragment on PersonResource {\n name\n linkblue\n teams {\n position\n team {\n name\n }\n }\n }\n": types.ProfileScreenUserFragmentFragmentDoc, - "\n query RootScreenDocument {\n loginState {\n ...ProfileScreenAuthFragment\n ...RootScreenAuthFragment\n }\n me {\n data {\n ...ProfileScreenUserFragment\n }\n }\n }\n": types.RootScreenDocumentDocument, - "\n fragment RootScreenAuthFragment on LoginState {\n role {\n dbRole\n }\n }\n": types.RootScreenAuthFragmentFragmentDoc, - "\n query Events(\n $earliestTimestamp: LuxonDateTime!\n $lastTimestamp: LuxonDateTime!\n ) {\n events(\n dateFilters: [\n {\n comparison: GREATER_THAN_OR_EQUAL_TO\n field: occurrenceStart\n value: $earliestTimestamp\n }\n {\n comparison: LESS_THAN_OR_EQUAL_TO\n field: occurrenceStart\n value: $lastTimestamp\n }\n ]\n sortDirection: ASCENDING\n sortBy: \"occurrence\"\n ) {\n data {\n ...EventScreenFragment\n }\n }\n }\n ": types.EventsDocument, - "\n query ServerFeed {\n feed(limit: 20) {\n uuid\n title\n createdAt\n textContent\n image {\n url\n alt\n width\n height\n thumbHash\n }\n }\n }\n": types.ServerFeedDocument, - "\n fragment HourScreenFragment on MarathonHourResource {\n uuid\n title\n details\n durationInfo\n mapImages {\n ...ImageViewFragment\n }\n }\n": types.HourScreenFragmentFragmentDoc, - "\n query MarathonScreen {\n currentMarathonHour {\n ...HourScreenFragment\n }\n nextMarathon {\n startDate\n endDate\n hours {\n ...HourScreenFragment\n }\n }\n }\n": types.MarathonScreenDocument, - "\n fragment ScoreBoardFragment on TeamResource {\n uuid\n name\n totalPoints\n legacyStatus\n type\n }\n": types.ScoreBoardFragmentFragmentDoc, - "\n fragment HighlightedTeamFragment on TeamResource {\n uuid\n name\n legacyStatus\n type\n }\n": types.HighlightedTeamFragmentFragmentDoc, - "\n query ScoreBoardDocument($type: [TeamType!]) {\n me {\n data {\n uuid\n teams {\n team {\n ...HighlightedTeamFragment\n ...MyTeamFragment\n }\n }\n }\n }\n teams(\n sendAll: true\n sortBy: [\"totalPoints\", \"name\"]\n sortDirection: [DESCENDING, ASCENDING]\n type: $type\n ) {\n data {\n ...ScoreBoardFragment\n }\n }\n }\n": types.ScoreBoardDocumentDocument, - "\n query ActiveMarathonDocument {\n currentMarathon {\n uuid\n }\n }\n": types.ActiveMarathonDocumentDocument, - "\n fragment MyTeamFragment on TeamResource {\n uuid\n name\n totalPoints\n pointEntries {\n personFrom {\n uuid\n name\n linkblue\n }\n points\n }\n members {\n position\n person {\n linkblue\n name\n }\n }\n }\n": types.MyTeamFragmentFragmentDoc, -}; - -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - * - * - * @example - * ```ts - * const query = graphql(`query GetUser($id: ID!) { user(id: $id) { name } }`); - * ``` - * - * The query argument is unknown! - * Please regenerate the types. - */ -export function graphql(source: string): unknown; - -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n fragment ImageViewFragment on ImageResource {\n uuid\n url\n thumbHash\n alt\n width\n height\n mimeType\n }\n"): (typeof documents)["\n fragment ImageViewFragment on ImageResource {\n uuid\n url\n thumbHash\n alt\n width\n height\n mimeType\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n fragment SimpleConfig on ConfigurationResource {\n uuid\n key\n value\n }\n"): (typeof documents)["\n fragment SimpleConfig on ConfigurationResource {\n uuid\n key\n value\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n fragment FullConfig on ConfigurationResource {\n ...SimpleConfig\n validAfter\n validUntil\n createdAt\n }\n"): (typeof documents)["\n fragment FullConfig on ConfigurationResource {\n ...SimpleConfig\n validAfter\n validUntil\n createdAt\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n fragment NotificationFragment on NotificationResource {\n uuid\n title\n body\n url\n }\n"): (typeof documents)["\n fragment NotificationFragment on NotificationResource {\n uuid\n title\n body\n url\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n fragment NotificationDeliveryFragment on NotificationDeliveryResource {\n uuid\n sentAt\n notification {\n ...NotificationFragment\n }\n }\n"): (typeof documents)["\n fragment NotificationDeliveryFragment on NotificationDeliveryResource {\n uuid\n sentAt\n notification {\n ...NotificationFragment\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query useAllowedLoginTypes {\n activeConfiguration(key: \"ALLOWED_LOGIN_TYPES\") {\n data {\n ...SimpleConfig\n }\n }\n }\n"): (typeof documents)["\n query useAllowedLoginTypes {\n activeConfiguration(key: \"ALLOWED_LOGIN_TYPES\") {\n data {\n ...SimpleConfig\n }\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query MarathonTime {\n nextMarathon {\n startDate\n endDate\n }\n }\n"): (typeof documents)["\n query MarathonTime {\n nextMarathon {\n startDate\n endDate\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query useTabBarConfig {\n activeConfiguration(key: \"TAB_BAR_CONFIG\") {\n data {\n ...SimpleConfig\n }\n }\n me {\n data {\n linkblue\n }\n }\n }\n"): (typeof documents)["\n query useTabBarConfig {\n activeConfiguration(key: \"TAB_BAR_CONFIG\") {\n data {\n ...SimpleConfig\n }\n }\n me {\n data {\n linkblue\n }\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query TriviaCrack {\n activeConfiguration(key: \"TRIVIA_CRACK\") {\n data {\n ...SimpleConfig\n }\n }\n\n me {\n data {\n teams {\n team {\n type\n name\n }\n }\n }\n }\n }\n "): (typeof documents)["\n query TriviaCrack {\n activeConfiguration(key: \"TRIVIA_CRACK\") {\n data {\n ...SimpleConfig\n }\n }\n\n me {\n data {\n teams {\n team {\n type\n name\n }\n }\n }\n }\n }\n "]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query AuthState {\n me {\n data {\n uuid\n }\n }\n loginState {\n role {\n dbRole\n committeeIdentifier\n committeeRole\n }\n loggedIn\n authSource\n }\n }\n"): (typeof documents)["\n query AuthState {\n me {\n data {\n uuid\n }\n }\n loginState {\n role {\n dbRole\n committeeIdentifier\n committeeRole\n }\n loggedIn\n authSource\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n mutation SetDevice($input: RegisterDeviceInput!) {\n registerDevice(input: $input) {\n ok\n }\n }\n"): (typeof documents)["\n mutation SetDevice($input: RegisterDeviceInput!) {\n registerDevice(input: $input) {\n ok\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n fragment EventScreenFragment on EventResource {\n uuid\n title\n summary\n description\n location\n occurrences {\n uuid\n interval\n fullDay\n }\n images {\n thumbHash\n url\n height\n width\n alt\n mimeType\n }\n }\n"): (typeof documents)["\n fragment EventScreenFragment on EventResource {\n uuid\n title\n summary\n description\n location\n occurrences {\n uuid\n interval\n fullDay\n }\n images {\n thumbHash\n url\n height\n width\n alt\n mimeType\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query DeviceNotifications(\n $deviceUuid: String!\n $page: Int\n $pageSize: Int\n $verifier: String!\n ) {\n device(uuid: $deviceUuid) {\n data {\n notificationDeliveries(\n pageSize: $pageSize\n page: $page\n verifier: $verifier\n ) {\n ...NotificationDeliveryFragment\n }\n }\n }\n }\n"): (typeof documents)["\n query DeviceNotifications(\n $deviceUuid: String!\n $page: Int\n $pageSize: Int\n $verifier: String!\n ) {\n device(uuid: $deviceUuid) {\n data {\n notificationDeliveries(\n pageSize: $pageSize\n page: $page\n verifier: $verifier\n ) {\n ...NotificationDeliveryFragment\n }\n }\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n fragment ProfileScreenAuthFragment on LoginState {\n role {\n committeeIdentifier\n committeeRole\n dbRole\n }\n authSource\n }\n"): (typeof documents)["\n fragment ProfileScreenAuthFragment on LoginState {\n role {\n committeeIdentifier\n committeeRole\n dbRole\n }\n authSource\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n fragment ProfileScreenUserFragment on PersonResource {\n name\n linkblue\n teams {\n position\n team {\n name\n }\n }\n }\n"): (typeof documents)["\n fragment ProfileScreenUserFragment on PersonResource {\n name\n linkblue\n teams {\n position\n team {\n name\n }\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query RootScreenDocument {\n loginState {\n ...ProfileScreenAuthFragment\n ...RootScreenAuthFragment\n }\n me {\n data {\n ...ProfileScreenUserFragment\n }\n }\n }\n"): (typeof documents)["\n query RootScreenDocument {\n loginState {\n ...ProfileScreenAuthFragment\n ...RootScreenAuthFragment\n }\n me {\n data {\n ...ProfileScreenUserFragment\n }\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n fragment RootScreenAuthFragment on LoginState {\n role {\n dbRole\n }\n }\n"): (typeof documents)["\n fragment RootScreenAuthFragment on LoginState {\n role {\n dbRole\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query Events(\n $earliestTimestamp: LuxonDateTime!\n $lastTimestamp: LuxonDateTime!\n ) {\n events(\n dateFilters: [\n {\n comparison: GREATER_THAN_OR_EQUAL_TO\n field: occurrenceStart\n value: $earliestTimestamp\n }\n {\n comparison: LESS_THAN_OR_EQUAL_TO\n field: occurrenceStart\n value: $lastTimestamp\n }\n ]\n sortDirection: ASCENDING\n sortBy: \"occurrence\"\n ) {\n data {\n ...EventScreenFragment\n }\n }\n }\n "): (typeof documents)["\n query Events(\n $earliestTimestamp: LuxonDateTime!\n $lastTimestamp: LuxonDateTime!\n ) {\n events(\n dateFilters: [\n {\n comparison: GREATER_THAN_OR_EQUAL_TO\n field: occurrenceStart\n value: $earliestTimestamp\n }\n {\n comparison: LESS_THAN_OR_EQUAL_TO\n field: occurrenceStart\n value: $lastTimestamp\n }\n ]\n sortDirection: ASCENDING\n sortBy: \"occurrence\"\n ) {\n data {\n ...EventScreenFragment\n }\n }\n }\n "]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query ServerFeed {\n feed(limit: 20) {\n uuid\n title\n createdAt\n textContent\n image {\n url\n alt\n width\n height\n thumbHash\n }\n }\n }\n"): (typeof documents)["\n query ServerFeed {\n feed(limit: 20) {\n uuid\n title\n createdAt\n textContent\n image {\n url\n alt\n width\n height\n thumbHash\n }\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n fragment HourScreenFragment on MarathonHourResource {\n uuid\n title\n details\n durationInfo\n mapImages {\n ...ImageViewFragment\n }\n }\n"): (typeof documents)["\n fragment HourScreenFragment on MarathonHourResource {\n uuid\n title\n details\n durationInfo\n mapImages {\n ...ImageViewFragment\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query MarathonScreen {\n currentMarathonHour {\n ...HourScreenFragment\n }\n nextMarathon {\n startDate\n endDate\n hours {\n ...HourScreenFragment\n }\n }\n }\n"): (typeof documents)["\n query MarathonScreen {\n currentMarathonHour {\n ...HourScreenFragment\n }\n nextMarathon {\n startDate\n endDate\n hours {\n ...HourScreenFragment\n }\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n fragment ScoreBoardFragment on TeamResource {\n uuid\n name\n totalPoints\n legacyStatus\n type\n }\n"): (typeof documents)["\n fragment ScoreBoardFragment on TeamResource {\n uuid\n name\n totalPoints\n legacyStatus\n type\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n fragment HighlightedTeamFragment on TeamResource {\n uuid\n name\n legacyStatus\n type\n }\n"): (typeof documents)["\n fragment HighlightedTeamFragment on TeamResource {\n uuid\n name\n legacyStatus\n type\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query ScoreBoardDocument($type: [TeamType!]) {\n me {\n data {\n uuid\n teams {\n team {\n ...HighlightedTeamFragment\n ...MyTeamFragment\n }\n }\n }\n }\n teams(\n sendAll: true\n sortBy: [\"totalPoints\", \"name\"]\n sortDirection: [DESCENDING, ASCENDING]\n type: $type\n ) {\n data {\n ...ScoreBoardFragment\n }\n }\n }\n"): (typeof documents)["\n query ScoreBoardDocument($type: [TeamType!]) {\n me {\n data {\n uuid\n teams {\n team {\n ...HighlightedTeamFragment\n ...MyTeamFragment\n }\n }\n }\n }\n teams(\n sendAll: true\n sortBy: [\"totalPoints\", \"name\"]\n sortDirection: [DESCENDING, ASCENDING]\n type: $type\n ) {\n data {\n ...ScoreBoardFragment\n }\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query ActiveMarathonDocument {\n currentMarathon {\n uuid\n }\n }\n"): (typeof documents)["\n query ActiveMarathonDocument {\n currentMarathon {\n uuid\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n fragment MyTeamFragment on TeamResource {\n uuid\n name\n totalPoints\n pointEntries {\n personFrom {\n uuid\n name\n linkblue\n }\n points\n }\n members {\n position\n person {\n linkblue\n name\n }\n }\n }\n"): (typeof documents)["\n fragment MyTeamFragment on TeamResource {\n uuid\n name\n totalPoints\n pointEntries {\n personFrom {\n uuid\n name\n linkblue\n }\n points\n }\n members {\n position\n person {\n linkblue\n name\n }\n }\n }\n"]; - -export function graphql(source: string) { - return (documents as any)[source] ?? {}; -} - -export type DocumentType> = TDocumentNode extends DocumentNode< infer TType, any> ? TType : never; \ No newline at end of file diff --git a/packages/common/lib/index.ts b/packages/common/lib/index.ts index 2c62688d..3c190d2b 100644 --- a/packages/common/lib/index.ts +++ b/packages/common/lib/index.ts @@ -36,10 +36,6 @@ export * from "./utility/time/intervalTools.js"; export * from "./api/resources/index.js"; export * from "./utility/errors/DetailedError.js"; -export * from "./api/scalars/DateRangeScalar.js"; -export * from "./api/scalars/DateTimeScalar.js"; -export * from "./api/scalars/DurationScalar.js"; - export * from "./api/filtering/list-query-args/FilterItem.js"; export * from "./api/filtering/list-query-args/FilteredListQueryArgs.js"; export * from "./api/filtering/list-query-args/UnfilteredListQueryArgs.js"; @@ -48,10 +44,6 @@ export * from "./api/filtering/list-query-args/registerFilterKeyEnums.js"; export * from "./ui/color.js"; -// React specific code: -export * from "./ui/formReducer.js"; -export { initializeReact } from "./ui/reactLib.js"; - /* Note: If the .js is missing in a bunch of places, use this regex to replace: diff --git a/packages/common/lib/react.ts b/packages/common/lib/react.ts deleted file mode 100644 index 62b0a456..00000000 --- a/packages/common/lib/react.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./ui/formReducer.js"; -export * from "./ui/reactLib.js"; diff --git a/packages/common/lib/ui/formReducer.ts b/packages/common/lib/ui/formReducer.ts deleted file mode 100644 index 7d8ef3c1..00000000 --- a/packages/common/lib/ui/formReducer.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type { PrimitiveObject } from "../utility/primitive/TypeUtils.js"; - -import { getReact } from "./reactLib.js"; - -type FormMap = Map; - -export type UpdatePayload = T extends Map ? [k, V] : never; - -export type FormErrors = Partial< - Record ->; - -/** - * Allowed action names: - * - "reset": Resets the form to the initial state - * - "update": Updates the form with a new value - * - "remove-field": Removes a field from the form (ONLY USE FOR OPTIONAL FIELDS) - * - "set": Sets the entire form to a new value - * - * @param initialState The initial state of the form - * @param validator A function that validates the form and returns an object with errors - * @return A tuple with the reducer and the errors - */ -export const useFormReducer = ( - initialState: T, - validator?: (state: T) => FormErrors -) => { - const { useState, useReducer } = getReact(); - - const [errors, setErrors] = useState>({}); - const reducer = useReducer( - ( - state: T, - [keyword, payload]: - | ["reset"] - | ["update", UpdatePayload] - | ["remove-field", keyof T] - | ["set", T] - ): T => { - const updatedState = state; - switch (keyword) { - case "reset": { - return initialState; - } - case "update": { - const [key, newValue] = payload; - updatedState.set(key, newValue); - if (validator) { - setErrors(validator(updatedState)); - } - return updatedState; - } - case "remove-field": { - const updatedState = { - ...state, - }; - updatedState.delete(payload); - if (validator) { - setErrors(validator(updatedState)); - } - return updatedState; - } - case "set": { - if (validator) { - setErrors(validator(payload)); - } - return payload; - } - default: { - throw new Error("Invalid action"); - } - } - }, - initialState - ); - - return [reducer, errors] as const; -}; diff --git a/packages/common/lib/ui/reactLib.ts b/packages/common/lib/ui/reactLib.ts deleted file mode 100644 index bc375847..00000000 --- a/packages/common/lib/ui/reactLib.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { - useReducer as useReducerType, - useState as useStateType, -} from "react"; - -let reactLib: { - useReducer: typeof useReducerType; - useState: typeof useStateType; -} | null = null; - -export const initializeReact = (lib: typeof reactLib) => { - reactLib = lib; -}; - -export const getReact = () => { - if (!reactLib) { - throw new Error( - "You must call initializeReact before using db-app-commons's react functionality!" - ); - } - return reactLib; -}; diff --git a/packages/common/lib/utility/time/intervalTools.ts b/packages/common/lib/utility/time/intervalTools.ts index c7d7f933..f917c01e 100644 --- a/packages/common/lib/utility/time/intervalTools.ts +++ b/packages/common/lib/utility/time/intervalTools.ts @@ -1,5 +1,4 @@ -import type { Interval } from "luxon"; -import { DateTime } from "luxon"; +import { DateTime, Interval } from "luxon"; type ValidInterval = Interval & { start: NonNullable; @@ -28,41 +27,120 @@ export function validateInterval( } export function dateTimeFromSomething( - something: string | number | Date | DateTime -): DateTime; + something: string | number | Date | DateTime +): DateTime; export function dateTimeFromSomething(something: null): null; export function dateTimeFromSomething(something: undefined): undefined; export function dateTimeFromSomething( - something: string | number | Date | DateTime | null -): DateTime | null; + something: string | number | Date | DateTime | null +): DateTime | null; export function dateTimeFromSomething( - something: string | number | Date | DateTime | undefined -): DateTime | undefined; + something: string | number | Date | DateTime | undefined +): DateTime | undefined; export function dateTimeFromSomething( - something: string | number | Date | DateTime | null | undefined -): DateTime | null | undefined; + something: string | number | Date | DateTime | null | undefined +): DateTime | null | undefined; export function dateTimeFromSomething( - something: string | number | Date | DateTime | null | undefined -): DateTime | null | undefined { + something: string | number | Date | DateTime | null | undefined +): DateTime | null | undefined { if (something == null) { return something; } + let dateTime = null; switch (typeof something) { case "string": { - return DateTime.fromISO(something); + dateTime = DateTime.fromISO(something); + break; } case "number": { - return DateTime.fromMillis(something); + dateTime = DateTime.fromMillis(something); + break; } case "object": { - return something instanceof Date - ? DateTime.fromJSDate(something) - : DateTime.isDateTime(something) - ? something - : DateTime.invalid("Invalid input type for dateTimeFromSomething"); + dateTime = + something instanceof Date + ? DateTime.fromJSDate(something) + : DateTime.isDateTime(something) + ? something + : DateTime.invalid("Invalid input type for dateTimeFromSomething"); + break; } default: { - return DateTime.invalid("Invalid input type for dateTimeFromSomething"); + dateTime = DateTime.invalid( + "Invalid input type for dateTimeFromSomething" + ); + break; } } + + return dateTime.isValid ? dateTime : null; +} + +export function intervalFromSomething( + something: + | string + | { + start: string | number | Date | DateTime; + end: string | number | Date | DateTime; + } +): Interval | Interval; +export function intervalFromSomething(something: { + start: DateTime; + end: DateTime; +}): Interval; +export function intervalFromSomething(something: null): null; +export function intervalFromSomething(something: undefined): undefined; +export function intervalFromSomething( + something: + | string + | { + start: string | number | Date | DateTime; + end: string | number | Date | DateTime; + } + | null + | undefined +): Interval | Interval | null | undefined; +export function intervalFromSomething( + something: + | string + | { + start: string | number | Date | DateTime; + end: string | number | Date | DateTime; + } + | null +): Interval | Interval | null; +export function intervalFromSomething( + something: + | string + | { + start: string | number | Date | DateTime; + end: string | number | Date | DateTime; + } + | undefined +): Interval | Interval | undefined; +export function intervalFromSomething( + something: + | string + | { + start: string | number | Date | DateTime; + end: string | number | Date | DateTime; + } + | null + | undefined +): Interval | Interval | null | undefined { + if (something == null) { + return something; + } + + let interval = null; + if (typeof something === "string") { + return Interval.fromISO(something); + } else if (typeof something === "object") { + const start = dateTimeFromSomething(something.start); + const end = dateTimeFromSomething(something.end); + + interval = Interval.fromDateTimes(start, end); + } + + return interval; } diff --git a/packages/common/package.json b/packages/common/package.json index 700c6fbf..0590d9ac 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -15,32 +15,33 @@ "node": "./dist/index.js", "types": "./dist/index.d.ts" }, - "./react": { - "node": null, - "types": "./dist/react.d.ts", - "default": "./dist/react/index.js" - }, "./client-parsers": { "node": null, "default": "./dist/client-parsers/index.js" }, - "./graphql-client-admin": { + "./error": { + "types": "./dist/error/index.d.ts", + "node": "./dist/error/index.js", + "default": null + }, + "./graphql-client-portal": { "node": null, "react-native": null, - "types": "./dist/graphql-client-admin/index.d.ts", - "default": "./dist/graphql-client-admin/index.js" + "types": "./dist/graphql-client-portal/index.d.ts", + "default": "./dist/graphql-client-portal/index.js" }, - "./graphql-client-public": { + "./graphql-client-mobile": { "node": null, - "react-native": "./dist/graphql-client-public/index.js", - "types": "./dist/graphql-client-public/index.d.ts", - "default": "./dist/graphql-client-public/index.js" + "browser": null, + "react-native": "./dist/graphql-client-mobile/index.js", + "types": "./dist/graphql-client-mobile/index.d.ts", + "default": "./dist/graphql-client-mobile/index.js" }, - "./graphql-client-admin/raw-types": { - "types": "./dist/graphql-client-admin/graphql.d.ts" + "./graphql-client-portal/raw-types": { + "types": "./dist/graphql-client-portal/graphql.d.ts" }, - "./graphql-client-public/raw-types": { - "types": "./dist/graphql-client-public/graphql.d.ts" + "./graphql-client-mobile/raw-types": { + "types": "./dist/graphql-client-mobile/graphql.d.ts" } }, "main": "./dist/index.js", @@ -63,7 +64,7 @@ "jest": "^29.5.0", "ts-jest": "^29.1.0", "ts-node": "^10.9.1", - "typescript": "^5.4.3", + "typescript": "^5.5.3", "vitest": "^1.4.0" }, "peerDependencies": { @@ -79,6 +80,7 @@ }, "packageManager": "yarn@4.1.1+sha256.f3cc0eda8e5560e529c7147565b30faa43b4e472d90e8634d7134a37c7f59781", "dependencies": { - "htmlparser2": "^9.1.0" + "htmlparser2": "^9.1.0", + "ts-results-es": "^4.2.0" } } diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json index 494ebb4d..21eba09e 100644 --- a/packages/common/tsconfig.json +++ b/packages/common/tsconfig.json @@ -3,7 +3,7 @@ "noEmit": false, /* Visit https://aka.ms/tsconfig to read more about this file */ /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + "incremental": true /* Save .tsbuildinfo files to allow for incremental compilation of projects. */, // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ diff --git a/packages/mobile/package.json b/packages/mobile/package.json index 5d891e28..2eba830f 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -105,7 +105,7 @@ "reflect-metadata": "^0.1.13", "type-graphql": "^2.0.0-beta.3", "typedi": "^0.10.0", - "typescript": "^5.4.3", + "typescript": "^5.5.3", "urql": "^4.0.6", "utility-types": "^3.10.0", "validator": "^13.9.0" diff --git a/packages/mobile/src/common/components/ImageView/ImageView.tsx b/packages/mobile/src/common/components/ImageView/ImageView.tsx index d7a5632e..22c99008 100644 --- a/packages/mobile/src/common/components/ImageView/ImageView.tsx +++ b/packages/mobile/src/common/components/ImageView/ImageView.tsx @@ -1,13 +1,13 @@ -import type { FragmentType } from "@ukdanceblue/common/dist/graphql-client-public"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-mobile"; import { getFragmentData, graphql, -} from "@ukdanceblue/common/dist/graphql-client-public"; +} from "@ukdanceblue/common/graphql-client-mobile"; import { Image, type ImageProps } from "expo-image"; export const ImageViewFragment = graphql(/* GraphQL */ ` - fragment ImageViewFragment on ImageResource { - uuid + fragment ImageViewFragment on ImageNode { + id url thumbHash alt diff --git a/packages/mobile/src/common/fragments/Configuration.ts b/packages/mobile/src/common/fragments/Configuration.ts index a98a1ecc..804dacf9 100644 --- a/packages/mobile/src/common/fragments/Configuration.ts +++ b/packages/mobile/src/common/fragments/Configuration.ts @@ -1,15 +1,15 @@ -import { graphql } from "@ukdanceblue/common/dist/graphql-client-public"; +import { graphql } from "@ukdanceblue/common/graphql-client-mobile"; export const SimpleConfigFragment = graphql(/* GraphQL */ ` - fragment SimpleConfig on ConfigurationResource { - uuid + fragment SimpleConfig on ConfigurationNode { + id key value } `); export const FullConfigFragment = graphql(/* GraphQL */ ` - fragment FullConfig on ConfigurationResource { + fragment FullConfig on ConfigurationNode { ...SimpleConfig validAfter validUntil diff --git a/packages/mobile/src/common/fragments/NotificationScreenGQL.ts b/packages/mobile/src/common/fragments/NotificationScreenGQL.ts index 4c9e160b..bcee6d59 100644 --- a/packages/mobile/src/common/fragments/NotificationScreenGQL.ts +++ b/packages/mobile/src/common/fragments/NotificationScreenGQL.ts @@ -1,8 +1,8 @@ -import { graphql } from "@ukdanceblue/common/dist/graphql-client-public"; +import { graphql } from "@ukdanceblue/common/graphql-client-mobile"; export const NotificationFragment = graphql(/* GraphQL */ ` - fragment NotificationFragment on NotificationResource { - uuid + fragment NotificationFragment on NotificationNode { + id title body url @@ -10,8 +10,8 @@ export const NotificationFragment = graphql(/* GraphQL */ ` `); export const NotificationDeliveryFragment = graphql(/* GraphQL */ ` - fragment NotificationDeliveryFragment on NotificationDeliveryResource { - uuid + fragment NotificationDeliveryFragment on NotificationDeliveryNode { + id sentAt notification { ...NotificationFragment diff --git a/packages/mobile/src/common/hooks/useAllowedLoginTypes.ts b/packages/mobile/src/common/hooks/useAllowedLoginTypes.ts index 67ad10e1..730076d0 100644 --- a/packages/mobile/src/common/hooks/useAllowedLoginTypes.ts +++ b/packages/mobile/src/common/hooks/useAllowedLoginTypes.ts @@ -4,7 +4,7 @@ import type { UserLoginType } from "@context/user"; import { getFragmentData, graphql, -} from "@ukdanceblue/common/dist/graphql-client-public"; +} from "@ukdanceblue/common/graphql-client-mobile"; import { useEffect, useMemo } from "react"; import { useQuery } from "urql"; diff --git a/packages/mobile/src/common/hooks/useMarathonTime.ts b/packages/mobile/src/common/hooks/useMarathonTime.ts index 04279b07..918838a0 100644 --- a/packages/mobile/src/common/hooks/useMarathonTime.ts +++ b/packages/mobile/src/common/hooks/useMarathonTime.ts @@ -1,6 +1,6 @@ import { Logger } from "@common/logger/Logger"; import { dateTimeFromSomething } from "@ukdanceblue/common"; -import { graphql } from "@ukdanceblue/common/dist/graphql-client-public"; +import { graphql } from "@ukdanceblue/common/graphql-client-mobile"; import { DateTime } from "luxon"; import { useEffect, useMemo } from "react"; import { useQuery } from "urql"; @@ -12,7 +12,7 @@ export interface MarathonTime { const marathonTimeQuery = graphql(/* GraphQL */ ` query MarathonTime { - nextMarathon { + latestMarathon { startDate endDate } @@ -37,25 +37,28 @@ export function useMarathonTime(): { }); const marathonInterval = useMemo(() => { - let startTime: DateTime = DateTime.fromMillis(0); - let endTime: DateTime = DateTime.fromMillis(0); - try { - if (data?.nextMarathon) { - startTime = dateTimeFromSomething(data.nextMarathon.startDate); - if (!startTime.isValid) { + if (data?.latestMarathon) { + const startTime = dateTimeFromSomething(data.latestMarathon.startDate); + if (!startTime?.isValid) { + Logger.warn( + `Unrecognized marathon start time: ${startTime?.toString()}`, + { + source: "useMarathonTime", + } + ); + } + const endTime = dateTimeFromSomething(data.latestMarathon.endDate); + if (!endTime?.isValid) { Logger.warn( - `Unrecognized marathon start time: ${startTime.toString()}`, + `Unrecognized marathon end time: ${endTime?.toString()}`, { source: "useMarathonTime", } ); } - endTime = dateTimeFromSomething(data.nextMarathon.endDate); - if (!endTime.isValid) { - Logger.warn(`Unrecognized marathon end time: ${endTime.toString()}`, { - source: "useMarathonTime", - }); + if (startTime && endTime) { + return { startTime, endTime }; } } } catch (error) { @@ -65,8 +68,12 @@ export function useMarathonTime(): { }); } - return { startTime, endTime } as MarathonTime; - }, [data?.nextMarathon]); + // TODO: find a better indicator of "no marathon" + return { + startTime: DateTime.fromMillis(0), + endTime: DateTime.fromMillis(0), + }; + }, [data?.latestMarathon]); return { timesLoading: fetching, diff --git a/packages/mobile/src/common/hooks/useTabBarConfig.ts b/packages/mobile/src/common/hooks/useTabBarConfig.ts index 6e09095e..6627a60d 100644 --- a/packages/mobile/src/common/hooks/useTabBarConfig.ts +++ b/packages/mobile/src/common/hooks/useTabBarConfig.ts @@ -3,7 +3,7 @@ import { Logger } from "@common/logger/Logger"; import { getFragmentData, graphql, -} from "@ukdanceblue/common/dist/graphql-client-public"; +} from "@ukdanceblue/common/graphql-client-mobile"; import { useEffect, useMemo } from "react"; import { useQuery } from "urql"; @@ -15,9 +15,7 @@ const useTabBarConfigQuery = graphql(/* GraphQL */ ` } } me { - data { - linkblue - } + linkblue } } `); @@ -109,6 +107,6 @@ export function useTabBarConfig(): { tabConfigLoading: fetching, fancyTab, shownTabs, - forceAll: data?.me.data?.linkblue === "demo-user", + forceAll: data?.me?.linkblue === "demo-user", }; } diff --git a/packages/mobile/src/common/marathonComponents/TriviaCrack.tsx b/packages/mobile/src/common/marathonComponents/TriviaCrack.tsx index d5c2b483..50ad257e 100644 --- a/packages/mobile/src/common/marathonComponents/TriviaCrack.tsx +++ b/packages/mobile/src/common/marathonComponents/TriviaCrack.tsx @@ -4,7 +4,7 @@ import { TeamType } from "@ukdanceblue/common"; import { getFragmentData, graphql, -} from "@ukdanceblue/common/dist/graphql-client-public"; +} from "@ukdanceblue/common/graphql-client-mobile"; import { Text, View } from "native-base"; import { useEffect, useMemo, useState } from "react"; import { ActivityIndicator } from "react-native"; @@ -47,12 +47,10 @@ export function TriviaCrack() { } me { - data { - teams { - team { - type - name - } + teams { + team { + type + name } } } @@ -72,9 +70,9 @@ export function TriviaCrack() { null; let moraleTeamNumber: number | undefined; if (typeof value === "object" && value !== null) { - if ((data?.me.data?.teams.length ?? 0) > 0) { + if ((data?.me?.teams.length ?? 0) > 0) { const moraleTeams = - data?.me.data?.teams.filter( + data?.me?.teams.filter( (team) => team.team.type === TeamType.Morale ) ?? []; if (moraleTeams[0]?.team.name.startsWith("Morale Team")) { @@ -112,7 +110,7 @@ export function TriviaCrack() { } } return { stationOrder, moraleTeamNumber }; - }, [data?.me.data?.teams, option?.value]); + }, [data?.me?.teams, option?.value]); useEffect(() => { if (stationOrder && spins == null) { diff --git a/packages/mobile/src/context/auth.tsx b/packages/mobile/src/context/auth.tsx index e6772f12..eed7faa1 100644 --- a/packages/mobile/src/context/auth.tsx +++ b/packages/mobile/src/context/auth.tsx @@ -1,6 +1,6 @@ import { Logger } from "@common/logger/Logger"; -import { AuthSource, RoleResource, defaultRole } from "@ukdanceblue/common"; -import { graphql } from "@ukdanceblue/common/dist/graphql-client-public"; +import { AuthSource } from "@ukdanceblue/common"; +import { graphql } from "@ukdanceblue/common/graphql-client-mobile"; import type { ReactNode } from "react"; import { createContext, useContext, useEffect } from "react"; import { useQuery } from "urql"; @@ -8,7 +8,6 @@ import { useQuery } from "urql"; export interface AuthState { personUuid: string | null; loggedIn: boolean; - role: RoleResource; authSource: AuthSource; ready: boolean; @@ -17,7 +16,6 @@ export interface AuthState { const authStateContext = createContext({ personUuid: null, loggedIn: false, - role: defaultRole, authSource: AuthSource.None, ready: false, @@ -26,16 +24,10 @@ const authStateContext = createContext({ const authStateDocument = graphql(/* GraphQL */ ` query AuthState { me { - data { - uuid - } + id } loginState { - role { - dbRole - committeeIdentifier - committeeRole - } + dbRole loggedIn authSource } @@ -59,8 +51,8 @@ export function AuthStateProvider({ children }: { children: ReactNode }) { context: { loggedIn: data?.loginState.loggedIn, authSource: data?.loginState.authSource, - role: data?.loginState.role, - userUuid: data?.me.data?.uuid, + role: data?.loginState.dbRole, + userUuid: data?.me?.id, }, tags: ["graphql"], }); @@ -70,11 +62,8 @@ export function AuthStateProvider({ children }: { children: ReactNode }) { return ( { )} {eventData.occurrences.map((occurrence) => { - const highlighted = occurrence.uuid === occurrenceId; + const highlighted = occurrence.id === occurrenceId; - const interval = Interval.fromISO(occurrence.interval); + const interval = intervalFromSomething(occurrence.interval); const { whenString, allDay } = stringifyInterval(interval); return ( diff --git a/packages/mobile/src/navigation/root/EventScreen/EventScreenFragment.ts b/packages/mobile/src/navigation/root/EventScreen/EventScreenFragment.ts index e11011b9..cc68ae11 100644 --- a/packages/mobile/src/navigation/root/EventScreen/EventScreenFragment.ts +++ b/packages/mobile/src/navigation/root/EventScreen/EventScreenFragment.ts @@ -1,15 +1,18 @@ -import { graphql } from "@ukdanceblue/common/dist/graphql-client-public"; +import { graphql } from "@ukdanceblue/common/graphql-client-mobile"; export const EventScreenFragment = graphql(/* GraphQL */ ` - fragment EventScreenFragment on EventResource { - uuid + fragment EventScreenFragment on EventNode { + id title summary description location occurrences { - uuid - interval + id + interval { + start + end + } fullDay } images { diff --git a/packages/mobile/src/navigation/root/EventScreen/addToCalendar.ts b/packages/mobile/src/navigation/root/EventScreen/addToCalendar.ts index 46d78ad2..4e7488b3 100644 --- a/packages/mobile/src/navigation/root/EventScreen/addToCalendar.ts +++ b/packages/mobile/src/navigation/root/EventScreen/addToCalendar.ts @@ -1,8 +1,9 @@ import { universalCatch } from "@common/logging"; import { showMessage, showPrompt } from "@common/util/alertUtils"; import { discoverDefaultCalendar } from "@common/util/calendar"; -import type { FragmentType } from "@ukdanceblue/common/dist/graphql-client-public"; -import { getFragmentData } from "@ukdanceblue/common/dist/graphql-client-public"; +import { intervalFromSomething } from "@ukdanceblue/common"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-mobile"; +import { getFragmentData } from "@ukdanceblue/common/graphql-client-mobile"; import type { Event } from "expo-calendar"; import { PermissionStatus, @@ -10,7 +11,6 @@ import { getCalendarPermissionsAsync, requestCalendarPermissionsAsync, } from "expo-calendar"; -import { Interval } from "luxon"; import { EventScreenFragment } from "./EventScreenFragment"; @@ -60,7 +60,7 @@ export async function onAddToCalendar( const eventDataToExpoEvent = ( occurrence: (typeof eventData.occurrences)[number] ): Partial => { - const interval = Interval.fromISO(occurrence.interval); + const interval = intervalFromSomething(occurrence.interval); if (!interval.isValid) { throw new Error("Invalid interval"); } @@ -78,7 +78,7 @@ export async function onAddToCalendar( endTimeZone: interval.end.zoneName, organizer: "UK DanceBlue", organizerEmail: "community@danceblue.org", - id: `${eventData.uuid}:${occurrence.uuid}`, + id: `${eventData.id}:${occurrence.id}`, }; }; @@ -86,7 +86,7 @@ export async function onAddToCalendar( expoEvents.push(...eventData.occurrences.map(eventDataToExpoEvent)); } else { const occurrence = eventData.occurrences.find( - (o) => o.uuid === occurrenceId + (o) => o.id === occurrenceId ); if (!occurrence) { diff --git a/packages/mobile/src/navigation/root/NotificationScreen/NotificationRow/NotificationRow.tsx b/packages/mobile/src/navigation/root/NotificationScreen/NotificationRow/NotificationRow.tsx index 27756ebd..79dccfd6 100644 --- a/packages/mobile/src/navigation/root/NotificationScreen/NotificationRow/NotificationRow.tsx +++ b/packages/mobile/src/navigation/root/NotificationScreen/NotificationRow/NotificationRow.tsx @@ -1,6 +1,6 @@ import type { NotificationDeliveryFragment } from "@common/fragments/NotificationScreenGQL"; import { showMessage } from "@common/util/alertUtils"; -import type { FragmentType } from "@ukdanceblue/common/dist/graphql-client-public"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-mobile"; import { Box, Button, Row, useTheme } from "native-base"; import type { SectionListRenderItem } from "react-native"; import { useWindowDimensions } from "react-native"; diff --git a/packages/mobile/src/navigation/root/NotificationScreen/NotificationRow/NotificationRowContent.tsx b/packages/mobile/src/navigation/root/NotificationScreen/NotificationRow/NotificationRowContent.tsx index 26052899..c1bc7ff7 100644 --- a/packages/mobile/src/navigation/root/NotificationScreen/NotificationRow/NotificationRowContent.tsx +++ b/packages/mobile/src/navigation/root/NotificationScreen/NotificationRow/NotificationRowContent.tsx @@ -4,8 +4,8 @@ import { NotificationFragment, } from "@common/fragments/NotificationScreenGQL"; import { Logger } from "@common/logger/Logger"; -import type { FragmentType } from "@ukdanceblue/common/dist/graphql-client-public"; -import { getFragmentData } from "@ukdanceblue/common/dist/graphql-client-public"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-mobile"; +import { getFragmentData } from "@ukdanceblue/common/graphql-client-mobile"; import { openURL } from "expo-linking"; import { isEqual } from "lodash"; import { DateTime } from "luxon"; @@ -54,8 +54,8 @@ const NonMemoizedNotificationRowContent = ({ deliveryFragmentData?.sentAt == null ? null : typeof deliveryFragmentData.sentAt === "string" - ? DateTime.fromISO(deliveryFragmentData.sentAt) - : DateTime.fromJSDate(deliveryFragmentData.sentAt); + ? DateTime.fromISO(deliveryFragmentData.sentAt) + : DateTime.fromJSDate(deliveryFragmentData.sentAt); return ( - getFragmentData(NotificationDeliveryFragment, data)?.uuid ?? + getFragmentData(NotificationDeliveryFragment, data)?.id ?? `notification-${i}` } ListEmptyComponent={() => ( diff --git a/packages/mobile/src/navigation/root/NotificationScreen/NotificationSectionHeader.tsx b/packages/mobile/src/navigation/root/NotificationScreen/NotificationSectionHeader.tsx index b9a527e7..07a1bc7d 100644 --- a/packages/mobile/src/navigation/root/NotificationScreen/NotificationSectionHeader.tsx +++ b/packages/mobile/src/navigation/root/NotificationScreen/NotificationSectionHeader.tsx @@ -1,5 +1,5 @@ import type { NotificationDeliveryFragment } from "@common/fragments/NotificationScreenGQL"; -import type { FragmentType } from "@ukdanceblue/common/dist/graphql-client-public"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-mobile"; import { Heading, View } from "native-base"; import type { SectionListProps } from "react-native"; diff --git a/packages/mobile/src/navigation/root/NotificationScreen/refresh.ts b/packages/mobile/src/navigation/root/NotificationScreen/refresh.ts index a2557f3d..ea4e8e5b 100644 --- a/packages/mobile/src/navigation/root/NotificationScreen/refresh.ts +++ b/packages/mobile/src/navigation/root/NotificationScreen/refresh.ts @@ -4,8 +4,8 @@ import { Logger } from "@common/logger/Logger"; import { showMessage } from "@common/util/alertUtils"; import { useDeviceData } from "@context/device"; import { ErrorCode } from "@ukdanceblue/common"; -import type { FragmentType } from "@ukdanceblue/common/dist/graphql-client-public"; -import { graphql } from "@ukdanceblue/common/dist/graphql-client-public"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-mobile"; +import { graphql } from "@ukdanceblue/common/graphql-client-mobile"; import { DateTime } from "luxon"; import { useCallback, useEffect, useState } from "react"; import { useClient } from "urql"; @@ -15,7 +15,7 @@ const INCOMPLETE_PAGE_TIMEOUT = 10_000; export const deviceNotificationsQuery = graphql(/* GraphQL */ ` query DeviceNotifications( - $deviceUuid: String! + $deviceUuid: GlobalId! $page: Int $pageSize: Int $verifier: String! diff --git a/packages/mobile/src/navigation/root/ProfileScreen/ProfileFooter.tsx b/packages/mobile/src/navigation/root/ProfileScreen/ProfileFooter.tsx index b9496eb9..3788c11a 100644 --- a/packages/mobile/src/navigation/root/ProfileScreen/ProfileFooter.tsx +++ b/packages/mobile/src/navigation/root/ProfileScreen/ProfileFooter.tsx @@ -3,8 +3,8 @@ import { useColorModeValue } from "@common/customHooks"; import { universalCatch } from "@common/logging"; import { Ionicons } from "@expo/vector-icons"; import { AuthSource } from "@ukdanceblue/common"; -import type { FragmentType } from "@ukdanceblue/common/dist/graphql-client-public"; -import { getFragmentData } from "@ukdanceblue/common/dist/graphql-client-public"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-mobile"; +import { getFragmentData } from "@ukdanceblue/common/graphql-client-mobile"; import { nativeApplicationVersion, nativeBuildVersion } from "expo-application"; import { openURL } from "expo-linking"; import { diff --git a/packages/mobile/src/navigation/root/ProfileScreen/ProfileScreen.tsx b/packages/mobile/src/navigation/root/ProfileScreen/ProfileScreen.tsx index 8aa7f5a5..86964336 100644 --- a/packages/mobile/src/navigation/root/ProfileScreen/ProfileScreen.tsx +++ b/packages/mobile/src/navigation/root/ProfileScreen/ProfileScreen.tsx @@ -9,11 +9,11 @@ import { DbRole, committeeNames, } from "@ukdanceblue/common"; -import type { FragmentType } from "@ukdanceblue/common/dist/graphql-client-public"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-mobile"; import { getFragmentData, graphql, -} from "@ukdanceblue/common/dist/graphql-client-public"; +} from "@ukdanceblue/common/graphql-client-mobile"; import { openURL } from "expo-linking"; import { Box, @@ -31,17 +31,13 @@ import { ProfileFooter } from "./ProfileFooter"; export const ProfileScreenAuthFragment = graphql(/* GraphQL */ ` fragment ProfileScreenAuthFragment on LoginState { - role { - committeeIdentifier - committeeRole - dbRole - } + dbRole authSource } `); export const ProfileScreenUserFragment = graphql(/* GraphQL */ ` - fragment ProfileScreenUserFragment on PersonResource { + fragment ProfileScreenUserFragment on PersonNode { name linkblue teams { @@ -50,6 +46,10 @@ export const ProfileScreenUserFragment = graphql(/* GraphQL */ ` name } } + primaryCommittee { + identifier + role + } } `); @@ -94,23 +94,22 @@ const ProfileScreen = ({ } const committeeString = useMemo(() => { - if (authData?.role.dbRole === DbRole.Committee) { + if (userData?.primaryCommittee) { if ( - authData.role.committeeIdentifier === - CommitteeIdentifier.viceCommittee && - authData.role.committeeRole === CommitteeRole.Chair + // TODO: Add a way to query committee info + userData.primaryCommittee.identifier === + CommitteeIdentifier.overallCommittee && + userData.primaryCommittee.role === CommitteeRole.Chair ) { return "✨ Overall Chair ✨"; } return `Committee: ${ - authData.role.committeeIdentifier - ? committeeNames[authData.role.committeeIdentifier] - : "Unknown" - } ${authData.role.committeeRole}`; + committeeNames[userData.primaryCommittee.identifier] + } ${userData.primaryCommittee.role}`; } else { return null; } - }, [authData]); + }, [userData?.primaryCommittee]); if (loading) { return ( @@ -118,7 +117,7 @@ const ProfileScreen = ({ ); - } else if (authData?.role.dbRole !== DbRole.None) { + } else if (authData?.dbRole !== DbRole.None) { return ( <> diff --git a/packages/mobile/src/navigation/root/RootScreen.tsx b/packages/mobile/src/navigation/root/RootScreen.tsx index ec36e329..9e80763b 100644 --- a/packages/mobile/src/navigation/root/RootScreen.tsx +++ b/packages/mobile/src/navigation/root/RootScreen.tsx @@ -4,7 +4,7 @@ import { DbRole } from "@ukdanceblue/common"; import { getFragmentData, graphql, -} from "@ukdanceblue/common/dist/graphql-client-public"; +} from "@ukdanceblue/common/graphql-client-mobile"; import { Center, Text, useTheme } from "native-base"; import { useEffect, useMemo } from "react"; import { useWindowDimensions } from "react-native"; @@ -32,18 +32,14 @@ const rootScreenDocument = graphql(/* GraphQL */ ` ...RootScreenAuthFragment } me { - data { - ...ProfileScreenUserFragment - } + ...ProfileScreenUserFragment } } `); const RootScreenAuthFragment = graphql(/* GraphQL */ ` fragment RootScreenAuthFragment on LoginState { - role { - dbRole - } + dbRole } `); @@ -72,7 +68,7 @@ const RootScreen = () => { rootScreenData?.loginState ?? null ); const isLoggedIn = useMemo(() => { - return authData?.role.dbRole !== DbRole.None; + return authData.dbRole !== DbRole.None; }, [authData]); const { colors } = useTheme(); @@ -123,7 +119,7 @@ const RootScreen = () => { profileScreenAuthFragment={ rootScreenData?.loginState ?? null } - profileScreenUserFragment={rootScreenData?.me.data ?? null} + profileScreenUserFragment={rootScreenData?.me ?? null} /> )} diff --git a/packages/mobile/src/navigation/root/tab/EventListScreen/EventListPage.tsx b/packages/mobile/src/navigation/root/tab/EventListScreen/EventListPage.tsx index a32d90dd..9f9489c3 100644 --- a/packages/mobile/src/navigation/root/tab/EventListScreen/EventListPage.tsx +++ b/packages/mobile/src/navigation/root/tab/EventListScreen/EventListPage.tsx @@ -1,5 +1,5 @@ import type { EventScreenFragment } from "@navigation/root/EventScreen/EventScreenFragment"; -import type { FragmentType } from "@ukdanceblue/common/dist/graphql-client-public"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-mobile"; import type { DateTime } from "luxon"; import { Column, Divider, Spinner, Text } from "native-base"; import { useEffect, useMemo, useRef, useState } from "react"; diff --git a/packages/mobile/src/navigation/root/tab/EventListScreen/EventListRenderItem.tsx b/packages/mobile/src/navigation/root/tab/EventListScreen/EventListRenderItem.tsx index 183d8f38..d000d664 100644 --- a/packages/mobile/src/navigation/root/tab/EventListScreen/EventListRenderItem.tsx +++ b/packages/mobile/src/navigation/root/tab/EventListScreen/EventListRenderItem.tsx @@ -1,8 +1,9 @@ import { EventScreenFragment } from "@navigation/root/EventScreen/EventScreenFragment"; -import type { FragmentType } from "@ukdanceblue/common/dist/graphql-client-public"; -import { getFragmentData } from "@ukdanceblue/common/dist/graphql-client-public"; +import { intervalFromSomething } from "@ukdanceblue/common"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-mobile"; +import { getFragmentData } from "@ukdanceblue/common/graphql-client-mobile"; import { Platform } from "expo-modules-core"; -import { DateTime, Interval } from "luxon"; +import { DateTime } from "luxon"; import { Box, Column, Heading, Row } from "native-base"; import type { MutableRefObject } from "react"; import { useCallback, useMemo } from "react"; @@ -13,7 +14,7 @@ import EventRow from "./EventRow"; import { RNCAL_DATE_FORMAT } from "./constants"; export const EventListRenderItem = ({ - item: [event, occurrenceUuid], + item: [event, occurrenceId], index, dayIndexesRef, tryToNavigate, @@ -37,16 +38,16 @@ export const EventListRenderItem = ({ const occurrence = useMemo(() => { const occurrence = eventData.occurrences.find( - (occurrence) => occurrence.uuid === occurrenceUuid + (occurrence) => occurrence.id === occurrenceId ); if (!occurrence) { return undefined; } return { ...occurrence, - interval: Interval.fromISO(occurrence.interval), + interval: intervalFromSomething(occurrence.interval), }; - }, [occurrenceUuid, eventData.occurrences]); + }, [occurrenceId, eventData.occurrences]); const eventDate = useMemo(() => { return occurrence?.interval.start?.toFormat(RNCAL_DATE_FORMAT); @@ -59,8 +60,8 @@ export const EventListRenderItem = ({ } const onPress = useCallback(() => { - tryToNavigate(event, occurrenceUuid); - }, [event, occurrenceUuid, tryToNavigate]); + tryToNavigate(event, occurrenceId); + }, [event, occurrenceId, tryToNavigate]); return useMemo( () => ( @@ -110,7 +111,7 @@ export const EventListRenderItem = ({ } > {hourScreenData.mapImages.map((image, i) => ( void; }) => { const [secretGestureState, setSecretGestureState] = useState<0 | 1 | 2 | 3>( @@ -27,19 +27,27 @@ export const MarathonCountdownScreen = ({ const { primary } = useThemeColors(); const ordinals = ["th", "st", "nd", "rd"]; // s - const startOrdinal = - ordinals[((marathonStart.day % 100) - 20) % 10] || - ordinals[marathonStart.day % 100] || - ordinals[0]; - const endOrdinal = - ordinals[((marathonEnd.day % 100) - 20) % 10] || - ordinals[marathonEnd.day % 100] || - ordinals[0]; + const startOrdinal = marathonStart + ? ordinals[((marathonStart.day % 100) - 20) % 10] || + ordinals[marathonStart.day % 100] || + ordinals[0] + : null; + const endOrdinal = marathonEnd + ? ordinals[((marathonEnd.day % 100) - 20) % 10] || + ordinals[marathonEnd.day % 100] || + ordinals[0] + : null; // technically this isn't the best way of doing the date but idrc atm - const dateString = `${marathonStart.toFormat("LLLL d")}${startOrdinal} - ${marathonEnd.toFormat("d, yyyy").replace(",", `${endOrdinal},`)}`; + const dateString = + marathonStart && marathonEnd + ? `${marathonStart.toFormat("LLLL d")}${startOrdinal} - ${marathonEnd.toFormat("d, yyyy").replace(",", `${endOrdinal},`)}` + : null; - const timeString = `${marathonStart.toFormat("h:mm a")} - ${marathonEnd.toFormat("h:mm a")}`; + const timeString = + marathonStart && marathonEnd + ? `${marathonStart.toFormat("h:mm a")} - ${marathonEnd.toFormat("h:mm a")}` + : null; return ( - + {marathonStart && ( + + )} { }); const [lastGoodData, setLastGoodData] = useState(data); useEffect(() => { - if (data?.currentMarathonHour || data?.nextMarathon) { + if (data?.currentMarathonHour || data?.latestMarathon) { setLastGoodData(data); } }, [data]); @@ -78,24 +78,25 @@ export const MarathonScreen = () => { No Internet Connection, cannot load marathon information ); - } else if (lastGoodData?.nextMarathon && hourOverride != null) { + } else if (lastGoodData?.latestMarathon && hourOverride != null) { if (hourOverride < 0) { return ( setShowSecretMenu(true)} /> ); } else { return ( refresh({ requestPolicy: "network-only" })} showSecretMenu={() => setShowSecretMenu(true)} /> @@ -110,13 +111,15 @@ export const MarathonScreen = () => { showSecretMenu={() => setShowSecretMenu(true)} /> ); - } else if (lastGoodData?.nextMarathon) { + } else if (lastGoodData?.latestMarathon) { return ( setShowSecretMenu(true)} /> ); @@ -130,7 +133,7 @@ export const MarathonScreen = () => { Logger.info("MarathonScreen has invalid data", { source: "MarathonScreen", context: { - nextMarathon: lastGoodData?.nextMarathon, + latestMarathon: lastGoodData?.latestMarathon, currentHour: lastGoodData?.currentMarathonHour, }, }); @@ -152,7 +155,7 @@ export const MarathonScreen = () => { hourOverride, isInternetReachable, lastGoodData?.currentMarathonHour, - lastGoodData?.nextMarathon, + lastGoodData?.latestMarathon, refresh, vpHeight, vpWidth, @@ -184,8 +187,8 @@ export const MarathonScreen = () => { if ( (!Number.isNaN(parsedText) && parsedText === -1) || (parsedText >= 0 && - data?.nextMarathon && - parsedText <= data.nextMarathon.hours.length) + data?.latestMarathon && + parsedText <= data.latestMarathon.hours.length) ) { setHourOverride(parsedText); } else { diff --git a/packages/mobile/src/navigation/root/tab/spirit/ScoreBoardScreen/ScoreBoardScreen.tsx b/packages/mobile/src/navigation/root/tab/spirit/ScoreBoardScreen/ScoreBoardScreen.tsx index 21f642b6..3431815c 100644 --- a/packages/mobile/src/navigation/root/tab/spirit/ScoreBoardScreen/ScoreBoardScreen.tsx +++ b/packages/mobile/src/navigation/root/tab/spirit/ScoreBoardScreen/ScoreBoardScreen.tsx @@ -2,11 +2,11 @@ import Jumbotron from "@common/components/Jumbotron"; import { FontAwesome5 } from "@expo/vector-icons"; import { useNavigation } from "@react-navigation/native"; import { TeamLegacyStatus, TeamType } from "@ukdanceblue/common"; -import type { FragmentType } from "@ukdanceblue/common/dist/graphql-client-public"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-mobile"; import { getFragmentData, graphql, -} from "@ukdanceblue/common/dist/graphql-client-public"; +} from "@ukdanceblue/common/graphql-client-mobile"; import { Box, CheckIcon, HStack, Select, Text, View } from "native-base"; import { Pressable } from "native-base/src/components/primitives"; import { useEffect, useMemo, useState } from "react"; @@ -32,8 +32,8 @@ function addOrdinal(num: number) { } const ScoreBoardFragment = graphql(/* GraphQL */ ` - fragment ScoreBoardFragment on TeamResource { - uuid + fragment ScoreBoardFragment on TeamNode { + id name totalPoints legacyStatus @@ -42,8 +42,8 @@ const ScoreBoardFragment = graphql(/* GraphQL */ ` `); const HighlightedTeamFragment = graphql(/* GraphQL */ ` - fragment HighlightedTeamFragment on TeamResource { - uuid + fragment HighlightedTeamFragment on TeamNode { + id name legacyStatus type @@ -114,11 +114,11 @@ const ScoreBoardScreen = ({ for (const team of filteredData) { newStandingData.push({ name: team.name, - id: team.uuid, + id: team.id, points: team.totalPoints, - highlighted: team.uuid === userTeamData?.uuid, + highlighted: team.id === userTeamData?.id, }); - if (team.uuid === userTeamData?.uuid) { + if (team.id === userTeamData?.id) { setUserTeamRank(newStandingData.length); } } @@ -129,7 +129,7 @@ const ScoreBoardScreen = ({ return ( {mode === "spirit" ? ( - userTeamData?.uuid == null ? ( + userTeamData?.id == null ? ( navigate("MyTeam") : undefined} + onTeamClick={userTeamData?.id ? () => navigate("MyTeam") : undefined} /> ); diff --git a/packages/mobile/src/navigation/root/tab/spirit/SpiritStack.tsx b/packages/mobile/src/navigation/root/tab/spirit/SpiritStack.tsx index aad9ed13..77df5097 100644 --- a/packages/mobile/src/navigation/root/tab/spirit/SpiritStack.tsx +++ b/packages/mobile/src/navigation/root/tab/spirit/SpiritStack.tsx @@ -2,7 +2,7 @@ import { Logger } from "@common/logger/Logger"; import { showMessage } from "@common/util/alertUtils"; import { createNativeStackNavigator } from "@react-navigation/native-stack"; import { TeamType } from "@ukdanceblue/common"; -import { graphql } from "@ukdanceblue/common/dist/graphql-client-public"; +import { graphql } from "@ukdanceblue/common/graphql-client-mobile"; import { useEffect, useState } from "react"; import { useQuery } from "urql"; @@ -14,20 +14,18 @@ import TeamScreen from "./TeamScreen"; const scoreBoardDocument = graphql(/* GraphQL */ ` query ScoreBoardDocument($type: [TeamType!]) { me { - data { - uuid - teams { - team { - ...HighlightedTeamFragment - ...MyTeamFragment - } + id + teams { + team { + ...HighlightedTeamFragment + ...MyTeamFragment } } } teams( sendAll: true sortBy: ["totalPoints", "name"] - sortDirection: [DESCENDING, ASCENDING] + sortDirection: [desc, asc] type: $type ) { data { @@ -40,7 +38,7 @@ const scoreBoardDocument = graphql(/* GraphQL */ ` const currentMarathonDocument = graphql(/* GraphQL */ ` query ActiveMarathonDocument { currentMarathon { - uuid + id } } `); @@ -103,9 +101,7 @@ const SpiritScreen = () => { {() => ( refresh({ requestPolicy: "network-only" })} @@ -116,8 +112,8 @@ const SpiritScreen = () => { {() => ( diff --git a/packages/mobile/src/navigation/root/tab/spirit/TeamScreen/TeamScreen.tsx b/packages/mobile/src/navigation/root/tab/spirit/TeamScreen/TeamScreen.tsx index c3f23159..2bb0d11f 100644 --- a/packages/mobile/src/navigation/root/tab/spirit/TeamScreen/TeamScreen.tsx +++ b/packages/mobile/src/navigation/root/tab/spirit/TeamScreen/TeamScreen.tsx @@ -1,11 +1,11 @@ import type { StandingType } from "@common-types/StandingType"; import { FontAwesome5 } from "@expo/vector-icons"; import { MembershipPositionType } from "@ukdanceblue/common"; -import type { FragmentType } from "@ukdanceblue/common/dist/graphql-client-public"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-mobile"; import { getFragmentData, graphql, -} from "@ukdanceblue/common/dist/graphql-client-public"; +} from "@ukdanceblue/common/graphql-client-mobile"; import { Center, Text } from "native-base"; import { useEffect, useState } from "react"; import { useWindowDimensions } from "react-native"; @@ -13,13 +13,13 @@ import { useWindowDimensions } from "react-native"; import TeamInformation from "./TeamInformation"; export const MyTeamFragment = graphql(/* GraphQL */ ` - fragment MyTeamFragment on TeamResource { - uuid + fragment MyTeamFragment on TeamNode { + id name totalPoints pointEntries { personFrom { - uuid + id name linkblue } @@ -58,7 +58,7 @@ const TeamScreen = ({ const entriesRecord = new Map(); for (const entry of team.pointEntries) { const { personFrom, points } = entry; - const { uuid, name, linkblue } = personFrom ?? {}; + const { id: uuid, name, linkblue } = personFrom ?? {}; if (uuid) { const existing = entriesRecord.get(uuid); if (existing == null) { diff --git a/packages/mobile/src/types/navigationTypes.ts b/packages/mobile/src/types/navigationTypes.ts index 13a035d8..9787e029 100644 --- a/packages/mobile/src/types/navigationTypes.ts +++ b/packages/mobile/src/types/navigationTypes.ts @@ -4,7 +4,7 @@ import type { NavigatorScreenParams, } from "@react-navigation/native"; import type { NativeStackScreenProps } from "@react-navigation/native-stack"; -import type { FragmentType } from "@ukdanceblue/common/dist/graphql-client-public"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-mobile"; import type { EventScreenFragment } from "../navigation/root/EventScreen/EventScreenFragment"; diff --git a/packages/portal/Dockerfile b/packages/portal/Dockerfile deleted file mode 100644 index 40a98b3d..00000000 --- a/packages/portal/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM steebchen/nginx-spa:stable - -COPY dist/ /app - -EXPOSE 80 - -CMD ["nginx"] diff --git a/packages/portal/package.json b/packages/portal/package.json index e401b016..9b6b9a9e 100644 --- a/packages/portal/package.json +++ b/packages/portal/package.json @@ -41,7 +41,7 @@ "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", "@vitejs/plugin-react-swc": "^3.3.2", - "typescript": "^5.4.3", + "typescript": "^5.5.3", "vite": "^4.4.5" }, "packageManager": "yarn@4.1.1+sha256.f3cc0eda8e5560e529c7147565b30faa43b4e472d90e8634d7134a37c7f59781" diff --git a/packages/portal/src/config/marathon.tsx b/packages/portal/src/config/marathon.tsx new file mode 100644 index 00000000..13e67a5e --- /dev/null +++ b/packages/portal/src/config/marathon.tsx @@ -0,0 +1,95 @@ +import { dateTimeFromSomething } from "@ukdanceblue/common"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; +import { useEffect, useMemo, useState } from "react"; +import { useQuery } from "urql"; + +import { marathonContext } from "./marathonContext"; +import { LocalStorageKeys } from "./storage"; + +const latestMarathonDocument = graphql(/* GraphQL */ ` + query ActiveMarathon { + latestMarathon { + id + year + startDate + endDate + } + marathons(sendAll: true) { + data { + id + year + } + } + } +`); + +const selectedMarathonDocument = graphql(/* GraphQL */ ` + query SelectedMarathon($marathonId: GlobalId!) { + marathon(uuid: $marathonId) { + id + year + startDate + endDate + } + } +`); + +export const MarathonConfigProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const [marathonId, setMarathonId] = useState( + localStorage.getItem(LocalStorageKeys.SelectedMarathon) || null + ); + + useEffect(() => { + if (marathonId) { + localStorage.setItem(LocalStorageKeys.SelectedMarathon, marathonId); + } else { + localStorage.removeItem(LocalStorageKeys.SelectedMarathon); + } + }, [marathonId]); + + const [latestMarathonResult] = useQuery({ query: latestMarathonDocument }); + const [selectedMarathonResult] = useQuery({ + query: selectedMarathonDocument, + variables: { marathonId: marathonId ?? "" }, + pause: marathonId == null, + }); + + let marathon = null; + if (marathonId != null && selectedMarathonResult.data != null) { + marathon = selectedMarathonResult.data.marathon; + } else if (latestMarathonResult.data != null) { + marathon = latestMarathonResult.data.latestMarathon; + } + + const startDate = useMemo(() => { + return dateTimeFromSomething(marathon?.startDate) ?? null; + }, [marathon?.startDate]); + const endDate = useMemo(() => { + return dateTimeFromSomething(marathon?.endDate) ?? null; + }, [marathon?.endDate]); + + return ( + + {children} + + ); +}; diff --git a/packages/portal/src/config/marathonContext.ts b/packages/portal/src/config/marathonContext.ts new file mode 100644 index 00000000..f44733ea --- /dev/null +++ b/packages/portal/src/config/marathonContext.ts @@ -0,0 +1,29 @@ +import type { DateTime } from "luxon"; +import { createContext, useContext } from "react"; + +export const marathonContext = createContext<{ + setMarathon: (marathonId: string | null) => void; + marathon: { + id: string; + year: string; + startDate: DateTime | null; + endDate: DateTime | null; + } | null; + marathons: + | readonly { + id: string; + year: string; + }[] + | null; + loading: boolean; +}>({ + setMarathon: () => {}, + marathon: null, + marathons: [], + loading: true, +}); + +export const useMarathon = () => { + const { marathon } = useContext(marathonContext); + return marathon; +}; diff --git a/packages/portal/src/config/storage.tsx b/packages/portal/src/config/storage.tsx new file mode 100644 index 00000000..1ad84177 --- /dev/null +++ b/packages/portal/src/config/storage.tsx @@ -0,0 +1,3 @@ +export const LocalStorageKeys = { + SelectedMarathon: "ukdb-selected-marathon", +} as const; diff --git a/packages/portal/src/elements/components/ImagePicker.tsx b/packages/portal/src/elements/components/ImagePicker.tsx index 7ab2fed7..cac9f158 100644 --- a/packages/portal/src/elements/components/ImagePicker.tsx +++ b/packages/portal/src/elements/components/ImagePicker.tsx @@ -1,6 +1,6 @@ import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { StringComparator } from "@ukdanceblue/common"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { Button, Flex, Image, Input } from "antd"; import { useState } from "react"; import { useQuery } from "urql"; @@ -9,7 +9,7 @@ const imagePickerDocument = graphql(/* GraphQL */ ` query ImagePicker($stringFilters: [ImageResolverKeyedStringFilterItem!]) { images(stringFilters: $stringFilters, pageSize: 9) { data { - uuid + id alt url } @@ -87,7 +87,7 @@ export function ImagePicker({ {group.map((image) => image ? ( diff --git a/packages/portal/src/elements/components/PersonSearch.tsx b/packages/portal/src/elements/components/PersonSearch.tsx index 151146c0..d3db73e8 100644 --- a/packages/portal/src/elements/components/PersonSearch.tsx +++ b/packages/portal/src/elements/components/PersonSearch.tsx @@ -1,5 +1,5 @@ import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { AutoComplete, type AutoCompleteProps } from "antd"; import { useState } from "react"; import { useQuery } from "urql"; @@ -7,18 +7,14 @@ import { useQuery } from "urql"; const personSearchDocument = graphql(/* GraphQL */ ` query PersonSearch($search: String!) { searchPeopleByName(name: $search) { - data { - uuid - name - linkblue - } + id + name + linkblue } personByLinkBlue(linkBlueId: $search) { - data { - uuid - name - linkblue - } + id + name + linkblue } } `); @@ -48,19 +44,19 @@ export function PersonSearch({ }); const options = - data?.searchPeopleByName.data.map((person) => ({ + data?.searchPeopleByName.map((person) => ({ value: person.name, label: person.name, person, })) || []; - if (data?.personByLinkBlue.data) { + if (data?.personByLinkBlue) { options.push({ - value: data.personByLinkBlue.data.uuid, - label: data.personByLinkBlue.data.linkblue - ? `${data.personByLinkBlue.data.name} (${data.personByLinkBlue.data.linkblue})` - : data.personByLinkBlue.data.name, - person: data.personByLinkBlue.data, + value: data.personByLinkBlue.id, + label: data.personByLinkBlue.linkblue + ? `${data.personByLinkBlue.name} (${data.personByLinkBlue.linkblue})` + : data.personByLinkBlue.name, + person: data.personByLinkBlue, }); } @@ -76,7 +72,7 @@ export function PersonSearch({ const option = options.find((option) => option.value === value); if (option) { onSelect?.({ - uuid: option.person.uuid, + uuid: option.person.id, name: option.person.name ?? undefined, linkblue: option.person.linkblue ?? undefined, }); diff --git a/packages/portal/src/elements/forms/notification/SingleNotificationGQL.ts b/packages/portal/src/elements/forms/notification/SingleNotificationGQL.ts index 4d4e0d9c..a48b9c93 100644 --- a/packages/portal/src/elements/forms/notification/SingleNotificationGQL.ts +++ b/packages/portal/src/elements/forms/notification/SingleNotificationGQL.ts @@ -1,8 +1,8 @@ -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; export const SingleNotificationFragment = graphql(/* GraphQL */ ` - fragment SingleNotificationFragment on NotificationResource { - uuid + fragment SingleNotificationFragment on NotificationNode { + id title body deliveryIssue diff --git a/packages/portal/src/elements/forms/notification/create/CreateNotificationGQL.ts b/packages/portal/src/elements/forms/notification/create/CreateNotificationGQL.ts index 7bc6f0d6..95c198ec 100644 --- a/packages/portal/src/elements/forms/notification/create/CreateNotificationGQL.ts +++ b/packages/portal/src/elements/forms/notification/create/CreateNotificationGQL.ts @@ -1,4 +1,4 @@ -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; export const createNotificationDocument = graphql(/* GraphQL */ ` mutation CreateNotification( diff --git a/packages/portal/src/elements/forms/notification/create/useNotificationCreator.ts b/packages/portal/src/elements/forms/notification/create/useNotificationCreator.ts index a4e1a1ed..51bf6018 100644 --- a/packages/portal/src/elements/forms/notification/create/useNotificationCreator.ts +++ b/packages/portal/src/elements/forms/notification/create/useNotificationCreator.ts @@ -1,7 +1,7 @@ import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useForm } from "@tanstack/react-form"; import type { TeamType } from "@ukdanceblue/common"; -import type { DocumentType } from "@ukdanceblue/common/graphql-client-admin"; +import type { DocumentType } from "@ukdanceblue/common/graphql-client-portal"; import { useMutation } from "urql"; import { createNotificationDocument } from "./CreateNotificationGQL"; diff --git a/packages/portal/src/elements/forms/notification/manage/ManageNotificationForm.tsx b/packages/portal/src/elements/forms/notification/manage/ManageNotificationForm.tsx index 58ab0018..af55d95f 100644 --- a/packages/portal/src/elements/forms/notification/manage/ManageNotificationForm.tsx +++ b/packages/portal/src/elements/forms/notification/manage/ManageNotificationForm.tsx @@ -1,8 +1,8 @@ import { LuxonDatePicker } from "@elements/components/antLuxonComponents"; import { NotificationViewer } from "@elements/viewers/notification/NotificationViewer"; import { useAntFeedback } from "@hooks/useAntFeedback"; -import type { FragmentType } from "@ukdanceblue/common/graphql-client-admin"; -import { getFragmentData } from "@ukdanceblue/common/graphql-client-admin"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-portal"; +import { getFragmentData } from "@ukdanceblue/common/graphql-client-portal"; import type { ModalFuncProps } from "antd"; import { Button, Empty, Flex, Form } from "antd"; import { DateTime } from "luxon"; @@ -36,7 +36,7 @@ export const ManageNotificationForm = ({ notificationFragment ); const actions = useNotificationManagerForm({ - uuid: notification?.uuid ?? "", + uuid: notification?.id ?? "", }); const [sendAt, setSendAt] = useState( diff --git a/packages/portal/src/elements/forms/notification/manage/NotificationManagerGQL.ts b/packages/portal/src/elements/forms/notification/manage/NotificationManagerGQL.ts index 0fed44e5..206c1ff1 100644 --- a/packages/portal/src/elements/forms/notification/manage/NotificationManagerGQL.ts +++ b/packages/portal/src/elements/forms/notification/manage/NotificationManagerGQL.ts @@ -1,7 +1,7 @@ -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; export const cancelNotificationScheduleDocument = graphql(/* GraphQL */ ` - mutation CancelNotificationSchedule($uuid: String!) { + mutation CancelNotificationSchedule($uuid: GlobalId!) { abortScheduledNotification(uuid: $uuid) { ok } @@ -9,7 +9,7 @@ export const cancelNotificationScheduleDocument = graphql(/* GraphQL */ ` `); export const deleteNotificationDocument = graphql(/* GraphQL */ ` - mutation DeleteNotification($uuid: String!, $force: Boolean) { + mutation DeleteNotification($uuid: GlobalId!, $force: Boolean) { deleteNotification(uuid: $uuid, force: $force) { ok } @@ -17,7 +17,7 @@ export const deleteNotificationDocument = graphql(/* GraphQL */ ` `); export const sendNotificationDocument = graphql(/* GraphQL */ ` - mutation SendNotification($uuid: String!) { + mutation SendNotification($uuid: GlobalId!) { sendNotification(uuid: $uuid) { ok } @@ -25,7 +25,7 @@ export const sendNotificationDocument = graphql(/* GraphQL */ ` `); export const scheduleNotificationDocument = graphql(/* GraphQL */ ` - mutation ScheduleNotification($uuid: String!, $sendAt: DateTimeISO!) { + mutation ScheduleNotification($uuid: GlobalId!, $sendAt: DateTimeISO!) { scheduleNotification(uuid: $uuid, sendAt: $sendAt) { ok } diff --git a/packages/portal/src/elements/forms/person/PersonFormsGQL.ts b/packages/portal/src/elements/forms/person/PersonFormsGQL.ts index 2fb3e765..560bfd5f 100644 --- a/packages/portal/src/elements/forms/person/PersonFormsGQL.ts +++ b/packages/portal/src/elements/forms/person/PersonFormsGQL.ts @@ -1,8 +1,8 @@ -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; export const TeamNameFragment = graphql(/* GraphQL */ ` - fragment TeamNameFragment on TeamResource { - uuid + fragment TeamNameFragment on TeamNode { + id name } `); diff --git a/packages/portal/src/elements/forms/person/create/PersonCreator.tsx b/packages/portal/src/elements/forms/person/create/PersonCreator.tsx index b3e9bc36..b75b0d79 100644 --- a/packages/portal/src/elements/forms/person/create/PersonCreator.tsx +++ b/packages/portal/src/elements/forms/person/create/PersonCreator.tsx @@ -1,7 +1,6 @@ import { useNavigate } from "@tanstack/react-router"; -import { CommitteeRole, committeeNames } from "@ukdanceblue/common"; -import type { FragmentType } from "@ukdanceblue/common/graphql-client-admin"; -import { getFragmentData } from "@ukdanceblue/common/graphql-client-admin"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-portal"; +import { getFragmentData } from "@ukdanceblue/common/graphql-client-portal"; import { App, Button, Empty, Flex, Form, Input, Select } from "antd"; import type { BaseOptionType } from "antd/es/select"; import { useMemo, useState } from "react"; @@ -22,10 +21,10 @@ export function PersonCreator({ const { message } = App.useApp(); const { formApi } = usePersonCreatorForm((ret) => { - if (ret?.uuid) { + if (ret?.id) { navigate({ to: "/people/$personId/", - params: { personId: ret.uuid }, + params: { personId: ret.id }, }).catch((error: unknown) => console.error(error)); } }); @@ -49,13 +48,13 @@ export function PersonCreator({ for (const team of teamNamesData ?? []) { captaincyOptions.push({ label: team.name, - value: team.uuid, - disabled: formMemberOf.includes(team.uuid), + value: team.id, + disabled: formMemberOf.includes(team.id), }); membershipOptions.push({ label: team.name, - value: team.uuid, - disabled: formCaptainOf.includes(team.uuid), + value: team.id, + disabled: formCaptainOf.includes(team.id), }); } return { captaincyOptions, membershipOptions }; @@ -171,7 +170,7 @@ export function PersonCreator({ )} /> - ( )} - /> + /> */}

Note: If someone is captain of a team that also means they are a member of that team, so you don't need to select both. diff --git a/packages/portal/src/elements/forms/person/create/PersonCreatorGQL.ts b/packages/portal/src/elements/forms/person/create/PersonCreatorGQL.ts index 68f4addb..fc6e5a5f 100644 --- a/packages/portal/src/elements/forms/person/create/PersonCreatorGQL.ts +++ b/packages/portal/src/elements/forms/person/create/PersonCreatorGQL.ts @@ -1,10 +1,9 @@ -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; export const personCreatorDocument = graphql(/* GraphQL */ ` mutation PersonCreator($input: CreatePersonInput!) { createPerson(input: $input) { - ok - uuid + id } } `); diff --git a/packages/portal/src/elements/forms/person/create/usePersonCreatorForm.ts b/packages/portal/src/elements/forms/person/create/usePersonCreatorForm.ts index 759b46be..4cdf3a84 100644 --- a/packages/portal/src/elements/forms/person/create/usePersonCreatorForm.ts +++ b/packages/portal/src/elements/forms/person/create/usePersonCreatorForm.ts @@ -1,8 +1,7 @@ import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useForm } from "@tanstack/react-form"; -import { DbRole } from "@ukdanceblue/common"; -import type { DocumentType } from "@ukdanceblue/common/graphql-client-admin"; -import { type CreatePersonInput } from "@ukdanceblue/common/graphql-client-admin/raw-types"; +import type { DocumentType } from "@ukdanceblue/common/graphql-client-portal"; +import { type CreatePersonInput } from "@ukdanceblue/common/graphql-client-portal/raw-types"; import { useMutation } from "urql"; import { personCreatorDocument } from "./PersonCreatorGQL"; @@ -31,11 +30,6 @@ export function usePersonCreatorForm( name: "", linkblue: "", email: "", - role: { - dbRole: DbRole.None, - committeeRole: null, - committeeIdentifier: null, - }, captainOf: [], memberOf: [], }, @@ -66,10 +60,6 @@ export function usePersonCreatorForm( } } - if (values.role?.committeeIdentifier && !values.role.committeeRole) { - return "Committee role is required if a committee is selected"; - } - return undefined; }, onSubmit: async (values) => { @@ -82,11 +72,6 @@ export function usePersonCreatorForm( name: values.name || null, linkblue: values.linkblue || null, email: values.email, - role: { - dbRole: values.role?.dbRole ?? DbRole.None, - committeeRole: values.role?.committeeRole ?? null, - committeeIdentifier: values.role?.committeeIdentifier ?? null, - }, captainOf: values.captainOf ?? [], memberOf: values.memberOf ?? [], }, diff --git a/packages/portal/src/elements/forms/person/edit/PersonEditor.tsx b/packages/portal/src/elements/forms/person/edit/PersonEditor.tsx index ce5321de..05e00e0f 100644 --- a/packages/portal/src/elements/forms/person/edit/PersonEditor.tsx +++ b/packages/portal/src/elements/forms/person/edit/PersonEditor.tsx @@ -1,6 +1,5 @@ -import { CommitteeRole, committeeNames } from "@ukdanceblue/common"; -import type { FragmentType } from "@ukdanceblue/common/graphql-client-admin"; -import { getFragmentData } from "@ukdanceblue/common/graphql-client-admin"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-portal"; +import { getFragmentData } from "@ukdanceblue/common/graphql-client-portal"; import { App, Button, Empty, Flex, Form, Input, Select } from "antd"; import type { BaseOptionType } from "antd/es/select"; import { useMemo, useState } from "react"; @@ -49,13 +48,13 @@ export function PersonEditor({ for (const team of teamNamesData ?? []) { captaincyOptions.push({ label: team.name, - value: team.uuid, - disabled: formMemberOf.includes(team.uuid), + value: team.id, + disabled: formMemberOf.includes(team.id), }); membershipOptions.push({ label: team.name, - value: team.uuid, - disabled: formCaptainOf.includes(team.uuid), + value: team.id, + disabled: formCaptainOf.includes(team.id), }); } return { captaincyOptions, membershipOptions }; @@ -184,8 +183,8 @@ export function PersonEditor({ )} /> - ( )} - /> + /> */}

Note: If someone is captain of a team that also means they are a member of that team, so you don't need to select both. diff --git a/packages/portal/src/elements/forms/person/edit/PersonEditorGQL.ts b/packages/portal/src/elements/forms/person/edit/PersonEditorGQL.ts index 73f0906a..5d25cc9a 100644 --- a/packages/portal/src/elements/forms/person/edit/PersonEditorGQL.ts +++ b/packages/portal/src/elements/forms/person/edit/PersonEditorGQL.ts @@ -1,19 +1,15 @@ -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; export const PersonEditorFragment = graphql(/* GraphQL */ ` - fragment PersonEditorFragment on PersonResource { - uuid + fragment PersonEditorFragment on PersonNode { + id name linkblue email - role { - committeeRole - committeeIdentifier - } teams { position team { - uuid + id name } } @@ -21,9 +17,9 @@ export const PersonEditorFragment = graphql(/* GraphQL */ ` `); export const personEditorDocument = graphql(/* GraphQL */ ` - mutation PersonEditor($uuid: String!, $input: SetPersonInput!) { + mutation PersonEditor($uuid: GlobalId!, $input: SetPersonInput!) { setPerson(uuid: $uuid, input: $input) { - ok + id } } `); diff --git a/packages/portal/src/elements/forms/person/edit/usePersonEditorForm.ts b/packages/portal/src/elements/forms/person/edit/usePersonEditorForm.ts index 7e266a7b..8419da0d 100644 --- a/packages/portal/src/elements/forms/person/edit/usePersonEditorForm.ts +++ b/packages/portal/src/elements/forms/person/edit/usePersonEditorForm.ts @@ -1,12 +1,12 @@ import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useForm } from "@tanstack/react-form"; -import { DbRole, MembershipPositionType } from "@ukdanceblue/common"; +import { MembershipPositionType } from "@ukdanceblue/common"; import type { DocumentType, FragmentType, -} from "@ukdanceblue/common/graphql-client-admin"; -import { getFragmentData } from "@ukdanceblue/common/graphql-client-admin"; -import { type SetPersonInput } from "@ukdanceblue/common/graphql-client-admin/raw-types"; +} from "@ukdanceblue/common/graphql-client-portal"; +import { getFragmentData } from "@ukdanceblue/common/graphql-client-portal"; +import { type SetPersonInput } from "@ukdanceblue/common/graphql-client-portal/raw-types"; import { useMutation } from "urql"; import { PersonEditorFragment, personEditorDocument } from "./PersonEditorGQL"; @@ -34,25 +34,20 @@ export function usePersonEditorForm( name: personData?.name ?? "", linkblue: personData?.linkblue ?? "", email: personData?.email ?? "", - role: { - dbRole: DbRole.None, - committeeRole: personData?.role.committeeRole ?? null, - committeeIdentifier: personData?.role.committeeIdentifier ?? null, - }, captainOf: personData?.teams .filter( (membership) => membership.position === MembershipPositionType.Captain ) - .map((membership) => membership.team.uuid) ?? [], + .map((membership) => membership.team.id) ?? [], memberOf: personData?.teams .filter( (membership) => membership.position === MembershipPositionType.Member ) - .map((membership) => membership.team.uuid) ?? [], + .map((membership) => membership.team.id) ?? [], }, onChange: (values) => { const memberOfCount: Record = {}; @@ -81,10 +76,6 @@ export function usePersonEditorForm( } } - if (values.role?.committeeIdentifier && !values.role.committeeRole) { - return "Committee role is required if a committee is selected"; - } - return undefined; }, onSubmit: async (values) => { @@ -96,21 +87,12 @@ export function usePersonEditorForm( throw new Error("Email is required"); } - // TODO: This is actually ignored on the server, we need to find a way to - // remove it here - const dbRole: DbRole = DbRole.None; - const { data } = await setPerson({ - uuid: personData.uuid, + uuid: personData.id, input: { name: values.name || null, linkblue: values.linkblue || null, email: values.email, - role: { - dbRole, - committeeRole: values.role?.committeeRole ?? null, - committeeIdentifier: values.role?.committeeIdentifier ?? null, - }, captainOf: values.captainOf ?? [], memberOf: values.memberOf ?? [], }, diff --git a/packages/portal/src/elements/forms/point-entry/create/PointEntryCreatorGQL.ts b/packages/portal/src/elements/forms/point-entry/create/PointEntryCreatorGQL.ts index 6fbc663e..441ee711 100644 --- a/packages/portal/src/elements/forms/point-entry/create/PointEntryCreatorGQL.ts +++ b/packages/portal/src/elements/forms/point-entry/create/PointEntryCreatorGQL.ts @@ -1,23 +1,21 @@ -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; export const createPointEntryDocument = graphql(/* GraphQL */ ` mutation CreatePointEntry($input: CreatePointEntryInput!) { createPointEntry(input: $input) { data { - uuid + id } } } `); export const getPersonByUuidDocument = graphql(/* GraphQL */ ` - query GetPersonByUuid($uuid: String!) { + query GetPersonByUuid($uuid: GlobalId!) { person(uuid: $uuid) { - data { - uuid - name - linkblue - } + id + name + linkblue } } `); @@ -25,10 +23,8 @@ export const getPersonByUuidDocument = graphql(/* GraphQL */ ` export const getPersonByLinkBlueDocument = graphql(/* GraphQL */ ` query GetPersonByLinkBlue($linkBlue: String!) { personByLinkBlue(linkBlueId: $linkBlue) { - data { - uuid - name - } + id + name } } `); @@ -36,10 +32,8 @@ export const getPersonByLinkBlueDocument = graphql(/* GraphQL */ ` export const searchPersonByNameDocument = graphql(/* GraphQL */ ` query SearchPersonByName($name: String!) { searchPeopleByName(name: $name) { - data { - uuid - name - } + id + name } } `); @@ -53,7 +47,7 @@ export const createPersonByLinkBlue = graphql(/* GraphQL */ ` createPerson( input: { email: $email, linkblue: $linkBlue, memberOf: [$teamUuid] } ) { - uuid + id } } `); diff --git a/packages/portal/src/elements/forms/point-entry/create/PointEntryOpportunityLookup.tsx b/packages/portal/src/elements/forms/point-entry/create/PointEntryOpportunityLookup.tsx index c10add56..3e5ed4a0 100644 --- a/packages/portal/src/elements/forms/point-entry/create/PointEntryOpportunityLookup.tsx +++ b/packages/portal/src/elements/forms/point-entry/create/PointEntryOpportunityLookup.tsx @@ -1,8 +1,8 @@ import { PlusOutlined } from "@ant-design/icons"; import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { TeamType } from "@ukdanceblue/common"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; -import type { CreatePointOpportunityInput } from "@ukdanceblue/common/graphql-client-admin/raw-types"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; +import type { CreatePointOpportunityInput } from "@ukdanceblue/common/graphql-client-portal/raw-types"; import { App, AutoComplete, @@ -27,7 +27,7 @@ const pointEntryOpportunityLookup = graphql(/* GraphQL */ ` ) { data { name - uuid + id } } } @@ -81,7 +81,7 @@ export function PointEntryOpportunityLookup({ .data) { if (opportunity.name) { newNameAutocomplete.push({ - value: opportunity.uuid, + value: opportunity.id, label: opportunity.name, }); } diff --git a/packages/portal/src/elements/forms/point-entry/create/PointEntryPersonLookup.tsx b/packages/portal/src/elements/forms/point-entry/create/PointEntryPersonLookup.tsx index 7a0c783b..641964a0 100644 --- a/packages/portal/src/elements/forms/point-entry/create/PointEntryPersonLookup.tsx +++ b/packages/portal/src/elements/forms/point-entry/create/PointEntryPersonLookup.tsx @@ -72,14 +72,12 @@ export function PointEntryPersonLookup({ }); useEffect(() => { - if (getPersonByLinkBlueQuery.data?.personByLinkBlue.data) { - setPersonFromUuid( - getPersonByLinkBlueQuery.data.personByLinkBlue.data.uuid - ); + if (getPersonByLinkBlueQuery.data?.personByLinkBlue) { + setPersonFromUuid(getPersonByLinkBlueQuery.data.personByLinkBlue.id); } }, [ - getPersonByLinkBlueQuery.data?.personByLinkBlue.data, - getPersonByLinkBlueQuery.data?.personByLinkBlue.data?.uuid, + getPersonByLinkBlueQuery.data?.personByLinkBlue, + getPersonByLinkBlueQuery.data?.personByLinkBlue.id, setPersonFromUuid, ]); @@ -96,7 +94,7 @@ export function PointEntryPersonLookup({ if (linkblueFieldValue) { if ( linkblueFieldValue === searchedForLinkblue && - !getPersonByLinkBlueQuery.data?.personByLinkBlue.data + !getPersonByLinkBlueQuery.data?.personByLinkBlue ) { setLinkblueKnownDoesNotExist(linkblueFieldValue); } else { @@ -104,7 +102,7 @@ export function PointEntryPersonLookup({ } } }, [ - getPersonByLinkBlueQuery.data?.personByLinkBlue.data, + getPersonByLinkBlueQuery.data?.personByLinkBlue, linkblueFieldValue, searchedForLinkblue, ]); @@ -128,19 +126,19 @@ export function PointEntryPersonLookup({ { value: string; label: string }[] >([]); useEffect(() => { - if (searchByNameQuery.data?.searchPeopleByName.data) { + if (searchByNameQuery.data?.searchPeopleByName) { const newNameAutocomplete: typeof nameAutocomplete = []; - for (const person of searchByNameQuery.data.searchPeopleByName.data) { + for (const person of searchByNameQuery.data.searchPeopleByName) { if (person.name) { newNameAutocomplete.push({ - value: person.uuid, + value: person.id, label: person.name, }); } } setNameAutocomplete(newNameAutocomplete); } - }, [searchByNameQuery.data?.searchPeopleByName.data]); + }, [searchByNameQuery.data?.searchPeopleByName]); const updateAutocomplete = useDebouncedCallback( (name: string) => { @@ -194,7 +192,7 @@ export function PointEntryPersonLookup({ placeholder="Search by Name" value={ (personFromUuid && - selectedPersonQuery.data?.person.data?.name) || + selectedPersonQuery.data?.person.name) || searchByNameField } onBlur={field.handleBlur} @@ -220,7 +218,7 @@ export function PointEntryPersonLookup({ name={`${field.name}-linkblue-field`} value={ (personFromUuid && - selectedPersonQuery.data?.person.data?.linkblue) || + selectedPersonQuery.data?.person.linkblue) || linkblueFieldValue } onChange={(e) => { @@ -271,8 +269,8 @@ export function PointEntryPersonLookup({ email: `${linkblueFieldValue}@uky.edu`, teamUuid, }); - if (result.data?.createPerson.uuid) { - setPersonFromUuid(result.data.createPerson.uuid); + if (result.data?.createPerson.id) { + setPersonFromUuid(result.data.createPerson.id); } } catch (error) { showErrorMessage(error); @@ -293,10 +291,10 @@ export function PointEntryPersonLookup({ {field.state.value ? ( <> Selected Person:{" "} - {selectedPersonQuery.data?.person.data ? ( + {selectedPersonQuery.data?.person ? ( - {selectedPersonQuery.data.person.data.name ?? - selectedPersonQuery.data.person.data.linkblue} + {selectedPersonQuery.data.person.name ?? + selectedPersonQuery.data.person.linkblue} ) : ( "No name or linkblue found" diff --git a/packages/portal/src/elements/forms/point-entry/create/usePointEntryCreatorForm.ts b/packages/portal/src/elements/forms/point-entry/create/usePointEntryCreatorForm.ts index ebe56b50..f4b7e621 100644 --- a/packages/portal/src/elements/forms/point-entry/create/usePointEntryCreatorForm.ts +++ b/packages/portal/src/elements/forms/point-entry/create/usePointEntryCreatorForm.ts @@ -1,6 +1,6 @@ import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useForm } from "@tanstack/react-form"; -import type { CreatePointEntryInput } from "@ukdanceblue/common/graphql-client-admin/raw-types"; +import type { CreatePointEntryInput } from "@ukdanceblue/common/graphql-client-portal/raw-types"; import { useMutation } from "urql"; import { createPointEntryDocument } from "./PointEntryCreatorGQL"; diff --git a/packages/portal/src/elements/forms/team/create/TeamCreator.tsx b/packages/portal/src/elements/forms/team/create/TeamCreator.tsx index 8ac9e03f..57b11a78 100644 --- a/packages/portal/src/elements/forms/team/create/TeamCreator.tsx +++ b/packages/portal/src/elements/forms/team/create/TeamCreator.tsx @@ -1,3 +1,4 @@ +import { useMarathon } from "@config/marathonContext"; import { TanAntFormItem } from "@elements/components/form/TanAntFormItem"; import { useNavigate } from "@tanstack/react-router"; import { TeamLegacyStatus, TeamType } from "@ukdanceblue/common"; @@ -58,7 +59,7 @@ export function TeamCreator() { )} -

Marathon Year: DB24

+

Marathon Year: {useMarathon()?.year}

)} - {formApi.getFieldValue("persistentIdentifier") ? ( -

- Special Identifier:{" "} - {formApi.getFieldValue("persistentIdentifier")} -

- ) : null} {(field) => ( ({ defaultValues: { name: "", legacyStatus: TeamLegacyStatus.NewTeam, - marathonYear: "DB24", - persistentIdentifier: null, type: TeamType.Spirit, }, onSubmit: async (values) => { + if (!marathonId) { + void showErrorMessage("No marathon selected"); + return; + } const { data } = await createTeam({ input: { name: values.name, legacyStatus: values.legacyStatus, - // TODO: Make this dynamic - marathonYear: "DB24", - persistentIdentifier: values.persistentIdentifier ?? null, type: values.type, }, + marathonUuid: marathonId, }); return afterSubmit?.(data?.createTeam); diff --git a/packages/portal/src/elements/forms/team/edit/TeamEditor.tsx b/packages/portal/src/elements/forms/team/edit/TeamEditor.tsx index c54ced8f..d810d8e7 100644 --- a/packages/portal/src/elements/forms/team/edit/TeamEditor.tsx +++ b/packages/portal/src/elements/forms/team/edit/TeamEditor.tsx @@ -1,6 +1,6 @@ import { TeamLegacyStatus } from "@ukdanceblue/common"; -import type { FragmentType } from "@ukdanceblue/common/graphql-client-admin"; -import { getFragmentData } from "@ukdanceblue/common/graphql-client-admin"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-portal"; +import { getFragmentData } from "@ukdanceblue/common/graphql-client-portal"; import { App, Button, Empty, Flex, Form, Input, Select } from "antd"; import type { UseQueryExecute } from "urql"; diff --git a/packages/portal/src/elements/forms/team/edit/TeamEditorGQL.ts b/packages/portal/src/elements/forms/team/edit/TeamEditorGQL.ts index 5e8cbf3f..ca358801 100644 --- a/packages/portal/src/elements/forms/team/edit/TeamEditorGQL.ts +++ b/packages/portal/src/elements/forms/team/edit/TeamEditorGQL.ts @@ -1,18 +1,20 @@ -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; export const TeamEditorFragment = graphql(/* GraphQL */ ` - fragment TeamEditorFragment on TeamResource { - uuid + fragment TeamEditorFragment on TeamNode { + id name - marathonYear + marathon { + id + year + } legacyStatus - persistentIdentifier type } `); export const teamEditorDocument = graphql(/* GraphQL */ ` - mutation TeamEditor($uuid: String!, $input: SetTeamInput!) { + mutation TeamEditor($uuid: GlobalId!, $input: SetTeamInput!) { setTeam(uuid: $uuid, input: $input) { ok } diff --git a/packages/portal/src/elements/forms/team/edit/useTeamEditorForm.ts b/packages/portal/src/elements/forms/team/edit/useTeamEditorForm.ts index 6ba07565..16b5b394 100644 --- a/packages/portal/src/elements/forms/team/edit/useTeamEditorForm.ts +++ b/packages/portal/src/elements/forms/team/edit/useTeamEditorForm.ts @@ -4,9 +4,9 @@ import { TeamType } from "@ukdanceblue/common"; import type { DocumentType, FragmentType, -} from "@ukdanceblue/common/graphql-client-admin"; -import { getFragmentData } from "@ukdanceblue/common/graphql-client-admin"; -import { type SetTeamInput } from "@ukdanceblue/common/graphql-client-admin/raw-types"; +} from "@ukdanceblue/common/graphql-client-portal"; +import { getFragmentData } from "@ukdanceblue/common/graphql-client-portal"; +import { type SetTeamInput } from "@ukdanceblue/common/graphql-client-portal/raw-types"; import { useMutation } from "urql"; import { TeamEditorFragment, teamEditorDocument } from "./TeamEditorGQL"; @@ -33,18 +33,15 @@ export function useTeamEditorForm( defaultValues: { name: teamData?.name ?? "", legacyStatus: teamData?.legacyStatus ?? null, - // TODO: Make this dynamic - marathonYear: teamData?.marathonYear ?? "DB24", - persistentIdentifier: teamData?.persistentIdentifier ?? null, type: teamData?.type ?? TeamType.Spirit, }, onSubmit: async (values) => { - if (!teamData?.uuid) { + if (!teamData?.id) { throw new Error("Team UUID is required"); } const { data } = await setTeam({ - uuid: teamData.uuid, + uuid: teamData.id, input: { name: values.name ?? null, legacyStatus: values.legacyStatus ?? null, diff --git a/packages/portal/src/elements/singletons/NavigationMenu.css b/packages/portal/src/elements/singletons/NavigationMenu.css new file mode 100644 index 00000000..5d3c5bde --- /dev/null +++ b/packages/portal/src/elements/singletons/NavigationMenu.css @@ -0,0 +1,4 @@ +.ant-menu-title-content .ant-select span, +.ant-menu-title-content .ant-select svg { + color: rgba(255, 255, 255, 0.65) !important; +} diff --git a/packages/portal/src/elements/singletons/NavigationMenu.tsx b/packages/portal/src/elements/singletons/NavigationMenu.tsx index b1cecf5d..f5ddbe7a 100644 --- a/packages/portal/src/elements/singletons/NavigationMenu.tsx +++ b/packages/portal/src/elements/singletons/NavigationMenu.tsx @@ -1,12 +1,19 @@ -import { LoadingOutlined, MoonOutlined, SunOutlined } from "@ant-design/icons"; +import { MoonOutlined, SunOutlined } from "@ant-design/icons"; import { themeConfigContext } from "@config/antThemeConfig"; import { API_BASE_URL } from "@config/api"; +import { marathonContext } from "@config/marathonContext"; +import { useAntFeedback } from "@hooks/useAntFeedback"; import { useLoginState } from "@hooks/useLoginState"; import type { AuthorizationRule } from "@ukdanceblue/common"; -import { AccessLevel, checkAuthorization } from "@ukdanceblue/common"; -import { Button, Menu } from "antd"; -import type { ItemType } from "antd/es/menu/hooks/useItems"; -import { useContext, useMemo } from "react"; +import { + AccessLevel, + checkAuthorization, + defaultAuthorization, +} from "@ukdanceblue/common"; +import { Button, Menu, Select } from "antd"; +import { useContext, useEffect, useState } from "react"; + +import "./NavigationMenu.css"; interface NavItemType { slug: string; @@ -16,183 +23,235 @@ interface NavItemType { authorizationRules?: AuthorizationRule[]; } -export const NavigationMenu = () => { - const { dark, setDark } = useContext(themeConfigContext); - - const { loggedIn, authorization } = useLoginState(); - const navItems = useMemo((): NavItemType[] => { - return [ +const navItems = [ + { + slug: "home", + title: "Home", + url: "/", + }, + { + slug: "events", + title: "Events", + authorizationRules: [ { - slug: "home", - title: "Home", - url: "/", + accessLevel: AccessLevel.CommitteeChairOrCoordinator, }, + ], + }, + { + slug: "teams", + title: "Teams", + authorizationRules: [ { - slug: "events", - title: "Events", - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], + accessLevel: AccessLevel.CommitteeChairOrCoordinator, }, + ], + }, + { + slug: "people", + title: "People", + authorizationRules: [ { - slug: "teams", - title: "Teams", - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], + accessLevel: AccessLevel.CommitteeChairOrCoordinator, }, + ], + }, + { + slug: "notifications", + title: "Notifications", + authorizationRules: [ { - slug: "people", - title: "People", - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], + accessLevel: AccessLevel.CommitteeChairOrCoordinator, }, + ], + }, + { + slug: "marathon", + title: "Marathon", + authorizationRules: [ { - slug: "notifications", - title: "Notifications", - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], + accessLevel: AccessLevel.CommitteeChairOrCoordinator, }, + ], + }, + { + slug: "feed", + title: "Feed", + authorizationRules: [ { - slug: "marathon", - title: "Marathon", - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], + accessLevel: AccessLevel.CommitteeChairOrCoordinator, }, + ], + }, + { + slug: "images", + title: "Images", + authorizationRules: [ { - slug: "feed", - title: "Feed", - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], + accessLevel: AccessLevel.CommitteeChairOrCoordinator, }, + ], + }, + { + slug: "config", + title: "Config", + authorizationRules: [ { - slug: "images", - title: "Images", - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], + accessLevel: AccessLevel.Admin, }, - { - slug: "config", - title: "Config", - authorizationRules: [ - { - accessLevel: AccessLevel.Admin, - }, - ], - }, - ]; - }, []); + ], + }, +]; +const { pathname, href } = window.location; +const activeKeys: string[] = []; +for (const item of navItems) { + if (item.url != null && (pathname || "/") === item.url) { + activeKeys.push(item.slug); + } else if (item.slug === pathname.slice(1)) { + activeKeys.push(item.slug); + } +} + +const loadingOption = [ + + Loading... + , +]; + +export const NavigationMenu = () => { + const { dark, setDark } = useContext(themeConfigContext); + const { showErrorMessage } = useAntFeedback(); + + const { loggedIn, authorization } = useLoginState(); + + const [menuItems, setMenuItems] = useState< + { + key: string; + title: string; + label: JSX.Element; + }[] + >([]); - const menuItems = useMemo((): ItemType[] => { - return navItems - .filter( - ({ authorizationRules }) => - !authorizationRules || - (authorization && - authorizationRules.some((authorizationRule) => - checkAuthorization(authorizationRule, authorization) - )) - ) - .map((item) => ({ + useEffect(() => { + const fetchMenuItems = async () => { + const filteredItems: NavItemType[] = []; + for (const item of navItems) { + if (!item.authorizationRules) { + filteredItems.push(item); + } else { + let isAuthorized = false; + for (const authorizationRule of item.authorizationRules) { + if ( + // eslint-disable-next-line no-await-in-loop + await checkAuthorization( + authorizationRule, + authorization ?? defaultAuthorization + ) + ) { + isAuthorized = true; + break; + } + } + if (isAuthorized) { + filteredItems.push(item); + } + } + } + + const updatedMenuItems = filteredItems.map((item) => ({ key: item.slug, title: item.title, label: ( {item.element ?? item.title} ), })); - }, [authorization, navItems]); - const activeKeys = useMemo((): string[] => { - const { pathname } = window.location; - const activeKeys: string[] = []; - for (const item of navItems) { - if (item.url != null && pathname === item.url) { - activeKeys.push(item.slug); - } else if (item.slug === pathname.slice(1)) { - activeKeys.push(item.slug); - } - } - return activeKeys; - }, [navItems]); + setMenuItems(updatedMenuItems); + }; + + fetchMenuItems().catch((error: unknown) => { + void showErrorMessage({ content: "Failed to fetch menu items" }); + console.error("Failed to fetch menu items", error); + }); + }, [authorization, showErrorMessage]); + + const { setMarathon, marathon, loading, marathons } = + useContext(marathonContext); return ( - : undefined, - disabled: loggedIn == null, - label: loggedIn ? ( - - Logout - - ) : ( - - Login - - ), - style: { - background: "transparent", - marginLeft: "auto", - }, - }, - { - key: "dark", - title: "Dark", - label: ( - ); }; diff --git a/packages/portal/src/elements/tables/PeopleTable.tsx b/packages/portal/src/elements/tables/PeopleTable.tsx index 03914478..a8496c24 100644 --- a/packages/portal/src/elements/tables/PeopleTable.tsx +++ b/packages/portal/src/elements/tables/PeopleTable.tsx @@ -14,20 +14,20 @@ import { import { getFragmentData, graphql, -} from "@ukdanceblue/common/graphql-client-admin"; +} from "@ukdanceblue/common/graphql-client-portal"; import { Button, Flex, Table } from "antd"; import { useQuery } from "urql"; const PeopleTableFragment = graphql(/* GraphQL */ ` - fragment PeopleTableFragment on PersonResource { - uuid + fragment PeopleTableFragment on PersonNode { + id name linkblue email - role { - dbRole - committeeRole - committeeIdentifier + dbRole + primaryCommittee { + identifier + role } } `); @@ -134,8 +134,8 @@ export const PeopleTable = () => { | "committeeName", direction: sort.order === "ascend" - ? SortDirection.ASCENDING - : SortDirection.DESCENDING, + ? SortDirection.asc + : SortDirection.desc, }); } clearFilters(); @@ -179,7 +179,7 @@ export const PeopleTable = () => { } : false } - rowKey={({ uuid }) => uuid} + rowKey={({ id }) => id} sortDirections={["ascend", "descend"]} columns={[ { @@ -219,7 +219,7 @@ export const PeopleTable = () => { title: "Role", dataIndex: "dbRole", render: (_, record) => { - return stringifyDbRole(record.role.dbRole); + return stringifyDbRole(record.dbRole); }, sorter: true, sortDirections: ["ascend", "descend"], @@ -232,7 +232,8 @@ export const PeopleTable = () => { title: "Committee Role", dataIndex: "committeeRole", render: (_, record) => { - return record.role.committeeRole ?? "None"; + // TODO: fix + return record.primaryCommittee?.role ?? "None"; }, sorter: true, sortDirections: ["ascend", "descend"], @@ -245,8 +246,9 @@ export const PeopleTable = () => { title: "Committee Name", dataIndex: "committeeName", render: (_, record) => { - return record.role.committeeIdentifier - ? committeeNames[record.role.committeeIdentifier] + // TODO: fix + return record.primaryCommittee?.identifier + ? committeeNames[record.primaryCommittee.identifier] : "None"; }, sorter: true, @@ -266,7 +268,7 @@ export const PeopleTable = () => { onClick={() => navigate({ to: "/people/$personId/", - params: { personId: record.uuid }, + params: { personId: record.id }, }).catch((error: unknown) => console.error(error)) } icon={} @@ -275,7 +277,7 @@ export const PeopleTable = () => { onClick={() => navigate({ to: "/people/$personId/edit", - params: { personId: record.uuid }, + params: { personId: record.id }, }).catch((error: unknown) => console.error(error)) } icon={} diff --git a/packages/portal/src/elements/tables/TeamsTable.tsx b/packages/portal/src/elements/tables/TeamsTable.tsx index 8832bf6c..b813eeb0 100644 --- a/packages/portal/src/elements/tables/TeamsTable.tsx +++ b/packages/portal/src/elements/tables/TeamsTable.tsx @@ -1,14 +1,16 @@ -import { EditOutlined, EyeOutlined } from "@ant-design/icons"; +import { DollarOutlined, EditOutlined, EyeOutlined } from "@ant-design/icons"; +import { useMarathon } from "@config/marathonContext"; import { useListQuery } from "@hooks/useListQuery"; import { useMakeStringSearchFilterProps } from "@hooks/useMakeSearchFilterProps"; import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useNavigate } from "@tanstack/react-router"; -import { SortDirection } from "@ukdanceblue/common"; +import { SortDirection, TeamLegacyStatus, TeamType } from "@ukdanceblue/common"; import { getFragmentData, graphql, -} from "@ukdanceblue/common/graphql-client-admin"; +} from "@ukdanceblue/common/graphql-client-portal"; import { Button, Flex, Table } from "antd"; +import { useEffect } from "react"; import { useQuery } from "urql"; const teamsTableQueryDocument = graphql(/* GraphQL */ ` @@ -41,12 +43,11 @@ const teamsTableQueryDocument = graphql(/* GraphQL */ ` `); export const TeamsTableFragment = graphql(/* GraphQL */ ` - fragment TeamsTableFragment on TeamResource { - uuid + fragment TeamsTableFragment on TeamNode { + id type name legacyStatus - marathonYear totalPoints } `); @@ -66,22 +67,14 @@ export const TeamsTable = () => { { initPage: 1, initPageSize: 20, - initSorting: [ - { field: "totalPoints", direction: SortDirection.DESCENDING }, - ], + initSorting: [{ field: "totalPoints", direction: SortDirection.desc }], }, { - allFields: [ - "name", - "type", - "legacyStatus", - "marathonYear", - "totalPoints", - ], + allFields: ["name", "type", "legacyStatus", "marathonId", "totalPoints"], dateFields: [], isNullFields: [], numericFields: ["totalPoints"], - oneOfFields: ["type", "marathonYear", "legacyStatus"], + oneOfFields: ["type", "marathonId", "legacyStatus"], stringFields: ["name"], } ); @@ -96,6 +89,22 @@ export const TeamsTable = () => { loadingMessage: "Loading teams...", }); + const marathonId = useMarathon()?.id; + + useEffect(() => { + if ( + queryOptions.oneOfFilters.find((f) => f.field === "marathonId") + ?.value[0] !== marathonId + ) { + if (marathonId) { + updateFilter("marathonId", { + field: "marathonId", + value: [marathonId], + }); + } + } + }, [marathonId, queryOptions.oneOfFilters, updateFilter]); + return ( { title: "Type", dataIndex: "type", sorter: true, - filters: [ - { - text: "Committee", - value: "Committee", - }, - { - text: "Spirit", - value: "Spirit", - }, - ], + filters: Object.entries(TeamType).map(([key, value]) => ({ + text: key, + value, + })), }, { title: "Legacy Status", dataIndex: "legacyStatus", sorter: true, - filters: [ - { - text: "New Team", - value: "NewTeam", - }, - { - text: "Returning Team", - value: "ReturningTeam", - }, - ], + filters: Object.values(TeamLegacyStatus).map((value) => { + let text: string; + switch (value) { + case TeamLegacyStatus.NewTeam: { + text = "New Team"; + break; + } + case TeamLegacyStatus.ReturningTeam: { + text = "Returning Team"; + break; + } + case TeamLegacyStatus.DemoTeam: { + text = "Demo Team"; + break; + } + default: { + value satisfies never; + text = String(value); + break; + } + } + return { + text, + value, + }; + }), render: (value) => { switch (value) { case "NewTeam": { @@ -148,17 +167,6 @@ export const TeamsTable = () => { } }, }, - { - title: "Marathon Year", - dataIndex: "marathonYear", - sorter: true, - filters: [ - { - text: "DanceBlue 2024", - value: "DB24", - }, - ], - }, { title: "Total Points", dataIndex: "totalPoints", @@ -172,17 +180,26 @@ export const TeamsTable = () => {
uuid} + rowKey={({ id }) => id} loading={fetching} pagination={ notificationDeliveriesDocument @@ -141,8 +141,8 @@ export const NotificationDeliveriesTable = ({ | "deliveryError", direction: sort.order === "ascend" - ? SortDirection.ASCENDING - : SortDirection.DESCENDING, + ? SortDirection.asc + : SortDirection.desc, }); } diff --git a/packages/portal/src/elements/tables/notification/NotificationsTable.tsx b/packages/portal/src/elements/tables/notification/NotificationsTable.tsx index 64dd6814..5b8b21f9 100644 --- a/packages/portal/src/elements/tables/notification/NotificationsTable.tsx +++ b/packages/portal/src/elements/tables/notification/NotificationsTable.tsx @@ -7,14 +7,14 @@ import { SortDirection } from "@ukdanceblue/common"; import { getFragmentData, graphql, -} from "@ukdanceblue/common/graphql-client-admin"; +} from "@ukdanceblue/common/graphql-client-portal"; import { Button, Flex, Table } from "antd"; import { DateTime } from "luxon"; import { useQuery } from "urql"; const NotificationsTableFragment = graphql(/* GraphQL */ ` - fragment NotificationsTableFragment on NotificationResource { - uuid + fragment NotificationsTableFragment on NotificationNode { + id title body deliveryIssue @@ -109,7 +109,7 @@ export const NotificationsTable = () => { <>
uuid} + rowKey={({ id }) => id} loading={fetching} pagination={ notificationsDocument @@ -136,8 +136,8 @@ export const NotificationsTable = () => { field: sort.field as "title" | "body" | "createdAt" | "updatedAt", direction: sort.order === "ascend" - ? SortDirection.ASCENDING - : SortDirection.DESCENDING, + ? SortDirection.asc + : SortDirection.desc, }); } }} diff --git a/packages/portal/src/elements/tables/point-entry/PointEntryDeletePopup.tsx b/packages/portal/src/elements/tables/point-entry/PointEntryDeletePopup.tsx index c7c21726..8aa89dfe 100644 --- a/packages/portal/src/elements/tables/point-entry/PointEntryDeletePopup.tsx +++ b/packages/portal/src/elements/tables/point-entry/PointEntryDeletePopup.tsx @@ -1,11 +1,11 @@ -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { Modal } from "antd"; import useNotification from "antd/es/notification/useNotification"; import { useEffect, useState } from "react"; import { useMutation } from "urql"; const deletePointEntryDocument = graphql(/* GraphQL */ ` - mutation DeletePointEntry($uuid: String!) { + mutation DeletePointEntry($uuid: GlobalId!) { deletePointEntry(uuid: $uuid) { ok } diff --git a/packages/portal/src/elements/tables/point-entry/PointEntryTable.tsx b/packages/portal/src/elements/tables/point-entry/PointEntryTable.tsx index c5ed210f..a1c6a816 100644 --- a/packages/portal/src/elements/tables/point-entry/PointEntryTable.tsx +++ b/packages/portal/src/elements/tables/point-entry/PointEntryTable.tsx @@ -1,18 +1,18 @@ import { DeleteOutlined } from "@ant-design/icons"; -import type { FragmentType } from "@ukdanceblue/common/graphql-client-admin"; +import { dateTimeFromSomething } from "@ukdanceblue/common"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-portal"; import { getFragmentData, graphql, -} from "@ukdanceblue/common/graphql-client-admin"; +} from "@ukdanceblue/common/graphql-client-portal"; import { Button, Table } from "antd"; -import { DateTime } from "luxon"; import type { UseQueryExecute } from "urql"; import { usePointEntryDeletePopup } from "./PointEntryDeletePopup"; export const PointEntryTableFragment = graphql(/* GraphQL */ ` - fragment PointEntryTableFragment on PointEntryResource { - uuid + fragment PointEntryTableFragment on PointEntryNode { + id personFrom { name linkblue @@ -86,7 +86,7 @@ export function PointEntryTable({ const { name, opportunityDate } = record.pointOpportunity; let str = name; if (opportunityDate) { - str += ` (${DateTime.fromISO(opportunityDate).toFormat( + str += ` (${dateTimeFromSomething(opportunityDate).toFormat( "yyyy-MM-dd" )})`; } @@ -99,7 +99,7 @@ export function PointEntryTable({ render: (_, record) => (
uuid} + rowKey={({ id }) => id} loading={fetching} pagination={ eventsDocument @@ -165,8 +168,8 @@ export const EventsTable = () => { | "summary", direction: sort.order === "ascend" - ? SortDirection.ASCENDING - : SortDirection.DESCENDING, + ? SortDirection.asc + : SortDirection.desc, }); } }} @@ -195,7 +198,7 @@ export const EventsTable = () => { parseEventOccurrence(occurrence) ); return ( -
  • +
  • {startString} to {endString}
  • ); diff --git a/packages/portal/src/pages/events/single-event/edit-event/EditEventPage.tsx b/packages/portal/src/pages/events/single-event/edit-event/EditEventPage.tsx index e0af1674..bbd35c20 100644 --- a/packages/portal/src/pages/events/single-event/edit-event/EditEventPage.tsx +++ b/packages/portal/src/pages/events/single-event/edit-event/EditEventPage.tsx @@ -1,12 +1,12 @@ import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useParams } from "@tanstack/react-router"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { useQuery } from "urql"; import { EventEditor } from "./EventEditor"; const viewEventPageDocument = graphql(/* GraphQL */ ` - query EditEventPage($uuid: String!) { + query EditEventPage($uuid: GlobalId!) { event(uuid: $uuid) { data { ...EventEditorFragment diff --git a/packages/portal/src/pages/events/single-event/edit-event/EventEditor.tsx b/packages/portal/src/pages/events/single-event/edit-event/EventEditor.tsx index bef0ee81..4cfc6eea 100644 --- a/packages/portal/src/pages/events/single-event/edit-event/EventEditor.tsx +++ b/packages/portal/src/pages/events/single-event/edit-event/EventEditor.tsx @@ -1,7 +1,7 @@ import { PlusOutlined } from "@ant-design/icons"; import { base64StringToArray } from "@ukdanceblue/common"; -import type { FragmentType } from "@ukdanceblue/common/graphql-client-admin"; -import { getFragmentData } from "@ukdanceblue/common/graphql-client-admin"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-portal"; +import { getFragmentData } from "@ukdanceblue/common/graphql-client-portal"; import { App, Button, Empty, Flex, Form, Image, Input, List } from "antd"; import { DateTime, Interval } from "luxon"; import { thumbHashToDataURL } from "thumbhash"; diff --git a/packages/portal/src/pages/events/single-event/edit-event/EventEditorGQL.ts b/packages/portal/src/pages/events/single-event/edit-event/EventEditorGQL.ts index 10b1f120..1e9739bb 100644 --- a/packages/portal/src/pages/events/single-event/edit-event/EventEditorGQL.ts +++ b/packages/portal/src/pages/events/single-event/edit-event/EventEditorGQL.ts @@ -1,15 +1,18 @@ -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; export const EventEditorFragment = graphql(/* GraphQL */ ` - fragment EventEditorFragment on EventResource { - uuid + fragment EventEditorFragment on EventNode { + id title summary description location occurrences { - uuid - interval + id + interval { + start + end + } fullDay } images { @@ -23,7 +26,7 @@ export const EventEditorFragment = graphql(/* GraphQL */ ` `); export const eventEditorDocument = graphql(/* GraphQL */ ` - mutation SaveEvent($uuid: String!, $input: SetEventInput!) { + mutation SaveEvent($uuid: GlobalId!, $input: SetEventInput!) { setEvent(uuid: $uuid, input: $input) { data { ...EventEditorFragment diff --git a/packages/portal/src/pages/events/single-event/edit-event/useEventEditorForm.ts b/packages/portal/src/pages/events/single-event/edit-event/useEventEditorForm.ts index e9120860..1e2c3470 100644 --- a/packages/portal/src/pages/events/single-event/edit-event/useEventEditorForm.ts +++ b/packages/portal/src/pages/events/single-event/edit-event/useEventEditorForm.ts @@ -1,12 +1,13 @@ import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useForm } from "@tanstack/react-form"; -import type { FragmentType } from "@ukdanceblue/common/graphql-client-admin"; -import { getFragmentData } from "@ukdanceblue/common/graphql-client-admin"; +import { intervalFromSomething } from "@ukdanceblue/common"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-portal"; +import { getFragmentData } from "@ukdanceblue/common/graphql-client-portal"; import type { SetEventInput, SetEventOccurrenceInput, -} from "@ukdanceblue/common/graphql-client-admin/raw-types"; -import { Interval } from "luxon"; +} from "@ukdanceblue/common/graphql-client-portal/raw-types"; +import type { Interval } from "luxon"; import type { UseQueryExecute } from "urql"; import { useMutation } from "urql"; @@ -42,8 +43,8 @@ export function useEventEditorForm( description: eventData?.description || null, occurrences: eventData?.occurrences.map((occurrence) => ({ - uuid: occurrence.uuid, - interval: Interval.fromISO(occurrence.interval), + uuid: occurrence.id, + interval: intervalFromSomething(occurrence.interval), fullDay: occurrence.fullDay, })) ?? [], }, @@ -53,7 +54,7 @@ export function useEventEditorForm( } await setEvent({ - uuid: eventData.uuid, + uuid: eventData.id, input: { title: values.title, summary: values.summary ?? eventData.summary ?? null, @@ -63,7 +64,10 @@ export function useEventEditorForm( let retVal: Parameters< typeof setEvent >[0]["input"]["occurrences"][number] = { - interval: occurrence.interval.toISO(), + interval: { + start: occurrence.interval.start!.toISO(), + end: occurrence.interval.end!.toISO(), + }, fullDay: occurrence.fullDay, }; if (occurrence.uuid) { diff --git a/packages/portal/src/pages/events/single-event/view-event/EventDeletePopup.tsx b/packages/portal/src/pages/events/single-event/view-event/EventDeletePopup.tsx index 5d8bb4ad..0511d1a4 100644 --- a/packages/portal/src/pages/events/single-event/view-event/EventDeletePopup.tsx +++ b/packages/portal/src/pages/events/single-event/view-event/EventDeletePopup.tsx @@ -1,11 +1,11 @@ -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { Modal } from "antd"; import useNotification from "antd/es/notification/useNotification"; import { useEffect, useState } from "react"; import { useMutation } from "urql"; const deleteEventDocument = graphql(/* GraphQL */ ` - mutation DeleteEvent($uuid: String!) { + mutation DeleteEvent($uuid: GlobalId!) { deleteEvent(uuid: $uuid) { ok } diff --git a/packages/portal/src/pages/events/single-event/view-event/EventViewer.tsx b/packages/portal/src/pages/events/single-event/view-event/EventViewer.tsx index afba7e3f..9ecc295e 100644 --- a/packages/portal/src/pages/events/single-event/view-event/EventViewer.tsx +++ b/packages/portal/src/pages/events/single-event/view-event/EventViewer.tsx @@ -1,28 +1,35 @@ import { DeleteOutlined, EditOutlined } from "@ant-design/icons"; import { Link, useNavigate } from "@tanstack/react-router"; -import { base64StringToArray } from "@ukdanceblue/common"; -import type { FragmentType } from "@ukdanceblue/common/graphql-client-admin"; +import { + base64StringToArray, + intervalFromSomething, +} from "@ukdanceblue/common"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-portal"; import { getFragmentData, graphql, -} from "@ukdanceblue/common/graphql-client-admin"; +} from "@ukdanceblue/common/graphql-client-portal"; import { Button, Descriptions, Flex, Image, List, Typography } from "antd"; import DescriptionsItem from "antd/es/descriptions/Item"; -import { DateTime, Interval } from "luxon"; +import type { Interval } from "luxon"; +import { DateTime } from "luxon"; import { useMemo } from "react"; import { thumbHashToDataURL } from "thumbhash"; import { useEventDeletePopup } from "./EventDeletePopup"; export const EventViewerFragment = graphql(/* GraphQL */ ` - fragment EventViewerFragment on EventResource { - uuid + fragment EventViewerFragment on EventNode { + id title summary description location occurrences { - interval + interval { + start + end + } fullDay } images { @@ -50,7 +57,7 @@ export function EventViewer({ () => eventData?.occurrences ? eventData.occurrences.map((occurrence) => { - const interval = Interval.fromISO(occurrence.interval); + const interval = intervalFromSomething(occurrence.interval); return { interval, fullDay: occurrence.fullDay, @@ -62,7 +69,7 @@ export function EventViewer({ const navigate = useNavigate(); const { EventDeletePopup, showModal } = useEventDeletePopup({ - uuid: eventData?.uuid ?? "", + uuid: eventData?.id ?? "", onDelete: () => { navigate({ to: "/events" }).catch((error: unknown) => console.error(error) @@ -93,7 +100,7 @@ export function EventViewer({ {eventData.title} diff --git a/packages/portal/src/pages/events/single-event/view-event/ViewEventPage.tsx b/packages/portal/src/pages/events/single-event/view-event/ViewEventPage.tsx index bb80bfbf..af969071 100644 --- a/packages/portal/src/pages/events/single-event/view-event/ViewEventPage.tsx +++ b/packages/portal/src/pages/events/single-event/view-event/ViewEventPage.tsx @@ -1,12 +1,12 @@ import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useParams } from "@tanstack/react-router"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { useQuery } from "urql"; import { EventViewer } from "./EventViewer"; const viewEventPageDocument = graphql(/* GraphQL */ ` - query ViewEventPage($uuid: String!) { + query ViewEventPage($uuid: GlobalId!) { event(uuid: $uuid) { data { ...EventViewerFragment diff --git a/packages/portal/src/pages/feed/FeedPage.tsx b/packages/portal/src/pages/feed/FeedPage.tsx index 63bf4b78..f275a842 100644 --- a/packages/portal/src/pages/feed/FeedPage.tsx +++ b/packages/portal/src/pages/feed/FeedPage.tsx @@ -1,7 +1,7 @@ import { useImagePicker } from "@hooks/useImagePicker"; import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { dateTimeFromSomething } from "@ukdanceblue/common"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { Button, Card, @@ -19,7 +19,7 @@ import { useClient, useQuery } from "urql"; const feedPageDocument = graphql(/* GraphQL */ ` query FeedPage { feed(limit: null) { - uuid + id title createdAt textContent @@ -34,7 +34,7 @@ const feedPageDocument = graphql(/* GraphQL */ ` const createFeedItemDocument = graphql(/* GraphQL */ ` mutation CreateFeedItem($input: CreateFeedInput!) { createFeedItem(input: $input) { - uuid + id } } `); @@ -155,7 +155,7 @@ export function FeedPage() { {result.data?.feed.map((feedItem) => ( - + { await client .mutation(deleteFeedItemDocument, { - uuid: feedItem.uuid, + uuid: feedItem.id, }) .toPromise(); setTimeout( diff --git a/packages/portal/src/pages/images/list/CreateImagePopup.tsx b/packages/portal/src/pages/images/list/CreateImagePopup.tsx index 746231ba..b4470212 100644 --- a/packages/portal/src/pages/images/list/CreateImagePopup.tsx +++ b/packages/portal/src/pages/images/list/CreateImagePopup.tsx @@ -1,14 +1,14 @@ import { TanAntFormItem } from "@elements/components/form/TanAntFormItem"; import { useAntFeedback } from "@hooks/useAntFeedback"; import { useForm } from "@tanstack/react-form"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { Form, Input, Modal } from "antd"; import { useClient } from "urql"; const createImageDocument = graphql(/* GraphQL */ ` mutation CreateImage($input: CreateImageInput!) { createImage(input: $input) { - uuid + id } } `); @@ -42,7 +42,7 @@ export function CreateImagePopup({ if (error) { void showErrorMessage(error.message); } - onClose(data.createImage.uuid); + onClose(data.createImage.id); } else { void showErrorMessage(error?.message ?? "An unknown error occurred"); } diff --git a/packages/portal/src/pages/images/list/ImagesTable.tsx b/packages/portal/src/pages/images/list/ImagesTable.tsx index 16e0fc5c..bf509af1 100644 --- a/packages/portal/src/pages/images/list/ImagesTable.tsx +++ b/packages/portal/src/pages/images/list/ImagesTable.tsx @@ -7,15 +7,15 @@ import { SortDirection, base64StringToArray } from "@ukdanceblue/common"; import { getFragmentData, graphql, -} from "@ukdanceblue/common/graphql-client-admin"; +} from "@ukdanceblue/common/graphql-client-portal"; import { Button, Flex, Image, Modal, Table, Typography, Upload } from "antd"; import { useState } from "react"; import { thumbHashToDataURL } from "thumbhash"; import { useQuery } from "urql"; const ImagesTableFragment = graphql(/* GraphQL */ ` - fragment ImagesTableFragment on ImageResource { - uuid + fragment ImagesTableFragment on ImageNode { + id url thumbHash height @@ -65,9 +65,7 @@ export const ImagesTable = () => { { initPage: 1, initPageSize: 10, - initSorting: [ - { field: "createdAt", direction: SortDirection.DESCENDING }, - ], + initSorting: [{ field: "createdAt", direction: SortDirection.desc }], }, { allFields: ["alt", "width", "height", "createdAt", "updatedAt"], @@ -124,7 +122,7 @@ export const ImagesTable = () => { {
    uuid} + rowKey={({ id }) => id} loading={fetching} pagination={ imagesDocument @@ -186,8 +184,8 @@ export const ImagesTable = () => { | "updatedAt", direction: sort.order === "ascend" - ? SortDirection.ASCENDING - : SortDirection.DESCENDING, + ? SortDirection.asc + : SortDirection.desc, }); } }} diff --git a/packages/portal/src/pages/marathon/create/useMarathonCreatorForm.ts b/packages/portal/src/pages/marathon/create/useMarathonCreatorForm.ts index 18b4b97f..3f0add52 100644 --- a/packages/portal/src/pages/marathon/create/useMarathonCreatorForm.ts +++ b/packages/portal/src/pages/marathon/create/useMarathonCreatorForm.ts @@ -1,7 +1,7 @@ import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useForm } from "@tanstack/react-form"; import { useNavigate } from "@tanstack/react-router"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import type { DateTime } from "luxon"; import { useMutation } from "urql"; @@ -11,7 +11,7 @@ export function useMarathonCreatorForm() { graphql(/* GraphQL */ ` mutation CreateMarathon($input: CreateMarathonInput!) { createMarathon(input: $input) { - uuid + id } } `) @@ -67,7 +67,7 @@ export function useMarathonCreatorForm() { resetWatcher(); await navigate({ to: "/marathon/$marathonId/", - params: { marathonId: data.createMarathon.uuid }, + params: { marathonId: data.createMarathon.id }, }); } }, diff --git a/packages/portal/src/pages/marathon/overview/MarathonOverviewPage.tsx b/packages/portal/src/pages/marathon/overview/MarathonOverviewPage.tsx index b0bc5c92..80c5eaee 100644 --- a/packages/portal/src/pages/marathon/overview/MarathonOverviewPage.tsx +++ b/packages/portal/src/pages/marathon/overview/MarathonOverviewPage.tsx @@ -1,6 +1,6 @@ import { PlusOutlined } from "@ant-design/icons"; import { useLinkProps } from "@tanstack/react-router"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { Button, Empty, Flex } from "antd"; import { useQuery } from "urql"; @@ -10,7 +10,7 @@ import { MarathonsTable } from "./MarathonsTable"; const marathonOverviewPageDocument = graphql(/* GraphQL */ ` query MarathonOverviewPage { - nextMarathon { + latestMarathon { ...MarathonViewerFragment } marathons(sendAll: true) { @@ -41,10 +41,10 @@ export function MarathonOverviewPage() { Create New Marathon - {result.data?.nextMarathon || result.data?.marathons.data.length ? ( + {result.data?.latestMarathon || result.data?.marathons.data.length ? (

    Current Marathon

    - +

    All Marathons

    diff --git a/packages/portal/src/pages/marathon/overview/MarathonsTable.tsx b/packages/portal/src/pages/marathon/overview/MarathonsTable.tsx index b1a7729a..2d744a60 100644 --- a/packages/portal/src/pages/marathon/overview/MarathonsTable.tsx +++ b/packages/portal/src/pages/marathon/overview/MarathonsTable.tsx @@ -1,11 +1,11 @@ import { EditOutlined, EyeOutlined } from "@ant-design/icons"; -import type { FragmentType } from "@ukdanceblue/common/graphql-client-admin"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-portal"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { Button, Empty, Table } from "antd"; export const MarathonTableFragment = graphql(/* GraphQL */ ` - fragment MarathonTableFragment on MarathonResource { - uuid + fragment MarathonTableFragment on MarathonNode { + id year startDate endDate diff --git a/packages/portal/src/pages/marathon/single/edit/useMarathonEditorForm.ts b/packages/portal/src/pages/marathon/single/edit/useMarathonEditorForm.ts index 85f72471..74b7a275 100644 --- a/packages/portal/src/pages/marathon/single/edit/useMarathonEditorForm.ts +++ b/packages/portal/src/pages/marathon/single/edit/useMarathonEditorForm.ts @@ -2,7 +2,7 @@ import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useForm } from "@tanstack/react-form"; import { useNavigate } from "@tanstack/react-router"; import { dateTimeFromSomething } from "@ukdanceblue/common"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import type { DateTime } from "luxon"; import { useMutation, useQuery } from "urql"; @@ -11,9 +11,12 @@ export function useMarathonCreatorForm({ marathonId }: { marathonId: string }) { const [{ fetching: editFetching, error: editError }, editMarathon] = useMutation( graphql(/* GraphQL */ ` - mutation EditMarathon($input: SetMarathonInput!, $marathonId: String!) { + mutation EditMarathon( + $input: SetMarathonInput! + $marathonId: GlobalId! + ) { setMarathon(input: $input, uuid: $marathonId) { - uuid + id } } `) @@ -29,7 +32,7 @@ export function useMarathonCreatorForm({ marathonId }: { marathonId: string }) { { data: existingData, fetching: existingFetching, error: existingError }, ] = useQuery({ query: graphql(/* GraphQL */ ` - query GetMarathon($marathonId: String!) { + query GetMarathon($marathonId: GlobalId!) { marathon(uuid: $marathonId) { year startDate @@ -55,8 +58,10 @@ export function useMarathonCreatorForm({ marathonId }: { marathonId: string }) { }>({ defaultValues: { year: existingData?.marathon.year, - startDate: dateTimeFromSomething(existingData?.marathon.startDate), - endDate: dateTimeFromSomething(existingData?.marathon.endDate), + startDate: + dateTimeFromSomething(existingData?.marathon.startDate) ?? undefined, + endDate: + dateTimeFromSomething(existingData?.marathon.endDate) ?? undefined, }, onChange: ({ startDate, endDate }) => { if (startDate && endDate && startDate.toMillis() > endDate.toMillis()) { @@ -94,7 +99,7 @@ export function useMarathonCreatorForm({ marathonId }: { marathonId: string }) { resetExistingWatcher(); await navigate({ to: "/marathon/$marathonId/", - params: { marathonId: data.setMarathon.uuid }, + params: { marathonId: data.setMarathon.id }, }); } }, diff --git a/packages/portal/src/pages/marathon/single/view/MarathonViewer.tsx b/packages/portal/src/pages/marathon/single/view/MarathonViewer.tsx index c76fd155..63d9b9b5 100644 --- a/packages/portal/src/pages/marathon/single/view/MarathonViewer.tsx +++ b/packages/portal/src/pages/marathon/single/view/MarathonViewer.tsx @@ -1,22 +1,22 @@ import { Link } from "@tanstack/react-router"; import { dateTimeFromSomething } from "@ukdanceblue/common"; -import type { FragmentType } from "@ukdanceblue/common/graphql-client-admin"; +import type { FragmentType } from "@ukdanceblue/common/graphql-client-portal"; import { getFragmentData, graphql, -} from "@ukdanceblue/common/graphql-client-admin"; +} from "@ukdanceblue/common/graphql-client-portal"; import { Descriptions, Empty, Flex } from "antd"; import { DateTime } from "luxon"; import { useMemo } from "react"; export const MarathonViewerFragment = graphql(/* GraphQL */ ` - fragment MarathonViewerFragment on MarathonResource { - uuid + fragment MarathonViewerFragment on MarathonNode { + id year startDate endDate hours { - uuid + id shownStartingAt title } @@ -46,12 +46,12 @@ export const MarathonViewer = ({ {marathonData.year} - {dateTimeFromSomething(marathonData.startDate).toLocaleString( + {dateTimeFromSomething(marathonData.startDate)?.toLocaleString( DateTime.DATETIME_MED )} - {dateTimeFromSomething(marathonData.endDate).toLocaleString( + {dateTimeFromSomething(marathonData.endDate)?.toLocaleString( DateTime.DATETIME_MED )} @@ -62,7 +62,7 @@ export const MarathonViewer = ({ Hours Add @@ -72,12 +72,12 @@ export const MarathonViewer = ({ > {sortedHours.map((hour) => ( {hour.title} diff --git a/packages/portal/src/pages/marathon/single/view/ViewMarathonPage.tsx b/packages/portal/src/pages/marathon/single/view/ViewMarathonPage.tsx index 8f7a79cf..5b8701dd 100644 --- a/packages/portal/src/pages/marathon/single/view/ViewMarathonPage.tsx +++ b/packages/portal/src/pages/marathon/single/view/ViewMarathonPage.tsx @@ -1,11 +1,11 @@ import { useParams } from "@tanstack/react-router"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { useQuery } from "urql"; import { MarathonViewer } from "./MarathonViewer"; const marathonPageDocument = graphql(/* GraphQL */ ` - query MarathonPage($marathonUuid: String!) { + query MarathonPage($marathonUuid: GlobalId!) { marathon(uuid: $marathonUuid) { ...MarathonViewerFragment } diff --git a/packages/portal/src/pages/marathon/single/view/hour/add/AddMarathonHourPage.tsx b/packages/portal/src/pages/marathon/single/view/hour/add/AddMarathonHourPage.tsx index 88d16fe9..bcfcc322 100644 --- a/packages/portal/src/pages/marathon/single/view/hour/add/AddMarathonHourPage.tsx +++ b/packages/portal/src/pages/marathon/single/view/hour/add/AddMarathonHourPage.tsx @@ -5,7 +5,7 @@ import { TanAntFormItem } from "@elements/components/form/TanAntFormItem"; import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useForm } from "@tanstack/react-form"; import { useNavigate, useParams } from "@tanstack/react-router"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { Editable, useEditor } from "@wysimark/react"; import { Button, Input } from "antd"; import type { DateTime } from "luxon"; @@ -19,7 +19,7 @@ export function AddMarathonHourPage() { $marathonUuid: String! ) { createMarathonHour(input: $input, marathonUuid: $marathonUuid) { - uuid + id } } `) diff --git a/packages/portal/src/pages/marathon/single/view/hour/edit/EditMarathonHourPage.tsx b/packages/portal/src/pages/marathon/single/view/hour/edit/EditMarathonHourPage.tsx index 7627be01..f722f078 100644 --- a/packages/portal/src/pages/marathon/single/view/hour/edit/EditMarathonHourPage.tsx +++ b/packages/portal/src/pages/marathon/single/view/hour/edit/EditMarathonHourPage.tsx @@ -6,14 +6,14 @@ import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useForm } from "@tanstack/react-form"; import { useNavigate, useParams } from "@tanstack/react-router"; import { dateTimeFromSomething } from "@ukdanceblue/common"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { Editable, useEditor } from "@wysimark/react"; import { Button, Input } from "antd"; import type { DateTime } from "luxon"; import { useMutation, useQuery } from "urql"; const editMarathonHourDataDocument = graphql(/* GraphQL */ ` - query EditMarathonHourData($marathonHourUuid: String!) { + query EditMarathonHourData($marathonHourUuid: GlobalId!) { marathonHour(uuid: $marathonHourUuid) { details durationInfo @@ -24,9 +24,9 @@ const editMarathonHourDataDocument = graphql(/* GraphQL */ ` `); const editMarathonHourDocument = graphql(/* GraphQL */ ` - mutation EditMarathonHour($input: SetMarathonHourInput!, $uuid: String!) { + mutation EditMarathonHour($input: SetMarathonHourInput!, $uuid: GlobalId!) { setMarathonHour(input: $input, uuid: $uuid) { - uuid + id } } `); diff --git a/packages/portal/src/pages/notifications/single/manage/ManageNotificationPage.tsx b/packages/portal/src/pages/notifications/single/manage/ManageNotificationPage.tsx index 892f3195..795e44f0 100644 --- a/packages/portal/src/pages/notifications/single/manage/ManageNotificationPage.tsx +++ b/packages/portal/src/pages/notifications/single/manage/ManageNotificationPage.tsx @@ -1,11 +1,11 @@ import { ManageNotificationForm } from "@elements/forms/notification/manage/ManageNotificationForm"; import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useParams } from "@tanstack/react-router"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { useQuery } from "urql"; const notificationManagerDocument = graphql(/* GraphQL */ ` - query NotificationManager($uuid: String!) { + query NotificationManager($uuid: GlobalId!) { notification(uuid: $uuid) { data { ...SingleNotificationFragment diff --git a/packages/portal/src/pages/notifications/single/view/ViewNotificationPage.tsx b/packages/portal/src/pages/notifications/single/view/ViewNotificationPage.tsx index 8df878e1..2fc8d402 100644 --- a/packages/portal/src/pages/notifications/single/view/ViewNotificationPage.tsx +++ b/packages/portal/src/pages/notifications/single/view/ViewNotificationPage.tsx @@ -3,12 +3,12 @@ import { NotificationDeliveriesTable } from "@elements/tables/notification/Notif import { NotificationViewer } from "@elements/viewers/notification/NotificationViewer"; import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useNavigate, useParams } from "@tanstack/react-router"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { Button, Flex, Typography } from "antd"; import { useQuery } from "urql"; const notificationViewerDocument = graphql(/* GraphQL */ ` - query NotificationViewer($uuid: String!) { + query NotificationViewer($uuid: GlobalId!) { notification(uuid: $uuid) { data { ...SingleNotificationFragment diff --git a/packages/portal/src/pages/people/create-person/CreatePersonPage.tsx b/packages/portal/src/pages/people/create-person/CreatePersonPage.tsx index f6ce14f7..39677d1f 100644 --- a/packages/portal/src/pages/people/create-person/CreatePersonPage.tsx +++ b/packages/portal/src/pages/people/create-person/CreatePersonPage.tsx @@ -1,11 +1,11 @@ import { PersonCreator } from "@elements/forms/person/create/PersonCreator"; import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { useQuery } from "urql"; const createPersonPageDocument = graphql(/* GraphQL */ ` query CreatePersonPage { - teams(sendAll: true, sortBy: ["name"], sortDirection: [ASCENDING]) { + teams(sendAll: true, sortBy: ["name"], sortDirection: [asc]) { data { ...TeamNameFragment } diff --git a/packages/portal/src/pages/people/single-person/edit-person/EditPersonPage.tsx b/packages/portal/src/pages/people/single-person/edit-person/EditPersonPage.tsx index 5bf971fd..85b185a5 100644 --- a/packages/portal/src/pages/people/single-person/edit-person/EditPersonPage.tsx +++ b/packages/portal/src/pages/people/single-person/edit-person/EditPersonPage.tsx @@ -1,17 +1,15 @@ import { PersonEditor } from "@elements/forms/person/edit/PersonEditor"; import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useParams } from "@tanstack/react-router"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { useQuery } from "urql"; const viewPersonPageDocument = graphql(/* GraphQL */ ` - query EditPersonPage($uuid: String!) { + query EditPersonPage($uuid: GlobalId!) { person(uuid: $uuid) { - data { - ...PersonEditorFragment - } + ...PersonEditorFragment } - teams(sendAll: true, sortBy: ["name"], sortDirection: [ASCENDING]) { + teams(sendAll: true, sortBy: ["name"], sortDirection: [asc]) { data { ...TeamNameFragment } @@ -36,7 +34,7 @@ export function EditPersonPage() { return (
    diff --git a/packages/portal/src/pages/people/single-person/view-person/ViewPersonPage.tsx b/packages/portal/src/pages/people/single-person/view-person/ViewPersonPage.tsx index cfefde13..50de9f8d 100644 --- a/packages/portal/src/pages/people/single-person/view-person/ViewPersonPage.tsx +++ b/packages/portal/src/pages/people/single-person/view-person/ViewPersonPage.tsx @@ -1,15 +1,13 @@ import { PersonViewer } from "@elements/viewers/person/PersonViewer"; import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useParams } from "@tanstack/react-router"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { useQuery } from "urql"; const viewPersonPageDocument = graphql(/* GraphQL */ ` - query ViewPersonPage($uuid: String!) { + query ViewPersonPage($uuid: GlobalId!) { person(uuid: $uuid) { - data { - ...PersonViewerFragment - } + ...PersonViewerFragment } } `); @@ -30,7 +28,7 @@ export function ViewPersonPage() { return (
    - +
    ); } diff --git a/packages/portal/src/pages/teams/spirit/single-team/edit-team/EditTeamPage.tsx b/packages/portal/src/pages/teams/spirit/single-team/edit-team/EditTeamPage.tsx index d492d3a2..12695ea2 100644 --- a/packages/portal/src/pages/teams/spirit/single-team/edit-team/EditTeamPage.tsx +++ b/packages/portal/src/pages/teams/spirit/single-team/edit-team/EditTeamPage.tsx @@ -1,11 +1,11 @@ import { TeamEditor } from "@elements/forms/team/edit/TeamEditor"; import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useParams } from "@tanstack/react-router"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; import { useQuery } from "urql"; const viewTeamPageDocument = graphql(/* GraphQL */ ` - query EditTeamPage($uuid: String!) { + query EditTeamPage($uuid: GlobalId!) { team(uuid: $uuid) { data { ...TeamEditorFragment diff --git a/packages/portal/src/pages/teams/spirit/single-team/view-team/ViewTeamPage.tsx b/packages/portal/src/pages/teams/spirit/single-team/view-team/ViewTeamPage.tsx index 3f071fcd..6fe1b606 100644 --- a/packages/portal/src/pages/teams/spirit/single-team/view-team/ViewTeamPage.tsx +++ b/packages/portal/src/pages/teams/spirit/single-team/view-team/ViewTeamPage.tsx @@ -1,29 +1,15 @@ -import { PointEntryCreator } from "@elements/forms/point-entry/create/PointEntryCreator"; -import { PointEntryTable } from "@elements/tables/point-entry/PointEntryTable"; import { TeamViewer } from "@elements/viewers/team/TeamViewer"; import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; -import { useParams } from "@tanstack/react-router"; -import { graphql } from "@ukdanceblue/common/graphql-client-admin"; +import { Outlet, useParams } from "@tanstack/react-router"; import { Flex } from "antd"; import { useQuery } from "urql"; -const teamPageDocument = graphql(/* GraphQL */ ` - query ViewTeamPage($teamUuid: String!) { - team(uuid: $teamUuid) { - data { - ...TeamViewerFragment - pointEntries { - ...PointEntryTableFragment - } - } - } - } -`); +import { teamPageDocument } from "./teamPageDocument"; export function ViewTeamPage() { const { teamId: teamUuid } = useParams({ from: "/teams/$teamId/" }); - const [{ fetching, data, error }, refetch] = useQuery({ + const [{ fetching, data, error }] = useQuery({ query: teamPageDocument, variables: { teamUuid }, }); @@ -39,23 +25,7 @@ export function ViewTeamPage() {

    Team Details

    - -
    -

    Point Entries

    - -
    -
    -

    Create Point Entry

    - refetch({ requestPolicy: "network-only" })} - /> -
    -
    +
    ); diff --git a/packages/portal/src/pages/teams/spirit/single-team/view-team/fundraising/ViewTeamFundraising.tsx b/packages/portal/src/pages/teams/spirit/single-team/view-team/fundraising/ViewTeamFundraising.tsx new file mode 100644 index 00000000..8845e127 --- /dev/null +++ b/packages/portal/src/pages/teams/spirit/single-team/view-team/fundraising/ViewTeamFundraising.tsx @@ -0,0 +1,86 @@ +import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; +import { useParams } from "@tanstack/react-router"; +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; +import { Flex } from "antd"; +import { useQuery } from "urql"; + +const ViewTeamFundraisingDocument = graphql(/* GraphQL */ ` + query ViewTeamFundraisingDocument($teamUuid: GlobalId!) { + team(uuid: $teamUuid) { + data { + # TODO: Add filtering and pagination + fundraisingEntries(sendAll: true) { + data { + id + amount + donatedByText + donatedToText + donatedOn + assignments { + id + amount + person { + name + } + } + } + } + } + } + } +`); + +export function ViewTeamFundraising() { + const { teamId: teamUuid } = useParams({ from: "/teams/$teamId/" }); + + const [{ data, fetching, error }] = useQuery({ + query: ViewTeamFundraisingDocument, + variables: { teamUuid }, + }); + useQueryStatusWatcher({ fetching, error }); + + return ( + +
    + + + + + + + + + + + {data?.team.data.fundraisingEntries.data.map( + ({ + id, + donatedByText, + donatedToText, + donatedOn, + amount, + assignments, + }) => ( + + + + + + + + ) + )} + +
    Donated ByDonated ToDonated OnAmountAssigned To
    {donatedByText}{donatedToText}{donatedOn.toLocaleString()}${amount} +
    + {assignments.map(({ id, person, amount }) => ( +
    +
    {person?.name}
    +
    ${amount}
    +
    + ))} +
    +
    + + ); +} diff --git a/packages/portal/src/pages/teams/spirit/single-team/view-team/points/ViewTeamPoints.tsx b/packages/portal/src/pages/teams/spirit/single-team/view-team/points/ViewTeamPoints.tsx new file mode 100644 index 00000000..11c3ad7f --- /dev/null +++ b/packages/portal/src/pages/teams/spirit/single-team/view-team/points/ViewTeamPoints.tsx @@ -0,0 +1,42 @@ +import { PointEntryCreator } from "@elements/forms/point-entry/create/PointEntryCreator"; +import { PointEntryTable } from "@elements/tables/point-entry/PointEntryTable"; +import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; +import { useParams } from "@tanstack/react-router"; +import { Flex } from "antd"; +import { useQuery } from "urql"; + +import { teamPageDocument } from "../teamPageDocument"; + +export function ViewTeamPoints() { + const { teamId: teamUuid } = useParams({ from: "/teams/$teamId/" }); + + const [{ fetching, data, error }, refetch] = useQuery({ + query: teamPageDocument, + variables: { teamUuid }, + }); + useQueryStatusWatcher({ + fetching, + error, + loadingMessage: "Loading point entries...", + }); + + return ( + +
    +

    Point Entries

    + +
    +
    +

    Create Point Entry

    + refetch({ requestPolicy: "network-only" })} + /> +
    +
    + ); +} diff --git a/packages/portal/src/pages/teams/spirit/single-team/view-team/teamPageDocument.tsx b/packages/portal/src/pages/teams/spirit/single-team/view-team/teamPageDocument.tsx new file mode 100644 index 00000000..315a17dc --- /dev/null +++ b/packages/portal/src/pages/teams/spirit/single-team/view-team/teamPageDocument.tsx @@ -0,0 +1,14 @@ +import { graphql } from "@ukdanceblue/common/graphql-client-portal"; + +export const teamPageDocument = graphql(/* GraphQL */ ` + query ViewTeamPage($teamUuid: GlobalId!) { + team(uuid: $teamUuid) { + data { + ...TeamViewerFragment + pointEntries { + ...PointEntryTableFragment + } + } + } + } +`); diff --git a/packages/portal/src/routing/router.ts b/packages/portal/src/routing/router.ts index 5b28a48d..891ac893 100644 --- a/packages/portal/src/routing/router.ts +++ b/packages/portal/src/routing/router.ts @@ -46,6 +46,8 @@ import { } from "./personRouter"; import { rootRoute } from "./rootRoute"; import { + ViewTeamFundraisingRoute, + ViewTeamPointsRoute, createTeamRoute, editTeamRoute, singleTeamRoute, @@ -63,7 +65,13 @@ const routeTree = rootRoute.addChildren([ teamsRoute.addChildren([ teamsTableRoute, createTeamRoute, - singleTeamRoute.addChildren([editTeamRoute, viewTeamRoute]), + singleTeamRoute.addChildren([ + editTeamRoute, + viewTeamRoute.addChildren([ + ViewTeamFundraisingRoute, + ViewTeamPointsRoute, + ]), + ]), ]), peopleRoute.addChildren([ peopleTableRoute, diff --git a/packages/portal/src/routing/teamRoutes.ts b/packages/portal/src/routing/teamRoutes.ts index e3587bb9..029fd0b6 100644 --- a/packages/portal/src/routing/teamRoutes.ts +++ b/packages/portal/src/routing/teamRoutes.ts @@ -3,6 +3,8 @@ import { ListTeamsPage } from "@pages/teams/spirit/list-teams/ListTeamsPage"; import { SingleTeamPage } from "@pages/teams/spirit/single-team/SingleTeamPage"; import { EditTeamPage } from "@pages/teams/spirit/single-team/edit-team/EditTeamPage"; import { ViewTeamPage } from "@pages/teams/spirit/single-team/view-team/ViewTeamPage"; +import { ViewTeamFundraising } from "@pages/teams/spirit/single-team/view-team/fundraising/ViewTeamFundraising"; +import { ViewTeamPoints } from "@pages/teams/spirit/single-team/view-team/points/ViewTeamPoints"; import { Route } from "@tanstack/react-router"; import { teamsRoute } from "./baseRoutes"; @@ -36,3 +38,15 @@ export const viewTeamRoute = new Route({ getParentRoute: () => singleTeamRoute, component: ViewTeamPage, }); + +export const ViewTeamFundraisingRoute = new Route({ + path: "fundraising", + getParentRoute: () => viewTeamRoute, + component: ViewTeamFundraising, +}); + +export const ViewTeamPointsRoute = new Route({ + path: "points", + getParentRoute: () => viewTeamRoute, + component: ViewTeamPoints, +}); diff --git a/packages/server/.env.example b/packages/server/.env.example index 884397d7..5ab75912 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -1,14 +1,10 @@ # If using the dev container these values are set automatically: -DB_HOST=localhost -DB_PORT=5432 -DB_NAME=danceblue -DB_UNAME=danceblue -DB_PWD=danceblue DATABASE_URL=postgres://danceblue:danceblue@localhost:5432/danceblue?schema=danceblue NODE_ENV=development APPLICATION_HOST=localhost APPLICATION_PORT=8000 MS_OIDC_URL= +LOG_DIR="logs" # These values must always be set manually: COOKIE_SECRET= diff --git a/packages/server/Dockerfile b/packages/server/Dockerfile deleted file mode 100644 index f3c2a059..00000000 --- a/packages/server/Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -# Needs to be run with the context set to project root, i.e. docker build -t app-server ../.. -f Dockerfile - -FROM node:18 - -ENV NODE_ENV="production" -ENV APPLICATION_PORT="8000" -ENV APPLICATION_HOST="0.0.0.0" - -EXPOSE ${APPLICATION_PORT} - -# ENV COOKIE_SECRET="" -# ENV JWT_SECRET="" - -ENV DB_HOST="app-database" -ENV DB_PORT="5432" -ENV DB_NAME="danceblue" -ENV DB_UNAME="danceblue" -# ENV DB_PWD="" - -ENV MS_OIDC_URL="https://login.microsoftonline.com/2b30530b-69b6-4457-b818-481cb53d42ae/v2.0/.well-known/openid-configuration" -# ENV MS_CLIENT_ID="" -# ENV MS_CLIENT_SECRET="" - -RUN mkdir -p /app/packages/server -RUN mkdir -p /app/packages/common -RUN mkdir -p /app/node_modules - -COPY yarn.lock /app/yarn.lock -COPY package.json /app/package.json -COPY node_modules /app/node_modules/ -COPY packages/server /app/packages/server/ -COPY packages/common /app/packages/common/ - -WORKDIR /app/packages/server - -CMD corepack yarn run migrate-and-start diff --git a/packages/server/package.json b/packages/server/package.json index 1addc86b..724b1589 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -5,15 +5,14 @@ "main": "dist/index.js", "type": "module", "engines": { - "node": ">=16.0.0" + "node": ">=22.0.0" }, "scripts": { "start": "node .", "build": "tsc", "bs": "yarn run build && yarn run start", - "this-is-bullshit": "yarn run bs", + "dev": "tsc --watch & node --watch . --watch-path=./dist", "migrate-and-start": "yarn dlx prisma migrate deploy && yarn run start", - "dev": "nodemon -w ./dist ./dist/index.js ../common/dist --", "test": "jest", "start-seed": "node . --seed-db --reset-db", "start-reset": "node . --reset-db", @@ -27,49 +26,30 @@ "@as-integrations/koa": "^1.1.1", "@azure/msal-node": "^1.16.0", "@faker-js/faker": "^7.6.0", - "@jest/globals": "^29.5.0", "@koa/cors": "^4.0.0", "@koa/router": "^12.0.1", "@prisma/client": "^5.8.0", - "@types/body-parser": "^1.19.2", - "@types/cookie-parser": "^1.4.3", - "@types/cors": "^2.8.13", - "@types/ejs": "^3.1.2", - "@types/express": "^4.17.17", "@types/express-session": "^1.17.7", "@types/http-errors": "^2.0.1", "@types/jsonwebtoken": "^9.0.1", "@types/luxon": "^3.4.2", - "@types/multer": "^1.4.7", "@types/node": "^18.15.11", "@types/node-fetch": "^2.6.4", "@types/normalize-path": "^3.0.0", - "@types/qs": "^6.9.7", - "@types/ws": "^8.5.4", "@ukdanceblue/common": "workspace:^", - "body-parser": "^1.20.2", "class-validator": "^0.14.0", - "cookie-parser": "^1.4.6", - "cors": "^2.8.5", "croner": "^8.0.1", "dotenv": "^16.0.3", "dree": "^4.5.2", - "ejs": "^3.1.9", "expo-server-sdk": "^3.7.0", - "express": "^4.18.2", - "express-session": "^1.17.3", "graphql": "^16.8.0", "graphql-scalars": "^1.22.5", - "http-errors": "^2.0.0", "http-status-codes": "^2.2.0", - "jest": "^29.5.0", - "joi": "^17.9.1", "jsonwebtoken": "^9.0.0", "koa": "^2.14.2", "koa-body": "^6.0.1", "luxon": "^3.4.4", "mime": "^4.0.1", - "multer": "1.4.5-lts.1", "node-fetch": "^3.3.1", "normalize-path": "^3.0.0", "openid-client": "^5.4.0", @@ -77,20 +57,18 @@ "pg-hstore": "^2.3.4", "postgres-interval": "^4.0.0", "postgres-range": "^1.1.3", - "qs": "^6.11.1", "reflect-metadata": "^0.2.1", "sharp": "^0.32.1", "thumbhash": "^0.1.1", - "ts-jest": "^29.0.5", "ts-node": "^10.9.1", + "ts-results-es": "^4.2.0", "type-graphql": "^2.0.0-beta.3", "typedi": "^0.10.0", - "typescript": "^5.4.3", - "umzug": "^3.3.1", + "typescript": "^5.5.3", "utility-types": "^3.10.0", "validator": "^13.9.0", "winston": "^3.8.2", - "ws": "^8.13.0" + "zod": "^3.23.8" }, "devDependencies": { "@types/koa": "^2.13.6", @@ -105,5 +83,18 @@ "prisma": { "seed": "node ./dist/seed.js" }, + "imports": { + "#auth/*.js": "./dist/lib/auth/*.js", + "#error/*.js": "./dist/lib/error/*.js", + "#files/*.js": "./dist/lib/files/*.js", + "#logging/*.js": "./dist/lib/logging/*.js", + "#notification/*.js": "./dist/lib/notification/*.js", + "#lib/*.js": "./dist/lib/*.js", + "#jobs/*.js": "./dist/jobs/*.js", + "#repositories/*.js": "./dist/repositories/*.js", + "#resolvers/*.js": "./dist/resolvers/*.js", + "#routes/*.js": "./dist/routes/*.js", + "#environment": "./dist/environment.js" + }, "packageManager": "yarn@4.1.1+sha256.f3cc0eda8e5560e529c7147565b30faa43b4e472d90e8634d7134a37c7f59781" } diff --git a/packages/server/prisma/migrations/20240424003954_team_marathon_relation/migration.sql b/packages/server/prisma/migrations/20240424003954_team_marathon_relation/migration.sql new file mode 100644 index 00000000..306bd006 --- /dev/null +++ b/packages/server/prisma/migrations/20240424003954_team_marathon_relation/migration.sql @@ -0,0 +1,53 @@ +/* + Warnings: + + - You are about to drop the column `marathon_year` on the `teams` table. All the data in the column will be lost. + - A unique constraint covering the columns `[marathon_id,persistent_identifier]` on the table `teams` will be added. If there are existing duplicate values, this will fail. + - Added the required column `marathon_id` to the `teams` table without a default value. This is not possible if the table is not empty. + +*/ + + + +-- AlterTable +ALTER TABLE "teams" ADD COLUMN "marathon_id" INTEGER; + +UPDATE "teams" SET "marathon_id" = ( + SELECT "id" + FROM "marathons" + WHERE "year" = 'DB24' +) WHERE "marathon_year" = 'DB24'; + +ALTER TABLE "teams" ALTER COLUMN "marathon_id" SET NOT NULL; + +-- Replace the view +DROP VIEW "teams_with_total_points"; + + +-- DropColumn +ALTER TABLE "teams" DROP COLUMN "marathon_year"; + +create view teams_with_total_points + (id, uuid, name, type, legacyStatus, persistentIdentifier, marathonId, createdAt, updatedAt, + totalPoints) as +SELECT teams.id, + teams.uuid, + teams.name, + teams.type, + teams.legacy_status AS legacyStatus, + teams.persistent_identifier AS persistentIdentifier, + teams.marathon_id AS marathonId, + teams.created_at AS createdAt, + teams.updated_at AS updatedAt, + COALESCE(points.totalPoints, 0::bigint) AS totalPoints +FROM teams + LEFT JOIN (SELECT sum(entry.points) AS totalPoints, + entry.team_id + FROM point_entries entry + GROUP BY entry.team_id) points ON teams.id = points.team_id; + +-- AddForeignKey +ALTER TABLE "teams" ADD CONSTRAINT "teams_marathon_id_fkey" FOREIGN KEY ("marathon_id") REFERENCES "marathons"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- CreateIndex +CREATE UNIQUE INDEX "teams_marathon_id_persistent_identifier_key" ON "teams"("marathon_id", "persistent_identifier"); \ No newline at end of file diff --git a/packages/server/prisma/migrations/20240427193820_team_committees/migration.sql b/packages/server/prisma/migrations/20240427193820_team_committees/migration.sql new file mode 100644 index 00000000..6e213d2a --- /dev/null +++ b/packages/server/prisma/migrations/20240427193820_team_committees/migration.sql @@ -0,0 +1,40 @@ +/* + Warnings: + + - A unique constraint covering the columns `[marathon_id,committee_id]` on the table `teams` will be added. If there are existing duplicate values, this will fail. + +*/ +-- DropIndex +DROP INDEX "teams_persistent_identifier_unique"; + +-- AlterTable +ALTER TABLE "teams" ADD COLUMN "committee_id" INTEGER; + +-- CreateTable +CREATE TABLE "committees" ( + "id" SERIAL NOT NULL, + "uuid" UUID NOT NULL, + "identifier" "enum_people_committee_name" NOT NULL, + "parent_committee_id" INTEGER, + "created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMPTZ(6) NOT NULL, + + CONSTRAINT "committees_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "committees_uuid_unique" ON "committees"("uuid"); +CREATE UNIQUE INDEX "committees_identifier_unique" ON "committees"("identifier"); + +-- CreateIndex +CREATE INDEX "committees_uuid" ON "committees"("uuid"); +CREATE INDEX "committees_identifier" ON "committees"("identifier"); + +-- CreateIndex +CREATE UNIQUE INDEX "teams_marathon_id_committee_id_key" ON "teams"("marathon_id", "committee_id"); + +-- AddForeignKey +ALTER TABLE "teams" ADD CONSTRAINT "teams_committee_id_fkey" FOREIGN KEY ("committee_id") REFERENCES "committees"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "committees" ADD CONSTRAINT "committees_parent_committee_id_fkey" FOREIGN KEY ("parent_committee_id") REFERENCES "committees"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/packages/server/prisma/migrations/20240503111807_add_overall_committee_to_committee_name_enum/migration.sql b/packages/server/prisma/migrations/20240503111807_add_overall_committee_to_committee_name_enum/migration.sql new file mode 100644 index 00000000..f24f39c3 --- /dev/null +++ b/packages/server/prisma/migrations/20240503111807_add_overall_committee_to_committee_name_enum/migration.sql @@ -0,0 +1,8 @@ +-- AlterEnum +ALTER TYPE "enum_people_committee_name" ADD VALUE 'overallCommittee'; + +-- DropIndex +DROP INDEX "committees_identifier"; + +-- RenameIndex +ALTER INDEX "committees_identifier_unique" RENAME TO "committees_identifier_key"; diff --git a/packages/server/prisma/migrations/20240513003536_committee_role_on_membership/migration.sql b/packages/server/prisma/migrations/20240513003536_committee_role_on_membership/migration.sql new file mode 100644 index 00000000..d747c83d --- /dev/null +++ b/packages/server/prisma/migrations/20240513003536_committee_role_on_membership/migration.sql @@ -0,0 +1,13 @@ +/* + Warnings: + + - You are about to drop the column `committee_name` on the `people` table. All the data in the column will be lost. + - You are about to drop the column `committee_role` on the `people` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "memberships" ADD COLUMN "committee_role" "enum_people_committee_role"; + +-- AlterTable +ALTER TABLE "people" DROP COLUMN "committee_name", +DROP COLUMN "committee_role"; diff --git a/packages/server/prisma/migrations/20240514220651_optional_marathon_date/migration.sql b/packages/server/prisma/migrations/20240514220651_optional_marathon_date/migration.sql new file mode 100644 index 00000000..2276edb9 --- /dev/null +++ b/packages/server/prisma/migrations/20240514220651_optional_marathon_date/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "marathons" ALTER COLUMN "start_date" DROP NOT NULL, +ALTER COLUMN "end_date" DROP NOT NULL; diff --git a/packages/server/prisma/migrations/20240529060132_dbfunds_sync/migration.sql b/packages/server/prisma/migrations/20240529060132_dbfunds_sync/migration.sql new file mode 100644 index 00000000..a99b90c9 --- /dev/null +++ b/packages/server/prisma/migrations/20240529060132_dbfunds_sync/migration.sql @@ -0,0 +1,99 @@ +/* + Warnings: + + - A unique constraint covering the columns `[db_funds_team_id]` on the table `teams` will be added. If there are existing duplicate values, this will fail. + +*/ +-- AlterTable +ALTER TABLE "teams" ADD COLUMN "db_funds_team_id" INTEGER; + +-- CreateTable +CREATE TABLE "db_funds_teams" ( + "id" SERIAL NOT NULL, + "db_num" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "total_amount" DOUBLE PRECISION NOT NULL, + "active" BOOLEAN NOT NULL, + + CONSTRAINT "db_funds_teams_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "db_funds_team_entries" ( + "id" SERIAL NOT NULL, + "amount" DECIMAL(65,30) NOT NULL, + "donated_by" TEXT NOT NULL, + "donated_to" TEXT NOT NULL, + "date" TIMESTAMP(3) NOT NULL, + "team_db_num" INTEGER NOT NULL, + + CONSTRAINT "db_funds_team_entries_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "fundraising_entries" ( + "id" SERIAL NOT NULL, + "uuid" UUID NOT NULL, + "created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMPTZ(6) NOT NULL, + "total_amount" DECIMAL(65,30) NOT NULL, + "db_funds_entry_donated_by" TEXT, + "db_funds_entry_date" TIMESTAMP(3), + + CONSTRAINT "fundraising_entries_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "fundraising_assignments" ( + "id" SERIAL NOT NULL, + "uuid" UUID NOT NULL, + "created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMPTZ(6) NOT NULL, + "amount" DECIMAL(65,30) NOT NULL, + "person_id" INTEGER NOT NULL, + "fundraising_id" INTEGER NOT NULL, + + CONSTRAINT "fundraising_assignments_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "db_funds_teams_db_num_key" ON "db_funds_teams"("db_num"); + +-- CreateIndex +CREATE UNIQUE INDEX "db_funds_team_entries_donated_by_date_key" ON "db_funds_team_entries"("donated_by", "date"); + +-- CreateIndex +CREATE UNIQUE INDEX "fundraising_entries_uuid_unique" ON "fundraising_entries"("uuid"); + +-- CreateIndex +CREATE INDEX "fundraising_entries_uuid" ON "fundraising_entries"("uuid"); + +-- CreateIndex +CREATE UNIQUE INDEX "fundraising_entries_db_funds_entry_donated_by_date_key" ON "fundraising_entries"("db_funds_entry_donated_by", "db_funds_entry_date"); + +-- CreateIndex +CREATE UNIQUE INDEX "fundraising_assignments_uuid_unique" ON "fundraising_assignments"("uuid"); + +-- CreateIndex +CREATE INDEX "fundraising_assignments_uuid" ON "fundraising_assignments"("uuid"); + +-- CreateIndex +CREATE UNIQUE INDEX "fundraising_assignments_fundraising_id_person_id_key" ON "fundraising_assignments"("fundraising_id", "person_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "teams_db_funds_team_id_key" ON "teams"("db_funds_team_id"); + +-- AddForeignKey +ALTER TABLE "teams" ADD CONSTRAINT "teams_db_funds_team_id_fkey" FOREIGN KEY ("db_funds_team_id") REFERENCES "db_funds_teams"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "db_funds_team_entries" ADD CONSTRAINT "db_funds_team_entries_team_db_num_fkey" FOREIGN KEY ("team_db_num") REFERENCES "db_funds_teams"("db_num") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "fundraising_entries" ADD CONSTRAINT "fundraising_entries_db_funds_entry_donated_by_db_funds_ent_fkey" FOREIGN KEY ("db_funds_entry_donated_by", "db_funds_entry_date") REFERENCES "db_funds_team_entries"("donated_by", "date") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "fundraising_assignments" ADD CONSTRAINT "fundraising_assignments_person_id_fkey" FOREIGN KEY ("person_id") REFERENCES "people"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "fundraising_assignments" ADD CONSTRAINT "fundraising_assignments_fundraising_id_fkey" FOREIGN KEY ("fundraising_id") REFERENCES "fundraising_entries"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/packages/server/prisma/migrations/20240529070925_unique_dbnum_on_marathon/migration.sql b/packages/server/prisma/migrations/20240529070925_unique_dbnum_on_marathon/migration.sql new file mode 100644 index 00000000..5ccaedaa --- /dev/null +++ b/packages/server/prisma/migrations/20240529070925_unique_dbnum_on_marathon/migration.sql @@ -0,0 +1,23 @@ +/* + Warnings: + + - A unique constraint covering the columns `[db_num,marathon_id]` on the table `db_funds_teams` will be added. If there are existing duplicate values, this will fail. + +*/ +-- DropForeignKey +ALTER TABLE "db_funds_team_entries" DROP CONSTRAINT "db_funds_team_entries_team_db_num_fkey"; + +-- DropIndex +DROP INDEX "db_funds_teams_db_num_key"; + +-- AlterTable +ALTER TABLE "db_funds_teams" ADD COLUMN "marathon_id" INTEGER; + +-- CreateIndex +CREATE UNIQUE INDEX "db_funds_teams_db_num_marathon_id_key" ON "db_funds_teams"("db_num", "marathon_id"); + +-- AddForeignKey +ALTER TABLE "db_funds_teams" ADD CONSTRAINT "db_funds_teams_marathon_id_fkey" FOREIGN KEY ("marathon_id") REFERENCES "marathons"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "db_funds_team_entries" ADD CONSTRAINT "db_funds_team_entries_team_db_num_fkey" FOREIGN KEY ("team_db_num") REFERENCES "db_funds_teams"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/packages/server/prisma/migrations/20240530184804_dbfunds_tweaks/migration.sql b/packages/server/prisma/migrations/20240530184804_dbfunds_tweaks/migration.sql new file mode 100644 index 00000000..fa583e43 --- /dev/null +++ b/packages/server/prisma/migrations/20240530184804_dbfunds_tweaks/migration.sql @@ -0,0 +1,8 @@ +-- DropForeignKey +ALTER TABLE "db_funds_team_entries" DROP CONSTRAINT "db_funds_team_entries_team_db_num_fkey"; + +-- AlterTable +ALTER TABLE "db_funds_team_entries" ALTER COLUMN "donated_to" DROP NOT NULL; + +-- AddForeignKey +ALTER TABLE "db_funds_team_entries" ADD CONSTRAINT "db_funds_team_entries_team_db_num_fkey" FOREIGN KEY ("team_db_num") REFERENCES "db_funds_teams"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/server/prisma/migrations/20240531023738_optional_donated_by/migration.sql b/packages/server/prisma/migrations/20240531023738_optional_donated_by/migration.sql new file mode 100644 index 00000000..4a4d63f2 --- /dev/null +++ b/packages/server/prisma/migrations/20240531023738_optional_donated_by/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "db_funds_team_entries" ALTER COLUMN "donated_by" DROP NOT NULL; diff --git a/packages/server/prisma/migrations/20240606202016_more_fundraising/migration.sql b/packages/server/prisma/migrations/20240606202016_more_fundraising/migration.sql new file mode 100644 index 00000000..47826fc7 --- /dev/null +++ b/packages/server/prisma/migrations/20240606202016_more_fundraising/migration.sql @@ -0,0 +1,29 @@ +/* + Warnings: + + - You are about to drop the column `db_funds_entry_date` on the `fundraising_entries` table. All the data in the column will be lost. + - You are about to drop the column `db_funds_entry_donated_by` on the `fundraising_entries` table. All the data in the column will be lost. + - You are about to drop the column `total_amount` on the `fundraising_entries` table. All the data in the column will be lost. + - A unique constraint covering the columns `[db_funds_entry_id]` on the table `fundraising_entries` will be added. If there are existing duplicate values, this will fail. + +*/ +-- DropForeignKey +ALTER TABLE "fundraising_entries" DROP CONSTRAINT "fundraising_entries_db_funds_entry_donated_by_db_funds_ent_fkey"; + +-- DropIndex +DROP INDEX "db_funds_team_entries_donated_by_date_key"; + +-- DropIndex +DROP INDEX "fundraising_entries_db_funds_entry_donated_by_date_key"; + +-- AlterTable +ALTER TABLE "fundraising_entries" DROP COLUMN "db_funds_entry_date", +DROP COLUMN "db_funds_entry_donated_by", +DROP COLUMN "total_amount", +ADD COLUMN "db_funds_entry_id" INTEGER; + +-- CreateIndex +CREATE UNIQUE INDEX "fundraising_entries_db_funds_entry_id_key" ON "fundraising_entries"("db_funds_entry_id"); + +-- AddForeignKey +ALTER TABLE "fundraising_entries" ADD CONSTRAINT "fundraising_entries_db_funds_entry_id_fkey" FOREIGN KEY ("db_funds_entry_id") REFERENCES "db_funds_team_entries"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/packages/server/prisma/migrations/20240609033749_multiple_dbfunds_teams/migration.sql b/packages/server/prisma/migrations/20240609033749_multiple_dbfunds_teams/migration.sql new file mode 100644 index 00000000..80855b13 --- /dev/null +++ b/packages/server/prisma/migrations/20240609033749_multiple_dbfunds_teams/migration.sql @@ -0,0 +1,2 @@ +-- DropIndex +DROP INDEX "teams_db_funds_team_id_key"; diff --git a/packages/server/prisma/migrations/20240613224347_unique_donated_by_to_on/migration.sql b/packages/server/prisma/migrations/20240613224347_unique_donated_by_to_on/migration.sql new file mode 100644 index 00000000..5f05f476 --- /dev/null +++ b/packages/server/prisma/migrations/20240613224347_unique_donated_by_to_on/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[donated_to,donated_by,date]` on the table `db_funds_team_entries` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "db_funds_team_entries_donated_to_donated_by_date_key" ON "db_funds_team_entries"("donated_to", "donated_by", "date"); diff --git a/packages/server/prisma/migrations/20240614023816_cascade_on_entry_delete/migration.sql b/packages/server/prisma/migrations/20240614023816_cascade_on_entry_delete/migration.sql new file mode 100644 index 00000000..7f2b0e5e --- /dev/null +++ b/packages/server/prisma/migrations/20240614023816_cascade_on_entry_delete/migration.sql @@ -0,0 +1,26 @@ +/* + Warnings: + + - Made the column `db_funds_entry_id` on table `fundraising_entries` required. This step will fail if there are existing NULL values in that column. + +*/ +-- DropForeignKey +ALTER TABLE "fundraising_assignments" DROP CONSTRAINT "fundraising_assignments_fundraising_id_fkey"; + +-- DropForeignKey +ALTER TABLE "fundraising_assignments" DROP CONSTRAINT "fundraising_assignments_person_id_fkey"; + +-- DropForeignKey +ALTER TABLE "fundraising_entries" DROP CONSTRAINT "fundraising_entries_db_funds_entry_id_fkey"; + +-- AlterTable +ALTER TABLE "fundraising_entries" ALTER COLUMN "db_funds_entry_id" SET NOT NULL; + +-- AddForeignKey +ALTER TABLE "fundraising_entries" ADD CONSTRAINT "fundraising_entries_db_funds_entry_id_fkey" FOREIGN KEY ("db_funds_entry_id") REFERENCES "db_funds_team_entries"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "fundraising_assignments" ADD CONSTRAINT "fundraising_assignments_person_id_fkey" FOREIGN KEY ("person_id") REFERENCES "people"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "fundraising_assignments" ADD CONSTRAINT "fundraising_assignments_fundraising_id_fkey" FOREIGN KEY ("fundraising_id") REFERENCES "fundraising_entries"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/server/prisma/migrations/20240710193233_job_state/migration.sql b/packages/server/prisma/migrations/20240710193233_job_state/migration.sql new file mode 100644 index 00000000..908e3cac --- /dev/null +++ b/packages/server/prisma/migrations/20240710193233_job_state/migration.sql @@ -0,0 +1,7 @@ +-- CreateTable +CREATE TABLE "job_states" ( + "job_name" TEXT NOT NULL, + "last_run" TIMESTAMPTZ(6) NOT NULL, + + CONSTRAINT "job_states_pkey" PRIMARY KEY ("job_name") +); diff --git a/packages/server/prisma/schema.prisma b/packages/server/prisma/schema.prisma index 286c8e0d..3147a5c9 100644 --- a/packages/server/prisma/schema.prisma +++ b/packages/server/prisma/schema.prisma @@ -150,15 +150,16 @@ model LoginFlowSession { } model Membership { - id Int @id @default(autoincrement()) - uuid String @unique(map: "memberships_uuid_unique") @default(uuid()) @db.Uuid - createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) - updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6) - personId Int @map("person_id") - teamId Int @map("team_id") - position MembershipPosition - person Person @relation(fields: [personId], references: [id], onDelete: Cascade) - team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) + id Int @id @default(autoincrement()) + uuid String @unique(map: "memberships_uuid_unique") @default(uuid()) @db.Uuid + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) + updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6) + personId Int @map("person_id") + teamId Int @map("team_id") + committeeRole CommitteeRole? @map("committee_role") + position MembershipPosition + person Person @relation(fields: [personId], references: [id], onDelete: Cascade) + team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) @@unique([personId, teamId], map: "memberships_person_id_team_id_key") @@index([uuid], map: "memberships_uuid") @@ -166,20 +167,19 @@ model Membership { } model Person { - id Int @id @default(autoincrement()) - uuid String @unique(map: "people_uuid_unique") @default(uuid()) @db.Uuid - createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) - updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6) - name String? - email String @unique(map: "people_email_unique") - linkblue String? @unique(map: "people_linkblue_unique") - committeeRole CommitteeRole? @map("committee_role") - committeeName CommitteeName? @map("committee_name") - authIdPairs AuthIdPair[] - devices Device[] - memberships Membership[] - pointEntries PointEntry[] - ownedFiles File[] + id Int @id @default(autoincrement()) + uuid String @unique(map: "people_uuid_unique") @default(uuid()) @db.Uuid + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) + updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6) + name String? + email String @unique(map: "people_email_unique") + linkblue String? @unique(map: "people_linkblue_unique") + authIdPairs AuthIdPair[] + devices Device[] + memberships Membership[] + pointEntries PointEntry[] + ownedFiles File[] + assignedFundraisingEntries FundraisingAssignment[] @@index([uuid], map: "people_uuid") @@map("people") @@ -230,22 +230,46 @@ model PointOpportunity { } model Team { - id Int @id @default(autoincrement()) - uuid String @unique(map: "teams_uuid_unique") @default(uuid()) @db.Uuid - createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) - updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6) - name String - type TeamType - legacyStatus TeamLegacyStatus @map("legacy_status") - marathonYear String @map("marathon_year") @db.Char(4) - persistentIdentifier String? @unique(map: "teams_persistent_identifier_unique") @map("persistent_identifier") - memberships Membership[] - pointEntries PointEntry[] + id Int @id @default(autoincrement()) + uuid String @unique(map: "teams_uuid_unique") @default(uuid()) @db.Uuid + name String + type TeamType + legacyStatus TeamLegacyStatus @map("legacy_status") + marathon Marathon @relation(fields: [marathonId], references: [id], onDelete: Cascade) + marathonId Int @map("marathon_id") + persistentIdentifier String? @map("persistent_identifier") + memberships Membership[] + pointEntries PointEntry[] + correspondingCommittee Committee? @relation(fields: [correspondingCommitteeId], references: [id]) + correspondingCommitteeId Int? @map("committee_id") + dbFundsTeam DBFundsTeam? @relation(fields: [dbFundsTeamId], references: [id]) + dbFundsTeamId Int? @map("db_funds_team_id") + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) + updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6) + + @@unique([marathonId, persistentIdentifier], map: "teams_marathon_id_persistent_identifier_key") + @@unique([marathonId, correspondingCommitteeId], map: "teams_marathon_id_committee_id_key") @@index([uuid], map: "teams_uuid") @@map("teams") } +model Committee { + id Int @id @default(autoincrement()) + uuid String @unique(map: "committees_uuid_unique") @default(uuid()) @db.Uuid + identifier CommitteeName @unique @map("identifier") + correspondingTeams Team[] + parentCommittee Committee? @relation(fields: [parentCommitteeId], references: [id], name: "parent_committee") + childCommittees Committee[] @relation("parent_committee") + parentCommitteeId Int? @map("parent_committee_id") + + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) + updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6) + + @@index([uuid], map: "committees_uuid") + @@map("committees") +} + view TeamsWithTotalPoints { id Int @id @unique uuid String @unique @db.Uuid @@ -253,7 +277,8 @@ view TeamsWithTotalPoints { type TeamType legacyStatus TeamLegacyStatus @map("legacystatus") persistentIdentifier String? @unique @map("persistentidentifier") - marathonYear String @map("marathonyear") @db.Char(4) + marathon Marathon @relation(fields: [marathonId], references: [id]) + marathonId Int @map("marathonid") createdAt DateTime @map("createdat") @db.Timestamptz(6) updatedAt DateTime @map("updatedat") @db.Timestamptz(6) totalPoints BigInt @map("totalpoints") @@ -316,15 +341,17 @@ model NotificationDelivery { } model Marathon { - id Int @id @default(autoincrement()) - uuid String @unique(map: "marathons_uuid_unique") @default(uuid()) @db.Uuid - createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) - updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6) - // Same format as Team.marathonYear - year String @unique - startDate DateTime @map("start_date") @db.Timestamptz(6) - endDate DateTime @map("end_date") @db.Timestamptz(6) - hours MarathonHour[] + id Int @id @default(autoincrement()) + uuid String @unique(map: "marathons_uuid_unique") @default(uuid()) @db.Uuid + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) + updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6) + year String @unique + startDate DateTime? @map("start_date") @db.Timestamptz(6) + endDate DateTime? @map("end_date") @db.Timestamptz(6) + hours MarathonHour[] + teams Team[] + teamsWithTotalPoints TeamsWithTotalPoints[] + dbFundsTeams DBFundsTeam[] @@index([uuid], map: "marathons_uuid") @@map("marathons") @@ -375,6 +402,93 @@ model FeedItem { @@map("feed_items") } +// This table is kept in sync with the DBFunds API and should not be modified +model DBFundsTeam { + // Unrelated to the 'id' field in the DBFunds API + id Int @id @default(autoincrement()) + // The team's DbNum from the DBFunds API + dbNum Int @map("db_num") + // The team's name from the DBFunds API + name String + // The team's total fundraising amount from the DBFunds API + totalAmount Float @map("total_amount") + // Whether the team is active in DBFunds + active Boolean + // All fundraising entries for the team + fundraisingEntries DBFundsFundraisingEntry[] + + // The marathon the team is associated with + marathon Marathon? @relation(fields: [marathonId], references: [id]) + marathonId Int? @map("marathon_id") + + // The corresponding teams in our database + teams Team[] + + @@unique([dbNum, marathonId], map: "db_funds_teams_db_num_marathon_id_key") + @@map("db_funds_teams") +} + +// This table is kept in sync with the DBFunds API and should not be modified +model DBFundsFundraisingEntry { + // Unrelated to the 'id' field in the DBFunds API + id Int @id @default(autoincrement()) + // The amount of the entry + amount Decimal + // Who made the donation + donatedBy String? @map("donated_by") + // Who the donation was made for + donatedTo String? @map("donated_to") + // The date of the donation + date DateTime + // The team's DbNum from the DBFunds API + dbFundsTeamId Int @map("team_db_num") + dbFundsTeam DBFundsTeam @relation(fields: [dbFundsTeamId], references: [id], onDelete: Cascade) + + // The corresponding fundraising entry in our database + fundraisingEntry FundraisingEntry? + + @@unique([donatedTo, donatedBy, date]) + @@map("db_funds_team_entries") +} + +model FundraisingEntry { + id Int @id @default(autoincrement()) + uuid String @unique(map: "fundraising_entries_uuid_unique") @default(uuid()) @db.Uuid + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) + updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6) + + dbFundsEntry DBFundsFundraisingEntry @relation(fields: [dbFundsEntryId], references: [id], onDelete: Cascade) + dbFundsEntryId Int @unique @map("db_funds_entry_id") + + assignments FundraisingAssignment[] + + @@index([uuid], map: "fundraising_entries_uuid") + @@map("fundraising_entries") +} + +model FundraisingAssignment { + id Int @id @default(autoincrement()) + uuid String @unique(map: "fundraising_assignments_uuid_unique") @default(uuid()) @db.Uuid + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) + updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6) + amount Decimal + personId Int @map("person_id") + person Person @relation(fields: [personId], references: [id], onDelete: Cascade) + parentEntry FundraisingEntry @relation(fields: [fundraisingId], references: [id], onDelete: Cascade) + fundraisingId Int @map("fundraising_id") + + @@unique([fundraisingId, personId], map: "fundraising_assignments_fundraising_id_person_id_key") + @@index([uuid], map: "fundraising_assignments_uuid") + @@map("fundraising_assignments") +} + +model JobState { + jobName String @id @map("job_name") + lastRun DateTime @map("last_run") @db.Timestamptz(6) + + @@map("job_states") +} + // Potential errors after sending a notification to Expo (see https://docs.expo.dev/push-notifications/sending-notifications/#individual-errors) enum NotificationError { // The device cannot receive push notifications anymore and you should stop sending messages to the corresponding Expo push token. @@ -410,6 +524,7 @@ enum CommitteeName { corporateCommittee miniMarathonsCommittee viceCommittee + overallCommittee @@map("enum_people_committee_name") } diff --git a/packages/server/src/environment.ts b/packages/server/src/environment.ts index 26eff440..17ec833e 100644 --- a/packages/server/src/environment.ts +++ b/packages/server/src/environment.ts @@ -3,143 +3,138 @@ import path, { isAbsolute } from "path"; import dotenv from "dotenv"; import { Expo } from "expo-server-sdk"; -import { Container } from "typedi"; +import { Container, Token } from "typedi"; -import type { SyslogLevels } from "./lib/logging/standardLogging.js"; +import type { SyslogLevels } from "#logging/standardLogging.js"; +import { readFile } from "fs/promises"; dotenv.config(); -// Core env -export const isDevelopment = process.env.NODE_ENV === "development"; -export const isProduction = process.env.NODE_ENV === "production"; -export const nodeEnvironment = process.env.NODE_ENV || "development"; - -export const loggingLevel: SyslogLevels = - (process.env.LOGGING_LEVEL as SyslogLevels | undefined) ?? - (isDevelopment ? "debug" : "notice"); - -// Port, Host, and Protocol -let applicationPort: number = 8000; -if (process.env.APPLICATION_PORT) { - const envApplicationPort = Number.parseInt(process.env.APPLICATION_PORT, 10); - if (Number.isNaN(envApplicationPort)) { - throw new TypeError("Env variable 'APPLICATION_PORT' is not a number"); +async function getEnv( + name: string, + def?: undefined +): Promise; +async function getEnv(name: string, def: string | null): Promise; +async function getEnv( + name: string, + def?: string | null +): Promise { + let value; + if (process.env[`${name}_FILE`]) { + try { + value = await readFile(process.env[`${name}_FILE`]!, "utf-8"); + } catch { + value = process.env[name]; + } + } else { + const lowercaseName = name.toLowerCase(); + try { + value = await readFile(`/run/secrets/${lowercaseName}`, "utf-8"); + } catch { + value = process.env[name]; + } } - if (envApplicationPort < 0 || envApplicationPort > 65_535) { - throw new RangeError( - "Env variable 'APPLICATION_PORT' is not a valid port number" - ); + + if (!value) { + if (def === null) { + throw new Error(`Env variable '${name}' is not set`); + } + if (def !== undefined) { + return def; + } } - applicationPort = envApplicationPort; -} -export { applicationPort }; -export const applicationHost = process.env.APPLICATION_HOST || "localhost"; -export const applicationProtocol = process.env.APPLICATION_PROTOCOL || "http"; -// Secrets -const { COOKIE_SECRET, JWT_SECRET, ASSET_PATH } = process.env; -if (!JWT_SECRET) { - throw new Error("JWT_SECRET is not set"); + return value; } -if (!COOKIE_SECRET) { - throw new Error("COOKIE_SECRET is not set"); -} -export const cookieSecret = COOKIE_SECRET; -export const jwtSecret = JWT_SECRET; -// System Paths -export const assetPath = ASSET_PATH; +export const isDevelopment = process.env.NODE_ENV === "development"; -// Database -const { DB_HOST, DB_PORT, DB_UNAME, DB_PWD, DB_NAME } = process.env; -if (!DB_HOST) { - throw new Error("DB_HOST is not set"); -} -if (!DB_PORT) { - throw new Error("DB_PORT is not set"); -} -if (!DB_UNAME) { - throw new Error("DB_UNAME is not set"); -} -if (!DB_PWD) { - throw new Error("DB_PWD is not set"); +const LOGGING_LEVEL = getEnv( + "LOGGING_LEVEL", + isDevelopment ? "debug" : "notice" +); +const APPLICATION_PORT = getEnv("APPLICATION_PORT", "8000"); +const APPLICATION_HOST = getEnv("APPLICATION_HOST", "localhost"); +const COOKIE_SECRET = getEnv("COOKIE_SECRET", null); +const JWT_SECRET = getEnv("JWT_SECRET", null); +const MS_OIDC_URL = getEnv("MS_OIDC_URL", null); +const MS_CLIENT_ID = getEnv("MS_CLIENT_ID", null); +const MS_CLIENT_SECRET = getEnv("MS_CLIENT_SECRET", null); +const EXPO_ACCESS_TOKEN = getEnv("EXPO_ACCESS_TOKEN", null); +const DBFUNDS_API_KEY = getEnv("DBFUNDS_API_KEY", null); +const DBFUNDS_API_ORIGIN = getEnv("DBFUNDS_API_ORIGIN", null); +const MAX_FILE_SIZE = getEnv("MAX_FILE_SIZE", null); +const SERVE_PATH = getEnv("SERVE_PATH", null); +const UPLOAD_PATH = getEnv("UPLOAD_PATH", null); +const SERVE_ORIGIN = getEnv("SERVE_ORIGIN", null); +const OVERRIDE_AUTH = getEnv("OVERRIDE_AUTH", null); +const LOG_DIR = getEnv("LOG_DIR", null); + +// Core env +export const loggingLevel: SyslogLevels = (await LOGGING_LEVEL) as SyslogLevels; + +// Port, Host, and Protocol +export const applicationPort = Number.parseInt(await APPLICATION_PORT, 10); +if (Number.isNaN(applicationPort)) { + throw new TypeError("Env variable 'APPLICATION_PORT' is not a number"); } -if (!DB_NAME) { - throw new Error("DB_NAME is not set"); +if (applicationPort < 0 || applicationPort > 65_535) { + throw new RangeError( + "Env variable 'APPLICATION_PORT' is not a valid port number" + ); } -export const databaseHost = DB_HOST; -export const databasePort = DB_PORT; -export const databaseUsername = DB_UNAME; -export const databasePassword = DB_PWD; -export const databaseName = DB_NAME; -// This check is used to try and prevent any chance of resetting the production database -// Obviously not foolproof, but it's better than nothing -export const isDatabaseLocal = databaseHost === "localhost" && isDevelopment; +export const applicationHost = await APPLICATION_HOST; + +// Secrets +export const cookieSecret = await COOKIE_SECRET; +export const jwtSecret = await JWT_SECRET; // MS Auth -const { MS_OIDC_URL, MS_CLIENT_ID, MS_CLIENT_SECRET } = process.env; -if (!MS_OIDC_URL) { - throw new Error("MS_OIDC_URL is not set"); -} -if (!MS_CLIENT_ID) { - throw new Error("MS_CLIENT_ID is not set"); -} -if (!MS_CLIENT_SECRET) { - throw new Error("MS_CLIENT_SECRET is not set"); -} -export const msOidcUrl = MS_OIDC_URL; -export const msClientId = MS_CLIENT_ID; -export const msClientSecret = MS_CLIENT_SECRET; +export const msOidcUrl = await MS_OIDC_URL; +export const msClientId = await MS_CLIENT_ID; +export const msClientSecret = await MS_CLIENT_SECRET; // Expo access token -const { EXPO_ACCESS_TOKEN } = process.env; -if (!EXPO_ACCESS_TOKEN) { - throw new Error("EXPO_ACCESS_TOKEN is not set"); -} -export const expoAccessToken = EXPO_ACCESS_TOKEN; +export const expoAccessToken = await EXPO_ACCESS_TOKEN; Container.set(Expo, new Expo({ accessToken: expoAccessToken })); +// DBFunds +export const dbFundsApiKeyToken = new Token("DBFUNDS_API_KEY"); +export const dbFundsApiOriginToken = new Token("DBFUNDS_API_ORIGIN"); + +Container.set(dbFundsApiKeyToken, await DBFUNDS_API_KEY); +Container.set(dbFundsApiOriginToken, await DBFUNDS_API_ORIGIN); + // File upload settings -const { MAX_FILE_SIZE, SERVE_PATH, UPLOAD_PATH, SERVE_ORIGIN } = process.env; -if (!MAX_FILE_SIZE) { - throw new Error("MAX_FILE_SIZE is not set"); -} -if (!SERVE_PATH) { - throw new Error("SERVE_PATH is not set"); -} -if (!UPLOAD_PATH) { - throw new Error("UPLOAD_PATH is not set"); -} -if (!SERVE_ORIGIN) { - throw new Error("SERVE_ORIGIN is not set"); -} +export const serveOrigin = await SERVE_ORIGIN; try { - new URL(SERVE_ORIGIN); + new URL(serveOrigin); } catch { throw new Error("SERVE_ORIGIN is not a valid URL"); } +export const servePath = await SERVE_PATH; +export const uploadPath = await UPLOAD_PATH; -const maxFileSize = Number.parseInt(MAX_FILE_SIZE, 10); +export const maxFileSize = Number.parseInt(await MAX_FILE_SIZE, 10); if (Number.isNaN(maxFileSize)) { throw new TypeError("MAX_FILE_SIZE is not a number"); } if (maxFileSize < 10) { throw new RangeError("MAX_FILE_SIZE must be at least 10 (MB)"); } -export { maxFileSize }; -if (!isAbsolute(SERVE_PATH) || !isAbsolute(UPLOAD_PATH)) { +if (!isAbsolute(servePath) || !isAbsolute(uploadPath)) { throw new Error("SERVE_PATH and UPLOAD_PATH must be absolute paths"); } -if (statSync(SERVE_PATH).isFile() || statSync(UPLOAD_PATH).isFile()) { +if (statSync(servePath).isFile() || statSync(uploadPath).isFile()) { throw new Error("SERVE_PATH and UPLOAD_PATH must be directories"); } -let uploadParentPath = UPLOAD_PATH; +let uploadParentPath = uploadPath; let isUploadInServe = false; while (path.dirname(uploadParentPath) !== uploadParentPath) { - if (uploadParentPath === SERVE_PATH) { + if (uploadParentPath === servePath) { isUploadInServe = true; break; } @@ -149,10 +144,12 @@ if (!isUploadInServe) { throw new Error("UPLOAD_PATH must be a subdirectory of SERVE_PATH"); } -export const serveOrigin = SERVE_ORIGIN; -export const servePath = SERVE_PATH; -export const uploadPath = UPLOAD_PATH; - // Disable all authorization checks -const { OVERRIDE_AUTH } = process.env; -export const authorizationOverride = OVERRIDE_AUTH === "THIS IS DANGEROUS"; +export const authorizationOverride = + isDevelopment && (await OVERRIDE_AUTH) === "THIS IS DANGEROUS"; + +// Log directory +export const logDir = await LOG_DIR; +if (!logDir) { + throw new Error("LOG_DIR is not set"); +} diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 646532f1..a02e96d1 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -1,4 +1,4 @@ -import { logger } from "./lib/logging/logger.js"; +import { logger } from "#logging/logger.js"; import "reflect-metadata"; @@ -7,7 +7,7 @@ import "reflect-metadata"; logger.info("DanceBlue Server Starting"); -await import("./environment.js"); +await import("#environment"); logger.info("Loaded environment variables"); await import("./prisma.js"); @@ -37,4 +37,4 @@ logger.info("API started"); logger.info("DanceBlue Server Started"); // Start any manual cron jobs -await import("./jobs/index.js"); +await import("#jobs/index.js"); diff --git a/packages/server/src/jobs/NotificationScheduler.ts b/packages/server/src/jobs/NotificationScheduler.ts index 89703ef6..abc2716f 100644 --- a/packages/server/src/jobs/NotificationScheduler.ts +++ b/packages/server/src/jobs/NotificationScheduler.ts @@ -2,10 +2,10 @@ import type { Notification } from "@prisma/client"; import Cron, { scheduledJobs } from "croner"; import { Inject, Service } from "typedi"; -import { logger } from "../lib/logging/standardLogging.js"; -import { ExpoNotificationProvider } from "../lib/notification/ExpoNotificationProvider.js"; -import * as NotificationProviderJs from "../lib/notification/NotificationProvider.js"; -import { NotificationRepository } from "../repositories/notification/NotificationRepository.js"; +import { logger } from "#logging/standardLogging.js"; +import { ExpoNotificationProvider } from "#notification/ExpoNotificationProvider.js"; +import * as NotificationProviderJs from "#notification/NotificationProvider.js"; +import { NotificationRepository } from "#repositories/notification/NotificationRepository.js"; function scheduleId(notificationUuid: string) { return `scheduled-notification:${notificationUuid}`; diff --git a/packages/server/src/jobs/fetchPushReceipts.ts b/packages/server/src/jobs/fetchPushReceipts.ts index 89af1d51..3cc9d727 100644 --- a/packages/server/src/jobs/fetchPushReceipts.ts +++ b/packages/server/src/jobs/fetchPushReceipts.ts @@ -1,13 +1,16 @@ import Cron from "croner"; import { Container } from "typedi"; -import { logger } from "../lib/logging/standardLogging.js"; -import { ExpoPushReceiptHandler } from "../lib/notification/ExpoPushReceiptHandler.js"; +import { logger } from "#logging/standardLogging.js"; +import { ExpoPushReceiptHandler } from "#notification/ExpoPushReceiptHandler.js"; +import { JobStateRepository } from "#repositories/JobState.js"; +const jobStateRepository = Container.get(JobStateRepository); export const fetchPushReceipts = new Cron( "0 */8 * * * *", { name: "fetch-push-receipts", + paused: true, catch: (error) => { console.error("Failed to fetch push receipts", error); }, @@ -17,8 +20,14 @@ export const fetchPushReceipts = new Cron( logger.info("Fetching push receipts"); const expoPushReceiptHandler = Container.get(ExpoPushReceiptHandler); await expoPushReceiptHandler.handlePushReceipts(); + + await jobStateRepository.logCompletedJob(fetchPushReceipts); } catch (error) { console.error("Failed to fetch push receipts", { error }); } } ); + +fetchPushReceipts.options.startAt = + await jobStateRepository.getNextJobDate(fetchPushReceipts); +fetchPushReceipts.resume(); diff --git a/packages/server/src/jobs/garbageCollectLogins.ts b/packages/server/src/jobs/garbageCollectLogins.ts index 82d03176..08f37bae 100644 --- a/packages/server/src/jobs/garbageCollectLogins.ts +++ b/packages/server/src/jobs/garbageCollectLogins.ts @@ -1,13 +1,16 @@ import Cron from "croner"; import { Container } from "typedi"; -import { logger } from "../lib/logging/standardLogging.js"; -import { LoginFlowSessionRepository } from "../resolvers/LoginFlowSession.js"; +import { logger } from "#logging/standardLogging.js"; +import { JobStateRepository } from "#repositories/JobState.js"; +import { LoginFlowSessionRepository } from "#repositories/LoginFlowSession.js"; +const jobStateRepository = Container.get(JobStateRepository); export const garbageCollectLoginFlowSessions = new Cron( "0 0 */6 * * *", { name: "garbage-collect-login-flow-sessions", + paused: true, catch: (error) => { console.error("Failed to fetch push receipts", error); }, @@ -19,8 +22,14 @@ export const garbageCollectLoginFlowSessions = new Cron( LoginFlowSessionRepository ); await loginFlowSessionRepository.gcOldLoginFlows(); + + await jobStateRepository.logCompletedJob(garbageCollectLoginFlowSessions); } catch (error) { console.error("Failed to garbage collect old login flows", { error }); } } ); + +garbageCollectLoginFlowSessions.options.startAt = + await jobStateRepository.getNextJobDate(garbageCollectLoginFlowSessions); +garbageCollectLoginFlowSessions.resume(); diff --git a/packages/server/src/jobs/index.ts b/packages/server/src/jobs/index.ts index 7fe84d11..c0e14d80 100644 --- a/packages/server/src/jobs/index.ts +++ b/packages/server/src/jobs/index.ts @@ -1,10 +1,9 @@ import { Container } from "typedi"; import { NotificationScheduler } from "./NotificationScheduler.js"; -import { fetchPushReceipts } from "./fetchPushReceipts.js"; -import { garbageCollectLoginFlowSessions } from "./garbageCollectLogins.js"; +import "./fetchPushReceipts.js"; +import "./garbageCollectLogins.js"; +import "./syncDbFunds.js"; -await fetchPushReceipts.trigger(); -await garbageCollectLoginFlowSessions.trigger(); const scheduler = Container.get(NotificationScheduler); scheduler.ensureNotificationScheduler(); diff --git a/packages/server/src/jobs/syncDbFunds.ts b/packages/server/src/jobs/syncDbFunds.ts new file mode 100644 index 00000000..a9b14a14 --- /dev/null +++ b/packages/server/src/jobs/syncDbFunds.ts @@ -0,0 +1,104 @@ +import type { MarathonYearString } from "@ukdanceblue/common"; +import type { NotFoundError } from "@ukdanceblue/common/error"; +import { CompositeError, toBasicError } from "@ukdanceblue/common/error"; +import Cron from "croner"; +import { Err, None, Ok, type Result } from "ts-results-es"; +import { Container } from "typedi"; +const jobStateRepository = Container.get(JobStateRepository); + +import type { PrismaError } from "#error/prisma.js"; +import { + DBFundsFundraisingProvider, + type DBFundsFundraisingProviderError, +} from "#lib/fundraising/DbFundsProvider.js"; +import { logger } from "#logging/standardLogging.js"; +import { JobStateRepository } from "#repositories/JobState.js"; +import { DBFundsRepository } from "#repositories/fundraising/DBFundsRepository.js"; +import { MarathonRepository } from "#repositories/marathon/MarathonRepository.js"; + +type DoSyncError = + | NotFoundError + | PrismaError + | DBFundsFundraisingProviderError; +async function doSync(): Promise< + Result> +> { + const marathonRepository = Container.get(MarathonRepository); + const fundraisingRepository = Container.get(DBFundsRepository); + const fundraisingProvider = Container.get(DBFundsFundraisingProvider); + const activeMarathon = await marathonRepository.findActiveMarathon(); + logger.trace("Found current marathon for DBFunds sync", activeMarathon); + if (activeMarathon.isErr()) { + return activeMarathon; + } + const teams = await fundraisingProvider.getTeams( + activeMarathon.value.year as MarathonYearString + ); + if (teams.isErr()) { + return Err(teams.error); + } + logger.trace("Got teams for DBFunds sync", { teamCount: teams.value.length }); + + const promises = teams.value.map(async (team) => { + const entries = await fundraisingProvider.getTeamEntries( + activeMarathon.value.year as MarathonYearString, + team.identifier + ); + if (entries.isErr()) { + return Err(entries.error); + } + return fundraisingRepository.overwriteTeamForFiscalYear( + { + active: team.active, + dbNum: team.identifier, + name: team.name, + total: team.total, + }, + { id: activeMarathon.value.id }, + entries.value + ); + }); + + const results = await Promise.allSettled(promises); + + const errors: DoSyncError[] = []; + for (const result of results) { + if (result.status === "rejected") { + errors.push(toBasicError(result.reason)); + } else if (result.value.isErr()) { + if (result.value.error instanceof CompositeError) { + errors.push(...result.value.error.errors); + } else { + errors.push(result.value.error); + } + } + } + + return errors.length > 0 ? Err(new CompositeError(errors)) : Ok(None); +} + +export const syncDbFunds = new Cron( + "0 */31 * * * *", + { + name: "sync-db-funds", + paused: true, + catch: (error) => { + console.error("Failed to sync DBFunds", error); + }, + }, + async () => { + logger.info("Syncing DBFunds"); + const result = await doSync(); + + if (result.isErr()) { + logger.error("Failed to sync DBFunds", result.error); + } else { + logger.info("DBFunds sync complete"); + await jobStateRepository.logCompletedJob(syncDbFunds); + } + } +); + +syncDbFunds.options.startAt = + await jobStateRepository.getNextJobDate(syncDbFunds); +syncDbFunds.resume(); diff --git a/packages/server/src/lib/InvariantError.ts b/packages/server/src/lib/InvariantError.ts deleted file mode 100644 index 445a383f..00000000 --- a/packages/server/src/lib/InvariantError.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { logger } from "./logging/logger.js"; - -export class InvariantError extends Error { - readonly name: string = "InvariantError"; - - constructor(message: string) { - super(message); - logger.error("Invariant Violation", message); - } -} diff --git a/packages/server/src/lib/auth/findPersonForLogin.ts b/packages/server/src/lib/auth/findPersonForLogin.ts index 990b6c71..7d95ab40 100644 --- a/packages/server/src/lib/auth/findPersonForLogin.ts +++ b/packages/server/src/lib/auth/findPersonForLogin.ts @@ -1,8 +1,8 @@ import type { AuthSource, Prisma, PrismaClient } from "@prisma/client"; -import type { RoleResource } from "@ukdanceblue/common"; +import type { DbRole } from "@ukdanceblue/common"; import { MembershipPositionType } from "@ukdanceblue/common"; -import { logger } from "../logging/logger.js"; +import { logger } from "#logging/logger.js"; // TODO: rework this whole thing, it's pretty boated and confusing @@ -24,17 +24,17 @@ const include = { * Searches the database for a user with the given auth IDs or user data, or creates a new user if none is found * * @param authIds The auth IDs to search for - * @param userData The user data to fall back on if no user is found with the given auth IDs + * @param userInfo The user data to fall back on if no user is found with the given auth IDs */ export async function findPersonForLogin( client: PrismaClient, authIds: [AuthSource, string][], - userData: { + userInfo: { uuid?: string | null; email?: string | null; linkblue?: string | null; name?: string | null; - role?: RoleResource | null; + dbRole?: DbRole | null; }, memberOf?: (string | number)[], captainOf?: (string | number)[] @@ -45,9 +45,9 @@ export async function findPersonForLogin( let created = false; // If we have a UUID, search for an existing person with that UUID - if (userData.uuid) { + if (userInfo.uuid) { currentPerson = await client.person.findUnique({ - where: { uuid: userData.uuid }, + where: { uuid: userInfo.uuid }, include, }); if (currentPerson) { @@ -72,18 +72,18 @@ export async function findPersonForLogin( } // Search for an existing person with the given unique user data - if (!currentPerson && userData.linkblue) { + if (!currentPerson && userInfo.linkblue) { currentPerson = await client.person.findUnique({ - where: { linkblue: userData.linkblue }, + where: { linkblue: userInfo.linkblue }, include, }); if (currentPerson) { logger.trace(`Found person by linkblue: ${currentPerson.uuid}`); } } - if (!currentPerson && userData.email) { + if (!currentPerson && userInfo.email) { currentPerson = await client.person.findUnique({ - where: { email: userData.email }, + where: { email: userInfo.email }, include, }); if (currentPerson) { @@ -93,36 +93,9 @@ export async function findPersonForLogin( if (!currentPerson) { logger.trace("No person found, creating new person"); - if (!userData.email) { + if (!userInfo.email) { throw new Error("No email provided for new user"); } - // currentPerson = PersonModel.build({ - // authIds, - // email: userData.email, - // }); - - // const { name, linkblue, role } = userData; - - // if (name) { - // currentPerson.name = name; - // } - // if (linkblue) { - // currentPerson.linkblue = linkblue; - // } - // if (role) { - // currentPerson.committeeRole = role.committeeRole; - // currentPerson.committeeName = role.committeeIdentifier; - // } - - // const savedPerson = await currentPerson.save({ - // transaction: t, - // returning: ["id"], - // }); - - // const { uuid: finalPersonUuid } = await savedPerson.save({ - // transaction: t, - // returning: ["uuid"], - // }); const memberOfIds = await Promise.all( memberOf?.map(async (teamId) => { @@ -159,11 +132,9 @@ export async function findPersonForLogin( })), }, }, - email: userData.email, - name: userData.name ?? null, - linkblue: userData.linkblue ?? null, - committeeRole: userData.role?.committeeRole ?? null, - committeeName: userData.role?.committeeIdentifier ?? null, + email: userInfo.email, + name: userInfo.name ?? null, + linkblue: userInfo.linkblue ?? null, memberships: { createMany: { data: [ @@ -204,5 +175,5 @@ export async function findPersonForLogin( created = true; } - return [currentPerson, created] as const; + return { currentPerson, created } as const; } diff --git a/packages/server/src/lib/auth/index.ts b/packages/server/src/lib/auth/index.ts index 6d80d7d6..170ad928 100644 --- a/packages/server/src/lib/auth/index.ts +++ b/packages/server/src/lib/auth/index.ts @@ -1,69 +1,9 @@ -import type { Authorization, JwtPayload, UserData } from "@ukdanceblue/common"; -import { - AccessLevel, - AuthSource, - CommitteeRole, - DbRole, - defaultAuthorization, -} from "@ukdanceblue/common"; -import type { Request } from "express"; +import type { JwtPayload, UserData } from "@ukdanceblue/common"; +import { AuthSource } from "@ukdanceblue/common"; import jsonwebtoken from "jsonwebtoken"; +import type { Request } from "koa"; -import { jwtSecret } from "../../environment.js"; - -/** - * Compares an authorization object to a minimum authorization object - * and returns true if the authorization object satisfies the minimum - * authorization object (i.e. the authorization object has at least - * the same authorization as the minimum authorization object) - * - * @param minAuth The minimum authorization object - * @param auth The authorization object to compare to the minimum authorization object - * @return True if the authorization object satisfies the minimum authorization object - * and false otherwise - */ -export function isMinAuthSatisfied( - minAuth: Authorization, - auth: Authorization -): boolean { - if (auth.accessLevel < minAuth.accessLevel) { - return false; - } - if (minAuth.committeeRole && auth.committeeRole !== minAuth.committeeRole) { - return false; - } - if ( - minAuth.committeeIdentifier && - auth.committeeIdentifier !== minAuth.committeeIdentifier - ) { - return false; - } - return true; -} - -export const simpleAuthorizations: Record = { - [AccessLevel.None]: defaultAuthorization, - [AccessLevel.Public]: { - dbRole: DbRole.Public, - accessLevel: AccessLevel.Public, - }, - [AccessLevel.UKY]: { - dbRole: DbRole.UKY, - accessLevel: AccessLevel.UKY, - }, - [AccessLevel.Committee]: { - dbRole: DbRole.Committee, - accessLevel: AccessLevel.Committee, - }, - [AccessLevel.CommitteeChairOrCoordinator]: { - dbRole: DbRole.Committee, - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - [AccessLevel.Admin]: { - dbRole: DbRole.Committee, - accessLevel: AccessLevel.Admin, - }, -}; +import { jwtSecret } from "#environment"; const jwtIssuer = "https://app.danceblue.org"; @@ -75,16 +15,7 @@ export function isValidJwtPayload(payload: unknown): payload is JwtPayload { if (typeof payload !== "object" || payload === null) { return false; } - const { - sub, - auth_source, - dbRole, - committee_role, - committee, - access_level, - team_ids, - captain_of_team_ids, - } = payload as Record; + const { sub, auth_source } = payload as Record; if (sub !== undefined && typeof sub !== "string") { return false; } @@ -95,37 +26,6 @@ export function isValidJwtPayload(payload: unknown): payload is JwtPayload { ) { return false; } - if ( - typeof dbRole !== "string" || - !Object.values(DbRole).includes(dbRole as DbRole) - ) { - return false; - } - if ( - committee_role !== undefined && - (typeof committee_role !== "string" || - !Object.values(CommitteeRole).includes(committee_role as CommitteeRole)) - ) { - return false; - } - if (committee !== undefined && typeof committee !== "string") { - return false; - } - if ( - typeof access_level !== "number" || - !Object.values(AccessLevel).includes(access_level as AccessLevel) - ) { - return false; - } - if (team_ids !== undefined && !Array.isArray(team_ids)) { - return false; - } - if ( - captain_of_team_ids !== undefined && - !Array.isArray(captain_of_team_ids) - ) { - return false; - } return true; } @@ -143,25 +43,11 @@ export function makeUserJwt(user: UserData): string { (user.authSource as string) === "UkyLinkblue" ? AuthSource.LinkBlue : user.authSource, - dbRole: user.auth.dbRole, - access_level: user.auth.accessLevel, }; if (user.userId) { payload.sub = user.userId; } - if (user.auth.committeeRole) { - payload.committee_role = user.auth.committeeRole; - } - if (user.auth.committeeIdentifier) { - payload.committee = user.auth.committeeIdentifier; - } - if (user.teamIds) { - payload.team_ids = user.teamIds; - } - if (user.captainOfTeamIds) { - payload.captain_of_team_ids = user.captainOfTeamIds; - } return jsonwebtoken.sign(payload, jwtSecret, { issuer: jwtIssuer, @@ -188,38 +74,13 @@ export function parseUserJwt(token: string): UserData { throw new Error("Invalid JWT payload"); } - if ( - payload.auth_source === AuthSource.Anonymous && - payload.access_level > AccessLevel.Public - ) { - throw new jsonwebtoken.JsonWebTokenError( - "Anonymous users cannot have access levels greater than public" - ); - } - const userData: UserData = { - auth: { - accessLevel: payload.access_level, - dbRole: payload.dbRole, - }, authSource: payload.auth_source, }; if (payload.sub) { userData.userId = payload.sub; } - if (payload.committee_role) { - userData.auth.committeeRole = payload.committee_role; - } - if (payload.committee) { - userData.auth.committeeIdentifier = payload.committee; - } - if (payload.team_ids) { - userData.teamIds = payload.team_ids; - } - if (payload.captain_of_team_ids) { - userData.captainOfTeamIds = payload.captain_of_team_ids; - } return userData; } @@ -236,7 +97,7 @@ export function tokenFromRequest( try { // Prefer cookie let jsonWebToken: string | undefined = undefined; - const cookies = req.cookies as unknown; + const cookies = req.ctx.cookies as unknown; if ( typeof cookies === "object" && cookies && diff --git a/packages/server/src/lib/auth/role.ts b/packages/server/src/lib/auth/role.ts deleted file mode 100644 index 45a59a18..00000000 --- a/packages/server/src/lib/auth/role.ts +++ /dev/null @@ -1 +0,0 @@ -export { roleToAccessLevel, roleToAuthorization } from "@ukdanceblue/common"; diff --git a/packages/server/src/lib/demo.ts b/packages/server/src/lib/demo.ts index 3f22faf1..3eea2a64 100644 --- a/packages/server/src/lib/demo.ts +++ b/packages/server/src/lib/demo.ts @@ -1,6 +1,6 @@ import { Container } from "typedi"; -import { PersonRepository } from "../repositories/person/PersonRepository.js"; +import { PersonRepository } from "#repositories/person/PersonRepository.js"; export async function getOrMakeDemoUser() { return Container.get(PersonRepository).getDemoUser(); diff --git a/packages/server/src/lib/error/prisma.ts b/packages/server/src/lib/error/prisma.ts new file mode 100644 index 00000000..4915e1ee --- /dev/null +++ b/packages/server/src/lib/error/prisma.ts @@ -0,0 +1,120 @@ +import { + PrismaClientInitializationError, + PrismaClientKnownRequestError, + PrismaClientRustPanicError, + PrismaClientUnknownRequestError, + PrismaClientValidationError, +} from "@prisma/client/runtime/library"; +import { ConcreteError } from "@ukdanceblue/common/error"; +import type { Option } from "ts-results-es"; +import { None, Some } from "ts-results-es"; + +type RawPrismaError = + | PrismaClientKnownRequestError + | PrismaClientUnknownRequestError + | PrismaClientRustPanicError + | PrismaClientInitializationError + | PrismaClientValidationError; + +const PrismaErrorTag = Symbol("PrismaError"); +type PrismaErrorTag = typeof PrismaErrorTag; +export abstract class PrismaError extends ConcreteError { + readonly error: RawPrismaError; + + constructor(error: RawPrismaError) { + super(); + this.error = error; + } + + get message(): string { + return this.error.message; + } + + get stack(): string | undefined { + return this.error.stack; + } + + get expose(): boolean { + return false; + } + + static get Tag(): PrismaErrorTag { + return PrismaErrorTag; + } + get tag(): PrismaErrorTag { + return PrismaErrorTag; + } +} + +export class PrismaKnownRequestError extends PrismaError { + readonly error: PrismaClientKnownRequestError; + + constructor(error: PrismaClientKnownRequestError) { + super(error); + this.error = error; + } +} + +export class PrismaUnknownRequestError extends PrismaError { + readonly error: PrismaClientUnknownRequestError; + + constructor(error: PrismaClientUnknownRequestError) { + super(error); + this.error = error; + } +} + +export class PrismaRustPanicError extends PrismaError { + readonly error: PrismaClientRustPanicError; + + constructor(error: PrismaClientRustPanicError) { + super(error); + this.error = error; + } +} + +export class PrismaInitializationError extends PrismaError { + readonly error: PrismaClientInitializationError; + + constructor(error: PrismaClientInitializationError) { + super(error); + this.error = error; + } +} + +export class PrismaValidationError extends PrismaError { + readonly error: PrismaClientValidationError; + + constructor(error: PrismaClientValidationError) { + super(error); + this.error = error; + } +} + +export type SomePrismaError = + | PrismaInitializationError + | PrismaKnownRequestError + | PrismaRustPanicError + | PrismaUnknownRequestError + | PrismaValidationError; + +export function toPrismaError(error: RawPrismaError): Some; +export function toPrismaError(error: unknown): Option; +export function toPrismaError(error: unknown): Option { + if (error instanceof PrismaClientInitializationError) { + return Some(new PrismaInitializationError(error)); + } + if (error instanceof PrismaClientKnownRequestError) { + return Some(new PrismaKnownRequestError(error)); + } + if (error instanceof PrismaClientRustPanicError) { + return Some(new PrismaRustPanicError(error)); + } + if (error instanceof PrismaClientUnknownRequestError) { + return Some(new PrismaUnknownRequestError(error)); + } + if (error instanceof PrismaClientValidationError) { + return Some(new PrismaValidationError(error)); + } + return None; +} diff --git a/packages/server/src/lib/error/zod.ts b/packages/server/src/lib/error/zod.ts new file mode 100644 index 00000000..b93c57e3 --- /dev/null +++ b/packages/server/src/lib/error/zod.ts @@ -0,0 +1,25 @@ +import { ConcreteError } from "@ukdanceblue/common/error"; +import type { ZodError as RawZodError } from "zod"; + +const ZodErrorTag = Symbol("ZodError"); +type ZodErrorTag = typeof ZodErrorTag; +export class ZodError extends ConcreteError { + readonly error: RawZodError; + constructor(error: RawZodError) { + super(); + this.error = error; + } + get message() { + return this.error.message; + } + get expose() { + return false; + } + + static get Tag() { + return ZodErrorTag; + } + get tag(): ZodErrorTag { + return ZodErrorTag; + } +} diff --git a/packages/server/src/lib/files/FileManager.ts b/packages/server/src/lib/files/FileManager.ts index 6e733bf7..00d1e4fa 100644 --- a/packages/server/src/lib/files/FileManager.ts +++ b/packages/server/src/lib/files/FileManager.ts @@ -3,23 +3,20 @@ import { MIMEType } from "util"; import type { File } from "@prisma/client"; import { Service } from "typedi"; -import { serveOrigin } from "../../environment.js"; -import { FileRepository } from "../../repositories/file/fileRepository.js"; -import { logger } from "../logging/standardLogging.js"; - import { LocalStorageProvider } from "./storage/LocalStorageProvider.js"; import type { StorableFile, StorageProvider, } from "./storage/StorageProvider.js"; import { UnsupportedAccessMethod } from "./storage/StorageProvider.js"; +import { serveOrigin } from "#environment"; +import { logger } from "#logging/standardLogging.js"; +import { FileRepository } from "#repositories/file/fileRepository.js"; const FILE_API = new URL("/api/file/download/", serveOrigin); logger.info(`Serving files from ${FILE_API.href}`); -logger.info(`Serving files from ${FILE_API.href}`); - @Service() export class FileManager { constructor( diff --git a/packages/server/src/lib/files/storage/LocalStorageProvider.ts b/packages/server/src/lib/files/storage/LocalStorageProvider.ts index 45359b00..655202a8 100644 --- a/packages/server/src/lib/files/storage/LocalStorageProvider.ts +++ b/packages/server/src/lib/files/storage/LocalStorageProvider.ts @@ -7,15 +7,14 @@ import type { MIMEType } from "util"; import { DateTime } from "luxon"; import { Service } from "typedi"; -import { servePath, uploadPath } from "../../../environment.js"; -import { logger } from "../../logging/standardLogging.js"; - import type { StorableFile, StorageProvider, UnsupportedAccessMethod, } from "./StorageProvider.js"; import { BaseStorageProvider } from "./StorageProvider.js"; +import { servePath, uploadPath } from "#environment"; +import { logger } from "#logging/standardLogging.js"; /** * Determines if the location a path refers to is within `servePath` diff --git a/packages/server/src/lib/formatError.ts b/packages/server/src/lib/formatError.ts index d69a4fcd..87b8078a 100644 --- a/packages/server/src/lib/formatError.ts +++ b/packages/server/src/lib/formatError.ts @@ -9,17 +9,28 @@ import { UnionValidationError, isErrorCode, } from "@ukdanceblue/common"; +import type { ConcreteError } from "@ukdanceblue/common/error"; import type { GraphQLFormattedError } from "graphql"; import { GraphQLError } from "graphql"; import jwt from "jsonwebtoken"; import type { Writable } from "utility-types"; + export interface DbGraphQLFormattedErrorExtensions extends Omit { internalDetails?: Record; stacktrace?: string[]; } +export class CatchableConcreteError extends Error { + declare cause: ConcreteError; + constructor(error: ConcreteError) { + super(error.message); + this.cause = error; + this.stack = error.stack; + } +} + /** * * @param originalFormattedError @@ -59,7 +70,22 @@ export function formatError( formattedError.extensions.stacktrace = error.stack?.split("\n") ?? []; } - if (error instanceof DetailedError) { + if (error instanceof CatchableConcreteError) { + const { message, detailedMessage, expose, stack } = error.cause; + formattedError.message = message; + if (expose) { + formattedError.extensions.stacktrace = stack?.split("\n") ?? []; + } else { + delete formattedError.extensions.stacktrace; + } + if (detailedMessage !== message) { + if (!formattedError.extensions.internalDetails) { + formattedError.extensions.internalDetails = {}; + } + formattedError.extensions.internalDetails.detailedMessage = + detailedMessage; + } + } else if (error instanceof DetailedError) { formattedError.extensions.code = error.code; if (error.details) { formattedError.extensions.details = error.details; diff --git a/packages/server/src/lib/fundraising/DbFundsProvider.ts b/packages/server/src/lib/fundraising/DbFundsProvider.ts new file mode 100644 index 00000000..d0949ff6 --- /dev/null +++ b/packages/server/src/lib/fundraising/DbFundsProvider.ts @@ -0,0 +1,201 @@ +import type { MarathonYearString } from "@ukdanceblue/common"; +import { + TimeoutError, + BasicError, + toBasicError, + HttpError, + optionOf, + ConcreteResult, +} from "@ukdanceblue/common/error"; +import { DateTime } from "luxon"; +import { Err, Ok } from "ts-results-es"; +import { Inject, Service } from "typedi"; +import { z } from "zod"; + +import type { + FundraisingEntry, + FundraisingProvider, + FundraisingTeam, +} from "./FundraisingProvider.js"; +import { dbFundsApiKeyToken, dbFundsApiOriginToken } from "#environment"; +import { ZodError } from "#error/zod.js"; + +const dbFundsFundraisingTeamSchema = z.object({ + DbNum: z.number().int().nonnegative().describe("The team's dbNum"), + Team: z.string().describe("The name of the team"), + Active: z.boolean().describe("Whether the team is active"), + Total: z + .number() + .describe( + "The amount donated in dollars, with two decimal places for cents" + ) + .nonnegative() + .multipleOf(0.01, "Must be a whole number of cents"), +}); + +// Checks for any variation of "n/a" or "na" (case-insensitive) +const isNA = /^n\/?a$/i; + +const dbFundsFundraisingEntrySchema = z.object({ + dbnum: z + .number() + .describe("The dbNum of the team to which these entries belong") + .int() + .nonnegative(), + name: z + .string() + .describe("The name of the team to which these entries belong"), + entries: z.array( + z.object({ + donatedBy: z + .string() + .describe("The name of the person who donated") + .transform((v) => (isNA.test(v) || v.length === 0 ? null : v)) + .nullable(), + donatedTo: z + .string() + .describe("The name of the person or team who received the donation") + .transform((v) => (isNA.test(v) || v.length === 0 ? null : v)) + .nullable(), + donatedOn: z + .string() + .describe("The date and time the donation was made, in Eastern time") + // Checks for what passes for an ISO date in the DBFunds API + .transform((v) => + DateTime.fromISO(v, { + zone: "America/New_York", + }) + ) + .refine((v) => v.isValid, { + message: "Must be a valid ISO 8601 date", + }), + // NOTE: Currently the API sends the amount as a number of cents (i.e. it sends 100.00 for $1.00) even though the total is + // in dollars (i.e. it sends 1.00 for $1.00). This is a bit inconsistent, but it's not a big deal. We can just convert the + // amount to dollars when we use it and this has already taken long enough to get going that I don't want to spend time + // waiting for DBFunds to fix it. + amount: z + .number() + .describe("The amount donated in cents") + .nonnegative() + .int(), + }) + ), +}); + +function teamTotalPath(year: number | string): string { + return `/api/report/teamtotals/${year}`; +} + +function teamEntriesPath( + dbNum: number | string, + year: number | string +): string { + return `/api/report/teamentries/${dbNum}/${year}`; +} + +export type DBFundsFundraisingProviderError = + | HttpError + | ZodError + | BasicError + | TimeoutError; + +@Service() +export class DBFundsFundraisingProvider implements FundraisingProvider { + constructor( + @Inject(dbFundsApiOriginToken) + private readonly dbFundsApiOrigin: string, + @Inject(dbFundsApiKeyToken) + private readonly dbFundsApiKey: string + ) {} + + private async fetchJson( + path: string | URL + ): Promise> { + let response: Response; + let timeout: ReturnType | undefined = undefined; + try { + const url = new URL(path, this.dbFundsApiOrigin); + const abort = new AbortController(); + timeout = setTimeout(() => { + abort.abort(); + }, 5000); + response = await fetch(url, { + headers: { + "X-AuthToken": this.dbFundsApiKey, + }, + signal: abort.signal, + }); + } catch (error) { + return Err( + error instanceof Error && error.name === "AbortError" + ? new TimeoutError("Fetching data from DbFunds") + : toBasicError(error) + ); + } finally { + clearTimeout(timeout); + } + + if (!response.ok) { + return Err(new HttpError(response.status)); + } + + return Ok(await response.json()); + } + + async getTeams( + marathonYear: MarathonYearString + ): Promise< + ConcreteResult[], DBFundsFundraisingProviderError> + > { + const calendarYear = `20${marathonYear.substring(2)}`; + const path = teamTotalPath(calendarYear); + const result = await this.fetchJson(path); + if (result.isErr()) { + return result; + } + + const teams = dbFundsFundraisingTeamSchema.array().safeParse(result.value); + if (teams.success) { + return Ok( + teams.data.map((team) => ({ + name: team.Team, + active: team.Active, + identifier: team.DbNum, + total: team.Total, + })) + ); + } else { + return Err(new ZodError(teams.error)); + } + } + async getTeamEntries( + marathonYear: MarathonYearString, + dbNum: number + ): Promise< + ConcreteResult + > { + const calendarYear = `20${marathonYear.substring(2)}`; + const path = teamEntriesPath(dbNum, calendarYear); + + const result = await this.fetchJson(path); + if (result.isErr()) { + return result; + } + + const entries = dbFundsFundraisingEntrySchema.safeParse(result.value); + if (entries.success) { + return Ok( + entries.data.entries.map((entry) => ({ + donatedBy: optionOf(entry.donatedBy), + donatedTo: optionOf(entry.donatedTo), + // donatedOn is in Eastern time + donatedOn: entry.donatedOn, + // Convert the amount from cents to dollars + amount: entry.amount / 100, + })) + ); + } else { + return Err(new ZodError(entries.error)); + } + } +} diff --git a/packages/server/src/lib/fundraising/FundraisingProvider.ts b/packages/server/src/lib/fundraising/FundraisingProvider.ts new file mode 100644 index 00000000..35b1f6ea --- /dev/null +++ b/packages/server/src/lib/fundraising/FundraisingProvider.ts @@ -0,0 +1,29 @@ +import type { MarathonYearString } from "@ukdanceblue/common"; +import type { ConcreteResult } from "@ukdanceblue/common/error"; +import type { DateTime } from "luxon"; +import type { Option } from "ts-results-es"; + + +export interface FundraisingTeam { + name: string; + active: boolean; + identifier: IDType; + total: number; +} + +export interface FundraisingEntry { + donatedBy: Option; + donatedTo: Option; + donatedOn: DateTime; + amount: number; +} + +export interface FundraisingProvider { + getTeams( + marathonYear: MarathonYearString + ): Promise[]>>; + getTeamEntries( + marathonYear: MarathonYearString, + identifier: unknown + ): Promise>; +} diff --git a/packages/server/src/lib/graphqlSchema.ts b/packages/server/src/lib/graphqlSchema.ts index c11944ab..630aa621 100644 --- a/packages/server/src/lib/graphqlSchema.ts +++ b/packages/server/src/lib/graphqlSchema.ts @@ -1,60 +1,103 @@ import { fileURLToPath } from "url"; +import { ConcreteError, toBasicError } from "@ukdanceblue/common/error"; +import { Result } from "ts-results-es"; import type { MiddlewareFn } from "type-graphql"; import { buildSchema } from "type-graphql"; import { Container } from "typedi"; -import { ConfigurationResolver } from "../resolvers/ConfigurationResolver.js"; -import { DeviceResolver } from "../resolvers/DeviceResolver.js"; -import { EventResolver } from "../resolvers/EventResolver.js"; -import { FeedResolver } from "../resolvers/FeedResolver.js"; -import { ImageResolver } from "../resolvers/ImageResolver.js"; -import { LoginStateResolver } from "../resolvers/LoginState.js"; -import { MarathonHourResolver } from "../resolvers/MarathonHourResolver.js"; -import { MarathonResolver } from "../resolvers/MarathonResolver.js"; -import { MembershipResolver } from "../resolvers/MembershipResolver.js"; +import { CatchableConcreteError } from "./formatError.js"; +import { logger } from "#logging/logger.js"; +import { ConfigurationResolver } from "#resolvers/ConfigurationResolver.js"; +import { DeviceResolver } from "#resolvers/DeviceResolver.js"; +import { EventResolver } from "#resolvers/EventResolver.js"; +import { FeedResolver } from "#resolvers/FeedResolver.js"; +import { FundraisingAssignmentResolver } from "#resolvers/FundraisingAssignmentResolver.js"; +import { FundraisingEntryResolver } from "#resolvers/FundraisingEntryResolver.js"; +import { ImageResolver } from "#resolvers/ImageResolver.js"; +import { LoginStateResolver } from "#resolvers/LoginState.js"; +import { MarathonHourResolver } from "#resolvers/MarathonHourResolver.js"; +import { MarathonResolver } from "#resolvers/MarathonResolver.js"; +import { MembershipResolver } from "#resolvers/MembershipResolver.js"; +import { NodeResolver } from "#resolvers/NodeResolver.js"; import { NotificationDeliveryResolver, NotificationResolver, -} from "../resolvers/NotificationResolver.js"; -import { PersonResolver } from "../resolvers/PersonResolver.js"; -import { PointEntryResolver } from "../resolvers/PointEntryResolver.js"; -import { PointOpportunityResolver } from "../resolvers/PointOpportunityResolver.js"; -import { TeamResolver } from "../resolvers/TeamResolver.js"; - -import { logger } from "./logging/logger.js"; +} from "#resolvers/NotificationResolver.js"; +import { PersonResolver } from "#resolvers/PersonResolver.js"; +import { PointEntryResolver } from "#resolvers/PointEntryResolver.js"; +import { PointOpportunityResolver } from "#resolvers/PointOpportunityResolver.js"; +import { TeamResolver } from "#resolvers/TeamResolver.js"; const schemaPath = fileURLToPath( new URL("../../../../schema.graphql", import.meta.url) ); +/** + * Logs errors, as well as allowing us to return a result from a resolver + */ const errorHandlingMiddleware: MiddlewareFn = async (_, next) => { + let result; try { - return void (await next()); + result = (await next()) as unknown; } catch (error) { logger.error("An error occurred in a resolver", error); throw error; } + + if (Result.isResult(result)) { + if (result.isErr()) { + logger.error("An error occurred in a resolver", result.error); + throw new CatchableConcreteError( + result.error instanceof ConcreteError + ? result.error + : toBasicError(result.error) + ); + } else { + return result.value as unknown; + } + } else { + return result; + } }; +const resolvers = [ + ConfigurationResolver, + DeviceResolver, + EventResolver, + ImageResolver, + PersonResolver, + MembershipResolver, + NotificationResolver, + NotificationDeliveryResolver, + TeamResolver, + LoginStateResolver, + PointEntryResolver, + PointOpportunityResolver, + MarathonHourResolver, + MarathonResolver, + FeedResolver, + FundraisingAssignmentResolver, + FundraisingEntryResolver, + NodeResolver, +] as const; + +for (const service of resolvers) { + // @ts-expect-error Typedi doesn't seem to like it, but it works + if (!Container.has(service)) { + logger.crit(`Failed to resolve service: "${service.name}"`); + } else { + try { + // @ts-expect-error Typedi doesn't seem to like it, but it works + Container.get(service); + } catch (error) { + logger.crit(`Failed to resolve service: "${service.name}"`, error); + } + } +} + export default await buildSchema({ - resolvers: [ - ConfigurationResolver, - DeviceResolver, - EventResolver, - ImageResolver, - PersonResolver, - MembershipResolver, - NotificationResolver, - NotificationDeliveryResolver, - TeamResolver, - LoginStateResolver, - PointEntryResolver, - PointOpportunityResolver, - MarathonHourResolver, - MarathonResolver, - FeedResolver, - ], + resolvers, emitSchemaFile: schemaPath, globalMiddlewares: [errorHandlingMiddleware], container: Container, diff --git a/packages/server/src/lib/logging/auditLogging.ts b/packages/server/src/lib/logging/auditLogging.ts index 1eb38d79..f32b78d5 100644 --- a/packages/server/src/lib/logging/auditLogging.ts +++ b/packages/server/src/lib/logging/auditLogging.ts @@ -1,7 +1,7 @@ import type { LeveledLogMethod, Logger } from "winston"; import { createLogger, format, transports } from "winston"; -import { isDevelopment } from "../../environment.js"; +import { isDevelopment, logDir } from "#environment"; export interface AuditLogger extends Logger { /** @@ -52,10 +52,14 @@ export interface AuditLogger extends Logger { notice: never; } +export const auditLoggerFileName = "audit.log.json"; + const auditLogTransport = new transports.File({ - filename: "audit.log.json", + filename: auditLoggerFileName, + dirname: logDir, maxsize: 1_000_000, maxFiles: 3, + format: format.combine(format.timestamp(), format.json()), }); const dangerousConsoleTransport = new transports.Console({ @@ -76,9 +80,8 @@ const dangerousConsoleTransport = new transports.Console({ export const auditLogger = createLogger({ level: "secure", - silent: isDevelopment, + silent: false, transports: [auditLogTransport, dangerousConsoleTransport], - format: format.combine(format.timestamp(), format.json()), levels: { info: 0, dangerous: 2, diff --git a/packages/server/src/lib/logging/sqlLogging.ts b/packages/server/src/lib/logging/sqlLogging.ts index e71331d3..58937e5c 100644 --- a/packages/server/src/lib/logging/sqlLogging.ts +++ b/packages/server/src/lib/logging/sqlLogging.ts @@ -1,13 +1,37 @@ +import type winston from "winston"; import { createLogger, format, transports } from "winston"; -import { isDevelopment } from "../../environment.js"; +import { isDevelopment, logDir } from "#environment"; const databaseLogTransport = new transports.File({ filename: "database.log", maxsize: 1_000_000, - maxFiles: 3, + maxFiles: 1, + dirname: logDir, }); +interface SqlLogger extends winston.Logger { + error: winston.LeveledLogMethod; + warning: winston.LeveledLogMethod; + info: winston.LeveledLogMethod; + sql: winston.LeveledLogMethod; + + emerg: never; + alert: never; + crit: never; + notice: never; + debug: never; + trace: never; + warn: never; + help: never; + data: never; + prompt: never; + http: never; + verbose: never; + input: never; + silly: never; +} + export const sqlLogger = createLogger({ exitOnError: false, level: "sql", @@ -20,7 +44,7 @@ export const sqlLogger = createLogger({ transports: isDevelopment ? databaseLogTransport : [], silent: !isDevelopment, format: format.combine(format.timestamp(), format.simple()), -}); +}) as SqlLogger; if (isDevelopment) { sqlLogger.info("SQL Logger initialized"); diff --git a/packages/server/src/lib/logging/standardLogging.ts b/packages/server/src/lib/logging/standardLogging.ts index 316f1725..0ee37d85 100644 --- a/packages/server/src/lib/logging/standardLogging.ts +++ b/packages/server/src/lib/logging/standardLogging.ts @@ -1,7 +1,7 @@ import type winston from "winston"; import { createLogger, format, transports } from "winston"; -import { loggingLevel } from "../../environment.js"; +import { logDir, loggingLevel } from "#environment"; export const SyslogLevels = { emerg: 0, @@ -64,6 +64,7 @@ const combinedLogTransport = new transports.File({ filename: "combined.log", maxsize: 1_000_000, maxFiles: 3, + dirname: logDir, }); export const logger = createLogger({ @@ -95,7 +96,7 @@ export const logger = createLogger({ */ export function logFatal(content: unknown) { // Logs the error and then crashes the server - + logger.emerg(String(content), () => process.exit(1)); } diff --git a/packages/server/src/lib/notification/ExpoNotificationProvider.ts b/packages/server/src/lib/notification/ExpoNotificationProvider.ts index f7f9ab17..5628a134 100644 --- a/packages/server/src/lib/notification/ExpoNotificationProvider.ts +++ b/packages/server/src/lib/notification/ExpoNotificationProvider.ts @@ -9,17 +9,16 @@ import { Expo } from "expo-server-sdk"; import { DateTime } from "luxon"; import { Service } from "typedi"; -import { isDevelopment } from "../../environment.js"; -import { DeviceRepository } from "../../repositories/device/DeviceRepository.js"; -import { NotificationRepository } from "../../repositories/notification/NotificationRepository.js"; -import { NotificationDeliveryRepository } from "../../repositories/notificationDelivery/NotificationDeliveryRepository.js"; -import { logger } from "../logging/standardLogging.js"; - import type { NotificationAudience, NotificationProvider, SendableNotification, } from "./NotificationProvider.js"; +import { isDevelopment } from "#environment"; +import { logger } from "#logging/standardLogging.js"; +import { DeviceRepository } from "#repositories/device/DeviceRepository.js"; +import { NotificationRepository } from "#repositories/notification/NotificationRepository.js"; +import { NotificationDeliveryRepository } from "#repositories/notificationDelivery/NotificationDeliveryRepository.js"; function makeExpoNotifications( content: { diff --git a/packages/server/src/lib/notification/ExpoPushReceiptHandler.ts b/packages/server/src/lib/notification/ExpoPushReceiptHandler.ts index f1863b5e..bc0863d6 100644 --- a/packages/server/src/lib/notification/ExpoPushReceiptHandler.ts +++ b/packages/server/src/lib/notification/ExpoPushReceiptHandler.ts @@ -3,8 +3,8 @@ import type { ExpoPushReceipt } from "expo-server-sdk"; import { Expo } from "expo-server-sdk"; import { Service } from "typedi"; -import { DeviceRepository } from "../../repositories/device/DeviceRepository.js"; -import { NotificationDeliveryRepository } from "../../repositories/notificationDelivery/NotificationDeliveryRepository.js"; +import { DeviceRepository } from "#repositories/device/DeviceRepository.js"; +import { NotificationDeliveryRepository } from "#repositories/notificationDelivery/NotificationDeliveryRepository.js"; @Service() export class ExpoPushReceiptHandler { diff --git a/packages/server/src/lib/prisma-utils/gqlFilterToSql.ts b/packages/server/src/lib/prisma-utils/gqlFilterToSql.ts deleted file mode 100644 index efce0112..00000000 --- a/packages/server/src/lib/prisma-utils/gqlFilterToSql.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { Prisma } from "@prisma/client"; -import type { - AbstractBooleanFilterItem, - AbstractDateFilterItem, - AbstractIsNullFilterItem, - AbstractNumericFilterItem, - AbstractOneOfFilterItem, - AbstractStringFilterItem, -} from "@ukdanceblue/common"; -import { - IsComparator, - NumericComparator, - StringComparator, -} from "@ukdanceblue/common"; - -export type FilterItems< - B extends string, - D extends string, - I extends string, - N extends string, - O extends string, - S extends string, -> = - | AbstractBooleanFilterItem - | AbstractDateFilterItem - | AbstractIsNullFilterItem - | AbstractNumericFilterItem - | AbstractOneOfFilterItem - | AbstractStringFilterItem; - -export function stringFilterToSql( - filter: AbstractStringFilterItem -): Prisma.Sql { - switch (filter.comparison) { - case StringComparator.IS: - case StringComparator.EQUALS: { - if (filter.negate) { - // return { not: { equals: filter.value }, mode: "insensitive" }; - return Prisma.sql`NOT ${filter.field} = ${filter.value}`; - } - // return { equals: filter.value }; - return Prisma.sql`${filter.field} = ${filter.value}`; - } - case StringComparator.STARTS_WITH: { - if (filter.negate) { - // return { not: { startsWith: filter.value }, mode: "insensitive" }; - return Prisma.sql`NOT ${filter.field} ILIKE ${filter.value}%`; - } - // return { startsWith: filter.value, mode: "insensitive" }; - return Prisma.sql`${filter.field} ILIKE ${filter.value}%`; - } - case StringComparator.ENDS_WITH: { - if (filter.negate) { - // return { not: { endsWith: filter.value }, mode: "insensitive" }; - return Prisma.sql`NOT ${filter.field} ILIKE %${filter.value}`; - } - // return { endsWith: filter.value, mode: "insensitive" }; - return Prisma.sql`${filter.field} ILIKE %${filter.value}`; - } - case StringComparator.SUBSTRING: { - if (filter.negate) { - // return { not: { contains: filter.value }, mode: "insensitive" }; - return Prisma.sql`NOT ${filter.field} ILIKE %${filter.value}%`; - } - // return { contains: filter.value, mode: "insensitive" }; - return Prisma.sql`${filter.field} ILIKE %${filter.value}%`; - } - default: { - throw new Error( - `Unsupported string comparator: ${String(filter.comparison)}` - ); - } - } -} - -export function booleanFilterToSql( - filter: AbstractBooleanFilterItem -): Prisma.Sql { - switch (filter.comparison) { - case IsComparator.IS: { - if (filter.negate) { - // return { not: { equals: filter.value } }; - return Prisma.sql`NOT ${filter.field} = ${filter.value}`; - } - // return { equals: filter.value }; - return Prisma.sql`${filter.field} = ${filter.value}`; - } - default: { - throw new Error( - `Unsupported boolean comparator: ${String(filter.comparison)}` - ); - } - } -} - -export function dateFilterToSql( - filter: AbstractDateFilterItem -): Prisma.Sql { - switch (filter.comparison) { - case IsComparator.IS: - case NumericComparator.EQUALS: { - if (filter.negate) { - // return { not: { equals: filter.value } }; - return Prisma.sql`NOT ${filter.field} = ${filter.value}`; - } - // return { equals: filter.value }; - return Prisma.sql`${filter.field} = ${filter.value}`; - } - case NumericComparator.GREATER_THAN: { - if (filter.negate) { - // return { not: { gt: filter.value } }; - return Prisma.sql`NOT ${filter.field} > ${filter.value}`; - } - // return { gt: filter.value }; - return Prisma.sql`${filter.field} > ${filter.value}`; - } - case NumericComparator.GREATER_THAN_OR_EQUAL_TO: { - if (filter.negate) { - // return { not: { gte: filter.value } }; - return Prisma.sql`NOT ${filter.field} >= ${filter.value}`; - } - // return { gte: filter.value }; - return Prisma.sql`${filter.field} >= ${filter.value}`; - } - case NumericComparator.LESS_THAN: { - if (filter.negate) { - // return { not: { lt: filter.value } }; - return Prisma.sql`NOT ${filter.field} < ${filter.value}`; - } - // return { lt: filter.value }; - return Prisma.sql`${filter.field} < ${filter.value}`; - } - case NumericComparator.LESS_THAN_OR_EQUAL_TO: { - if (filter.negate) { - // return { not: { lte: filter.value } }; - return Prisma.sql`NOT ${filter.field} <= ${filter.value}`; - } - // return { lte: filter.value }; - return Prisma.sql`${filter.field} <= ${filter.value}`; - } - default: { - throw new Error( - `Unsupported date comparator: ${String(filter.comparison)}` - ); - } - } -} - -export function isNullFilterToSql( - filter: AbstractIsNullFilterItem -): Prisma.Sql { - if (filter.negate) { - // return { not: { equals: null } }; - return Prisma.sql`NOT ${filter.field} IS NULL`; - } - // return { equals: null }; - return Prisma.sql`${filter.field} IS NULL`; -} - -export function numericFilterToSql( - filter: AbstractNumericFilterItem -): Prisma.Sql { - switch (filter.comparison) { - case NumericComparator.EQUALS: { - if (filter.negate) { - // return { not: { equals: filter.value } }; - return Prisma.sql`NOT ${filter.field} = ${filter.value}`; - } - // return { equals: filter.value }; - return Prisma.sql`${filter.field} = ${filter.value}`; - } - case NumericComparator.GREATER_THAN: { - if (filter.negate) { - // return { not: { gt: filter.value } }; - return Prisma.sql`NOT ${filter.field} > ${filter.value}`; - } - // return { gt: filter.value }; - return Prisma.sql`${filter.field} > ${filter.value}`; - } - case NumericComparator.GREATER_THAN_OR_EQUAL_TO: { - if (filter.negate) { - // return { not: { gte: filter.value } }; - return Prisma.sql`NOT ${filter.field} >= ${filter.value}`; - } - // return { gte: filter.value }; - return Prisma.sql`${filter.field} >= ${filter.value}`; - } - case NumericComparator.LESS_THAN: { - if (filter.negate) { - // return { not: { lt: filter.value } }; - return Prisma.sql`NOT ${filter.field} < ${filter.value}`; - } - // return { lt: filter.value }; - return Prisma.sql`${filter.field} < ${filter.value}`; - } - case NumericComparator.LESS_THAN_OR_EQUAL_TO: { - if (filter.negate) { - // return { not: { lte: filter.value } }; - return Prisma.sql`NOT ${filter.field} <= ${filter.value}`; - } - // return { lte: filter.value }; - return Prisma.sql`${filter.field} <= ${filter.value}`; - } - default: { - throw new Error( - `Unsupported numeric comparator: ${String(filter.comparison)}` - ); - } - } -} - -export function oneOfFilterToSql( - filter: AbstractOneOfFilterItem -): Prisma.Sql { - if (filter.negate) { - // return { not: { in: [...filter.value] as never[] } }; - return Prisma.sql`NOT ${filter.field} IN (${filter.value})`; - } - // return { in: [...filter.value] as never[] }; - return Prisma.sql`${filter.field} IN (${filter.value})`; -} diff --git a/packages/server/src/lib/sendResponse.ts b/packages/server/src/lib/sendResponse.ts deleted file mode 100644 index 16563c89..00000000 --- a/packages/server/src/lib/sendResponse.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type { Primitive, PrimitiveObject } from "@ukdanceblue/common"; -import { isPrimitive, isPrimitiveObject } from "@ukdanceblue/common"; -import type { Request, Response } from "express"; - -// Strings will be light blue, numbers will be purple, -// true will be dark green, and false will be light red, -// null will be dark red, and undefined will be dark gray. -const stringColor = "#11aaff"; -const numberColor = "#aa11ff"; -const trueColor = "#11ff11"; -const falseColor = "#cc1111"; -const nullColor = "#ff0000"; -const undefinedColor = "#888888"; - -/** - * Recursively converts a primitive or primitive object to an HTML string - * suitable for including in a document body. - * - * @param content The content to convert - * @param indentationLevel The indentation level to use - * @return The HTML string - */ -function htmlifyJson( - content: Primitive | PrimitiveObject | PrimitiveObject[] | Primitive[], - indentationLevel = 0 -): string { - if (Array.isArray(content)) { - return `
    Array (${ - content.length - } Elements)
      ${content - .map((item) => `
    1. ${htmlifyJson(item, indentationLevel + 1)}
    2. `) - .join("")}
    `; - } - if (isPrimitiveObject(content)) { - // If we are at indent level 0 skip the list - if (indentationLevel === 0) { - return Object.entries(content) - .map( - ([key, value]) => `
    ${key}: ${htmlifyJson(value)}
    ` - ) - .join(""); - } else { - return `
    Object:
      ${Object.entries(content) - .map( - ([key, value]) => - `
    • ${key}: ${htmlifyJson(value, indentationLevel + 1)}
    • ` - ) - .join("")}
    `; - } - } - if (typeof content === "string") { - return `"${content}"`; - } - if (typeof content === "number") { - return `${content}`; - } - if (typeof content === "boolean") { - return `${ - content ? "True" : "False" - }`; - } - if (content === null) { - return `Null`; - } - return `No Content`; -} - -/** - * Sends a response to the client. If the client accepts HTML, it will send - * the content as HTML. Otherwise, it will send the content as JSON. - * If the content is undefined, it will send no body. - * - * @param res The response object - * @param req The request object - * @param content The content to send - * @param status The status code to send - * @return The response - */ -export function sendResponse( - res: Response, - _: Request, - content?: unknown, - status = 200 -) { - res.status(status); - if (content === undefined) { - return res.send(); - } - - return res.format({ - html: () => { - if (!(isPrimitive(content) || isPrimitiveObject(content))) { - const stringifiedContent = JSON.stringify(content, null, 2); - return res.send(stringifiedContent); - } else { - return res - .contentType("text/html") - .send( - `DanceBlue API Viewer${htmlifyJson( - content ?? undefined - )}` - ); - } - }, - json: () => { - return res.json(content); - }, - text: () => { - const stringifiedContent = JSON.stringify(content, null, 2); - return res - .contentType("text/plain; charset=utf-8") - .send(stringifiedContent); - }, - default: () => { - return res.status(406).send("Not Acceptable"); - }, - }); -} diff --git a/packages/server/src/lib/transformers.ts b/packages/server/src/lib/transformers.ts deleted file mode 100644 index 05f9be19..00000000 --- a/packages/server/src/lib/transformers.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { LuxonError } from "@ukdanceblue/common"; -import { DateTime, Interval } from "luxon"; -import { - Range, - parse as parseRange, - serialize as serializeRange, -} from "postgres-range"; - -export const luxonDateTimeJsDateTransformer = { - from: (value: Date): DateTime => { - return DateTime.fromJSDate(value, { zone: "utc" }); - }, - to: ( - value?: DateTime | null | undefined | Record - ): Date | undefined => { - if (!value) { - return undefined; - } - if (!DateTime.isDateTime(value)) { - throw new Error("Not a DateTime"); - } - return value.toJSDate(); - }, -}; - -export const luxonDateTimeJsDateArrayTransformer = { - from: (value: Date[]): DateTime[] => { - return value.map((date) => DateTime.fromJSDate(date, { zone: "utc" })); - }, - to: (value?: DateTime[] | null | undefined | Record) => { - if (!value) { - return undefined; - } - if (!Array.isArray(value)) { - throw new TypeError("Not an array"); - } - return value.map((dateTime) => { - if (!DateTime.isDateTime(dateTime)) { - throw new Error("Not a DateTime"); - } - return dateTime.toJSDate(); - }); - }, -}; - -export const luxonIntervalPgRangeTransformer = { - from: (value: string) => { - const range = parseRange(value, (value) => DateTime.fromISO(value)); - if (range.lower == null || range.upper == null) { - throw new Error("Not a range"); - } - return Interval.fromDateTimes(range.lower, range.upper); - }, - to: (value?: Interval | null | undefined | Record) => { - if (value == null) { - return null; - } - if ( - !Interval.isInterval(value) || - value.start == null || - value.end == null - ) { - throw new Error("Not an Interval"); - } - if (!value.isValid) { - throw new LuxonError(value); - } - const range = new Range(value.start, value.end, 0); - return serializeRange(range, (dateTime) => { - const iso = dateTime.toISO(); - if (iso == null) { - const error = dateTime.isValid - ? new Error("Not an ISO string") - : new LuxonError(dateTime); - throw error; - } - return iso; - }); - }, -}; - -export const luxonDateISOStringTransformer = { - from: (value: string) => { - return DateTime.fromISO(value, { zone: "utc" }); - }, - to: (value?: DateTime | null | undefined | Record) => { - return value?.toISODate?.(); - }, -}; - -export const luxonTimeISOStringTransformer = { - from: (value: string) => { - return DateTime.fromISO(value, { zone: "utc" }); - }, - to: (value?: DateTime | null | undefined | Record) => { - return value?.toISOTime?.(); - }, -}; diff --git a/packages/server/src/lib/whatwg.ts b/packages/server/src/lib/whatwg.ts deleted file mode 100644 index c741defc..00000000 --- a/packages/server/src/lib/whatwg.ts +++ /dev/null @@ -1,114 +0,0 @@ -const sparseHttpTokenCodePoints = new Set([ - 0x21, 0x23, 0x24, 0x25, 0x26, 0x27, 0x2a, 0x2b, 0x2d, 0x2e, 0x5e, 0x5f, 0x60, - 0x7c, 0x7e, -]); -export function isHttpTokenCodePoint(codePoint: number | undefined): boolean { - if (codePoint === undefined) return false; - // First check if alphanumeric - if (codePoint >= 0x30 && codePoint <= 0x39) return true; - if (codePoint >= 0x41 && codePoint <= 0x5a) return true; - if (codePoint >= 0x61 && codePoint <= 0x7a) return true; - // Then check if in the list - return sparseHttpTokenCodePoints.has(codePoint); -} -export function isHttpQuotedStringTokenCodePoint( - codePoint: number | undefined -): boolean { - if (codePoint === undefined) return false; - if (codePoint === 0x09) return true; - if (codePoint >= 0x20 && codePoint <= 0x7e) return true; - if (codePoint >= 0x80 && codePoint <= 0xff) return true; - return false; -} -export function isHttpWhitespace(codePoint: number | undefined): boolean { - if (codePoint === undefined) return false; - return ( - codePoint === 0x0a || // LF - codePoint === 0x0d || // CR - codePoint === 0x09 || // TAB - codePoint === 0x20 // SPACE - ); -} -export function trimHttpWhitespace( - input: string, - direction: "start" | "end" | "both" = "both" -): string { - let start = 0; - let end = input.length; - if (direction === "start" || direction === "both") { - while (start < end && isHttpWhitespace(input.codePointAt(start))) { - start++; - } - } - if (direction === "end" || direction === "both") { - while (end > start && isHttpWhitespace(input.codePointAt(end - 1))) { - end--; - } - } - return input.slice(start, end); -} - -export function collectHttpQuotedString( - input: string, - position: number, - extractValue = false -): { output: string; position: number } { - const positionStart = position; - let value = ""; - - // Assert: the code point at position within input is U+0022 ("). - if (input.codePointAt(position) !== 0x22) { - throw new Error('Invalid input. Expected starting quote (").'); - } - - // Advance position by 1. - position++; - - for (;;) { - // Append the result of collecting a sequence of code points that are not U+0022 (") or U+005C (\) from input, given position, to value. - while ( - position < input.length && - input.codePointAt(position) !== 0x22 && - input.codePointAt(position) !== 0x5c - ) { - value += input.charAt(position); - position++; - } - - // If position is past the end of input, then break. - if (position >= input.length) { - break; - } - - // Let quoteOrBackslash be the code point at position within input. - const quoteOrBackslash = input.codePointAt(position); - - // Advance position by 1. - position++; - - if (quoteOrBackslash === 0x5c) { - // If quoteOrBackslash is U+005C (\), then: - if (position >= input.length) { - // If position is past the end of input, then append U+005C (\) to value and break. - value += "\\"; - break; - } - - // Append the code point at position within input to value. - value += input.charAt(position); - // Advance position by 1. - position++; - } else { - // Otherwise: - // Assert: quoteOrBackslash is U+0022 ("). - // Break. - break; - } - } - - return extractValue - ? // If extract-value is true, then return value. - { output: value, position } - : // Return the code points from positionStart to position, inclusive, within input. - { output: input.slice(positionStart, position + 1), position }; -} diff --git a/packages/server/src/prisma.ts b/packages/server/src/prisma.ts index 182126c9..4b53ae02 100644 --- a/packages/server/src/prisma.ts +++ b/packages/server/src/prisma.ts @@ -1,14 +1,51 @@ import { PrismaClient } from "@prisma/client"; +import { DetailedError, ErrorCode } from "@ukdanceblue/common"; import { Container } from "typedi"; -import { logger } from "./lib/logging/standardLogging.js"; +import { sqlLogger } from "#logging/sqlLogging.js"; +import { logger } from "#logging/standardLogging.js"; -export const prisma = new PrismaClient(); +export const prisma = new PrismaClient({ + log: [ + { + emit: "event", + level: "error", + }, + { + emit: "event", + level: "warn", + }, + { + emit: "event", + level: "info", + }, + { + emit: "event", + level: "query", + }, + ], +}); + +prisma.$on("query", (e) => { + sqlLogger.sql(e.query); +}); +prisma.$on("info", (e) => { + sqlLogger.info(e.message); +}); +prisma.$on("warn", (e) => { + sqlLogger.warning(e.message); +}); +prisma.$on("error", (e) => { + sqlLogger.error(e.message); +}); Container.set(PrismaClient, prisma); if (!Container.has(PrismaClient)) { - throw new Error("PrismaClient not registered"); + throw new DetailedError( + ErrorCode.InternalFailure, + "PrismaClient not registered" + ); } else { logger.info("PrismaClient registered"); } diff --git a/packages/server/src/repositories/JobState.ts b/packages/server/src/repositories/JobState.ts new file mode 100644 index 00000000..a16032c7 --- /dev/null +++ b/packages/server/src/repositories/JobState.ts @@ -0,0 +1,34 @@ +import { PrismaClient } from "@prisma/client"; +import Cron from "croner"; +import { Service } from "typedi"; + +@Service() +export class JobStateRepository { + constructor(private readonly prisma: PrismaClient) {} + + async logCompletedJob(job: Cron) { + const jobName = job.name; + const previousRun = job.currentRun(); + if (jobName && previousRun) { + await this.prisma.jobState.upsert({ + where: { jobName }, + update: { lastRun: previousRun }, + create: { jobName, lastRun: previousRun }, + }); + } + } + + async getNextJobDate(job: Cron) { + const jobName = job.name; + let baseDate = new Date(); + if (jobName) { + const jobState = await this.prisma.jobState.findUnique({ + where: { jobName }, + }); + if (jobState) { + baseDate = jobState.lastRun; + } + } + return job.nextRun(baseDate) ?? undefined; + } +} diff --git a/packages/server/src/resolvers/LoginFlowSession.ts b/packages/server/src/repositories/LoginFlowSession.ts similarity index 100% rename from packages/server/src/resolvers/LoginFlowSession.ts rename to packages/server/src/repositories/LoginFlowSession.ts diff --git a/packages/server/src/repositories/committee/CommitteeRepository.ts b/packages/server/src/repositories/committee/CommitteeRepository.ts new file mode 100644 index 00000000..03b80911 --- /dev/null +++ b/packages/server/src/repositories/committee/CommitteeRepository.ts @@ -0,0 +1,295 @@ +import { Committee, Prisma, PrismaClient, Team } from "@prisma/client"; +import { + CommitteeIdentifier, + CommitteeRole, + SortDirection, +} from "@ukdanceblue/common"; +import { CompositeError , InvariantError, NotFoundError , toBasicError } from "@ukdanceblue/common/error"; +import { Err, None, Ok, Result } from "ts-results-es"; +import { Service } from "typedi"; + +import * as CommitteeDescriptions from "./committeeDescriptions.js"; +import { + buildCommitteeOrder, + buildCommitteeWhere, +} from "./committeeRepositoryUtils.js"; +import type { FilterItems } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; +import type { UniqueMarathonParam } from "#repositories/marathon/MarathonRepository.js"; +import { MarathonRepository } from "#repositories/marathon/MarathonRepository.js"; +import { MembershipRepository } from "#repositories/membership/MembershipRepository.js"; +import { + RepositoryError, + SimpleUniqueParam, + handleRepositoryError, +} from "#repositories/shared.js"; + +// Make sure that we are exporting a description for every committee +CommitteeDescriptions[ + "" as CommitteeIdentifier +] satisfies Prisma.CommitteeUpsertWithoutChildCommitteesInput; + +const CommitteeOneOfKeys = ["identifier"] as const; +type CommitteeOneOfKeys = (typeof CommitteeOneOfKeys)[number]; + +const CommitteeDateKeys = ["createdAt", "updatedAt"] as const; +type CommitteeDateKey = (typeof CommitteeDateKeys)[number]; + +export type CommitteeFilters = FilterItems< + never, + CommitteeDateKey, + never, + never, + CommitteeOneOfKeys, + never +>; + +type CommitteeUniqueParam = + | SimpleUniqueParam + | { identifier: CommitteeIdentifier }; + +@Service() +export class CommitteeRepository { + constructor( + private readonly prisma: PrismaClient, + private readonly membershipRepository: MembershipRepository, + private readonly marathonRepository: MarathonRepository + ) {} + + // Finders + + async findCommittees( + filters: readonly CommitteeFilters[] | null | undefined, + order: readonly [key: string, sort: SortDirection][] | null | undefined, + limit?: number | undefined, + offset?: number | undefined + ): Promise> { + try { + const where = buildCommitteeWhere(filters); + const orderBy = buildCommitteeOrder(order); + + const committees = await this.prisma.committee.findMany({ + where, + orderBy, + take: limit, + skip: offset, + }); + + return Ok(committees); + } catch (error) { + return handleRepositoryError(error); + } + } + + async findCommitteeByUnique( + param: CommitteeUniqueParam + ): Promise> { + try { + const committee = await this.prisma.committee.findUnique({ + where: param, + }); + return Ok(committee); + } catch (error) { + return handleRepositoryError(error); + } + } + + async assignPersonToCommittee( + personParam: SimpleUniqueParam, + committeeParam: CommitteeIdentifier, + committeeRole: CommitteeRole, + marathonParam?: UniqueMarathonParam + ): Promise>> { + try { + const person = await this.prisma.person.findUnique({ + where: personParam, + }); + if (!person) { + return Err(new NotFoundError({ what: "Person" })); + } + + if (!marathonParam) { + const latestMarathon = + await this.marathonRepository.findActiveMarathon(); + if (latestMarathon.isErr()) { + return Err(latestMarathon.error); + } + marathonParam = { id: latestMarathon.value.id }; + } else { + const val = + await this.marathonRepository.findMarathonByUnique(marathonParam); + if (val.isErr()) { + return Err(val.error); + } + } + + const committee = await this.getCommittee(committeeParam, { + forMarathon: marathonParam, + }); + + if (committee.isErr()) { + return Err(committee.error); + } + + // for (const team of committee.value.correspondingTeams) { + // // eslint-disable-next-line no-await-in-loop + // await this.membershipRepository.assignPersonToTeam({ + // personParam: { id: person.id }, + // teamParam: { id: team.id }, + // committeeRole, + // }); + // } + const results = await Promise.allSettled( + committee.value.correspondingTeams.map((team) => + this.membershipRepository.assignPersonToTeam({ + personParam: { id: person.id }, + teamParam: { id: team.id }, + committeeRole, + }) + ) + ); + + const errors: RepositoryError[] = []; + + for (const result of results) { + if (result.status === "fulfilled") { + if (result.value.isErr()) { + errors.push(result.value.error); + } + } else { + errors.push(toBasicError(result.reason)); + } + } + + if (errors.length > 0) { + return Err(new CompositeError(errors)); + } + + return Ok(None); + } catch (error) { + return handleRepositoryError(error); + } + } + + // Mutators + + async deleteCommittee(uuid: string): Promise> { + try { + await this.prisma.committee.delete({ where: { uuid } }); + return Ok(None); + } catch (error) { + if ( + error instanceof Prisma.PrismaClientKnownRequestError && + error.code === "P2025" + ) { + return Err(new NotFoundError({ what: "Committee" })); + } else { + return handleRepositoryError(error); + } + } + } + + // Committee getter + + async getCommittee( + identifier: CommitteeIdentifier, + opts: { + forMarathon?: UniqueMarathonParam; + } = {} + ): Promise< + Result< + Committee & { + correspondingTeams: Team[]; + }, + RepositoryError + > + > { + try { + const committee = await this.prisma.committee.upsert({ + ...CommitteeDescriptions[identifier], + where: { identifier }, + include: { + correspondingTeams: opts.forMarathon + ? { + where: { + marathon: opts.forMarathon, + }, + } + : undefined, + }, + }); + + return Ok(committee); + } catch (error) { + return handleRepositoryError(error); + } + } + + async getCommitteeTeam( + committee: CommitteeIdentifier, + marathon: UniqueMarathonParam + ): Promise> { + try { + const result = await this.prisma.committee + .findUnique({ where: { identifier: committee } }) + .correspondingTeams({ + where: { + marathon, + }, + }); + + if (result?.length === 1) { + return Ok(result[0]!); + } else if (result?.length === 0) { + return Err( + new NotFoundError({ + what: "Team", + where: `Committee: ${committee}, Marathon: ${JSON.stringify(marathon)}`, + }) + ); + } else { + return Err( + new InvariantError( + "Multiple teams found for the given committee and marathon" + ) + ); + } + } catch (error) { + return handleRepositoryError(error); + } + } + + async getChildCommittees( + identifier: CommitteeUniqueParam + ): Promise> { + try { + const childCommittees = await this.prisma.committee + .findUnique({ + where: identifier, + }) + .childCommittees(); + if (!childCommittees) { + return Err(new NotFoundError({ what: "Committee" })); + } + + return Ok(childCommittees); + } catch (error) { + return handleRepositoryError(error); + } + } + + async getParentCommittee( + identifier: CommitteeUniqueParam + ): Promise> { + try { + const parentCommittee = await this.prisma.committee + .findUnique({ + where: identifier, + }) + .parentCommittee(); + + return Ok(parentCommittee); + } catch (error) { + return handleRepositoryError(error); + } + } +} diff --git a/packages/server/src/repositories/committee/committeeDescriptions.ts b/packages/server/src/repositories/committee/committeeDescriptions.ts new file mode 100644 index 00000000..bb3300c1 --- /dev/null +++ b/packages/server/src/repositories/committee/committeeDescriptions.ts @@ -0,0 +1,81 @@ +import type { Prisma } from "@prisma/client"; +import { CommitteeIdentifier } from "@ukdanceblue/common"; +export const createCommittee = ( + identifier: CommitteeIdentifier, + parentIdentifier?: CommitteeIdentifier +): Prisma.CommitteeUpsertWithoutChildCommitteesInput => { + const committee: Prisma.CommitteeUpsertWithoutChildCommitteesInput = { + create: { + identifier, + parentCommittee: parentIdentifier + ? { + connect: { + identifier: parentIdentifier, + }, + } + : undefined, + }, + update: { + parentCommittee: parentIdentifier + ? { + connect: { + identifier: parentIdentifier, + }, + } + : undefined, + }, + where: { + identifier, + }, + }; + + return committee; +}; + +export const overallCommittee = createCommittee( + CommitteeIdentifier.overallCommittee +); +export const viceCommittee = createCommittee( + CommitteeIdentifier.viceCommittee, + CommitteeIdentifier.overallCommittee +); +export const fundraisingCommittee = createCommittee( + CommitteeIdentifier.fundraisingCommittee, + CommitteeIdentifier.viceCommittee +); +export const dancerRelationsCommittee = createCommittee( + CommitteeIdentifier.dancerRelationsCommittee, + CommitteeIdentifier.viceCommittee +); +export const marketingCommittee = createCommittee( + CommitteeIdentifier.marketingCommittee, + CommitteeIdentifier.overallCommittee +); +export const corporateCommittee = createCommittee( + CommitteeIdentifier.corporateCommittee, + CommitteeIdentifier.overallCommittee +); +export const techCommittee = createCommittee( + CommitteeIdentifier.techCommittee, + CommitteeIdentifier.overallCommittee +); +export const operationsCommittee = createCommittee( + CommitteeIdentifier.operationsCommittee, + CommitteeIdentifier.overallCommittee +); +export const miniMarathonsCommittee = createCommittee( + CommitteeIdentifier.miniMarathonsCommittee, + CommitteeIdentifier.overallCommittee +); +export const communityDevelopmentCommittee = createCommittee( + CommitteeIdentifier.communityDevelopmentCommittee, + CommitteeIdentifier.overallCommittee +); +export const familyRelationsCommittee = createCommittee( + CommitteeIdentifier.familyRelationsCommittee, + CommitteeIdentifier.overallCommittee +); +export const programmingCommittee = createCommittee( + CommitteeIdentifier.programmingCommittee, + CommitteeIdentifier.overallCommittee +); diff --git a/packages/server/src/repositories/committee/committeeRepositoryUtils.ts b/packages/server/src/repositories/committee/committeeRepositoryUtils.ts new file mode 100644 index 00000000..d515ded7 --- /dev/null +++ b/packages/server/src/repositories/committee/committeeRepositoryUtils.ts @@ -0,0 +1,51 @@ +import type { Prisma } from "@prisma/client"; +import { SortDirection } from "@ukdanceblue/common"; + +import type { CommitteeFilters } from "./CommitteeRepository.js"; +import { + dateFilterToPrisma, + oneOfFilterToPrisma, +} from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; + + +export function buildCommitteeOrder( + order: readonly [key: string, sort: SortDirection][] | null | undefined +) { + const orderBy: Prisma.CommitteeOrderByWithRelationInput = {}; + + for (const [key, sort] of order ?? []) { + switch (key) { + case "identifier": + case "createdAt": + case "updatedAt": { + orderBy[key] = sort === SortDirection.asc ? "asc" : "desc"; + break; + } + default: { + throw new Error(`Unsupported sort key: ${key}`); + } + } + } + return orderBy; +} + +export function buildCommitteeWhere( + filters: readonly CommitteeFilters[] | null | undefined +) { + const where: Prisma.CommitteeWhereInput = {}; + + for (const filter of filters ?? []) { + switch (filter.field) { + case "identifier": { + where[filter.field] = oneOfFilterToPrisma(filter); + break; + } + case "createdAt": + case "updatedAt": { + where[filter.field] = dateFilterToPrisma(filter); + break; + } + } + } + return where; +} diff --git a/packages/server/src/repositories/configuration/ConfigurationRepository.ts b/packages/server/src/repositories/configuration/ConfigurationRepository.ts index da763c90..c226220e 100644 --- a/packages/server/src/repositories/configuration/ConfigurationRepository.ts +++ b/packages/server/src/repositories/configuration/ConfigurationRepository.ts @@ -3,12 +3,13 @@ import type { SortDirection } from "@ukdanceblue/common"; import type { DateTime } from "luxon"; import { Service } from "typedi"; -import type { FilterItems } from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; - import { buildConfigurationOrder, buildConfigurationWhere, } from "./configurationRepositoryUtils.js"; +import type { FilterItems } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; +import { SimpleUniqueParam } from "#repositories/shared.js"; + const configurationStringKeys = ["key", "value"] as const; type ConfigurationStringKey = (typeof configurationStringKeys)[number]; @@ -35,6 +36,9 @@ export class ConfigurationRepository { constructor(private prisma: PrismaClient) {} // Finders + findConfigurationByUnique(param: SimpleUniqueParam) { + return this.prisma.configuration.findUnique({ where: param }); + } findConfigurations( filters: readonly ConfigurationFilters[] | null | undefined, diff --git a/packages/server/src/repositories/configuration/configurationModelToResource.ts b/packages/server/src/repositories/configuration/configurationModelToResource.ts index ae936d0a..aa55f18a 100644 --- a/packages/server/src/repositories/configuration/configurationModelToResource.ts +++ b/packages/server/src/repositories/configuration/configurationModelToResource.ts @@ -1,15 +1,16 @@ import type { Configuration } from "@prisma/client"; -import { ConfigurationResource } from "@ukdanceblue/common"; -import { DateTime } from "luxon"; +import { ConfigurationNode } from "@ukdanceblue/common"; -export function configurationModelToResource(configuration: Configuration): ConfigurationResource { - return ConfigurationResource.init({ - uuid: configuration.uuid, +export function configurationModelToResource( + configuration: Configuration +): ConfigurationNode { + return ConfigurationNode.init({ + id: configuration.uuid, key: configuration.key, value: configuration.value, - validAfter: configuration.validAfter ? DateTime.fromJSDate(configuration.validAfter) : null, - validUntil: configuration.validUntil ? DateTime.fromJSDate(configuration.validUntil) : null, + validAfter: configuration.validAfter, + validUntil: configuration.validUntil, createdAt: configuration.createdAt, updatedAt: configuration.updatedAt, }); -} \ No newline at end of file +} diff --git a/packages/server/src/repositories/configuration/configurationRepositoryUtils.ts b/packages/server/src/repositories/configuration/configurationRepositoryUtils.ts index 8ea3c72d..63719480 100644 --- a/packages/server/src/repositories/configuration/configurationRepositoryUtils.ts +++ b/packages/server/src/repositories/configuration/configurationRepositoryUtils.ts @@ -1,12 +1,12 @@ import type { Prisma } from "@prisma/client"; import { SortDirection } from "@ukdanceblue/common"; +import type { ConfigurationFilters } from "./ConfigurationRepository.js"; import { dateFilterToPrisma, stringFilterToPrisma, -} from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; +} from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; -import type { ConfigurationFilters } from "./ConfigurationRepository.js"; export function buildConfigurationOrder( order: readonly [key: string, sort: SortDirection][] | null | undefined @@ -21,7 +21,7 @@ export function buildConfigurationOrder( case "validUntil": case "createdAt": case "updatedAt": { - orderBy[key] = sort === SortDirection.ASCENDING ? "asc" : "desc"; + orderBy[key] = sort === SortDirection.asc ? "asc" : "desc"; break; } default: { diff --git a/packages/server/src/repositories/device/DeviceRepository.test.ts b/packages/server/src/repositories/device/DeviceRepository.test.ts index 00e4a512..8597979c 100644 --- a/packages/server/src/repositories/device/DeviceRepository.test.ts +++ b/packages/server/src/repositories/device/DeviceRepository.test.ts @@ -1,8 +1,8 @@ import { beforeEach, describe, expect, it } from "vitest"; -import { makePrismaMock } from "../../testing/PrismaMock.js"; - import { DeviceRepository } from "./DeviceRepository.js"; +import { makePrismaMock } from "#repositories/../testing/PrismaMock.js"; + describe("deviceRepository", () => { const { prismaMock, resetMocks } = makePrismaMock(); diff --git a/packages/server/src/repositories/device/DeviceRepository.ts b/packages/server/src/repositories/device/DeviceRepository.ts index e90b4198..3dbaaabe 100644 --- a/packages/server/src/repositories/device/DeviceRepository.ts +++ b/packages/server/src/repositories/device/DeviceRepository.ts @@ -1,13 +1,16 @@ import type { Person } from "@prisma/client"; import { PrismaClient } from "@prisma/client"; import type { SortDirection } from "@ukdanceblue/common"; +import { NotFoundError } from "@ukdanceblue/common/error"; +import { Err, Result } from "ts-results-es"; import { Service } from "typedi"; -import type { NotificationAudience } from "../../lib/notification/NotificationProvider.js"; -import type { FilterItems } from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; -import { PersonRepository } from "../person/PersonRepository.js"; - import { buildDeviceOrder, buildDeviceWhere } from "./deviceRepositoryUtils.js"; +import { CatchableConcreteError } from "#lib/formatError.js"; +import type { FilterItems } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; +import type { NotificationAudience } from "#notification/NotificationProvider.js"; +import { PersonRepository } from "#repositories/person/PersonRepository.js"; +import { RepositoryError } from "#repositories/shared.js"; const deviceStringKeys = ["expoPushToken"] as const; type DeviceStringKey = (typeof deviceStringKeys)[number]; @@ -37,12 +40,16 @@ export class DeviceRepository { }); } - async getLastLoggedInUser(deviceUuid: string) { + async getLastLoggedInUser( + deviceUuid: string + ): Promise> { const device = await this.getDeviceByUuid(deviceUuid); - - return device?.lastSeenPersonId == null - ? null - : this.personRepository.findPersonById(device.lastSeenPersonId); + if (device?.lastSeenPersonId == null) { + return Err(new NotFoundError({ what: "Person" })); + } + return this.personRepository.findPersonByUnique({ + id: device.lastSeenPersonId, + }); } async listDevices({ @@ -98,9 +105,13 @@ export class DeviceRepository { let user: Person | null = null; if (lastUserId != null) { - user = await this.personRepository.findPersonByUuid(lastUserId); - if (user == null) { - throw new Error("Last user not found"); + const userResult = await this.personRepository.findPersonByUnique({ + uuid: lastUserId, + }); + if (userResult.isErr()) { + throw new CatchableConcreteError(userResult.error); + } else { + user = userResult.value; } } @@ -189,7 +200,7 @@ export class DeviceRepository { }, }); } - + if (where.length === 0) { throw new Error("Not implemented"); } diff --git a/packages/server/src/repositories/device/deviceModelToResource.test.ts b/packages/server/src/repositories/device/deviceModelToResource.test.ts index 8122ec0b..0a8b3d95 100644 --- a/packages/server/src/repositories/device/deviceModelToResource.test.ts +++ b/packages/server/src/repositories/device/deviceModelToResource.test.ts @@ -1,6 +1,5 @@ import type { Device } from "@prisma/client"; -import { DeviceResource } from "@ukdanceblue/common"; -import { DateTime } from "luxon"; +import { DeviceNode } from "@ukdanceblue/common"; import { describe, expect, it } from "vitest"; import { deviceModelToResource } from "./deviceModelToResource.js"; @@ -20,12 +19,11 @@ describe("deviceModelToResource", () => { const resource = deviceModelToResource(deviceModel); - expect(resource).toBeInstanceOf(DeviceResource); + expect(resource).toBeInstanceOf(DeviceNode); expect(resource).toStrictEqual( - DeviceResource.init({ - uuid: "uuid", - expoPushToken: "token", - lastLogin: DateTime.fromISO("2021-01-01T00:00:00Z"), + DeviceNode.init({ + id: "uuid", + lastLogin: new Date("2021-01-01T00:00:00Z"), createdAt: new Date("2021-01-01T00:00:00Z"), updatedAt: new Date("2021-01-01T00:00:00Z"), }) @@ -47,9 +45,8 @@ describe("deviceModelToResource", () => { const resource = deviceModelToResource(deviceModel); expect(resource).toStrictEqual( - DeviceResource.init({ - uuid: "uuid", - expoPushToken: "token", + DeviceNode.init({ + id: "uuid", lastLogin: null, createdAt: new Date("2021-01-01T00:00:00Z"), updatedAt: new Date("2021-01-01T00:00:00Z"), diff --git a/packages/server/src/repositories/device/deviceModelToResource.ts b/packages/server/src/repositories/device/deviceModelToResource.ts index 4ac0aac8..d6fc0c6c 100644 --- a/packages/server/src/repositories/device/deviceModelToResource.ts +++ b/packages/server/src/repositories/device/deviceModelToResource.ts @@ -1,14 +1,10 @@ import type { Device } from "@prisma/client"; -import { DeviceResource } from "@ukdanceblue/common"; -import { DateTime } from "luxon"; +import { DeviceNode } from "@ukdanceblue/common"; -export function deviceModelToResource(deviceModel: Device): DeviceResource { - return DeviceResource.init({ - uuid: deviceModel.uuid, - expoPushToken: deviceModel.expoPushToken, - lastLogin: deviceModel.lastSeen - ? DateTime.fromJSDate(deviceModel.lastSeen) - : null, +export function deviceModelToResource(deviceModel: Device): DeviceNode { + return DeviceNode.init({ + id: deviceModel.uuid, + lastLogin: deviceModel.lastSeen, createdAt: deviceModel.createdAt, updatedAt: deviceModel.updatedAt, }); diff --git a/packages/server/src/repositories/device/deviceRepositoryUtils.ts b/packages/server/src/repositories/device/deviceRepositoryUtils.ts index 2a980bc8..1f49d8a8 100644 --- a/packages/server/src/repositories/device/deviceRepositoryUtils.ts +++ b/packages/server/src/repositories/device/deviceRepositoryUtils.ts @@ -1,12 +1,12 @@ import type { Prisma } from "@prisma/client"; import { SortDirection } from "@ukdanceblue/common"; +import type { DeviceFilters } from "./DeviceRepository.js"; import { dateFilterToPrisma, stringFilterToPrisma, -} from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; +} from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; -import type { DeviceFilters } from "./DeviceRepository.js"; export function buildDeviceOrder( order: readonly [key: string, sort: SortDirection][] | null | undefined @@ -19,7 +19,7 @@ export function buildDeviceOrder( case "lastSeen": case "createdAt": case "updatedAt": { - orderBy[key] = sort === SortDirection.ASCENDING ? "asc" : "desc"; + orderBy[key] = sort === SortDirection.asc ? "asc" : "desc"; break; } default: { diff --git a/packages/server/src/repositories/event/EventRepository.ts b/packages/server/src/repositories/event/EventRepository.ts index 1bfd8f88..86cee35b 100644 --- a/packages/server/src/repositories/event/EventRepository.ts +++ b/packages/server/src/repositories/event/EventRepository.ts @@ -2,9 +2,9 @@ import { Prisma, PrismaClient } from "@prisma/client"; import { SortDirection } from "@ukdanceblue/common"; import { Service } from "typedi"; -import type { FilterItems } from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; - import { buildEventOrder, buildEventWhere } from "./eventRepositoryUtils.js"; +import type { FilterItems } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; + const eventBooleanKeys = [] as const; type EventBooleanKey = (typeof eventBooleanKeys)[number]; @@ -107,7 +107,7 @@ export class EventRepository { rows.sort((a, b) => { const aDate = a.eventOccurrences[0]?.date ?? new Date(0); const bDate = b.eventOccurrences[0]?.date ?? new Date(0); - return sort === SortDirection.ASCENDING + return sort === SortDirection.asc ? aDate.getTime() - bDate.getTime() : bDate.getTime() - aDate.getTime(); }); @@ -117,7 +117,7 @@ export class EventRepository { rows.sort((a, b) => { const aDate = a.eventOccurrences.at(-1)?.date ?? new Date(0); const bDate = b.eventOccurrences.at(-1)?.date ?? new Date(0); - return sort === SortDirection.ASCENDING + return sort === SortDirection.asc ? aDate.getTime() - bDate.getTime() : bDate.getTime() - aDate.getTime(); }); diff --git a/packages/server/src/repositories/event/eventModelToResource.ts b/packages/server/src/repositories/event/eventModelToResource.ts index 13b1174c..7b95ba8f 100644 --- a/packages/server/src/repositories/event/eventModelToResource.ts +++ b/packages/server/src/repositories/event/eventModelToResource.ts @@ -1,13 +1,16 @@ import type { Event, EventOccurrence } from "@prisma/client"; -import { EventOccurrenceResource, EventResource } from "@ukdanceblue/common"; -import { DateTime, Interval } from "luxon"; +import { + EventNode, + EventOccurrenceNode, + IntervalISO, +} from "@ukdanceblue/common"; export function eventModelToResource( eventModel: Event, - occurrences: EventOccurrenceResource[] = [] -): EventResource { - return EventResource.init({ - uuid: eventModel.uuid, + occurrences: EventOccurrenceNode[] = [] +): EventNode { + return EventNode.init({ + id: eventModel.uuid, title: eventModel.title, summary: eventModel.summary, description: eventModel.description, @@ -20,13 +23,10 @@ export function eventModelToResource( export function eventOccurrenceModelToResource( occurrenceModel: EventOccurrence -): EventOccurrenceResource { - return EventOccurrenceResource.init({ - uuid: occurrenceModel.uuid, - interval: Interval.fromDateTimes( - DateTime.fromJSDate(occurrenceModel.date), - DateTime.fromJSDate(occurrenceModel.endDate) - ), +): EventOccurrenceNode { + return EventOccurrenceNode.init({ + id: occurrenceModel.uuid, + interval: IntervalISO.init(occurrenceModel.date, occurrenceModel.endDate), fullDay: occurrenceModel.fullDay, }); } diff --git a/packages/server/src/repositories/event/eventRepositoryUtils.ts b/packages/server/src/repositories/event/eventRepositoryUtils.ts index 7a13c461..1e1f071a 100644 --- a/packages/server/src/repositories/event/eventRepositoryUtils.ts +++ b/packages/server/src/repositories/event/eventRepositoryUtils.ts @@ -2,12 +2,12 @@ import type { Prisma } from "@prisma/client"; import type { FilterItem } from "@ukdanceblue/common"; import { SortDirection } from "@ukdanceblue/common"; +import type { EventFilters, EventOrderKeys } from "./EventRepository.ts"; import { dateFilterToPrisma, stringFilterToPrisma, -} from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; +} from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; -import type { EventFilters, EventOrderKeys } from "./EventRepository.ts"; export function buildEventOrder( order: @@ -25,7 +25,7 @@ export function buildEventOrder( case "location": case "createdAt": case "updatedAt": { - orderBy[key] = sort === SortDirection.ASCENDING ? "asc" : "desc"; + orderBy[key] = sort === SortDirection.asc ? "asc" : "desc"; break; } case "occurrence": diff --git a/packages/server/src/repositories/event/images/EventImagesRepository.ts b/packages/server/src/repositories/event/images/EventImagesRepository.ts index 1b20d3f4..d5462533 100644 --- a/packages/server/src/repositories/event/images/EventImagesRepository.ts +++ b/packages/server/src/repositories/event/images/EventImagesRepository.ts @@ -1,9 +1,8 @@ import type { Prisma } from "@prisma/client"; import { PrismaClient } from "@prisma/client"; +import { DetailedError, ErrorCode } from "@ukdanceblue/common"; import { Service } from "typedi"; -import { InvariantError } from "../../../lib/InvariantError.js"; - type UniqueParam = | { eventId: number; @@ -113,7 +112,8 @@ export class EventImagesRepository { } else if (count === 1) { return true; } else { - throw new InvariantError( + throw new DetailedError( + ErrorCode.InternalFailure, "Expected to remove at most one event-image relationship, but removed more than one." ); } diff --git a/packages/server/src/repositories/feed/feedRepository.ts b/packages/server/src/repositories/feed/FeedRepository.ts similarity index 100% rename from packages/server/src/repositories/feed/feedRepository.ts rename to packages/server/src/repositories/feed/FeedRepository.ts diff --git a/packages/server/src/repositories/feed/feedModelToResource.ts b/packages/server/src/repositories/feed/feedModelToResource.ts index 2c3fadb0..e3bb6f8e 100644 --- a/packages/server/src/repositories/feed/feedModelToResource.ts +++ b/packages/server/src/repositories/feed/feedModelToResource.ts @@ -1,9 +1,9 @@ import type { FeedItem } from "@prisma/client"; -import { FeedResource } from "@ukdanceblue/common"; +import { FeedNode } from "@ukdanceblue/common"; -export function feedItemModelToResource(feedItem: FeedItem): FeedResource { - return FeedResource.init({ - uuid: feedItem.uuid, +export function feedItemModelToResource(feedItem: FeedItem): FeedNode { + return FeedNode.init({ + id: feedItem.uuid, title: feedItem.title, textContent: feedItem.textContent, createdAt: feedItem.createdAt, diff --git a/packages/server/src/repositories/fundraising/DBFundsRepository.ts b/packages/server/src/repositories/fundraising/DBFundsRepository.ts new file mode 100644 index 00000000..32716893 --- /dev/null +++ b/packages/server/src/repositories/fundraising/DBFundsRepository.ts @@ -0,0 +1,282 @@ +import { DBFundsTeam, Prisma, PrismaClient, Team } from "@prisma/client"; + +import { + CompositeError, + NotFoundError, + BasicError, +} from "@ukdanceblue/common/error"; +import { DateTime } from "luxon"; +import { Err, None, Ok, Option, Result } from "ts-results-es"; +import { Service } from "typedi"; +import { logger } from "#logging/standardLogging.js"; +import type { UniqueMarathonParam } from "#repositories/marathon/MarathonRepository.js"; +import { MarathonRepository } from "#repositories/marathon/MarathonRepository.js"; +import { + RepositoryError, + SimpleUniqueParam, + handleRepositoryError, +} from "#repositories/shared.js"; + +export type UniqueDbFundsTeamParam = + | { + id: number; + } + | { + dbNum: number; + marathon: { id: number }; + }; + +@Service() +export class DBFundsRepository { + constructor( + private readonly prisma: PrismaClient, + private readonly marathonRepository: MarathonRepository + ) {} + + async overwriteTeamForFiscalYear( + team: { + total: number; + dbNum: number; + active: boolean; + name: string; + }, + marathonParam: UniqueMarathonParam, + dbFundsEntries: { + donatedBy: Option; + donatedTo: Option; + donatedOn: DateTime; + amount: number; + }[] + ): Promise< + Result> + > { + try { + let marathonId: number; + if ("id" in marathonParam) { + marathonId = marathonParam.id; + } else { + const marathon = + await this.marathonRepository.findMarathonByUnique(marathonParam); + if (marathon.isErr()) { + return marathon; + } + marathonId = marathon.value.id; + } + + let dBFundsTeam = await this.prisma.dBFundsTeam.findUnique({ + where: { + dbNum_marathonId: { + dbNum: team.dbNum, + marathonId, + }, + }, + include: { + fundraisingEntries: true, + }, + }); + + if (!dBFundsTeam) { + dBFundsTeam = await this.prisma.dBFundsTeam.create({ + data: { + dbNum: team.dbNum, + totalAmount: team.total, + active: team.active, + name: team.name, + marathon: { + connect: { id: marathonId }, + }, + }, + include: { + fundraisingEntries: true, + }, + }); + } + + const entriesToCreate: Prisma.DBFundsFundraisingEntryCreateWithoutDbFundsTeamInput[] = + []; + const entriesToUpdate: { + amount: number; + id: number; + }[] = []; + + // Unlike the other lists, this one is removed from rather than added to + const entryIdsToDelete: Set = new Set( + dBFundsTeam.fundraisingEntries.map((entry) => entry.id) + ); + + for (const entry of dbFundsEntries) { + const entryDonatedOnMillis = entry.donatedOn.toMillis(); + const existingEntry = dBFundsTeam.fundraisingEntries.find( + (e) => + e.donatedBy === entry.donatedBy.unwrapOr(null) && + e.donatedTo === entry.donatedTo.unwrapOr(null) && + e.date.getTime() === entryDonatedOnMillis + ); + + if (!existingEntry) { + entriesToCreate.push({ + donatedBy: entry.donatedBy.unwrapOr(null), + donatedTo: entry.donatedTo.unwrapOr(null), + date: entry.donatedOn.toJSDate(), + amount: entry.amount, + fundraisingEntry: { + create: {}, + }, + }); + } else { + entryIdsToDelete.delete(existingEntry.id); + if (existingEntry.amount.toNumber() !== entry.amount) { + entriesToUpdate.push({ + amount: entry.amount, + id: existingEntry.id, + }); + } + } + } + + if ( + entriesToCreate.length > 0 || + entriesToUpdate.length > 0 || + entryIdsToDelete.size > 0 + ) { + logger.debug(`Fundraising Sync`, { + team: { name: team.name, dbNum: team.dbNum }, + entries: { + toCreate: entriesToCreate.length, + toUpdate: entriesToUpdate.length, + toDelete: entryIdsToDelete.size, + }, + }); + + await this.prisma.dBFundsTeam.update({ + where: { + id: dBFundsTeam.id, + }, + data: { + fundraisingEntries: { + deleteMany: { + id: { + in: [...entryIdsToDelete], + }, + }, + create: entriesToCreate, + update: entriesToUpdate.map(({ amount, id }) => ({ + where: { id }, + data: { + amount, + fundraisingEntry: { + upsert: { + update: { + assignments: { + deleteMany: {}, + }, + }, + create: {}, + }, + }, + }, + })), + }, + }, + include: { + fundraisingEntries: true, + }, + }); + } + + return Ok(None); + } catch (error) { + return handleRepositoryError(error); + } + } + + async getTeamsForDbFundsTeam( + dbFundsTeamParam: UniqueDbFundsTeamParam + ): Promise> { + try { + const team = await this.prisma.dBFundsTeam.findUnique({ + where: + "marathon" in dbFundsTeamParam + ? { + dbNum_marathonId: { + dbNum: dbFundsTeamParam.dbNum, + marathonId: dbFundsTeamParam.marathon.id, + }, + } + : dbFundsTeamParam, + include: { teams: true }, + }); + if (!team) { + return Err(new NotFoundError({ what: "Team" })); + } + return Ok(team.teams); + } catch (error) { + return handleRepositoryError(error); + } + } + + async assignTeamToDbFundsTeam( + teamParam: SimpleUniqueParam, + dbFundsTeamParam: + | UniqueDbFundsTeamParam + | { + dbNum: number; + } + ): Promise> { + try { + const team = await this.prisma.team.findUnique({ + where: teamParam, + }); + if (!team) { + return Err(new NotFoundError({ what: "Team" })); + } + await this.prisma.dBFundsTeam.update({ + where: + "dbNum" in dbFundsTeamParam + ? { + dbNum_marathonId: { + dbNum: dbFundsTeamParam.dbNum, + marathonId: + "marathon" in dbFundsTeamParam + ? dbFundsTeamParam.marathon.id + : team.marathonId, + }, + } + : dbFundsTeamParam, + data: { + teams: { + connect: { id: team.id }, + }, + }, + }); + return Ok(None); + } catch (error) { + return handleRepositoryError(error); + } + } + + async listDbFundsTeams(search: { + byDbNum?: number; + byName?: string; + onlyActive?: boolean; + }): Promise> { + try { + return Ok( + await this.prisma.dBFundsTeam.findMany({ + where: { + active: search.onlyActive ? true : undefined, + dbNum: search.byDbNum, + name: { + contains: search.byName, + }, + }, + orderBy: { + name: "asc", + }, + }) + ); + } catch (error) { + return handleRepositoryError(error); + } + } +} diff --git a/packages/server/src/repositories/fundraising/FundraisingRepository.ts b/packages/server/src/repositories/fundraising/FundraisingRepository.ts new file mode 100644 index 00000000..8489070e --- /dev/null +++ b/packages/server/src/repositories/fundraising/FundraisingRepository.ts @@ -0,0 +1,426 @@ +import { + DBFundsFundraisingEntry, + FundraisingAssignment, + FundraisingEntry, + Person, + Prisma, + PrismaClient, +} from "@prisma/client"; +import type { SortDirection } from "@ukdanceblue/common"; + +import { ActionDeniedError, NotFoundError } from "@ukdanceblue/common/error"; +import { Err, None, Ok, Option, Result, Some } from "ts-results-es"; +import { Service } from "typedi"; +import { + buildFundraisingEntryOrder, + buildFundraisingEntryWhere, +} from "./fundraisingEntryRepositoryUtils.js"; +import type { FilterItems } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; +import { UniquePersonParam } from "#repositories/person/PersonRepository.js"; +import { + RepositoryError, + SimpleUniqueParam, + handleRepositoryError, +} from "#repositories/shared.js"; + +const fundraisingEntryBooleanKeys = [] as const; +type FundraisingEntryBooleanKey = (typeof fundraisingEntryBooleanKeys)[number]; + +const fundraisingEntryDateKeys = [ + "donatedOn", + "createdAt", + "updatedAt", +] as const; +type FundraisingEntryDateKey = (typeof fundraisingEntryDateKeys)[number]; + +const fundraisingEntryIsNullKeys = [] as const; +type FundraisingEntryIsNullKey = (typeof fundraisingEntryIsNullKeys)[number]; + +const fundraisingEntryNumericKeys = ["amount"] as const; +type FundraisingEntryNumericKey = (typeof fundraisingEntryNumericKeys)[number]; + +const fundraisingEntryOneOfKeys = ["teamId"] as const; +type FundraisingEntryOneOfKey = (typeof fundraisingEntryOneOfKeys)[number]; + +const fundraisingEntryStringKeys = ["donatedTo", "donatedBy"] as const; +type FundraisingEntryStringKey = (typeof fundraisingEntryStringKeys)[number]; + +export type FundraisingEntryOrderKeys = + | "teamId" + | "donatedOn" + | "amount" + | "donatedTo" + | "donatedBy" + | "createdAt" + | "updatedAt"; + +export type FundraisingEntryFilters = FilterItems< + FundraisingEntryBooleanKey, + FundraisingEntryDateKey, + FundraisingEntryIsNullKey, + FundraisingEntryNumericKey, + FundraisingEntryOneOfKey, + FundraisingEntryStringKey +>; + +const defaultInclude = { + dbFundsEntry: true, +} satisfies Prisma.FundraisingEntryInclude; + +export type FundraisingEntryUniqueParam = SimpleUniqueParam; +export type FundraisingAssignmentUniqueParam = SimpleUniqueParam; + +@Service() +export class FundraisingEntryRepository { + constructor(private readonly prisma: PrismaClient) {} + + async findEntryByUnique( + param: FundraisingEntryUniqueParam + ): Promise< + Result< + FundraisingEntry & { dbFundsEntry: DBFundsFundraisingEntry }, + RepositoryError + > + > { + try { + const row = await this.prisma.fundraisingEntry.findUnique({ + where: param, + include: defaultInclude, + }); + if (!row) { + return Err(new NotFoundError({ what: "FundraisingEntry" })); + } + return Ok( + row as typeof row & { + dbFundsEntry: NonNullable; + } + ); + } catch (error: unknown) { + return handleRepositoryError(error); + } + } + + async findAssignmentByUnique( + param: FundraisingAssignmentUniqueParam + ): Promise> { + try { + const row = await this.prisma.fundraisingAssignment.findUnique({ + where: param, + }); + if (!row) { + return Err(new NotFoundError({ what: "FundraisingAssignment" })); + } + return Ok(row); + } catch (error: unknown) { + return handleRepositoryError(error); + } + } + + async getAssignmentsForEntry( + param: FundraisingEntryUniqueParam + ): Promise> { + try { + const entry = await this.prisma.fundraisingEntry.findUnique({ + where: param, + select: { assignments: true }, + }); + if (!entry) { + return Err(new NotFoundError({ what: "FundraisingEntry" })); + } + return Ok(entry.assignments); + } catch (error: unknown) { + return handleRepositoryError(error); + } + } + + async listEntries( + { + filters, + order, + skip, + take, + }: { + filters?: readonly FundraisingEntryFilters[] | undefined | null; + order?: + | readonly [key: FundraisingEntryOrderKeys, sort: SortDirection][] + | undefined + | null; + skip?: number | undefined | null; + take?: number | undefined | null; + }, + limits: { + assignedToPerson?: SimpleUniqueParam | undefined | null; + forTeam?: SimpleUniqueParam | undefined | null; + } = {} + ): Promise< + Result< + readonly (FundraisingEntry & { + dbFundsEntry: DBFundsFundraisingEntry; + })[], + RepositoryError | ActionDeniedError + > + > { + try { + const whereResult = buildFundraisingEntryWhere(filters); + const orderByResult = buildFundraisingEntryOrder(order); + if (whereResult.isErr()) { + return Err(whereResult.error); + } + if (orderByResult.isErr()) { + return Err(orderByResult.error); + } + const where = whereResult.value; + const orderBy = orderByResult.value; + + if (limits.assignedToPerson) { + where.assignments = { + ...where.assignments, + every: { + ...where.assignments?.every, + person: limits.assignedToPerson, + }, + }; + } + if (limits.forTeam) { + where.dbFundsEntry = { + ...where.dbFundsEntry, + // @ts-expect-error Don't know why this is causing an error, but I'm not going to worry about it + dbFundsTeam: { + teams: { + some: limits.forTeam, + }, + // This 'satisfies' is to make sure that we don't accidentally ignore errors due to the ts-expect-error above + } satisfies Prisma.DBFundsTeamWhereInput, + }; + } + + const rows = await this.prisma.fundraisingEntry.findMany({ + include: defaultInclude, + where, + orderBy, + skip: skip ?? undefined, + take: take ?? undefined, + }); + + return Ok( + rows.filter( + ( + row + ): row is typeof row & { + dbFundsEntry: NonNullable; + } => Boolean(row.dbFundsEntry) + ) + ); + } catch (error: unknown) { + return handleRepositoryError(error); + } + } + + async countEntries({ + filters, + }: { + filters?: readonly FundraisingEntryFilters[] | undefined | null; + }): Promise> { + try { + const where = buildFundraisingEntryWhere(filters); + if (where.isErr()) { + return Err(where.error); + } + + return Ok( + await this.prisma.fundraisingEntry.count({ where: where.value }) + ); + } catch (error: unknown) { + return handleRepositoryError(error); + } + } + + async deleteEntry( + param: FundraisingEntryUniqueParam + ): Promise, RepositoryError>> { + try { + return Ok( + Some(await this.prisma.fundraisingEntry.delete({ where: param })) + ); + } catch (error) { + if ( + error instanceof Prisma.PrismaClientKnownRequestError && + error.code === "P2025" + ) { + return Ok(None); + } else { + return handleRepositoryError(error); + } + } + } + + async addAssignmentToEntry( + entryParam: FundraisingEntryUniqueParam, + personParam: SimpleUniqueParam, + { amount }: { amount: number } + ): Promise< + Result + > { + try { + const entry = await this.findEntryByUnique(entryParam); + if (entry.isErr()) { + return Err(entry.error); + } + const assignments = await this.getAssignmentsForEntry(entryParam); + if (assignments.isErr()) { + return Err(assignments.error); + } + + const totalAssigned = assignments.value.reduce( + (acc, assignment) => acc.add(assignment.amount), + new Prisma.Decimal(0) + ); + if (entry.value.dbFundsEntry.amount.lessThan(totalAssigned.add(amount))) { + return Err( + new ActionDeniedError("Total assigned amount exceeds entry amount") + ); + } + + return Ok( + await this.prisma.fundraisingAssignment.create({ + data: { + amount, + parentEntry: { connect: entryParam }, + person: { connect: personParam }, + }, + }) + ); + } catch (error: unknown) { + return handleRepositoryError(error); + } + } + + async deleteAssignment( + assignmentParam: FundraisingAssignmentUniqueParam + ): Promise> { + try { + return Ok( + await this.prisma.fundraisingAssignment.delete({ + where: assignmentParam, + }) + ); + } catch (error: unknown) { + return handleRepositoryError(error); + } + } + + async updateAssignment( + assignmentParam: FundraisingAssignmentUniqueParam, + { amount }: { amount: number } + ): Promise< + Result + > { + try { + const assignment = await this.prisma.fundraisingAssignment.findUnique({ + where: assignmentParam, + select: { + id: true, + parentEntry: { + select: { dbFundsEntry: { select: { amount: true } }, id: true }, + }, + }, + }); + if (!assignment) { + return Err(new NotFoundError({ what: "FundraisingEntry" })); + } + const assignments = await this.getAssignmentsForEntry({ + id: assignment.parentEntry.id, + }); + if (assignments.isErr()) { + return Err(assignments.error); + } + + const totalAssigned = assignments.value + .filter((a) => a.id !== assignment.id) + .reduce( + (acc, assignment) => acc.add(assignment.amount), + new Prisma.Decimal(0) + ); + if ( + assignment.parentEntry.dbFundsEntry.amount.lessThan( + totalAssigned.add(amount) + ) + ) { + return Err( + new ActionDeniedError("Total assigned amount exceeds entry amount") + ); + } + + return Ok( + await this.prisma.fundraisingAssignment.update({ + where: assignmentParam, + data: { amount }, + }) + ); + } catch (error: unknown) { + return handleRepositoryError(error); + } + } + + async getPersonForAssignment( + assignmentParam: FundraisingAssignmentUniqueParam + ): Promise> { + try { + const assignment = await this.prisma.fundraisingAssignment.findUnique({ + where: assignmentParam, + select: { person: true }, + }); + if (!assignment) { + return Err(new NotFoundError({ what: "FundraisingAssignment" })); + } + return Ok(assignment.person); + } catch (error: unknown) { + return handleRepositoryError(error); + } + } + + async getEntryForAssignment( + assignmentParam: FundraisingAssignmentUniqueParam + ): Promise< + Result< + FundraisingEntry & { + dbFundsEntry: DBFundsFundraisingEntry; + }, + RepositoryError + > + > { + try { + const assignment = await this.prisma.fundraisingAssignment.findUnique({ + where: assignmentParam, + select: { + parentEntry: { + include: defaultInclude, + }, + }, + }); + if (!assignment) { + return Err(new NotFoundError({ what: "FundraisingAssignment" })); + } + return Ok( + assignment.parentEntry as typeof assignment.parentEntry & { + dbFundsEntry: NonNullable; + } + ); + } catch (error: unknown) { + return handleRepositoryError(error); + } + } + + async getAssignmentsForPerson( + personParam: UniquePersonParam + ): Promise> { + try { + const assignments = await this.prisma.fundraisingAssignment.findMany({ + where: { person: personParam }, + }); + return Ok(assignments); + } catch (error: unknown) { + return handleRepositoryError(error); + } + } +} diff --git a/packages/server/src/repositories/fundraising/fundraisingAssignmentModelToNode.ts b/packages/server/src/repositories/fundraising/fundraisingAssignmentModelToNode.ts new file mode 100644 index 00000000..49eada8a --- /dev/null +++ b/packages/server/src/repositories/fundraising/fundraisingAssignmentModelToNode.ts @@ -0,0 +1,15 @@ +import type { FundraisingAssignment } from "@prisma/client"; +import { FundraisingAssignmentNode } from "@ukdanceblue/common"; + +export function fundraisingAssignmentModelToNode( + marathonHourModel: FundraisingAssignment +): Promise { + return Promise.resolve( + FundraisingAssignmentNode.init({ + id: marathonHourModel.uuid, + amount: marathonHourModel.amount.toNumber(), + createdAt: marathonHourModel.createdAt, + updatedAt: marathonHourModel.updatedAt, + }) + ); +} diff --git a/packages/server/src/repositories/fundraising/fundraisingEntryModelToNode.ts b/packages/server/src/repositories/fundraising/fundraisingEntryModelToNode.ts new file mode 100644 index 00000000..1858522f --- /dev/null +++ b/packages/server/src/repositories/fundraising/fundraisingEntryModelToNode.ts @@ -0,0 +1,20 @@ +import type { DBFundsFundraisingEntry, FundraisingEntry } from "@prisma/client"; +import { FundraisingEntryNode } from "@ukdanceblue/common"; + +export function fundraisingEntryModelToNode( + entryModel: FundraisingEntry & { + dbFundsEntry: DBFundsFundraisingEntry; + } +): Promise { + return Promise.resolve( + FundraisingEntryNode.init({ + id: entryModel.uuid, + amount: entryModel.dbFundsEntry.amount.toNumber(), + donatedByText: entryModel.dbFundsEntry.donatedBy, + donatedToText: entryModel.dbFundsEntry.donatedTo, + donatedOn: entryModel.dbFundsEntry.date, + createdAt: entryModel.createdAt, + updatedAt: entryModel.updatedAt, + }) + ); +} diff --git a/packages/server/src/repositories/fundraising/fundraisingEntryRepositoryUtils.ts b/packages/server/src/repositories/fundraising/fundraisingEntryRepositoryUtils.ts new file mode 100644 index 00000000..32345d5f --- /dev/null +++ b/packages/server/src/repositories/fundraising/fundraisingEntryRepositoryUtils.ts @@ -0,0 +1,114 @@ +import type { Prisma } from "@prisma/client"; +import { SortDirection } from "@ukdanceblue/common"; +import { ActionDeniedError } from "@ukdanceblue/common/error"; +import type { Result } from "ts-results-es"; +import { Err, Ok } from "ts-results-es"; + +import type { + FundraisingEntryFilters, + FundraisingEntryOrderKeys, +} from "./FundraisingRepository.js"; +import { + dateFilterToPrisma, + numericFilterToPrisma, + oneOfFilterToPrisma, + stringFilterToPrisma, +} from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; + +export function buildFundraisingEntryOrder( + order: + | readonly [key: FundraisingEntryOrderKeys, sort: SortDirection][] + | null + | undefined +): Result { + const orderBy: Prisma.FundraisingEntryOrderByWithRelationInput = {}; + const dbFundsEntryOrderBy: Prisma.FundraisingEntryOrderByWithRelationInput["dbFundsEntry"] = + {}; + + for (const [key, sort] of order ?? []) { + switch (key) { + case "donatedOn": { + dbFundsEntryOrderBy["date"] = + sort === SortDirection.asc ? "asc" : "desc"; + break; + } + case "amount": + case "donatedTo": + case "donatedBy": { + dbFundsEntryOrderBy[key] = sort === SortDirection.asc ? "asc" : "desc"; + break; + } + case "createdAt": + case "updatedAt": { + orderBy[key] = sort === SortDirection.asc ? "asc" : "desc"; + break; + } + case "teamId": { + return Err(new ActionDeniedError("Cannot sort by teamId")); + } + default: { + key satisfies never; + return Err( + new ActionDeniedError(`Unsupported sort key: ${String(key)}`) + ); + } + } + } + + if (Object.keys(dbFundsEntryOrderBy).length > 0) { + orderBy["dbFundsEntry"] = dbFundsEntryOrderBy; + } + + return Ok(orderBy); +} + +export function buildFundraisingEntryWhere( + filters: readonly FundraisingEntryFilters[] | null | undefined +): Result { + const where: Prisma.FundraisingEntryWhereInput = {}; + const dbFundsEntryWhere: Prisma.FundraisingEntryWhereInput["dbFundsEntry"] = + {}; + + for (const filter of filters ?? []) { + switch (filter.field) { + case "updatedAt": + case "createdAt": { + where[filter.field] = dateFilterToPrisma(filter); + break; + } + case "amount": { + dbFundsEntryWhere[filter.field] = numericFilterToPrisma(filter); + break; + } + case "donatedOn": { + dbFundsEntryWhere["date"] = dateFilterToPrisma(filter); + break; + } + case "donatedTo": + case "donatedBy": { + dbFundsEntryWhere[filter.field] = stringFilterToPrisma(filter); + break; + } + case "teamId": { + dbFundsEntryWhere.dbFundsTeam = { + teams: { some: { uuid: oneOfFilterToPrisma(filter) } }, + }; + break; + } + default: { + filter satisfies never; + return Err( + new ActionDeniedError( + `Unsupported filter key: ${String((filter as { field?: string } | undefined)?.field)}` + ) + ); + } + } + } + + if (Object.keys(dbFundsEntryWhere).length > 0) { + where["dbFundsEntry"] = dbFundsEntryWhere; + } + + return Ok(where); +} diff --git a/packages/server/src/repositories/image/ImageRepository.ts b/packages/server/src/repositories/image/ImageRepository.ts index 97319488..2e632bf5 100644 --- a/packages/server/src/repositories/image/ImageRepository.ts +++ b/packages/server/src/repositories/image/ImageRepository.ts @@ -2,9 +2,9 @@ import { Prisma, PrismaClient } from "@prisma/client"; import type { SortDirection } from "@ukdanceblue/common"; import { Service } from "typedi"; -import type { FilterItems } from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; - import { buildImageOrder, buildImageWhere } from "./imageRepositoryUtils.js"; +import type { FilterItems } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; + const imageBooleanKeys = [] as const; type ImageBooleanKey = (typeof imageBooleanKeys)[number]; diff --git a/packages/server/src/repositories/image/imageModelToResource.ts b/packages/server/src/repositories/image/imageModelToResource.ts index a8b56031..b24ea651 100644 --- a/packages/server/src/repositories/image/imageModelToResource.ts +++ b/packages/server/src/repositories/image/imageModelToResource.ts @@ -1,14 +1,14 @@ import type { File, Image } from "@prisma/client"; -import { ImageResource } from "@ukdanceblue/common"; +import { ImageNode } from "@ukdanceblue/common"; -import type { FileManager } from "../../lib/files/FileManager.js"; -import { combineMimePartsToString } from "../../lib/files/mime.js"; +import type { FileManager } from "#files/FileManager.js"; +import { combineMimePartsToString } from "#files/mime.js"; export async function imageModelToResource( imageModel: Image, fileModel: File | undefined | null, fileManager: FileManager -): Promise { +): Promise { let fileData: | { url: URL; @@ -30,8 +30,8 @@ export async function imageModelToResource( } } - return ImageResource.init({ - uuid: imageModel.uuid, + return ImageNode.init({ + id: imageModel.uuid, url: fileData?.url ?? null, mimeType: fileData?.mimeType ?? "application/octet-stream", // "application/octet-stream" is the default MIME type if the file is not found thumbHash: imageModel.thumbHash?.toString("base64"), diff --git a/packages/server/src/repositories/image/imageRepositoryUtils.ts b/packages/server/src/repositories/image/imageRepositoryUtils.ts index 8c946947..7cd05775 100644 --- a/packages/server/src/repositories/image/imageRepositoryUtils.ts +++ b/packages/server/src/repositories/image/imageRepositoryUtils.ts @@ -1,13 +1,13 @@ import type { Prisma } from "@prisma/client"; import { SortDirection } from "@ukdanceblue/common"; +import type { ImageFilters } from "./ImageRepository.ts"; import { dateFilterToPrisma, numericFilterToPrisma, stringFilterToPrisma, -} from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; +} from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; -import type { ImageFilters } from "./ImageRepository.ts"; export function buildImageOrder( order: readonly [key: string, sort: SortDirection][] | null | undefined @@ -21,7 +21,7 @@ export function buildImageOrder( case "createdAt": case "alt": case "updatedAt": { - orderBy[key] = sort === SortDirection.ASCENDING ? "asc" : "desc"; + orderBy[key] = sort === SortDirection.asc ? "asc" : "desc"; break; } default: { diff --git a/packages/server/src/repositories/marathon/MarathonRepository.ts b/packages/server/src/repositories/marathon/MarathonRepository.ts index 5cbae147..c15b08e0 100644 --- a/packages/server/src/repositories/marathon/MarathonRepository.ts +++ b/packages/server/src/repositories/marathon/MarathonRepository.ts @@ -1,13 +1,18 @@ -import { Prisma, PrismaClient } from "@prisma/client"; +import { Marathon, MarathonHour, Prisma, PrismaClient } from "@prisma/client"; import type { SortDirection } from "@ukdanceblue/common"; +import { NotFoundError } from "@ukdanceblue/common/error"; +import { Err, Ok, Result } from "ts-results-es"; import { Service } from "typedi"; -import type { FilterItems } from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; - import { buildMarathonOrder, buildMarathonWhere, } from "./marathonRepositoryUtils.js"; +import type { FilterItems } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; +import { + handleRepositoryError, + type RepositoryError, +} from "#repositories/shared.js"; const marathonBooleanKeys = [] as const; type MarathonBooleanKey = (typeof marathonBooleanKeys)[number]; @@ -49,28 +54,56 @@ export type MarathonFilters = FilterItems< >; // type UniqueParam = { id: number } | { uuid: string }; -type UniqueMarathonParam = { id: number } | { uuid: string } | { year: string }; +export type UniqueMarathonParam = + | { id: number } + | { uuid: string } + | { year: string }; @Service() export class MarathonRepository { constructor(private prisma: PrismaClient) {} - findMarathonByUnique(param: UniqueMarathonParam) { - return this.prisma.marathon.findUnique({ where: param }); + async findMarathonByUnique( + param: UniqueMarathonParam + ): Promise> { + try { + const row = await this.prisma.marathon.findUnique({ where: param }); + if (!row) { + return Err(new NotFoundError({ what: "Marathon" })); + } + return Ok(row); + } catch (error) { + return handleRepositoryError(error); + } } - findCurrentMarathon() { - return this.prisma.marathon.findFirst({ - orderBy: { year: "desc" }, - where: { startDate: { lte: new Date() }, endDate: { gte: new Date() } }, - }); + async findCurrentMarathon(): Promise> { + try { + const marathon = await this.prisma.marathon.findFirst({ + orderBy: { year: "asc" }, + where: { startDate: { lte: new Date() }, endDate: { gte: new Date() } }, + }); + if (!marathon) { + return Err(new NotFoundError({ what: "Marathon" })); + } + return Ok(marathon); + } catch (error) { + return handleRepositoryError(error); + } } - findNextMarathon() { - return this.prisma.marathon.findFirst({ - orderBy: { year: "asc" }, - where: { endDate: { gte: new Date() } }, - }); + async findActiveMarathon(): Promise> { + try { + const marathon = await this.prisma.marathon.findFirst({ + orderBy: { year: "asc" }, + }); + if (!marathon) { + return Err(new NotFoundError({ what: "Marathon" })); + } + return Ok(marathon); + } catch (error) { + return handleRepositoryError(error); + } } listMarathons({ @@ -108,33 +141,44 @@ export class MarathonRepository { return this.prisma.marathon.count({ where }); } - async getMarathonHours(param: UniqueMarathonParam) { - const rows = await this.prisma.marathon.findUnique({ - where: param, - include: { hours: true }, - }); - return rows?.hours ?? []; + async getMarathonHours( + param: UniqueMarathonParam + ): Promise> { + try { + const rows = await this.prisma.marathon.findUnique({ + where: param, + include: { hours: true }, + }); + return Ok(rows?.hours ?? []); + } catch (error) { + return handleRepositoryError(error); + } } - createMarathon({ + async createMarathon({ year, startDate, endDate, }: { year: string; - startDate: string; - endDate: string; - }) { - return this.prisma.marathon.create({ - data: { - year, - startDate, - endDate, - }, - }); + startDate?: string; + endDate?: string; + }): Promise> { + try { + const marathon = await this.prisma.marathon.create({ + data: { + year, + startDate, + endDate, + }, + }); + return Ok(marathon); + } catch (error) { + return handleRepositoryError(error); + } } - updateMarathon( + async updateMarathon( param: UniqueMarathonParam, { year, @@ -145,9 +189,9 @@ export class MarathonRepository { startDate?: string; endDate?: string; } - ) { + ): Promise> { try { - return this.prisma.marathon.update({ + const marathon = await this.prisma.marathon.update({ where: param, data: { year, @@ -155,29 +199,33 @@ export class MarathonRepository { endDate, }, }); + return Ok(marathon); } catch (error) { if ( error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2025" ) { - return null; + return Err(new NotFoundError({ what: "Marathon" })); } else { - throw error; + return handleRepositoryError(error); } } } - deleteMarathon(param: UniqueMarathonParam) { + async deleteMarathon( + param: UniqueMarathonParam + ): Promise> { try { - return this.prisma.marathon.delete({ where: param }); + const marathon = await this.prisma.marathon.delete({ where: param }); + return Ok(marathon); } catch (error) { if ( error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2025" ) { - return null; + return Err(new NotFoundError({ what: "Marathon" })); } else { - throw error; + return handleRepositoryError(error); } } } diff --git a/packages/server/src/repositories/marathon/marathonModelToResource.ts b/packages/server/src/repositories/marathon/marathonModelToResource.ts index 409a9d4b..424675b5 100644 --- a/packages/server/src/repositories/marathon/marathonModelToResource.ts +++ b/packages/server/src/repositories/marathon/marathonModelToResource.ts @@ -1,14 +1,12 @@ import type { Marathon } from "@prisma/client"; -import { MarathonResource } from "@ukdanceblue/common"; +import { MarathonNode } from "@ukdanceblue/common"; -export function marathonModelToResource( - marathonModel: Marathon -): MarathonResource { - return MarathonResource.init({ - uuid: marathonModel.uuid, +export function marathonModelToResource(marathonModel: Marathon): MarathonNode { + return MarathonNode.init({ + id: marathonModel.uuid, year: marathonModel.year, - startDate: marathonModel.startDate.toISOString(), - endDate: marathonModel.endDate.toISOString(), + startDate: marathonModel.startDate, + endDate: marathonModel.endDate, createdAt: marathonModel.createdAt, updatedAt: marathonModel.updatedAt, }); diff --git a/packages/server/src/repositories/marathon/marathonRepositoryUtils.ts b/packages/server/src/repositories/marathon/marathonRepositoryUtils.ts index 5024482c..c9e03920 100644 --- a/packages/server/src/repositories/marathon/marathonRepositoryUtils.ts +++ b/packages/server/src/repositories/marathon/marathonRepositoryUtils.ts @@ -1,15 +1,15 @@ import type { Prisma } from "@prisma/client"; import { SortDirection } from "@ukdanceblue/common"; -import { - dateFilterToPrisma, - oneOfFilterToPrisma, -} from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; - import type { MarathonFilters, MarathonOrderKeys, } from "./MarathonRepository.ts"; +import { + dateFilterToPrisma, + oneOfFilterToPrisma, +} from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; + export function buildMarathonOrder( order: @@ -26,7 +26,7 @@ export function buildMarathonOrder( case "endDate": case "createdAt": case "updatedAt": { - orderBy[key] = sort === SortDirection.ASCENDING ? "asc" : "desc"; + orderBy[key] = sort === SortDirection.asc ? "asc" : "desc"; break; } default: { diff --git a/packages/server/src/repositories/marathonHour/MarathonHourRepository.ts b/packages/server/src/repositories/marathonHour/MarathonHourRepository.ts index 35ced2f8..d3592bda 100644 --- a/packages/server/src/repositories/marathonHour/MarathonHourRepository.ts +++ b/packages/server/src/repositories/marathonHour/MarathonHourRepository.ts @@ -2,12 +2,12 @@ import { Prisma, PrismaClient } from "@prisma/client"; import type { SortDirection } from "@ukdanceblue/common"; import { Service } from "typedi"; -import type { FilterItems } from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; - import { buildMarathonHourOrder, buildMarathonHourWhere, } from "./marathonHourRepositoryUtils.js"; +import type { FilterItems } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; + const marathonHourBooleanKeys = [] as const; type MarathonHourBooleanKey = (typeof marathonHourBooleanKeys)[number]; diff --git a/packages/server/src/repositories/marathonHour/marathonHourModelToResource.test.ts b/packages/server/src/repositories/marathonHour/marathonHourModelToResource.test.ts index a3a59b7b..1a790cc2 100644 --- a/packages/server/src/repositories/marathonHour/marathonHourModelToResource.test.ts +++ b/packages/server/src/repositories/marathonHour/marathonHourModelToResource.test.ts @@ -1,11 +1,11 @@ import type { MarathonHour } from "@prisma/client"; -import { MarathonHourResource } from "@ukdanceblue/common"; +import { MarathonHourNode } from "@ukdanceblue/common"; import { describe, expect, it } from "vitest"; import { marathonHourModelToResource } from "./marathonHourModelToResource.js"; describe("marathonHourModelToResource", () => { - it("should correctly transform MarathonHour to MarathonHourResource", () => { + it("should correctly transform MarathonHour to MarathonHourNode", () => { const now = new Date(); const marathonHourModel: MarathonHour = { id: 0, @@ -23,8 +23,8 @@ describe("marathonHourModelToResource", () => { const nowString = now.toISOString(); - expect(result).toBeInstanceOf(MarathonHourResource); - expect(result.uuid).toBe("test-uuid"); + expect(result).toBeInstanceOf(MarathonHourNode); + expect(result.id).toBe("test-uuid"); expect(result.title).toBe("test-title"); expect(result.details).toBe("test-details"); expect(result.durationInfo).toBe("test-durationInfo"); diff --git a/packages/server/src/repositories/marathonHour/marathonHourModelToResource.ts b/packages/server/src/repositories/marathonHour/marathonHourModelToResource.ts index 6e749575..880df403 100644 --- a/packages/server/src/repositories/marathonHour/marathonHourModelToResource.ts +++ b/packages/server/src/repositories/marathonHour/marathonHourModelToResource.ts @@ -1,15 +1,15 @@ import type { MarathonHour } from "@prisma/client"; -import { MarathonHourResource } from "@ukdanceblue/common"; +import { MarathonHourNode } from "@ukdanceblue/common"; export function marathonHourModelToResource( marathonHourModel: MarathonHour -): MarathonHourResource { - return MarathonHourResource.init({ - uuid: marathonHourModel.uuid, +): MarathonHourNode { + return MarathonHourNode.init({ + id: marathonHourModel.uuid, title: marathonHourModel.title, details: marathonHourModel.details, durationInfo: marathonHourModel.durationInfo, - shownStartingAt: marathonHourModel.shownStartingAt.toISOString(), + shownStartingAt: marathonHourModel.shownStartingAt, createdAt: marathonHourModel.createdAt, updatedAt: marathonHourModel.updatedAt, }); diff --git a/packages/server/src/repositories/marathonHour/marathonHourRepositoryUtils.ts b/packages/server/src/repositories/marathonHour/marathonHourRepositoryUtils.ts index bb6d1a6c..26072c9d 100644 --- a/packages/server/src/repositories/marathonHour/marathonHourRepositoryUtils.ts +++ b/packages/server/src/repositories/marathonHour/marathonHourRepositoryUtils.ts @@ -1,16 +1,16 @@ import type { Prisma } from "@prisma/client"; import { SortDirection } from "@ukdanceblue/common"; +import type { + MarathonHourFilters, + MarathonHourOrderKeys, +} from "./MarathonHourRepository.ts"; import { dateFilterToPrisma, oneOfFilterToPrisma, stringFilterToPrisma, -} from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; +} from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; -import type { - MarathonHourFilters, - MarathonHourOrderKeys, -} from "./MarathonHourRepository.ts"; export function buildMarathonHourOrder( order: @@ -28,12 +28,12 @@ export function buildMarathonHourOrder( case "shownStartingAt": case "createdAt": case "updatedAt": { - orderBy[key] = sort === SortDirection.ASCENDING ? "asc" : "desc"; + orderBy[key] = sort === SortDirection.asc ? "asc" : "desc"; break; } case "marathonYear": { orderBy.marathon = { - year: sort === SortDirection.ASCENDING ? "asc" : "desc", + year: sort === SortDirection.asc ? "asc" : "desc", }; break; } diff --git a/packages/server/src/repositories/membership/MembershipRepository.ts b/packages/server/src/repositories/membership/MembershipRepository.ts index ad811218..696b3bd5 100644 --- a/packages/server/src/repositories/membership/MembershipRepository.ts +++ b/packages/server/src/repositories/membership/MembershipRepository.ts @@ -1,13 +1,15 @@ -import { Prisma, PrismaClient } from "@prisma/client"; -import type { SortDirection } from "@ukdanceblue/common"; +import { Membership, Person, PrismaClient, Team } from "@prisma/client"; +import { CommitteeRole, MembershipPositionType } from "@ukdanceblue/common"; +import { NotFoundError } from "@ukdanceblue/common/error"; +import { Err, Ok, Result } from "ts-results-es"; import { Service } from "typedi"; -import type { FilterItems } from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; - +import type { FilterItems } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; import { - buildMembershipOrder, - buildMembershipWhere, -} from "./membershipRepositoryUtils.js"; + handleRepositoryError, + type RepositoryError, + type SimpleUniqueParam, +} from "#repositories/shared.js"; const membershipBooleanKeys = [] as const; type MembershipBooleanKey = (typeof membershipBooleanKeys)[number]; @@ -42,81 +44,205 @@ type UniqueMembershipParam = { id: number } | { uuid: string }; export class MembershipRepository { constructor(private prisma: PrismaClient) {} - findMembershipByUnique( + async findMembershipByUnique( param: UniqueMembershipParam, - include: Prisma.MembershipInclude - ) { - return this.prisma.membership.findUnique({ where: param, include }); - } - - listMemberships({ - filters, - order, - skip, - take, - }: { - filters?: readonly MembershipFilters[] | undefined | null; - order?: readonly [key: string, sort: SortDirection][] | undefined | null; - skip?: number | undefined | null; - take?: number | undefined | null; - }) { - const where = buildMembershipWhere(filters); - const orderBy = buildMembershipOrder(order); - - return this.prisma.membership.findMany({ - where, - orderBy, - skip: skip ?? undefined, - take: take ?? undefined, - }); + include?: { + person?: false; + team?: false; + } + ): Promise>; + async findMembershipByUnique( + param: UniqueMembershipParam, + include: { + person: true; + team?: false; + } + ): Promise>; + async findMembershipByUnique( + param: UniqueMembershipParam, + include: { + person?: false; + team: true; + } + ): Promise>; + async findMembershipByUnique( + param: UniqueMembershipParam, + include: { + person: true; + team: true; + } + ): Promise< + Result + >; + async findMembershipByUnique( + param: UniqueMembershipParam, + include?: { + person?: boolean; + team?: boolean; + } + ): Promise< + Result< + | Membership + | (Membership & { + person: Person; + }) + | (Membership & { + team: Team; + }) + | (Membership & { + person: Person; + team: Team; + }), + RepositoryError + > + > { + try { + const membership = await this.prisma.membership.findUnique({ + where: param, + include, + }); + if (!membership) { + return Err(new NotFoundError({ what: "Membership" })); + } + return Ok(membership); + } catch (error) { + return handleRepositoryError(error); + } } - countMemberships({ - filters, - }: { - filters?: readonly MembershipFilters[] | undefined | null; - }) { - const where = buildMembershipWhere(filters); - - return this.prisma.membership.count({ - where, - }); - } + private async lookupPersonAndTeamId( + personParam: SimpleUniqueParam, + teamParam: SimpleUniqueParam + ): Promise> { + try { + let personId, teamId; + if ("id" in personParam) { + personId = personParam.id; + } else if ("uuid" in personParam) { + const found = await this.prisma.person.findUnique({ + where: { uuid: personParam.uuid }, + select: { id: true }, + }); + if (found == null) { + return Err(new NotFoundError({ what: "Person" })); + } + personId = found.id; + } else { + throw new Error("Must provide either UUID or ID"); + } + if ("id" in teamParam) { + teamId = teamParam.id; + } else if ("uuid" in teamParam) { + const found = await this.prisma.team.findUnique({ + where: teamParam, + select: { id: true }, + }); + if (found == null) { + return Err(new NotFoundError({ what: "Team" })); + } + teamId = found.id; + } else { + throw new Error("Must provide either UUID or ID"); + } - createMembership(data: Prisma.MembershipCreateInput) { - return this.prisma.membership.create({ data }); + return Ok({ personId, teamId }); + } catch (error) { + return handleRepositoryError(error); + } } - updateMembership( - param: UniqueMembershipParam, - data: Prisma.MembershipUpdateInput - ) { + async assignPersonToTeam({ + personParam, + teamParam, + ...additionalData + }: { + personParam: SimpleUniqueParam; + teamParam: SimpleUniqueParam; + } & ( + | { + position: MembershipPositionType; + } + | { + committeeRole: CommitteeRole; + } + )): Promise> { try { - return this.prisma.membership.update({ where: param, data }); - } catch (error) { - if ( - error instanceof Prisma.PrismaClientKnownRequestError && - error.code === "P2025" - ) { - return null; + const result = await this.lookupPersonAndTeamId(personParam, teamParam); + if (result.isErr()) { + return Err(result.error); + } + const { personId, teamId } = result.value; + + let position: MembershipPositionType; + let committeeRole: CommitteeRole | undefined; + if ("position" in additionalData) { + position = additionalData.position; + } else if ("committeeRole" in additionalData) { + committeeRole = additionalData.committeeRole; + position = + additionalData.committeeRole === CommitteeRole.Chair + ? MembershipPositionType.Captain + : MembershipPositionType.Member; } else { - throw error; + throw new Error("Must provide either position or committeeRole"); } + + const membership = await this.prisma.membership.upsert({ + where: { + personId_teamId: { + personId, + teamId, + }, + team: { + correspondingCommitteeId: null, + }, + }, + create: { + person: { + connect: { + id: personId, + }, + }, + team: { + connect: { + id: teamId, + }, + }, + position, + committeeRole, + }, + update: {}, + }); + + return Ok(membership); + } catch (error) { + return handleRepositoryError(error); } } - deleteMembership(param: UniqueMembershipParam) { + async removePersonFromTeam( + personParam: SimpleUniqueParam, + teamParam: SimpleUniqueParam + ): Promise> { try { - return this.prisma.membership.delete({ where: param }); - } catch (error) { - if ( - error instanceof Prisma.PrismaClientKnownRequestError && - error.code === "P2025" - ) { - return null; - } else { - throw error; + const result = await this.lookupPersonAndTeamId(personParam, teamParam); + if (result.isErr()) { + return Err(result.error); } + const { personId, teamId } = result.value; + + const membership = await this.prisma.membership.delete({ + where: { + personId_teamId: { + personId, + teamId, + }, + }, + }); + + return Ok(membership); + } catch (error) { + return handleRepositoryError(error); } } } diff --git a/packages/server/src/repositories/membership/membershipModelToResource.ts b/packages/server/src/repositories/membership/membershipModelToResource.ts index c87d19b0..f0002e50 100644 --- a/packages/server/src/repositories/membership/membershipModelToResource.ts +++ b/packages/server/src/repositories/membership/membershipModelToResource.ts @@ -1,13 +1,39 @@ import type { Membership } from "@prisma/client"; -import { MembershipResource } from "@ukdanceblue/common"; +import type { CommitteeIdentifier } from "@ukdanceblue/common"; +import { + CommitteeMembershipNode, + DetailedError, + ErrorCode, + MembershipNode, +} from "@ukdanceblue/common"; export function membershipModelToResource( membershipModel: Membership -): MembershipResource { - return MembershipResource.init({ - uuid: membershipModel.uuid, +): MembershipNode { + return MembershipNode.init({ + id: membershipModel.uuid, position: membershipModel.position, createdAt: membershipModel.createdAt, updatedAt: membershipModel.updatedAt, }); } + +export function committeeMembershipModelToResource( + membershipModel: Membership, + committeeIdentifier: CommitteeIdentifier +): CommitteeMembershipNode { + if (!membershipModel.committeeRole) { + throw new DetailedError( + ErrorCode.PreconditionsFailed, + "Committee role is required" + ); + } + return CommitteeMembershipNode.init({ + id: membershipModel.uuid, + position: membershipModel.position, + identifier: committeeIdentifier, + role: membershipModel.committeeRole, + createdAt: membershipModel.createdAt, + updatedAt: membershipModel.updatedAt, + }); +} diff --git a/packages/server/src/repositories/membership/membershipRepositoryUtils.ts b/packages/server/src/repositories/membership/membershipRepositoryUtils.ts index ae5167d5..a95afc94 100644 --- a/packages/server/src/repositories/membership/membershipRepositoryUtils.ts +++ b/packages/server/src/repositories/membership/membershipRepositoryUtils.ts @@ -1,9 +1,9 @@ import type { Prisma } from "@prisma/client"; import { SortDirection } from "@ukdanceblue/common"; -import { dateFilterToPrisma } from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; - import type { MembershipFilters } from "./MembershipRepository.ts"; +import { dateFilterToPrisma } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; + export function buildMembershipOrder( order: readonly [key: string, sort: SortDirection][] | null | undefined @@ -14,7 +14,7 @@ export function buildMembershipOrder( switch (key) { case "createdAt": case "updatedAt": { - orderBy[key] = sort === SortDirection.ASCENDING ? "asc" : "desc"; + orderBy[key] = sort === SortDirection.asc ? "asc" : "desc"; break; } default: { diff --git a/packages/server/src/repositories/notification/NotificationRepository.ts b/packages/server/src/repositories/notification/NotificationRepository.ts index 740d65ad..2bb21eac 100644 --- a/packages/server/src/repositories/notification/NotificationRepository.ts +++ b/packages/server/src/repositories/notification/NotificationRepository.ts @@ -3,12 +3,11 @@ import { Prisma, PrismaClient } from "@prisma/client"; import type { SortDirection } from "@ukdanceblue/common"; import { Service } from "typedi"; -import type { FilterItems } from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; - import { buildNotificationOrder, buildNotificationWhere, } from "./notificationRepositoryUtils.js"; +import type { FilterItems } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; const notificationBooleanKeys = [] as const; type NotificationBooleanKey = (typeof notificationBooleanKeys)[number]; diff --git a/packages/server/src/repositories/notification/notificationModelToResource.ts b/packages/server/src/repositories/notification/notificationModelToResource.ts index 411e6b39..a426400d 100644 --- a/packages/server/src/repositories/notification/notificationModelToResource.ts +++ b/packages/server/src/repositories/notification/notificationModelToResource.ts @@ -1,11 +1,11 @@ import type { Notification } from "@prisma/client"; -import { NotificationResource } from "@ukdanceblue/common"; +import { NotificationNode } from "@ukdanceblue/common"; export function notificationModelToResource( notificationModel: Notification -): NotificationResource { - return NotificationResource.init({ - uuid: notificationModel.uuid, +): NotificationNode { + return NotificationNode.init({ + id: notificationModel.uuid, title: notificationModel.title, body: notificationModel.body, url: notificationModel.url ? new URL(notificationModel.url) : null, diff --git a/packages/server/src/repositories/notification/notificationRepositoryUtils.ts b/packages/server/src/repositories/notification/notificationRepositoryUtils.ts index 9fd9d30d..6f3c4973 100644 --- a/packages/server/src/repositories/notification/notificationRepositoryUtils.ts +++ b/packages/server/src/repositories/notification/notificationRepositoryUtils.ts @@ -1,16 +1,16 @@ import type { Prisma } from "@prisma/client"; import { SortDirection } from "@ukdanceblue/common"; +import type { + NotificationFilters, + NotificationOrderKeys, +} from "./NotificationRepository.ts"; import { dateFilterToPrisma, oneOfFilterToPrisma, stringFilterToPrisma, -} from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; +} from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; -import type { - NotificationFilters, - NotificationOrderKeys, -} from "./NotificationRepository.ts"; export function buildNotificationOrder( order: @@ -30,7 +30,7 @@ export function buildNotificationOrder( case "deliveryIssueAcknowledgedAt": case "sendAt": case "startedSendingAt": { - orderBy[key] = sort === SortDirection.ASCENDING ? "asc" : "desc"; + orderBy[key] = sort === SortDirection.asc ? "asc" : "desc"; break; } default: { diff --git a/packages/server/src/repositories/notificationDelivery/NotificationDeliveryRepository.ts b/packages/server/src/repositories/notificationDelivery/NotificationDeliveryRepository.ts index 20fe7d2d..6189cbe7 100644 --- a/packages/server/src/repositories/notificationDelivery/NotificationDeliveryRepository.ts +++ b/packages/server/src/repositories/notificationDelivery/NotificationDeliveryRepository.ts @@ -4,12 +4,12 @@ import type { ExpoPushReceipt, ExpoPushTicket } from "expo-server-sdk"; import type { DateTime } from "luxon"; import { Service } from "typedi"; -import type { FilterItems } from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; - import { buildNotificationDeliveryOrder, buildNotificationDeliveryWhere, } from "./notificationDeliveryRepositoryUtils.js"; +import type { FilterItems } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; + const notificationDeliveryBooleanKeys = [] as const; type NotificationDeliveryBooleanKey = diff --git a/packages/server/src/repositories/notificationDelivery/notificationDeliveryModelToResource.ts b/packages/server/src/repositories/notificationDelivery/notificationDeliveryModelToResource.ts index d82e6961..882ea7fc 100644 --- a/packages/server/src/repositories/notificationDelivery/notificationDeliveryModelToResource.ts +++ b/packages/server/src/repositories/notificationDelivery/notificationDeliveryModelToResource.ts @@ -1,11 +1,11 @@ import type { NotificationDelivery } from "@prisma/client"; -import { NotificationDeliveryResource } from "@ukdanceblue/common"; +import { NotificationDeliveryNode } from "@ukdanceblue/common"; export function notificationDeliveryModelToResource( notificationDeliveryModel: NotificationDelivery -): NotificationDeliveryResource { - return NotificationDeliveryResource.init({ - uuid: notificationDeliveryModel.uuid, +): NotificationDeliveryNode { + return NotificationDeliveryNode.init({ + id: notificationDeliveryModel.uuid, sentAt: notificationDeliveryModel.sentAt, receiptCheckedAt: notificationDeliveryModel.receiptCheckedAt, chunkUuid: notificationDeliveryModel.chunkUuid, diff --git a/packages/server/src/repositories/notificationDelivery/notificationDeliveryRepositoryUtils.ts b/packages/server/src/repositories/notificationDelivery/notificationDeliveryRepositoryUtils.ts index 8d404c38..454882b5 100644 --- a/packages/server/src/repositories/notificationDelivery/notificationDeliveryRepositoryUtils.ts +++ b/packages/server/src/repositories/notificationDelivery/notificationDeliveryRepositoryUtils.ts @@ -1,15 +1,15 @@ import type { Prisma } from "@prisma/client"; import { SortDirection } from "@ukdanceblue/common"; -import { - dateFilterToPrisma, - oneOfFilterToPrisma, -} from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; - import type { NotificationDeliveryFilters, NotificationDeliveryOrderKeys, } from "./NotificationDeliveryRepository.js"; +import { + dateFilterToPrisma, + oneOfFilterToPrisma, +} from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; + export function buildNotificationDeliveryOrder( order: @@ -26,7 +26,7 @@ export function buildNotificationDeliveryOrder( case "deliveryError": case "createdAt": case "updatedAt": { - orderBy[key] = sort === SortDirection.ASCENDING ? "asc" : "desc"; + orderBy[key] = sort === SortDirection.asc ? "asc" : "desc"; break; } default: { diff --git a/packages/server/src/repositories/person/PersonRepository.ts b/packages/server/src/repositories/person/PersonRepository.ts index 82e7a1ea..78665d20 100644 --- a/packages/server/src/repositories/person/PersonRepository.ts +++ b/packages/server/src/repositories/person/PersonRepository.ts @@ -1,20 +1,37 @@ -import type { Person } from "@prisma/client"; -import { AuthSource, Prisma, PrismaClient } from "@prisma/client"; -import type { - AuthIdPairResource, +import type { Committee, Membership, Person, Team } from "@prisma/client"; +import { Prisma, PrismaClient } from "@prisma/client"; +import { + AuthSource, CommitteeIdentifier, CommitteeRole, - RoleResource, + DbRole, + DetailedError, + EffectiveCommitteeRole, + ErrorCode, + MembershipPositionType, SortDirection, + TeamLegacyStatus, + TeamType, } from "@ukdanceblue/common"; -import { MembershipPositionType, TeamLegacyStatus } from "@ukdanceblue/common"; +import { ActionDeniedError , + InvalidArgumentError, + InvariantError, + NotFoundError, +} from "@ukdanceblue/common/error"; +import { Err, Ok, Result } from "ts-results-es"; import { Service } from "typedi"; -import { findPersonForLogin } from "../../lib/auth/findPersonForLogin.js"; -import type { FilterItems } from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; - import { buildPersonOrder, buildPersonWhere } from "./personRepositoryUtils.js"; +import { findPersonForLogin } from "#auth/findPersonForLogin.js"; +import type { FilterItems } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; +import type { UniqueMarathonParam } from "#repositories/marathon/MarathonRepository.js"; +import { + handleRepositoryError, + type RepositoryError, + type SimpleUniqueParam, +} from "#repositories/shared.js"; + const personStringKeys = ["name", "email", "linkblue"] as const; type PersonStringKey = (typeof personStringKeys)[number]; @@ -43,78 +60,208 @@ export type PersonOrderKeys = | "createdAt" | "updatedAt"; +export type UniquePersonParam = + | { + uuid: string; + } + | { + id: number; + } + | { + email: string; + } + | { + linkblue: string; + }; + @Service() export class PersonRepository { constructor(private prisma: PrismaClient) {} // Finders - findPersonForLogin( - authIds: [AuthSource, string][], - userData: { + async findPersonForLogin( + authIds: [Exclude, string][], + userInfo: { uuid?: string | null; email?: string | null; linkblue?: string | null; name?: string | null; - role?: RoleResource | null; + dbRole?: DbRole | null; }, memberOf?: (string | number)[], captainOf?: (string | number)[] - ) { - return findPersonForLogin( - this.prisma, - authIds, - userData, - memberOf, - captainOf - ); + ): Promise< + Result>, RepositoryError> + > { + try { + const row = await findPersonForLogin( + this.prisma, + authIds, + userInfo, + memberOf, + captainOf + ); + return Ok(row); + } catch (error) { + return handleRepositoryError(error); + } } - /** @deprecated Use findPersonByUnique directly */ - findPersonByUuid(uuid: string): Promise { - return this.prisma.person.findUnique({ where: { uuid } }); + async findPersonByUnique( + param: UniquePersonParam + ): Promise> { + try { + const row = await this.prisma.person.findUnique({ where: param }); + if (!row) { + return Err(new NotFoundError({ what: "Person" })); + } + return Ok(row); + } catch (error) { + return handleRepositoryError(error); + } } - /** @deprecated Use findPersonByUnique directly */ - findPersonById(id: number): Promise { - return this.prisma.person.findUnique({ where: { id } }); + async findPersonAndTeamsByUnique( + param: UniquePersonParam + ): Promise< + Result< + Person & { memberships: (Membership & { team: Team })[] }, + RepositoryError + > + > { + try { + const row = await this.prisma.person.findUnique({ + where: param, + include: { + memberships: { + include: { + team: true, + }, + }, + }, + }); + if (!row) { + return Err(new NotFoundError({ what: "Person" })); + } + return Ok(row); + } catch (error) { + return handleRepositoryError(error); + } } - /** @deprecated Use findPersonByUnique directly */ - findPersonByLinkblue(linkblue: string): Promise { - return this.prisma.person.findUnique({ where: { linkblue } }); - } + async getDbRoleOfPerson( + param: UniquePersonParam + ): Promise> { + try { + const person = await this.prisma.person.findUnique({ + where: param, + select: { + linkblue: true, + memberships: { + select: { + committeeRole: true, + }, + }, + }, + }); - async findPersonByUnique( - param: - | { uuid: string } - | { id: number } - | { email: string } - | { linkblue: string } - ) { - return this.prisma.person.findUnique({ where: param }); + if (!person) { + return Ok(DbRole.None); + } + if (person.memberships.some((m) => m.committeeRole != null)) { + return Ok(DbRole.Committee); + } + if (person.linkblue) { + return Ok(DbRole.UKY); + } + return Ok(DbRole.Public); + } catch (error) { + return handleRepositoryError(error); + } } - async findPersonAndTeamsByUnique( - param: - | { uuid: string } - | { id: number } - | { email: string } - | { linkblue: string } - ) { - return this.prisma.person.findUnique({ - where: param, - include: { - memberships: { - include: { - team: true, + async getEffectiveCommitteeRolesOfPerson( + param: UniquePersonParam + ): Promise< + Result + > { + try { + const effectiveCommitteeRoles: EffectiveCommitteeRole[] = []; + + const committees = await this.prisma.membership.findMany({ + where: { + person: param, + team: { + correspondingCommittee: { + isNot: null, + }, + }, + committeeRole: { + not: null, }, }, - }, - }); + select: { + team: { + select: { + correspondingCommittee: { + select: { + identifier: true, + parentCommittee: { + select: { + identifier: true, + }, + }, + childCommittees: { + select: { + identifier: true, + }, + }, + }, + }, + }, + }, + committeeRole: true, + }, + }); + + for (const committee of committees) { + if (committee.team.correspondingCommittee) { + if (!committee.committeeRole) { + return Err( + new InvariantError("No role found for committee membership") + ); + } + const role = EffectiveCommitteeRole.init( + committee.team.correspondingCommittee.identifier, + committee.committeeRole + ); + effectiveCommitteeRoles.push(role); + if (committee.team.correspondingCommittee.parentCommittee) { + const parentRole = EffectiveCommitteeRole.init( + committee.team.correspondingCommittee.parentCommittee.identifier, + CommitteeRole.Member + ); + effectiveCommitteeRoles.push(parentRole); + } + const childRoles = + committee.team.correspondingCommittee.childCommittees.map((child) => + EffectiveCommitteeRole.init( + child.identifier, + committee.committeeRole! + ) + ); + effectiveCommitteeRoles.push(...childRoles); + } + } + + return Ok(effectiveCommitteeRoles); + } catch (error) { + return handleRepositoryError(error); + } } - listPeople({ + async listPeople({ filters, order, skip, @@ -127,120 +274,226 @@ export class PersonRepository { | null; skip?: number | undefined | null; take?: number | undefined | null; - }): Promise { - const where: Prisma.PersonWhereInput = buildPersonWhere(filters); - const orderBy: Prisma.PersonOrderByWithRelationInput = - buildPersonOrder(order); - - return this.prisma.person.findMany({ - where, - orderBy, - skip: skip ?? undefined, - take: take ?? undefined, - }); + }): Promise> { + try { + const where: Prisma.PersonWhereInput = buildPersonWhere(filters); + const orderBy = buildPersonOrder(order); + if (orderBy.isErr()) { + return Err(orderBy.error); + } + + const rows = await this.prisma.person.findMany({ + where, + orderBy: orderBy.value, + skip: skip ?? undefined, + take: take ?? undefined, + }); + + return Ok(rows); + } catch (error) { + return handleRepositoryError(error); + } } - countPeople({ + async countPeople({ filters, }: { filters?: readonly PersonFilters[] | undefined | null; - }): Promise { - const where: Prisma.PersonWhereInput = buildPersonWhere(filters); + }): Promise> { + try { + const where: Prisma.PersonWhereInput = buildPersonWhere(filters); - return this.prisma.person.count({ where }); + return Ok(await this.prisma.person.count({ where })); + } catch (error) { + return handleRepositoryError(error); + } } - searchByName(name: string): Promise { - return this.prisma.person.findMany({ - where: { - name: { - contains: name, - mode: "insensitive", - }, - }, - }); + async searchByName(name: string): Promise> { + try { + return Ok( + await this.prisma.person.findMany({ + where: { + name: { + contains: name, + mode: "insensitive", + }, + }, + }) + ); + } catch (error) { + return handleRepositoryError(error); + } } - searchByLinkblue(linkblue: string): Promise { - return this.prisma.person.findMany({ - where: { - linkblue: { - contains: linkblue, - mode: "insensitive", + async searchByLinkblue( + linkblue: string + ): Promise> { + try { + return Ok( + await this.prisma.person.findMany({ + where: { + linkblue: { + contains: linkblue, + mode: "insensitive", + }, + }, + }) + ); + } catch (error) { + return handleRepositoryError(error); + } + } + + async findCommitteeMembershipsOfPerson( + param: UniquePersonParam + ): Promise< + Result< + (Membership & { team: { correspondingCommittee: Committee } })[], + RepositoryError + > + > { + try { + const rows = await this.prisma.person.findUnique({ + where: param, + select: { + memberships: { + where: { + team: { + type: TeamType.Committee, + correspondingCommittee: { + isNot: null, + }, + }, + }, + include: { + team: { + select: { + correspondingCommittee: true, + }, + }, + }, + }, }, - }, - }); + }); + + if (!rows) { + return Err(new NotFoundError({ what: "Person" })); + } + + return Ok( + rows.memberships.filter( + ( + m + ): m is typeof m & { + team: { correspondingCommittee: Committee }; + } => { + return m.team.correspondingCommittee != null; + } + ) + ); + } catch (error) { + return handleRepositoryError(error); + } } async findMembershipsOfPerson( - param: { uuid: string } | { id: number }, - opts: { - position?: MembershipPositionType; - } = {} - ) { - const rows = await this.prisma.person.findUnique({ - where: param, - select: { - memberships: { + param: UniquePersonParam, + opts: + | { + position: MembershipPositionType; + } + | { + committeeRole: CommitteeRole; + } + | Record = {}, + types: TeamType[] | undefined = undefined, + includeTeam: boolean = false + ): Promise> { + try { + const rows = await this.prisma.person + .findUnique({ + where: param, + }) + .memberships({ + include: { + team: includeTeam, + }, where: { - position: opts.position, + AND: [ + opts, + types + ? { + team: { + type: { + in: types, + }, + }, + } + : {}, + ], }, - }, - }, - }); + }); + + if (!rows) { + return Err(new NotFoundError({ what: "Person" })); + } - return rows?.memberships ?? null; + return Ok(rows); + } catch (error) { + return handleRepositoryError(error); + } } // Mutators - createPerson({ + async createPerson({ name, email, linkblue, - committeeRole, - committeeName, authIds, }: { name?: string | null; email: string; linkblue?: string | null; - committeeRole?: CommitteeRole | null; - committeeName?: CommitteeIdentifier | null; - authIds?: AuthIdPairResource>[] | null; - }): Promise { - return this.prisma.person.create({ - data: { - name, - email, - linkblue, - committeeRole, - committeeName, - authIdPairs: authIds - ? { - createMany: { - data: authIds.map((authId): Prisma.AuthIdPairCreateInput => { - return { - source: authId.source, - value: authId.value, - person: { connect: { email } }, - }; - }), - }, - } - : undefined, - }, - }); + authIds?: { source: Exclude; value: string }[] | null; + }): Promise> { + try { + return Ok( + await this.prisma.person.create({ + data: { + name, + email, + linkblue, + authIdPairs: authIds + ? { + createMany: { + data: authIds.map( + (authId): Prisma.AuthIdPairCreateInput => { + return { + source: authId.source, + value: authId.value, + person: { connect: { email } }, + }; + } + ), + }, + } + : undefined, + }, + }) + ); + } catch (error) { + return handleRepositoryError(error); + } } async updatePerson( - param: { uuid: string } | { id: number }, + param: UniquePersonParam, { name, email, linkblue, - committeeRole, - committeeName, authIds, memberOf, captainOf, @@ -248,229 +501,363 @@ export class PersonRepository { name?: string | undefined | null; email?: string | undefined; linkblue?: string | undefined | null; - committeeRole?: CommitteeRole | undefined | null; - committeeName?: CommitteeIdentifier | undefined | null; authIds?: | { source: Exclude; value: string }[] | undefined | null; - memberOf?: ({ uuid: string } | { id: number })[] | undefined | null; - captainOf?: ({ uuid: string } | { id: number })[] | undefined | null; + memberOf?: SimpleUniqueParam[] | undefined | null; + captainOf?: SimpleUniqueParam[] | undefined | null; } - ) { - let personId: number; - if ("id" in param) { - personId = param.id; - } else if ("uuid" in param) { - const found = await this.prisma.person.findUnique({ - where: { uuid: param.uuid }, - select: { id: true }, - }); - if (found == null) { - return null; + ): Promise> { + try { + let personId: number; + if ("id" in param) { + personId = param.id; + } else if ("uuid" in param) { + const found = await this.prisma.person.findUnique({ + where: { uuid: param.uuid }, + select: { id: true }, + }); + if (found == null) { + return Err(new NotFoundError({ what: "Person" })); + } + personId = found.id; + } else { + return Err(new InvalidArgumentError("Must provide either UUID or ID")); } - personId = found.id; - } else { - throw new Error("Must provide either UUID or ID"); - } - const [memberOfIds, captainOfIds] = await Promise.all([ - memberOf - ? Promise.all( - memberOf.map((team) => - this.prisma.team - .findUnique({ - where: team, - select: { id: true }, - }) - .then((team) => team?.id) + const [memberOfIds, captainOfIds] = await Promise.all([ + memberOf + ? Promise.all( + memberOf.map((team) => + this.prisma.team + .findUnique({ + where: team, + select: { id: true }, + }) + .then((team) => team?.id) + ) ) - ) - : Promise.resolve(null), - captainOf - ? Promise.all( - captainOf.map((team) => - this.prisma.team - .findUnique({ - where: team, - select: { id: true }, - }) - .then((team) => team?.id) + : Promise.resolve(null), + captainOf + ? Promise.all( + captainOf.map((team) => + this.prisma.team + .findUnique({ + where: team, + select: { id: true }, + }) + .then((team) => team?.id) + ) ) - ) - : Promise.resolve(null), - ]); + : Promise.resolve(null), + ]); - try { - return await this.prisma.person.update({ - where: param, - data: { - name, - email, - linkblue, - committeeRole, - committeeName, - authIdPairs: authIds - ? { - upsert: authIds.map((authId) => { - return { - create: { - source: authId.source, - value: authId.value, - }, - update: { - value: authId.value, - }, - where: { - personId_source: { - personId, + return Ok( + await this.prisma.person.update({ + where: param, + data: { + name, + email, + linkblue, + authIdPairs: authIds + ? { + upsert: authIds.map((authId) => { + return { + create: { source: authId.source, + value: authId.value, }, - }, - }; - }), - } - : undefined, - memberships: - // TODO: this nesting is nightmarish and should be refactored - memberOf || captainOf - ? { - connectOrCreate: [ - ...(memberOfIds - ?.filter( - (id): id is Exclude => id != null - ) - .map( - ( - teamId - ): Prisma.MembershipCreateOrConnectWithoutPersonInput => { - return { - where: { personId_teamId: { personId, teamId } }, - create: { - position: MembershipPositionType.Member, - team: { - connect: { - id: teamId, + update: { + value: authId.value, + }, + where: { + personId_source: { + personId, + source: authId.source, + }, + }, + }; + }), + } + : undefined, + memberships: + // How many indents is too many? This many. This many is too many + // TODO: this nesting is nightmarish and should be refactored + memberOf || captainOf + ? { + connectOrCreate: [ + ...(memberOfIds + ?.filter( + (id): id is Exclude => + id != null + ) + .map( + ( + teamId + ): Prisma.MembershipCreateOrConnectWithoutPersonInput => { + return { + where: { personId_teamId: { personId, teamId } }, + create: { + position: MembershipPositionType.Member, + team: { + connect: { + id: teamId, + }, }, }, - }, - }; - } - ) ?? []), - ...(captainOfIds - ?.filter( - (id): id is Exclude => id != null - ) - .map( - ( - teamId - ): Prisma.MembershipCreateOrConnectWithoutPersonInput => { - return { - where: { personId_teamId: { personId, teamId } }, - create: { - position: MembershipPositionType.Captain, - team: { - connect: { - id: teamId, + }; + } + ) ?? []), + ...(captainOfIds + ?.filter( + (id): id is Exclude => + id != null + ) + .map( + ( + teamId + ): Prisma.MembershipCreateOrConnectWithoutPersonInput => { + return { + where: { personId_teamId: { personId, teamId } }, + create: { + position: MembershipPositionType.Captain, + team: { + connect: { + id: teamId, + }, }, }, - }, - }; - } - ) ?? []), - ], - } - : undefined, - }, - }); + }; + } + ) ?? []), + ], + } + : undefined, + }, + }) + ); } catch (error) { if ( error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2025" ) { - return null; + return Err(new NotFoundError({ what: "Person" })); } else { - throw error; + return handleRepositoryError(error); } } } async deletePerson( - identifier: { uuid: string } | { id: number } - ): Promise { + identifier: UniquePersonParam + ): Promise> { try { - return await this.prisma.person.delete({ where: identifier }); + return Ok(await this.prisma.person.delete({ where: identifier })); } catch (error) { if ( error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2025" ) { - return null; + return Err(new NotFoundError({ what: "Person" })); } else { - throw error; + return handleRepositoryError(error); } } } - async getDemoUser() { - return this.prisma.person.upsert({ - where: { - authIdPairs: { - some: { - source: AuthSource.Demo, - value: "demo-user", - }, - }, - email: "demo-user@dancblue.org", + async getDemoUser(): Promise< + Result< + Person & { + memberships: (Membership & { + team: Team; + })[]; }, - create: { - email: "demo-user@dancblue.org", - name: "Demo User", - linkblue: "demo-user", - memberships: { + RepositoryError + > + > { + try { + let demoTeam = await this.prisma.team.findFirst({ + where: { + legacyStatus: TeamLegacyStatus.DemoTeam, + }, + }); + + if (!demoTeam) { + const someMarathon = await this.prisma.marathon.findFirst({ + orderBy: { + year: "asc", + }, + }); + + if (!someMarathon) { + return Err(new NotFoundError({ what: "Marathon" })); + } + + demoTeam = await this.prisma.team.create({ + data: { + name: "Demo Team", + type: "Spirit", + marathon: { + connect: someMarathon, + }, + legacyStatus: TeamLegacyStatus.DemoTeam, + persistentIdentifier: "demo-team", + }, + }); + } + + return Ok( + await this.prisma.person.upsert({ + where: { + authIdPairs: { + some: { + source: AuthSource.Demo, + value: "demo-user", + }, + }, + email: "demo-user@dancblue.org", + }, create: { - team: { - connectOrCreate: { - where: { - persistentIdentifier: "demo-team", - legacyStatus: TeamLegacyStatus.DemoTeam, + email: "demo-user@dancblue.org", + name: "Demo User", + linkblue: "demo-user", + memberships: { + create: { + team: { + connect: { + id: demoTeam.id, + }, }, - create: { - name: "Demo Team", - type: "Spirit", - marathonYear: "DB24", - legacyStatus: TeamLegacyStatus.DemoTeam, - persistentIdentifier: "demo-team", + position: MembershipPositionType.Captain, + }, + }, + pointEntries: { + create: { + team: { + connect: { + id: demoTeam.id, + }, }, + points: 1, + comment: "Demo point", }, }, - position: MembershipPositionType.Captain, }, - }, - pointEntries: { - create: { - team: { - connect: { - persistentIdentifier: "demo-team", + update: { + email: "demo-user@dancblue.org", + name: "Demo User", + linkblue: "demo-user", + }, + include: { + memberships: { + include: { + team: true, }, }, - points: 1, - comment: "Demo point", + }, + }) + ); + } catch (error) { + return handleRepositoryError(error); + } + } + + public async getPrimaryCommitteeOfPerson( + person: UniquePersonParam, + marathon?: UniqueMarathonParam + ): Promise< + Result<[Membership, Committee], RepositoryError | InvariantError> + > { + try { + const committees = await this.prisma.membership.findMany({ + where: { + person, + team: { + marathon, + type: TeamType.Committee, }, }, - }, - update: { - email: "demo-user@dancblue.org", - name: "Demo User", - linkblue: "demo-user", - }, - include: { - memberships: { - include: { - team: true, + include: { + team: { + include: { + correspondingCommittee: true, + }, }, }, - }, - }); + }); + + if (committees.length === 0) { + return Err(new NotFoundError({ what: "Primary committee" })); + } + + let bestCommittee = undefined; + let fallbackCommittee = undefined; + + for (let i = 1; i <= committees.length; i++) { + const committee = committees[i]; + if (committee) { + // We don't want to return the overall committee or vice committee if we have a better option + if ( + committee.team.correspondingCommittee?.identifier === + CommitteeIdentifier.overallCommittee || + committee.team.correspondingCommittee?.identifier === + CommitteeIdentifier.viceCommittee + ) { + fallbackCommittee = committee; + continue; + } + + if (bestCommittee) { + if ( + committee.committeeRole === CommitteeRole.Chair && + bestCommittee.committeeRole !== CommitteeRole.Chair + ) { + bestCommittee = committee; + continue; + } else if ( + committee.committeeRole === CommitteeRole.Coordinator && + !( + bestCommittee.committeeRole === CommitteeRole.Coordinator || + bestCommittee.committeeRole === CommitteeRole.Chair + ) + ) { + bestCommittee = committee; + continue; + } + } else { + bestCommittee = committee; + } + } + } + + if (bestCommittee) { + if ( + !bestCommittee.team.correspondingCommittee || + !bestCommittee.committeeRole + ) { + return Err(new InvariantError("Invalid committee assignment")); + } + return Ok([bestCommittee, bestCommittee.team.correspondingCommittee]); + } else if (fallbackCommittee) { + if ( + !fallbackCommittee.team.correspondingCommittee || + !fallbackCommittee.committeeRole + ) { + throw new DetailedError( + ErrorCode.InternalFailure, + "Invalid committee assignment" + ); + } + return Ok([ + fallbackCommittee, + fallbackCommittee.team.correspondingCommittee, + ]); + } else { + return Err(new NotFoundError({ what: "Primary committee" })); + } + } catch (error) { + return handleRepositoryError(error); + } } } diff --git a/packages/server/src/repositories/person/personModelToResource.ts b/packages/server/src/repositories/person/personModelToResource.ts index e2df0096..b8843946 100644 --- a/packages/server/src/repositories/person/personModelToResource.ts +++ b/packages/server/src/repositories/person/personModelToResource.ts @@ -1,27 +1,31 @@ import type { Person } from "@prisma/client"; -import { DbRole, PersonResource, RoleResource } from "@ukdanceblue/common"; +import { PersonNode } from "@ukdanceblue/common"; +import { AsyncResult } from "ts-results-es"; -export function personModelToResource(person: Person): PersonResource { - let dbRole: DbRole = DbRole.None; - if (person.committeeRole) { - dbRole = DbRole.Committee; - } else if (person.linkblue) { - dbRole = DbRole.UKY; - } else { - dbRole = DbRole.Public; - } +import type { PersonRepository } from "./PersonRepository.js"; +import type { RepositoryError } from "#repositories/shared.js"; - return PersonResource.init({ - uuid: person.uuid, - name: person.name, - email: person.email, - linkblue: person.linkblue, - role: RoleResource.init({ + +export function personModelToResource( + person: Person, + personRepository: PersonRepository +): AsyncResult { + return new AsyncResult( + personRepository.getDbRoleOfPerson({ + uuid: person.uuid, + }) + ).map((dbRole) => + PersonNode.init({ + id: person.uuid, + name: person.name, + email: person.email, + linkblue: person.linkblue, + createdAt: person.createdAt, + updatedAt: person.updatedAt, + + // !!! Potential source of issues !!! dbRole, - committeeRole: person.committeeRole, - committeeIdentifier: person.committeeName, - }), - createdAt: person.createdAt, - updatedAt: person.updatedAt, - }); + // !!! Potential source of issues !!! + }) + ); } diff --git a/packages/server/src/repositories/person/personRepositoryUtils.ts b/packages/server/src/repositories/person/personRepositoryUtils.ts index 6605484d..cf0c3413 100644 --- a/packages/server/src/repositories/person/personRepositoryUtils.ts +++ b/packages/server/src/repositories/person/personRepositoryUtils.ts @@ -1,20 +1,21 @@ import type { Prisma } from "@prisma/client"; import { SortDirection } from "@ukdanceblue/common"; +import { ActionDeniedError } from "@ukdanceblue/common/error"; +import type { Result } from "ts-results-es"; +import { Err, Ok } from "ts-results-es"; +import type { PersonFilters, PersonOrderKeys } from "./PersonRepository.js"; import { dateFilterToPrisma, - oneOfFilterToPrisma, stringFilterToPrisma, -} from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; - -import type { PersonFilters, PersonOrderKeys } from "./PersonRepository.js"; +} from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; export function buildPersonOrder( order: | readonly [key: PersonOrderKeys, sort: SortDirection][] | null | undefined -) { +): Result { const orderBy: Prisma.PersonOrderByWithRelationInput = {}; for (const [key, sort] of order ?? []) { @@ -22,19 +23,25 @@ export function buildPersonOrder( case "name": case "email": case "linkblue": - case "committeeRole": - case "committeeName": case "createdAt": case "updatedAt": { - orderBy[key] = sort === SortDirection.ASCENDING ? "asc" : "desc"; + orderBy[key] = sort === SortDirection.asc ? "asc" : "desc"; break; } + case "committeeRole": + case "committeeName": + case "dbRole": default: { - throw new Error(`Unsupported sort key: ${key}`); + key satisfies "committeeRole" | "committeeName" | "dbRole"; + return Err( + new ActionDeniedError( + `Unsupported filter key: ${String((key as { field?: string } | undefined)?.field)}` + ) + ); } } } - return orderBy; + return Ok(orderBy); } export function buildPersonWhere( filters: readonly PersonFilters[] | null | undefined @@ -49,14 +56,6 @@ export function buildPersonWhere( where[filter.field] = stringFilterToPrisma(filter); break; } - case "committeeRole": { - where[filter.field] = oneOfFilterToPrisma(filter); - break; - } - case "committeeName": { - where[filter.field] = oneOfFilterToPrisma(filter); - break; - } case "createdAt": case "updatedAt": { where[filter.field] = dateFilterToPrisma(filter); diff --git a/packages/server/src/repositories/pointEntry/PointEntryRepository.ts b/packages/server/src/repositories/pointEntry/PointEntryRepository.ts index eb0a4060..ea924bcb 100644 --- a/packages/server/src/repositories/pointEntry/PointEntryRepository.ts +++ b/packages/server/src/repositories/pointEntry/PointEntryRepository.ts @@ -2,12 +2,12 @@ import { Prisma, PrismaClient } from "@prisma/client"; import type { SortDirection } from "@ukdanceblue/common"; import { Service } from "typedi"; -import type { FilterItems } from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; - import { buildPointEntryOrder, buildPointEntryWhere, } from "./pointEntryRepositoryUtils.js"; +import type { FilterItems } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; + const pointEntryBooleanKeys = [] as const; type PointEntryBooleanKey = (typeof pointEntryBooleanKeys)[number]; @@ -145,14 +145,14 @@ export class PointEntryRepository { personParam === null ? { disconnect: true } : personParam === undefined - ? undefined - : { connect: personParam }, + ? undefined + : { connect: personParam }, pointOpportunity: opportunityParam === null ? { disconnect: true } : opportunityParam === undefined - ? undefined - : { connect: opportunityParam }, + ? undefined + : { connect: opportunityParam }, team: teamParam === undefined ? undefined : { connect: teamParam }, }, }); diff --git a/packages/server/src/repositories/pointEntry/pointEntryModelToResource.ts b/packages/server/src/repositories/pointEntry/pointEntryModelToResource.ts index 7b161ce3..0405b96a 100644 --- a/packages/server/src/repositories/pointEntry/pointEntryModelToResource.ts +++ b/packages/server/src/repositories/pointEntry/pointEntryModelToResource.ts @@ -1,11 +1,11 @@ import type { PointEntry } from "@prisma/client"; -import { PointEntryResource } from "@ukdanceblue/common"; +import { PointEntryNode } from "@ukdanceblue/common"; export function pointEntryModelToResource( pointEntryModel: PointEntry -): PointEntryResource { - return PointEntryResource.init({ - uuid: pointEntryModel.uuid, +): PointEntryNode { + return PointEntryNode.init({ + id: pointEntryModel.uuid, points: pointEntryModel.points, comment: pointEntryModel.comment, createdAt: pointEntryModel.createdAt, diff --git a/packages/server/src/repositories/pointEntry/pointEntryRepositoryUtils.ts b/packages/server/src/repositories/pointEntry/pointEntryRepositoryUtils.ts index 771c2338..acf874cd 100644 --- a/packages/server/src/repositories/pointEntry/pointEntryRepositoryUtils.ts +++ b/packages/server/src/repositories/pointEntry/pointEntryRepositoryUtils.ts @@ -12,7 +12,7 @@ export function buildPointEntryOrder( switch (key) { case "createdAt": case "updatedAt": { - orderBy[key] = sort === SortDirection.ASCENDING ? "asc" : "desc"; + orderBy[key] = sort === SortDirection.asc ? "asc" : "desc"; break; } default: { diff --git a/packages/server/src/repositories/pointOpportunity/PointOpportunityRepository.ts b/packages/server/src/repositories/pointOpportunity/PointOpportunityRepository.ts index 847f42ca..46334358 100644 --- a/packages/server/src/repositories/pointOpportunity/PointOpportunityRepository.ts +++ b/packages/server/src/repositories/pointOpportunity/PointOpportunityRepository.ts @@ -3,12 +3,12 @@ import { Prisma, PrismaClient } from "@prisma/client"; import type { SortDirection } from "@ukdanceblue/common"; import { Service } from "typedi"; -import type { FilterItems } from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; - import { buildPointOpportunityOrder, buildPointOpportunityWhere, } from "./pointOpportunityRepositoryUtils.js"; +import type { FilterItems } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; + const pointOpportunityBooleanKeys = [] as const; type PointOpportunityBooleanKey = (typeof pointOpportunityBooleanKeys)[number]; diff --git a/packages/server/src/repositories/pointOpportunity/pointOpportunityModelToResource.ts b/packages/server/src/repositories/pointOpportunity/pointOpportunityModelToResource.ts index 26808381..d6d66bbd 100644 --- a/packages/server/src/repositories/pointOpportunity/pointOpportunityModelToResource.ts +++ b/packages/server/src/repositories/pointOpportunity/pointOpportunityModelToResource.ts @@ -1,17 +1,14 @@ import type { PointOpportunity } from "@prisma/client"; -import { PointOpportunityResource } from "@ukdanceblue/common"; -import { DateTime } from "luxon"; +import { PointOpportunityNode } from "@ukdanceblue/common"; export function pointOpportunityModelToResource( pointOpportunityModel: PointOpportunity -): PointOpportunityResource { - return PointOpportunityResource.init({ - uuid: pointOpportunityModel.uuid, +): PointOpportunityNode { + return PointOpportunityNode.init({ + id: pointOpportunityModel.uuid, name: pointOpportunityModel.name, type: pointOpportunityModel.type, - opportunityDate: pointOpportunityModel.opportunityDate - ? DateTime.fromJSDate(pointOpportunityModel.opportunityDate) - : undefined, + opportunityDate: pointOpportunityModel.opportunityDate, createdAt: pointOpportunityModel.createdAt, updatedAt: pointOpportunityModel.updatedAt, }); diff --git a/packages/server/src/repositories/pointOpportunity/pointOpportunityRepositoryUtils.ts b/packages/server/src/repositories/pointOpportunity/pointOpportunityRepositoryUtils.ts index 18c1e5fd..2d2ee8da 100644 --- a/packages/server/src/repositories/pointOpportunity/pointOpportunityRepositoryUtils.ts +++ b/packages/server/src/repositories/pointOpportunity/pointOpportunityRepositoryUtils.ts @@ -1,16 +1,16 @@ import type { Prisma } from "@prisma/client"; import { SortDirection } from "@ukdanceblue/common"; +import type { + PointOpportunityFilters, + PointOpportunityOrderKeys, +} from "./PointOpportunityRepository.ts"; import { dateFilterToPrisma, oneOfFilterToPrisma, stringFilterToPrisma, -} from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; +} from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; -import type { - PointOpportunityFilters, - PointOpportunityOrderKeys, -} from "./PointOpportunityRepository.ts"; export function buildPointOpportunityOrder( order: @@ -27,7 +27,7 @@ export function buildPointOpportunityOrder( case "type": case "createdAt": case "updatedAt": { - orderBy[key] = sort === SortDirection.ASCENDING ? "asc" : "desc"; + orderBy[key] = sort === SortDirection.asc ? "asc" : "desc"; break; } default: { diff --git a/packages/server/src/repositories/shared.ts b/packages/server/src/repositories/shared.ts new file mode 100644 index 00000000..6648bd16 --- /dev/null +++ b/packages/server/src/repositories/shared.ts @@ -0,0 +1,17 @@ +import type { NotFoundError, BasicError } from "@ukdanceblue/common/error"; +import { toBasicError } from "@ukdanceblue/common/error"; +import { Err } from "ts-results-es"; +import { toPrismaError } from "#error/prisma.js"; +import type { SomePrismaError } from "#error/prisma.js"; + +export type SimpleUniqueParam = { id: number } | { uuid: string }; +export type RepositoryError = SomePrismaError | BasicError | NotFoundError; + +export function handleRepositoryError( + error: unknown +): Err { + const prismaError = toPrismaError(error); + return prismaError.isSome() + ? Err(prismaError.unwrap()) + : Err(toBasicError(error)); +} diff --git a/packages/server/src/repositories/team/TeamRepository.ts b/packages/server/src/repositories/team/TeamRepository.ts index bb8fa861..52d9110f 100644 --- a/packages/server/src/repositories/team/TeamRepository.ts +++ b/packages/server/src/repositories/team/TeamRepository.ts @@ -7,9 +7,11 @@ import type { import { TeamLegacyStatus } from "@ukdanceblue/common"; import { Service } from "typedi"; -import type { FilterItems } from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; - import { buildTeamOrder, buildTeamWhere } from "./teamRepositoryUtils.js"; +import type { FilterItems } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; +import type { UniqueMarathonParam } from "#repositories/marathon/MarathonRepository.js"; +import type { SimpleUniqueParam } from "#repositories/shared.js"; + const teamBooleanKeys = [] as const; type TeamBooleanKey = (typeof teamBooleanKeys)[number]; @@ -23,7 +25,7 @@ type TeamIsNullKey = (typeof teamIsNullKeys)[number]; const teamNumericKeys = ["totalPoints"] as const; type TeamNumericKey = (typeof teamNumericKeys)[number]; -const teamOneOfKeys = ["type", "marathonYear", "legacyStatus"] as const; +const teamOneOfKeys = ["type", "marathonId", "legacyStatus"] as const; type TeamOneOfKey = (typeof teamOneOfKeys)[number]; const teamStringKeys = ["name"] as const; @@ -42,18 +44,42 @@ export type TeamOrderKeys = | "createdAt" | "updatedAt" | "type" - | "marathonYear" + | "marathonId" | "legacyStatus" | "name" | "totalPoints"; -type UniqueTeamParam = { id: number } | { uuid: string }; +type MarathonParam = SimpleUniqueParam | { year: MarathonYearString }; + +function makeMarathonWhere(param: MarathonParam[]) { + const marathonIds: number[] = []; + const marathonUuids: string[] = []; + const marathonYears: MarathonYearString[] = []; + for (const m of param) { + if ("id" in m) { + marathonIds.push(m.id); + } else if ("uuid" in m) { + marathonUuids.push(m.uuid); + } else if ("year" in m) { + marathonYears.push(m.year); + } else { + m satisfies never; + } + } + return { + OR: [ + { id: { in: marathonIds } }, + { uuid: { in: marathonUuids } }, + { year: { in: marathonYears } }, + ], + }; +} @Service() export class TeamRepository { constructor(private prisma: PrismaClient) {} - findTeamByUnique(param: UniqueTeamParam) { + findTeamByUnique(param: SimpleUniqueParam) { return this.prisma.team.findUnique({ where: param }); } @@ -64,7 +90,7 @@ export class TeamRepository { take, onlyDemo, legacyStatus, - marathonYear, + marathon, type, }: { filters?: readonly TeamFilters[] | undefined | null; @@ -76,7 +102,7 @@ export class TeamRepository { take?: number | undefined | null; onlyDemo?: boolean; legacyStatus?: TeamLegacyStatus[] | undefined | null; - marathonYear?: MarathonYearString[] | undefined | null; + marathon: MarathonParam[] | undefined | null; type?: TeamType[] | undefined | null; }) { const where = buildTeamWhere(filters); @@ -85,7 +111,7 @@ export class TeamRepository { return this.prisma.teamsWithTotalPoints.findMany({ where: { type: type ? { in: type } : undefined, - marathonYear: marathonYear ? { in: marathonYear } : undefined, + marathon: makeMarathonWhere(marathon ?? []), ...where, legacyStatus: legacyStatus @@ -109,13 +135,13 @@ export class TeamRepository { filters, onlyDemo, legacyStatus, - marathonYear, + marathon, type, }: { filters?: readonly TeamFilters[] | undefined | null; onlyDemo?: boolean; legacyStatus?: TeamLegacyStatus[] | undefined | null; - marathonYear?: MarathonYearString[] | undefined | null; + marathon: MarathonParam[] | undefined | null; type?: TeamType[] | undefined | null; }) { const where = buildTeamWhere(filters); @@ -123,7 +149,7 @@ export class TeamRepository { return this.prisma.teamsWithTotalPoints.count({ where: { type: type ? { in: type } : undefined, - marathonYear: marathonYear ? { in: marathonYear } : undefined, + marathon: makeMarathonWhere(marathon ?? []), ...where, legacyStatus: legacyStatus @@ -160,7 +186,7 @@ export class TeamRepository { }); } - getTotalTeamPoints(param: UniqueTeamParam) { + getTotalTeamPoints(param: SimpleUniqueParam) { return this.prisma.pointEntry.aggregate({ _sum: { points: true, @@ -171,7 +197,7 @@ export class TeamRepository { }); } - getTeamPointEntries(param: UniqueTeamParam) { + getTeamPointEntries(param: SimpleUniqueParam) { return this.prisma.pointEntry.findMany({ where: { team: param, @@ -179,11 +205,33 @@ export class TeamRepository { }); } - createTeam(data: Prisma.TeamCreateInput) { - return this.prisma.team.create({ data }); + getMarathon(team: SimpleUniqueParam) { + return this.prisma.team + .findUnique({ + where: team, + }) + .marathon(); + } + + createTeam( + data: { + name: string; + type: TeamType; + legacyStatus: TeamLegacyStatus; + }, + marathon: UniqueMarathonParam + ) { + return this.prisma.team.create({ + data: { + ...data, + marathon: { + connect: marathon, + }, + }, + }); } - updateTeam(param: UniqueTeamParam, data: Prisma.TeamUpdateInput) { + updateTeam(param: SimpleUniqueParam, data: Prisma.TeamUpdateInput) { try { return this.prisma.team.update({ where: param, data }); } catch (error) { @@ -198,7 +246,7 @@ export class TeamRepository { } } - deleteTeam(param: UniqueTeamParam) { + deleteTeam(param: SimpleUniqueParam) { try { return this.prisma.team.delete({ where: param }); } catch (error) { diff --git a/packages/server/src/repositories/team/teamModelToResource.ts b/packages/server/src/repositories/team/teamModelToResource.ts index 0cbba275..92064779 100644 --- a/packages/server/src/repositories/team/teamModelToResource.ts +++ b/packages/server/src/repositories/team/teamModelToResource.ts @@ -1,20 +1,19 @@ -import type { Team } from "@prisma/client"; -import { TeamResource } from "@ukdanceblue/common"; +import type { TeamLegacyStatus, TeamType } from "@ukdanceblue/common"; +import { TeamNode } from "@ukdanceblue/common"; -const marathonYearRegex = /^DB\d{2}$/; - -export function teamModelToResource(teamModel: Team): TeamResource { - if (!marathonYearRegex.test(teamModel.marathonYear)) { - throw new Error(`Invalid marathon year: ${teamModel.marathonYear}`); - } - - return TeamResource.init({ - uuid: teamModel.uuid, +export function teamModelToResource(teamModel: { + uuid: string; + name: string; + type: TeamType; + legacyStatus: TeamLegacyStatus; + createdAt: Date; + updatedAt: Date; +}): TeamNode { + return TeamNode.init({ + id: teamModel.uuid, name: teamModel.name, - persistentIdentifier: teamModel.persistentIdentifier, type: teamModel.type, legacyStatus: teamModel.legacyStatus, - marathonYear: teamModel.marathonYear as `DB${number}`, createdAt: teamModel.createdAt, updatedAt: teamModel.updatedAt, }); diff --git a/packages/server/src/repositories/team/teamRepositoryUtils.ts b/packages/server/src/repositories/team/teamRepositoryUtils.ts index cfc5e03a..e89d16bc 100644 --- a/packages/server/src/repositories/team/teamRepositoryUtils.ts +++ b/packages/server/src/repositories/team/teamRepositoryUtils.ts @@ -1,14 +1,14 @@ import type { Prisma } from "@prisma/client"; import { SortDirection } from "@ukdanceblue/common"; +import type { TeamFilters, TeamOrderKeys } from "./TeamRepository.ts"; import { dateFilterToPrisma, numericFilterToPrisma, oneOfFilterToPrisma, stringFilterToPrisma, -} from "../../lib/prisma-utils/gqlFilterToPrismaFilter.js"; +} from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; -import type { TeamFilters, TeamOrderKeys } from "./TeamRepository.ts"; export function buildTeamOrder( order: readonly [key: TeamOrderKeys, sort: SortDirection][] | null | undefined @@ -19,13 +19,13 @@ export function buildTeamOrder( switch (key) { case "totalPoints": case "type": - case "marathonYear": + case "marathonId": case "legacyStatus": case "name": case "createdAt": case "updatedAt": { orderBy.push({ - [key]: sort === SortDirection.ASCENDING ? "asc" : "desc", + [key]: sort === SortDirection.asc ? "asc" : "desc", }); break; } @@ -49,8 +49,13 @@ export function buildTeamWhere( where[filter.field] = dateFilterToPrisma(filter); break; } + case "marathonId": { + where["marathon"] = { + uuid: oneOfFilterToPrisma(filter), + }; + break; + } case "type": - case "marathonYear": case "legacyStatus": { where[filter.field] = oneOfFilterToPrisma(filter); break; diff --git a/packages/server/src/resolvers/AdministrationResolver.ts b/packages/server/src/resolvers/AdministrationResolver.ts new file mode 100644 index 00000000..b72ad345 --- /dev/null +++ b/packages/server/src/resolvers/AdministrationResolver.ts @@ -0,0 +1,32 @@ +import { readFile } from "fs/promises"; +import { join } from "path"; + +import { AccessControl, AccessLevel } from "@ukdanceblue/common"; +import { FieldResolver, ObjectType, Query, Resolver } from "type-graphql"; + +import { logDir } from "#environment"; +import { auditLoggerFileName } from "#logging/auditLogging.js"; + +@ObjectType() +export class Administration { + @FieldResolver(() => Boolean) + ok() { + return true; + } +} + +@Resolver(() => Administration) +export class AdministrationResolver { + @AccessControl({ accessLevel: AccessLevel.SuperAdmin }) + @Query(() => Administration) + administration(): Promise { + return Promise.resolve(new Administration()); + } + + @AccessControl({ accessLevel: AccessLevel.SuperAdmin }) + @Query(() => String) + async auditLog(): Promise { + const fileLookup = await readFile(join(logDir, auditLoggerFileName)); + return fileLookup.toString("utf8"); + } +} diff --git a/packages/server/src/resolvers/ConfigurationResolver.ts b/packages/server/src/resolvers/ConfigurationResolver.ts index 6f0991c9..ac984afc 100644 --- a/packages/server/src/resolvers/ConfigurationResolver.ts +++ b/packages/server/src/resolvers/ConfigurationResolver.ts @@ -1,13 +1,15 @@ +import type { GlobalId } from "@ukdanceblue/common"; import { AccessControl, AccessLevel, - ConfigurationResource, - DateTimeScalar, + ConfigurationNode, DetailedError, ErrorCode, + GlobalIdScalar, SortDirection, + dateTimeFromSomething, } from "@ukdanceblue/common"; -import type { DateTime } from "luxon"; +import { DateTimeISOResolver } from "graphql-scalars"; import { Arg, Field, @@ -19,64 +21,63 @@ import { } from "type-graphql"; import { Service } from "typedi"; -import { auditLogger } from "../lib/logging/auditLogging.js"; -import { ConfigurationRepository } from "../repositories/configuration/ConfigurationRepository.js"; -import { configurationModelToResource } from "../repositories/configuration/configurationModelToResource.js"; - +import { auditLogger } from "#logging/auditLogging.js"; +import { ConfigurationRepository } from "#repositories/configuration/ConfigurationRepository.js"; +import { configurationModelToResource } from "#repositories/configuration/configurationModelToResource.js"; import { AbstractGraphQLArrayOkResponse, AbstractGraphQLCreatedResponse, AbstractGraphQLOkResponse, -} from "./ApiResponse.js"; +} from "#resolvers/ApiResponse.js"; @ObjectType("GetConfigurationByUuidResponse", { - implements: AbstractGraphQLOkResponse, + implements: AbstractGraphQLOkResponse, }) -class GetConfigurationResponse extends AbstractGraphQLOkResponse { - @Field(() => ConfigurationResource) - data!: ConfigurationResource; +class GetConfigurationResponse extends AbstractGraphQLOkResponse { + @Field(() => ConfigurationNode) + data!: ConfigurationNode; } @ObjectType("GetAllConfigurationsResponse", { - implements: AbstractGraphQLArrayOkResponse, + implements: AbstractGraphQLArrayOkResponse, }) -class GetAllConfigurationsResponse extends AbstractGraphQLArrayOkResponse { - @Field(() => [ConfigurationResource]) - data!: ConfigurationResource[]; +class GetAllConfigurationsResponse extends AbstractGraphQLArrayOkResponse { + @Field(() => [ConfigurationNode]) + data!: ConfigurationNode[]; } @ObjectType("CreateConfigurationResponse", { - implements: AbstractGraphQLCreatedResponse, + implements: AbstractGraphQLCreatedResponse, }) -class CreateConfigurationResponse extends AbstractGraphQLCreatedResponse { - @Field(() => ConfigurationResource) - data!: ConfigurationResource; +class CreateConfigurationResponse extends AbstractGraphQLCreatedResponse { + @Field(() => ConfigurationNode) + data!: ConfigurationNode; } @ObjectType("CreateConfigurationsResponse", { - implements: AbstractGraphQLCreatedResponse, + implements: AbstractGraphQLCreatedResponse, }) -class CreateConfigurationsResponse extends AbstractGraphQLArrayOkResponse { - @Field(() => [ConfigurationResource]) - data!: ConfigurationResource[]; +class CreateConfigurationsResponse extends AbstractGraphQLArrayOkResponse { + @Field(() => [ConfigurationNode]) + data!: ConfigurationNode[]; } @ObjectType("DeleteConfigurationResponse", { implements: AbstractGraphQLOkResponse, }) class DeleteConfigurationResponse extends AbstractGraphQLOkResponse {} @InputType() -class CreateConfigurationInput implements Partial { +class CreateConfigurationInput implements Partial { @Field() key!: string; @Field() value!: string; - @Field(() => DateTimeScalar, { nullable: true }) - validAfter!: DateTime | null; + @Field(() => DateTimeISOResolver, { nullable: true }) + validAfter!: Date | null; - @Field(() => DateTimeScalar, { nullable: true }) - validUntil!: DateTime | null; + @Field(() => DateTimeISOResolver, { nullable: true }) + validUntil!: Date | null; } -@Resolver(() => ConfigurationResource) +@Resolver(() => ConfigurationNode) @Service() export class ConfigurationResolver { constructor( @@ -100,10 +101,27 @@ export class ConfigurationResolver { return GetConfigurationResponse.newOk(configurationModelToResource(row)); } + @Query(() => GetConfigurationResponse, { + name: "configuration", + }) + async getByUuid( + @Arg("id", () => GlobalIdScalar) { id }: GlobalId + ): Promise { + const row = await this.configurationRepository.findConfigurationByUnique({ + uuid: id, + }); + + if (row == null) { + throw new DetailedError(ErrorCode.NotFound, "Configuration not found"); + } + + return GetConfigurationResponse.newOk(configurationModelToResource(row)); + } + @Query(() => GetAllConfigurationsResponse, { name: "allConfigurations" }) async getAll(): Promise { const rows = await this.configurationRepository.findConfigurations(null, [ - ["createdAt", SortDirection.DESCENDING], + ["createdAt", SortDirection.desc], ]); return GetAllConfigurationsResponse.newOk( @@ -119,8 +137,8 @@ export class ConfigurationResolver { const row = await this.configurationRepository.createConfiguration({ key: input.key, value: input.value, - validAfter: input.validAfter ?? null, - validUntil: input.validUntil ?? null, + validAfter: dateTimeFromSomething(input.validAfter ?? null), + validUntil: dateTimeFromSomething(input.validUntil ?? null), }); auditLogger.dangerous("Configuration created", { configuration: row }); @@ -143,8 +161,8 @@ export class ConfigurationResolver { this.configurationRepository.createConfiguration({ key: i.key, value: i.value, - validAfter: i.validAfter ?? null, - validUntil: i.validUntil ?? null, + validAfter: dateTimeFromSomething(i.validAfter ?? null), + validUntil: dateTimeFromSomething(i.validUntil ?? null), }) ) ); @@ -161,9 +179,9 @@ export class ConfigurationResolver { @AccessControl({ accessLevel: AccessLevel.Admin }) @Mutation(() => DeleteConfigurationResponse, { name: "deleteConfiguration" }) async delete( - @Arg("uuid") uuid: string + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId ): Promise { - const row = await this.configurationRepository.deleteConfiguration(uuid); + const row = await this.configurationRepository.deleteConfiguration(id); if (row == null) { throw new DetailedError(ErrorCode.NotFound, "Configuration not found"); diff --git a/packages/server/src/resolvers/DeviceResolver.ts b/packages/server/src/resolvers/DeviceResolver.ts index 3f145e37..3235aacd 100644 --- a/packages/server/src/resolvers/DeviceResolver.ts +++ b/packages/server/src/resolvers/DeviceResolver.ts @@ -1,12 +1,15 @@ +import type { GlobalId } from "@ukdanceblue/common"; import { DetailedError, - DeviceResource, + DeviceNode, ErrorCode, FilteredListQueryArgs, - NotificationDeliveryResource, - PersonResource, + GlobalIdScalar, + NotificationDeliveryNode, + PersonNode, SortDirection, } from "@ukdanceblue/common"; +import { ConcreteResult } from "@ukdanceblue/common/error"; import { Arg, Args, @@ -23,37 +26,37 @@ import { } from "type-graphql"; import { Service } from "typedi"; -import { auditLogger } from "../lib/logging/auditLogging.js"; -import { DeviceRepository } from "../repositories/device/DeviceRepository.js"; -import { deviceModelToResource } from "../repositories/device/deviceModelToResource.js"; -import { notificationDeliveryModelToResource } from "../repositories/notificationDelivery/notificationDeliveryModelToResource.js"; -import { personModelToResource } from "../repositories/person/personModelToResource.js"; - +import { auditLogger } from "#logging/auditLogging.js"; +import { DeviceRepository } from "#repositories/device/DeviceRepository.js"; +import { deviceModelToResource } from "#repositories/device/deviceModelToResource.js"; +import { notificationDeliveryModelToResource } from "#repositories/notificationDelivery/notificationDeliveryModelToResource.js"; +import { PersonRepository } from "#repositories/person/PersonRepository.js"; +import { personModelToResource } from "#repositories/person/personModelToResource.js"; import { AbstractGraphQLOkResponse, AbstractGraphQLPaginatedResponse, -} from "./ApiResponse.js"; +} from "#resolvers/ApiResponse.js"; @ObjectType("GetDeviceByUuidResponse", { - implements: AbstractGraphQLOkResponse, + implements: AbstractGraphQLOkResponse, }) -class GetDeviceByUuidResponse extends AbstractGraphQLOkResponse { - @Field(() => DeviceResource) - data!: DeviceResource; +class GetDeviceByUuidResponse extends AbstractGraphQLOkResponse { + @Field(() => DeviceNode) + data!: DeviceNode; } @ObjectType("ListDevicesResponse", { - implements: AbstractGraphQLPaginatedResponse, + implements: AbstractGraphQLPaginatedResponse, }) -class ListDevicesResponse extends AbstractGraphQLPaginatedResponse { - @Field(() => [DeviceResource]) - data!: DeviceResource[]; +class ListDevicesResponse extends AbstractGraphQLPaginatedResponse { + @Field(() => [DeviceNode]) + data!: DeviceNode[]; } @ObjectType("RegisterDeviceResponse", { - implements: AbstractGraphQLOkResponse, + implements: AbstractGraphQLOkResponse, }) -class RegisterDeviceResponse extends AbstractGraphQLOkResponse { - @Field(() => DeviceResource) - data!: DeviceResource; +class RegisterDeviceResponse extends AbstractGraphQLOkResponse { + @Field(() => DeviceNode) + data!: DeviceNode; } @ObjectType("DeleteDeviceResponse", { implements: AbstractGraphQLOkResponse, @@ -61,7 +64,7 @@ class RegisterDeviceResponse extends AbstractGraphQLOkResponse { class DeleteDeviceResponse extends AbstractGraphQLOkResponse {} @InputType() -class RegisterDeviceInput implements Partial { +class RegisterDeviceInput { @Field(() => String) deviceId!: string; @@ -114,14 +117,19 @@ class NotificationDeliveriesArgs { pageSize?: number; } -@Resolver(() => DeviceResource) +@Resolver(() => DeviceNode) @Service() export class DeviceResolver { - constructor(private deviceRepository: DeviceRepository) {} + constructor( + private readonly deviceRepository: DeviceRepository, + private readonly personRepository: PersonRepository + ) {} @Query(() => GetDeviceByUuidResponse, { name: "device" }) - async getByUuid(@Arg("uuid") uuid: string): Promise { - const row = await this.deviceRepository.getDeviceByUuid(uuid); + async getByUuid( + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId + ): Promise { + const row = await this.deviceRepository.getDeviceByUuid(id); if (row == null) { throw new DetailedError(ErrorCode.NotFound, "Device not found"); @@ -140,7 +148,7 @@ export class DeviceResolver { orderBy: query.sortBy?.map((key, i) => [ key, - query.sortDirection?.[i] ?? SortDirection.DESCENDING, + query.sortDirection?.[i] ?? SortDirection.desc, ]) ?? [], skip: query.page != null && query.pageSize != null @@ -176,29 +184,34 @@ export class DeviceResolver { } @Mutation(() => DeleteDeviceResponse, { name: "deleteDevice" }) - async delete(@Arg("uuid") uuid: string): Promise { - await this.deviceRepository.deleteDevice({ uuid }); + async delete( + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId + ): Promise { + await this.deviceRepository.deleteDevice({ uuid: id }); - auditLogger.normal("Device deleted", { uuid }); + auditLogger.normal("Device deleted", { uuid: id }); return DeleteDeviceResponse.newOk(true); } - @FieldResolver(() => PersonResource, { nullable: true }) + @FieldResolver(() => PersonNode, { nullable: true }) async lastLoggedInUser( - @Root() device: DeviceResource - ): Promise { - const user = await this.deviceRepository.getLastLoggedInUser(device.uuid); - - return user == null ? null : personModelToResource(user); + @Root() { id: { id } }: DeviceNode + ): Promise> { + const user = await this.deviceRepository.getLastLoggedInUser(id); + + return user + .toAsyncResult() + .andThen((person) => personModelToResource(person, this.personRepository)) + .promise; } - @FieldResolver(() => [NotificationDeliveryResource]) + @FieldResolver(() => [NotificationDeliveryNode]) async notificationDeliveries( - @Root() device: DeviceResource, + @Root() { id: { id } }: DeviceNode, @Args(() => NotificationDeliveriesArgs) query: NotificationDeliveriesArgs - ): Promise { - const row = await this.deviceRepository.getDeviceByUuid(device.uuid); + ): Promise { + const row = await this.deviceRepository.getDeviceByUuid(id); if (row == null) { throw new DetailedError(ErrorCode.NotFound, "Device not found"); @@ -215,16 +228,13 @@ export class DeviceResolver { } const rows = - await this.deviceRepository.findNotificationDeliveriesForDevice( - device.uuid, - { - skip: - query.page != null && query.pageSize != null - ? (query.page - 1) * query.pageSize - : undefined, - take: query.pageSize, - } - ); + await this.deviceRepository.findNotificationDeliveriesForDevice(id, { + skip: + query.page != null && query.pageSize != null + ? (query.page - 1) * query.pageSize + : undefined, + take: query.pageSize, + }); return rows.map(notificationDeliveryModelToResource); } diff --git a/packages/server/src/resolvers/EventResolver.ts b/packages/server/src/resolvers/EventResolver.ts index c089184f..cbf796b6 100644 --- a/packages/server/src/resolvers/EventResolver.ts +++ b/packages/server/src/resolvers/EventResolver.ts @@ -1,16 +1,17 @@ import type { Prisma } from "@prisma/client"; +import type { GlobalId } from "@ukdanceblue/common"; import { AccessControl, AccessLevel, - DateRangeScalar, DetailedError, ErrorCode, - EventResource, + EventNode, FilteredListQueryArgs, - ImageResource, + GlobalIdScalar, + ImageNode, + IntervalISO, SortDirection, } from "@ukdanceblue/common"; -import { Interval } from "luxon"; import { Arg, Args, @@ -26,42 +27,41 @@ import { } from "type-graphql"; import { Service } from "typedi"; -import { FileManager } from "../lib/files/FileManager.js"; -import { auditLogger } from "../lib/logging/auditLogging.js"; -import { EventRepository } from "../repositories/event/EventRepository.js"; +import { FileManager } from "#files/FileManager.js"; +import { auditLogger } from "#logging/auditLogging.js"; +import { EventRepository } from "#repositories/event/EventRepository.js"; import { eventModelToResource, eventOccurrenceModelToResource, -} from "../repositories/event/eventModelToResource.js"; -import { EventImagesRepository } from "../repositories/event/images/EventImagesRepository.js"; -import { imageModelToResource } from "../repositories/image/imageModelToResource.js"; - +} from "#repositories/event/eventModelToResource.js"; +import { EventImagesRepository } from "#repositories/event/images/EventImagesRepository.js"; +import { imageModelToResource } from "#repositories/image/imageModelToResource.js"; import { AbstractGraphQLCreatedResponse, AbstractGraphQLOkResponse, AbstractGraphQLPaginatedResponse, -} from "./ApiResponse.js"; +} from "#resolvers/ApiResponse.js"; @ObjectType("GetEventByUuidResponse", { - implements: AbstractGraphQLOkResponse, + implements: AbstractGraphQLOkResponse, }) -class GetEventByUuidResponse extends AbstractGraphQLOkResponse { - @Field(() => EventResource) - data!: EventResource; +class GetEventByUuidResponse extends AbstractGraphQLOkResponse { + @Field(() => EventNode) + data!: EventNode; } @ObjectType("CreateEventResponse", { - implements: AbstractGraphQLCreatedResponse, + implements: AbstractGraphQLCreatedResponse, }) -class CreateEventResponse extends AbstractGraphQLCreatedResponse { - @Field(() => EventResource) - data!: EventResource; +class CreateEventResponse extends AbstractGraphQLCreatedResponse { + @Field(() => EventNode) + data!: EventNode; } @ObjectType("SetEventResponse", { - implements: AbstractGraphQLOkResponse, + implements: AbstractGraphQLOkResponse, }) -class SetEventResponse extends AbstractGraphQLOkResponse { - @Field(() => EventResource) - data!: EventResource; +class SetEventResponse extends AbstractGraphQLOkResponse { + @Field(() => EventNode) + data!: EventNode; } @ObjectType("DeleteEventResponse", { implements: AbstractGraphQLOkResponse, @@ -77,25 +77,25 @@ class RemoveEventImageResponse extends AbstractGraphQLOkResponse { } @ObjectType("AddEventImageResponse", { - implements: AbstractGraphQLOkResponse, + implements: AbstractGraphQLOkResponse, }) -class AddEventImageResponse extends AbstractGraphQLOkResponse { - @Field(() => ImageResource) - data!: ImageResource; +class AddEventImageResponse extends AbstractGraphQLOkResponse { + @Field(() => ImageNode) + data!: ImageNode; } @ObjectType("ListEventsResponse", { - implements: AbstractGraphQLPaginatedResponse, + implements: AbstractGraphQLPaginatedResponse, }) -class ListEventsResponse extends AbstractGraphQLPaginatedResponse { - @Field(() => [EventResource]) - data!: EventResource[]; +class ListEventsResponse extends AbstractGraphQLPaginatedResponse { + @Field(() => [EventNode]) + data!: EventNode[]; } @InputType() export class CreateEventOccurrenceInput { - @Field(() => DateRangeScalar) - interval!: Interval; + @Field(() => IntervalISO) + interval!: IntervalISO; @Field(() => Boolean) fullDay!: boolean; } @@ -126,8 +126,8 @@ export class SetEventOccurrenceInput { "If updating an existing occurrence, the UUID of the occurrence to update", }) uuid!: string | null; - @Field(() => DateRangeScalar) - interval!: Interval; + @Field(() => IntervalISO) + interval!: IntervalISO; @Field(() => Boolean) fullDay!: boolean; } @@ -193,7 +193,7 @@ class ListEventsArgs extends FilteredListQueryArgs< }) {} @Service() -@Resolver(() => EventResource) +@Resolver(() => EventNode) export class EventResolver { constructor( private readonly eventRepository: EventRepository, @@ -201,8 +201,10 @@ export class EventResolver { private readonly fileManager: FileManager ) {} @Query(() => GetEventByUuidResponse, { name: "event" }) - async getByUuid(@Arg("uuid") uuid: string): Promise { - const row = await this.eventRepository.findEventByUnique({ uuid }); + async getByUuid( + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId + ): Promise { + const row = await this.eventRepository.findEventByUnique({ uuid: id }); if (row == null) { throw new DetailedError(ErrorCode.NotFound, "Event not found"); @@ -223,7 +225,7 @@ export class EventResolver { order: query.sortBy?.map((key, i) => [ key, - query.sortDirection?.[i] ?? SortDirection.DESCENDING, + query.sortDirection?.[i] ?? SortDirection.desc, ]) ?? [], skip: query.page != null && query.pageSize != null @@ -259,8 +261,8 @@ export class EventResolver { createMany: { data: input.occurrences.map( (occurrence): Prisma.EventOccurrenceCreateManyEventInput => ({ - date: occurrence.interval.start!.toJSDate(), - endDate: occurrence.interval.end!.toJSDate(), + date: occurrence.interval.start, + endDate: occurrence.interval.end, fullDay: occurrence.fullDay, }) ), @@ -281,14 +283,16 @@ export class EventResolver { @AccessControl({ accessLevel: AccessLevel.CommitteeChairOrCoordinator }) @Mutation(() => DeleteEventResponse, { name: "deleteEvent" }) - async delete(@Arg("uuid") uuid: string): Promise { - const row = await this.eventRepository.deleteEvent({ uuid }); + async delete( + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId + ): Promise { + const row = await this.eventRepository.deleteEvent({ uuid: id }); if (row == null) { throw new DetailedError(ErrorCode.NotFound, "Event not found"); } - auditLogger.sensitive("Event deleted", { uuid }); + auditLogger.sensitive("Event deleted", { uuid: id }); return DeleteEventResponse.newOk(true); } @@ -296,11 +300,11 @@ export class EventResolver { @AccessControl({ accessLevel: AccessLevel.CommitteeChairOrCoordinator }) @Mutation(() => SetEventResponse, { name: "setEvent" }) async set( - @Arg("uuid") uuid: string, + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId, @Arg("input") input: SetEventInput ): Promise { const row = await this.eventRepository.updateEvent( - { uuid }, + { uuid: id }, { title: input.title, summary: input.summary, @@ -312,8 +316,8 @@ export class EventResolver { .filter((occurrence) => occurrence.uuid == null) .map( (occurrence): Prisma.EventOccurrenceCreateManyEventInput => ({ - date: occurrence.interval.start!.toJSDate(), - endDate: occurrence.interval.end!.toJSDate(), + date: occurrence.interval.start, + endDate: occurrence.interval.end, fullDay: occurrence.fullDay, }) ), @@ -326,8 +330,8 @@ export class EventResolver { ): Prisma.EventOccurrenceUpdateManyWithWhereWithoutEventInput => ({ where: { uuid: occurrence.uuid! }, data: { - date: occurrence.interval.start!.toJSDate(), - endDate: occurrence.interval.end!.toJSDate(), + date: occurrence.interval.start, + endDate: occurrence.interval.end, fullDay: occurrence.fullDay, }, }) @@ -394,10 +398,10 @@ export class EventResolver { ); } - @FieldResolver(() => [ImageResource]) - async images(@Root() event: EventResource): Promise { + @FieldResolver(() => [ImageNode]) + async images(@Root() event: EventNode): Promise { const rows = await this.eventImageRepository.findEventImagesByEventUnique({ - uuid: event.uuid, + uuid: event.id.id, }); return Promise.all( diff --git a/packages/server/src/resolvers/FeedResolver.ts b/packages/server/src/resolvers/FeedResolver.ts index d40f6988..e17de7fb 100644 --- a/packages/server/src/resolvers/FeedResolver.ts +++ b/packages/server/src/resolvers/FeedResolver.ts @@ -1,8 +1,8 @@ import { DetailedError, ErrorCode, - FeedResource, - ImageResource, + FeedNode, + ImageNode, } from "@ukdanceblue/common"; import { Arg, @@ -17,10 +17,10 @@ import { } from "type-graphql"; import { Service } from "typedi"; -import { FileManager } from "../lib/files/FileManager.js"; -import { feedItemModelToResource } from "../repositories/feed/feedModelToResource.js"; -import { FeedRepository } from "../repositories/feed/feedRepository.js"; -import { imageModelToResource } from "../repositories/image/imageModelToResource.js"; +import { FileManager } from "#files/FileManager.js"; +import { FeedRepository } from "#repositories/feed/FeedRepository.js"; +import { feedItemModelToResource } from "#repositories/feed/feedModelToResource.js"; +import { imageModelToResource } from "#repositories/image/imageModelToResource.js"; @InputType() export class CreateFeedInput { @@ -40,7 +40,7 @@ export class SetFeedInput { textContent?: string | null | undefined; } -@Resolver(() => FeedResource) +@Resolver(() => FeedNode) @Service() export class FeedResolver { constructor( @@ -48,19 +48,19 @@ export class FeedResolver { private readonly fileManager: FileManager ) {} - @Query(() => [FeedResource]) + @Query(() => [FeedNode]) async feed( @Arg("limit", () => Int, { defaultValue: 10, nullable: true }) limit: number | null - ): Promise { + ): Promise { const rows = await this.feedRepository.getCompleteFeed({ limit }); return rows.map(feedItemModelToResource); } - @Mutation(() => FeedResource) + @Mutation(() => FeedNode) async createFeedItem( @Arg("input") input: CreateFeedInput - ): Promise { + ): Promise { const feedItem = await this.feedRepository.createFeedItem({ title: input.title, textContent: input.textContent, @@ -69,11 +69,11 @@ export class FeedResolver { return feedItemModelToResource(feedItem); } - @Mutation(() => FeedResource) + @Mutation(() => FeedNode) async attachImageToFeedItem( @Arg("feedItemUuid") feedItemUuid: string, @Arg("imageUuid") imageUuid: string - ): Promise { + ): Promise { const feedItem = await this.feedRepository.attachImageToFeedItem( { uuid: feedItemUuid, @@ -88,10 +88,10 @@ export class FeedResolver { return feedItemModelToResource(feedItem); } - @Mutation(() => FeedResource) + @Mutation(() => FeedNode) async removeImageFromFeedItem( @Arg("feedItemUuid") feedItemUuid: string - ): Promise { + ): Promise { const feedItem = await this.feedRepository.removeImageFromFeedItem({ uuid: feedItemUuid, }); @@ -101,11 +101,11 @@ export class FeedResolver { return feedItemModelToResource(feedItem); } - @Mutation(() => FeedResource) + @Mutation(() => FeedNode) async setFeedItem( @Arg("feedItemUuid") feedItemUuid: string, @Arg("input") input: SetFeedInput - ): Promise { + ): Promise { const feedItem = await this.feedRepository.updateFeedItem( { uuid: feedItemUuid }, { @@ -129,9 +129,9 @@ export class FeedResolver { return feedItem != null; } - @FieldResolver(() => ImageResource, { nullable: true }) - async image(@Root() { uuid }: FeedResource) { - const row = await this.feedRepository.getFeedItemImage({ uuid }); + @FieldResolver(() => ImageNode, { nullable: true }) + async image(@Root() { id: { id } }: FeedNode) { + const row = await this.feedRepository.getFeedItemImage({ uuid: id }); if (row == null) { return null; } diff --git a/packages/server/src/resolvers/FundraisingAssignmentResolver.ts b/packages/server/src/resolvers/FundraisingAssignmentResolver.ts new file mode 100644 index 00000000..79f52f37 --- /dev/null +++ b/packages/server/src/resolvers/FundraisingAssignmentResolver.ts @@ -0,0 +1,170 @@ +import type { GlobalId } from "@ukdanceblue/common"; +import { + AccessControl, + AccessControlParam, + CommitteeIdentifier, + CommitteeRole, + FundraisingAssignmentNode, + FundraisingEntryNode, + GlobalIdScalar, + MembershipPositionType, + PersonNode, +} from "@ukdanceblue/common"; +import { ConcreteResult } from "@ukdanceblue/common/error"; +import { + Arg, + Field, + FieldResolver, + InputType, + Mutation, + Query, + Resolver, + Root, +} from "type-graphql"; +import { Container, Service } from "typedi"; + +import { FundraisingEntryRepository } from "#repositories/fundraising/FundraisingRepository.js"; +import { fundraisingAssignmentModelToNode } from "#repositories/fundraising/fundraisingAssignmentModelToNode.js"; +import { fundraisingEntryModelToNode } from "#repositories/fundraising/fundraisingEntryModelToNode.js"; +import { PersonRepository } from "#repositories/person/PersonRepository.js"; +import { personModelToResource } from "#repositories/person/personModelToResource.js"; +import { globalFundraisingAccessParam } from "#resolvers/FundraisingEntryResolver.js"; + +@InputType() +class AssignEntryToPersonInput { + @Field() + amount!: number; +} + +@InputType() +class UpdateFundraisingAssignmentInput { + @Field() + amount!: number; +} + +const fundraisingAccess: AccessControlParam = { + authRules: [ + { + minCommitteeRole: CommitteeRole.Coordinator, + committeeIdentifiers: [CommitteeIdentifier.fundraisingCommittee], + }, + ], +}; + +@Resolver(() => FundraisingAssignmentNode) +@Service() +export class FundraisingAssignmentResolver { + constructor( + private readonly fundraisingEntryRepository: FundraisingEntryRepository, + private readonly personRepository: PersonRepository + ) {} + + @AccessControl(fundraisingAccess) + @Query(() => FundraisingAssignmentNode) + async fundraisingAssignment( + @Arg("id", () => GlobalIdScalar) { id }: GlobalId + ): Promise> { + const assignment = + await this.fundraisingEntryRepository.findAssignmentByUnique({ + uuid: id, + }); + + return assignment.toAsyncResult().map(fundraisingAssignmentModelToNode) + .promise; + } + + @AccessControl(fundraisingAccess) + @Mutation(() => FundraisingAssignmentNode) + async assignEntryToPerson( + @Arg("entryId") entryId: string, + @Arg("personId") personId: string, + @Arg("input") { amount }: AssignEntryToPersonInput + ): Promise>> { + const assignment = + await this.fundraisingEntryRepository.addAssignmentToEntry( + { uuid: entryId }, + { uuid: personId }, + { amount } + ); + + return assignment.map(fundraisingAssignmentModelToNode); + } + + @AccessControl(fundraisingAccess) + @Mutation(() => FundraisingAssignmentNode) + async updateFundraisingAssignment( + @Arg("id", () => GlobalIdScalar) { id }: GlobalId, + @Arg("input") { amount }: UpdateFundraisingAssignmentInput + ): Promise>> { + const assignment = await this.fundraisingEntryRepository.updateAssignment( + { uuid: id }, + { amount } + ); + + return assignment.map(fundraisingAssignmentModelToNode); + } + + @AccessControl(fundraisingAccess) + @Mutation(() => FundraisingAssignmentNode) + async deleteFundraisingAssignment( + @Arg("id", () => GlobalIdScalar) { id }: GlobalId + ): Promise>> { + const assignment = await this.fundraisingEntryRepository.deleteAssignment({ + uuid: id, + }); + + return assignment.map(fundraisingAssignmentModelToNode); + } + + @AccessControl(globalFundraisingAccessParam, { + custom: async (_, { teamMemberships }, { id: { id } }) => { + const personRepository = Container.get(PersonRepository); + const memberships = await personRepository.findMembershipsOfPerson( + { uuid: id }, + undefined, + undefined, + true + ); + const userCaptaincies = teamMemberships.filter( + (membership) => membership.position === MembershipPositionType.Captain + ); + for (const targetPersonMembership of memberships) { + if ( + userCaptaincies.some( + (userCaptaincy) => + userCaptaincy.teamId === targetPersonMembership.team.uuid + ) + ) { + return true; + } + } + return null; + }, + }) + @FieldResolver(() => PersonNode, { + nullable: true, + description: + "The person assigned to this assignment, only null when access is denied", + }) + async person( + @Root() { id: { id } }: FundraisingAssignmentNode + ): Promise> { + const person = await this.fundraisingEntryRepository.getPersonForAssignment( + { uuid: id } + ); + return person + .toAsyncResult() + .andThen((person) => personModelToResource(person, this.personRepository)) + .promise; + } + + @FieldResolver(() => FundraisingEntryNode) + async entry( + @Root() { id: { id } }: FundraisingAssignmentNode + ): Promise>> { + const entry = await this.fundraisingEntryRepository.getEntryForAssignment({ + uuid: id, + }); + return entry.map(fundraisingEntryModelToNode); + } +} diff --git a/packages/server/src/resolvers/FundraisingEntryResolver.ts b/packages/server/src/resolvers/FundraisingEntryResolver.ts new file mode 100644 index 00000000..1dc9cad0 --- /dev/null +++ b/packages/server/src/resolvers/FundraisingEntryResolver.ts @@ -0,0 +1,204 @@ +import { CommitteeRole } from "@prisma/client"; +import type { GlobalId } from "@ukdanceblue/common"; +import { + AccessControl, + AccessControlParam, + CommitteeIdentifier, + FilteredListQueryArgs, + FundraisingAssignmentNode, + FundraisingEntryNode, + GlobalIdScalar, + MembershipPositionType, + SortDirection, +} from "@ukdanceblue/common"; +import { ConcreteResult } from "@ukdanceblue/common/error"; +import { + Arg, + Args, + ArgsType, + Field, + FieldResolver, + ObjectType, + Query, + Resolver, + Root, +} from "type-graphql"; +import { Container, Service } from "typedi"; + +import { CatchableConcreteError } from "#lib/formatError.js"; +import { DBFundsRepository } from "#repositories/fundraising/DBFundsRepository.js"; +import { FundraisingEntryRepository } from "#repositories/fundraising/FundraisingRepository.js"; +import { fundraisingAssignmentModelToNode } from "#repositories/fundraising/fundraisingAssignmentModelToNode.js"; +import { fundraisingEntryModelToNode } from "#repositories/fundraising/fundraisingEntryModelToNode.js"; +import { AbstractGraphQLPaginatedResponse } from "#resolvers/ApiResponse.js"; + +@ArgsType() +export class ListFundraisingEntriesArgs extends FilteredListQueryArgs< + | "donatedOn" + | "amount" + | "donatedTo" + | "donatedBy" + | "teamId" + | "createdAt" + | "updatedAt", + "donatedTo" | "donatedBy", + "teamId", + "amount", + "donatedOn" | "createdAt" | "updatedAt", + never +>("FundraisingEntryResolver", { + all: [ + "donatedOn", + "amount", + "donatedTo", + "donatedBy", + "createdAt", + "updatedAt", + ], + string: ["donatedTo", "donatedBy"], + numeric: ["amount"], + oneOf: ["teamId"], + date: ["donatedOn", "createdAt", "updatedAt"], +}) {} + +@ObjectType("ListFundraisingEntriesResponse", { + implements: AbstractGraphQLPaginatedResponse, +}) +export class ListFundraisingEntriesResponse extends AbstractGraphQLPaginatedResponse { + @Field(() => [FundraisingEntryNode]) + data!: FundraisingEntryNode[]; +} + +/** + * Access control param for granting access to all fundraising entries. + */ +export const globalFundraisingAccessParam: AccessControlParam< + unknown, + unknown +> = { + authRules: [ + { + minCommitteeRole: CommitteeRole.Coordinator, + committeeIdentifiers: [CommitteeIdentifier.fundraisingCommittee], + }, + ], +}; + +@Resolver(() => FundraisingEntryNode) +@Service() +export class FundraisingEntryResolver { + constructor( + private readonly fundraisingEntryRepository: FundraisingEntryRepository + ) {} + + @AccessControl(globalFundraisingAccessParam) + @Query(() => FundraisingEntryNode) + async fundraisingEntry( + @Arg("id", () => GlobalIdScalar) { id }: GlobalId + ): Promise> { + const entry = await this.fundraisingEntryRepository.findEntryByUnique({ + uuid: id, + }); + return entry.toAsyncResult().map(fundraisingEntryModelToNode).promise; + } + + @AccessControl(globalFundraisingAccessParam) + @Query(() => ListFundraisingEntriesResponse) + async fundraisingEntries( + @Args(() => ListFundraisingEntriesArgs) args: ListFundraisingEntriesArgs + ): Promise { + const entries = await this.fundraisingEntryRepository.listEntries({ + filters: args.filters, + order: + args.sortBy?.map((key, i) => [ + key, + args.sortDirection?.[i] ?? SortDirection.desc, + ]) ?? [], + skip: + args.page != null && args.pageSize != null + ? (args.page - 1) * args.pageSize + : null, + take: args.pageSize, + }); + const count = await this.fundraisingEntryRepository.countEntries({ + filters: args.filters, + }); + + if (entries.isErr()) { + throw new CatchableConcreteError(entries.error); + } + if (count.isErr()) { + throw new CatchableConcreteError(count.error); + } + + return ListFundraisingEntriesResponse.newPaginated({ + data: await Promise.all( + entries.value.map((model) => fundraisingEntryModelToNode(model)) + ), + total: count.value, + page: args.page, + pageSize: args.pageSize, + }); + } + + @AccessControl( + // We can't grant blanket access as otherwise people would see who else was assigned to an entry + // You can view all assignments for an entry if you are: + // 1. A fundraising coordinator or chair + globalFundraisingAccessParam, + // 2. The captain of the team the entry is associated with + { + custom: async ( + { id: { id } }, + { teamMemberships, userData: { userId } } + ): Promise => { + if (userId == null) { + return false; + } + const captainOf = teamMemberships.filter( + (membership) => membership.position === MembershipPositionType.Captain + ); + if (captainOf.length === 0) { + return false; + } + + const fundraisingEntryRepository = Container.get( + FundraisingEntryRepository + ); + const entry = await fundraisingEntryRepository.findEntryByUnique({ + uuid: id, + }); + if (entry.isErr()) { + return false; + } + const dbFundsRepository = Container.get(DBFundsRepository); + const teams = await dbFundsRepository.getTeamsForDbFundsTeam({ + id: entry.value.dbFundsEntry.dbFundsTeamId, + }); + if (teams.isErr()) { + return false; + } + return captainOf.some(({ teamId }) => + teams.value.some((team) => team.uuid === teamId) + ); + }, + } + ) + @FieldResolver(() => [FundraisingAssignmentNode]) + async assignments( + @Root() { id: { id } }: FundraisingEntryNode + ): Promise { + const assignments = + await this.fundraisingEntryRepository.getAssignmentsForEntry({ + uuid: id, + }); + if (assignments.isErr()) { + throw new CatchableConcreteError(assignments.error); + } + return Promise.all( + assignments.value.map((assignment) => + fundraisingAssignmentModelToNode(assignment) + ) + ); + } +} diff --git a/packages/server/src/resolvers/ImageResolver.ts b/packages/server/src/resolvers/ImageResolver.ts index f7c60d68..f89032f8 100644 --- a/packages/server/src/resolvers/ImageResolver.ts +++ b/packages/server/src/resolvers/ImageResolver.ts @@ -1,12 +1,14 @@ import { MIMEType } from "node:util"; +import type { GlobalId } from "@ukdanceblue/common"; import { AccessControl, AccessLevel, DetailedError, ErrorCode, FilteredListQueryArgs, - ImageResource, + GlobalIdScalar, + ImageNode, SortDirection, } from "@ukdanceblue/common"; import { URLResolver } from "graphql-scalars"; @@ -24,22 +26,21 @@ import { } from "type-graphql"; import { Service } from "typedi"; -import { FileManager } from "../lib/files/FileManager.js"; -import { auditLogger } from "../lib/logging/auditLogging.js"; -import { logger } from "../lib/logging/standardLogging.js"; -import { generateThumbHash } from "../lib/thumbHash.js"; -import { ImageRepository } from "../repositories/image/ImageRepository.js"; -import { imageModelToResource } from "../repositories/image/imageModelToResource.js"; - +import { FileManager } from "#files/FileManager.js"; +import { generateThumbHash } from "#lib/thumbHash.js"; +import { auditLogger } from "#logging/auditLogging.js"; +import { logger } from "#logging/standardLogging.js"; +import { ImageRepository } from "#repositories/image/ImageRepository.js"; +import { imageModelToResource } from "#repositories/image/imageModelToResource.js"; import { AbstractGraphQLOkResponse, AbstractGraphQLPaginatedResponse, -} from "./ApiResponse.js"; +} from "#resolvers/ApiResponse.js"; @ObjectType("GetImageByUuidResponse", { implements: AbstractGraphQLOkResponse }) -class GetImageByUuidResponse extends AbstractGraphQLOkResponse { - @Field(() => ImageResource) - data!: ImageResource; +class GetImageByUuidResponse extends AbstractGraphQLOkResponse { + @Field(() => ImageNode) + data!: ImageNode; } @ObjectType("DeleteImageResponse", { @@ -47,7 +48,7 @@ class GetImageByUuidResponse extends AbstractGraphQLOkResponse { }) class DeleteImageResponse extends AbstractGraphQLOkResponse {} @InputType() -class CreateImageInput implements Partial { +class CreateImageInput implements Partial { @Field(() => String, { nullable: true }) alt?: string | null; @@ -71,14 +72,14 @@ class ListImagesArgs extends FilteredListQueryArgs< }) {} @ObjectType("ListImagesResponse", { - implements: AbstractGraphQLPaginatedResponse, + implements: AbstractGraphQLPaginatedResponse, }) -class ListImagesResponse extends AbstractGraphQLPaginatedResponse { - @Field(() => [ImageResource]) - data!: ImageResource[]; +class ListImagesResponse extends AbstractGraphQLPaginatedResponse { + @Field(() => [ImageNode]) + data!: ImageNode[]; } -@Resolver(() => ImageResource) +@Resolver(() => ImageNode) @Service() export class ImageResolver { constructor( @@ -87,8 +88,10 @@ export class ImageResolver { ) {} @Query(() => GetImageByUuidResponse, { name: "image" }) - async getByUuid(@Arg("uuid") uuid: string): Promise { - const result = await this.imageRepository.findImageByUnique({ uuid }); + async getByUuid( + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId + ): Promise { + const result = await this.imageRepository.findImageByUnique({ uuid: id }); if (result == null) { throw new DetailedError(ErrorCode.NotFound, "Image not found"); @@ -108,7 +111,7 @@ export class ImageResolver { order: args.sortBy?.map((key, i) => [ key, - args.sortDirection?.[i] ?? SortDirection.DESCENDING, + args.sortDirection?.[i] ?? SortDirection.desc, ]) ?? [], skip: args.page != null && args.pageSize != null @@ -133,8 +136,8 @@ export class ImageResolver { } @AccessControl({ accessLevel: AccessLevel.CommitteeChairOrCoordinator }) - @Mutation(() => ImageResource, { name: "createImage" }) - async create(@Arg("input") input: CreateImageInput): Promise { + @Mutation(() => ImageNode, { name: "createImage" }) + async create(@Arg("input") input: CreateImageInput): Promise { const { mime, thumbHash, width, height } = await handleImageUrl(input.url); const result = await this.imageRepository.createImage({ width, @@ -173,14 +176,14 @@ export class ImageResolver { } @AccessControl({ accessLevel: AccessLevel.CommitteeChairOrCoordinator }) - @Mutation(() => ImageResource, { name: "setImageAltText" }) + @Mutation(() => ImageNode, { name: "setImageAltText" }) async setAltText( - @Arg("uuid") uuid: string, + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId, @Arg("alt") alt: string - ): Promise { + ): Promise { const result = await this.imageRepository.updateImage( { - uuid, + uuid: id, }, { alt, @@ -197,15 +200,15 @@ export class ImageResolver { } @AccessControl({ accessLevel: AccessLevel.CommitteeChairOrCoordinator }) - @Mutation(() => ImageResource, { name: "setImageUrl" }) + @Mutation(() => ImageNode, { name: "setImageUrl" }) async setImageUrl( - @Arg("uuid") uuid: string, + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId, @Arg("url", () => URLResolver) url: URL - ): Promise { + ): Promise { const { mime, thumbHash, width, height } = await handleImageUrl(url); const result = await this.imageRepository.updateImage( { - uuid, + uuid: id, }, { width, @@ -240,11 +243,13 @@ export class ImageResolver { } @AccessControl({ accessLevel: AccessLevel.CommitteeChairOrCoordinator }) - @Mutation(() => ImageResource, { name: "setImageUrl" }) + @Mutation(() => ImageNode, { name: "setImageUrl" }) @AccessControl({ accessLevel: AccessLevel.CommitteeChairOrCoordinator }) @Mutation(() => DeleteImageResponse, { name: "deleteImage" }) - async delete(@Arg("uuid") uuid: string): Promise { - const result = await this.imageRepository.deleteImage({ uuid }); + async delete( + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId + ): Promise { + const result = await this.imageRepository.deleteImage({ uuid: id }); if (result == null) { throw new DetailedError(ErrorCode.NotFound, "Image not found"); diff --git a/packages/server/src/resolvers/LoginState.ts b/packages/server/src/resolvers/LoginState.ts index 9aace5c1..f3eace55 100644 --- a/packages/server/src/resolvers/LoginState.ts +++ b/packages/server/src/resolvers/LoginState.ts @@ -1,19 +1,26 @@ -import { AuthSource, RoleResource } from "@ukdanceblue/common"; +import { + AuthSource, + DbRole, + EffectiveCommitteeRole, +} from "@ukdanceblue/common"; import { Ctx, Field, ObjectType, Query, Resolver } from "type-graphql"; import { Service } from "typedi"; -import * as Context from "./context.js"; +import * as Context from "#resolvers/context.js"; @ObjectType("LoginState") export class LoginState { @Field(() => Boolean) loggedIn!: boolean; - @Field(() => RoleResource) - role!: RoleResource; + @Field(() => DbRole) + dbRole!: DbRole; @Field(() => AuthSource) authSource!: AuthSource; + + @Field(() => [EffectiveCommitteeRole]) + effectiveCommitteeRoles!: EffectiveCommitteeRole[]; } @Resolver(() => LoginState) @@ -22,8 +29,9 @@ export class LoginStateResolver { @Query(() => LoginState) loginState(@Ctx() ctx: Context.GraphQLContext): LoginState { return { - loggedIn: ctx.authenticatedUser != null, - role: RoleResource.fromAuthorization(ctx.userData.auth), + loggedIn: ctx.authorization.dbRole !== DbRole.None, + effectiveCommitteeRoles: ctx.authorization.committees, + dbRole: ctx.authorization.dbRole, authSource: ctx.userData.authSource, }; } diff --git a/packages/server/src/resolvers/MarathonHourResolver.ts b/packages/server/src/resolvers/MarathonHourResolver.ts index 72a30cf5..da0db3b8 100644 --- a/packages/server/src/resolvers/MarathonHourResolver.ts +++ b/packages/server/src/resolvers/MarathonHourResolver.ts @@ -1,9 +1,11 @@ +import type { GlobalId } from "@ukdanceblue/common"; import { DetailedError, ErrorCode, FilteredListQueryArgs, - ImageResource, - MarathonHourResource, + GlobalIdScalar, + ImageNode, + MarathonHourNode, } from "@ukdanceblue/common"; import { DateTimeISOResolver, VoidResolver } from "graphql-scalars"; import { @@ -21,17 +23,16 @@ import { } from "type-graphql"; import { Service } from "typedi"; -import { MarathonHourRepository } from "../repositories/marathonHour/MarathonHourRepository.js"; -import { marathonHourModelToResource } from "../repositories/marathonHour/marathonHourModelToResource.js"; - -import { AbstractGraphQLPaginatedResponse } from "./ApiResponse.js"; +import { MarathonHourRepository } from "#repositories/marathonHour/MarathonHourRepository.js"; +import { marathonHourModelToResource } from "#repositories/marathonHour/marathonHourModelToResource.js"; +import { AbstractGraphQLPaginatedResponse } from "#resolvers/ApiResponse.js"; @ObjectType("ListMarathonHoursResponse", { - implements: AbstractGraphQLPaginatedResponse, + implements: AbstractGraphQLPaginatedResponse, }) -class ListMarathonHoursResponse extends AbstractGraphQLPaginatedResponse { - @Field(() => [MarathonHourResource]) - data!: MarathonHourResource[]; +class ListMarathonHoursResponse extends AbstractGraphQLPaginatedResponse { + @Field(() => [MarathonHourNode]) + data!: MarathonHourNode[]; } @InputType() @@ -93,18 +94,18 @@ class ListMarathonHoursArgs extends FilteredListQueryArgs< date: ["shownStartingAt", "createdAt", "updatedAt"], }) {} -@Resolver(() => MarathonHourResource) +@Resolver(() => MarathonHourNode) @Service() export class MarathonHourResolver { constructor( private readonly marathonHourRepository: MarathonHourRepository ) {} - @Query(() => MarathonHourResource) - async marathonHour(@Arg("uuid") uuid: string) { + @Query(() => MarathonHourNode) + async marathonHour(@Arg("uuid", () => GlobalIdScalar) { id }: GlobalId) { const marathonHour = await this.marathonHourRepository.findMarathonHourByUnique({ - uuid, + uuid: id, }); if (marathonHour == null) { throw new DetailedError(ErrorCode.NotFound, "MarathonHour not found"); @@ -112,7 +113,7 @@ export class MarathonHourResolver { return marathonHourModelToResource(marathonHour); } - @Query(() => MarathonHourResource, { nullable: true }) + @Query(() => MarathonHourNode, { nullable: true }) async currentMarathonHour() { const marathonHour = await this.marathonHourRepository.findCurrentMarathonHour(); @@ -135,12 +136,12 @@ export class MarathonHourResolver { }); } - @FieldResolver(() => [ImageResource]) - async mapImages(@Root() marathonHour: MarathonHourResource) { - return this.marathonHourRepository.getMaps({ uuid: marathonHour.uuid }); + @FieldResolver(() => [ImageNode]) + async mapImages(@Root() { id: { id } }: MarathonHourNode) { + return this.marathonHourRepository.getMaps({ uuid: id }); } - @Mutation(() => MarathonHourResource) + @Mutation(() => MarathonHourNode) async createMarathonHour( @Arg("input") input: CreateMarathonHourInput, @Arg("marathonUuid") marathonUuid: string @@ -152,13 +153,13 @@ export class MarathonHourResolver { return marathonHourModelToResource(marathonHour); } - @Mutation(() => MarathonHourResource) + @Mutation(() => MarathonHourNode) async setMarathonHour( - @Arg("uuid") uuid: string, + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId, @Arg("input") input: SetMarathonHourInput ) { const marathonHour = await this.marathonHourRepository.updateMarathonHour( - { uuid }, + { uuid: id }, input ); if (marathonHour == null) { @@ -168,19 +169,24 @@ export class MarathonHourResolver { } @Mutation(() => VoidResolver) - async deleteMarathonHour(@Arg("uuid") uuid: string) { + async deleteMarathonHour( + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId + ) { const marathonHour = await this.marathonHourRepository.deleteMarathonHour({ - uuid, + uuid: id, }); if (marathonHour == null) { throw new DetailedError(ErrorCode.NotFound, "MarathonHour not found"); } } - @Mutation(() => MarathonHourResource) - async addMap(@Arg("uuid") uuid: string, @Arg("imageUuid") imageUuid: string) { + @Mutation(() => MarathonHourNode) + async addMap( + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId, + @Arg("imageUuid") imageUuid: string + ) { const marathonHour = await this.marathonHourRepository.addMap( - { uuid }, + { uuid: id }, { uuid: imageUuid } ); if (marathonHour == null) { @@ -191,11 +197,11 @@ export class MarathonHourResolver { @Mutation(() => VoidResolver) async removeMap( - @Arg("uuid") uuid: string, + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId, @Arg("imageUuid") imageUuid: string ) { const marathonHour = await this.marathonHourRepository.removeMap( - { uuid }, + { uuid: id }, { uuid: imageUuid } ); if (marathonHour == null) { diff --git a/packages/server/src/resolvers/MarathonResolver.ts b/packages/server/src/resolvers/MarathonResolver.ts index aed0d0be..3e1c5926 100644 --- a/packages/server/src/resolvers/MarathonResolver.ts +++ b/packages/server/src/resolvers/MarathonResolver.ts @@ -1,11 +1,14 @@ +import type { GlobalId } from "@ukdanceblue/common"; import { - DetailedError, - ErrorCode, + CommitteeIdentifier, FilteredListQueryArgs, - MarathonHourResource, - MarathonResource, + GlobalIdScalar, + MarathonHourNode, + MarathonNode, SortDirection, + TeamNode, } from "@ukdanceblue/common"; +import { ConcreteResult } from "@ukdanceblue/common/error"; import { DateTimeISOResolver, VoidResolver } from "graphql-scalars"; import { Arg, @@ -22,18 +25,19 @@ import { } from "type-graphql"; import { Service } from "typedi"; -import { MarathonRepository } from "../repositories/marathon/MarathonRepository.js"; -import { marathonModelToResource } from "../repositories/marathon/marathonModelToResource.js"; -import { marathonHourModelToResource } from "../repositories/marathonHour/marathonHourModelToResource.js"; - -import { AbstractGraphQLPaginatedResponse } from "./ApiResponse.js"; +import { CommitteeRepository } from "#repositories/committee/CommitteeRepository.js"; +import { MarathonRepository } from "#repositories/marathon/MarathonRepository.js"; +import { marathonModelToResource } from "#repositories/marathon/marathonModelToResource.js"; +import { marathonHourModelToResource } from "#repositories/marathonHour/marathonHourModelToResource.js"; +import { teamModelToResource } from "#repositories/team/teamModelToResource.js"; +import { AbstractGraphQLPaginatedResponse } from "#resolvers/ApiResponse.js"; @ObjectType("ListMarathonsResponse", { - implements: AbstractGraphQLPaginatedResponse, + implements: AbstractGraphQLPaginatedResponse, }) -class ListMarathonsResponse extends AbstractGraphQLPaginatedResponse { - @Field(() => [MarathonResource]) - data!: MarathonResource[]; +class ListMarathonsResponse extends AbstractGraphQLPaginatedResponse { + @Field(() => [MarathonNode]) + data!: MarathonNode[]; } @InputType() @@ -74,31 +78,34 @@ class ListMarathonsArgs extends FilteredListQueryArgs< date: ["startDate", "endDate", "createdAt", "updatedAt"], }) {} -@Resolver(() => MarathonResource) +@Resolver(() => MarathonNode) @Service() -export class MarathonResolver { - constructor(private readonly marathonRepository: MarathonRepository) {} +export class MarathonResolver + implements + Record< + `${CommitteeIdentifier}Team`, + (marathon: MarathonNode) => Promise> + > +{ + constructor( + private readonly marathonRepository: MarathonRepository, + private readonly committeeRepository: CommitteeRepository + ) {} - @Query(() => MarathonResource) - async marathon(@Arg("uuid") uuid: string) { + @Query(() => MarathonNode) + async marathon(@Arg("uuid", () => GlobalIdScalar) { id }: GlobalId) { const marathon = await this.marathonRepository.findMarathonByUnique({ - uuid, + uuid: id, }); - if (marathon == null) { - throw new DetailedError(ErrorCode.NotFound, "Marathon not found"); - } - return marathonModelToResource(marathon); + return marathon.map(marathonModelToResource); } - @Query(() => MarathonResource) + @Query(() => MarathonNode) async marathonForYear(@Arg("year") year: string) { const marathon = await this.marathonRepository.findMarathonByUnique({ year, }); - if (marathon == null) { - throw new DetailedError(ErrorCode.NotFound, "Marathon not found"); - } - return marathonModelToResource(marathon); + return marathon.map(marathonModelToResource); } @Query(() => ListMarathonsResponse) @@ -108,7 +115,7 @@ export class MarathonResolver { order: args.sortBy?.map((key, i) => [ key, - args.sortDirection?.[i] ?? SortDirection.DESCENDING, + args.sortDirection?.[i] ?? SortDirection.desc, ]) ?? [], skip: args.page != null && args.pageSize != null @@ -127,58 +134,145 @@ export class MarathonResolver { }); } - @Query(() => MarathonResource, { nullable: true }) + @Query(() => MarathonNode, { nullable: true }) async currentMarathon() { const marathon = await this.marathonRepository.findCurrentMarathon(); - if (marathon == null) { - return null; - } - return marathonModelToResource(marathon); + return marathon.map(marathonModelToResource); } - @Query(() => MarathonResource, { nullable: true }) - async nextMarathon() { - const marathon = await this.marathonRepository.findNextMarathon(); - if (marathon == null) { - return null; - } - return marathonModelToResource(marathon); + @Query(() => MarathonNode, { nullable: true }) + async latestMarathon() { + const marathon = await this.marathonRepository.findActiveMarathon(); + return marathon.map(marathonModelToResource); } - @Mutation(() => MarathonResource) + @Mutation(() => MarathonNode) async createMarathon(@Arg("input") input: CreateMarathonInput) { const marathon = await this.marathonRepository.createMarathon(input); - return marathonModelToResource(marathon); + return marathon.map(marathonModelToResource); } - @Mutation(() => MarathonResource) + @Mutation(() => MarathonNode) async setMarathon( - @Arg("uuid") uuid: string, + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId, @Arg("input") input: SetMarathonInput ) { const marathon = await this.marathonRepository.updateMarathon( - { uuid }, + { uuid: id }, input ); - if (marathon == null) { - throw new DetailedError(ErrorCode.NotFound, "Marathon not found"); - } - return marathonModelToResource(marathon); + return marathon.map(marathonModelToResource); } @Mutation(() => VoidResolver) - async deleteMarathon(@Arg("uuid") uuid: string) { - const marathon = await this.marathonRepository.deleteMarathon({ uuid }); - if (marathon == null) { - throw new DetailedError(ErrorCode.NotFound, "Marathon not found"); - } + async deleteMarathon(@Arg("uuid", () => GlobalIdScalar) { id }: GlobalId) { + const marathon = await this.marathonRepository.deleteMarathon({ uuid: id }); + return marathon.map(marathonModelToResource); } - @FieldResolver(() => [MarathonHourResource]) - async hours(@Root() marathon: MarathonResource) { + @FieldResolver(() => [MarathonHourNode]) + async hours(@Root() { id: { id } }: MarathonNode) { const rows = await this.marathonRepository.getMarathonHours({ - uuid: marathon.uuid, + uuid: id, + }); + return rows.map((hours) => hours.map(marathonHourModelToResource)); + } + + async #committeeTeam( + committee: CommitteeIdentifier, + { id: { id } }: MarathonNode + ) { + const result = await this.committeeRepository.getCommitteeTeam(committee, { + uuid: id, }); - return rows.map(marathonHourModelToResource); + return result.map(teamModelToResource); + } + + // Committees + @FieldResolver(() => TeamNode) + async communityDevelopmentCommitteeTeam(marathon: MarathonNode) { + return this.#committeeTeam( + CommitteeIdentifier.communityDevelopmentCommittee, + marathon + ); + } + + @FieldResolver(() => TeamNode) + async programmingCommitteeTeam(marathon: MarathonNode) { + return this.#committeeTeam( + CommitteeIdentifier.programmingCommittee, + marathon + ); + } + + @FieldResolver(() => TeamNode) + async fundraisingCommitteeTeam(marathon: MarathonNode) { + return this.#committeeTeam( + CommitteeIdentifier.fundraisingCommittee, + marathon + ); + } + + @FieldResolver(() => TeamNode) + async dancerRelationsCommitteeTeam(marathon: MarathonNode) { + return this.#committeeTeam( + CommitteeIdentifier.dancerRelationsCommittee, + marathon + ); + } + + @FieldResolver(() => TeamNode) + async familyRelationsCommitteeTeam(marathon: MarathonNode) { + return this.#committeeTeam( + CommitteeIdentifier.familyRelationsCommittee, + marathon + ); + } + + @FieldResolver(() => TeamNode) + async techCommitteeTeam(marathon: MarathonNode) { + return this.#committeeTeam(CommitteeIdentifier.techCommittee, marathon); + } + + @FieldResolver(() => TeamNode) + async operationsCommitteeTeam(marathon: MarathonNode) { + return this.#committeeTeam( + CommitteeIdentifier.operationsCommittee, + marathon + ); + } + + @FieldResolver(() => TeamNode) + async marketingCommitteeTeam(marathon: MarathonNode) { + return this.#committeeTeam( + CommitteeIdentifier.marketingCommittee, + marathon + ); + } + + @FieldResolver(() => TeamNode) + async corporateCommitteeTeam(marathon: MarathonNode) { + return this.#committeeTeam( + CommitteeIdentifier.corporateCommittee, + marathon + ); + } + + @FieldResolver(() => TeamNode) + async miniMarathonsCommitteeTeam(marathon: MarathonNode) { + return this.#committeeTeam( + CommitteeIdentifier.miniMarathonsCommittee, + marathon + ); + } + + @FieldResolver(() => TeamNode) + async viceCommitteeTeam(marathon: MarathonNode) { + return this.#committeeTeam(CommitteeIdentifier.viceCommittee, marathon); + } + + @FieldResolver(() => TeamNode) + async overallCommitteeTeam(marathon: MarathonNode) { + return this.#committeeTeam(CommitteeIdentifier.overallCommittee, marathon); } } diff --git a/packages/server/src/resolvers/MembershipResolver.ts b/packages/server/src/resolvers/MembershipResolver.ts index b48df25d..abf4cdc0 100644 --- a/packages/server/src/resolvers/MembershipResolver.ts +++ b/packages/server/src/resolvers/MembershipResolver.ts @@ -1,53 +1,49 @@ -import { - DetailedError, - ErrorCode, - MembershipResource, - PersonResource, - TeamResource, -} from "@ukdanceblue/common"; +import { MembershipNode, PersonNode, TeamNode } from "@ukdanceblue/common"; +import { ConcreteResult } from "@ukdanceblue/common/error"; import { FieldResolver, Resolver, Root } from "type-graphql"; import { Service } from "typedi"; -import { MembershipRepository } from "../repositories/membership/MembershipRepository.js"; -import { personModelToResource } from "../repositories/person/personModelToResource.js"; -import { teamModelToResource } from "../repositories/team/teamModelToResource.js"; - -@Resolver(() => MembershipResource) +import { MembershipRepository } from "#repositories/membership/MembershipRepository.js"; +import { PersonRepository } from "#repositories/person/PersonRepository.js"; +import { personModelToResource } from "#repositories/person/personModelToResource.js"; +import { teamModelToResource } from "#repositories/team/teamModelToResource.js"; +@Resolver(() => MembershipNode) @Service() export class MembershipResolver { - constructor(private readonly membershipRepository: MembershipRepository) {} + constructor( + private readonly membershipRepository: MembershipRepository, + private readonly personRepository: PersonRepository + ) {} - @FieldResolver(() => PersonResource) + @FieldResolver(() => PersonNode) async person( - @Root() membership: MembershipResource - ): Promise { + @Root() { id: { id } }: MembershipNode + ): Promise> { const row = await this.membershipRepository.findMembershipByUnique( - { uuid: membership.uuid }, + { uuid: id }, { person: true, } ); - if (row == null) { - throw new DetailedError(ErrorCode.NotFound, "Membership not found"); - } - - return personModelToResource(row.person); + return row + .toAsyncResult() + .andThen((row) => + personModelToResource(row.person, this.personRepository) + ).promise; } - @FieldResolver(() => TeamResource) - async team(@Root() membership: MembershipResource): Promise { + @FieldResolver(() => TeamNode) + async team( + @Root() { id: { id } }: MembershipNode + ): Promise> { const row = await this.membershipRepository.findMembershipByUnique( - { uuid: membership.uuid }, + { uuid: id }, { team: true, } ); - if (row == null) { - throw new DetailedError(ErrorCode.NotFound, "Membership not found"); - } - - return teamModelToResource(row.team); + return row.map((row) => teamModelToResource(row.team)); } } diff --git a/packages/server/src/resolvers/NodeResolver.ts b/packages/server/src/resolvers/NodeResolver.ts new file mode 100644 index 00000000..2097622d --- /dev/null +++ b/packages/server/src/resolvers/NodeResolver.ts @@ -0,0 +1,122 @@ +import type { GlobalId } from "@ukdanceblue/common"; +import { + ConfigurationNode, + DeviceNode, + EventNode, + FundraisingAssignmentNode, + FundraisingEntryNode, + GlobalIdScalar, + ImageNode, + MarathonHourNode, + MarathonNode, + Node, + NotificationNode, + PersonNode, + PointEntryNode, + PointOpportunityNode, + TeamNode, +} from "@ukdanceblue/common"; +import { ConcreteResult } from "@ukdanceblue/common/error"; +import { Ok } from "ts-results-es"; +import { Arg, Query, Resolver } from "type-graphql"; +import { Service } from "typedi"; + +import { ConfigurationResolver } from "#resolvers/ConfigurationResolver.js"; +import { DeviceResolver } from "#resolvers/DeviceResolver.js"; +import { EventResolver } from "#resolvers/EventResolver.js"; +// import { FeedResolver } from "#resolvers/FeedResolver.js"; +import { FundraisingAssignmentResolver } from "#resolvers/FundraisingAssignmentResolver.js"; +import { FundraisingEntryResolver } from "#resolvers/FundraisingEntryResolver.js"; +import { ImageResolver } from "#resolvers/ImageResolver.js"; +import { MarathonHourResolver } from "#resolvers/MarathonHourResolver.js"; +import { MarathonResolver } from "#resolvers/MarathonResolver.js"; +import { NotificationResolver } from "#resolvers/NotificationResolver.js"; +import { PersonResolver } from "#resolvers/PersonResolver.js"; +import { PointEntryResolver } from "#resolvers/PointEntryResolver.js"; +import { PointOpportunityResolver } from "#resolvers/PointOpportunityResolver.js"; +import { TeamResolver } from "#resolvers/TeamResolver.js"; + +@Resolver(() => Node) +@Service() +export class NodeResolver { + constructor( + private readonly configurationResolver: ConfigurationResolver, + private readonly deviceResolver: DeviceResolver, + private readonly eventResolver: EventResolver, + // private readonly feedResolver: FeedResolver, + private readonly fundraisingAssignmentResolver: FundraisingAssignmentResolver, + private readonly fundraisingEntryResolver: FundraisingEntryResolver, + private readonly imageResolver: ImageResolver, + private readonly marathonHourResolver: MarathonHourResolver, + private readonly marathonResolver: MarathonResolver, + private readonly notificationResolver: NotificationResolver, + private readonly personResolver: PersonResolver, + private readonly pointOpportunityResolver: PointOpportunityResolver, + private readonly pointEntryResolver: PointEntryResolver, + private readonly teamResolver: TeamResolver + ) {} + + @Query(() => Node) + async node( + @Arg("id", () => GlobalIdScalar) id: GlobalId + ): Promise> { + switch (id.typename) { + case ConfigurationNode.constructor.name: { + const { data } = await this.configurationResolver.getByUuid(id); + return Ok(data); + } + case DeviceNode.constructor.name: { + const { data } = await this.deviceResolver.getByUuid(id); + return Ok(data); + } + case EventNode.constructor.name: { + const { data } = await this.eventResolver.getByUuid(id); + return Ok(data); + } + // TODO: fix this + // case FeedResolver.constructor.name: { + // const { data } = await this.feedResolver.getByUuid(id); + // return Ok(data); + // } + case FundraisingAssignmentNode.constructor.name: { + return this.fundraisingAssignmentResolver.fundraisingAssignment(id); + } + case FundraisingEntryNode.constructor.name: { + return this.fundraisingEntryResolver.fundraisingEntry(id); + } + case ImageNode.constructor.name: { + const { data } = await this.imageResolver.getByUuid(id); + return Ok(data); + } + case MarathonHourNode.constructor.name: { + const data = await this.marathonHourResolver.marathonHour(id); + return Ok(data); + } + case MarathonNode.constructor.name: { + return this.marathonResolver.marathon(id); + } + case NotificationNode.constructor.name: { + const { data } = await this.notificationResolver.getByUuid(id); + return Ok(data); + } + case PersonNode.constructor.name: { + return this.personResolver.getByUuid(id); + } + case PointOpportunityNode.constructor.name: { + const { data } = await this.pointOpportunityResolver.getByUuid(id); + return Ok(data); + } + case PointEntryNode.constructor.name: { + const { data } = await this.pointEntryResolver.getByUuid(id); + return Ok(data); + } + case TeamNode.constructor.name: { + const { data } = await this.teamResolver.getByUuid(id); + return Ok(data); + } + default: { + throw new Error(`Unknown typename: ${id.typename}`); + } + } + } +} diff --git a/packages/server/src/resolvers/NotificationResolver.ts b/packages/server/src/resolvers/NotificationResolver.ts index 52f33a09..6109bbcf 100644 --- a/packages/server/src/resolvers/NotificationResolver.ts +++ b/packages/server/src/resolvers/NotificationResolver.ts @@ -1,12 +1,14 @@ import type { NotificationError } from "@prisma/client"; +import type { GlobalId } from "@ukdanceblue/common"; import { AccessControl, AccessLevel, DetailedError, ErrorCode, FilteredListQueryArgs, - NotificationDeliveryResource, - NotificationResource, + GlobalIdScalar, + NotificationDeliveryNode, + NotificationNode, SortDirection, TeamType, } from "@ukdanceblue/common"; @@ -26,33 +28,32 @@ import { } from "type-graphql"; import { Inject, Service } from "typedi"; -import { NotificationScheduler } from "../jobs/NotificationScheduler.js"; -import { ExpoNotificationProvider } from "../lib/notification/ExpoNotificationProvider.js"; -import * as NotificationProviderJs from "../lib/notification/NotificationProvider.js"; -import { NotificationRepository } from "../repositories/notification/NotificationRepository.js"; -import { notificationModelToResource } from "../repositories/notification/notificationModelToResource.js"; -import { NotificationDeliveryRepository } from "../repositories/notificationDelivery/NotificationDeliveryRepository.js"; -import { notificationDeliveryModelToResource } from "../repositories/notificationDelivery/notificationDeliveryModelToResource.js"; - +import { NotificationScheduler } from "#jobs/NotificationScheduler.js"; +import { ExpoNotificationProvider } from "#notification/ExpoNotificationProvider.js"; +import * as NotificationProviderJs from "#notification/NotificationProvider.js"; +import { NotificationRepository } from "#repositories/notification/NotificationRepository.js"; +import { notificationModelToResource } from "#repositories/notification/notificationModelToResource.js"; +import { NotificationDeliveryRepository } from "#repositories/notificationDelivery/NotificationDeliveryRepository.js"; +import { notificationDeliveryModelToResource } from "#repositories/notificationDelivery/notificationDeliveryModelToResource.js"; import { AbstractGraphQLCreatedResponse, AbstractGraphQLOkResponse, AbstractGraphQLPaginatedResponse, -} from "./ApiResponse.js"; +} from "#resolvers/ApiResponse.js"; @ObjectType("GetNotificationByUuidResponse", { - implements: AbstractGraphQLOkResponse, + implements: AbstractGraphQLOkResponse, }) -class GetNotificationByUuidResponse extends AbstractGraphQLOkResponse { - @Field(() => NotificationResource) - data!: NotificationResource; +class GetNotificationByUuidResponse extends AbstractGraphQLOkResponse { + @Field(() => NotificationNode) + data!: NotificationNode; } @ObjectType("ListNotificationsResponse", { - implements: AbstractGraphQLPaginatedResponse, + implements: AbstractGraphQLPaginatedResponse, }) -class ListNotificationsResponse extends AbstractGraphQLPaginatedResponse { - @Field(() => [NotificationResource]) - data!: NotificationResource[]; +class ListNotificationsResponse extends AbstractGraphQLPaginatedResponse { + @Field(() => [NotificationNode]) + data!: NotificationNode[]; } @InputType() @@ -86,11 +87,11 @@ class StageNotificationArgs { } @ObjectType("StageNotificationResponse", { - implements: AbstractGraphQLCreatedResponse, + implements: AbstractGraphQLCreatedResponse, }) -class StageNotificationResponse extends AbstractGraphQLCreatedResponse { - @Field(() => NotificationResource) - data!: NotificationResource; +class StageNotificationResponse extends AbstractGraphQLCreatedResponse { + @Field(() => NotificationNode) + data!: NotificationNode; } @ObjectType("SendNotificationResponse", { @@ -186,11 +187,11 @@ class ListNotificationDeliveriesArgs extends FilteredListQueryArgs< } @ObjectType("ListNotificationDeliveriesResponse", { - implements: AbstractGraphQLPaginatedResponse, + implements: AbstractGraphQLPaginatedResponse, }) -class ListNotificationDeliveriesResponse extends AbstractGraphQLPaginatedResponse { - @Field(() => [NotificationDeliveryResource]) - data!: NotificationDeliveryResource[]; +class ListNotificationDeliveriesResponse extends AbstractGraphQLPaginatedResponse { + @Field(() => [NotificationDeliveryNode]) + data!: NotificationDeliveryNode[]; } @ObjectType("NotificationDeliveryIssueCount", { @@ -214,7 +215,7 @@ class NotificationDeliveryIssueCount Unknown!: number; } -@Resolver(() => NotificationResource) +@Resolver(() => NotificationNode) @Service() export class NotificationResolver { constructor( @@ -230,10 +231,10 @@ export class NotificationResolver { }) @Query(() => GetNotificationByUuidResponse, { name: "notification" }) async getByUuid( - @Arg("uuid") uuid: string + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId ): Promise { const row = await this.notificationRepository.findNotificationByUnique({ - uuid, + uuid: id, }); if (row == null) { @@ -257,7 +258,7 @@ export class NotificationResolver { order: query.sortBy?.map((key, i) => [ key, - query.sortDirection?.[i] ?? SortDirection.DESCENDING, + query.sortDirection?.[i] ?? SortDirection.desc, ]) ?? [], skip: query.page != null && query.pageSize != null @@ -294,7 +295,7 @@ export class NotificationResolver { order: query.sortBy?.map((key, i) => [ key, - query.sortDirection?.[i] ?? SortDirection.DESCENDING, + query.sortDirection?.[i] ?? SortDirection.desc, ]) ?? [], skip: query.page != null && query.pageSize != null @@ -369,9 +370,11 @@ export class NotificationResolver { name: "sendNotification", description: "Send a notification immediately.", }) - async send(@Arg("uuid") uuid: string): Promise { + async send( + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId + ): Promise { const databaseNotification = - await this.notificationRepository.findNotificationByUnique({ uuid }); + await this.notificationRepository.findNotificationByUnique({ uuid: id }); if (databaseNotification == null) { throw new DetailedError(ErrorCode.NotFound, "Notification not found"); @@ -398,11 +401,11 @@ export class NotificationResolver { name: "scheduleNotification", }) async schedule( - @Arg("uuid") uuid: string, + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId, @Arg("sendAt") sendAt: Date ): Promise { const notification = - await this.notificationRepository.findNotificationByUnique({ uuid }); + await this.notificationRepository.findNotificationByUnique({ uuid: id }); if (notification == null) { throw new DetailedError(ErrorCode.NotFound, "Notification not found"); @@ -432,10 +435,10 @@ export class NotificationResolver { name: "acknowledgeDeliveryIssue", }) async acknowledgeDeliveryIssue( - @Arg("uuid") uuid: string + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId ): Promise { const notification = - await this.notificationRepository.findNotificationByUnique({ uuid }); + await this.notificationRepository.findNotificationByUnique({ uuid: id }); if (notification == null) { throw new DetailedError(ErrorCode.NotFound, "Notification not found"); @@ -463,10 +466,10 @@ export class NotificationResolver { name: "abortScheduledNotification", }) async abortScheduled( - @Arg("uuid") uuid: string + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId ): Promise { const notification = - await this.notificationRepository.findNotificationByUnique({ uuid }); + await this.notificationRepository.findNotificationByUnique({ uuid: id }); if (notification == null) { throw new DetailedError(ErrorCode.NotFound, "Notification not found"); @@ -499,7 +502,7 @@ export class NotificationResolver { }) @Mutation(() => DeleteNotificationResponse, { name: "deleteNotification" }) async delete( - @Arg("uuid") uuid: string, + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId, @Arg("force", { nullable: true, description: @@ -508,7 +511,7 @@ export class NotificationResolver { force?: boolean ): Promise { const notification = - await this.notificationRepository.findNotificationByUnique({ uuid }); + await this.notificationRepository.findNotificationByUnique({ uuid: id }); if (notification == null) { throw new DetailedError(ErrorCode.NotFound, "Notification not found"); @@ -532,8 +535,12 @@ export class NotificationResolver { accessLevel: AccessLevel.CommitteeChairOrCoordinator, }) @FieldResolver(() => Int, { name: "deliveryCount" }) - async deliveryCount(@Root() { uuid }: NotificationResource): Promise { - return this.notificationRepository.countDeliveriesForNotification({ uuid }); + async deliveryCount( + @Root() { id: { id } }: NotificationNode + ): Promise { + return this.notificationRepository.countDeliveriesForNotification({ + uuid: id, + }); } @AccessControl({ @@ -543,11 +550,11 @@ export class NotificationResolver { name: "deliveryIssueCount", }) async deliveryIssueCount( - @Root() { uuid }: NotificationResource + @Root() { id: { id } }: NotificationNode ): Promise { const issues = await this.notificationRepository.countFailedDeliveriesForNotification({ - uuid, + uuid: id, }); const retVal = new NotificationDeliveryIssueCount(); @@ -557,22 +564,22 @@ export class NotificationResolver { } } -@Resolver(() => NotificationDeliveryResource) +@Resolver(() => NotificationDeliveryNode) @Service() export class NotificationDeliveryResolver { constructor( private readonly notificationDeliveryRepository: NotificationDeliveryRepository ) {} - @FieldResolver(() => NotificationResource, { + @FieldResolver(() => NotificationNode, { name: "notification", }) async getNotificationForDelivery( - @Root() { uuid }: NotificationDeliveryResource - ): Promise { + @Root() { id: { id } }: NotificationDeliveryNode + ): Promise { const notification = await this.notificationDeliveryRepository.findNotificationForDelivery({ - uuid, + uuid: id, }); if (notification == null) { diff --git a/packages/server/src/resolvers/PersonResolver.ts b/packages/server/src/resolvers/PersonResolver.ts index a560d75e..9ef056b0 100644 --- a/packages/server/src/resolvers/PersonResolver.ts +++ b/packages/server/src/resolvers/PersonResolver.ts @@ -1,16 +1,22 @@ +import { TeamType } from "@prisma/client"; +import type { GlobalId } from "@ukdanceblue/common"; import { AccessControl, AccessLevel, - DetailedError, - ErrorCode, + CommitteeMembershipNode, + DbRole, FilteredListQueryArgs, + FundraisingAssignmentNode, + FundraisingEntryNode, + GlobalIdScalar, + MembershipNode, MembershipPositionType, - MembershipResource, - PersonResource, - RoleResource, + PersonNode, SortDirection, } from "@ukdanceblue/common"; +import { ConcreteError , ConcreteResult } from "@ukdanceblue/common/error"; import { EmailAddressResolver } from "graphql-scalars"; +import { Ok, Result } from "ts-results-es"; import { Arg, Args, @@ -25,53 +31,36 @@ import { Resolver, Root, } from "type-graphql"; -import { Service } from "typedi"; - -import { auditLogger } from "../lib/logging/auditLogging.js"; -import { membershipModelToResource } from "../repositories/membership/membershipModelToResource.js"; -import { PersonRepository } from "../repositories/person/PersonRepository.js"; -import { personModelToResource } from "../repositories/person/personModelToResource.js"; - +import { Container, Service } from "typedi"; + +import { CatchableConcreteError } from "#lib/formatError.js"; +import { auditLogger } from "#logging/auditLogging.js"; +import { DBFundsRepository } from "#repositories/fundraising/DBFundsRepository.js"; +import { FundraisingEntryRepository } from "#repositories/fundraising/FundraisingRepository.js"; +import { fundraisingAssignmentModelToNode } from "#repositories/fundraising/fundraisingAssignmentModelToNode.js"; +import { fundraisingEntryModelToNode } from "#repositories/fundraising/fundraisingEntryModelToNode.js"; +import { MembershipRepository } from "#repositories/membership/MembershipRepository.js"; import { - AbstractGraphQLArrayOkResponse, - AbstractGraphQLCreatedResponse, - AbstractGraphQLOkResponse, - AbstractGraphQLPaginatedResponse, -} from "./ApiResponse.js"; -import * as Context from "./context.js"; - -@ObjectType("CreatePersonResponse", { - implements: AbstractGraphQLCreatedResponse, -}) -class CreatePersonResponse extends AbstractGraphQLCreatedResponse { - @Field(() => PersonResource) - data!: PersonResource; -} -@ObjectType("GetPersonResponse", { - implements: AbstractGraphQLOkResponse, -}) -class GetPersonResponse extends AbstractGraphQLOkResponse { - @Field(() => PersonResource, { nullable: true }) - data!: PersonResource | null; -} -@ObjectType("GetPeopleResponse", { - implements: AbstractGraphQLArrayOkResponse, -}) -class GetPeopleResponse extends AbstractGraphQLArrayOkResponse { - @Field(() => [PersonResource]) - data!: PersonResource[]; -} + committeeMembershipModelToResource, + membershipModelToResource, +} from "#repositories/membership/membershipModelToResource.js"; +import { PersonRepository } from "#repositories/person/PersonRepository.js"; +import { personModelToResource } from "#repositories/person/personModelToResource.js"; +import { AbstractGraphQLPaginatedResponse } from "#resolvers/ApiResponse.js"; +import { + ListFundraisingEntriesArgs, + ListFundraisingEntriesResponse, + globalFundraisingAccessParam, +} from "#resolvers/FundraisingEntryResolver.js"; +import type { GraphQLContext } from "#resolvers/context.js"; + @ObjectType("ListPeopleResponse", { - implements: AbstractGraphQLPaginatedResponse, + implements: AbstractGraphQLPaginatedResponse, }) -class ListPeopleResponse extends AbstractGraphQLPaginatedResponse { - @Field(() => [PersonResource]) - data!: PersonResource[]; +class ListPeopleResponse extends AbstractGraphQLPaginatedResponse { + @Field(() => [PersonNode]) + data!: PersonNode[]; } -@ObjectType("DeletePersonResponse", { - implements: AbstractGraphQLOkResponse, -}) -class DeletePersonResponse extends AbstractGraphQLOkResponse {} @ArgsType() class ListPeopleArgs extends FilteredListQueryArgs< @@ -104,8 +93,8 @@ class CreatePersonInput { @Field(() => String, { nullable: true }) linkblue?: string; - @Field(() => RoleResource, { nullable: true }) - role?: RoleResource; + @Field(() => DbRole, { nullable: true }) + dbRole?: DbRole; @Field(() => [String], { defaultValue: [] }) memberOf?: string[]; @@ -124,9 +113,6 @@ class SetPersonInput { @Field(() => String, { nullable: true }) linkblue?: string; - @Field(() => RoleResource, { nullable: true }) - role?: RoleResource; - @Field(() => [String], { nullable: true }) memberOf?: string[]; @@ -134,56 +120,55 @@ class SetPersonInput { captainOf?: string[]; } -@Resolver(() => PersonResource) +@Resolver(() => PersonNode) @Service() export class PersonResolver { - constructor(private personRepository: PersonRepository) {} - - @Query(() => GetPersonResponse, { name: "person" }) - async getByUuid(@Arg("uuid") uuid: string): Promise { - const row = await this.personRepository.findPersonByUuid(uuid); - - if (row == null) { - return GetPersonResponse.newOk( - null - ); - } + constructor( + private readonly personRepository: PersonRepository, + private readonly membershipRepository: MembershipRepository, + private readonly fundraisingEntryRepository: FundraisingEntryRepository + ) {} - return GetPersonResponse.newOk( - personModelToResource(row) - ); + @AccessControl({ accessLevel: AccessLevel.Committee }) + @Query(() => PersonNode, { name: "person" }) + async getByUuid( + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId + ): Promise> { + const row = await this.personRepository.findPersonByUnique({ uuid: id }); + + return row + .toAsyncResult() + .andThen((row) => personModelToResource(row, this.personRepository)) + .promise; } @AccessControl({ accessLevel: AccessLevel.Committee }) - @Query(() => GetPersonResponse, { name: "personByLinkBlue" }) + @Query(() => PersonNode, { name: "personByLinkBlue" }) async getByLinkBlueId( @Arg("linkBlueId") linkBlueId: string - ): Promise { - const row = await this.personRepository.findPersonByLinkblue(linkBlueId); - - if (row == null) { - return GetPersonResponse.newOk( - null - ); - } + ): Promise> { + const row = await this.personRepository.findPersonByUnique({ + linkblue: linkBlueId, + }); - return GetPersonResponse.newOk( - personModelToResource(row) - ); + return row + .toAsyncResult() + .andThen((row) => personModelToResource(row, this.personRepository)) + .promise; } @AccessControl({ accessLevel: AccessLevel.Committee }) @Query(() => ListPeopleResponse, { name: "listPeople" }) async list( @Args(() => ListPeopleArgs) args: ListPeopleArgs - ): Promise { + ): Promise> { const [rows, total] = await Promise.all([ this.personRepository.listPeople({ filters: args.filters, order: args.sortBy?.map((key, i) => [ key, - args.sortDirection?.[i] ?? SortDirection.DESCENDING, + args.sortDirection?.[i] ?? SortDirection.desc, ]) ?? [], skip: args.page != null && args.pageSize != null @@ -194,55 +179,80 @@ export class PersonResolver { this.personRepository.countPeople({ filters: args.filters }), ]); - return ListPeopleResponse.newPaginated({ - data: rows.map((row) => personModelToResource(row)), + return Result.all([ + await rows + .toAsyncResult() + .andThen(async (rows) => + Result.all( + await Promise.all( + rows.map( + (row) => + personModelToResource(row, this.personRepository).promise + ) + ) + ) + ).promise, total, - page: args.page, - pageSize: args.pageSize, + ]).andThen(([rows, total]) => { + return Ok( + ListPeopleResponse.newPaginated({ + data: rows, + total, + page: args.page, + pageSize: args.pageSize, + }) + ); }); } - @Query(() => GetPersonResponse, { name: "me" }) - me(@Ctx() ctx: Context.GraphQLContext): GetPersonResponse | null { - return GetPersonResponse.newOk( - ctx.authenticatedUser - ); + @Query(() => PersonNode, { name: "me", nullable: true }) + me(@Ctx() ctx: GraphQLContext): PersonNode | null { + return ctx.authenticatedUser; } - @Query(() => GetPeopleResponse, { name: "searchPeopleByName" }) - async searchByName(@Arg("name") name: string): Promise { + @AccessControl({ accessLevel: AccessLevel.Committee }) + @Query(() => [PersonNode], { name: "searchPeopleByName" }) + async searchByName( + @Arg("name") name: string + ): Promise> { const rows = await this.personRepository.searchByName(name); - return GetPeopleResponse.newOk( - rows.map((row) => personModelToResource(row)) - ); + return rows + .toAsyncResult() + .andThen(async (rows) => + Result.all( + await Promise.all( + rows.map( + (row) => personModelToResource(row, this.personRepository).promise + ) + ) + ) + ).promise; } - @AccessControl({ accessLevel: AccessLevel.Committee }) - @Mutation(() => CreatePersonResponse, { name: "createPerson" }) + @AccessControl({ accessLevel: AccessLevel.CommitteeChairOrCoordinator }) + @Mutation(() => PersonNode, { name: "createPerson" }) async create( @Arg("input") input: CreatePersonInput - ): Promise { + ): Promise> { const person = await this.personRepository.createPerson({ name: input.name, email: input.email, linkblue: input.linkblue, - committeeRole: input.role?.committeeRole, - committeeName: input.role?.committeeIdentifier, }); - return CreatePersonResponse.newCreated( - personModelToResource(person), - person.uuid - ); + return person + .toAsyncResult() + .andThen((person) => personModelToResource(person, this.personRepository)) + .promise; } - @AccessControl({ accessLevel: AccessLevel.Committee }) - @Mutation(() => GetPersonResponse, { name: "setPerson" }) + @AccessControl({ accessLevel: AccessLevel.CommitteeChairOrCoordinator }) + @Mutation(() => PersonNode, { name: "setPerson" }) async set( - @Arg("uuid") id: string, + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId, @Arg("input") input: SetPersonInput - ): Promise { + ): Promise> { const row = await this.personRepository.updatePerson( { uuid: id, @@ -251,42 +261,140 @@ export class PersonResolver { name: input.name, email: input.email, linkblue: input.linkblue, - committeeRole: input.role?.committeeRole, - committeeName: input.role?.committeeIdentifier, memberOf: input.memberOf?.map((uuid) => ({ uuid })), captainOf: input.captainOf?.map((uuid) => ({ uuid })), } ); - if (row == null) { - return GetPersonResponse.newOk( - null - ); + return row + .toAsyncResult() + .andThen((row) => personModelToResource(row, this.personRepository)) + .promise; + } + + @AccessControl({ accessLevel: AccessLevel.CommitteeChairOrCoordinator }) + @Mutation(() => MembershipNode, { name: "addPersonToTeam" }) + async assignPersonToTeam( + @Arg("personUuid") personUuid: string, + @Arg("teamUuid") teamUuid: string + ): Promise> { + const membership = await this.membershipRepository.assignPersonToTeam({ + personParam: { + uuid: personUuid, + }, + teamParam: { + uuid: teamUuid, + }, + position: MembershipPositionType.Member, + }); + + return membership.map(membershipModelToResource); + } + + @AccessControl({ accessLevel: AccessLevel.CommitteeChairOrCoordinator }) + @Mutation(() => PersonNode, { name: "deletePerson" }) + async delete( + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId + ): Promise> { + const result = await this.personRepository.deletePerson({ uuid: id }); + + return result + .toAsyncResult() + .andThen((row) => personModelToResource(row, this.personRepository)) + .map((person) => { + auditLogger.sensitive("Person deleted", { + person: { + name: person.name, + email: person.email, + uuid: person.id, + }, + }); + + return person; + }).promise; + } + + @AccessControl( + { accessLevel: AccessLevel.Committee }, + { + rootMatch: [ + { + root: "id", + extractor: ({ userData }) => userData.userId, + }, + ], } + ) + @FieldResolver(() => [CommitteeMembershipNode]) + async committees( + @Root() { id: { id } }: PersonNode + ): Promise> { + const models = await this.personRepository.findCommitteeMembershipsOfPerson( + { + uuid: id, + } + ); - return GetPersonResponse.newOk( - personModelToResource(row) + return models.map((models) => + models.map((membership) => { + return committeeMembershipModelToResource( + membership, + membership.team.correspondingCommittee.identifier + ); + }) ); } - @AccessControl({ accessLevel: AccessLevel.Committee }) - @Mutation(() => DeletePersonResponse, { name: "deletePerson" }) - async delete(@Arg("uuid") uuid: string): Promise { - const result = await this.personRepository.deletePerson({ uuid }); - - if (result == null) { - throw new DetailedError(ErrorCode.DatabaseFailure, "Failed to delete"); + @AccessControl( + { accessLevel: AccessLevel.Committee }, + { + rootMatch: [ + { + root: "id", + extractor: ({ userData }) => userData.userId, + }, + ], } + ) + @FieldResolver(() => [MembershipNode]) + async teams( + @Root() { id: { id } }: PersonNode + ): Promise> { + const models = await this.personRepository.findMembershipsOfPerson( + { + uuid: id, + }, + {}, + [TeamType.Spirit] + ); + + return models.map((models) => models.map(membershipModelToResource)); + } - auditLogger.sensitive("Person deleted", { - person: { - name: result.name, - email: result.email, - uuid: result.uuid, + @AccessControl( + { accessLevel: AccessLevel.Committee }, + { + rootMatch: [ + { + root: "id", + extractor: ({ userData }) => userData.userId, + }, + ], + } + ) + @FieldResolver(() => [MembershipNode]) + async moraleTeams( + @Root() { id: { id } }: PersonNode + ): Promise> { + const models = await this.personRepository.findMembershipsOfPerson( + { + uuid: id, }, - }); + {}, + [TeamType.Morale] + ); - return DeletePersonResponse.newOk(true); + return models.map((models) => models.map(membershipModelToResource)); } @AccessControl( @@ -294,41 +402,139 @@ export class PersonResolver { { rootMatch: [ { - root: "uuid", - extractor: (userData) => userData.userId, + root: "id", + extractor: ({ userData }) => userData.userId, }, ], } ) - @FieldResolver(() => [MembershipResource]) - async teams(@Root() person: PersonResource): Promise { - const models = await this.personRepository.findMembershipsOfPerson({ - uuid: person.uuid, + @FieldResolver(() => CommitteeMembershipNode, { nullable: true }) + async primaryCommittee( + @Root() { id: { id } }: PersonNode + ): Promise> { + const models = await this.personRepository.getPrimaryCommitteeOfPerson({ + uuid: id, + }); + + return models.map(([membership, committee]) => + committeeMembershipModelToResource(membership, committee.identifier) + ); + } + + @AccessControl( + // We can't grant blanket access as otherwise people would see who else was assigned to an entry + // You can view all assignments for an entry if you are: + // 1. A fundraising coordinator or chair + globalFundraisingAccessParam, + // 2. The captain of the team the entry is associated with + { + custom: async ( + { id: { id } }, + { teamMemberships, userData: { userId } } + ): Promise => { + if (userId == null) { + return false; + } + const captainOf = teamMemberships.filter( + (membership) => membership.position === MembershipPositionType.Captain + ); + if (captainOf.length === 0) { + return false; + } + + const fundraisingEntryRepository = Container.get( + FundraisingEntryRepository + ); + const entry = await fundraisingEntryRepository.findEntryByUnique({ + uuid: id, + }); + if (entry.isErr()) { + return false; + } + const dbFundsRepository = Container.get(DBFundsRepository); + const teams = await dbFundsRepository.getTeamsForDbFundsTeam({ + id: entry.value.dbFundsEntry.dbFundsTeamId, + }); + if (teams.isErr()) { + return false; + } + return captainOf.some(({ teamId }) => + teams.value.some((team) => team.uuid === teamId) + ); + }, + } + ) + @FieldResolver(() => CommitteeMembershipNode, { nullable: true }) + async assignedDonationEntries( + @Root() { id: { id } }: PersonNode, + @Args(() => ListFundraisingEntriesArgs) args: ListFundraisingEntriesArgs + ): Promise { + const entries = await this.fundraisingEntryRepository.listEntries( + { + filters: args.filters, + order: + args.sortBy?.map((key, i) => [ + key, + args.sortDirection?.[i] ?? SortDirection.desc, + ]) ?? [], + skip: + args.page != null && args.pageSize != null + ? (args.page - 1) * args.pageSize + : null, + take: args.pageSize, + }, + { + // EXTREMELY IMPORTANT FOR SECURITY + assignedToPerson: { uuid: id }, + } + ); + const count = await this.fundraisingEntryRepository.countEntries({ + filters: args.filters, }); - if (models == null) { - return []; + if (entries.isErr()) { + throw new CatchableConcreteError(entries.error); + } + if (count.isErr()) { + throw new CatchableConcreteError(count.error); } - return models.map((row) => membershipModelToResource(row)); + return ListFundraisingEntriesResponse.newPaginated({ + data: await Promise.all( + entries.value.map((model) => fundraisingEntryModelToNode(model)) + ), + total: count.value, + page: args.page, + pageSize: args.pageSize, + }); } - @AccessControl({ accessLevel: AccessLevel.Committee }) - @FieldResolver(() => [MembershipResource], { - deprecationReason: "Use teams instead and filter by position", + // This is the only way normal dancers or committee members can access fundraising info + // as it will only grant them the individual assignment they are associated with plus + // shallow access to the entry itself + @AccessControl({ + rootMatch: [ + { + root: "id", + extractor: ({ userData }) => userData.userId, + }, + ], }) - async captaincies( - @Root() person: PersonResource - ): Promise { - const models = await this.personRepository.findMembershipsOfPerson( - { uuid: person.uuid }, - { position: MembershipPositionType.Captain } - ); + @FieldResolver(() => [FundraisingAssignmentNode]) + async fundraisingAssignments( + @Root() { id: { id } }: PersonNode + ): Promise { + const models = + await this.fundraisingEntryRepository.getAssignmentsForPerson({ + uuid: id, + }); - if (models == null) { - return []; + if (models.isErr()) { + throw new CatchableConcreteError(models.error); } - return models.map((row) => membershipModelToResource(row)); + return Promise.all( + models.value.map((row) => fundraisingAssignmentModelToNode(row)) + ); } } diff --git a/packages/server/src/resolvers/PointEntryResolver.ts b/packages/server/src/resolvers/PointEntryResolver.ts index 0bb00088..7e782b85 100644 --- a/packages/server/src/resolvers/PointEntryResolver.ts +++ b/packages/server/src/resolvers/PointEntryResolver.ts @@ -1,13 +1,17 @@ +import type { GlobalId } from "@ukdanceblue/common"; import { DetailedError, ErrorCode, FilteredListQueryArgs, - PersonResource, - PointEntryResource, - PointOpportunityResource, + GlobalIdScalar, + PersonNode, + PointEntryNode, + PointOpportunityNode, SortDirection, - TeamResource, + TeamNode, } from "@ukdanceblue/common"; +import { NotFoundError , ConcreteResult } from "@ukdanceblue/common/error"; +import { Err } from "ts-results-es"; import { Arg, Args, @@ -24,38 +28,38 @@ import { } from "type-graphql"; import { Service } from "typedi"; -import { personModelToResource } from "../repositories/person/personModelToResource.js"; -import { PointEntryRepository } from "../repositories/pointEntry/PointEntryRepository.js"; -import { pointEntryModelToResource } from "../repositories/pointEntry/pointEntryModelToResource.js"; -import { pointOpportunityModelToResource } from "../repositories/pointOpportunity/pointOpportunityModelToResource.js"; -import { teamModelToResource } from "../repositories/team/teamModelToResource.js"; - +import { PersonRepository } from "#repositories/person/PersonRepository.js"; +import { personModelToResource } from "#repositories/person/personModelToResource.js"; +import { PointEntryRepository } from "#repositories/pointEntry/PointEntryRepository.js"; +import { pointEntryModelToResource } from "#repositories/pointEntry/pointEntryModelToResource.js"; +import { pointOpportunityModelToResource } from "#repositories/pointOpportunity/pointOpportunityModelToResource.js"; +import { teamModelToResource } from "#repositories/team/teamModelToResource.js"; import { AbstractGraphQLCreatedResponse, AbstractGraphQLOkResponse, AbstractGraphQLPaginatedResponse, -} from "./ApiResponse.js"; +} from "#resolvers/ApiResponse.js"; @ObjectType("GetPointEntryByUuidResponse", { - implements: AbstractGraphQLOkResponse, + implements: AbstractGraphQLOkResponse, }) -class GetPointEntryByUuidResponse extends AbstractGraphQLOkResponse { - @Field(() => PointEntryResource) - data!: PointEntryResource; +class GetPointEntryByUuidResponse extends AbstractGraphQLOkResponse { + @Field(() => PointEntryNode) + data!: PointEntryNode; } @ObjectType("ListPointEntriesResponse", { - implements: AbstractGraphQLPaginatedResponse, + implements: AbstractGraphQLPaginatedResponse, }) -class ListPointEntriesResponse extends AbstractGraphQLPaginatedResponse { - @Field(() => [PointEntryResource]) - data!: PointEntryResource[]; +class ListPointEntriesResponse extends AbstractGraphQLPaginatedResponse { + @Field(() => [PointEntryNode]) + data!: PointEntryNode[]; } @ObjectType("CreatePointEntryResponse", { - implements: AbstractGraphQLCreatedResponse, + implements: AbstractGraphQLCreatedResponse, }) -class CreatePointEntryResponse extends AbstractGraphQLCreatedResponse { - @Field(() => PointEntryResource) - data!: PointEntryResource; +class CreatePointEntryResponse extends AbstractGraphQLCreatedResponse { + @Field(() => PointEntryNode) + data!: PointEntryNode; } @ObjectType("DeletePointEntryResponse", { implements: AbstractGraphQLOkResponse, @@ -63,7 +67,7 @@ class CreatePointEntryResponse extends AbstractGraphQLCreatedResponse {} @InputType() -class CreatePointEntryInput implements Partial { +class CreatePointEntryInput implements Partial { @Field(() => String, { nullable: true }) comment!: string | null; @@ -93,17 +97,20 @@ class ListPointEntriesArgs extends FilteredListQueryArgs< date: ["createdAt", "updatedAt"], }) {} -@Resolver(() => PointEntryResource) +@Resolver(() => PointEntryNode) @Service() export class PointEntryResolver { - constructor(private readonly pointEntryRepository: PointEntryRepository) {} + constructor( + private readonly pointEntryRepository: PointEntryRepository, + private readonly personRepository: PersonRepository + ) {} @Query(() => GetPointEntryByUuidResponse, { name: "pointEntry" }) async getByUuid( - @Arg("uuid") uuid: string + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId ): Promise { const model = await this.pointEntryRepository.findPointEntryByUnique({ - uuid, + uuid: id, }); if (model == null) { @@ -123,7 +130,7 @@ export class PointEntryResolver { order: query.sortBy?.map((key, i) => [ key, - query.sortDirection?.[i] ?? SortDirection.DESCENDING, + query.sortDirection?.[i] ?? SortDirection.desc, ]) ?? [], skip: query.page != null && query.pageSize != null @@ -164,27 +171,31 @@ export class PointEntryResolver { } @Mutation(() => DeletePointEntryResponse, { name: "deletePointEntry" }) - async delete(@Arg("uuid") id: string): Promise { + async delete( + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId + ): Promise { await this.pointEntryRepository.deletePointEntry({ uuid: id }); return DeletePointEntryResponse.newOk(true); } - @FieldResolver(() => PersonResource, { nullable: true }) + @FieldResolver(() => PersonNode, { nullable: true }) async personFrom( - @Root() pointEntry: PointEntryResource - ): Promise { + @Root() { id: { id } }: PointEntryNode + ): Promise> { const model = await this.pointEntryRepository.getPointEntryPersonFrom({ - uuid: pointEntry.uuid, + uuid: id, }); - return model ? personModelToResource(model) : null; + return model + ? personModelToResource(model, this.personRepository).promise + : Err(new NotFoundError({ what: "Person" })); } - @FieldResolver(() => TeamResource) - async team(@Root() pointEntry: PointEntryResource): Promise { + @FieldResolver(() => TeamNode) + async team(@Root() { id: { id } }: PointEntryNode): Promise { const model = await this.pointEntryRepository.getPointEntryTeam({ - uuid: pointEntry.uuid, + uuid: id, }); if (model == null) { @@ -194,12 +205,12 @@ export class PointEntryResolver { return teamModelToResource(model); } - @FieldResolver(() => PointOpportunityResource, { nullable: true }) + @FieldResolver(() => PointOpportunityNode, { nullable: true }) async pointOpportunity( - @Root() pointEntry: PointEntryResource - ): Promise { + @Root() { id: { id } }: PointEntryNode + ): Promise { const model = await this.pointEntryRepository.getPointEntryOpportunity({ - uuid: pointEntry.uuid, + uuid: id, }); return model ? pointOpportunityModelToResource(model) : null; diff --git a/packages/server/src/resolvers/PointOpportunityResolver.ts b/packages/server/src/resolvers/PointOpportunityResolver.ts index 62aa91c0..530ddced 100644 --- a/packages/server/src/resolvers/PointOpportunityResolver.ts +++ b/packages/server/src/resolvers/PointOpportunityResolver.ts @@ -1,14 +1,15 @@ +import type { GlobalId } from "@ukdanceblue/common"; import { - DateTimeScalar, DetailedError, ErrorCode, - EventResource, + EventNode, FilteredListQueryArgs, - PointOpportunityResource, + GlobalIdScalar, + PointOpportunityNode, SortDirection, TeamType, } from "@ukdanceblue/common"; -import type { DateTime } from "luxon"; +import { DateTimeISOResolver } from "graphql-scalars"; import { Arg, Args, @@ -25,36 +26,35 @@ import { } from "type-graphql"; import { Service } from "typedi"; -import { eventModelToResource } from "../repositories/event/eventModelToResource.js"; -import { PointOpportunityRepository } from "../repositories/pointOpportunity/PointOpportunityRepository.js"; -import { pointOpportunityModelToResource } from "../repositories/pointOpportunity/pointOpportunityModelToResource.js"; - +import { eventModelToResource } from "#repositories/event/eventModelToResource.js"; +import { PointOpportunityRepository } from "#repositories/pointOpportunity/PointOpportunityRepository.js"; +import { pointOpportunityModelToResource } from "#repositories/pointOpportunity/pointOpportunityModelToResource.js"; import { AbstractGraphQLCreatedResponse, AbstractGraphQLOkResponse, AbstractGraphQLPaginatedResponse, -} from "./ApiResponse.js"; +} from "#resolvers/ApiResponse.js"; @ObjectType("SinglePointOpportunityResponse", { - implements: AbstractGraphQLOkResponse, + implements: AbstractGraphQLOkResponse, }) -class SinglePointOpportunityResponse extends AbstractGraphQLOkResponse { - @Field(() => PointOpportunityResource) - data!: PointOpportunityResource; +class SinglePointOpportunityResponse extends AbstractGraphQLOkResponse { + @Field(() => PointOpportunityNode) + data!: PointOpportunityNode; } @ObjectType("ListPointOpportunitiesResponse", { - implements: AbstractGraphQLPaginatedResponse, + implements: AbstractGraphQLPaginatedResponse, }) -class ListPointOpportunitiesResponse extends AbstractGraphQLPaginatedResponse { - @Field(() => [PointOpportunityResource]) - data!: PointOpportunityResource[]; +class ListPointOpportunitiesResponse extends AbstractGraphQLPaginatedResponse { + @Field(() => [PointOpportunityNode]) + data!: PointOpportunityNode[]; } @ObjectType("CreatePointOpportunityResponse", { - implements: AbstractGraphQLCreatedResponse, + implements: AbstractGraphQLCreatedResponse, }) -class CreatePointOpportunityResponse extends AbstractGraphQLCreatedResponse { - @Field(() => PointOpportunityResource) - data!: PointOpportunityResource; +class CreatePointOpportunityResponse extends AbstractGraphQLCreatedResponse { + @Field(() => PointOpportunityNode) + data!: PointOpportunityNode; } @ObjectType("DeletePointOpportunityResponse", { implements: AbstractGraphQLOkResponse, @@ -66,8 +66,8 @@ class CreatePointOpportunityInput { @Field(() => String) name!: string; - @Field(() => DateTimeScalar, { nullable: true }) - opportunityDate!: DateTime | null; + @Field(() => DateTimeISOResolver, { nullable: true }) + opportunityDate!: Date | null; @Field(() => TeamType) type!: TeamType; @@ -81,8 +81,8 @@ class SetPointOpportunityInput { @Field(() => String, { nullable: true }) name!: string | null; - @Field(() => DateTimeScalar, { nullable: true }) - opportunityDate!: DateTime | null; + @Field(() => DateTimeISOResolver, { nullable: true }) + opportunityDate!: Date | null; @Field(() => TeamType, { nullable: true }) type!: TeamType | null; @@ -106,7 +106,7 @@ class ListPointOpportunitiesArgs extends FilteredListQueryArgs< date: ["opportunityDate", "createdAt", "updatedAt"], }) {} -@Resolver(() => PointOpportunityResource) +@Resolver(() => PointOpportunityNode) @Service() export class PointOpportunityResolver { constructor( @@ -115,11 +115,11 @@ export class PointOpportunityResolver { @Query(() => SinglePointOpportunityResponse, { name: "pointOpportunity" }) async getByUuid( - @Arg("uuid") uuid: string + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId ): Promise { const row = await this.pointOpportunityRepository.findPointOpportunityByUnique({ - uuid, + uuid: id, }); if (row == null) { @@ -141,7 +141,7 @@ export class PointOpportunityResolver { order: query.sortBy?.map((key, i) => [ key, - query.sortDirection?.[i] ?? SortDirection.DESCENDING, + query.sortDirection?.[i] ?? SortDirection.desc, ]) ?? [], skip: query.page != null && query.pageSize != null @@ -172,7 +172,7 @@ export class PointOpportunityResolver { name: input.name, type: input.type, eventParam: input.eventUuid ? { uuid: input.eventUuid } : null, - opportunityDate: input.opportunityDate?.toJSDate() ?? null, + opportunityDate: input.opportunityDate ?? null, }); return CreatePointOpportunityResponse.newOk( @@ -184,16 +184,16 @@ export class PointOpportunityResolver { name: "setPointOpportunity", }) async set( - @Arg("uuid") uuid: string, + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId, @Arg("input") input: SetPointOpportunityInput ): Promise { const row = await this.pointOpportunityRepository.updatePointOpportunity( - { uuid }, + { uuid: id }, { name: input.name ?? undefined, type: input.type ?? undefined, eventParam: input.eventUuid ? { uuid: input.eventUuid } : undefined, - opportunityDate: input.opportunityDate?.toJSDate() ?? undefined, + opportunityDate: input.opportunityDate ?? undefined, } ); @@ -210,7 +210,7 @@ export class PointOpportunityResolver { name: "deletePointOpportunity", }) async delete( - @Arg("uuid") id: string + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId ): Promise { const row = await this.pointOpportunityRepository.deletePointOpportunity({ uuid: id, @@ -223,13 +223,13 @@ export class PointOpportunityResolver { return DeletePointOpportunityResponse.newOk(true); } - @FieldResolver(() => EventResource, { nullable: true }) + @FieldResolver(() => EventNode, { nullable: true }) async event( - @Root() pointOpportunity: PointOpportunityResource - ): Promise { + @Root() { id: { id } }: PointOpportunityNode + ): Promise { const model = await this.pointOpportunityRepository.getEventForPointOpportunity({ - uuid: pointOpportunity.uuid, + uuid: id, }); return model ? eventModelToResource(model) : null; diff --git a/packages/server/src/resolvers/TeamResolver.ts b/packages/server/src/resolvers/TeamResolver.ts index 96ec229d..14562cbb 100644 --- a/packages/server/src/resolvers/TeamResolver.ts +++ b/packages/server/src/resolvers/TeamResolver.ts @@ -1,7 +1,4 @@ -import type { - MarathonYearString, - OptionalToNullable, -} from "@ukdanceblue/common"; +import type { GlobalId, OptionalToNullable } from "@ukdanceblue/common"; import * as Common from "@ukdanceblue/common"; import { AccessControl, @@ -12,13 +9,15 @@ import { DetailedError, ErrorCode, FilteredListQueryArgs, - MembershipResource, - PointEntryResource, + GlobalIdScalar, + MembershipNode, + PointEntryNode, SortDirection, TeamLegacyStatus, - TeamResource, + TeamNode, TeamType, } from "@ukdanceblue/common"; +import { VoidResolver } from "graphql-scalars"; import { Arg, Args, @@ -36,38 +35,47 @@ import { } from "type-graphql"; import { Service } from "typedi"; -import { membershipModelToResource } from "../repositories/membership/membershipModelToResource.js"; -import { pointEntryModelToResource } from "../repositories/pointEntry/pointEntryModelToResource.js"; -import { TeamRepository } from "../repositories/team/TeamRepository.js"; -import { teamModelToResource } from "../repositories/team/teamModelToResource.js"; - +import { CatchableConcreteError } from "#lib/formatError.js"; +import { DBFundsRepository } from "#repositories/fundraising/DBFundsRepository.js"; +import { FundraisingEntryRepository } from "#repositories/fundraising/FundraisingRepository.js"; +import { fundraisingEntryModelToNode } from "#repositories/fundraising/fundraisingEntryModelToNode.js"; +import { marathonModelToResource } from "#repositories/marathon/marathonModelToResource.js"; +import { membershipModelToResource } from "#repositories/membership/membershipModelToResource.js"; +import { pointEntryModelToResource } from "#repositories/pointEntry/pointEntryModelToResource.js"; +import { TeamRepository } from "#repositories/team/TeamRepository.js"; +import { teamModelToResource } from "#repositories/team/teamModelToResource.js"; import { AbstractGraphQLCreatedResponse, AbstractGraphQLOkResponse, AbstractGraphQLPaginatedResponse, -} from "./ApiResponse.js"; -import * as Context from "./context.js"; +} from "#resolvers/ApiResponse.js"; +import { + ListFundraisingEntriesArgs, + ListFundraisingEntriesResponse, + globalFundraisingAccessParam, +} from "#resolvers/FundraisingEntryResolver.js"; +import * as Context from "#resolvers/context.js"; @ObjectType("SingleTeamResponse", { - implements: AbstractGraphQLOkResponse, + implements: AbstractGraphQLOkResponse, }) -class SingleTeamResponse extends AbstractGraphQLOkResponse { - @Field(() => TeamResource) - data!: TeamResource; +class SingleTeamResponse extends AbstractGraphQLOkResponse { + @Field(() => TeamNode) + data!: TeamNode; } @ObjectType("ListTeamsResponse", { - implements: AbstractGraphQLPaginatedResponse, + implements: AbstractGraphQLPaginatedResponse, }) -class ListTeamsResponse extends AbstractGraphQLPaginatedResponse { - @Field(() => [TeamResource]) - data!: TeamResource[]; +class ListTeamsResponse extends AbstractGraphQLPaginatedResponse { + @Field(() => [TeamNode]) + data!: TeamNode[]; } @ObjectType("CreateTeamResponse", { - implements: AbstractGraphQLCreatedResponse, + implements: AbstractGraphQLCreatedResponse, }) -class CreateTeamResponse extends AbstractGraphQLCreatedResponse { - @Field(() => TeamResource) - data!: TeamResource; +class CreateTeamResponse extends AbstractGraphQLCreatedResponse { + @Field(() => TeamNode) + data!: TeamNode; } @ObjectType("DeleteTeamResponse", { implements: AbstractGraphQLOkResponse, @@ -75,7 +83,7 @@ class CreateTeamResponse extends AbstractGraphQLCreatedResponse { class DeleteTeamResponse extends AbstractGraphQLOkResponse {} @InputType() -class CreateTeamInput implements OptionalToNullable> { +class CreateTeamInput implements OptionalToNullable> { @Field(() => String) name!: string; @@ -84,16 +92,10 @@ class CreateTeamInput implements OptionalToNullable> { @Field(() => TeamLegacyStatus) legacyStatus!: TeamLegacyStatus; - - @Field(() => String) - marathonYear!: Common.MarathonYearString; - - @Field(() => String, { nullable: true }) - persistentIdentifier!: string | null; } @InputType() -class SetTeamInput implements OptionalToNullable> { +class SetTeamInput implements OptionalToNullable> { @Field(() => String, { nullable: true }) name!: string | null; @@ -103,26 +105,23 @@ class SetTeamInput implements OptionalToNullable> { @Field(() => TeamLegacyStatus, { nullable: true }) legacyStatus!: TeamLegacyStatus | null; - @Field(() => String, { nullable: true }) - marathonYear!: MarathonYearString | null; - @Field(() => String, { nullable: true }) persistentIdentifier!: string | null; } @ArgsType() class ListTeamsArgs extends FilteredListQueryArgs< - "name" | "type" | "legacyStatus" | "marathonYear", + "name" | "type" | "legacyStatus" | "marathonId", "name", - "type" | "legacyStatus" | "marathonYear", + "type" | "legacyStatus" | "marathonId", never, never, never >("TeamResolver", { - all: ["name", "type", "legacyStatus", "marathonYear"], + all: ["name", "type", "legacyStatus", "marathonId"], string: ["name"], numeric: [], - oneOf: ["type", "marathonYear", "legacyStatus"], + oneOf: ["type", "marathonId", "legacyStatus"], }) { @Field(() => [TeamType], { nullable: true }) type!: [TeamType] | null; @@ -134,18 +133,33 @@ class ListTeamsArgs extends FilteredListQueryArgs< visibility!: [DbRole] | null; @Field(() => [String], { nullable: true }) - marathonYear!: [MarathonYearString] | null; + marathonId!: string[] | null; +} + +@ObjectType("DbFundsTeamInfo", { implements: [Common.Node] }) +class DbFundsTeamInfo { + @Field(() => Int) + dbNum!: number; + + @Field(() => String) + name!: string; } -@Resolver(() => TeamResource) +@Resolver(() => TeamNode) @Service() export class TeamResolver { - constructor(private teamRepository: TeamRepository) {} + constructor( + private teamRepository: TeamRepository, + private fundraisingEntryRepository: FundraisingEntryRepository, + private dbFundsRepository: DBFundsRepository + ) {} @AccessControl({ accessLevel: AccessLevel.Committee }) @Query(() => SingleTeamResponse, { name: "team" }) - async getByUuid(@Arg("uuid") uuid: string): Promise { - const row = await this.teamRepository.findTeamByUnique({ uuid }); + async getByUuid( + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId + ): Promise { + const row = await this.teamRepository.findTeamByUnique({ uuid: id }); if (row == null) { throw new DetailedError(ErrorCode.NotFound, "Team not found"); @@ -166,7 +180,7 @@ export class TeamResolver { order: query.sortBy?.map((key, i) => [ key, - query.sortDirection?.[i] ?? SortDirection.DESCENDING, + query.sortDirection?.[i] ?? SortDirection.desc, ]) ?? [], skip: query.page != null && query.pageSize != null @@ -175,14 +189,14 @@ export class TeamResolver { take: query.pageSize, onlyDemo: ctx.userData.authSource === AuthSource.Demo, legacyStatus: query.legacyStatus, - marathonYear: query.marathonYear, + marathon: query.marathonId?.map((marathonId) => ({ uuid: marathonId })), type: query.type, }), this.teamRepository.countTeams({ filters: query.filters, onlyDemo: ctx.userData.authSource === AuthSource.Demo, legacyStatus: query.legacyStatus, - marathonYear: query.marathonYear, + marathon: query.marathonId?.map((marathonId) => ({ uuid: marathonId })), type: query.type, }), ]); @@ -190,12 +204,9 @@ export class TeamResolver { return ListTeamsResponse.newPaginated({ data: rows.map((row) => teamModelToResource({ - id: row.id, uuid: row.uuid, name: row.name, - persistentIdentifier: row.persistentIdentifier, legacyStatus: row.legacyStatus, - marathonYear: row.marathonYear, type: row.type, updatedAt: row.updatedAt, createdAt: row.createdAt, @@ -223,15 +234,17 @@ export class TeamResolver { ) @Mutation(() => CreateTeamResponse, { name: "createTeam" }) async create( - @Arg("input") input: CreateTeamInput + @Arg("input") input: CreateTeamInput, + @Arg("marathon") marathonUuid: string ): Promise { - const row = await this.teamRepository.createTeam({ - name: input.name, - type: input.type, - legacyStatus: input.legacyStatus, - marathonYear: input.marathonYear, - persistentIdentifier: input.persistentIdentifier, - }); + const row = await this.teamRepository.createTeam( + { + name: input.name, + type: input.type, + legacyStatus: input.legacyStatus, + }, + { uuid: marathonUuid } + ); return CreateTeamResponse.newCreated(teamModelToResource(row), row.uuid); } @@ -252,17 +265,16 @@ export class TeamResolver { ) @Mutation(() => SingleTeamResponse, { name: "setTeam" }) async set( - @Arg("uuid") uuid: string, + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId, @Arg("input") input: SetTeamInput ): Promise { const row = await this.teamRepository.updateTeam( - { uuid }, + { uuid: id }, { - uuid, + uuid: id, name: input.name ?? undefined, type: input.type ?? undefined, legacyStatus: input.legacyStatus ?? undefined, - marathonYear: input.marathonYear ?? undefined, persistentIdentifier: input.persistentIdentifier, } ); @@ -289,8 +301,10 @@ export class TeamResolver { } ) @Mutation(() => DeleteTeamResponse, { name: "deleteTeam" }) - async delete(@Arg("uuid") uuid: string): Promise { - const row = await this.teamRepository.deleteTeam({ uuid }); + async delete( + @Arg("uuid", () => GlobalIdScalar) { id }: GlobalId + ): Promise { + const row = await this.teamRepository.deleteTeam({ uuid: id }); if (row == null) { throw new DetailedError(ErrorCode.NotFound, "Team not found"); @@ -304,28 +318,29 @@ export class TeamResolver { { rootMatch: [ { - root: "uuid", - extractor: ({ teamIds }) => teamIds, + root: "id", + extractor: ({ teamMemberships }) => + teamMemberships.map(({ teamId }) => teamId), }, ], } ) - @FieldResolver(() => [MembershipResource]) - async members(@Root() team: TeamResource): Promise { + @FieldResolver(() => [MembershipNode]) + async members(@Root() { id: { id } }: TeamNode): Promise { const memberships = await this.teamRepository.findMembersOfTeam({ - uuid: team.uuid, + uuid: id, }); return memberships.map((row) => membershipModelToResource(row)); } @AccessControl({ accessLevel: AccessLevel.Committee }) - @FieldResolver(() => [MembershipResource], { + @FieldResolver(() => [MembershipNode], { deprecationReason: "Just query the members field and filter by role", }) - async captains(@Root() team: TeamResource): Promise { + async captains(@Root() { id: { id } }: TeamNode): Promise { const memberships = await this.teamRepository.findMembersOfTeam( - { uuid: team.uuid }, + { uuid: id }, { captainsOnly: true } ); @@ -337,18 +352,19 @@ export class TeamResolver { { rootMatch: [ { - root: "uuid", - extractor: ({ teamIds }) => teamIds, + root: "id", + extractor: ({ teamMemberships }) => + teamMemberships.map(({ teamId }) => teamId), }, ], } ) - @FieldResolver(() => [PointEntryResource]) + @FieldResolver(() => [PointEntryNode]) async pointEntries( - @Root() team: TeamResource - ): Promise { + @Root() { id: { id } }: TeamNode + ): Promise { const rows = await this.teamRepository.getTeamPointEntries({ - uuid: team.uuid, + uuid: id, }); return rows.map((row) => pointEntryModelToResource(row)); @@ -356,11 +372,134 @@ export class TeamResolver { @AccessControl({ accessLevel: AccessLevel.Public }) @FieldResolver(() => Int) - async totalPoints(@Root() team: TeamResource): Promise { + async totalPoints(@Root() { id: { id } }: TeamNode): Promise { const result = await this.teamRepository.getTotalTeamPoints({ - uuid: team.uuid, + uuid: id, }); return result._sum.points ?? 0; } + + @AccessControl({ accessLevel: AccessLevel.Public }) + @FieldResolver(() => Common.MarathonNode) + async marathon( + @Root() { id: { id } }: TeamNode + ): Promise { + const result = await this.teamRepository.getMarathon({ uuid: id }); + + if (result == null) { + throw new DetailedError(ErrorCode.NotFound, "Team not found"); + } + + return marathonModelToResource(result); + } + + @AccessControl(globalFundraisingAccessParam, { + rootMatch: [ + { + root: "id", + extractor: ({ teamMemberships }) => + teamMemberships + .filter( + ({ position }) => + position === Common.MembershipPositionType.Captain + ) + .map(({ teamId }) => teamId), + }, + ], + }) + @FieldResolver(() => ListFundraisingEntriesResponse) + async fundraisingEntries( + @Root() { id: { id } }: TeamNode, + @Args(() => ListFundraisingEntriesArgs) args: ListFundraisingEntriesArgs + ): Promise { + const entries = await this.fundraisingEntryRepository.listEntries( + { + filters: args.filters, + order: + args.sortBy?.map((key, i) => [ + key, + args.sortDirection?.[i] ?? SortDirection.desc, + ]) ?? [], + skip: + args.page != null && args.pageSize != null + ? (args.page - 1) * args.pageSize + : null, + take: args.pageSize, + }, + { + // EXTREMELY IMPORTANT FOR SECURITY + forTeam: { uuid: id }, + } + ); + const count = await this.fundraisingEntryRepository.countEntries({ + filters: args.filters, + }); + + if (entries.isErr()) { + throw new CatchableConcreteError(entries.error); + } + if (count.isErr()) { + throw new CatchableConcreteError(count.error); + } + + return ListFundraisingEntriesResponse.newPaginated({ + data: await Promise.all( + entries.value.map((model) => fundraisingEntryModelToNode(model)) + ), + total: count.value, + page: args.page, + pageSize: args.pageSize, + }); + } + + @AccessControl(globalFundraisingAccessParam) + @Query(() => [DbFundsTeamInfo], { name: "dbFundsTeams" }) + async dbFundsTeams( + @Arg("search") search: string + ): Promise { + const searchParam: { + byDbNum?: number; + byName?: string; + } = {}; + const searchAsNum = Number.parseInt(search, 10); + if (Number.isInteger(searchAsNum)) { + searchParam.byDbNum = searchAsNum; + } else { + searchParam.byName = search; + } + const rows = await this.dbFundsRepository.listDbFundsTeams({ + ...searchParam, + onlyActive: true, + }); + + if (rows.isErr()) { + throw new CatchableConcreteError(rows.error); + } + + return rows.value.map((row) => { + const teamInfoInstance = new DbFundsTeamInfo(); + teamInfoInstance.dbNum = row.dbNum; + teamInfoInstance.name = row.name; + return teamInfoInstance; + }); + } + + @AccessControl(globalFundraisingAccessParam) + @Mutation(() => VoidResolver, { name: "assignTeamToDbFundsTeam" }) + async assignTeamToDbFundsTeam( + @Arg("teamId") teamId: string, + @Arg("dbFundsTeamId") dbFundsTeamId: number + ): Promise { + const result = await this.dbFundsRepository.assignTeamToDbFundsTeam( + { uuid: teamId }, + { dbNum: dbFundsTeamId } + ); + + if (result.isErr()) { + throw new CatchableConcreteError(result.error); + } + + return undefined; + } } diff --git a/packages/server/src/resolvers/context.ts b/packages/server/src/resolvers/context.ts index 20f2d891..2eaaf86d 100644 --- a/packages/server/src/resolvers/context.ts +++ b/packages/server/src/resolvers/context.ts @@ -1,23 +1,150 @@ import type { ContextFunction } from "@apollo/server"; import type { KoaContextFunctionArgument } from "@as-integrations/koa"; -import type { AuthorizationContext } from "@ukdanceblue/common"; -import { AuthSource, MembershipPositionType } from "@ukdanceblue/common"; +import type { + AuthorizationContext, + EffectiveCommitteeRole, +} from "@ukdanceblue/common"; +import { + AccessLevel, + AuthSource, + CommitteeIdentifier, + CommitteeRole, + DbRole, + roleToAccessLevel, +} from "@ukdanceblue/common"; +import { NotFoundError } from "@ukdanceblue/common/error"; +import type { ConcreteResult } from "@ukdanceblue/common/error"; import type { DefaultState } from "koa"; +import { Ok } from "ts-results-es"; import { Container } from "typedi"; -import { defaultAuthorization, parseUserJwt } from "../lib/auth/index.js"; -import { logger } from "../lib/logging/logger.js"; -import { PersonRepository } from "../repositories/person/PersonRepository.js"; -import { personModelToResource } from "../repositories/person/personModelToResource.js"; +import { defaultAuthorization, parseUserJwt } from "#auth/index.js"; +import { logger } from "#logging/logger.js"; +import { PersonRepository } from "#repositories/person/PersonRepository.js"; +import { personModelToResource } from "#repositories/person/personModelToResource.js"; export interface GraphQLContext extends AuthorizationContext { contextErrors: string[]; } +function isSuperAdmin(committeeRoles: EffectiveCommitteeRole[]): boolean { + return committeeRoles.some( + (role) => + // TODO: replace "=== Coordinator" with a check for just app&web, but that requires information about what kind of coordinator someone is + role.identifier === CommitteeIdentifier.techCommittee && + (role.role === CommitteeRole.Chair || + role.role === CommitteeRole.Coordinator) + ); +} + +async function withUserInfo( + inputContext: GraphQLContext, + userId: string +): Promise> { + const outputContext = structuredClone(inputContext); + const personRepository = Container.get(PersonRepository); + const person = await personRepository.findPersonAndTeamsByUnique({ + uuid: userId, + }); + + if (person.isErr()) { + if (person.error.tag === NotFoundError.Tag) { + // Short-circuit if the user is not found + return Ok(outputContext); + } + return person; + } + + // If we found a user, set the authenticated user + // Convert the user to a resource and set it on the context + const personResource = await personModelToResource( + person.value, + personRepository + ).promise; + if (personResource.isErr()) { + return personResource; + } + logger.trace("graphqlContextFunction Found user", personResource.value); + outputContext.authenticatedUser = personResource.value; + outputContext.userData.userId = userId; + + // Set the committees the user is on + const committeeRoles = + await personRepository.getEffectiveCommitteeRolesOfPerson({ + id: person.value.id, + }); + if (committeeRoles.isErr()) { + return committeeRoles; + } + logger.trace( + "graphqlContextFunction Found committees", + ...committeeRoles.value + ); + outputContext.authorization.committees = committeeRoles.value; + + // Set the teams the user is on + let teamMemberships = await personRepository.findMembershipsOfPerson( + { + id: person.value.id, + }, + {}, + undefined, + true + ); + if (teamMemberships.isErr()) { + if (teamMemberships.error.tag === NotFoundError.Tag) { + teamMemberships = Ok([]); + } else { + return teamMemberships; + } + } + logger.trace( + "graphqlContextFunction Found team memberships", + ...teamMemberships.value + ); + outputContext.teamMemberships = teamMemberships.value.map((membership) => ({ + teamType: membership.team.type, + position: membership.position, + teamId: membership.team.uuid, + })); + + // Set the effective committee roles the user has + const effectiveCommitteeRoles = + await personRepository.getEffectiveCommitteeRolesOfPerson({ + id: person.value.id, + }); + if (effectiveCommitteeRoles.isErr()) { + return effectiveCommitteeRoles; + } + logger.trace( + "graphqlContextFunction Effective committee roles", + ...effectiveCommitteeRoles.value + ); + outputContext.authorization.committees = effectiveCommitteeRoles.value; + + // If the user is on a committee, override the dbRole + if (effectiveCommitteeRoles.value.length > 0) { + outputContext.authorization.dbRole = DbRole.Committee; + } + + return Ok(outputContext); +} + +const defaultContext: Readonly = Object.freeze({ + authenticatedUser: null, + teamMemberships: [], + userData: { + authSource: AuthSource.None, + }, + authorization: defaultAuthorization, + contextErrors: [], +}); + export const graphqlContextFunction: ContextFunction< [KoaContextFunctionArgument], GraphQLContext > = async ({ ctx }): Promise => { + // Get the token from the cookies or the Authorization header let token = ctx.cookies.get("token"); if (!token) { const authorizationHeader = ctx.get("Authorization"); @@ -26,59 +153,84 @@ export const graphqlContextFunction: ContextFunction< } } if (!token) { - return { - authenticatedUser: null, - userData: { - auth: defaultAuthorization, - authSource: AuthSource.None, - }, - contextErrors: [], - }; + // Short-circuit if no token is present + return defaultContext; } - const { userId, auth, authSource } = parseUserJwt(token); - if (!userId) { - logger.trace("graphqlContextFunction No userId found"); - return { - authenticatedUser: null, - userData: { - auth, - authSource, - }, - contextErrors: [], - }; + + // Parse the token + const { userId, authSource } = parseUserJwt(token); + + // Set the dbRole based on the auth source + let authSourceDbRole: DbRole; + if (authSource === AuthSource.LinkBlue || authSource === AuthSource.Demo) { + authSourceDbRole = DbRole.UKY; + } else if (authSource === AuthSource.Anonymous) { + authSourceDbRole = DbRole.Public; + } else { + authSourceDbRole = DbRole.None; } - const personRepository = Container.get(PersonRepository); - const person = await personRepository.findPersonAndTeamsByUnique({ - uuid: userId, - }); + if (!userId) { + logger.trace("graphqlContextFunction No user ID"); + return structuredClone(defaultContext); + } - if (person) { - const personResource = personModelToResource(person); - - logger.trace("graphqlContextFunction Found user", personResource); - return { - authenticatedUser: personResource, - userData: { - auth: personResource.role.toAuthorization(), - userId, - teamIds: person.memberships.map((m) => m.team.uuid), - captainOfTeamIds: person.memberships - .filter((m) => m.position === MembershipPositionType.Captain) - .map((m) => m.team.uuid), - authSource, + // If we have a user ID, look up the user + let contextWithUser = await withUserInfo( + { + ...defaultContext, + authorization: { + ...defaultContext.authorization, + dbRole: authSourceDbRole, }, - contextErrors: [], - }; - } else { - logger.trace("graphqlContextFunction User not found"); - return { - authenticatedUser: null, - userData: { - auth: defaultAuthorization, - authSource: AuthSource.None, + }, + userId + ); + if (contextWithUser.isErr()) { + logger.error( + "graphqlContextFunction Error looking up user", + contextWithUser.error + ); + return structuredClone(defaultContext); + } + let superAdmin = isSuperAdmin(contextWithUser.value.authorization.committees); + if ( + superAdmin && + ctx.request.headers["x-ukdb-masquerade"] && + typeof ctx.request.headers["x-ukdb-masquerade"] === "string" + ) { + // We need to reset the dbRole to the default one in case the masquerade user is not a committee member + contextWithUser = await withUserInfo( + { + ...defaultContext, + authorization: { + ...defaultContext.authorization, + dbRole: authSourceDbRole, + }, }, - contextErrors: ["User not found"], - }; + ctx.request.headers["x-ukdb-masquerade"] + ); + if (contextWithUser.isErr()) { + logger.error( + "graphqlContextFunction Error looking up user", + contextWithUser.error + ); + return structuredClone(defaultContext); + } + superAdmin = false; } + + const finalContext: GraphQLContext = { + ...contextWithUser.value, + authorization: { + ...contextWithUser.value.authorization, + accessLevel: superAdmin + ? AccessLevel.SuperAdmin + : roleToAccessLevel(contextWithUser.value.authorization), + }, + }; + + logger.trace("graphqlContextFunction Context", finalContext); + + return finalContext; }; diff --git a/packages/server/src/resolvers/index.ts b/packages/server/src/resolvers/index.ts index 603b0a5e..50c60b6b 100644 --- a/packages/server/src/resolvers/index.ts +++ b/packages/server/src/resolvers/index.ts @@ -1,2 +1,2 @@ -export { ConfigurationResolver } from "./ConfigurationResolver.js"; -export { ImageResolver } from "./ImageResolver.js"; +export { ConfigurationResolver } from "#resolvers/ConfigurationResolver.js"; +export { ImageResolver } from "#resolvers/ImageResolver.js"; diff --git a/packages/server/src/routes/api/auth/anonymous.ts b/packages/server/src/routes/api/auth/anonymous.ts index 84cd02ec..2c5acc10 100644 --- a/packages/server/src/routes/api/auth/anonymous.ts +++ b/packages/server/src/routes/api/auth/anonymous.ts @@ -1,8 +1,8 @@ -import { AccessLevel, AuthSource, DbRole } from "@ukdanceblue/common"; +import { AuthSource } from "@ukdanceblue/common"; import type { Context } from "koa"; import { DateTime } from "luxon"; -import { makeUserJwt } from "../../../lib/auth/index.js"; +import { makeUserJwt } from "#auth/index.js"; export const anonymousLogin = (ctx: Context) => { let redirectTo = "/"; @@ -27,10 +27,6 @@ export const anonymousLogin = (ctx: Context) => { } const jwt = makeUserJwt({ - auth: { - accessLevel: AccessLevel.Public, - dbRole: DbRole.Public, - }, authSource: AuthSource.Anonymous, }); if (setCookie) { diff --git a/packages/server/src/routes/api/auth/demo.ts b/packages/server/src/routes/api/auth/demo.ts index c33022e0..dfb157a1 100644 --- a/packages/server/src/routes/api/auth/demo.ts +++ b/packages/server/src/routes/api/auth/demo.ts @@ -1,14 +1,9 @@ -import { - AccessLevel, - AuthSource, - DbRole, - MembershipPositionType, -} from "@ukdanceblue/common"; +import { AuthSource } from "@ukdanceblue/common"; import type { Context } from "koa"; import { DateTime } from "luxon"; -import { makeUserJwt } from "../../../lib/auth/index.js"; -import { getOrMakeDemoUser } from "../../../lib/demo.js"; +import { makeUserJwt } from "#auth/index.js"; +import { getOrMakeDemoUser } from "#lib/demo.js"; export const demoLogin = async (ctx: Context) => { let redirectTo = "/"; @@ -33,17 +28,15 @@ export const demoLogin = async (ctx: Context) => { } const person = await getOrMakeDemoUser(); + if (person.isErr()) { + return ctx.throw( + person.error.expose ? person.error.message : "Error creating demo user", + 500 + ); + } const jwt = makeUserJwt({ - auth: { - accessLevel: AccessLevel.UKY, - dbRole: DbRole.UKY, - }, - userId: person.uuid, - teamIds: person.memberships.map((m) => m.team.uuid), - captainOfTeamIds: person.memberships - .filter((m) => m.position === MembershipPositionType.Captain) - .map((m) => m.team.uuid), + userId: person.value.uuid, authSource: AuthSource.Demo, }); if (setCookie) { diff --git a/packages/server/src/routes/api/auth/login.ts b/packages/server/src/routes/api/auth/login.ts index 3f44dca6..77c24518 100644 --- a/packages/server/src/routes/api/auth/login.ts +++ b/packages/server/src/routes/api/auth/login.ts @@ -2,9 +2,9 @@ import type { Context } from "koa"; import { generators } from "openid-client"; import { Container } from "typedi"; -import { LoginFlowSessionRepository } from "../../../resolvers/LoginFlowSession.js"; - import { makeOidcClient } from "./oidcClient.js"; +import { LoginFlowSessionRepository } from "#repositories/LoginFlowSession.js"; + // TODO: convert to OAuth2 export const login = async (ctx: Context) => { diff --git a/packages/server/src/routes/api/auth/oidcCallback.ts b/packages/server/src/routes/api/auth/oidcCallback.ts index f956cef1..152e84ed 100644 --- a/packages/server/src/routes/api/auth/oidcCallback.ts +++ b/packages/server/src/routes/api/auth/oidcCallback.ts @@ -1,22 +1,17 @@ import type { IncomingMessage } from "node:http"; -import { - AuthSource, - MembershipPositionType, - makeUserData, -} from "@ukdanceblue/common"; -import createHttpError from "http-errors"; +import { AuthSource, makeUserData } from "@ukdanceblue/common"; import jsonwebtoken from "jsonwebtoken"; import type { Context } from "koa"; import { DateTime } from "luxon"; import { Container } from "typedi"; -import { makeUserJwt } from "../../../lib/auth/index.js"; -import { PersonRepository } from "../../../repositories/person/PersonRepository.js"; -import { personModelToResource } from "../../../repositories/person/personModelToResource.js"; -import { LoginFlowSessionRepository } from "../../../resolvers/LoginFlowSession.js"; - import { makeOidcClient } from "./oidcClient.js"; +import { makeUserJwt } from "#auth/index.js"; +import { LoginFlowSessionRepository } from "#repositories/LoginFlowSession.js"; +import { PersonRepository } from "#repositories/person/PersonRepository.js"; +import { personModelToResource } from "#repositories/person/personModelToResource.js"; + export const oidcCallback = async (ctx: Context) => { const oidcClient = await makeOidcClient(ctx.request); @@ -42,7 +37,7 @@ export const oidcCallback = async (ctx: Context) => { uuid: flowSessionId, }); if (!session?.codeVerifier) { - throw new createHttpError.InternalServerError( + throw new Error( `No ${session == null ? "session" : "codeVerifier"} found` ); } @@ -58,14 +53,14 @@ export const oidcCallback = async (ctx: Context) => { }); sessionDeleted = true; if (!tokenSet.access_token) { - throw new createHttpError.InternalServerError("Missing access token"); + throw new Error("Missing access token"); } const { oid: objectId, email } = tokenSet.claims(); const decodedJwt = jsonwebtoken.decode(tokenSet.access_token, { json: true, }); if (!decodedJwt) { - throw new createHttpError.InternalServerError("Error decoding JWT"); + throw new Error("Error decoding JWT"); } const { given_name: firstName, @@ -82,11 +77,21 @@ export const oidcCallback = async (ctx: Context) => { if (typeof objectId !== "string") { return ctx.throw("Missing OID", 500); } - const [currentPerson] = await personRepository.findPersonForLogin( + const findPersonForLoginResult = await personRepository.findPersonForLogin( [[AuthSource.LinkBlue, objectId]], { email, linkblue } ); + if (findPersonForLoginResult.isErr()) { + return ctx.throw( + findPersonForLoginResult.error.expose + ? findPersonForLoginResult.error.message + : "Error finding person", + 500 + ); + } + const { currentPerson } = findPersonForLoginResult.value; + if ( !currentPerson.authIdPairs.some( ({ source, value }) => @@ -121,8 +126,6 @@ export const oidcCallback = async (ctx: Context) => { name: currentPerson.name, email: currentPerson.email, linkblue: currentPerson.linkblue, - committeeName: currentPerson.committeeName, - committeeRole: currentPerson.committeeRole, authIds: currentPerson.authIdPairs.map((a) => ({ source: a.source, value: a.value, @@ -130,19 +133,24 @@ export const oidcCallback = async (ctx: Context) => { } ); - if (!updatedPerson) { + if (updatedPerson.isErr()) { return ctx.throw("Failed to update database entry", 500); } + const personNode = await personModelToResource( + updatedPerson.value, + personRepository + ).promise; + if (personNode.isErr()) { + return ctx.throw( + personNode.error.expose + ? personNode.error.message + : "Error creating person node", + 500 + ); + } const jwt = makeUserJwt( - makeUserData( - personModelToResource(updatedPerson), - AuthSource.LinkBlue, - currentPerson.memberships.map((m) => m.team.uuid), - currentPerson.memberships - .filter((m) => m.position === MembershipPositionType.Captain) - .map((m) => m.team.uuid) - ) + makeUserData(personNode.value, AuthSource.LinkBlue) ); let redirectTo = session.redirectToAfterLogin; if (session.sendToken) { diff --git a/packages/server/src/routes/api/auth/oidcClient.ts b/packages/server/src/routes/api/auth/oidcClient.ts index d1399e65..d1c6e4fd 100644 --- a/packages/server/src/routes/api/auth/oidcClient.ts +++ b/packages/server/src/routes/api/auth/oidcClient.ts @@ -7,7 +7,7 @@ import { msClientId, msClientSecret, msOidcUrl, -} from "../../../environment.js"; +} from "#environment"; export async function makeOidcClient(req: Request): Promise { const forwardedProto = req.get("x-forwarded-proto"); diff --git a/packages/server/src/routes/api/events/upcomingEvents.ts b/packages/server/src/routes/api/events/upcomingEvents.ts index ac37e8de..9b45ec7f 100644 --- a/packages/server/src/routes/api/events/upcomingEvents.ts +++ b/packages/server/src/routes/api/events/upcomingEvents.ts @@ -3,9 +3,9 @@ import { DateTime } from "luxon"; import type { NextFn } from "type-graphql"; import { Container } from "typedi"; -import { FileManager } from "../../../lib/files/FileManager.js"; -import { combineMimePartsToString } from "../../../lib/files/mime.js"; -import { EventRepository } from "../../../repositories/event/EventRepository.js"; +import { FileManager } from "#files/FileManager.js"; +import { combineMimePartsToString } from "#files/mime.js"; +import { EventRepository } from "#repositories/event/EventRepository.js"; const EMPTY_PNG_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NgAAIAAAUAAR4f7BQAAAAASUVORK5CYII="; diff --git a/packages/server/src/routes/api/file/index.ts b/packages/server/src/routes/api/file/index.ts index a323f0e5..62bef7c2 100644 --- a/packages/server/src/routes/api/file/index.ts +++ b/packages/server/src/routes/api/file/index.ts @@ -1,8 +1,8 @@ import Router from "@koa/router"; import { Container } from "typedi"; -import { FileManager } from "../../../lib/files/FileManager.js"; -import { combineMimePartsToString } from "../../../lib/files/mime.js"; +import { FileManager } from "#files/FileManager.js"; +import { combineMimePartsToString } from "#files/mime.js"; const fileRouter = new Router({ prefix: "/file" }); diff --git a/packages/server/src/routes/api/upload/index.ts b/packages/server/src/routes/api/upload/index.ts index 4c767464..746511ff 100644 --- a/packages/server/src/routes/api/upload/index.ts +++ b/packages/server/src/routes/api/upload/index.ts @@ -5,11 +5,11 @@ import type { File } from "@prisma/client"; import { koaBody } from "koa-body"; import { Container } from "typedi"; -import { maxFileSize } from "../../../environment.js"; -import { FileManager } from "../../../lib/files/FileManager.js"; -import { logger } from "../../../lib/logging/standardLogging.js"; -import { generateThumbHash } from "../../../lib/thumbHash.js"; -import { ImageRepository } from "../../../repositories/image/ImageRepository.js"; +import { maxFileSize } from "#environment"; +import { FileManager } from "#files/FileManager.js"; +import { generateThumbHash } from "#lib/thumbHash.js"; +import { logger } from "#logging/standardLogging.js"; +import { ImageRepository } from "#repositories/image/ImageRepository.js"; const uploadRouter = new Router({ prefix: "/upload" }).post( "/image/:uuid", diff --git a/packages/server/src/seed.ts b/packages/server/src/seed.ts index b90d4ff6..14e24c5c 100644 --- a/packages/server/src/seed.ts +++ b/packages/server/src/seed.ts @@ -1,9 +1,17 @@ -import { CommitteeIdentifier, CommitteeRole } from "@ukdanceblue/common"; +import { + CommitteeIdentifier, + CommitteeRole, + TeamLegacyStatus, + TeamType, +} from "@ukdanceblue/common"; import { Container } from "typedi"; -import { isDevelopment } from "./environment.js"; -import { ConfigurationRepository } from "./repositories/configuration/ConfigurationRepository.js"; -import { PersonRepository } from "./repositories/person/PersonRepository.js"; +import { isDevelopment } from "#environment"; +import { CommitteeRepository } from "#repositories/committee/CommitteeRepository.js"; +import { ConfigurationRepository } from "#repositories/configuration/ConfigurationRepository.js"; +import { MarathonRepository } from "#repositories/marathon/MarathonRepository.js"; +import { PersonRepository } from "#repositories/person/PersonRepository.js"; +import { TeamRepository } from "#repositories/team/TeamRepository.js"; if (!isDevelopment) { throw new Error("Seeding is only allowed in development mode"); @@ -13,11 +21,13 @@ const { prisma } = await import("./prisma.js"); try { const personRepository = Container.get(PersonRepository); + const committeeRepository = Container.get(CommitteeRepository); + const teamRepository = Container.get(TeamRepository); + const configurationRepository = Container.get(ConfigurationRepository); + const marathonRepository = Container.get(MarathonRepository); const techCommittee: [string, string][] = [ ["jtho264@uky.edu", "jtho264"], - ["jp.huse@uky.edu", "jphu235"], - ["bartholomai@uky.edu", "mdba238"], ["Camille.Dyer@uky.edu", "chdy223"], ["str249@uky.edu", "str249"], ]; @@ -33,12 +43,14 @@ try { personRepository.createPerson({ email, linkblue, - committeeRole: CommitteeRole.Coordinator, - committeeName: CommitteeIdentifier.techCommittee, }) ) ); + if (techPeople.some((p) => p.isErr())) { + throw new Error("Failed to create all tech committee people"); + } + const randomPeople = await Promise.all( randoms.map(([email, linkblue, name]) => personRepository.createPerson({ @@ -56,30 +68,55 @@ try { throw new Error("Failed to create all tech committee people"); } - // const teamRepository = Container.get(TeamRepository); - - // await teamRepository.createTeam({ - // name: "Tech Committee", - // legacyStatus: TeamLegacyStatus.ReturningTeam, - // marathonYear: "DB24", - // type: TeamType.Committee, - // memberships: { - // connect: techPeople, - // }, - // persistentIdentifier: CommitteeIdentifier.techCommittee, - // }); - - // await teamRepository.createTeam({ - // name: "Random People", - // legacyStatus: TeamLegacyStatus.NewTeam, - // marathonYear: "DB24", - // type: TeamType.Spirit, - // memberships: { - // connect: randomPeople, - // }, - // }); + const marathon = await marathonRepository.createMarathon({ + year: "DB24", + }); - const configurationRepository = Container.get(ConfigurationRepository); + if (marathon.isErr()) { + throw new Error("Failed to create marathon"); + } + + const techCommitteeTeam = await teamRepository.createTeam( + { + name: "Tech Committee", + legacyStatus: TeamLegacyStatus.ReturningTeam, + type: TeamType.Spirit, + }, + { id: marathon.value.id } + ); + + await teamRepository.createTeam( + { + name: "Random People", + legacyStatus: TeamLegacyStatus.NewTeam, + type: TeamType.Spirit, + }, + { id: marathon.value.id } + ); + + await teamRepository.updateTeam( + { id: techCommitteeTeam.id }, + { + correspondingCommittee: { + connect: { + identifier: CommitteeIdentifier.techCommittee, + }, + }, + } + ); + + await Promise.all( + techPeople.flatMap((person) => + committeeRepository.assignPersonToCommittee( + { + id: person.unwrap().id, + }, + CommitteeIdentifier.techCommittee, + CommitteeRole.Coordinator, + { id: marathon.value.id } + ) + ) + ); await configurationRepository.createConfiguration({ key: "TAB_BAR_CONFIG", diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index 3983f56d..00899cec 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -13,17 +13,13 @@ import type { DefaultState } from "koa"; import Koa from "koa"; import { koaBody } from "koa-body"; -import { - applicationHost, - applicationPort, - loggingLevel, -} from "./environment.js"; -import { logger } from "./lib/logging/logger.js"; -import type { GraphQLContext } from "./resolvers/context.js"; -import eventsApiRouter from "./routes/api/events/index.js"; -import fileRouter from "./routes/api/file/index.js"; -import healthCheckRouter from "./routes/api/healthcheck/index.js"; -import uploadRouter from "./routes/api/upload/index.js"; +import { applicationHost, applicationPort, loggingLevel } from "#environment"; +import { logger } from "#logging/logger.js"; +import type { GraphQLContext } from "#resolvers/context.js"; +import eventsApiRouter from "#routes/api/events/index.js"; +import fileRouter from "#routes/api/file/index.js"; +import healthCheckRouter from "#routes/api/healthcheck/index.js"; +import uploadRouter from "#routes/api/upload/index.js"; const basicLoggingPlugin: ApolloServerPlugin = { requestDidStart(requestContext) { diff --git a/packages/server/src/types.d.ts b/packages/server/src/types.d.ts index 98668072..68d10255 100644 --- a/packages/server/src/types.d.ts +++ b/packages/server/src/types.d.ts @@ -1,4 +1,4 @@ -import { SyslogLevels } from "./lib/logging/standardLogging.ts"; +import { SyslogLevels } from "#logging/standardLogging.ts"; declare global { namespace NodeJS { @@ -12,12 +12,6 @@ declare global { COOKIE_SECRET?: string; JWT_SECRET?: string; - DB_HOST?: string; - DB_PORT?: string; - DB_UNAME?: string; - DB_PWD?: string; - DB_NAME?: string; - // These don't need to be optional because they are checked in index.ts MS_OIDC_URL?: string; MS_CLIENT_ID?: string; diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index 3e959a5e..2d82c995 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -13,22 +13,19 @@ "checkJs": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, - "types": [ - "body-parser", - "cookie-parser", - "cors", - "ejs", - "express", - "express-session", - "http-errors", - "jsonwebtoken", - "luxon", - "multer", - "node-fetch", - "normalize-path", - "qs", - "ws" - ] + "paths": { + "#auth/*": ["./src/lib/auth/*"], + "#error/*": ["./src/lib/error/*"], + "#files/*": ["./src/lib/files/*"], + "#logging/*": ["./src/lib/logging/*"], + "#notification/*": ["./src/lib/notification/*"], + "#lib/*": ["./src/lib/*"], + "#jobs/*": ["./src/jobs/*"], + "#repositories/*": ["./src/repositories/*"], + "#resolvers/*": ["./src/resolvers/*"], + "#routes/*": ["./src/routes/*"], + "#environment": ["./src/environment.ts"] + } }, "include": ["src"], "extends": "../../tsconfig.json" diff --git a/schema.graphql b/schema.graphql index 28d13702..4d11e122 100644 --- a/schema.graphql +++ b/schema.graphql @@ -44,13 +44,12 @@ type AcknowledgeDeliveryIssueResponse implements AbstractGraphQLOkResponse & Gra } type AddEventImageResponse implements AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: ImageResource! + data: ImageNode! ok: Boolean! } -type AuthIdPairResource { - source: AuthSource! - value: String! +input AssignEntryToPersonInput { + amount: Float! } """The source of authentication""" @@ -71,11 +70,30 @@ enum CommitteeIdentifier { marketingCommittee miniMarathonsCommittee operationsCommittee + overallCommittee programmingCommittee techCommittee viceCommittee } +type CommitteeMembershipNode implements Node { + createdAt: DateTimeISO + id: GlobalId! + identifier: CommitteeIdentifier! + person: PersonNode! + position: MembershipPositionType! + role: CommitteeRole! + team: TeamNode! + updatedAt: DateTimeISO +} + +type CommitteeNode implements Node { + createdAt: DateTimeISO + id: GlobalId! + identifier: CommitteeIdentifier! + updatedAt: DateTimeISO +} + """Roles within a committee""" enum CommitteeRole { Chair @@ -83,25 +101,25 @@ enum CommitteeRole { Member } -type ConfigurationResource { +type ConfigurationNode implements Node { createdAt: DateTimeISO + id: GlobalId! key: String! updatedAt: DateTimeISO - uuid: ID! - validAfter: LuxonDateTime - validUntil: LuxonDateTime + validAfter: DateTimeISO + validUntil: DateTimeISO value: String! } input CreateConfigurationInput { key: String! - validAfter: LuxonDateTime - validUntil: LuxonDateTime + validAfter: DateTimeISO + validUntil: DateTimeISO value: String! } type CreateConfigurationResponse implements AbstractGraphQLCreatedResponse & AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: ConfigurationResource! + data: ConfigurationNode! ok: Boolean! uuid: String! } @@ -116,11 +134,11 @@ input CreateEventInput { input CreateEventOccurrenceInput { fullDay: Boolean! - interval: LuxonDateRange! + interval: IntervalISOInput! } type CreateEventResponse implements AbstractGraphQLCreatedResponse & AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: EventResource! + data: EventNode! ok: Boolean! uuid: String! } @@ -151,17 +169,11 @@ input CreateMarathonInput { input CreatePersonInput { captainOf: [String!]! = [] + dbRole: DbRole email: EmailAddress! linkblue: String memberOf: [String!]! = [] name: String - role: RoleResourceInput -} - -type CreatePersonResponse implements AbstractGraphQLCreatedResponse & AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: PersonResource! - ok: Boolean! - uuid: String! } input CreatePointEntryInput { @@ -173,7 +185,7 @@ input CreatePointEntryInput { } type CreatePointEntryResponse implements AbstractGraphQLCreatedResponse & AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: PointEntryResource! + data: PointEntryNode! ok: Boolean! uuid: String! } @@ -181,26 +193,24 @@ type CreatePointEntryResponse implements AbstractGraphQLCreatedResponse & Abstra input CreatePointOpportunityInput { eventUuid: ID name: String! - opportunityDate: LuxonDateTime + opportunityDate: DateTimeISO type: TeamType! } type CreatePointOpportunityResponse implements AbstractGraphQLCreatedResponse & AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: PointOpportunityResource! + data: PointOpportunityNode! ok: Boolean! uuid: String! } input CreateTeamInput { legacyStatus: TeamLegacyStatus! - marathonYear: String! name: String! - persistentIdentifier: String type: TeamType! } type CreateTeamResponse implements AbstractGraphQLCreatedResponse & AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: TeamResource! + data: TeamNode! ok: Boolean! uuid: String! } @@ -210,6 +220,12 @@ A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `dat """ scalar DateTimeISO +type DbFundsTeamInfo implements Node { + dbNum: Int! + id: GlobalId! + name: String! +} + """DanceBlue roles""" enum DbRole { Committee @@ -239,10 +255,6 @@ type DeleteNotificationResponse implements AbstractGraphQLOkResponse & GraphQLBa ok: Boolean! } -type DeletePersonResponse implements AbstractGraphQLOkResponse & GraphQLBaseResponse { - ok: Boolean! -} - type DeletePointEntryResponse implements AbstractGraphQLOkResponse & GraphQLBaseResponse { ok: Boolean! } @@ -255,6 +267,23 @@ type DeleteTeamResponse implements AbstractGraphQLOkResponse & GraphQLBaseRespon ok: Boolean! } +type DeviceNode implements Node { + createdAt: DateTimeISO + id: GlobalId! + lastLoggedInUser: PersonNode + lastLogin: DateTimeISO + notificationDeliveries( + page: Int = 1 + pageSize: Int = 10 + + """ + The verifier code for this device, if it does not match then the query will be rejected + """ + verifier: String + ): [NotificationDeliveryNode!]! + updatedAt: DateTimeISO +} + enum DeviceResolverAllKeys { createdAt expoPushToken @@ -279,7 +308,7 @@ input DeviceResolverKeyedDateFilterItem { Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. """ negate: Boolean = false - value: LuxonDateTime! + value: DateTimeISO! } input DeviceResolverKeyedIsNullFilterItem { @@ -321,22 +350,9 @@ enum DeviceResolverStringFilterKeys { expoPushToken } -type DeviceResource { - createdAt: DateTimeISO - expoPushToken: String - lastLoggedInUser: PersonResource - lastLogin: LuxonDateTime - notificationDeliveries( - page: Int = 1 - pageSize: Int = 10 - - """ - The verifier code for this device, if it does not match then the query will be rejected - """ - verifier: String - ): [NotificationDeliveryResource!]! - updatedAt: DateTimeISO - uuid: ID! +type EffectiveCommitteeRole { + identifier: CommitteeIdentifier! + role: CommitteeRole! } """ @@ -344,10 +360,22 @@ A field whose value conforms to the standard internet email address format as sp """ scalar EmailAddress @specifiedBy(url: "https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address") -type EventOccurrenceResource { +type EventNode implements Node { + createdAt: DateTimeISO + description: String + id: GlobalId! + images: [ImageNode!]! + location: String + occurrences: [EventOccurrenceNode!]! + summary: String + title: String! + updatedAt: DateTimeISO +} + +type EventOccurrenceNode { fullDay: Boolean! - interval: LuxonDateRange! - uuid: ID! + id: ID! + interval: IntervalISO! } enum EventResolverAllKeys { @@ -381,7 +409,7 @@ input EventResolverKeyedDateFilterItem { Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. """ negate: Boolean = false - value: LuxonDateTime! + value: DateTimeISO! } input EventResolverKeyedIsNullFilterItem { @@ -426,77 +454,185 @@ enum EventResolverStringFilterKeys { title } -type EventResource { +type FeedNode implements Node { createdAt: DateTimeISO - description: String - images: [ImageResource!]! - location: String - occurrences: [EventOccurrenceResource!]! - summary: String + id: GlobalId! + image: ImageNode + textContent: String title: String! updatedAt: DateTimeISO - uuid: ID! } -type FeedResource { +type FundraisingAssignmentNode implements Node { + amount: Float! createdAt: DateTimeISO - image: ImageResource - textContent: String - title: String! + entry: FundraisingEntryNode! + id: GlobalId! + + """ + The person assigned to this assignment, only null when access is denied + """ + person: PersonNode updatedAt: DateTimeISO - uuid: ID! +} + +type FundraisingEntryNode implements Node { + amount: Float! + assignments: [FundraisingAssignmentNode!]! + createdAt: DateTimeISO + donatedByText: String + donatedOn: DateTimeISO! + donatedToText: String + id: GlobalId! + updatedAt: DateTimeISO +} + +enum FundraisingEntryResolverAllKeys { + amount + createdAt + donatedBy + donatedOn + donatedTo + updatedAt +} + +enum FundraisingEntryResolverDateFilterKeys { + createdAt + donatedOn + updatedAt +} + +input FundraisingEntryResolverKeyedDateFilterItem { + """The comparator to use for the filter""" + comparison: NumericComparator! + + """The field to filter on""" + field: FundraisingEntryResolverDateFilterKeys! + + """ + Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. + """ + negate: Boolean = false + value: DateTimeISO! +} + +input FundraisingEntryResolverKeyedIsNullFilterItem { + """The field to filter on""" + field: FundraisingEntryResolverAllKeys! + + """ + Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. + """ + negate: Boolean = false +} + +input FundraisingEntryResolverKeyedNumericFilterItem { + """The comparator to use for the filter""" + comparison: NumericComparator! + + """The field to filter on""" + field: FundraisingEntryResolverNumericFilterKeys! + + """ + Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. + """ + negate: Boolean = false + value: Float! +} + +input FundraisingEntryResolverKeyedOneOfFilterItem { + """The field to filter on""" + field: FundraisingEntryResolverOneOfFilterKeys! + + """ + Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. + """ + negate: Boolean = false + value: [String!]! +} + +input FundraisingEntryResolverKeyedStringFilterItem { + """The comparator to use for the filter""" + comparison: StringComparator! + + """The field to filter on""" + field: FundraisingEntryResolverStringFilterKeys! + + """ + Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. + """ + negate: Boolean = false + value: String! +} + +enum FundraisingEntryResolverNumericFilterKeys { + amount +} + +enum FundraisingEntryResolverOneOfFilterKeys { + teamId +} + +enum FundraisingEntryResolverStringFilterKeys { + donatedBy + donatedTo } type GetAllConfigurationsResponse implements AbstractGraphQLArrayOkResponse & GraphQLBaseResponse { - data: [ConfigurationResource!]! + data: [ConfigurationNode!]! ok: Boolean! } type GetConfigurationByUuidResponse implements AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: ConfigurationResource! + data: ConfigurationNode! ok: Boolean! } type GetDeviceByUuidResponse implements AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: DeviceResource! + data: DeviceNode! ok: Boolean! } type GetEventByUuidResponse implements AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: EventResource! + data: EventNode! ok: Boolean! } type GetImageByUuidResponse implements AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: ImageResource! + data: ImageNode! ok: Boolean! } type GetNotificationByUuidResponse implements AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: NotificationResource! - ok: Boolean! -} - -type GetPeopleResponse implements AbstractGraphQLArrayOkResponse & GraphQLBaseResponse { - data: [PersonResource!]! - ok: Boolean! -} - -type GetPersonResponse implements AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: PersonResource + data: NotificationNode! ok: Boolean! } type GetPointEntryByUuidResponse implements AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: PointEntryResource! + data: PointEntryNode! ok: Boolean! } +"""GlobalId custom scalar type""" +scalar GlobalId + """API response""" interface GraphQLBaseResponse { ok: Boolean! } +type ImageNode implements Node { + alt: String + createdAt: DateTimeISO + height: Int! + id: GlobalId! + mimeType: String! + thumbHash: String + updatedAt: DateTimeISO + url: URL + width: Int! +} + enum ImageResolverAllKeys { alt createdAt @@ -521,7 +657,7 @@ input ImageResolverKeyedDateFilterItem { Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. """ negate: Boolean = false - value: LuxonDateTime! + value: DateTimeISO! } input ImageResolverKeyedIsNullFilterItem { @@ -582,20 +718,18 @@ enum ImageResolverStringFilterKeys { alt } -type ImageResource { - alt: String - createdAt: DateTimeISO - height: Int! - mimeType: String! - thumbHash: String - updatedAt: DateTimeISO - url: URL - uuid: ID! - width: Int! +type IntervalISO { + end: DateTimeISO! + start: DateTimeISO! +} + +input IntervalISOInput { + end: DateTimeISO! + start: DateTimeISO! } type ListDevicesResponse implements AbstractGraphQLArrayOkResponse & AbstractGraphQLPaginatedResponse & GraphQLBaseResponse { - data: [DeviceResource!]! + data: [DeviceNode!]! ok: Boolean! """The current page number (1-indexed)""" @@ -609,7 +743,21 @@ type ListDevicesResponse implements AbstractGraphQLArrayOkResponse & AbstractGra } type ListEventsResponse implements AbstractGraphQLArrayOkResponse & AbstractGraphQLPaginatedResponse & GraphQLBaseResponse { - data: [EventResource!]! + data: [EventNode!]! + ok: Boolean! + + """The current page number (1-indexed)""" + page: PositiveInt! + + """The number of items per page""" + pageSize: NonNegativeInt! + + """The total number of items""" + total: NonNegativeInt! +} + +type ListFundraisingEntriesResponse implements AbstractGraphQLArrayOkResponse & AbstractGraphQLPaginatedResponse & GraphQLBaseResponse { + data: [FundraisingEntryNode!]! ok: Boolean! """The current page number (1-indexed)""" @@ -623,7 +771,7 @@ type ListEventsResponse implements AbstractGraphQLArrayOkResponse & AbstractGrap } type ListImagesResponse implements AbstractGraphQLArrayOkResponse & AbstractGraphQLPaginatedResponse & GraphQLBaseResponse { - data: [ImageResource!]! + data: [ImageNode!]! ok: Boolean! """The current page number (1-indexed)""" @@ -637,7 +785,7 @@ type ListImagesResponse implements AbstractGraphQLArrayOkResponse & AbstractGrap } type ListMarathonsResponse implements AbstractGraphQLArrayOkResponse & AbstractGraphQLPaginatedResponse & GraphQLBaseResponse { - data: [MarathonResource!]! + data: [MarathonNode!]! ok: Boolean! """The current page number (1-indexed)""" @@ -651,7 +799,7 @@ type ListMarathonsResponse implements AbstractGraphQLArrayOkResponse & AbstractG } type ListNotificationDeliveriesResponse implements AbstractGraphQLArrayOkResponse & AbstractGraphQLPaginatedResponse & GraphQLBaseResponse { - data: [NotificationDeliveryResource!]! + data: [NotificationDeliveryNode!]! ok: Boolean! """The current page number (1-indexed)""" @@ -665,7 +813,7 @@ type ListNotificationDeliveriesResponse implements AbstractGraphQLArrayOkRespons } type ListNotificationsResponse implements AbstractGraphQLArrayOkResponse & AbstractGraphQLPaginatedResponse & GraphQLBaseResponse { - data: [NotificationResource!]! + data: [NotificationNode!]! ok: Boolean! """The current page number (1-indexed)""" @@ -679,7 +827,7 @@ type ListNotificationsResponse implements AbstractGraphQLArrayOkResponse & Abstr } type ListPeopleResponse implements AbstractGraphQLArrayOkResponse & AbstractGraphQLPaginatedResponse & GraphQLBaseResponse { - data: [PersonResource!]! + data: [PersonNode!]! ok: Boolean! """The current page number (1-indexed)""" @@ -693,7 +841,7 @@ type ListPeopleResponse implements AbstractGraphQLArrayOkResponse & AbstractGrap } type ListPointEntriesResponse implements AbstractGraphQLArrayOkResponse & AbstractGraphQLPaginatedResponse & GraphQLBaseResponse { - data: [PointEntryResource!]! + data: [PointEntryNode!]! ok: Boolean! """The current page number (1-indexed)""" @@ -707,7 +855,7 @@ type ListPointEntriesResponse implements AbstractGraphQLArrayOkResponse & Abstra } type ListPointOpportunitiesResponse implements AbstractGraphQLArrayOkResponse & AbstractGraphQLPaginatedResponse & GraphQLBaseResponse { - data: [PointOpportunityResource!]! + data: [PointOpportunityNode!]! ok: Boolean! """The current page number (1-indexed)""" @@ -721,7 +869,7 @@ type ListPointOpportunitiesResponse implements AbstractGraphQLArrayOkResponse & } type ListTeamsResponse implements AbstractGraphQLArrayOkResponse & AbstractGraphQLPaginatedResponse & GraphQLBaseResponse { - data: [TeamResource!]! + data: [TeamNode!]! ok: Boolean! """The current page number (1-indexed)""" @@ -736,25 +884,42 @@ type ListTeamsResponse implements AbstractGraphQLArrayOkResponse & AbstractGraph type LoginState { authSource: AuthSource! + dbRole: DbRole! + effectiveCommitteeRoles: [EffectiveCommitteeRole!]! loggedIn: Boolean! - role: RoleResource! } -"""Date range custom scalar type (just an ISO 8601 interval)""" -scalar LuxonDateRange @specifiedBy(url: "https://www.iso.org/iso-8601-date-and-time-format.html") - -"""Luxon DateTime custom scalar type""" -scalar LuxonDateTime - -type MarathonHourResource { +type MarathonHourNode implements Node { createdAt: DateTimeISO details: String durationInfo: String! - mapImages: [ImageResource!]! + id: GlobalId! + mapImages: [ImageNode!]! shownStartingAt: DateTimeISO! title: String! updatedAt: DateTimeISO - uuid: ID! +} + +type MarathonNode implements Node { + communityDevelopmentCommitteeTeam: TeamNode! + corporateCommitteeTeam: TeamNode! + createdAt: DateTimeISO + dancerRelationsCommitteeTeam: TeamNode! + endDate: DateTimeISO + familyRelationsCommitteeTeam: TeamNode! + fundraisingCommitteeTeam: TeamNode! + hours: [MarathonHourNode!]! + id: GlobalId! + marketingCommitteeTeam: TeamNode! + miniMarathonsCommitteeTeam: TeamNode! + operationsCommitteeTeam: TeamNode! + overallCommitteeTeam: TeamNode! + programmingCommitteeTeam: TeamNode! + startDate: DateTimeISO + techCommitteeTeam: TeamNode! + updatedAt: DateTimeISO + viceCommitteeTeam: TeamNode! + year: String! } enum MarathonResolverAllKeys { @@ -783,7 +948,7 @@ input MarathonResolverKeyedDateFilterItem { Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. """ negate: Boolean = false - value: LuxonDateTime! + value: DateTimeISO! } input MarathonResolverKeyedIsNullFilterItem { @@ -796,14 +961,13 @@ input MarathonResolverKeyedIsNullFilterItem { negate: Boolean = false } -type MarathonResource { +type MembershipNode implements Node { createdAt: DateTimeISO - endDate: DateTimeISO! - hours: [MarathonHourResource!]! - startDate: DateTimeISO! + id: GlobalId! + person: PersonNode! + position: MembershipPositionType! + team: TeamNode! updatedAt: DateTimeISO - uuid: ID! - year: String! } """The position of a member on a team""" @@ -812,68 +976,68 @@ enum MembershipPositionType { Member } -type MembershipResource { - createdAt: DateTimeISO - person: PersonResource! - position: MembershipPositionType! - team: TeamResource! - updatedAt: DateTimeISO - uuid: ID! -} - type Mutation { - abortScheduledNotification(uuid: String!): AbortScheduledNotificationResponse! - acknowledgeDeliveryIssue(uuid: String!): AcknowledgeDeliveryIssueResponse! + abortScheduledNotification(uuid: GlobalId!): AbortScheduledNotificationResponse! + acknowledgeDeliveryIssue(uuid: GlobalId!): AcknowledgeDeliveryIssueResponse! addExistingImageToEvent(eventId: String!, imageId: String!): AddEventImageResponse! - addMap(imageUuid: String!, uuid: String!): MarathonHourResource! - attachImageToFeedItem(feedItemUuid: String!, imageUuid: String!): FeedResource! + addMap(imageUuid: String!, uuid: GlobalId!): MarathonHourNode! + addPersonToTeam(personUuid: String!, teamUuid: String!): MembershipNode! + assignEntryToPerson(entryId: String!, input: AssignEntryToPersonInput!, personId: String!): FundraisingAssignmentNode! + assignTeamToDbFundsTeam(dbFundsTeamId: Float!, teamId: String!): Void! + attachImageToFeedItem(feedItemUuid: String!, imageUuid: String!): FeedNode! createConfiguration(input: CreateConfigurationInput!): CreateConfigurationResponse! createConfigurations(input: [CreateConfigurationInput!]!): CreateConfigurationResponse! createEvent(input: CreateEventInput!): CreateEventResponse! - createFeedItem(input: CreateFeedInput!): FeedResource! - createImage(input: CreateImageInput!): ImageResource! - createMarathon(input: CreateMarathonInput!): MarathonResource! - createMarathonHour(input: CreateMarathonHourInput!, marathonUuid: String!): MarathonHourResource! - createPerson(input: CreatePersonInput!): CreatePersonResponse! + createFeedItem(input: CreateFeedInput!): FeedNode! + createImage(input: CreateImageInput!): ImageNode! + createMarathon(input: CreateMarathonInput!): MarathonNode! + createMarathonHour(input: CreateMarathonHourInput!, marathonUuid: String!): MarathonHourNode! + createPerson(input: CreatePersonInput!): PersonNode! createPointEntry(input: CreatePointEntryInput!): CreatePointEntryResponse! createPointOpportunity(input: CreatePointOpportunityInput!): CreatePointOpportunityResponse! - createTeam(input: CreateTeamInput!): CreateTeamResponse! - deleteConfiguration(uuid: String!): DeleteConfigurationResponse! - deleteDevice(uuid: String!): DeleteDeviceResponse! - deleteEvent(uuid: String!): DeleteEventResponse! + createTeam(input: CreateTeamInput!, marathon: String!): CreateTeamResponse! + deleteConfiguration(uuid: GlobalId!): DeleteConfigurationResponse! + deleteDevice(uuid: GlobalId!): DeleteDeviceResponse! + deleteEvent(uuid: GlobalId!): DeleteEventResponse! deleteFeedItem(feedItemUuid: String!): Boolean! - deleteImage(uuid: String!): DeleteImageResponse! - deleteMarathon(uuid: String!): Void! - deleteMarathonHour(uuid: String!): Void! + deleteFundraisingAssignment(id: GlobalId!): FundraisingAssignmentNode! + deleteImage(uuid: GlobalId!): DeleteImageResponse! + deleteMarathon(uuid: GlobalId!): Void! + deleteMarathonHour(uuid: GlobalId!): Void! deleteNotification( """ If true, the notification will be deleted even if it has already been sent, which will also delete the delivery records. """ force: Boolean - uuid: String! + uuid: GlobalId! ): DeleteNotificationResponse! - deletePerson(uuid: String!): DeletePersonResponse! - deletePointEntry(uuid: String!): DeletePointEntryResponse! - deletePointOpportunity(uuid: String!): DeletePointOpportunityResponse! - deleteTeam(uuid: String!): DeleteTeamResponse! + deletePerson(uuid: GlobalId!): PersonNode! + deletePointEntry(uuid: GlobalId!): DeletePointEntryResponse! + deletePointOpportunity(uuid: GlobalId!): DeletePointOpportunityResponse! + deleteTeam(uuid: GlobalId!): DeleteTeamResponse! registerDevice(input: RegisterDeviceInput!): RegisterDeviceResponse! removeImageFromEvent(eventId: String!, imageId: String!): RemoveEventImageResponse! - removeImageFromFeedItem(feedItemUuid: String!): FeedResource! - removeMap(imageUuid: String!, uuid: String!): Void! - scheduleNotification(sendAt: DateTimeISO!, uuid: String!): ScheduleNotificationResponse! + removeImageFromFeedItem(feedItemUuid: String!): FeedNode! + removeMap(imageUuid: String!, uuid: GlobalId!): Void! + scheduleNotification(sendAt: DateTimeISO!, uuid: GlobalId!): ScheduleNotificationResponse! """Send a notification immediately.""" - sendNotification(uuid: String!): SendNotificationResponse! - setEvent(input: SetEventInput!, uuid: String!): SetEventResponse! - setFeedItem(feedItemUuid: String!, input: SetFeedInput!): FeedResource! - setImageAltText(alt: String!, uuid: String!): ImageResource! - setImageUrl(uuid: String!): ImageResource! - setMarathon(input: SetMarathonInput!, uuid: String!): MarathonResource! - setMarathonHour(input: SetMarathonHourInput!, uuid: String!): MarathonHourResource! - setPerson(input: SetPersonInput!, uuid: String!): GetPersonResponse! - setPointOpportunity(input: SetPointOpportunityInput!, uuid: String!): SinglePointOpportunityResponse! - setTeam(input: SetTeamInput!, uuid: String!): SingleTeamResponse! + sendNotification(uuid: GlobalId!): SendNotificationResponse! + setEvent(input: SetEventInput!, uuid: GlobalId!): SetEventResponse! + setFeedItem(feedItemUuid: String!, input: SetFeedInput!): FeedNode! + setImageAltText(alt: String!, uuid: GlobalId!): ImageNode! + setImageUrl(uuid: GlobalId!): ImageNode! + setMarathon(input: SetMarathonInput!, uuid: GlobalId!): MarathonNode! + setMarathonHour(input: SetMarathonHourInput!, uuid: GlobalId!): MarathonHourNode! + setPerson(input: SetPersonInput!, uuid: GlobalId!): PersonNode! + setPointOpportunity(input: SetPointOpportunityInput!, uuid: GlobalId!): SinglePointOpportunityResponse! + setTeam(input: SetTeamInput!, uuid: GlobalId!): SingleTeamResponse! stageNotification(audience: NotificationAudienceInput!, body: String!, title: String!, url: String): StageNotificationResponse! + updateFundraisingAssignment(id: GlobalId!, input: UpdateFundraisingAssignmentInput!): FundraisingAssignmentNode! +} + +interface Node { + id: GlobalId! } """Integers that will have a value of 0 or more.""" @@ -896,6 +1060,26 @@ type NotificationDeliveryIssueCount { Unknown: Int! } +type NotificationDeliveryNode implements Node { + """ + A unique identifier corresponding the group of notifications this was sent to Expo with. + """ + chunkUuid: String + createdAt: DateTimeISO + + """Any error message returned by Expo when sending the notification.""" + deliveryError: String + id: GlobalId! + notification: NotificationNode! + + """The time the server received a delivery receipt from the user.""" + receiptCheckedAt: DateTimeISO + + """The time the server sent the notification to Expo for delivery.""" + sentAt: DateTimeISO + updatedAt: DateTimeISO +} + enum NotificationDeliveryResolverAllKeys { createdAt deliveryError @@ -922,7 +1106,7 @@ input NotificationDeliveryResolverKeyedDateFilterItem { Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. """ negate: Boolean = false - value: LuxonDateTime! + value: DateTimeISO! } input NotificationDeliveryResolverKeyedIsNullFilterItem { @@ -935,24 +1119,25 @@ input NotificationDeliveryResolverKeyedIsNullFilterItem { negate: Boolean = false } -type NotificationDeliveryResource { - """ - A unique identifier corresponding the group of notifications this was sent to Expo with. - """ - chunkUuid: String +type NotificationNode implements Node { + body: String! createdAt: DateTimeISO + deliveryCount: Int! + deliveryIssue: String + deliveryIssueAcknowledgedAt: DateTimeISO + deliveryIssueCount: NotificationDeliveryIssueCount! + id: GlobalId! - """Any error message returned by Expo when sending the notification.""" - deliveryError: String - notification: NotificationResource! - - """The time the server received a delivery receipt from the user.""" - receiptCheckedAt: DateTimeISO + """ + The time the notification is scheduled to be sent, if null it is either already sent or unscheduled. + """ + sendAt: DateTimeISO - """The time the server sent the notification to Expo for delivery.""" - sentAt: DateTimeISO + """The time the server started sending the notification.""" + startedSendingAt: DateTimeISO + title: String! updatedAt: DateTimeISO - uuid: ID! + url: URL } enum NotificationResolverAllKeys { @@ -983,7 +1168,7 @@ input NotificationResolverKeyedDateFilterItem { Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. """ negate: Boolean = false - value: LuxonDateTime! + value: DateTimeISO! } input NotificationResolverKeyedIsNullFilterItem { @@ -1030,27 +1215,6 @@ enum NotificationResolverStringFilterKeys { title } -type NotificationResource { - body: String! - createdAt: DateTimeISO - deliveryCount: Int! - deliveryIssue: String - deliveryIssueAcknowledgedAt: DateTimeISO - deliveryIssueCount: NotificationDeliveryIssueCount! - - """ - The time the notification is scheduled to be sent, if null it is either already sent or unscheduled. - """ - sendAt: DateTimeISO - - """The time the server started sending the notification.""" - startedSendingAt: DateTimeISO - title: String! - updatedAt: DateTimeISO - url: URL - uuid: ID! -} - enum NumericComparator { EQUALS GREATER_THAN @@ -1060,6 +1224,64 @@ enum NumericComparator { LESS_THAN_OR_EQUAL_TO } +type PersonNode implements Node { + assignedDonationEntries( + """The boolean filters to apply to the query""" + booleanFilters: Void + + """The date filters to apply to the query""" + dateFilters: [FundraisingEntryResolverKeyedDateFilterItem!] + + """Whether to include deleted items in the results""" + includeDeleted: Boolean @deprecated(reason: "Soft-deletion is no longer used in this project, this parameter is ignored") + + """The is-null filters to apply to the query""" + isNullFilters: [FundraisingEntryResolverKeyedIsNullFilterItem!] + + """The numeric filters to apply to the query""" + numericFilters: [FundraisingEntryResolverKeyedNumericFilterItem!] + + """The one-of filters to apply to the query""" + oneOfFilters: [FundraisingEntryResolverKeyedOneOfFilterItem!] + + """The page number to return, defaults to 1""" + page: Int + + """The number of items to return per page, defaults to 10""" + pageSize: Int + + """ + Whether to send all results in a single page, defaults to false (should generally be avoided) + """ + sendAll: Boolean + + """ + The fields to sort by, in order of priority. If unspecified, the sort order is undefined + """ + sortBy: [String!] + + """ + The direction to sort, if not specified will default to ascending, the order of the values in this array should match the order of the values in the sortBy array, if only one value is specified it will be used for all sortBy values, otherwise the lengths must match + """ + sortDirection: [SortDirection!] + + """The string filters to apply to the query""" + stringFilters: [FundraisingEntryResolverKeyedStringFilterItem!] + ): CommitteeMembershipNode + committees: [CommitteeMembershipNode!]! + createdAt: DateTimeISO + dbRole: DbRole! + email: String! + fundraisingAssignments: [FundraisingAssignmentNode!]! + id: GlobalId! + linkblue: String + moraleTeams: [MembershipNode!]! + name: String + primaryCommittee: CommitteeMembershipNode + teams: [MembershipNode!]! + updatedAt: DateTimeISO +} + enum PersonResolverAllKeys { committeeName committeeRole @@ -1116,17 +1338,15 @@ enum PersonResolverStringFilterKeys { name } -type PersonResource { - authIds: [AuthIdPairResource!]! @deprecated(reason: "This is now provided on the AuthIdPair resource.") - captaincies: [MembershipResource!]! @deprecated(reason: "Use teams instead and filter by position") +type PointEntryNode implements Node { + comment: String createdAt: DateTimeISO - email: String! - linkblue: String - name: String - role: RoleResource! - teams: [MembershipResource!]! + id: GlobalId! + personFrom: PersonNode + pointOpportunity: PointOpportunityNode + points: Int! + team: TeamNode! updatedAt: DateTimeISO - uuid: ID! } enum PointEntryResolverAllKeys { @@ -1150,7 +1370,7 @@ input PointEntryResolverKeyedDateFilterItem { Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. """ negate: Boolean = false - value: LuxonDateTime! + value: DateTimeISO! } input PointEntryResolverKeyedIsNullFilterItem { @@ -1163,15 +1383,14 @@ input PointEntryResolverKeyedIsNullFilterItem { negate: Boolean = false } -type PointEntryResource { - comment: String +type PointOpportunityNode implements Node { createdAt: DateTimeISO - personFrom: PersonResource - pointOpportunity: PointOpportunityResource - points: Int! - team: TeamResource! + event: EventNode + id: GlobalId! + name: String! + opportunityDate: DateTimeISO + type: TeamType! updatedAt: DateTimeISO - uuid: ID! } enum PointOpportunityResolverAllKeys { @@ -1199,7 +1418,7 @@ input PointOpportunityResolverKeyedDateFilterItem { Should the comparator be negated? WARNING: This will throw if used on a comparator that does not support negation. """ negate: Boolean = false - value: LuxonDateTime! + value: DateTimeISO! } input PointOpportunityResolverKeyedIsNullFilterItem { @@ -1245,25 +1464,17 @@ enum PointOpportunityResolverStringFilterKeys { name } -type PointOpportunityResource { - createdAt: DateTimeISO - event: EventResource - name: String! - opportunityDate: LuxonDateTime - type: TeamType! - updatedAt: DateTimeISO - uuid: ID! -} - """Integers that will have a value greater than 0.""" scalar PositiveInt type Query { activeConfiguration(key: String!): GetConfigurationByUuidResponse! allConfigurations: GetAllConfigurationsResponse! - currentMarathon: MarathonResource - currentMarathonHour: MarathonHourResource - device(uuid: String!): GetDeviceByUuidResponse! + configuration(id: GlobalId!): GetConfigurationByUuidResponse! + currentMarathon: MarathonNode + currentMarathonHour: MarathonHourNode + dbFundsTeams(search: String!): [DbFundsTeamInfo!]! + device(uuid: GlobalId!): GetDeviceByUuidResponse! devices( """The boolean filters to apply to the query""" booleanFilters: Void @@ -1307,7 +1518,7 @@ type Query { """The string filters to apply to the query""" stringFilters: [DeviceResolverKeyedStringFilterItem!] ): ListDevicesResponse! - event(uuid: String!): GetEventByUuidResponse! + event(uuid: GlobalId!): GetEventByUuidResponse! events( """The boolean filters to apply to the query""" booleanFilters: Void @@ -1351,8 +1562,53 @@ type Query { """The string filters to apply to the query""" stringFilters: [EventResolverKeyedStringFilterItem!] ): ListEventsResponse! - feed(limit: Int = 10): [FeedResource!]! - image(uuid: String!): GetImageByUuidResponse! + feed(limit: Int = 10): [FeedNode!]! + fundraisingAssignment(id: GlobalId!): FundraisingAssignmentNode! + fundraisingEntries( + """The boolean filters to apply to the query""" + booleanFilters: Void + + """The date filters to apply to the query""" + dateFilters: [FundraisingEntryResolverKeyedDateFilterItem!] + + """Whether to include deleted items in the results""" + includeDeleted: Boolean @deprecated(reason: "Soft-deletion is no longer used in this project, this parameter is ignored") + + """The is-null filters to apply to the query""" + isNullFilters: [FundraisingEntryResolverKeyedIsNullFilterItem!] + + """The numeric filters to apply to the query""" + numericFilters: [FundraisingEntryResolverKeyedNumericFilterItem!] + + """The one-of filters to apply to the query""" + oneOfFilters: [FundraisingEntryResolverKeyedOneOfFilterItem!] + + """The page number to return, defaults to 1""" + page: Int + + """The number of items to return per page, defaults to 10""" + pageSize: Int + + """ + Whether to send all results in a single page, defaults to false (should generally be avoided) + """ + sendAll: Boolean + + """ + The fields to sort by, in order of priority. If unspecified, the sort order is undefined + """ + sortBy: [String!] + + """ + The direction to sort, if not specified will default to ascending, the order of the values in this array should match the order of the values in the sortBy array, if only one value is specified it will be used for all sortBy values, otherwise the lengths must match + """ + sortDirection: [SortDirection!] + + """The string filters to apply to the query""" + stringFilters: [FundraisingEntryResolverKeyedStringFilterItem!] + ): ListFundraisingEntriesResponse! + fundraisingEntry(id: GlobalId!): FundraisingEntryNode! + image(uuid: GlobalId!): GetImageByUuidResponse! images( """The boolean filters to apply to the query""" booleanFilters: Void @@ -1396,6 +1652,7 @@ type Query { """The string filters to apply to the query""" stringFilters: [ImageResolverKeyedStringFilterItem!] ): ListImagesResponse! + latestMarathon: MarathonNode listPeople( """The boolean filters to apply to the query""" booleanFilters: Void @@ -1440,9 +1697,9 @@ type Query { stringFilters: [PersonResolverKeyedStringFilterItem!] ): ListPeopleResponse! loginState: LoginState! - marathon(uuid: String!): MarathonResource! - marathonForYear(year: String!): MarathonResource! - marathonHour(uuid: String!): MarathonHourResource! + marathon(uuid: GlobalId!): MarathonNode! + marathonForYear(year: String!): MarathonNode! + marathonHour(uuid: GlobalId!): MarathonHourNode! marathons( """The boolean filters to apply to the query""" booleanFilters: Void @@ -1486,9 +1743,9 @@ type Query { """The string filters to apply to the query""" stringFilters: Void ): ListMarathonsResponse! - me: GetPersonResponse! - nextMarathon: MarathonResource - notification(uuid: String!): GetNotificationByUuidResponse! + me: PersonNode + node(id: GlobalId!): Node! + notification(uuid: GlobalId!): GetNotificationByUuidResponse! notificationDeliveries( """The boolean filters to apply to the query""" booleanFilters: Void @@ -1576,8 +1833,8 @@ type Query { """The string filters to apply to the query""" stringFilters: [NotificationResolverKeyedStringFilterItem!] ): ListNotificationsResponse! - person(uuid: String!): GetPersonResponse! - personByLinkBlue(linkBlueId: String!): GetPersonResponse! + person(uuid: GlobalId!): PersonNode! + personByLinkBlue(linkBlueId: String!): PersonNode! pointEntries( """The boolean filters to apply to the query""" booleanFilters: Void @@ -1621,7 +1878,7 @@ type Query { """The string filters to apply to the query""" stringFilters: Void ): ListPointEntriesResponse! - pointEntry(uuid: String!): GetPointEntryByUuidResponse! + pointEntry(uuid: GlobalId!): GetPointEntryByUuidResponse! pointOpportunities( """The boolean filters to apply to the query""" booleanFilters: Void @@ -1665,9 +1922,9 @@ type Query { """The string filters to apply to the query""" stringFilters: [PointOpportunityResolverKeyedStringFilterItem!] ): ListPointOpportunitiesResponse! - pointOpportunity(uuid: String!): SinglePointOpportunityResponse! - searchPeopleByName(name: String!): GetPeopleResponse! - team(uuid: String!): SingleTeamResponse! + pointOpportunity(uuid: GlobalId!): SinglePointOpportunityResponse! + searchPeopleByName(name: String!): [PersonNode!]! + team(uuid: GlobalId!): SingleTeamResponse! teams( """The boolean filters to apply to the query""" booleanFilters: Void @@ -1681,7 +1938,7 @@ type Query { """The is-null filters to apply to the query""" isNullFilters: [TeamResolverKeyedIsNullFilterItem!] legacyStatus: [TeamLegacyStatus!] - marathonYear: [String!] + marathonId: [String!] """The numeric filters to apply to the query""" numericFilters: Void @@ -1731,7 +1988,7 @@ input RegisterDeviceInput { } type RegisterDeviceResponse implements AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: DeviceResource! + data: DeviceNode! ok: Boolean! } @@ -1740,18 +1997,6 @@ type RemoveEventImageResponse implements AbstractGraphQLOkResponse & GraphQLBase ok: Boolean! } -type RoleResource { - committeeIdentifier: CommitteeIdentifier - committeeRole: CommitteeRole - dbRole: DbRole! -} - -input RoleResourceInput { - committeeIdentifier: CommitteeIdentifier - committeeRole: CommitteeRole - dbRole: DbRole! = None -} - type ScheduleNotificationResponse implements AbstractGraphQLOkResponse & GraphQLBaseResponse { data: Boolean! ok: Boolean! @@ -1772,7 +2017,7 @@ input SetEventInput { input SetEventOccurrenceInput { fullDay: Boolean! - interval: LuxonDateRange! + interval: IntervalISOInput! """ If updating an existing occurrence, the UUID of the occurrence to update @@ -1781,7 +2026,7 @@ input SetEventOccurrenceInput { } type SetEventResponse implements AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: EventResource! + data: EventNode! ok: Boolean! } @@ -1809,41 +2054,39 @@ input SetPersonInput { linkblue: String memberOf: [String!] name: String - role: RoleResourceInput } input SetPointOpportunityInput { eventUuid: ID name: String - opportunityDate: LuxonDateTime + opportunityDate: DateTimeISO type: TeamType } input SetTeamInput { legacyStatus: TeamLegacyStatus - marathonYear: String name: String persistentIdentifier: String type: TeamType } type SinglePointOpportunityResponse implements AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: PointOpportunityResource! + data: PointOpportunityNode! ok: Boolean! } type SingleTeamResponse implements AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: TeamResource! + data: TeamNode! ok: Boolean! } enum SortDirection { - ASCENDING - DESCENDING + asc + desc } type StageNotificationResponse implements AbstractGraphQLCreatedResponse & AbstractGraphQLOkResponse & GraphQLBaseResponse { - data: NotificationResource! + data: NotificationNode! ok: Boolean! uuid: String! } @@ -1863,9 +2106,66 @@ enum TeamLegacyStatus { ReturningTeam } +type TeamNode implements Node { + captains: [MembershipNode!]! @deprecated(reason: "Just query the members field and filter by role") + createdAt: DateTimeISO + fundraisingEntries( + """The boolean filters to apply to the query""" + booleanFilters: Void + + """The date filters to apply to the query""" + dateFilters: [FundraisingEntryResolverKeyedDateFilterItem!] + + """Whether to include deleted items in the results""" + includeDeleted: Boolean @deprecated(reason: "Soft-deletion is no longer used in this project, this parameter is ignored") + + """The is-null filters to apply to the query""" + isNullFilters: [FundraisingEntryResolverKeyedIsNullFilterItem!] + + """The numeric filters to apply to the query""" + numericFilters: [FundraisingEntryResolverKeyedNumericFilterItem!] + + """The one-of filters to apply to the query""" + oneOfFilters: [FundraisingEntryResolverKeyedOneOfFilterItem!] + + """The page number to return, defaults to 1""" + page: Int + + """The number of items to return per page, defaults to 10""" + pageSize: Int + + """ + Whether to send all results in a single page, defaults to false (should generally be avoided) + """ + sendAll: Boolean + + """ + The fields to sort by, in order of priority. If unspecified, the sort order is undefined + """ + sortBy: [String!] + + """ + The direction to sort, if not specified will default to ascending, the order of the values in this array should match the order of the values in the sortBy array, if only one value is specified it will be used for all sortBy values, otherwise the lengths must match + """ + sortDirection: [SortDirection!] + + """The string filters to apply to the query""" + stringFilters: [FundraisingEntryResolverKeyedStringFilterItem!] + ): ListFundraisingEntriesResponse! + id: GlobalId! + legacyStatus: TeamLegacyStatus! + marathon: MarathonNode! + members: [MembershipNode!]! + name: String! + pointEntries: [PointEntryNode!]! + totalPoints: Int! + type: TeamType! + updatedAt: DateTimeISO +} + enum TeamResolverAllKeys { legacyStatus - marathonYear + marathonId name type } @@ -1907,7 +2207,7 @@ input TeamResolverKeyedStringFilterItem { enum TeamResolverOneOfFilterKeys { legacyStatus - marathonYear + marathonId type } @@ -1915,21 +2215,6 @@ enum TeamResolverStringFilterKeys { name } -type TeamResource { - captains: [MembershipResource!]! @deprecated(reason: "Just query the members field and filter by role") - createdAt: DateTimeISO - legacyStatus: TeamLegacyStatus! - marathonYear: String! - members: [MembershipResource!]! - name: String! - persistentIdentifier: String - pointEntries: [PointEntryResource!]! - totalPoints: Int! - type: TeamType! - updatedAt: DateTimeISO - uuid: ID! -} - """Types of teams""" enum TeamType { Committee @@ -1942,5 +2227,9 @@ A field whose value conforms to the standard URL format as specified in RFC3986: """ scalar URL +input UpdateFundraisingAssignmentInput { + amount: Float! +} + """Represents NULL values""" scalar Void \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index b70faefe..5604f89f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6994,52 +6994,6 @@ __metadata: languageName: node linkType: hard -"@rushstack/node-core-library@npm:4.0.2": - version: 4.0.2 - resolution: "@rushstack/node-core-library@npm:4.0.2" - dependencies: - fs-extra: "npm:~7.0.1" - import-lazy: "npm:~4.0.0" - jju: "npm:~1.4.0" - resolve: "npm:~1.22.1" - semver: "npm:~7.5.4" - z-schema: "npm:~5.0.2" - peerDependencies: - "@types/node": "*" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10/d28ba48e4cb755f39ccc9050f0bbc2cdabe7e706b2e7ee2f7dd2c851129f2198e024c2b1f3b5932a0689c9b86d07ae72e58a6bd62f9349f398dbbcf85d399b85 - languageName: node - linkType: hard - -"@rushstack/terminal@npm:0.10.0": - version: 0.10.0 - resolution: "@rushstack/terminal@npm:0.10.0" - dependencies: - "@rushstack/node-core-library": "npm:4.0.2" - supports-color: "npm:~8.1.1" - peerDependencies: - "@types/node": "*" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10/4fb496558f4bf03235a6716fac3bbdefa92209c8ba05838b34b8986eaec59961938cb7b3ae5e7dfa4d96b692696291894b0cb7090d76ff29753e8c54624e5343 - languageName: node - linkType: hard - -"@rushstack/ts-command-line@npm:^4.12.2": - version: 4.19.1 - resolution: "@rushstack/ts-command-line@npm:4.19.1" - dependencies: - "@rushstack/terminal": "npm:0.10.0" - "@types/argparse": "npm:1.0.38" - argparse: "npm:~1.0.9" - string-argv: "npm:~0.3.1" - checksum: 10/b529e5ea287369d837066a40689ac501b768c07fcb2af0e291d804d1ba885707742d674be34ec2b77173b8ac3b2e69d9296015412dcf582dbec6d9c5abd49ff8 - languageName: node - linkType: hard - "@segment/ajv-human-errors@npm:^2.1.2": version: 2.12.0 resolution: "@segment/ajv-human-errors@npm:2.12.0" @@ -7473,13 +7427,6 @@ __metadata: languageName: node linkType: hard -"@types/argparse@npm:1.0.38": - version: 1.0.38 - resolution: "@types/argparse@npm:1.0.38" - checksum: 10/26ed7e3f1e3595efdb883a852f5205f971b798e4c28b7e30a32c5298eee596e8b45834ce831f014d250b9730819ab05acff5b31229666d3af4ba465b4697d0eb - languageName: node - linkType: hard - "@types/babel__core@npm:^7.1.14": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" @@ -7521,7 +7468,7 @@ __metadata: languageName: node linkType: hard -"@types/body-parser@npm:*, @types/body-parser@npm:^1.19.2": +"@types/body-parser@npm:*": version: 1.19.5 resolution: "@types/body-parser@npm:1.19.5" dependencies: @@ -7587,15 +7534,6 @@ __metadata: languageName: node linkType: hard -"@types/cookie-parser@npm:^1.4.3": - version: 1.4.7 - resolution: "@types/cookie-parser@npm:1.4.7" - dependencies: - "@types/express": "npm:*" - checksum: 10/7b87c59420598e686a57e240be6e0db53967c3c8814be9326bf86609ee2fc39c4b3b9f2263e1deba43526090121d1df88684b64c19f7b494a80a4437caf3d40b - languageName: node - linkType: hard - "@types/cookies@npm:*": version: 0.9.0 resolution: "@types/cookies@npm:0.9.0" @@ -7608,15 +7546,6 @@ __metadata: languageName: node linkType: hard -"@types/cors@npm:^2.8.13": - version: 2.8.17 - resolution: "@types/cors@npm:2.8.17" - dependencies: - "@types/node": "npm:*" - checksum: 10/469bd85e29a35977099a3745c78e489916011169a664e97c4c3d6538143b0a16e4cc72b05b407dc008df3892ed7bf595f9b7c0f1f4680e169565ee9d64966bde - languageName: node - linkType: hard - "@types/debug@npm:^4.0.0": version: 4.1.12 resolution: "@types/debug@npm:4.1.12" @@ -7626,13 +7555,6 @@ __metadata: languageName: node linkType: hard -"@types/ejs@npm:^3.1.2": - version: 3.1.5 - resolution: "@types/ejs@npm:3.1.5" - checksum: 10/918898fd279108087722c1713e2ddb0c152ab839397946d164db8a18b5bbd732af9746373882a9bcf4843d35c6b191a8f569a7a4e51e90726d24501b39f40367 - languageName: node - linkType: hard - "@types/eslint-config-prettier@npm:^6.11.3": version: 6.11.3 resolution: "@types/eslint-config-prettier@npm:6.11.3" @@ -7687,7 +7609,7 @@ __metadata: languageName: node linkType: hard -"@types/express@npm:*, @types/express@npm:^4.17.13, @types/express@npm:^4.17.17": +"@types/express@npm:*, @types/express@npm:^4.17.13": version: 4.17.21 resolution: "@types/express@npm:4.17.21" dependencies: @@ -7994,15 +7916,6 @@ __metadata: languageName: node linkType: hard -"@types/multer@npm:^1.4.7": - version: 1.4.11 - resolution: "@types/multer@npm:1.4.11" - dependencies: - "@types/express": "npm:*" - checksum: 10/5abbc9a8b0d7bb817a52429c52f052152ebe2fb212e7138359c0c0b9207486ef7b1e54f65915c968300a0874cee546dbfc850415584fc9d14eff2b27bb926e7f - languageName: node - linkType: hard - "@types/node-fetch@npm:^2.6.1, @types/node-fetch@npm:^2.6.4": version: 2.6.11 resolution: "@types/node-fetch@npm:2.6.11" @@ -8070,7 +7983,7 @@ __metadata: languageName: node linkType: hard -"@types/qs@npm:*, @types/qs@npm:^6.9.7": +"@types/qs@npm:*": version: 6.9.14 resolution: "@types/qs@npm:6.9.14" checksum: 10/d3b76021d36b86c0063ec4b7373e9fa470754914e486fbfe54b3a8866dad037800a2c2068ecbcaa9399ae3ed15772a26b07e67793ed2519cf2de199104014716 @@ -8208,7 +8121,7 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:^8.0.0, @types/ws@npm:^8.5.4": +"@types/ws@npm:^8.0.0": version: 8.5.10 resolution: "@types/ws@npm:8.5.10" dependencies: @@ -8449,7 +8362,8 @@ __metadata: jest: "npm:^29.5.0" ts-jest: "npm:^29.1.0" ts-node: "npm:^10.9.1" - typescript: "npm:^5.4.3" + ts-results-es: "npm:^4.2.0" + typescript: "npm:^5.5.3" vitest: "npm:^1.4.0" peerDependencies: class-validator: 0.14.0 @@ -8587,7 +8501,7 @@ __metadata: ts-node: "npm:^10.9.1" type-graphql: "npm:^2.0.0-beta.3" typedi: "npm:^0.10.0" - typescript: "npm:^5.4.3" + typescript: "npm:^5.5.3" urql: "npm:^4.0.6" utility-types: "npm:^3.10.0" validator: "npm:^13.9.0" @@ -8625,7 +8539,7 @@ __metadata: graphql: "npm:^16.8.1" graphql-scalars: "npm:^1.23.0" prettier: "npm:^3.2.5" - typescript: "npm:^5.4.3" + typescript: "npm:^5.5.3" vitest: "npm:^1.4.0" languageName: unknown linkType: soft @@ -8659,7 +8573,7 @@ __metadata: thumbhash: "npm:^0.1.1" type-graphql: "npm:^2.0.0-beta.3" typedi: "npm:^0.10.0" - typescript: "npm:^5.4.3" + typescript: "npm:^5.5.3" urql: "npm:^4.0.5" use-debounce: "npm:^10.0.0" utility-types: "npm:^3.10.0" @@ -8676,15 +8590,9 @@ __metadata: "@as-integrations/koa": "npm:^1.1.1" "@azure/msal-node": "npm:^1.16.0" "@faker-js/faker": "npm:^7.6.0" - "@jest/globals": "npm:^29.5.0" "@koa/cors": "npm:^4.0.0" "@koa/router": "npm:^12.0.1" "@prisma/client": "npm:^5.8.0" - "@types/body-parser": "npm:^1.19.2" - "@types/cookie-parser": "npm:^1.4.3" - "@types/cors": "npm:^2.8.13" - "@types/ejs": "npm:^3.1.2" - "@types/express": "npm:^4.17.17" "@types/express-session": "npm:^1.17.7" "@types/http-errors": "npm:^2.0.1" "@types/jsonwebtoken": "npm:^9.0.1" @@ -8693,37 +8601,24 @@ __metadata: "@types/koa__cors": "npm:^4.0.0" "@types/koa__router": "npm:^12.0.4" "@types/luxon": "npm:^3.4.2" - "@types/multer": "npm:^1.4.7" "@types/node": "npm:^18.15.11" "@types/node-fetch": "npm:^2.6.4" "@types/normalize-path": "npm:^3.0.0" "@types/pg": "npm:^8.6.6" - "@types/qs": "npm:^6.9.7" - "@types/ws": "npm:^8.5.4" "@ukdanceblue/common": "workspace:^" - body-parser: "npm:^1.20.2" class-validator: "npm:^0.14.0" - cookie-parser: "npm:^1.4.6" - cors: "npm:^2.8.5" croner: "npm:^8.0.1" dotenv: "npm:^16.0.3" dree: "npm:^4.5.2" - ejs: "npm:^3.1.9" expo-server-sdk: "npm:^3.7.0" - express: "npm:^4.18.2" - express-session: "npm:^1.17.3" graphql: "npm:^16.8.0" graphql-scalars: "npm:^1.22.5" - http-errors: "npm:^2.0.0" http-status-codes: "npm:^2.2.0" - jest: "npm:^29.5.0" - joi: "npm:^17.9.1" jsonwebtoken: "npm:^9.0.0" koa: "npm:^2.14.2" koa-body: "npm:^6.0.1" luxon: "npm:^3.4.4" mime: "npm:^4.0.1" - multer: "npm:1.4.5-lts.1" node-fetch: "npm:^3.3.1" nodemon: "npm:^2.0.22" normalize-path: "npm:^3.0.0" @@ -8733,21 +8628,19 @@ __metadata: postgres-interval: "npm:^4.0.0" postgres-range: "npm:^1.1.3" prisma: "npm:^5.8.0" - qs: "npm:^6.11.1" reflect-metadata: "npm:^0.2.1" sharp: "npm:^0.32.1" thumbhash: "npm:^0.1.1" - ts-jest: "npm:^29.0.5" ts-node: "npm:^10.9.1" + ts-results-es: "npm:^4.2.0" type-graphql: "npm:^2.0.0-beta.3" typedi: "npm:^0.10.0" - typescript: "npm:^5.4.3" - umzug: "npm:^3.3.1" + typescript: "npm:^5.5.3" utility-types: "npm:^3.10.0" validator: "npm:^13.9.0" vitest: "npm:^1.4.0" winston: "npm:^3.8.2" - ws: "npm:^8.13.0" + zod: "npm:^3.23.8" languageName: unknown linkType: soft @@ -9426,13 +9319,6 @@ __metadata: languageName: node linkType: hard -"append-field@npm:^1.0.0": - version: 1.0.0 - resolution: "append-field@npm:1.0.0" - checksum: 10/afb50f5ff668af1cb66bc5cfebb55ed9a1d99e24901782ee83d00aed1a499835f9375a149cf27b17f79595ecfcc3d1de0cd5b020b210a5359c43eaf607c217de - languageName: node - linkType: hard - "application-config-path@npm:^0.1.0": version: 0.1.1 resolution: "application-config-path@npm:0.1.1" @@ -9468,7 +9354,7 @@ __metadata: languageName: node linkType: hard -"argparse@npm:^1.0.7, argparse@npm:~1.0.9": +"argparse@npm:^1.0.7": version: 1.0.10 resolution: "argparse@npm:1.0.10" dependencies: @@ -10430,7 +10316,7 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:1.20.2, body-parser@npm:^1.20.2": +"body-parser@npm:1.20.2": version: 1.20.2 resolution: "body-parser@npm:1.20.2" dependencies: @@ -10665,7 +10551,7 @@ __metadata: languageName: node linkType: hard -"busboy@npm:^1.0.0, busboy@npm:^1.6.0": +"busboy@npm:^1.6.0": version: 1.6.0 resolution: "busboy@npm:1.6.0" dependencies: @@ -11627,18 +11513,6 @@ __metadata: languageName: node linkType: hard -"concat-stream@npm:^1.5.2": - version: 1.6.2 - resolution: "concat-stream@npm:1.6.2" - dependencies: - buffer-from: "npm:^1.0.0" - inherits: "npm:^2.0.3" - readable-stream: "npm:^2.2.2" - typedarray: "npm:^0.0.6" - checksum: 10/71db903c84fc073ca35a274074e8d26c4330713d299f8623e993c448c1f6bf8b967806dd1d1a7b0f8add6f15ab1af7435df21fe79b4fe7efd78420c89e054e28 - languageName: node - linkType: hard - "connect@npm:^3.6.5, connect@npm:^3.7.0": version: 3.7.0 resolution: "connect@npm:3.7.0" @@ -11692,16 +11566,6 @@ __metadata: languageName: node linkType: hard -"cookie-parser@npm:^1.4.6": - version: 1.4.6 - resolution: "cookie-parser@npm:1.4.6" - dependencies: - cookie: "npm:0.4.1" - cookie-signature: "npm:1.0.6" - checksum: 10/1e5a63aa82e8eb4e02d2977c6902983dee87b02e87ec5ec43ac3cb1e72da354003716570cd5190c0ad9e8a454c9d3237f4ad6e2f16d0902205a96a1c72b77ba5 - languageName: node - linkType: hard - "cookie-signature@npm:1.0.6": version: 1.0.6 resolution: "cookie-signature@npm:1.0.6" @@ -11709,20 +11573,6 @@ __metadata: languageName: node linkType: hard -"cookie-signature@npm:1.0.7": - version: 1.0.7 - resolution: "cookie-signature@npm:1.0.7" - checksum: 10/1a62808cd30d15fb43b70e19829b64d04b0802d8ef00275b57d152de4ae6a3208ca05c197b6668d104c4d9de389e53ccc2d3bc6bcaaffd9602461417d8c40710 - languageName: node - linkType: hard - -"cookie@npm:0.4.1": - version: 0.4.1 - resolution: "cookie@npm:0.4.1" - checksum: 10/0f2defd60ac93645ee31e82d11da695080435eb4fe5bed9b14d2fc4e0621a66f4c5c60f3eb05761df08a9d6279366e8646edfd1654f359d0b5afc25304fc4ddc - languageName: node - linkType: hard - "cookie@npm:0.6.0": version: 0.6.0 resolution: "cookie@npm:0.6.0" @@ -12768,7 +12618,7 @@ __metadata: languageName: node linkType: hard -"ejs@npm:^3.1.5, ejs@npm:^3.1.6, ejs@npm:^3.1.7, ejs@npm:^3.1.8, ejs@npm:^3.1.9": +"ejs@npm:^3.1.5, ejs@npm:^3.1.6, ejs@npm:^3.1.7, ejs@npm:^3.1.8": version: 3.1.9 resolution: "ejs@npm:3.1.9" dependencies: @@ -12786,7 +12636,7 @@ __metadata: languageName: node linkType: hard -"emittery@npm:^0.13.0, emittery@npm:^0.13.1": +"emittery@npm:^0.13.1": version: 0.13.1 resolution: "emittery@npm:0.13.1" checksum: 10/fbe214171d878b924eedf1757badf58a5dce071cd1fa7f620fa841a0901a80d6da47ff05929d53163105e621ce11a71b9d8acb1148ffe1745e045145f6e69521 @@ -14371,23 +14221,7 @@ __metadata: languageName: node linkType: hard -"express-session@npm:^1.17.3": - version: 1.18.0 - resolution: "express-session@npm:1.18.0" - dependencies: - cookie: "npm:0.6.0" - cookie-signature: "npm:1.0.7" - debug: "npm:2.6.9" - depd: "npm:~2.0.0" - on-headers: "npm:~1.0.2" - parseurl: "npm:~1.3.3" - safe-buffer: "npm:5.2.1" - uid-safe: "npm:~2.1.5" - checksum: 10/d0d8290615ef66fe01cd0514b0fcb4f0ed8399cf3dcaabac29f2e09e04636367d8a35948d4d8694edf655e0303f996a245ed6154e2fa44b70a107caa2638b0fb - languageName: node - linkType: hard - -"express@npm:^4.17.1, express@npm:^4.18.2": +"express@npm:^4.17.1": version: 4.19.2 resolution: "express@npm:4.19.2" dependencies: @@ -15088,17 +14922,6 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:~7.0.1": - version: 7.0.1 - resolution: "fs-extra@npm:7.0.1" - dependencies: - graceful-fs: "npm:^4.1.2" - jsonfile: "npm:^4.0.0" - universalify: "npm:^0.1.0" - checksum: 10/3fc6e56ba2f07c00d452163f27f21a7076b72ef7da8a50fef004336d59ef4c34deda11d10ecd73fd8fbcf20e4f575f52857293090b3c9f8741d4e0598be30fea - languageName: node - linkType: hard - "fs-minipass@npm:^2.0.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" @@ -15512,7 +15335,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.4, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.4, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 @@ -16132,13 +15955,6 @@ __metadata: languageName: node linkType: hard -"import-lazy@npm:~4.0.0": - version: 4.0.0 - resolution: "import-lazy@npm:4.0.0" - checksum: 10/943309cc8eb01ada12700448c288b0384f77a1bc33c7e00fa4cb223c665f467a13ce9aaceb8d2e4cf586b07c1d2828040263dcc069873ce63cfc2ac6fd087971 - languageName: node - linkType: hard - "import-local@npm:^3.0.2": version: 3.1.0 resolution: "import-local@npm:3.1.0" @@ -17671,13 +17487,6 @@ __metadata: languageName: node linkType: hard -"jju@npm:~1.4.0": - version: 1.4.0 - resolution: "jju@npm:1.4.0" - checksum: 10/1067ff8ce02221faac5a842116ed0ec79a53312a111d0bf8342a80bd02c0a3fdf0b8449694a65947db0a3e8420e8b326dffb489c7dd5866efc380c0d1708a707 - languageName: node - linkType: hard - "jks-js@npm:1.1.0": version: 1.1.0 resolution: "jks-js@npm:1.1.0" @@ -17702,7 +17511,7 @@ __metadata: languageName: node linkType: hard -"joi@npm:^17.11.0, joi@npm:^17.2.1, joi@npm:^17.9.1": +"joi@npm:^17.11.0, joi@npm:^17.2.1": version: 17.12.2 resolution: "joi@npm:17.12.2" dependencies: @@ -20117,7 +19926,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.4, mkdirp@npm:~0.5.1": +"mkdirp@npm:^0.5.1, mkdirp@npm:~0.5.1": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" dependencies: @@ -20191,21 +20000,6 @@ __metadata: languageName: node linkType: hard -"multer@npm:1.4.5-lts.1": - version: 1.4.5-lts.1 - resolution: "multer@npm:1.4.5-lts.1" - dependencies: - append-field: "npm:^1.0.0" - busboy: "npm:^1.0.0" - concat-stream: "npm:^1.5.2" - mkdirp: "npm:^0.5.4" - object-assign: "npm:^4.1.1" - type-is: "npm:^1.6.4" - xtend: "npm:^4.0.0" - checksum: 10/957c09956f3b7f79d8586cac5e2a50e9a5c3011eb841667b5e4590c5f31d9464f5b46aecd399c83e183a15b88b019cccf0e4fa5620db40bf16b9e3af7fab3ac6 - languageName: node - linkType: hard - "mute-stream@npm:0.0.8": version: 0.0.8 resolution: "mute-stream@npm:0.0.8" @@ -21664,13 +21458,6 @@ __metadata: languageName: node linkType: hard -"pony-cause@npm:^2.1.4": - version: 2.1.10 - resolution: "pony-cause@npm:2.1.10" - checksum: 10/906563565030996d0c40ba79a584e2f298391931acc59c98510f9fd583d72cd9e9c58b0fb5a25bbae19daf16840f94cb9c1ee72c7ed5ef249ecba147cee40495 - languageName: node - linkType: hard - "posix-character-classes@npm:^0.1.0": version: 0.1.1 resolution: "posix-character-classes@npm:0.1.1" @@ -22075,7 +21862,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.11.0, qs@npm:^6.11.1, qs@npm:^6.11.2, qs@npm:^6.5.2": +"qs@npm:^6.11.0, qs@npm:^6.11.2, qs@npm:^6.5.2": version: 6.12.0 resolution: "qs@npm:6.12.0" dependencies: @@ -22144,13 +21931,6 @@ __metadata: languageName: node linkType: hard -"random-bytes@npm:~1.0.0": - version: 1.0.0 - resolution: "random-bytes@npm:1.0.0" - checksum: 10/09faa256394aa2ca9754aa57e92a27c452c3e97ffb266e98bebb517332e9df7168fea393159f88d884febce949ba8bec8ddb02f03342da6c6023ecc7b155e0ae - languageName: node - linkType: hard - "randomatic@npm:^3.0.0": version: 3.1.1 resolution: "randomatic@npm:3.1.1" @@ -23197,7 +22977,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.0.2, readable-stream@npm:^2.2.2, readable-stream@npm:~2.3.6": +"readable-stream@npm:^2.0.2, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -23663,7 +23443,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.10.0, resolve@npm:^1.10.1, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.2, resolve@npm:^1.22.4, resolve@npm:~1.22.1": +"resolve@npm:^1.10.0, resolve@npm:^1.10.1, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.2, resolve@npm:^1.22.4": version: 1.22.8 resolution: "resolve@npm:1.22.8" dependencies: @@ -23698,7 +23478,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.10.1#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.2#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A~1.22.1#optional!builtin": +"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.10.1#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.2#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin": version: 1.22.8 resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" dependencies: @@ -24128,7 +23908,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.5.4, semver@npm:~7.5.4": +"semver@npm:7.5.4": version: 7.5.4 resolution: "semver@npm:7.5.4" dependencies: @@ -25030,13 +24810,6 @@ __metadata: languageName: node linkType: hard -"string-argv@npm:~0.3.1": - version: 0.3.2 - resolution: "string-argv@npm:0.3.2" - checksum: 10/f9d3addf887026b4b5f997a271149e93bf71efc8692e7dc0816e8807f960b18bcb9787b45beedf0f97ff459575ee389af3f189d8b649834cac602f2e857e75af - languageName: node - linkType: hard - "string-convert@npm:^0.2.0": version: 0.2.1 resolution: "string-convert@npm:0.2.1" @@ -25377,7 +25150,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^8.0.0, supports-color@npm:^8.1.1, supports-color@npm:~8.1.1": +"supports-color@npm:^8.0.0, supports-color@npm:^8.1.1": version: 8.1.1 resolution: "supports-color@npm:8.1.1" dependencies: @@ -25933,7 +25706,7 @@ __metadata: languageName: node linkType: hard -"ts-jest@npm:^29.0.3, ts-jest@npm:^29.0.5, ts-jest@npm:^29.1.0": +"ts-jest@npm:^29.0.3, ts-jest@npm:^29.1.0": version: 29.1.2 resolution: "ts-jest@npm:29.1.2" dependencies: @@ -26018,6 +25791,13 @@ __metadata: languageName: node linkType: hard +"ts-results-es@npm:^4.2.0": + version: 4.2.0 + resolution: "ts-results-es@npm:4.2.0" + checksum: 10/d438a09419ec621b5bf092534104df285b343866fdbb3afb91e6fd6225f48b78ea5755da055aaf4143f4aa695588e4babd71aecd0bd758b12655cf874e7b2b06 + languageName: node + linkType: hard + "tsconfig-paths@npm:^3.15.0": version: 3.15.0 resolution: "tsconfig-paths@npm:3.15.0" @@ -26141,13 +25921,6 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^4.0.0": - version: 4.14.0 - resolution: "type-fest@npm:4.14.0" - checksum: 10/fcf3b62fed194a3fc3d22bf287af3b1e1e93d4b5cb9723c093b694998d1588f27cc78dc4942ff4d319bb94ec60cb4afe5d1cd3d50ef144e47e327acbc34a5234 - languageName: node - linkType: hard - "type-graphql@npm:2.0.0-beta.3": version: 2.0.0-beta.3 resolution: "type-graphql@npm:2.0.0-beta.3" @@ -26190,7 +25963,7 @@ __metadata: languageName: node linkType: hard -"type-is@npm:^1.6.16, type-is@npm:^1.6.4, type-is@npm:~1.6.18": +"type-is@npm:^1.6.16, type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" dependencies: @@ -26252,13 +26025,6 @@ __metadata: languageName: node linkType: hard -"typedarray@npm:^0.0.6": - version: 0.0.6 - resolution: "typedarray@npm:0.0.6" - checksum: 10/2cc1bcf7d8c1237f6a16c04efc06637b2c5f2d74e58e84665445cf87668b85a21ab18dd751fa49eee6ae024b70326635d7b79ad37b1c370ed2fec6aeeeb52714 - languageName: node - linkType: hard - "typedi@npm:^0.10.0": version: 0.10.0 resolution: "typedi@npm:0.10.0" @@ -26266,23 +26032,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.4.3": - version: 5.4.3 - resolution: "typescript@npm:5.4.3" +"typescript@npm:^5.5.3": + version: 5.5.3 + resolution: "typescript@npm:5.5.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/de4c69f49a7ad4b1ea66a6dcc8b055ac34eb56af059a069d8988dd811c5e649be07e042e5bf573e8d0ac3ec2f30e6c999aa651cd09f6e9cbc6113749e8b6be20 + checksum: 10/11a867312419ed497929aafd2f1d28b2cd41810a5eb6c6e9e169559112e9ea073d681c121a29102e67cd4478d0a4ae37a306a5800f3717f59c4337e6a9bd5e8d languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.4.3#optional!builtin": - version: 5.4.3 - resolution: "typescript@patch:typescript@npm%3A5.4.3#optional!builtin::version=5.4.3&hash=5adc0c" +"typescript@patch:typescript@npm%3A^5.5.3#optional!builtin": + version: 5.5.3 + resolution: "typescript@patch:typescript@npm%3A5.5.3#optional!builtin::version=5.5.3&hash=5adc0c" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/5aedd97595582b08aadb8a70e8e3ddebaf5a9c1e5ad4d6503c2fcfc15329b5cf8d01145b09913e9555683ac16c5123a96be32b6d72614098ebd42df520eed9b1 + checksum: 10/b61b8bb4b4d6a8a00f9d5f931f8c67070eed6ad11feabf4c41744a326987080bfc806a621596c70fbf2e5974eca3ed65bafeeeb22a078071bdfb51d8abd7c013 languageName: node linkType: hard @@ -26314,28 +26080,6 @@ __metadata: languageName: node linkType: hard -"uid-safe@npm:~2.1.5": - version: 2.1.5 - resolution: "uid-safe@npm:2.1.5" - dependencies: - random-bytes: "npm:~1.0.0" - checksum: 10/07536043da9a026f4a2bc397543d0ace7587449afa1d9d2c4fd3ce76af8a5263a678788bcc429dff499ef29d45843cd5ee9d05434450fcfc19cc661229f703d1 - languageName: node - linkType: hard - -"umzug@npm:^3.3.1": - version: 3.7.0 - resolution: "umzug@npm:3.7.0" - dependencies: - "@rushstack/ts-command-line": "npm:^4.12.2" - emittery: "npm:^0.13.0" - glob: "npm:^8.0.3" - pony-cause: "npm:^2.1.4" - type-fest: "npm:^4.0.0" - checksum: 10/2cd3ce78dfa473ae41bed7a0819710caef8fa5ed7a625a42b5f877610bfaa31bf2e99bb92d782d59d01db975f686bffdb60e4bcbf98b524434fc27eb43f56946 - languageName: node - linkType: hard - "unbox-primitive@npm:^1.0.2": version: 1.0.2 resolution: "unbox-primitive@npm:1.0.2" @@ -27712,23 +27456,6 @@ __metadata: languageName: node linkType: hard -"z-schema@npm:~5.0.2": - version: 5.0.5 - resolution: "z-schema@npm:5.0.5" - dependencies: - commander: "npm:^9.4.1" - lodash.get: "npm:^4.4.2" - lodash.isequal: "npm:^4.5.0" - validator: "npm:^13.7.0" - dependenciesMeta: - commander: - optional: true - bin: - z-schema: bin/z-schema - checksum: 10/8ac2fa445f5a00e790d1f91a48aeff0ccfc340f84626771853e03f4d97cdc2f5f798cdb2e38418f7815ffc3aac3952c45caabcf077bf4f83fedf0cdef43b885b - languageName: node - linkType: hard - "zen-observable-ts@npm:^1.2.5": version: 1.2.5 resolution: "zen-observable-ts@npm:1.2.5" @@ -27752,6 +27479,13 @@ __metadata: languageName: node linkType: hard +"zod@npm:^3.23.8": + version: 3.23.8 + resolution: "zod@npm:3.23.8" + checksum: 10/846fd73e1af0def79c19d510ea9e4a795544a67d5b34b7e1c4d0425bf6bfd1c719446d94cdfa1721c1987d891321d61f779e8236fde517dc0e524aa851a6eff1 + languageName: node + linkType: hard + "zustand@npm:^4.1.5": version: 4.5.2 resolution: "zustand@npm:4.5.2"