Merge branch 'main' into viewer-redux-integration

This commit is contained in:
Dimitrie Stefanescu
2022-09-06 09:14:41 +01:00
committed by GitHub
257 changed files with 12669 additions and 4335 deletions
+7 -18
View File
@@ -10,27 +10,16 @@ fi
# enables building the test-deployment container with the same script
# defaults to packages for minimal intervention in the ci config
FOLDER="${FOLDER:-packages}"
SHOULD_PUBLISH="${SHOULD_PUBLISH:-false}"
DOCKER_IMAGE_TAG="speckle/speckle-${SPECKLE_SERVER_PACKAGE}"
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# shellcheck disable=SC1090,SC1091
source "${SCRIPT_DIR}/common.sh"
# IMAGE_VERSION_TAG=$(./.circleci/get_version.sh)
# if there is not image version tag, uses the SHA1 of the last git commit of the branch that triggered this build
IMAGE_VERSION_TAG="${IMAGE_VERSION_TAG:-${CIRCLE_SHA1}}"
echo "${IMAGE_VERSION_TAG}"
echo "Building image: ${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}"
export DOCKER_BUILDKIT=1
docker build --build-arg SPECKLE_SERVER_VERSION="${IMAGE_VERSION_TAG}" -t "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}" . --file "${FOLDER}/${SPECKLE_SERVER_PACKAGE}/Dockerfile"
docker build --build-arg SPECKLE_SERVER_VERSION="${IMAGE_VERSION_TAG}" --tag "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}" --file "${FOLDER}/${SPECKLE_SERVER_PACKAGE}/Dockerfile" .
if [[ "${SHOULD_PUBLISH}" == "true" ]]; then
echo "publishing images"
docker tag "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}" "${DOCKER_IMAGE_TAG}:latest"
if [[ "${IMAGE_VERSION_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
docker tag "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}" "${DOCKER_IMAGE_TAG}:2"
fi
echo "${DOCKER_REG_PASS}" | docker login -u "${DOCKER_REG_USER}" --password-stdin "${DOCKER_REG_URL}"
docker push --all-tags "${DOCKER_IMAGE_TAG}"
fi
echo " Saving image: ${DOCKER_FILE_NAME}"
docker save --output "/tmp/ci/workspace/${DOCKER_FILE_NAME}" "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}"
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -eo pipefail
DOCKER_IMAGE_TAG="speckle/speckle-${SPECKLE_SERVER_PACKAGE}"
IMAGE_VERSION_TAG="${IMAGE_VERSION_TAG:-${CIRCLE_SHA1}}"
# shellcheck disable=SC2034,SC2086
DOCKER_FILE_NAME="$(echo ${DOCKER_IMAGE_TAG}_${IMAGE_VERSION_TAG} | sed -e 's/[^A-Za-z0-9._-]/_/g')"
+174 -61
View File
@@ -23,9 +23,7 @@ workflows:
- pre-commit:
filters: *filters-everything
- docker-build-and-publish-server:
context: &docker-hub-context
- docker-hub
- docker-build-server:
filters: &filters-build
tags:
only: /.*/
@@ -33,59 +31,114 @@ workflows:
- test-server
- get-version
- should-build
- should-publish
- docker-build-and-publish-frontend:
context: *docker-hub-context
- docker-build-frontend:
filters: *filters-build
requires:
- get-version
- should-build
- should-publish
- docker-build-and-publish-webhooks:
context: *docker-hub-context
- docker-build-webhooks:
filters: *filters-build
requires:
- get-version
- test-server
- should-build
- should-publish
- docker-build-and-publish-file-imports:
context: *docker-hub-context
- docker-build-file-imports:
filters: *filters-build
requires:
- get-version
- test-server
- should-build
- should-publish
- docker-build-and-publish-previews:
context: *docker-hub-context
- docker-build-previews:
filters: *filters-build
requires:
- get-version
- test-server
- should-build
- should-publish
- docker-build-and-publish-test-container:
context: *docker-hub-context
- docker-build-test-container:
filters: *filters-build
requires:
- get-version
- test-server
- should-build
- should-publish
- docker-build-and-publish-monitor-container:
context: *docker-hub-context
- docker-build-monitor-container:
filters: *filters-build
requires:
- get-version
- should-build
- docker-publish-server:
context: &docker-hub-context
- docker-hub
filters: &filters-publish
branches:
ignore: /pull\/[0-9]+/
tags:
only: /.*/
requires:
- get-version
- should-publish
- docker-build-server
- pre-commit
- docker-publish-frontend:
context: *docker-hub-context
filters: *filters-publish
requires:
- get-version
- should-publish
- docker-build-frontend
- pre-commit
- docker-publish-webhooks:
context: *docker-hub-context
filters: *filters-publish
requires:
- get-version
- should-publish
- docker-build-webhooks
- pre-commit
- docker-publish-file-imports:
context: *docker-hub-context
filters: *filters-publish
requires:
- get-version
- should-publish
- docker-build-file-imports
- pre-commit
- docker-publish-previews:
context: *docker-hub-context
filters: *filters-publish
requires:
- get-version
- should-publish
- docker-build-previews
- pre-commit
- docker-publish-test-container:
context: *docker-hub-context
filters: *filters-publish
requires:
- get-version
- should-publish
- docker-build-test-container
- pre-commit
- docker-publish-monitor-container:
context: *docker-hub-context
filters: *filters-publish
requires:
- get-version
- should-publish
- docker-build-monitor-container
- pre-commit
- publish-helm-chart:
filters: &filters-publish
@@ -96,16 +149,15 @@ workflows:
tags:
only: &filters-tag /^[0-9]+\.[0-9]+\.[0-9]+$/
requires:
- test-server
- get-version
- should-publish
- docker-build-and-publish-server
- docker-build-and-publish-frontend
- docker-build-and-publish-webhooks
- docker-build-and-publish-file-imports
- docker-build-and-publish-previews
- docker-build-and-publish-monitor-container
- docker-build-and-publish-test-container
- docker-publish-server
- docker-publish-frontend
- docker-publish-webhooks
- docker-publish-file-imports
- docker-publish-previews
- docker-publish-monitor-container
- docker-publish-test-container
- publish-npm:
filters:
@@ -125,7 +177,6 @@ jobs:
working_directory: &work-dir /tmp/ci
steps:
- checkout
- run: pwd
- run: mkdir -p workspace
- run:
name: set version
@@ -142,13 +193,12 @@ jobs:
docker:
- image: cimg/base:2022.08
working_directory: *work-dir
environment:
# £ delimited strings of regex for matches which should be published
environment: &publishable-tags-branches
PUBLISHABLE_TAGS: '^[0-9]+\.[0-9]+\.[0-9]+$'
# £ delimited strings of regex for matches which should be published
PUBLISHABLE_BRANCHES: '^main$£^hotfix.*£^alpha.*'
steps:
- checkout
- run: pwd
- run: mkdir -p workspace
- run:
name: determine whether to publish
@@ -165,9 +215,9 @@ jobs:
docker:
- image: cimg/base:2022.08
working_directory: *work-dir
environment: *publishable-tags-branches
steps:
- checkout
- run: pwd
- run: mkdir -p workspace
- run:
name: determine whether to build
@@ -197,7 +247,7 @@ jobs:
type: string
docker:
- image: speckle/pre-commit-runner:latest
resource_class: large
resource_class: medium
working_directory: *work-dir
steps:
- checkout
@@ -259,6 +309,7 @@ jobs:
S3_SECRET_KEY: 'minioadmin'
S3_BUCKET: 'speckle-server'
S3_CREATE_BUCKET: 'true'
REDIS_URL: 'redis://localhost:6379'
S3_REGION: '' # optional, defaults to 'us-east-1'
steps:
- checkout
@@ -304,10 +355,10 @@ jobs:
path: packages/server/coverage/lcov-report
destination: package/server/coverage
docker-build-and-publish: &docker-job
docker-build: &build-job
docker: &docker-image
- image: cimg/node:16.15
resource_class: xlarge
resource_class: medium
working_directory: *work-dir
steps:
- checkout
@@ -315,7 +366,11 @@ jobs:
at: /tmp/ci/workspace
- run: cat workspace/env-vars >> $BASH_ENV
- run: cat workspace/should-build >> $BASH_ENV
- run: cat workspace/should-publish >> $BASH_ENV
- run:
name: 'Check if should proceed'
command: |
[[ "${SHOULD_BUILD}" != true ]] && echo "Should not build, stopping" && circleci-agent step halt
echo 'Proceeding with build'
- setup_remote_docker:
# a weird issue with yarn installing packages throwing EPERM errors
# this fixes it
@@ -324,40 +379,106 @@ jobs:
- run:
name: Build and Publish
command: ./.circleci/build.sh
- persist_to_workspace:
root: workspace
paths:
- speckle*
docker-build-and-publish-server:
<<: *docker-job
docker-build-server:
<<: *build-job
environment:
SPECKLE_SERVER_PACKAGE: server
docker-build-and-publish-frontend:
<<: *docker-job
docker-build-frontend:
<<: *build-job
environment:
SPECKLE_SERVER_PACKAGE: frontend
docker-build-and-publish-previews:
<<: *docker-job
docker-build-previews:
<<: *build-job
environment:
SPECKLE_SERVER_PACKAGE: preview-service
docker-build-and-publish-webhooks:
<<: *docker-job
docker-build-webhooks:
<<: *build-job
environment:
SPECKLE_SERVER_PACKAGE: webhook-service
docker-build-and-publish-file-imports:
<<: *docker-job
docker-build-file-imports:
<<: *build-job
environment:
SPECKLE_SERVER_PACKAGE: fileimport-service
docker-build-and-publish-test-container:
<<: *docker-job
docker-build-test-container:
<<: *build-job
environment:
FOLDER: utils
SPECKLE_SERVER_PACKAGE: test-deployment
docker-build-and-publish-monitor-container:
<<: *docker-job
docker-build-monitor-container:
<<: *build-job
environment:
FOLDER: utils
SPECKLE_SERVER_PACKAGE: monitor-deployment
docker-publish: &publish-job
docker: &base-image
- image: cimg/base:2022.08
resource_class: medium
working_directory: *work-dir
steps:
- checkout
- attach_workspace:
at: /tmp/ci/workspace
- run: cat workspace/env-vars >> $BASH_ENV
- run: cat workspace/should-publish >> $BASH_ENV
- run:
name: 'Check if should proceed'
command: |
[[ "${SHOULD_PUBLISH}" != true ]] && echo "Should not publish, stopping" && circleci-agent step halt
echo 'Proceeding with publish'
- setup_remote_docker:
# a weird issue with yarn installing packages throwing EPERM errors
# this fixes it
version: 20.10.12
docker_layer_caching: true
- run:
name: Publish
command: ./.circleci/publish.sh
docker-publish-server:
<<: *publish-job
environment:
SPECKLE_SERVER_PACKAGE: server
docker-publish-frontend:
<<: *publish-job
environment:
SPECKLE_SERVER_PACKAGE: frontend
docker-publish-previews:
<<: *publish-job
environment:
SPECKLE_SERVER_PACKAGE: preview-service
docker-publish-webhooks:
<<: *publish-job
environment:
SPECKLE_SERVER_PACKAGE: webhook-service
docker-publish-file-imports:
<<: *publish-job
environment:
SPECKLE_SERVER_PACKAGE: fileimport-service
docker-publish-test-container:
<<: *publish-job
environment:
FOLDER: utils
SPECKLE_SERVER_PACKAGE: test-deployment
docker-publish-monitor-container:
<<: *publish-job
environment:
FOLDER: utils
SPECKLE_SERVER_PACKAGE: monitor-deployment
@@ -389,7 +510,7 @@ jobs:
- run:
name: auth to npm as Speckle
command: |
echo "npmRegistryServer: https://registry.npmjs.org/" >> .yarnrc.yml
echo "npmRegistryServer: https://registry.npmjs.org/" >> .yarnrc.yml
echo "npmAuthToken: ${NPM_TOKEN}" >> .yarnrc.yml
- run:
name: try login to npm
@@ -407,14 +528,6 @@ jobs:
name: publish to npm
command: 'yarn workspaces foreach -pv --no-private npm publish --access public'
# - run:
# name: commit changes
# command: |
# yarn prettier:fix
# git add .
# git commit -m '[ci skip] bump version to $IMAGE_VERSION_TAG'
# git push
publish-helm-chart:
docker: *docker-image
working_directory: *work-dir
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -eo pipefail
SHOULD_PUBLISH="${SHOULD_PUBLISH:-false}"
if [[ "${SHOULD_PUBLISH}" != "true" ]]; then
echo "Not publishing as the SHOULD_PUBLISH environment variable is not 'true'."
exit 0
fi
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# shellcheck disable=SC1090,SC1091
source "${SCRIPT_DIR}/common.sh"
echo "Publishing: ${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}"
echo "💾 Loading image"
docker load --input "/tmp/ci/workspace/${DOCKER_FILE_NAME}"
echo "🐳 Publishing image"
docker tag "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}" "${DOCKER_IMAGE_TAG}:latest"
if [[ "${IMAGE_VERSION_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
docker tag "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}" "${DOCKER_IMAGE_TAG}:2"
fi
echo "${DOCKER_REG_PASS}" | docker login -u "${DOCKER_REG_USER}" --password-stdin "${DOCKER_REG_URL}"
docker push --all-tags "${DOCKER_IMAGE_TAG}"
+6
View File
@@ -1,6 +1,12 @@
#!/bin/bash
set -eo pipefail
IFS='£' read -r -a PUB_TAGS <<< "${PUBLISHABLE_TAGS}"
# shellcheck disable=SC2068
for item in ${PUB_TAGS[@]}; do
[[ "${CIRCLE_TAG}" =~ ${item} ]] && echo "true" && exit 0
done
# it's on the main branch
[[ "${CIRCLE_BRANCH}" == "main" ]] && echo "true" && exit 0
+2 -2
View File
@@ -1,3 +1,3 @@
matches-ignore:
name: MIXPANEL_TOKEN
match: acd87c5a50b56df91a795e999812a3a4
- name: MIXPANEL_TOKEN
match: acd87c5a50b56df91a795e999812a3a4
+5
View File
@@ -16,5 +16,10 @@ repos:
hooks:
- id: helmlint
- repo: https://github.com/syntaqx/git-hooks
rev: 'v0.0.17'
hooks:
- id: circleci-config-validate
ci:
autoupdate_schedule: quarterly
+2 -1
View File
@@ -21,6 +21,7 @@
"dev": "yarn workspaces foreach -piv -j unlimited run dev",
"dev:no-server": "yarn workspaces foreach --exclude @speckle/server -piv -j unlimited run dev",
"dev:minimal": "yarn workspaces foreach -piv -j unlimited --include '{@speckle/server,@speckle/frontend}' run dev",
"gqlgen": "yarn workspaces foreach -piv -j unlimited --include '{@speckle/server,@speckle/frontend}' run gqlgen",
"dev:server": "yarn workspace @speckle/server dev",
"dev:frontend": "yarn workspace @speckle/frontend dev",
"prepare": "husky install",
@@ -40,7 +41,7 @@
"resolutions": {
"tslib": "^2.3.1",
"core-js": "3.22.4",
"vue-cli-plugin-apollo/graphql": "^15",
"graphql": "^15",
"typescript": "^4.5.4",
"vue-loader": "^15.10.0"
},
+2 -1
View File
@@ -26,7 +26,8 @@ const config = {
extends: ['plugin:vue/recommended', '@vue/eslint-config-typescript', 'prettier'],
rules: {
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['error']
'@typescript-eslint/no-unused-vars': ['error'],
'vue/component-name-in-template-casing': ['warn', 'kebab-case']
}
},
{
+2
View File
@@ -15,3 +15,5 @@ generates:
config:
scalars:
JSONObject: Record<string, unknown>
DateTime: string
dedupeFragments: true
@@ -110,6 +110,9 @@ function createCache(): InMemoryCache {
},
pendingCollaborators: {
merge: incomingOverwritesExistingMergeFunction
},
pendingAccessRequests: {
merge: incomingOverwritesExistingMergeFunction
}
}
},
@@ -0,0 +1,32 @@
import { basicStreamAccessRequestFieldsFragment } from '@/graphql/fragments/accessRequests'
import { gql } from '@apollo/client/core'
export const getStreamAccessRequestQuery = gql`
query GetStreamAccessRequest($streamId: String!) {
streamAccessRequest(streamId: $streamId) {
...BasicStreamAccessRequestFields
}
}
${basicStreamAccessRequestFieldsFragment}
`
export const createStreamAccessRequestMutation = gql`
mutation CreateStreamAccessRequest($streamId: String!) {
streamAccessRequestCreate(streamId: $streamId) {
...BasicStreamAccessRequestFields
}
}
${basicStreamAccessRequestFieldsFragment}
`
export const useStreamAccessRequestMutation = gql`
mutation UseStreamAccessRequest(
$requestId: String!
$accept: Boolean!
$role: StreamRole = STREAM_CONTRIBUTOR
) {
streamAccessRequestUse(requestId: $requestId, accept: $accept, role: $role)
}
`
@@ -0,0 +1,22 @@
import { limitedUserFieldsFragment } from '@/graphql/fragments/user'
import { gql } from '@apollo/client/core'
export const basicStreamAccessRequestFieldsFragment = gql`
fragment BasicStreamAccessRequestFields on StreamAccessRequest {
id
streamId
createdAt
}
`
export const fullStreamAccessRequestFieldsFragment = gql`
fragment FullStreamAccessRequestFields on StreamAccessRequest {
...BasicStreamAccessRequestFields
requester {
...LimitedUserFields
}
}
${limitedUserFieldsFragment}
${basicStreamAccessRequestFieldsFragment}
`
@@ -0,0 +1,12 @@
import { fullStreamAccessRequestFieldsFragment } from '@/graphql/fragments/accessRequests'
import { gql } from '@apollo/client/core'
export const streamPendingAccessRequestsFragment = gql`
fragment StreamPendingAccessRequests on Stream {
pendingAccessRequests {
...FullStreamAccessRequestFields
}
}
${fullStreamAccessRequestFieldsFragment}
`
@@ -15,7 +15,7 @@ export type Scalars = {
/** The `BigInt` scalar type represents non-fractional signed whole numeric values. */
BigInt: any;
/** A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */
DateTime: any;
DateTime: string;
EmailAddress: any;
/** The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
JSONObject: Record<string, unknown>;
@@ -393,6 +393,16 @@ export type CommitUpdateInput = {
streamId: Scalars['String'];
};
export enum DiscoverableStreamsSortType {
CreatedDate = 'CREATED_DATE',
FavoritesCount = 'FAVORITES_COUNT'
}
export type DiscoverableStreamsSortingInput = {
direction: SortDirection;
type: DiscoverableStreamsSortType;
};
export type FileUpload = {
__typename?: 'FileUpload';
branchName?: Maybe<Scalars['String']>;
@@ -467,10 +477,16 @@ export type Mutation = {
/** Re-send a pending invite */
inviteResend: Scalars['Boolean'];
objectCreate: Array<Maybe<Scalars['String']>>;
/** (Re-)send the account verification e-mail */
requestVerification: Scalars['Boolean'];
serverInfoUpdate?: Maybe<Scalars['Boolean']>;
serverInviteBatchCreate: Scalars['Boolean'];
/** Invite a new user to the speckle server and return the invite ID */
serverInviteCreate: Scalars['Boolean'];
/** Request access to a specific stream */
streamAccessRequestCreate: StreamAccessRequest;
/** Accept or decline a stream access request. Must be a stream owner to invoke this. */
streamAccessRequestUse: Scalars['Boolean'];
/** Creates a new stream. */
streamCreate?: Maybe<Scalars['String']>;
/** Deletes an existing stream. */
@@ -639,6 +655,18 @@ export type MutationServerInviteCreateArgs = {
};
export type MutationStreamAccessRequestCreateArgs = {
streamId: Scalars['String'];
};
export type MutationStreamAccessRequestUseArgs = {
accept: Scalars['Boolean'];
requestId: Scalars['String'];
role?: StreamRole;
};
export type MutationStreamCreateArgs = {
stream: StreamCreateInput;
};
@@ -797,6 +825,27 @@ export type ObjectCreateInput = {
streamId: Scalars['String'];
};
export type PasswordStrengthCheckFeedback = {
__typename?: 'PasswordStrengthCheckFeedback';
suggestions: Array<Scalars['String']>;
warning?: Maybe<Scalars['String']>;
};
export type PasswordStrengthCheckResults = {
__typename?: 'PasswordStrengthCheckResults';
/** Verbal feedback to help choose better passwords. set when score <= 2. */
feedback: PasswordStrengthCheckFeedback;
/**
* Integer from 0-4 (useful for implementing a strength bar):
* 0 too guessable: risky password. (guesses < 10^3)
* 1 very guessable: protection from throttled online attacks. (guesses < 10^6)
* 2 somewhat guessable: protection from unthrottled online attacks. (guesses < 10^8)
* 3 safely unguessable: moderate protection from offline slow-hash scenario. (guesses < 10^10)
* 4 very unguessable: strong protection from offline slow-hash scenario. (guesses >= 10^10)
*/
score: Scalars['Int'];
};
export type PendingStreamCollaborator = {
__typename?: 'PendingStreamCollaborator';
id: Scalars['String'];
@@ -817,6 +866,7 @@ export type Query = {
__typename?: 'Query';
/** Stare into the void. */
_?: Maybe<Scalars['String']>;
/** All the streams of the server. Available to admins only. */
adminStreams?: Maybe<StreamCollection>;
/**
* Get all (or search for specific) users, registered or invited, from the server in a paginated view.
@@ -836,6 +886,8 @@ export type Query = {
comments?: Maybe<CommentCollection>;
/** Commit/Object viewer state (local-only) */
commitObjectViewerState: CommitObjectViewerState;
/** All of the discoverable streams of the server */
discoverableStreams?: Maybe<StreamCollection>;
serverInfo: ServerInfo;
serverStats: ServerStats;
/**
@@ -843,6 +895,8 @@ export type Query = {
* to see it.
*/
stream?: Maybe<Stream>;
/** Get authed user's stream access request */
streamAccessRequest?: Maybe<StreamAccessRequest>;
/**
* Look for an invitation to a stream, for the current user (authed or not). If token
* isn't specified, the server will look for any valid invite.
@@ -852,12 +906,10 @@ export type Query = {
streamInvites: Array<PendingStreamCollaborator>;
/** All the streams of the current user, pass in the `query` parameter to search by name, description or ID. */
streams?: Maybe<StreamCollection>;
/**
* Gets the profile of a user. If no id argument is provided, will return the current authenticated user's profile (as extracted from the authorization header).
* If ID is provided, admin access is required
*/
/** Gets the profile of a user. If no id argument is provided, will return the current authenticated user's profile (as extracted from the authorization header). */
user?: Maybe<User>;
userPwdStrength?: Maybe<Scalars['JSONObject']>;
/** Validate password strength */
userPwdStrength: PasswordStrengthCheckResults;
/**
* Search for users and return limited metadata about them, if you have the server:user role.
* The query looks for matches in name & email
@@ -902,11 +954,23 @@ export type QueryCommentsArgs = {
};
export type QueryDiscoverableStreamsArgs = {
cursor?: InputMaybe<Scalars['String']>;
limit?: Scalars['Int'];
sort?: InputMaybe<DiscoverableStreamsSortingInput>;
};
export type QueryStreamArgs = {
id: Scalars['String'];
};
export type QueryStreamAccessRequestArgs = {
streamId: Scalars['String'];
};
export type QueryStreamInviteArgs = {
streamId: Scalars['String'];
token?: InputMaybe<Scalars['String']>;
@@ -1084,6 +1148,11 @@ export type SmartTextEditorValue = {
version: Scalars['String'];
};
export enum SortDirection {
Asc = 'ASC',
Desc = 'DESC'
}
export type Stream = {
__typename?: 'Stream';
/** All the recent activity on this stream in chronological order */
@@ -1119,9 +1188,17 @@ export type Stream = {
/** Returns a list of all the file uploads for this stream. */
fileUploads?: Maybe<Array<Maybe<FileUpload>>>;
id: Scalars['String'];
/**
* Whether the stream (if public) can be found on public stream exploration pages
* and searches
*/
isDiscoverable: Scalars['Boolean'];
/** Whether the stream can be viewed by non-contributors */
isPublic: Scalars['Boolean'];
name: Scalars['String'];
object?: Maybe<Object>;
/** Pending stream access requests */
pendingAccessRequests?: Maybe<Array<StreamAccessRequest>>;
/** Collaborators who have been invited, but not yet accepted. */
pendingCollaborators?: Maybe<Array<PendingStreamCollaborator>>;
/** Your role for this stream. `null` if request is not authenticated, or the stream is not explicitly shared with you. */
@@ -1189,6 +1266,18 @@ export type StreamWebhooksArgs = {
id?: InputMaybe<Scalars['String']>;
};
/** Created when a user requests to become a contributor on a stream */
export type StreamAccessRequest = {
__typename?: 'StreamAccessRequest';
createdAt: Scalars['DateTime'];
id: Scalars['ID'];
requester: LimitedUser;
requesterId: Scalars['String'];
/** Can only be selected if authed user has proper access */
stream: Stream;
streamId: Scalars['String'];
};
export type StreamCollaborator = {
__typename?: 'StreamCollaborator';
avatar?: Maybe<Scalars['String']>;
@@ -1207,6 +1296,12 @@ export type StreamCollection = {
export type StreamCreateInput = {
description?: InputMaybe<Scalars['String']>;
/**
* Whether the stream (if public) can be found on public stream exploration pages
* and searches
*/
isDiscoverable?: InputMaybe<Scalars['Boolean']>;
/** Whether the stream can be viewed by non-contributors */
isPublic?: InputMaybe<Scalars['Boolean']>;
name?: InputMaybe<Scalars['String']>;
/** Optionally specify user IDs of users that you want to invite to be contributors to this stream */
@@ -1237,6 +1332,12 @@ export type StreamUpdateInput = {
allowPublicComments?: InputMaybe<Scalars['Boolean']>;
description?: InputMaybe<Scalars['String']>;
id: Scalars['String'];
/**
* Whether the stream (if public) can be found on public stream exploration pages
* and searches
*/
isDiscoverable?: InputMaybe<Scalars['Boolean']>;
/** Whether the stream can be viewed by non-contributors */
isPublic?: InputMaybe<Scalars['Boolean']>;
name?: InputMaybe<Scalars['String']>;
};
@@ -1378,13 +1479,14 @@ export type User = {
email?: Maybe<Scalars['String']>;
/** All the streams that a user has favorited */
favoriteStreams?: Maybe<StreamCollection>;
/** Whether the user has a pending/active email verification token */
hasPendingVerification?: Maybe<Scalars['Boolean']>;
id: Scalars['String'];
name?: Maybe<Scalars['String']>;
profiles?: Maybe<Scalars['JSONObject']>;
role?: Maybe<Scalars['String']>;
/** All the streams that a user has access to. */
streams?: Maybe<StreamCollection>;
suuid?: Maybe<Scalars['String']>;
timeline?: Maybe<ActivityCollection>;
/** Total amount of favorites attached to streams owned by the user */
totalOwnedStreamsFavorites: Scalars['Int'];
@@ -1531,6 +1633,29 @@ export type WebhookUpdateInput = {
url?: InputMaybe<Scalars['String']>;
};
export type GetStreamAccessRequestQueryVariables = Exact<{
streamId: Scalars['String'];
}>;
export type GetStreamAccessRequestQuery = { __typename?: 'Query', streamAccessRequest?: { __typename?: 'StreamAccessRequest', id: string, streamId: string, createdAt: string } | null };
export type CreateStreamAccessRequestMutationVariables = Exact<{
streamId: Scalars['String'];
}>;
export type CreateStreamAccessRequestMutation = { __typename?: 'Mutation', streamAccessRequestCreate: { __typename?: 'StreamAccessRequest', id: string, streamId: string, createdAt: string } };
export type UseStreamAccessRequestMutationVariables = Exact<{
requestId: Scalars['String'];
accept: Scalars['Boolean'];
role?: InputMaybe<StreamRole>;
}>;
export type UseStreamAccessRequestMutation = { __typename?: 'Mutation', streamAccessRequestUse: boolean };
export type StreamWithBranchQueryVariables = Exact<{
streamId: Scalars['String'];
branchName: Scalars['String'];
@@ -1538,7 +1663,7 @@ export type StreamWithBranchQueryVariables = Exact<{
}>;
export type StreamWithBranchQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, branch?: { __typename?: 'Branch', id: string, name: string, description?: string | null, commits?: { __typename?: 'CommitCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Commit', id: string, authorName?: string | null, authorId?: string | null, authorAvatar?: string | null, sourceApplication?: string | null, message?: string | null, referencedObject: string, createdAt?: any | null, commentCount: number } | null> | null } | null } | null } | null };
export type StreamWithBranchQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, branch?: { __typename?: 'Branch', id: string, name: string, description?: string | null, commits?: { __typename?: 'CommitCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Commit', id: string, authorName?: string | null, authorId?: string | null, authorAvatar?: string | null, sourceApplication?: string | null, message?: string | null, referencedObject: string, createdAt?: string | null, commentCount: number } | null> | null } | null } | null } | null };
export type BranchCreatedSubscriptionVariables = Exact<{
streamId: Scalars['String'];
@@ -1547,7 +1672,7 @@ export type BranchCreatedSubscriptionVariables = Exact<{
export type BranchCreatedSubscription = { __typename?: 'Subscription', branchCreated?: Record<string, unknown> | null };
export type CommentFullInfoFragment = { __typename?: 'Comment', id: string, archived: boolean, authorId: string, data?: Record<string, unknown> | null, screenshot?: string | null, createdAt?: any | null, updatedAt?: any | null, viewedAt?: any | null, text: { __typename?: 'SmartTextEditorValue', doc?: Record<string, unknown> | null, attachments?: Array<{ __typename?: 'BlobMetadata', id: string, fileName: string, streamId: string, fileType: string, fileSize?: number | null }> | null }, replies?: { __typename?: 'CommentCollection', totalCount: number } | null, resources: Array<{ __typename?: 'ResourceIdentifier', resourceId: string, resourceType: ResourceType } | null> };
export type CommentFullInfoFragment = { __typename?: 'Comment', id: string, archived: boolean, authorId: string, data?: Record<string, unknown> | null, screenshot?: string | null, createdAt?: string | null, updatedAt?: string | null, viewedAt?: string | null, text: { __typename?: 'SmartTextEditorValue', doc?: Record<string, unknown> | null, attachments?: Array<{ __typename?: 'BlobMetadata', id: string, fileName: string, streamId: string, fileType: string, fileSize?: number | null }> | null }, replies?: { __typename?: 'CommentCollection', totalCount: number } | null, resources: Array<{ __typename?: 'ResourceIdentifier', resourceId: string, resourceType: ResourceType } | null> };
export type StreamCommitQueryQueryVariables = Exact<{
streamId: Scalars['String'];
@@ -1555,11 +1680,17 @@ export type StreamCommitQueryQueryVariables = Exact<{
}>;
export type StreamCommitQueryQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, role?: string | null, commit?: { __typename?: 'Commit', id: string, message?: string | null, referencedObject: string, authorName?: string | null, authorId?: string | null, authorAvatar?: string | null, createdAt?: any | null, branchName?: string | null, sourceApplication?: string | null } | null } | null };
export type StreamCommitQueryQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, role?: string | null, commit?: { __typename?: 'Commit', id: string, message?: string | null, referencedObject: string, authorName?: string | null, authorId?: string | null, authorAvatar?: string | null, createdAt?: string | null, branchName?: string | null, sourceApplication?: string | null } | null } | null };
export type ActivityMainFieldsFragment = { __typename?: 'Activity', id: string, actionType: string, info: Record<string, unknown>, userId: string, streamId?: string | null, resourceId: string, resourceType: string, time: any, message: string };
export type BasicStreamAccessRequestFieldsFragment = { __typename?: 'StreamAccessRequest', id: string, streamId: string, createdAt: string };
export type LimitedCommitActivityFieldsFragment = { __typename?: 'Activity', id: string, info: Record<string, unknown>, time: any, userId: string, message: string };
export type FullStreamAccessRequestFieldsFragment = { __typename?: 'StreamAccessRequest', id: string, streamId: string, createdAt: string, requester: { __typename?: 'LimitedUser', id: string, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null } };
export type ActivityMainFieldsFragment = { __typename?: 'Activity', id: string, actionType: string, info: Record<string, unknown>, userId: string, streamId?: string | null, resourceId: string, resourceType: string, time: string, message: string };
export type LimitedCommitActivityFieldsFragment = { __typename?: 'Activity', id: string, info: Record<string, unknown>, time: string, userId: string, message: string };
export type StreamPendingAccessRequestsFragment = { __typename?: 'Stream', pendingAccessRequests?: Array<{ __typename?: 'StreamAccessRequest', id: string, streamId: string, createdAt: string, requester: { __typename?: 'LimitedUser', id: string, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null } }> | null };
export type LimitedUserFieldsFragment = { __typename?: 'LimitedUser', id: string, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null };
@@ -1669,30 +1800,30 @@ export type StreamCommitsQueryVariables = Exact<{
}>;
export type StreamCommitsQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, role?: string | null, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, authorId?: string | null, authorName?: string | null, authorAvatar?: string | null, createdAt?: any | null, message?: string | null, referencedObject: string, branchName?: string | null, sourceApplication?: string | null } | null> | null } | null } | null };
export type StreamCommitsQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, role?: string | null, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, authorId?: string | null, authorName?: string | null, authorAvatar?: string | null, createdAt?: string | null, message?: string | null, referencedObject: string, branchName?: string | null, sourceApplication?: string | null } | null> | null } | null } | null };
export type StreamsQueryVariables = Exact<{
cursor?: InputMaybe<Scalars['String']>;
}>;
export type StreamsQuery = { __typename?: 'Query', streams?: { __typename?: 'StreamCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: any, updatedAt: any, commentCount: number, favoritedDate?: any | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, company?: string | null, avatar?: string | null, role: string }>, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, createdAt?: any | null, message?: string | null, authorId?: string | null, branchName?: string | null, authorName?: string | null, authorAvatar?: string | null, referencedObject: string } | null> | null } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null }> | null } | null };
export type StreamsQuery = { __typename?: 'Query', streams?: { __typename?: 'StreamCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: string, updatedAt: string, commentCount: number, favoritedDate?: string | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, company?: string | null, avatar?: string | null, role: string }>, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, createdAt?: string | null, message?: string | null, authorId?: string | null, branchName?: string | null, authorName?: string | null, authorAvatar?: string | null, referencedObject: string } | null> | null } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null }> | null } | null };
export type CommonStreamFieldsFragment = { __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: any, updatedAt: any, commentCount: number, favoritedDate?: any | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, company?: string | null, avatar?: string | null, role: string }>, commits?: { __typename?: 'CommitCollection', totalCount: number } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null };
export type CommonStreamFieldsFragment = { __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: string, updatedAt: string, commentCount: number, favoritedDate?: string | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, company?: string | null, avatar?: string | null, role: string }>, commits?: { __typename?: 'CommitCollection', totalCount: number } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null };
export type StreamQueryVariables = Exact<{
id: Scalars['String'];
}>;
export type StreamQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: any, updatedAt: any, commentCount: number, favoritedDate?: any | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, company?: string | null, avatar?: string | null, role: string }>, commits?: { __typename?: 'CommitCollection', totalCount: number } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null } | null };
export type StreamQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: string, updatedAt: string, commentCount: number, favoritedDate?: string | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, company?: string | null, avatar?: string | null, role: string }>, commits?: { __typename?: 'CommitCollection', totalCount: number } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null } | null };
export type StreamWithCollaboratorsQueryVariables = Exact<{
id: Scalars['String'];
}>;
export type StreamWithCollaboratorsQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, isPublic: boolean, role?: string | null, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, role: string, company?: string | null, avatar?: string | null }>, pendingCollaborators?: Array<{ __typename?: 'PendingStreamCollaborator', title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', id: string, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null } | null }> | null } | null };
export type StreamWithCollaboratorsQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, isPublic: boolean, role?: string | null, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, role: string, company?: string | null, avatar?: string | null }>, pendingCollaborators?: Array<{ __typename?: 'PendingStreamCollaborator', title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', id: string, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null } | null }> | null, pendingAccessRequests?: Array<{ __typename?: 'StreamAccessRequest', id: string, streamId: string, createdAt: string, requester: { __typename?: 'LimitedUser', id: string, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null } }> | null } | null };
export type StreamWithActivityQueryVariables = Exact<{
id: Scalars['String'];
@@ -1700,7 +1831,7 @@ export type StreamWithActivityQueryVariables = Exact<{
}>;
export type StreamWithActivityQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, createdAt: any, commits?: { __typename?: 'CommitCollection', totalCount: number } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null, activity?: { __typename?: 'ActivityCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Activity', id: string, actionType: string, info: Record<string, unknown>, userId: string, streamId?: string | null, resourceId: string, resourceType: string, time: any, message: string } | null> | null } | null } | null };
export type StreamWithActivityQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, createdAt: string, commits?: { __typename?: 'CommitCollection', totalCount: number } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null, activity?: { __typename?: 'ActivityCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Activity', id: string, actionType: string, info: Record<string, unknown>, userId: string, streamId?: string | null, resourceId: string, resourceType: string, time: string, message: string } | null> | null } | null } | null };
export type LeaveStreamMutationVariables = Exact<{
streamId: Scalars['String'];
@@ -1731,24 +1862,24 @@ export type StreamBranchFirstCommitQueryVariables = Exact<{
export type StreamBranchFirstCommitQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, branch?: { __typename?: 'Branch', commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, referencedObject: string } | null> | null } | null } | null } | null };
export type CommonUserFieldsFragment = { __typename?: 'User', id: string, suuid?: string | null, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, streams?: { __typename?: 'StreamCollection', totalCount: number } | null, commits?: { __typename?: 'CommitCollectionUser', totalCount: number, items?: Array<{ __typename?: 'CommitCollectionUserNode', id: string, createdAt?: any | null } | null> | null } | null };
export type CommonUserFieldsFragment = { __typename?: 'User', id: string, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, hasPendingVerification?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, streams?: { __typename?: 'StreamCollection', totalCount: number } | null, commits?: { __typename?: 'CommitCollectionUser', totalCount: number, items?: Array<{ __typename?: 'CommitCollectionUserNode', id: string, createdAt?: string | null } | null> | null } | null };
export type UserFavoriteStreamsQueryVariables = Exact<{
cursor?: InputMaybe<Scalars['String']>;
}>;
export type UserFavoriteStreamsQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, suuid?: string | null, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, favoriteStreams?: { __typename?: 'StreamCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: any, updatedAt: any, commentCount: number, favoritedDate?: any | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, company?: string | null, avatar?: string | null, role: string }>, commits?: { __typename?: 'CommitCollection', totalCount: number } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null }> | null } | null, streams?: { __typename?: 'StreamCollection', totalCount: number } | null, commits?: { __typename?: 'CommitCollectionUser', totalCount: number, items?: Array<{ __typename?: 'CommitCollectionUserNode', id: string, createdAt?: any | null } | null> | null } | null } | null };
export type UserFavoriteStreamsQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, hasPendingVerification?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, favoriteStreams?: { __typename?: 'StreamCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: string, updatedAt: string, commentCount: number, favoritedDate?: string | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, company?: string | null, avatar?: string | null, role: string }>, commits?: { __typename?: 'CommitCollection', totalCount: number } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null }> | null } | null, streams?: { __typename?: 'StreamCollection', totalCount: number } | null, commits?: { __typename?: 'CommitCollectionUser', totalCount: number, items?: Array<{ __typename?: 'CommitCollectionUserNode', id: string, createdAt?: string | null } | null> | null } | null } | null };
export type MainUserDataQueryVariables = Exact<{ [key: string]: never; }>;
export type MainUserDataQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, suuid?: string | null, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, streams?: { __typename?: 'StreamCollection', totalCount: number } | null, commits?: { __typename?: 'CommitCollectionUser', totalCount: number, items?: Array<{ __typename?: 'CommitCollectionUserNode', id: string, createdAt?: any | null } | null> | null } | null } | null };
export type MainUserDataQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, hasPendingVerification?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, streams?: { __typename?: 'StreamCollection', totalCount: number } | null, commits?: { __typename?: 'CommitCollectionUser', totalCount: number, items?: Array<{ __typename?: 'CommitCollectionUserNode', id: string, createdAt?: string | null } | null> | null } | null } | null };
export type ExtraUserDataQueryVariables = Exact<{ [key: string]: never; }>;
export type ExtraUserDataQuery = { __typename?: 'Query', user?: { __typename?: 'User', totalOwnedStreamsFavorites: number, id: string, suuid?: string | null, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, streams?: { __typename?: 'StreamCollection', totalCount: number } | null, commits?: { __typename?: 'CommitCollectionUser', totalCount: number, items?: Array<{ __typename?: 'CommitCollectionUserNode', id: string, createdAt?: any | null } | null> | null } | null } | null };
export type ExtraUserDataQuery = { __typename?: 'Query', user?: { __typename?: 'User', totalOwnedStreamsFavorites: number, id: string, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, hasPendingVerification?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, streams?: { __typename?: 'StreamCollection', totalCount: number } | null, commits?: { __typename?: 'CommitCollectionUser', totalCount: number, items?: Array<{ __typename?: 'CommitCollectionUserNode', id: string, createdAt?: string | null } | null> | null } | null } | null };
export type UserSearchQueryVariables = Exact<{
query: Scalars['String'];
@@ -1772,21 +1903,38 @@ export type AdminUsersListQueryVariables = Exact<{
}>;
export type AdminUsersListQuery = { __typename?: 'Query', adminUsers?: { __typename?: 'AdminUsersListCollection', totalCount: number, items: Array<{ __typename?: 'AdminUsersListItem', id: string, registeredUser?: { __typename?: 'User', id: string, suuid?: string | null, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, authorizedApps?: Array<{ __typename?: 'ServerAppListItem', name: string } | null> | null } | null, invitedUser?: { __typename?: 'ServerInvite', id: string, email: string, invitedBy: { __typename?: 'LimitedUser', id: string, name?: string | null } } | null }> } | null };
export type AdminUsersListQuery = { __typename?: 'Query', adminUsers?: { __typename?: 'AdminUsersListCollection', totalCount: number, items: Array<{ __typename?: 'AdminUsersListItem', id: string, registeredUser?: { __typename?: 'User', id: string, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, authorizedApps?: Array<{ __typename?: 'ServerAppListItem', name: string } | null> | null } | null, invitedUser?: { __typename?: 'ServerInvite', id: string, email: string, invitedBy: { __typename?: 'LimitedUser', id: string, name?: string | null } } | null }> } | null };
export type UserTimelineQueryVariables = Exact<{
cursor?: InputMaybe<Scalars['DateTime']>;
}>;
export type UserTimelineQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, timeline?: { __typename?: 'ActivityCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Activity', id: string, actionType: string, info: Record<string, unknown>, userId: string, streamId?: string | null, resourceId: string, resourceType: string, time: any, message: string } | null> | null } | null } | null };
export type UserTimelineQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, timeline?: { __typename?: 'ActivityCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Activity', id: string, actionType: string, info: Record<string, unknown>, userId: string, streamId?: string | null, resourceId: string, resourceType: string, time: string, message: string } | null> | null } | null } | null };
export type UserQueryVariables = Exact<{
export type ValidatePasswordStrengthQueryVariables = Exact<{
pwd: Scalars['String'];
}>;
export type ValidatePasswordStrengthQuery = { __typename?: 'Query', userPwdStrength: { __typename?: 'PasswordStrengthCheckResults', score: number, feedback: { __typename?: 'PasswordStrengthCheckFeedback', warning?: string | null, suggestions: Array<string> } } };
export type EmailVerificationBannerStateQueryVariables = Exact<{ [key: string]: never; }>;
export type EmailVerificationBannerStateQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, email?: string | null, verified?: boolean | null, hasPendingVerification?: boolean | null } | null };
export type RequestVerificationMutationVariables = Exact<{ [key: string]: never; }>;
export type RequestVerificationMutation = { __typename?: 'Mutation', requestVerification: boolean };
export type UserByIdQueryVariables = Exact<{
id: Scalars['String'];
}>;
export type UserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, suuid?: string | null } | null };
export type UserByIdQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null } | null };
export type UserProfileQueryVariables = Exact<{
id: Scalars['String'];
@@ -1808,7 +1956,7 @@ export type WebhooksQueryVariables = Exact<{
}>;
export type WebhooksQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, role?: string | null, webhooks?: { __typename?: 'WebhookCollection', items?: Array<{ __typename?: 'Webhook', id: string, streamId: string, url: string, description?: string | null, triggers: Array<string | null>, enabled?: boolean | null, history?: { __typename?: 'WebhookEventCollection', items?: Array<{ __typename?: 'WebhookEvent', status: number, statusInfo: string, lastUpdate: any } | null> | null } | null } | null> | null } | null } | null };
export type WebhooksQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, role?: string | null, webhooks?: { __typename?: 'WebhookCollection', items?: Array<{ __typename?: 'Webhook', id: string, streamId: string, url: string, description?: string | null, triggers: Array<string | null>, enabled?: boolean | null, history?: { __typename?: 'WebhookEventCollection', items?: Array<{ __typename?: 'WebhookEvent', status: number, statusInfo: string, lastUpdate: string } | null> | null } | null } | null> | null } | null } | null };
export const CommentFullInfo = gql`
fragment CommentFullInfo on Comment {
@@ -1861,13 +2009,11 @@ export const LimitedCommitActivityFields = gql`
message
}
`;
export const StreamCollaboratorFields = gql`
fragment StreamCollaboratorFields on StreamCollaborator {
export const BasicStreamAccessRequestFields = gql`
fragment BasicStreamAccessRequestFields on StreamAccessRequest {
id
name
role
company
avatar
streamId
createdAt
}
`;
export const LimitedUserFields = gql`
@@ -1880,6 +2026,30 @@ export const LimitedUserFields = gql`
verified
}
`;
export const FullStreamAccessRequestFields = gql`
fragment FullStreamAccessRequestFields on StreamAccessRequest {
...BasicStreamAccessRequestFields
requester {
...LimitedUserFields
}
}
`;
export const StreamPendingAccessRequests = gql`
fragment StreamPendingAccessRequests on Stream {
pendingAccessRequests {
...FullStreamAccessRequestFields
}
}
`;
export const StreamCollaboratorFields = gql`
fragment StreamCollaboratorFields on StreamCollaborator {
id
name
role
company
avatar
}
`;
export const UsersOwnInviteFields = gql`
fragment UsersOwnInviteFields on PendingStreamCollaborator {
id
@@ -1891,7 +2061,7 @@ export const UsersOwnInviteFields = gql`
...LimitedUserFields
}
}
${LimitedUserFields}`;
`;
export const ServerInfoBlobSizeFields = gql`
fragment ServerInfoBlobSizeFields on ServerInfo {
blobSizeLimitBytes
@@ -1956,16 +2126,15 @@ export const CommonStreamFields = gql`
export const CommonUserFields = gql`
fragment CommonUserFields on User {
id
suuid
email
name
bio
company
avatar
verified
hasPendingVerification
profiles
role
suuid
streams {
totalCount
}
@@ -1978,6 +2147,25 @@ export const CommonUserFields = gql`
}
}
`;
export const GetStreamAccessRequest = gql`
query GetStreamAccessRequest($streamId: String!) {
streamAccessRequest(streamId: $streamId) {
...BasicStreamAccessRequestFields
}
}
${BasicStreamAccessRequestFields}`;
export const CreateStreamAccessRequest = gql`
mutation CreateStreamAccessRequest($streamId: String!) {
streamAccessRequestCreate(streamId: $streamId) {
...BasicStreamAccessRequestFields
}
}
${BasicStreamAccessRequestFields}`;
export const UseStreamAccessRequest = gql`
mutation UseStreamAccessRequest($requestId: String!, $accept: Boolean!, $role: StreamRole = STREAM_CONTRIBUTOR) {
streamAccessRequestUse(requestId: $requestId, accept: $accept, role: $role)
}
`;
export const StreamWithBranch = gql`
query StreamWithBranch($streamId: String!, $branchName: String!, $cursor: String) {
stream(id: $streamId) {
@@ -2037,14 +2225,16 @@ export const StreamInvite = gql`
...UsersOwnInviteFields
}
}
${UsersOwnInviteFields}`;
${UsersOwnInviteFields}
${LimitedUserFields}`;
export const UserStreamInvites = gql`
query UserStreamInvites {
streamInvites {
...UsersOwnInviteFields
}
}
${UsersOwnInviteFields}`;
${UsersOwnInviteFields}
${LimitedUserFields}`;
export const UseStreamInvite = gql`
mutation UseStreamInvite($accept: Boolean!, $streamId: String!, $token: String!) {
streamInviteUse(accept: $accept, streamId: $streamId, token: $token)
@@ -2218,10 +2408,15 @@ export const StreamWithCollaborators = gql`
...LimitedUserFields
}
}
pendingAccessRequests {
...FullStreamAccessRequestFields
}
}
}
${StreamCollaboratorFields}
${LimitedUserFields}`;
${LimitedUserFields}
${FullStreamAccessRequestFields}
${BasicStreamAccessRequestFields}`;
export const StreamWithActivity = gql`
query StreamWithActivity($id: String!, $cursor: DateTime) {
stream(id: $id) {
@@ -2339,7 +2534,6 @@ export const AdminUsersList = gql`
id
registeredUser {
id
suuid
email
name
bio
@@ -2378,8 +2572,34 @@ export const UserTimeline = gql`
}
}
${ActivityMainFields}`;
export const User = gql`
query User($id: String!) {
export const ValidatePasswordStrength = gql`
query ValidatePasswordStrength($pwd: String!) {
userPwdStrength(pwd: $pwd) {
score
feedback {
warning
suggestions
}
}
}
`;
export const EmailVerificationBannerState = gql`
query EmailVerificationBannerState {
user {
id
email
verified
hasPendingVerification
}
}
`;
export const RequestVerification = gql`
mutation RequestVerification {
requestVerification
}
`;
export const UserById = gql`
query UserById($id: String!) {
user(id: $id) {
id
email
@@ -2390,7 +2610,6 @@ export const User = gql`
verified
profiles
role
suuid
}
}
`;
@@ -2459,20 +2678,26 @@ export const Webhooks = gql`
export const CommentFullInfoFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CommentFullInfo"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Comment"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"archived"}},{"kind":"Field","name":{"kind":"Name","value":"authorId"}},{"kind":"Field","name":{"kind":"Name","value":"text"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"doc"}},{"kind":"Field","name":{"kind":"Name","value":"attachments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fileName"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"fileType"}},{"kind":"Field","name":{"kind":"Name","value":"fileSize"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"data"}},{"kind":"Field","name":{"kind":"Name","value":"screenshot"}},{"kind":"Field","name":{"kind":"Name","value":"replies"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"resources"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"resourceId"}},{"kind":"Field","name":{"kind":"Name","value":"resourceType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"viewedAt"}}]}}]} as unknown as DocumentNode<CommentFullInfoFragment, unknown>;
export const ActivityMainFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ActivityMainFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Activity"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"actionType"}},{"kind":"Field","name":{"kind":"Name","value":"info"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"resourceId"}},{"kind":"Field","name":{"kind":"Name","value":"resourceType"}},{"kind":"Field","name":{"kind":"Name","value":"time"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]} as unknown as DocumentNode<ActivityMainFieldsFragment, unknown>;
export const LimitedCommitActivityFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedCommitActivityFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Activity"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"info"}},{"kind":"Field","name":{"kind":"Name","value":"time"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]} as unknown as DocumentNode<LimitedCommitActivityFieldsFragment, unknown>;
export const StreamCollaboratorFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"StreamCollaboratorFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StreamCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}}]} as unknown as DocumentNode<StreamCollaboratorFieldsFragment, unknown>;
export const BasicStreamAccessRequestFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StreamAccessRequest"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<BasicStreamAccessRequestFieldsFragment, unknown>;
export const LimitedUserFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}}]}}]} as unknown as DocumentNode<LimitedUserFieldsFragment, unknown>;
export const UsersOwnInviteFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UsersOwnInviteFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PendingStreamCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"streamName"}},{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"invitedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserFields"}}]}}]}},...LimitedUserFieldsFragmentDoc.definitions]} as unknown as DocumentNode<UsersOwnInviteFieldsFragment, unknown>;
export const FullStreamAccessRequestFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FullStreamAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StreamAccessRequest"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"}},{"kind":"Field","name":{"kind":"Name","value":"requester"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserFields"}}]}}]}}]} as unknown as DocumentNode<FullStreamAccessRequestFieldsFragment, unknown>;
export const StreamPendingAccessRequestsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"StreamPendingAccessRequests"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Stream"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pendingAccessRequests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullStreamAccessRequestFields"}}]}}]}}]} as unknown as DocumentNode<StreamPendingAccessRequestsFragment, unknown>;
export const StreamCollaboratorFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"StreamCollaboratorFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StreamCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}}]} as unknown as DocumentNode<StreamCollaboratorFieldsFragment, unknown>;
export const UsersOwnInviteFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UsersOwnInviteFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PendingStreamCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"streamName"}},{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"invitedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserFields"}}]}}]}}]} as unknown as DocumentNode<UsersOwnInviteFieldsFragment, unknown>;
export const ServerInfoBlobSizeFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerInfoBlobSizeFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"blobSizeLimitBytes"}}]}}]} as unknown as DocumentNode<ServerInfoBlobSizeFieldsFragment, unknown>;
export const MainServerInfoFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MainServerInfoFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"adminContact"}},{"kind":"Field","name":{"kind":"Name","value":"canonicalUrl"}},{"kind":"Field","name":{"kind":"Name","value":"termsOfService"}},{"kind":"Field","name":{"kind":"Name","value":"inviteOnly"}},{"kind":"Field","name":{"kind":"Name","value":"version"}}]}}]} as unknown as DocumentNode<MainServerInfoFieldsFragment, unknown>;
export const ServerInfoRolesFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerInfoRolesFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"roles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"resourceTarget"}}]}}]}}]} as unknown as DocumentNode<ServerInfoRolesFieldsFragment, unknown>;
export const ServerInfoScopesFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerInfoScopesFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"scopes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}}]}}]} as unknown as DocumentNode<ServerInfoScopesFieldsFragment, unknown>;
export const CommonStreamFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CommonStreamFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Stream"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"isPublic"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"commentCount"}},{"kind":"Field","name":{"kind":"Name","value":"collaborators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"Field","name":{"kind":"Name","value":"commits"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"branches"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"favoritedDate"}},{"kind":"Field","name":{"kind":"Name","value":"favoritesCount"}}]}}]} as unknown as DocumentNode<CommonStreamFieldsFragment, unknown>;
export const CommonUserFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CommonUserFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"suuid"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"profiles"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"suuid"}},{"kind":"Field","name":{"kind":"Name","value":"streams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"commits"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]}}]} as unknown as DocumentNode<CommonUserFieldsFragment, unknown>;
export const CommonUserFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CommonUserFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"hasPendingVerification"}},{"kind":"Field","name":{"kind":"Name","value":"profiles"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"streams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"commits"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]}}]} as unknown as DocumentNode<CommonUserFieldsFragment, unknown>;
export const GetStreamAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetStreamAccessRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamAccessRequest"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"streamId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"}}]}}]}},...BasicStreamAccessRequestFieldsFragmentDoc.definitions]} as unknown as DocumentNode<GetStreamAccessRequestQuery, GetStreamAccessRequestQueryVariables>;
export const CreateStreamAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateStreamAccessRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamAccessRequestCreate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"streamId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"}}]}}]}},...BasicStreamAccessRequestFieldsFragmentDoc.definitions]} as unknown as DocumentNode<CreateStreamAccessRequestMutation, CreateStreamAccessRequestMutationVariables>;
export const UseStreamAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UseStreamAccessRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"requestId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"accept"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"role"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"StreamRole"}},"defaultValue":{"kind":"EnumValue","value":"STREAM_CONTRIBUTOR"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamAccessRequestUse"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"requestId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"requestId"}}},{"kind":"Argument","name":{"kind":"Name","value":"accept"},"value":{"kind":"Variable","name":{"kind":"Name","value":"accept"}}},{"kind":"Argument","name":{"kind":"Name","value":"role"},"value":{"kind":"Variable","name":{"kind":"Name","value":"role"}}}]}]}}]} as unknown as DocumentNode<UseStreamAccessRequestMutation, UseStreamAccessRequestMutationVariables>;
export const StreamWithBranchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"StreamWithBranch"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"branchName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"branch"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"branchName"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"commits"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorName"}},{"kind":"Field","name":{"kind":"Name","value":"authorId"}},{"kind":"Field","name":{"kind":"Name","value":"authorAvatar"}},{"kind":"Field","name":{"kind":"Name","value":"sourceApplication"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"commentCount"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<StreamWithBranchQuery, StreamWithBranchQueryVariables>;
export const BranchCreatedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"BranchCreated"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"branchCreated"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"streamId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}]}]}}]} as unknown as DocumentNode<BranchCreatedSubscription, BranchCreatedSubscriptionVariables>;
export const StreamCommitQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"StreamCommitQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"commit"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}},{"kind":"Field","name":{"kind":"Name","value":"authorName"}},{"kind":"Field","name":{"kind":"Name","value":"authorId"}},{"kind":"Field","name":{"kind":"Name","value":"authorAvatar"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"branchName"}},{"kind":"Field","name":{"kind":"Name","value":"sourceApplication"}}]}}]}}]}}]} as unknown as DocumentNode<StreamCommitQueryQuery, StreamCommitQueryQueryVariables>;
export const StreamInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"StreamInvite"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"token"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamInvite"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"streamId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}},{"kind":"Argument","name":{"kind":"Name","value":"token"},"value":{"kind":"Variable","name":{"kind":"Name","value":"token"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"UsersOwnInviteFields"}}]}}]}},...UsersOwnInviteFieldsFragmentDoc.definitions]} as unknown as DocumentNode<StreamInviteQuery, StreamInviteQueryVariables>;
export const UserStreamInvitesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserStreamInvites"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamInvites"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"UsersOwnInviteFields"}}]}}]}},...UsersOwnInviteFieldsFragmentDoc.definitions]} as unknown as DocumentNode<UserStreamInvitesQuery, UserStreamInvitesQueryVariables>;
export const StreamInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"StreamInvite"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"token"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamInvite"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"streamId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}},{"kind":"Argument","name":{"kind":"Name","value":"token"},"value":{"kind":"Variable","name":{"kind":"Name","value":"token"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"UsersOwnInviteFields"}}]}}]}},...UsersOwnInviteFieldsFragmentDoc.definitions,...LimitedUserFieldsFragmentDoc.definitions]} as unknown as DocumentNode<StreamInviteQuery, StreamInviteQueryVariables>;
export const UserStreamInvitesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserStreamInvites"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamInvites"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"UsersOwnInviteFields"}}]}}]}},...UsersOwnInviteFieldsFragmentDoc.definitions,...LimitedUserFieldsFragmentDoc.definitions]} as unknown as DocumentNode<UserStreamInvitesQuery, UserStreamInvitesQueryVariables>;
export const UseStreamInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UseStreamInvite"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"accept"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"token"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamInviteUse"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"accept"},"value":{"kind":"Variable","name":{"kind":"Name","value":"accept"}}},{"kind":"Argument","name":{"kind":"Name","value":"streamId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}},{"kind":"Argument","name":{"kind":"Name","value":"token"},"value":{"kind":"Variable","name":{"kind":"Name","value":"token"}}}]}]}}]} as unknown as DocumentNode<UseStreamInviteMutation, UseStreamInviteMutationVariables>;
export const CancelStreamInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CancelStreamInvite"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"inviteId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamInviteCancel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"streamId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}},{"kind":"Argument","name":{"kind":"Name","value":"inviteId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"inviteId"}}}]}]}}]} as unknown as DocumentNode<CancelStreamInviteMutation, CancelStreamInviteMutationVariables>;
export const DeleteInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteInvite"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"inviteId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"inviteDelete"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"inviteId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"inviteId"}}}]}]}}]} as unknown as DocumentNode<DeleteInviteMutation, DeleteInviteMutationVariables>;
@@ -2487,7 +2712,7 @@ export const ServerInfoBlobSizeLimitDocument = {"kind":"Document","definitions":
export const StreamCommitsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"StreamCommits"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"commits"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorId"}},{"kind":"Field","name":{"kind":"Name","value":"authorName"}},{"kind":"Field","name":{"kind":"Name","value":"authorAvatar"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}},{"kind":"Field","name":{"kind":"Name","value":"branchName"}},{"kind":"Field","name":{"kind":"Name","value":"sourceApplication"}}]}}]}}]}}]}}]} as unknown as DocumentNode<StreamCommitsQuery, StreamCommitsQueryVariables>;
export const StreamsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Streams"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streams"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"10"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"isPublic"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"commentCount"}},{"kind":"Field","name":{"kind":"Name","value":"collaborators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"Field","name":{"kind":"Name","value":"commits"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"authorId"}},{"kind":"Field","name":{"kind":"Name","value":"branchName"}},{"kind":"Field","name":{"kind":"Name","value":"authorName"}},{"kind":"Field","name":{"kind":"Name","value":"authorAvatar"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"branches"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"favoritedDate"}},{"kind":"Field","name":{"kind":"Name","value":"favoritesCount"}}]}}]}}]}}]} as unknown as DocumentNode<StreamsQuery, StreamsQueryVariables>;
export const StreamDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Stream"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CommonStreamFields"}}]}}]}},...CommonStreamFieldsFragmentDoc.definitions]} as unknown as DocumentNode<StreamQuery, StreamQueryVariables>;
export const StreamWithCollaboratorsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"StreamWithCollaborators"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isPublic"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"collaborators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"StreamCollaboratorFields"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingCollaborators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserFields"}}]}}]}}]}}]}},...StreamCollaboratorFieldsFragmentDoc.definitions,...LimitedUserFieldsFragmentDoc.definitions]} as unknown as DocumentNode<StreamWithCollaboratorsQuery, StreamWithCollaboratorsQueryVariables>;
export const StreamWithCollaboratorsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"StreamWithCollaborators"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isPublic"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"collaborators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"StreamCollaboratorFields"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingCollaborators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserFields"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingAccessRequests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullStreamAccessRequestFields"}}]}}]}}]}},...StreamCollaboratorFieldsFragmentDoc.definitions,...LimitedUserFieldsFragmentDoc.definitions,...FullStreamAccessRequestFieldsFragmentDoc.definitions,...BasicStreamAccessRequestFieldsFragmentDoc.definitions]} as unknown as DocumentNode<StreamWithCollaboratorsQuery, StreamWithCollaboratorsQueryVariables>;
export const StreamWithActivityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"StreamWithActivity"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"DateTime"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"commits"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"branches"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"activity"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ActivityMainFields"}}]}}]}}]}}]}},...ActivityMainFieldsFragmentDoc.definitions]} as unknown as DocumentNode<StreamWithActivityQuery, StreamWithActivityQueryVariables>;
export const LeaveStreamDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"LeaveStream"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamLeave"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"streamId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}]}]}}]} as unknown as DocumentNode<LeaveStreamMutation, LeaveStreamMutationVariables>;
export const UpdateStreamPermissionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateStreamPermission"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"params"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"StreamUpdatePermissionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamUpdatePermission"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"permissionParams"},"value":{"kind":"Variable","name":{"kind":"Name","value":"params"}}}]}]}}]} as unknown as DocumentNode<UpdateStreamPermissionMutation, UpdateStreamPermissionMutationVariables>;
@@ -2498,9 +2723,12 @@ export const MainUserDataDocument = {"kind":"Document","definitions":[{"kind":"O
export const ExtraUserDataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ExtraUserData"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CommonUserFields"}},{"kind":"Field","name":{"kind":"Name","value":"totalOwnedStreamsFavorites"}}]}}]}},...CommonUserFieldsFragmentDoc.definitions]} as unknown as DocumentNode<ExtraUserDataQuery, ExtraUserDataQueryVariables>;
export const UserSearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserSearch"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"query"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"archived"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userSearch"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"Variable","name":{"kind":"Name","value":"query"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"archived"},"value":{"kind":"Variable","name":{"kind":"Name","value":"archived"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserFields"}}]}}]}}]}},...LimitedUserFieldsFragmentDoc.definitions]} as unknown as DocumentNode<UserSearchQuery, UserSearchQueryVariables>;
export const IsLoggedInDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"IsLoggedIn"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode<IsLoggedInQuery, IsLoggedInQueryVariables>;
export const AdminUsersListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AdminUsersList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"query"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"adminUsers"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}},{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"Variable","name":{"kind":"Name","value":"query"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"registeredUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"suuid"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"profiles"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"authorizedApps"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"invitedUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"invitedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<AdminUsersListQuery, AdminUsersListQueryVariables>;
export const AdminUsersListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AdminUsersList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"query"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"adminUsers"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}},{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"Variable","name":{"kind":"Name","value":"query"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"registeredUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"profiles"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"authorizedApps"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"invitedUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"invitedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<AdminUsersListQuery, AdminUsersListQueryVariables>;
export const UserTimelineDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserTimeline"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"DateTime"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"timeline"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ActivityMainFields"}}]}}]}}]}}]}},...ActivityMainFieldsFragmentDoc.definitions]} as unknown as DocumentNode<UserTimelineQuery, UserTimelineQueryVariables>;
export const UserDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"User"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"profiles"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"suuid"}}]}}]}}]} as unknown as DocumentNode<UserQuery, UserQueryVariables>;
export const ValidatePasswordStrengthDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ValidatePasswordStrength"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pwd"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userPwdStrength"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pwd"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pwd"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"score"}},{"kind":"Field","name":{"kind":"Name","value":"feedback"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"warning"}},{"kind":"Field","name":{"kind":"Name","value":"suggestions"}}]}}]}}]}}]} as unknown as DocumentNode<ValidatePasswordStrengthQuery, ValidatePasswordStrengthQueryVariables>;
export const EmailVerificationBannerStateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EmailVerificationBannerState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"hasPendingVerification"}}]}}]}}]} as unknown as DocumentNode<EmailVerificationBannerStateQuery, EmailVerificationBannerStateQueryVariables>;
export const RequestVerificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RequestVerification"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"requestVerification"}}]}}]} as unknown as DocumentNode<RequestVerificationMutation, RequestVerificationMutationVariables>;
export const UserByIdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserById"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"profiles"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]} as unknown as DocumentNode<UserByIdQuery, UserByIdQueryVariables>;
export const UserProfileDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserProfile"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}}]}}]}}]} as unknown as DocumentNode<UserProfileQuery, UserProfileQueryVariables>;
export const WebhookDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"webhook"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"webhookId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"webhooks"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"webhookId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"triggers"}},{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"history"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusInfo"}}]}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<WebhookQuery, WebhookQueryVariables>;
export const WebhooksDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"webhooks"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"webhooks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"triggers"}},{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"history"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"50"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusInfo"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdate"}}]}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<WebhooksQuery, WebhooksQueryVariables>;
+5
View File
@@ -1,3 +1,4 @@
import { fullStreamAccessRequestFieldsFragment } from '@/graphql/fragments/accessRequests'
import { activityMainFieldsFragment } from '@/graphql/fragments/activity'
import {
limitedUserFieldsFragment,
@@ -70,10 +71,14 @@ export const streamWithCollaboratorsQuery = gql`
...LimitedUserFields
}
}
pendingAccessRequests {
...FullStreamAccessRequestFields
}
}
}
${limitedUserFieldsFragment}
${streamCollaboratorFieldsFragment}
${fullStreamAccessRequestFieldsFragment}
`
export const streamWithActivityQuery = gql`
+30 -3
View File
@@ -6,16 +6,15 @@ import { gql } from '@apollo/client/core'
export const commonUserFieldsFragment = gql`
fragment CommonUserFields on User {
id
suuid
email
name
bio
company
avatar
verified
hasPendingVerification
profiles
role
suuid
streams {
totalCount
}
@@ -115,7 +114,6 @@ export const adminUsersListQuery = gql`
id
registeredUser {
id
suuid
email
name
bio
@@ -157,3 +155,32 @@ export const userTimelineQuery = gql`
${activityMainFieldsFragment}
`
export const validatePasswordStrengthQuery = gql`
query ValidatePasswordStrength($pwd: String!) {
userPwdStrength(pwd: $pwd) {
score
feedback {
warning
suggestions
}
}
}
`
export const emailVerificationBannerStateQuery = gql`
query EmailVerificationBannerState {
user {
id
email
verified
hasPendingVerification
}
}
`
export const requestVerificationMutation = gql`
mutation RequestVerification {
requestVerification
}
`
+1 -2
View File
@@ -1,4 +1,4 @@
query User($id: String!) {
query UserById($id: String!) {
user(id: $id) {
id
email
@@ -9,6 +9,5 @@ query User($id: String!) {
verified
profiles
role
suuid
}
}
@@ -1,25 +0,0 @@
/**
* Speckle role constants
*/
export const Roles = Object.freeze({
Stream: {
Owner: 'stream:owner',
Contributor: 'stream:contributor',
Reviewer: 'stream:reviewer'
},
Server: {
Admin: 'server:admin',
User: 'server:user',
ArchivedUser: 'server:archived-user'
}
})
/**
* Keys for values stored in localStorage
*/
export const LocalStorageKeys = Object.freeze({
AuthToken: 'AuthToken',
RefreshToken: 'RefreshToken',
Uuid: 'uuid',
ShouldRedirectTo: 'shouldRedirectTo'
})
@@ -0,0 +1,46 @@
import { StreamRole } from '@/graphql/generated/graphql'
/**
* Speckle role constants
*/
export const Roles = Object.freeze(<const>{
Stream: {
Owner: 'stream:owner',
Contributor: 'stream:contributor',
Reviewer: 'stream:reviewer'
},
Server: {
Admin: 'server:admin',
User: 'server:user',
ArchivedUser: 'server:archived-user'
}
})
export type ServerRoles = typeof Roles['Server'][keyof typeof Roles['Server']]
export type StreamRoles = typeof Roles['Stream'][keyof typeof Roles['Stream']]
/**
* Keys for values stored in localStorage
*/
export const LocalStorageKeys = Object.freeze({
AuthToken: 'AuthToken',
RefreshToken: 'RefreshToken',
Uuid: 'uuid',
ShouldRedirectTo: 'shouldRedirectTo'
})
/**
* Our GQL schema has a StreamRoles enum that unfortunately can't have the same exact values as our roles constants, because
* we can't use colons (:) there. So you can use this function to map from our constant value to the GQL one.
*/
export function streamRoleToGraphQLEnum(role: StreamRoles): StreamRole {
switch (role) {
case Roles.Stream.Owner:
return StreamRole.StreamOwner
case Roles.Stream.Reviewer:
return StreamRole.StreamReviewer
case Roles.Stream.Contributor:
default:
return StreamRole.StreamContributor
}
}
@@ -1,4 +1,5 @@
import { ReactiveVar } from '@apollo/client/core'
import { isUndefined } from 'lodash'
import Vue, { VueConstructor } from 'vue'
export type Nullable<T> = T | null
@@ -7,6 +8,16 @@ export type Optional<T> = T | undefined
export type MaybeFalsy<T> = T | null | undefined | false | '' | 0
export type MaybeNullOrUndefined<T> = T | null | undefined
export type MaybeAsync<T> = T | Promise<T>
/**
* In TS undefined !== void, so use this type guard to check for both
*/
export const isUndefinedOrVoid = (val: unknown): val is void | undefined =>
isUndefined(val)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type GetReactiveVarType<V extends ReactiveVar<any>> = V extends ReactiveVar<
infer T
@@ -17,5 +17,6 @@ export type VFormInstance = CombinedVueInstance<
validate(): boolean
},
unknown,
unknown,
unknown
>
+3 -3
View File
@@ -10,7 +10,7 @@ import { DefaultApolloClient } from '@vue/apollo-composable'
import { createProvider, installVueApollo } from '@/config/apolloConfig'
import {
checkAccessCodeAndGetTokens,
prefetchUserAndSetSuuid
prefetchUserAndSetID
} from '@/plugins/authHelpers'
import router from '@/main/router/index'
@@ -58,7 +58,7 @@ installVueApollo(apolloProvider)
// TODO: Sort out error handling here, if something goes wrong it just goes into an infinite loop
if (AuthToken) {
prefetchUserAndSetSuuid(apolloProvider.defaultClient)
prefetchUserAndSetID(apolloProvider.defaultClient)
.then(() => {
postAuthInit()
})
@@ -72,7 +72,7 @@ if (AuthToken) {
} else {
checkAccessCodeAndGetTokens()
.then(() => {
return prefetchUserAndSetSuuid(apolloProvider.defaultClient)
return prefetchUserAndSetID(apolloProvider.defaultClient)
})
.then(() => {
postAuthInit()
@@ -46,6 +46,12 @@
{{ val ? 'public' : 'private' }}
</i>
</template>
<template v-else-if="key === UpdatedInfoKeys.IsDiscoverable">
📺 Stream is now
<i>
{{ val ? 'discoverable' : 'not discoverable' }}
</i>
</template>
</p>
</template>
</template>
@@ -61,7 +67,8 @@ const UpdatedInfoKeys = {
Name: 'name',
Description: 'description',
Message: 'message',
IsPublic: 'isPublic'
IsPublic: 'isPublic',
IsDiscoverable: 'isDiscoverable'
}
export default {
@@ -18,8 +18,8 @@
block
:color="s.color"
:href="`${s.url}?appId=${appId}&challenge=${challenge}${
suuid ? '&suuid=' + suuid : ''
}${token ? '&token=' + token : ''}`"
token ? '&token=' + token : ''
}`"
>
<v-icon small class="mr-5">{{ s.icon }}</v-icon>
{{ s.name }}
@@ -45,10 +45,6 @@ export default {
challenge: {
type: String,
default: () => null
},
suuid: {
type: String,
default: () => null
}
},
computed: {
@@ -3,7 +3,7 @@
<div class="text-caption d-flex flex-column">
<a
v-for="attachment in attachments"
:key="attachment.url"
:key="attachment.id"
v-tooltip="attachment.fileName"
href="javascript:;"
:class="`my-1 ${primary ? '' : 'blue--text'}`"
@@ -34,6 +34,7 @@
import { BlobMetadata } from '@/graphql/generated/graphql'
import Vue, { PropType } from 'vue'
import CommentThreadAttachmentPreview from '@/main/components/comments/CommentThreadAttachmentPreview.vue'
import { Nullable } from '@/helpers/typeHelpers'
export default Vue.extend({
name: 'CommentThreadReplyAttachments',
@@ -53,7 +54,7 @@ export default Vue.extend({
data: () => {
return {
showAttachmentPreview: false,
selectedAttachment: null
selectedAttachment: null as Nullable<BlobMetadata>
}
},
methods: {
@@ -12,47 +12,16 @@
</v-snackbar>
</template>
<script lang="ts">
import { Nullable } from '@/helpers/typeHelpers'
import Vue from 'vue'
import {
GlobalEvents,
NotificationEventPayload,
ToastNotificationType
} from '@/main/lib/core/helpers/eventHubHelper'
import { defineComponent } from 'vue'
import { setupGlobalToast } from '@/main/lib/core/composables/notifications'
export default Vue.extend({
export default defineComponent({
name: 'GlobalToast',
data() {
setup() {
const globalToastData = setupGlobalToast()
return {
snack: false,
text: null as Nullable<string>,
actionName: null as Nullable<string>,
to: null as Nullable<string>,
type: 'primary' as ToastNotificationType
...globalToastData
}
},
computed: {
color(): ToastNotificationType {
return this.type || 'primary'
}
},
watch: {
snack(newVal) {
if (!newVal) {
this.text = null
this.actionName = null
this.to = null
}
}
},
mounted() {
this.$eventHub.$on(GlobalEvents.Notification, (args: NotificationEventPayload) => {
this.snack = true
this.text = args.text
this.actionName = args.action ? args.action.name : null
this.to = args.action ? args.action.to : null
this.type = args.type || 'primary'
})
}
})
</script>
@@ -26,7 +26,8 @@
link
class="primary mb-4"
dark
@click="downloadManager"
href="https://releases.speckle.systems/"
target="_blank"
>
<v-list-item-icon>
<v-icon class="pt-4">mdi-download</v-icon>
@@ -183,23 +184,6 @@ export default {
},
beforeDestroy() {
clearInterval(this.checkAccountTimer)
},
methods: {
async downloadManager() {
this.$mixpanel.track('Manager Download', {
type: 'action'
})
const url = `https://releases.speckle.dev/manager/SpeckleManager Setup.exe`
const a = document.createElement('a')
document.body.appendChild(a)
a.style = 'display: none'
a.href = url
a.download = 'SpeckleManager Setup.exe'
a.click()
document.body.removeChild(a)
}
}
}
</script>
@@ -49,7 +49,7 @@ export default {
props: {
url: {
type: String,
default: ''
default: () => ''
},
color: {
type: Boolean,
@@ -69,18 +69,27 @@
></user-avatar-icon>
</div>
</template>
<script>
import userByIdQuery from '@/graphql/userById.gql'
import UserAvatarIcon from '@/main/components/common/UserAvatarIcon'
<script lang="ts">
import UserAvatarIcon from '@/main/components/common/UserAvatarIcon.vue'
import { AppLocalStorage } from '@/utils/localStorage'
import { LocalStorageKeys } from '@/helpers/mainConstants'
import { useIsLoggedIn } from '@/main/lib/core/composables/core'
import { computed, defineComponent, PropType } from 'vue'
import { useQuery } from '@vue/apollo-composable'
import { UserByIdDocument } from '@/graphql/generated/graphql'
import { MaybeNullOrUndefined } from '@/helpers/typeHelpers'
export default {
export default defineComponent({
components: { UserAvatarIcon },
props: {
avatar: { type: String, default: null },
name: { type: String, default: null },
avatar: {
type: String as PropType<MaybeNullOrUndefined<string>>,
default: null
},
name: {
type: String as PropType<MaybeNullOrUndefined<string>>,
default: null
},
showHover: {
type: Boolean,
default: true
@@ -102,30 +111,21 @@ export default {
default: null
}
},
setup() {
setup(props) {
const { isLoggedIn } = useIsLoggedIn()
return { isLoggedIn }
const { result: userByIdResult } = useQuery(
UserByIdDocument,
() => ({ id: props.id }),
() => ({ enabled: isLoggedIn.value })
)
const userById = computed(() => userByIdResult.value?.user)
return { isLoggedIn, userById }
},
computed: {
isSelf() {
isSelf(): boolean {
return this.id === AppLocalStorage.get(LocalStorageKeys.Uuid)
}
},
apollo: {
userById: {
query: userByIdQuery,
variables() {
return {
id: this.id
}
},
skip() {
return !this.isLoggedIn
},
update: (data) => {
return data.user
}
}
}
}
})
</script>
@@ -17,6 +17,9 @@ export default {
required: true
},
avatar: {
/**
* @type {import('vue').PropType<string | null | undefined>}
*/
type: String,
default: null
}
@@ -0,0 +1,3 @@
<template>
<v-alert rounded="lg" elevation="4" dense class="mb-0"><slot /></v-alert>
</template>
@@ -0,0 +1,16 @@
<template>
<v-list rounded class="transparent">
<slot />
</v-list>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'RoundedButtonList'
})
</script>
<style lang="scss" scoped>
:deep(.v-list-item:not(:last-of-type)) {
margin-bottom: 16px !important;
}
</style>
@@ -0,0 +1,69 @@
<template>
<v-list-item
link
:href="to"
:class="itemClass"
:dark="isPrimary"
@click="$emit('click', $event)"
>
<v-list-item-icon v-if="icon">
<v-icon>{{ icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>
<slot />
</v-list-item-title>
<v-list-item-subtitle class="caption">
<slot name="subtitle" />
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</template>
<script lang="ts">
import { Optional } from '@/helpers/typeHelpers'
import { useVuetify } from '@/main/lib/core/composables/core'
import { computed, defineComponent, PropType } from 'vue'
type ItemType = 'primary' | 'secondary'
export default defineComponent({
name: 'RoundedButtonListItem',
props: {
type: {
type: String as PropType<ItemType>,
default: () => 'primary'
},
icon: {
type: String as PropType<Optional<string>>,
default: () => undefined
},
to: {
type: String as PropType<Optional<string>>,
default: () => undefined
}
},
setup(props) {
const vuetify = useVuetify()
const itemClass = computed(() => {
const classes = ['']
switch (props.type) {
case 'primary':
classes.push('primary')
break
case 'secondary':
classes.push(`grey ${vuetify.theme.dark ? 'darken-4' : 'lighten-4'}`)
break
}
return classes
})
const isPrimary = computed(() => props.type === 'primary')
return {
itemClass,
isPrimary
}
}
})
</script>
@@ -82,6 +82,7 @@ import { UserTimelineDocument } from '@/graphql/generated/graphql'
import { useQuery } from '@vue/apollo-composable'
import { computed } from 'vue'
import { AppLocalStorage } from '@/utils/localStorage'
import { SKIPPABLE_ACTION_TYPES } from '@/main/lib/feed/helpers/activityStream'
export default {
name: 'FeedTimeline',
@@ -112,7 +113,7 @@ export default {
const data = timelineResult.value
if (!data) return []
const skippableActionTypes = ['stream_invite_sent', 'stream_invite_declined']
const skippableActionTypes = SKIPPABLE_ACTION_TYPES
const groupedTimeline = data.user.timeline.items.reduce(function (prev, curr) {
if (skippableActionTypes.includes(curr.actionType)) {
return prev
@@ -1,11 +1,7 @@
<template>
<v-navigation-drawer app right fixed class="transparent overflow-auto" floating>
<v-card
rounded="lg"
style="overflow: hidden"
class="transparent elevation-0 pl-1 pr-3 pb-4"
>
<v-toolbar class="mt-3" rounded="lg" dense>
<div class="latest-blogposts">
<v-card rounded="lg" style="overflow: hidden" class="transparent elevation-0 pb-4">
<v-toolbar class="mt-0" rounded="lg" dense>
<v-toolbar-title class="body-2 font-weight-bold">
<a
class="text-decoration-none"
@@ -113,7 +109,7 @@
</v-card-text>
</v-card>
</v-card>
</v-navigation-drawer>
</div>
</template>
<script>
import GhostContentAPI from '@tryghost/content-api'
@@ -147,3 +143,8 @@ export default {
}
}
</script>
<style lang="scss" scoped>
.latest-blogposts {
width: 240px;
}
</style>
@@ -0,0 +1,176 @@
<template>
<basic-panel class="stream-access-request-banner">
<div class="d-flex flex-column flex-md-row align-center">
<div class="flex-grow-1 d-flex align-center">
<user-avatar
:id="requester.id"
:name="requester.name"
:avatar="requester.avatar"
:size="25"
class="mr-1"
/>
<div>
<strong>{{ requester.name }}</strong>
has requested access to this stream
</div>
</div>
<div class="d-flex align-center mt-2 mt-md-0">
<v-select
v-model="selectedRole"
class="mr-2"
filled
rounded
dense
hide-details
:items="availableRolesSelectItems"
style="max-width: 200px"
></v-select>
<v-btn
small
color="primary"
class="mr-2 flex-grow-1 flex-md-grow-0"
@click="approveRequest"
>
Add
</v-btn>
<v-btn
small
color="error"
dark
class="flex-grow-1 flex-md-grow-0"
@click="declineRequest"
>
Ignore
</v-btn>
</div>
</div>
</basic-panel>
</template>
<script setup lang="ts">
import { computed, PropType, ref } from 'vue'
import { Get } from 'type-fest'
import {
StreamRole,
StreamWithCollaboratorsQuery,
UseStreamAccessRequestDocument,
StreamPendingAccessRequestsFragment
} from '@/graphql/generated/graphql'
import BasicPanel from '@/main/components/common/layout/BasicPanel.vue'
import UserAvatar from '@/main/components/common/UserAvatar.vue'
import { Roles, streamRoleToGraphQLEnum } from '@/helpers/mainConstants'
import { useApolloClient } from '@vue/apollo-composable'
import {
convertThrowIntoFetchResult,
getFirstErrorMessage,
updateCacheByFilter,
getCacheId
} from '@/main/lib/common/apollo/helpers/apolloOperationHelper'
import { useEventHub } from '@/main/lib/core/composables/core'
import { StreamEvents } from '@/main/lib/core/helpers/eventHubHelper'
import { useGlobalToast } from '@/main/lib/core/composables/notifications'
import { streamPendingAccessRequestsFragment } from '@/graphql/fragments/streams'
type StreamAccessRequest = NonNullable<
Get<StreamWithCollaboratorsQuery, 'stream.pendingAccessRequests.0'>
>
const props = defineProps({
/**
* Request from the StreamWithCollaborators query
*/
request: {
type: Object as PropType<StreamAccessRequest>,
required: true
}
})
const availableRolesSelectItems: { text: string; value: StreamRole }[] = Object.entries(
Roles.Stream
).map(([text, value]) => ({
text,
value: streamRoleToGraphQLEnum(value)
}))
const selectedRole = ref<StreamRole>(streamRoleToGraphQLEnum(Roles.Stream.Contributor))
const requester = computed(() => props.request.requester)
const apollo = useApolloClient().client
const eventHub = useEventHub()
const { triggerNotification } = useGlobalToast()
/**
* Accept or decline the access request
*/
const processRequest = async (accept: boolean) => {
const reqId = props.request.id
const res = await apollo
.mutate({
mutation: UseStreamAccessRequestDocument,
variables: {
requestId: reqId,
accept,
role: accept ? selectedRole.value : undefined
},
update: (cache, res) => {
const reqId = props.request.id
const streamId = props.request.streamId
const { data } = res
if (!data?.streamAccessRequestUse) return
// Remove request from cache
updateCacheByFilter<StreamPendingAccessRequestsFragment>(
cache,
{
fragment: {
id: getCacheId('Stream', streamId),
// (not using typed doc, cause of nested fragments which don't get converted to typed doc correctly)
fragment: streamPendingAccessRequestsFragment,
fragmentName: 'StreamPendingAccessRequests'
}
},
(data) => {
if (!data.pendingAccessRequests?.length) return
return {
...data,
pendingAccessRequests: data.pendingAccessRequests.filter(
(r) => r.id !== reqId
)
}
}
)
}
})
.catch(convertThrowIntoFetchResult)
const { data, errors } = res
if (data?.streamAccessRequestUse) {
triggerNotification({
text: accept ? 'Access request approved' : 'Access request declined'
})
} else {
triggerNotification({
type: 'error',
text: getFirstErrorMessage(errors)
})
}
if (accept) {
// reload stream collaborators
eventHub.$emit(StreamEvents.RefetchCollaborators)
}
}
const declineRequest = async () => {
await processRequest(false)
}
const approveRequest = async () => {
await processRequest(true)
}
</script>
<style lang="scss" scoped>
.stream-access-request-banner:not(:last-child) {
margin-bottom: 16px !important;
}
</style>
@@ -41,6 +41,7 @@ import { StreamWithActivityDocument } from '@/graphql/generated/graphql'
import { useQuery } from '@vue/apollo-composable'
import { useRoute } from '@/main/lib/core/composables/router'
import { computed } from 'vue'
import { SKIPPABLE_ACTION_TYPES } from '@/main/lib/feed/helpers/activityStream'
export default {
name: 'StreamActivity',
@@ -61,7 +62,7 @@ export default {
}))
const stream = computed(() => result.value?.stream || null)
const skippableActionTypes = ['stream_invite_sent', 'stream_invite_declined']
const skippableActionTypes = SKIPPABLE_ACTION_TYPES
const groupedActivity = computed(() =>
(stream.value?.activity?.items || []).reduce(function (prev, curr) {
if (skippableActionTypes.includes(curr.actionType)) {
@@ -0,0 +1,80 @@
<template>
<div class="d-flex flex-column">
<v-switch
v-model="isPublicModel"
inset
:label="isPublicModel ? 'Link Sharing On' : 'Link Sharing Off'"
:hint="
isPublicModel
? 'Anyone with the link can view this stream. It is also visible on your profile page. Only collaborators can push data to it.'
: 'Only collaborators can access this stream.'
"
persistent-hint
:disabled="disabled"
class="visibility-toggle"
/>
<v-switch
v-model="isDiscoverableModel"
inset
:label="isDiscoverableModel ? 'Discoverable' : 'Not Discoverable'"
:hint="
isDiscoverableModel
? 'This stream can be found on public stream discovery pages'
: 'This stream is not shown on any public stream discovery pages'
"
persistent-hint
:disabled="disabled || !isPublicModel"
class="visibility-toggle"
/>
</div>
</template>
<script lang="ts">
import Vue, { computed } from 'vue'
export default Vue.extend({
name: 'StreamVisibilityToggle',
props: {
isPublic: {
type: Boolean,
required: true
},
isDiscoverable: {
type: Boolean,
required: true
},
disabled: {
type: Boolean,
default: false
}
},
setup(props, { emit }) {
const isPublicModel = computed({
get: () => props.isPublic,
set: (val) => {
emit('update:isPublic', val)
if (!val) {
isDiscoverableModel.value = false
}
}
})
const isDiscoverableModel = computed({
get: () => props.isDiscoverable && isPublicModel.value,
set: (val) => {
if (!isPublicModel.value) {
val = false
}
emit('update:isDiscoverable', val)
}
})
return { isPublicModel, isDiscoverableModel }
}
})
</script>
<style scoped lang="scss">
.visibility-toggle {
// incase hint breaks up in 2 lines
min-height: 60px;
}
</style>
@@ -1,7 +1,7 @@
<template>
<transition name="component-fade" mode="out-in">
<transition v-if="shouldShowBanner" name="component-fade" mode="out-in">
<v-alert
v-if="!success & !errors"
v-if="!success && !errors"
type="warning"
dismissible
rounded="lg"
@@ -10,16 +10,16 @@
dense
>
<v-row align="center">
<v-col class="grow">Your email {{ user.email }} is not verified.</v-col>
<v-col class="grow">{{ verifyBannerText }}</v-col>
<v-col class="shrink">
<v-btn plain small :loading="loading" @click="requestVerification">
Send verification
{{ verifyBannerCtaText }}
</v-btn>
</v-col>
</v-row>
</v-alert>
<v-alert
v-if="success & !errors"
v-if="success && !errors"
type="success"
color="primary"
dismissible
@@ -28,7 +28,7 @@
height="44"
dense
>
Verification email sent, please check you inbox.
Verification e-mail sent, please check you inbox.
</v-alert>
<v-alert
v-if="errors"
@@ -39,18 +39,49 @@
elevation="8"
dense
>
Email verification failed.{{ errorMessage ? ` Reason: ${errorMessage}` : '' }}
E-mail verification failed.{{ errorMessage ? ` Reason: ${errorMessage}` : '' }}
</v-alert>
</transition>
</template>
<script>
import { AppLocalStorage } from '@/utils/localStorage'
<script lang="ts">
import {
EmailVerificationBannerStateDocument,
RequestVerificationDocument
} from '@/graphql/generated/graphql'
import { Nullable } from '@/helpers/typeHelpers'
import { convertThrowIntoFetchResult } from '@/main/lib/common/apollo/helpers/apolloOperationHelper'
import { useQuery } from '@vue/apollo-composable'
import { computed, defineComponent } from 'vue'
export default {
props: {
user: {
type: Object,
default: () => null
export default defineComponent({
setup() {
const { result } = useQuery(EmailVerificationBannerStateDocument)
const user = computed(() => result.value?.user || null)
const shouldShowBanner = computed(() => {
if (!user.value) return false
if (user.value.verified) return false
return true
})
const hasPendingVerification = computed(() => !!user.value?.hasPendingVerification)
const verifyBannerText = computed(() =>
hasPendingVerification.value
? `Please check your inbox (${user.value?.email}) for the verification e-mail`
: `Please verify your e-mail address (${user.value?.email})`
)
const verifyBannerCtaText = computed(() =>
hasPendingVerification.value ? `Re-send verification` : `Send verification`
)
return {
user,
shouldShowBanner,
hasPendingVerification,
verifyBannerText,
verifyBannerCtaText
}
},
data() {
@@ -58,30 +89,44 @@ export default {
errors: false,
success: false,
loading: false,
errorMessage: null
errorMessage: null as Nullable<string>
}
},
methods: {
async requestVerification() {
this.loading = true
const res = await fetch(`/auth/emailverification/request`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: AppLocalStorage.get('AuthToken')
},
body: JSON.stringify({ email: this.user.email })
})
if (res.status !== 200) {
this.errors = true
this.errorMessage = await res.text()
this.loading = false
return
}
const user = this.user
if (!user) return
this.success = true
this.loading = false
this.loading = true
const { data, errors } = await this.$apollo
.mutate({
mutation: RequestVerificationDocument,
update: (cache, { data }) => {
const isSuccess = !!data?.requestVerification
if (!isSuccess) return
// Switch hasPendingVerification to true
cache.modify({
id: cache.identify(user),
fields: {
hasPendingVerification: () => true
}
})
}
})
.catch(convertThrowIntoFetchResult)
.finally(() => (this.loading = false))
if (errors?.length || !data?.requestVerification) {
const errMsg = errors?.[0].message || 'An unexpected issue occurred!'
this.errors = true
this.errorMessage = errMsg
this.loading = false
} else {
this.success = true
}
}
}
}
})
</script>
@@ -64,8 +64,6 @@
<span v-if="isSelf" class="caption">
id:
<code>{{ user.id }}</code>
, suuid:
<code>{{ user.suuid }}</code>
</span>
<br />
</v-col>
@@ -11,7 +11,7 @@
<v-alert v-show="error" dismissible type="error">
{{ error }}
</v-alert>
<v-form ref="form" v-model="valid" lazy-validation>
<v-form ref="form" v-model="valid" lazy-validation @submit.prevent="updateBranch">
<v-card-text>
<v-text-field
v-model="editableBranch.name"
@@ -18,6 +18,7 @@
<v-card-text>
<v-text-field
v-model="name"
:disabled="isLoading"
:rules="nameRules"
validate-on-blur
autofocus
@@ -25,27 +26,22 @@
/>
<v-textarea
v-model="description"
:disabled="isLoading"
rows="1"
row-height="15"
label="Description (optional)"
/>
<v-switch
v-model="isPublic"
v-tooltip="
isPublic
? `Anyone with the link can view this stream. It is also visible on your profile page. Only collaborators
can edit it.`
: `Only collaborators can access this stream.`
"
inset
:label="`${isPublic ? 'Link Sharing On' : 'Link Sharing Off'}`"
<stream-visibility-toggle
:disabled="isLoading"
:is-public.sync="isPublic"
:is-discoverable.sync="isDiscoverable"
/>
<p class="mt-5">
<b>Invite collaborators</b>
</p>
<v-text-field
v-model="search"
:disabled="isLoading"
label="Search users..."
placeholder="Search by name or by email"
/>
@@ -100,7 +96,7 @@
color="primary"
block
large
:disabled="!valid"
:disabled="!valid || isLoading"
:loading="isLoading"
elevation="0"
type="submit"
@@ -115,10 +111,13 @@
import { gql } from '@apollo/client/core'
import { userSearchQuery } from '@/graphql/user'
import { AppLocalStorage } from '@/utils/localStorage'
import StreamVisibilityToggle from '@/main/components/stream/editor/StreamVisibilityToggle.vue'
import UserAvatar from '@/main/components/common/UserAvatar.vue'
export default {
components: {
UserAvatar: () => import('@/main/components/common/UserAvatar')
UserAvatar,
StreamVisibilityToggle
},
props: {
open: {
@@ -156,6 +155,7 @@ export default {
search: null,
nameRules: [],
isPublic: true,
isDiscoverable: false,
collabs: [],
isLoading: false,
users: null
@@ -210,6 +210,7 @@ export default {
myStream: {
name: this.name,
isPublic: this.isPublic,
isDiscoverable: this.isDiscoverable,
description: this.description,
withContributors: collabIds
}
@@ -223,8 +224,9 @@ export default {
this.$eventHub.$emit('notification', {
text: e.message
})
} finally {
this.isLoading = false
}
this.isLoading = false
}
}
}
+36 -18
View File
@@ -46,8 +46,7 @@
</v-app-bar>
<v-main class="background">
<email-verification-banner
v-if="!hideEmailBanner && user && !user.verified"
:user="user"
v-if="!hideEmailBanner"
class="my-2 mx-4 email-banner"
></email-verification-banner>
<v-container fluid class="px-4">
@@ -62,9 +61,9 @@
</template>
<script>
import { gql } from '@apollo/client/core'
import { mainUserDataQuery } from '@/graphql/user'
import { useNavigationDrawerAutoResize } from '../lib/core/composables/dom'
import { ref } from 'vue'
import { useIsLoggedIn } from '../lib/core/composables/core'
export default {
name: 'TheMain',
@@ -78,12 +77,6 @@ export default {
import('@/main/components/user/EmailVerificationBanner')
},
apollo: {
user: {
query: mainUserDataQuery,
skip() {
return !this.$loggedIn()
}
},
$subscribe: {
userStreamAdded: {
query: gql`
@@ -103,7 +96,7 @@ export default {
})
},
skip() {
return !this.user
return !this.isLoggedIn
}
}
}
@@ -115,10 +108,13 @@ export default {
drawerRef: navDrawer
})
const { isLoggedIn } = useIsLoggedIn()
// drawer ref must be returned, for it to be filled
return {
navDrawer,
navWidth
navWidth,
isLoggedIn
}
},
data() {
@@ -134,15 +130,37 @@ export default {
this.hideEmailBanner = !!to.meta.hideEmailBanner
},
immediate: true
},
'$route.query.emailverifiedstatus': {
handler(emailVerifiedStatus, oldStatus) {
if (!oldStatus && emailVerifiedStatus === 'true') {
this.$triggerNotification({
text: '✉️ Email successfully verified!',
type: 'success'
})
this.cleanQuery()
}
},
immediate: true
},
'$route.query.emailverifiederror': {
handler(emailVerifiedError, oldError) {
if (!oldError && emailVerifiedError) {
this.$triggerNotification({
text: `✉️ ${emailVerifiedError}`,
type: 'error'
})
this.cleanQuery()
}
},
immediate: true
}
},
mounted() {
if (this.$route.query.emailverfiedstatus) {
setTimeout(() => {
this.$eventHub.$emit('notification', {
text: '✉️ Email successfully verfied!'
})
}, 1000) // todo: ask fabian if there's a better way, feels icky
methods: {
cleanQuery() {
this.$router.replace({ ...this.$router.currentRoute, query: '' })
}
}
}
@@ -0,0 +1,75 @@
import { ValidatePasswordStrengthDocument } from '@/graphql/generated/graphql'
import { Nullable } from '@/helpers/typeHelpers'
import { useApolloClient } from '@vue/apollo-composable'
import { ref, watch } from 'vue'
export function useValidatablePasswordEntry() {
const password = ref<Nullable<string>>(null)
const passwordConfirmation = ref<Nullable<string>>(null)
/**
* Strength value from 1 to 100. Its PasswordStrengthCheckResults.score times 25.
*/
const passwordStrength = ref<Nullable<number>>(null)
const passwordSuggestion = ref<Nullable<string>>(null)
const apollo = useApolloClient()
/**
* Re-check password strength
*/
const updatePasswordStrength = async () => {
if (!password.value) {
passwordStrength.value = 1
passwordSuggestion.value = null
return
}
const result = await apollo.client.query({
query: ValidatePasswordStrengthDocument,
variables: { pwd: password.value }
})
passwordStrength.value = result.data.userPwdStrength.score * 25
passwordSuggestion.value =
result.data.userPwdStrength.feedback.suggestions[0] || null
}
/**
* Do basic validation
*/
const validatePassword = () => {
if (!password.value) {
throw new Error('Password is empty')
}
if (password.value !== passwordConfirmation.value) {
throw new Error('Passwords do not match')
}
}
/**
* Asynchronously validate that the password is strong enough
*/
const validatePasswordStrength = async () => {
await updatePasswordStrength()
if ((passwordStrength.value || 0) < 50) {
throw new Error('Password too weak')
}
}
// Wipe old suggestion, if password is changed
watch(password, () => {
passwordStrength.value = 0
passwordSuggestion.value = null
})
return {
password,
passwordConfirmation,
passwordStrength,
passwordSuggestion,
updatePasswordStrength,
validatePassword,
validatePasswordStrength
}
}
@@ -1,10 +1,30 @@
import { ApolloError, FetchResult } from '@apollo/client/core'
import { isUndefinedOrVoid } from '@/helpers/typeHelpers'
import {
ApolloError,
FetchResult,
DataProxy,
ApolloCache,
defaultDataIdFromObject
} from '@apollo/client/core'
import { GraphQLError } from 'graphql'
/**
* Get a cached object's identifier
*/
export function getCacheId(typeName: string, id: string) {
const cachedId = defaultDataIdFromObject({
__typename: typeName,
id
})
if (!cachedId) throw new Error('Unable to build Apollo cache ID')
return cachedId
}
/**
* Convert an error thrown during $apollo.mutate() into a fetch result
*/
export function convertThrowIntoFetchResult(err: unknown): FetchResult {
export function convertThrowIntoFetchResult(err: unknown): FetchResult<undefined> {
let gqlErrors: readonly GraphQLError[]
if (err instanceof ApolloError) {
gqlErrors = err.graphQLErrors
@@ -19,3 +39,87 @@ export function convertThrowIntoFetchResult(err: unknown): FetchResult {
errors: gqlErrors
}
}
/**
* Get first error message from a GQL errors array
*/
export function getFirstErrorMessage(
errs: readonly GraphQLError[] | undefined | null,
fallbackMessage = 'An unexpected issue occurred'
): string {
return errs?.[0]?.message || fallbackMessage
}
/**
* Find some cached Apollo data through a fragment/query and use the updater function
* to return the replacement for the data that the fragment initially found
* @returns Whether an update was made
*/
export function updateCacheByFilter<TData, TVariables = unknown>(
cache: ApolloCache<unknown>,
filter: {
fragment?: DataProxy.Fragment<TVariables, TData>
query?: DataProxy.Query<TVariables, TData>
},
/**
* If returns undefined/void, then updating is essentially canceled. Be careful not to
* mutate anything being passed into this function! E.g. if you want to mutate arrays,
* create new arrays through slice()/filter() instead
*/
updater: (data: TData) => TData | undefined | void,
options: Partial<{
/**
* Whether to suppress errors that occur when the fragment being queried
* doesn't find anything
* Default: true
*/
ignoreCacheErrors: boolean
}> = {}
): boolean {
const { fragment, query } = filter
const { ignoreCacheErrors = true } = options
if (!fragment && !query) {
throw new Error(
'Either fragment or query must be specified to be able to find cached data to update'
)
}
const readData = (): TData | null => {
if (fragment) {
return cache.readFragment(fragment)
} else if (query) {
return cache.readQuery(query)
} else {
return null
}
}
const writeData = (data: TData): boolean => {
if (fragment) {
cache.writeFragment({ ...fragment, data })
return true
} else if (query) {
cache.writeQuery({ ...query, data })
return true
} else {
return false
}
}
try {
const currentData = readData()
if (!currentData) return false
const newData = updater(currentData)
if (isUndefinedOrVoid(newData)) return false
return writeData(newData)
} catch (e: unknown) {
if (ignoreCacheErrors) {
console.warn('Failed Apollo cache update', e)
return false
}
throw e
}
}
@@ -32,3 +32,13 @@ export function useIsLoggedIn() {
const isLoggedIn = computed(() => !!result.value?.user?.id)
return { isLoggedIn }
}
/**
* Get Vuetify
*/
export function useVuetify() {
const vm = getCurrentInstance()
if (!vm) throw new ComposableInvokedOutOfScopeError()
return vm.proxy.$vuetify
}
@@ -0,0 +1,110 @@
import { Nullable } from '@/helpers/typeHelpers'
import { useEventHub } from '@/main/lib/core/composables/core'
import {
GlobalEvents,
NotificationEventPayload,
ToastNotificationType
} from '@/main/lib/core/helpers/eventHubHelper'
import Vue, { computed, onMounted, onUnmounted, ref, watch } from 'vue'
const globalToastState = Vue.observable({
isInitialized: false,
queuedNotifications: [] as NotificationEventPayload[]
})
const isInitialized = () => !!globalToastState.isInitialized
const queuedNotifications = () => globalToastState.queuedNotifications
const resetQueue = () => (globalToastState.queuedNotifications = [])
const queueNotification = (e: NotificationEventPayload) => {
const notifications = queuedNotifications().slice()
notifications.push(e)
Vue.set(globalToastState, 'queuedNotifications', notifications)
}
/**
* Invoke this only in GlobalToast.vue to properly initialize it
*/
export function setupGlobalToast() {
const eventHub = useEventHub()
const snack = ref(false)
const text = ref(null as Nullable<string>)
const actionName = ref(null as Nullable<string>)
const to = ref(null as Nullable<string>)
const type = ref('primary' as ToastNotificationType)
const color = computed((): ToastNotificationType => type.value || 'primary')
watch(snack, (newVal) => {
if (!newVal) {
text.value = null
actionName.value = null
to.value = null
}
})
const handleEvent = (e: NotificationEventPayload) => {
snack.value = true
text.value = e.text
actionName.value = e.action ? e.action.name : null
to.value = e.action ? e.action.to : null
type.value = e.type || 'primary'
}
onMounted(() => {
Vue.set(globalToastState, 'isInitialized', true)
eventHub.$on(GlobalEvents.Notification, handleEvent)
const queue = queuedNotifications()
for (const queueItem of queue) {
handleEvent(queueItem)
}
resetQueue()
})
onUnmounted(() => {
Vue.set(globalToastState, 'isInitialized', false)
eventHub.$off(GlobalEvents.Notification, handleEvent)
})
return {
snack,
text,
actionName,
to,
type,
color
}
}
/**
* Trigger notification or queue it up to be triggered when GlobalToast.vue is ready
*/
export async function triggerToastNotification(
eventHub: Vue,
e: NotificationEventPayload
) {
if (isInitialized()) {
eventHub.$emit(GlobalEvents.Notification, e)
} else {
queueNotification(e)
}
}
/**
* Allows you to emit toast notifications
*/
export function useGlobalToast() {
const eventHub = useEventHub()
return {
/**
* Trigger a toast notification
*/
triggerNotification: (args: NotificationEventPayload) => {
triggerToastNotification(eventHub, args)
}
}
}
@@ -0,0 +1,9 @@
/**
* Activity stream item action types that shouldn't be displayed in the FE
*/
export const SKIPPABLE_ACTION_TYPES = <const>[
'stream_invite_sent',
'stream_invite_declined',
'stream_access_request_sent',
'stream_access_request_declined'
]
@@ -43,7 +43,7 @@ export const UsersStreamInviteMixin = vueWithMixins(IsLoggedInMixin).extend({
token(): Nullable<string> {
return this.streamInvite.token || this.inviteToken || null
},
streamInviter(): Nullable<Get<StreamInviteQuery, 'streamInvite.invitedBy'>> {
streamInviter(): NonNullable<Get<StreamInviteQuery, 'streamInvite.invitedBy'>> {
return this.streamInvite.invitedBy
},
hasInvite(): boolean {
+3 -3
View File
@@ -4,11 +4,11 @@
<div class="font-weight-bold">Feed</div>
</portal>
<v-row>
<v-col cols="12" lg="12">
<feed-timeline />
<v-col cols="12" class="d-flex">
<feed-timeline class="flex-grow-1" />
<latest-blogposts class="d-none d-lg-block ml-6" />
</v-col>
</v-row>
<latest-blogposts />
</div>
</template>
<script>
@@ -147,7 +147,7 @@ export default {
window.location.replace(
`${window.location.origin}/auth/accesscode?appId=${this.app.id}&challenge=${
this.$route.params.challenge
}&token=${AppLocalStorage.get('AuthToken')}&suuid=${this.$route.query.suuid}`
}&token=${AppLocalStorage.get('AuthToken')}`
)
}
}
@@ -20,23 +20,24 @@
<v-col cols="12">
<v-text-field
id="new-password"
v-model="form.password"
v-model="password"
label="new password"
type="password"
autocomplete="new-password"
:rules="validation.passwordRules"
:readonly="loading"
filled
single-line
style="margin-top: -12px"
@keydown="debouncedPwdTest"
/>
</v-col>
<v-col cols="12">
<v-text-field
v-model="form.passwordConf"
v-model="passwordConfirmation"
label="confirm new password"
type="password"
:rules="validation.passwordRules"
:readonly="loading"
filled
single-line
style="margin-top: -12px"
@@ -44,7 +45,7 @@
</v-col>
<v-col cols="12" class="py-2" style="margin-top: -18px">
<v-row
v-show="passwordStrength !== 1 && form.password"
v-show="passwordStrength !== 1 && password"
no-gutters
align="center"
>
@@ -68,16 +69,27 @@
</v-col>
<v-col cols="12" class="caption text-center mt-3">
{{
pwdSuggestions ? pwdSuggestions : form.password ? 'Looks good.' : null
passwordSuggestion
? passwordSuggestion
: password && password === passwordConfirmation
? 'Looks good.'
: null
}}
<span v-if="form.password !== form.passwordConf">
<div v-if="password !== passwordConfirmation">
<b>Passwords do not match.</b>
</span>
</div>
</v-col>
</v-row>
</v-col>
<v-col cols="12">
<v-btn type="submit" block large color="primary" @click="resetPassword()">
<v-btn
type="submit"
block
large
color="primary"
:disabled="loading"
@click="resetPassword()"
>
Save new password
</v-btn>
</v-col>
@@ -88,7 +100,7 @@
</template>
<script>
import { gql } from '@apollo/client/core'
import debounce from 'lodash/debounce'
import { useValidatablePasswordEntry } from '@/main/lib/auth/composables/useValidatablePasswordEntry'
export default {
name: 'ResetPasswordRequest',
@@ -117,22 +129,22 @@ export default {
`
}
},
setup() {
const validatablePasswordEntry = useValidatablePasswordEntry()
return {
...validatablePasswordEntry
}
},
data() {
return {
passwordStrength: 1,
pwdSuggestions: null,
form: {
password: null,
passwordConf: null
},
validation: {
passwordRules: [(v) => !!v || 'Required']
},
tokenId: null,
errors: false,
errorMessage: null,
success: false
success: false,
loading: false
}
},
computed: {
@@ -147,28 +159,24 @@ export default {
this.tokenId = this.$route.query.t
},
methods: {
debouncedPwdTest: debounce(async function () {
const result = await this.$apollo.query({
query: gql` query{ userPwdStrength(pwd:"${this.form.password}")}`
})
this.passwordStrength = result.data.userPwdStrength.score * 25
this.pwdSuggestions = result.data.userPwdStrength.feedback.suggestions[0]
}, 1000),
async resetPassword() {
if (this.loading) return
try {
this.success = false
this.errors = false
this.errorMessage = null
const valid = this.$refs.form.validate()
if (!valid) return
if (this.form.password !== this.form.passwordConf)
throw new Error('Passwords do not match')
if (this.passwordStrength < 3) throw new Error('Password too weak.')
this.validatePassword()
this.loading = true
await this.validatePasswordStrength()
const res = await fetch(`/auth/pwdreset/finalize`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tokenId: this.tokenId, password: this.form.password })
body: JSON.stringify({ tokenId: this.tokenId, password: this.password })
})
if (res.status !== 200) {
@@ -181,6 +189,8 @@ export default {
} catch (err) {
this.errorMessage = err.message
this.errors = true
} finally {
this.loading = false
}
}
}
@@ -19,12 +19,7 @@
<span class="hidden-md-and-up mr-2 primary--text">Speckle:</span>
Interoperability in seconds
</v-card-title>
<auth-strategies
:strategies="strategies"
:app-id="appId"
:challenge="challenge"
:suuid="suuid"
/>
<auth-strategies :strategies="strategies" :app-id="appId" :challenge="challenge" />
<div v-if="hasLocalStrategy">
<v-card-title class="justify-center pb-5 pt-0 body-1 text--secondary">
<v-divider class="mx-4"></v-divider>
@@ -155,7 +150,6 @@ export default {
errorMessage: '',
serverApp: null,
appId: null,
suuid: null,
challenge: null
}),
computed: {
@@ -174,7 +168,6 @@ export default {
query: {
appId: this.$route.query.appId,
challenge: this.$route.query.challenge,
suuid: this.$route.query.suuid,
token: this.token
}
}
@@ -184,8 +177,6 @@ export default {
const urlParams = new URLSearchParams(window.location.search)
const appId = urlParams.get('appId')
const challenge = urlParams.get('challenge')
const suuid = urlParams.get('suuid')
this.suuid = suuid
this.$mixpanel.track('Visit Log In')
@@ -210,8 +201,6 @@ export default {
password: this.form.password
}
if (this.suuid) user.suuid = this.suuid
const res = await fetch(`/auth/local/login?challenge=${this.challenge}`, {
method: 'POST',
headers: {
@@ -28,7 +28,6 @@
:strategies="strategies"
:app-id="appId"
:challenge="challenge"
:suuid="suuid"
/>
<v-card-title class="justify-center pb-5 pt-0 body-1 text--secondary">
<v-divider class="mx-4"></v-divider>
@@ -64,6 +63,7 @@
v-model="form.email"
label="your email"
:rules="validation.emailRules"
:readonly="loading"
filled
single-line
prepend-icon="mdi-email"
@@ -77,6 +77,7 @@
v-model="form.firstName"
label="name"
:rules="validation.nameRules"
:readonly="loading"
filled
single-line
style="margin-top: -12px"
@@ -88,6 +89,7 @@
v-model="form.company"
label="company/team"
:rules="validation.companyRules"
:readonly="loading"
filled
single-line
style="margin-top: -12px"
@@ -97,11 +99,12 @@
<v-col cols="12" sm="6">
<v-text-field
id="new-password"
v-model="form.password"
v-model="password"
label="password"
type="password"
autocomplete="new-password"
:rules="validation.passwordRules"
:readonly="loading"
filled
single-line
style="margin-top: -12px"
@@ -111,11 +114,12 @@
<v-col cols="12" sm="6">
<v-text-field
id="confirm-password"
v-model="form.passwordConf"
v-model="passwordConfirmation"
label="confirm password"
type="password"
autocomplete="new-password"
:rules="validation.passwordRules"
:readonly="loading"
filled
single-line
style="margin-top: -12px"
@@ -123,7 +127,7 @@
</v-col>
<v-col cols="12" class="py-2 pl-9" style="margin-top: -18px">
<v-row
v-show="passwordStrength !== 1 && form.password"
v-show="passwordStrength !== 1 && password"
no-gutters
align="center"
>
@@ -137,9 +141,9 @@
height="5"
class="mt-1 mb-0"
:color="`${
passwordStrength >= 75 && form.password === form.passwordConf
passwordStrength >= 75 && password === passwordConfirmation
? 'green'
: passwordStrength >= 50 && form.password === form.passwordConf
: passwordStrength >= 50 && password === passwordConfirmation
? 'orange'
: 'red'
}`"
@@ -147,15 +151,15 @@
</v-col>
<v-col cols="12" class="caption text-center mt-3">
{{
pwdSuggestions
? pwdSuggestions
: form.password && form.password === form.passwordConf
passwordSuggestion
? passwordSuggestion
: password && password === passwordConfirmation
? 'Looks good.'
: null
}}
<span v-if="form.password !== form.passwordConf">
<div v-if="password !== passwordConfirmation">
<b>Passwords do not match.</b>
</span>
</div>
</v-col>
</v-row>
</v-col>
@@ -196,6 +200,7 @@ import {
processSuccessfulAuth
} from '@/main/lib/auth/services/authService'
import { AppLocalStorage } from '@/utils/localStorage'
import { useValidatablePasswordEntry } from '@/main/lib/auth/composables/useValidatablePasswordEntry'
export default {
name: 'TheRegistration',
@@ -226,6 +231,12 @@ export default {
`
}
},
setup() {
const validatablePasswordEntry = useValidatablePasswordEntry()
return {
...validatablePasswordEntry
}
},
data() {
return {
serverInfo: { authStrategies: [] },
@@ -233,9 +244,7 @@ export default {
email: null,
firstName: null,
lastName: null,
company: null,
password: null,
passwordConf: null
company: null
},
registrationError: false,
errorMessage: '',
@@ -251,11 +260,8 @@ export default {
(v) => isEmailValid(v) || 'E-mail must be valid'
]
},
passwordStrength: 1,
pwdSuggestions: null,
appId: null,
challenge: null,
suuid: null,
loading: false
}
},
@@ -269,7 +275,6 @@ export default {
query: {
appId: this.$route.query.appId,
challenge: this.$route.query.challenge,
suuid: this.$route.query.suuid,
token: this.token
}
}
@@ -285,8 +290,6 @@ export default {
const urlParams = new URLSearchParams(window.location.search)
const appId = urlParams.get('appId')
const challenge = urlParams.get('challenge')
const suuid = urlParams.get('suuid')
this.suuid = suuid
this.$mixpanel.track('Visit Sign Up')
@@ -301,42 +304,25 @@ export default {
}
},
methods: {
async validatePasswordStrength() {
const result = await this.$apollo.query({
query: gql`
query ($pwd: String!) {
userPwdStrength(pwd: $pwd)
}
`,
variables: { pwd: this.form.password }
})
this.passwordStrength = result.data.userPwdStrength.score * 25
this.pwdSuggestions = result.data.userPwdStrength.feedback.suggestions[0]
},
async registerUser() {
if (this.loading) return
try {
const valid = this.$refs.form.validate()
if (!valid) return
if (this.form.password !== this.form.passwordConf)
throw new Error('Passwords do not match')
this.validatePassword()
this.loading = true
// Validate password strength
await this.validatePasswordStrength()
if (this.passwordStrength < 3) throw new Error('Password too weak')
const user = {
email: this.form.email,
company: this.form.company,
password: this.form.password,
password: this.password,
name: `${this.form.firstName}`
}
if (this.suuid) user.suuid = this.suuid
const res = await fetch(
`/auth/local/register?challenge=${this.challenge}${
this.token ? '&token=' + this.token : ''
@@ -1,15 +1,22 @@
<template>
<div class="commit-object-viewer">
<div v-if="(isMultiple || isCommit || isObject) && !singleResourceError">
<div
v-if="
firstResource && (isMultiple || isCommit || isObject) && !singleResourceError
"
>
<commit-toolbar
v-if="isCommit"
:stream="resources[0].data"
v-if="isCommitResource(firstResource)"
:stream="firstResource.data"
@edit-commit="showCommitEditDialog = true"
/>
<object-toolbar v-if="isObject" :stream="resources[0].data" />
<object-toolbar
v-if="isObjectResource(firstResource)"
:stream="firstResource.data"
/>
<multiple-resources-toolbar
v-if="isMultiple"
:stream="{ name: resources[0].data.name, id: streamId }"
v-if="isMultiple && isNotErrorResource(firstResource)"
:stream="{ name: firstResource.data.name, id: streamId }"
:resources="resources"
/>
@@ -27,9 +34,9 @@
</div>
<v-list nav dense class="mt-0 pt-0">
<v-list-item
v-if="isCommit"
v-if="isCommitResource(firstResource)"
link
:to="`/streams/${streamId}/branches/${resources[0].data.commit.branchName}`"
:to="`/streams/${streamId}/branches/${firstResource.data.commit?.branchName}`"
class=""
>
<v-list-item-icon>
@@ -38,7 +45,7 @@
<v-list-item-content>
<v-list-item-title class="font-weight-bold">
<v-icon small class="mr-1 caption">mdi-source-branch</v-icon>
{{ resources[0].data.commit.branchName }}
{{ firstResource.data.commit?.branchName }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
@@ -81,8 +88,8 @@
class="mt-4"
:source-application="
resources
.filter((r) => r.type === 'commit')
.map((r) => r.data.commit.sourceApplication)
.filter(isCommitResource)
.map((r) => r.data.commit?.sourceApplication)
.join(',')
"
/>
@@ -92,7 +99,10 @@
<!-- Preview image -->
<v-fade-transition>
<preview-image
v-if="!loadedModel && (isCommit || isObject)"
v-if="
!loadedModel &&
(isCommitResource(firstResource) || isObjectResource(firstResource))
"
:style="`
height: 100vh;
width: 100%;
@@ -104,9 +114,9 @@
`"
:height="420"
:url="`/preview/${streamId}/objects/${
isCommit
? resources[0].data.commit.referencedObject
: resources[0].data.object.id
isCommitResource(firstResource)
? firstResource.data.commit?.referencedObject
: firstResource.data.object?.id
}`"
></preview-image>
</v-fade-transition>
@@ -141,10 +151,11 @@
:style="`width: 100%; bottom: 12px; left: 0px; position: ${
$isMobile() ? 'fixed' : 'absolute'
}; z-index: 20`"
:class="`d-flex justify-center`"
:class="`d-flex justify-center no-mouse`"
>
<viewer-controls
v-show="!hideControls"
class="mouse"
@show-add-overlay="showAddOverlay = true"
/>
</div>
@@ -229,12 +240,12 @@
/>
</v-dialog>
<v-dialog
v-if="isCommit"
v-if="firstResource && isCommitResource(firstResource)"
v-model="showCommitEditDialog"
width="500"
:fullscreen="$vuetify.breakpoint.smAndDown"
>
<commit-edit :stream="resources[0].data" @close="showCommitEditDialog = false" />
<commit-edit :stream="firstResource.data" @close="showCommitEditDialog = false" />
</v-dialog>
</div>
</template>
@@ -277,10 +288,9 @@ type CommitResourceData = NonNullable<Get<StreamCommitQueryQuery, 'stream'>>
type ObjectResourceData = NonNullable<Get<StreamObjectNoDataQuery, 'stream'>>
type AllSupportedDataTypes =
| ErroredResourceData
| CommitResourceData
| ObjectResourceData
type NonErrorDataTypes = CommitResourceData | ObjectResourceData
type AllSupportedDataTypes = ErroredResourceData | NonErrorDataTypes
type ResourceTypeValue = 'commit' | 'object'
@@ -294,6 +304,10 @@ const isErrorResource = (
resource: ResourceObjectType<unknown>
): resource is ResourceObjectType<ErroredResourceData> => has(resource.data, 'error')
const isNotErrorResource = (
resource: ResourceObjectType<unknown>
): resource is ResourceObjectType<NonErrorDataTypes> => !isErrorResource(resource)
const isCommitResource = (
resource: ResourceObjectType<unknown>
): resource is ResourceObjectType<CommitResourceData> => resource.type === 'commit'
@@ -375,7 +389,11 @@ export default defineComponent({
return {
viewer,
viewerState
viewerState,
isCommitResource,
isObjectResource,
isErrorResource,
isNotErrorResource
}
},
data: () => ({
@@ -397,22 +415,21 @@ export default defineComponent({
return !this.$vuetify.breakpoint.smAndDown ? 'top: -64px;' : 'top: -56px;'
},
isCommit(): boolean {
if (this.resources.length === 0) return false
if (this.resources.length === 1 && this.resources[0].type === 'commit')
return true
return false
if (this.isMultiple) return false
return this.firstResource?.type === 'commit'
},
isObject(): boolean {
if (this.resources.length === 0) return false
if (this.resources.length === 1 && this.resources[0].type === 'object')
return true
return false
if (this.isMultiple) return false
return this.firstResource?.type === 'object'
},
isMultiple(): boolean {
if (this.resources.length === 0) return false
if (this.resources.length > 1) return true
return false
},
firstResource(): Nullable<ResourceObjectType<AllSupportedDataTypes>> {
return this.resources[0] || null
},
singleResourceError(): boolean {
if (this.resources.length !== 1) return false
const resource = this.resources[0]
@@ -21,8 +21,8 @@
</portal>
<v-row v-if="stream" justify="center">
<v-col v-if="serverInfo && stream" cols="12">
<!-- Add contributors panel -->
<v-row>
<!-- Add contributors panel -->
<v-col v-if="isStreamOwner" cols="12">
<section-card :elevation="4">
<v-progress-linear v-show="loading" indeterminate></v-progress-linear>
@@ -97,6 +97,20 @@
</v-alert>
</v-col>
<!-- Stream access requests -->
<v-col
v-if="
isStreamOwner && pendingAccessRequests && pendingAccessRequests.length
"
cols="12"
>
<stream-access-request-banner
v-for="req in pendingAccessRequests"
:key="req.id"
:request="req"
/>
</v-col>
<!-- Current users/invites for each role - owner, contributor, reviewer -->
<v-col v-for="role in roles" :key="role.name" cols="12" md="4">
<stream-role-collaborators
@@ -146,6 +160,7 @@ import { IsLoggedInMixin } from '@/main/lib/core/mixins/isLoggedInMixin'
import { vueWithMixins } from '@/helpers/typeHelpers'
import { convertThrowIntoFetchResult } from '@/main/lib/common/apollo/helpers/apolloOperationHelper'
import { AppLocalStorage } from '@/utils/localStorage'
import StreamAccessRequestBanner from '@/main/components/stream/StreamAccessRequestBanner.vue'
export default vueWithMixins(IsLoggedInMixin).extend({
// @vue/component
@@ -155,7 +170,8 @@ export default vueWithMixins(IsLoggedInMixin).extend({
InviteDialog,
BasicUserInfoRow,
StreamRoleCollaborators,
LeaveStreamPanel
LeaveStreamPanel,
StreamAccessRequestBanner
},
mixins: [
buildPortalStateMixin([STANDARD_PORTAL_KEYS.Toolbar], 'stream-collaborators', 1)
@@ -255,6 +271,9 @@ export default vueWithMixins(IsLoggedInMixin).extend({
if (!this.stream) return []
return this.stream.collaborators.filter((u) => u.role === 'stream:owner')
},
pendingAccessRequests() {
return this.stream?.pendingAccessRequests
},
filteredSearchResults() {
if (!this.userSearch) return null
const users = []
@@ -6,7 +6,7 @@
>
<!-- BG image -->
<div
v-if="resourceMetadata && !isModelLoaded"
v-if="previewUrl && resourceMetadata && !isModelLoaded"
style="position: fixed; top: 0; width: 100%; height: 100%; cursor: pointer"
class="embed-bg"
@click="load()"
@@ -34,7 +34,7 @@
<!-- This should always be conditionally and asynchronously loaded so that heavy viewer deps are lazy loaded -->
<embedded-commit-object-viewer
v-if="shouldLoadHeavyDeps"
v-if="resourceMetadata && shouldLoadHeavyDeps"
:stream-id="streamId"
:resource-id="resourceMetadata.resourceId"
@models-loaded="onModelsLoaded"
@@ -51,18 +51,10 @@
:disabled="stream.role !== 'stream:owner'"
/>
<h2>Privacy</h2>
<v-switch
v-model="isPublic"
inset
class="mt-5"
:label="isPublic ? 'Link Sharing On' : 'Link Sharing Off'"
:hint="
isPublic
? 'Anyone with the link can view this stream. It is also visible on your profile page. Only collaborators can push data to it.'
: 'Only collaborators can access this stream.'
"
persistent-hint
:disabled="stream.role !== 'stream:owner'"
<stream-visibility-toggle
:disabled="isEditDisabled"
:is-public.sync="isPublic"
:is-discoverable.sync="isDiscoverable"
/>
<br />
<h2>Comments</h2>
@@ -179,11 +171,14 @@ import {
STANDARD_PORTAL_KEYS,
buildPortalStateMixin
} from '@/main/utils/portalStateManager'
import SectionCard from '@/main/components/common/SectionCard.vue'
import StreamVisibilityToggle from '@/main/components/stream/editor/StreamVisibilityToggle.vue'
export default {
name: 'TheSettings',
components: {
SectionCard: () => import('@/main/components/common/SectionCard')
SectionCard,
StreamVisibilityToggle
},
mixins: [buildPortalStateMixin([STANDARD_PORTAL_KEYS.Toolbar], 'stream-settings', 1)],
apollo: {
@@ -195,6 +190,7 @@ export default {
name
description
isPublic
isDiscoverable
allowPublicComments
role
}
@@ -213,6 +209,7 @@ export default {
name: this.name,
description: this.description,
isPublic: this.isPublic,
isDiscoverable: this.isDiscoverable,
allowPublicComments: this.allowPublicComments
} = stream)
@@ -230,6 +227,7 @@ export default {
streamNameConfirm: '',
description: null,
isPublic: true,
isDiscoverable: false,
allowPublicComments: true,
validation: {
nameRules: [(v) => !!v || 'A stream must have a name!']
@@ -238,13 +236,17 @@ export default {
computed: {
canSave() {
return (
this.stream.role === 'stream:owner' &&
!this.isEditDisabled &&
this.valid &&
(this.name !== this.stream.name ||
this.description !== this.stream.description ||
this.isPublic !== this.stream.isPublic ||
this.allowPublicComments !== this.stream.allowPublicComments)
this.allowPublicComments !== this.stream.allowPublicComments ||
this.isDiscoverable !== this.stream.isDiscoverable)
)
},
isEditDisabled() {
return this.stream?.role !== 'stream:owner'
}
},
watch: {
@@ -272,7 +274,8 @@ export default {
name: this.name,
description: this.description,
isPublic: this.isPublic,
allowPublicComments: this.allowPublicComments
allowPublicComments: this.allowPublicComments,
isDiscoverable: this.isDiscoverable
}
}
})
@@ -22,15 +22,43 @@
</transition>
</div>
<div v-else style="width: 100%">
<error-placeholder v-if="!showInvitePlaceholder" :error-type="errorType">
<h2>{{ errorMsg }}</h2>
</error-placeholder>
<stream-invite-placeholder
v-else
v-if="showInvitePlaceholder"
:stream-invite="streamInvite"
:invite-token="inviteToken"
@invite-used="onInviteClosed"
/>
<error-placeholder v-else :error-type="errorType">
<template #default>
<h2>{{ errorMsg }}</h2>
</template>
<template v-if="allowRequestAccess" #actions>
<rounded-button-list>
<rounded-button-list-item
type="primary"
icon="mdi-lock-outline"
@click="onRequestAccess"
>
<span v-if="hasStreamAccessRequest">Access Request sent</span>
<span v-else>Request Access to Stream</span>
<template #subtitle>
<span v-if="hasStreamAccessRequest">
You will get a confirmation email once it's been approved
</span>
<span v-else>Request Access from the stream owners</span>
</template>
</rounded-button-list-item>
<rounded-button-list-item
type="secondary"
icon="mdi-home-outline"
@click="onBackToStreams"
>
Back to your Streams
</rounded-button-list-item>
</rounded-button-list>
</template>
</error-placeholder>
</div>
</v-col>
</v-row>
@@ -40,7 +68,7 @@
import { gql } from '@apollo/client/core'
import StreamInviteBanner from '@/main/components/stream/StreamInviteBanner.vue'
import { StreamEvents } from '@/main/lib/core/helpers/eventHubHelper'
import Vue from 'vue'
import Vue, { defineComponent, computed } from 'vue'
import { Nullable, MaybeFalsy } from '@/helpers/typeHelpers'
import {
StreamInviteDocument,
@@ -48,13 +76,24 @@ import {
MainUserDataQuery,
StreamQuery,
StreamQueryVariables,
StreamDocument
StreamDocument,
CreateStreamAccessRequestDocument,
GetStreamAccessRequestDocument
} from '@/graphql/generated/graphql'
import type { ApolloQueryResult, ApolloError } from '@apollo/client/core'
import type { Get } from 'type-fest'
import StreamInvitePlaceholder from '@/main/components/stream/StreamInvitePlaceholder.vue'
import { StreamInviteType } from '@/main/lib/stream/mixins/streamInviteMixin'
import { getInviteTokenFromRoute } from '@/main/lib/auth/services/authService'
import RoundedButtonList from '@/main/components/common/layout/RoundedButtonList.vue'
import RoundedButtonListItem from '@/main/components/common/layout/rounded-button-list/RoundedButtonListItem.vue'
import {
convertThrowIntoFetchResult,
getFirstErrorMessage
} from '@/main/lib/common/apollo/helpers/apolloOperationHelper'
import { useIsLoggedIn } from '@/main/lib/core/composables/core'
import { useQuery } from '@vue/apollo-composable'
import { useRoute } from '@/main/lib/core/composables/router'
// Cause of a limitation of Vue Apollo Options API TS types, this needs to be duplicated
// (the better option is to just use the Composition API)
@@ -62,16 +101,39 @@ type VueThis = Vue & {
streamId: string
inviteToken: Nullable<string>
error: Nullable<Error>
isLoggedIn: boolean
}
export default Vue.extend({
export default defineComponent({
name: 'TheStream',
components: {
ErrorPlaceholder: () => import('@/main/components/common/ErrorPlaceholder.vue'),
StreamNav: () => import('@/main/navigation/StreamNav.vue'),
StreamToolbar: () => import('@/main/toolbars/StreamToolbar.vue'),
StreamInviteBanner,
StreamInvitePlaceholder
StreamInvitePlaceholder,
RoundedButtonList,
RoundedButtonListItem
},
setup() {
const route = useRoute()
const streamId = computed(() => route.params.streamId as string)
const { isLoggedIn } = useIsLoggedIn()
const { result: streamAccessRequestResult } = useQuery(
GetStreamAccessRequestDocument,
() => ({ streamId: streamId.value })
)
const hasStreamAccessRequest = computed(
() => !!streamAccessRequestResult.value?.streamAccessRequest?.id
)
return {
isLoggedIn,
streamId,
hasStreamAccessRequest
}
},
data() {
return {
@@ -80,16 +142,14 @@ export default Vue.extend({
streamInvite: null as Nullable<StreamInviteType>,
shareStream: false,
branchMenuOpen: false,
inviteClosed: false
inviteClosed: false,
stream: null as Nullable<StreamQuery>
}
},
computed: {
inviteToken(): Nullable<string> {
return getInviteTokenFromRoute(this.$route)
},
streamId(): string {
return this.$route.params.streamId
},
errorMsg(): MaybeFalsy<string> {
return this.error?.message.replace('GraphQL error: ', '')
},
@@ -113,6 +173,9 @@ export default Vue.extend({
showInvitePlaceholder(): boolean {
return !!(this.hasInvite && this.isAccessError)
},
allowRequestAccess(): boolean {
return !!(this.isAccessError && this.isLoggedIn)
},
hasInvite(): boolean {
return !!(this.streamInvite && !this.inviteClosed)
}
@@ -172,7 +235,7 @@ export default Vue.extend({
})
},
skip(this: VueThis): boolean {
return !this.$loggedIn()
return !this.isLoggedIn
}
},
commitCreated: {
@@ -200,7 +263,7 @@ export default Vue.extend({
})
},
skip(this: VueThis): boolean {
return !this.$loggedIn()
return !this.isLoggedIn
}
}
}
@@ -214,6 +277,43 @@ export default Vue.extend({
onInviteClosed() {
this.inviteClosed = true
this.error = null
},
onBackToStreams() {
this.$router.push('/streams')
},
async onRequestAccess() {
if (this.hasStreamAccessRequest) return
const { data, errors } = await this.$apollo
.mutate({
mutation: CreateStreamAccessRequestDocument,
variables: {
streamId: this.streamId
},
update: (cache, { data }) => {
if (!data?.streamAccessRequestCreate.id) return
// Update GetStreamAccessRequest query
const newReq = data.streamAccessRequestCreate
cache.writeQuery({
query: GetStreamAccessRequestDocument,
variables: { streamId: this.streamId },
data: { streamAccessRequest: { ...newReq } }
})
}
})
.catch(convertThrowIntoFetchResult)
if (data?.streamAccessRequestCreate.id) {
this.$triggerNotification({
text: 'A stream access request has been submitted'
})
} else {
this.$triggerNotification({
text: getFirstErrorMessage(errors),
type: 'error'
})
}
}
}
})
@@ -70,7 +70,6 @@ export default {
verified
profiles
role
suuid
totalOwnedStreamsFavorites
streams {
totalCount
+2 -2
View File
@@ -33,7 +33,7 @@ const routes = [
path: 'resetpassword',
name: 'Reset Password',
meta: {
title: 'Register | Speckle'
title: 'Reset Password | Speckle'
},
component: () => import('@/main/pages/auth/ResetPasswordRequest.vue')
},
@@ -41,7 +41,7 @@ const routes = [
path: 'resetpassword/finalize',
name: 'Reset Password Finalization',
meta: {
title: 'Register | Speckle'
title: 'Reset Password | Speckle'
},
component: () => import('@/main/pages/auth/ResetPasswordFinalization.vue')
},
+5 -9
View File
@@ -31,11 +31,11 @@ export async function checkAccessCodeAndGetTokens() {
}
/**
* Gets the user id and suuid, sets them in local storage
* Gets the user id and sets it in localStorage
* @param {import('@apollo/client/core').ApolloClient} apolloClient
* @return {Object} The full graphql response.
*/
export async function prefetchUserAndSetSuuid(apolloClient) {
export async function prefetchUserAndSetID(apolloClient) {
const token = AppLocalStorage.get(LocalStorageKeys.AuthToken)
if (!token) return
@@ -45,11 +45,8 @@ export async function prefetchUserAndSetSuuid(apolloClient) {
})
if (data.user) {
// eslint-disable-next-line camelcase
const distinct_id = '@' + md5(data.user.email.toLowerCase()).toUpperCase()
AppLocalStorage.set('suuid', data.user.suuid)
AppLocalStorage.set('distinct_id', distinct_id)
const distinctId = '@' + md5(data.user.email.toLowerCase()).toUpperCase()
AppLocalStorage.set('distinct_id', distinctId)
AppLocalStorage.set('uuid', data.user.id)
AppLocalStorage.set('stcount', data.user.streams.totalCount)
return data
@@ -95,7 +92,6 @@ export async function signOut(mixpanelInstance) {
AppLocalStorage.remove(LocalStorageKeys.AuthToken)
AppLocalStorage.remove(LocalStorageKeys.RefreshToken)
AppLocalStorage.remove('suuid')
AppLocalStorage.remove('uuid')
AppLocalStorage.remove('distinct_id')
AppLocalStorage.remove('stcount')
@@ -131,7 +127,7 @@ export async function refreshToken() {
if (data.hasOwnProperty('token')) {
AppLocalStorage.set(LocalStorageKeys.AuthToken, data.token)
AppLocalStorage.set(LocalStorageKeys.RefreshToken, data.refreshToken)
await prefetchUserAndSetSuuid()
await prefetchUserAndSetID()
return true
}
}
+2 -1
View File
@@ -4,6 +4,7 @@ import { NotificationEventPayload } from '@/main/lib/core/helpers/eventHubHelper
import { AppLocalStorage } from '@/utils/localStorage'
import { LocalStorageKeys } from '@/helpers/mainConstants'
import { getInviteTokenFromURL } from '@/main/lib/auth/services/authService'
import { triggerToastNotification } from '@/main/lib/core/composables/notifications'
Vue.prototype.$userId = function () {
return AppLocalStorage.get(LocalStorageKeys.Uuid)
@@ -46,5 +47,5 @@ Vue.prototype.$loginAndSetRedirect = function () {
* Trigger a toast notification from anywhere
*/
Vue.prototype.$triggerNotification = function (args: NotificationEventPayload) {
this.$eventHub.$emit('notification', args)
triggerToastNotification(this.$eventHub, args)
}
@@ -267,3 +267,10 @@ html {
opacity: 0.5;
width: 0px;
}
.no-mouse {
pointer-events: none;
}
.mouse {
pointer-events: auto;
}
@@ -1,4 +0,0 @@
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
+10
View File
@@ -1,4 +1,6 @@
declare module 'vue/types/vue' {
import { Nullable } from '@/helpers/typeHelpers'
export interface Vue {
/**
* Mixpanel instance
@@ -16,6 +18,14 @@ declare module 'vue/types/vue' {
args: import('@/main/lib/core/helpers/eventHubHelper').NotificationEventPayload
): void
$userId: () => Nullable<string>
/**
* Whether the client seems to be a mobile device. Note that this doesn't check for screen size, only
* whether this is a mobile device.
*/
$isMobile: () => boolean
/**
* Redirect to log in and redirect back to current page post-login
*/
+3
View File
@@ -27,6 +27,9 @@
"@types/dompurify"
]
},
"vueCompilerOptions": {
"target": 2.7
},
"include": [
"src/**/*.ts",
"src/**/*.js",
+1 -1
View File
@@ -11,7 +11,7 @@
"directory": "packages/objectloader"
},
"engines": {
"node": "^16.0.0"
"node": ">=14.0.0"
},
"scripts": {
"lint": "eslint . --ext .js,.ts",
+8
View File
@@ -59,6 +59,14 @@ EMAIL_PORT="1025"
# EMAIL_PASSWORD="-> FILL IN <-"
# EMAIL_FROM="-> FILL IN <-"
############################################################
# Notifications
# Settings related to the MQ based notifications module
############################################################
# If set to true, will prevent the server from consuming notification jobs
DISABLE_NOTIFICATIONS_CONSUMPTION=false
############################################################
# Auth strategies
# At least one needs to be enabled!
+15 -6
View File
@@ -14,12 +14,6 @@ const config = {
},
ignorePatterns: ['node_modules', 'dist', 'generated/**/*'],
overrides: [
{
files: '*.spec.{js,ts}',
env: {
mocha: true
}
},
{
files: '*.ts',
plugins: ['@typescript-eslint'],
@@ -32,6 +26,21 @@ const config = {
'prettier'
],
parser: '@typescript-eslint/parser'
},
{
files: '*.d.ts',
rules: {
'@typescript-eslint/no-explicit-any': 'off'
}
},
{
files: '*.spec.{js,ts}',
env: {
mocha: true
},
rules: {
'@typescript-eslint/no-non-null-assertion': 'off'
}
}
]
}
+6 -3
View File
@@ -29,7 +29,6 @@ import { buildErrorFormatter } from '@/modules/core/graph/setup'
import { isDevEnv, isTestEnv } from '@/modules/shared/helpers/envHelper'
import * as ModulesSetup from '@/modules'
import { Optional } from '@/modules/shared/helpers/typeHelper'
import apolloPlugin from '@/logging/apolloPlugin'
import { get, has, isString, toNumber } from 'lodash'
@@ -100,7 +99,7 @@ export function buildApolloServer(
metricConnectedClients.dec()
}
},
plugins: [apolloPlugin],
plugins: [require('@/logging/apolloPlugin')],
tracing: debug,
introspection: true,
playground: true,
@@ -159,6 +158,10 @@ export async function init() {
return { app, graphqlServer }
}
export async function shutdown(): Promise<void> {
await ModulesSetup.shutdown()
}
/**
* Starts a http server, hoisting the express app to it.
*/
@@ -211,7 +214,7 @@ export async function startHttp(app: Express, customPortOverride?: number) {
debug('speckle:shutdown')('Shutting down (signal received)...')
},
onSignal: async () => {
// Other custom cleanup after connections are finished
await shutdown()
},
onShutdown: () => {
debug('speckle:shutdown')('Shutdown completed')
@@ -0,0 +1,47 @@
extend type Query {
"""
Get authed user's stream access request
"""
streamAccessRequest(streamId: String!): StreamAccessRequest
@hasRole(role: "server:user")
}
extend type Stream {
"""
Pending stream access requests
"""
pendingAccessRequests: [StreamAccessRequest!] @hasStreamRole(role: STREAM_OWNER)
}
extend type Mutation {
"""
Accept or decline a stream access request. Must be a stream owner to invoke this.
"""
streamAccessRequestUse(
requestId: String!
accept: Boolean!
role: StreamRole! = STREAM_CONTRIBUTOR
): Boolean! @hasRole(role: "server:user") @hasScope(scope: "users:invite")
"""
Request access to a specific stream
"""
streamAccessRequestCreate(streamId: String!): StreamAccessRequest!
@hasRole(role: "server:user")
@hasScope(scope: "users:invite")
}
"""
Created when a user requests to become a contributor on a stream
"""
type StreamAccessRequest {
id: ID!
requester: LimitedUser!
requesterId: String!
streamId: String!
"""
Can only be selected if authed user has proper access
"""
stream: Stream!
createdAt: DateTime!
}
@@ -26,3 +26,8 @@ type SmartTextEditorValue {
"""
attachments: [BlobMetadata!]
}
enum SortDirection {
ASC
DESC
}
@@ -11,6 +11,9 @@ extend type Query {
streams(query: String, limit: Int = 25, cursor: String): StreamCollection
@hasScope(scope: "streams:read")
"""
All the streams of the server. Available to admins only.
"""
adminStreams(
offset: Int = 0
query: String
@@ -18,13 +21,33 @@ extend type Query {
visibility: String
limit: Int = 25
): StreamCollection @hasRole(role: "server:admin")
"""
All of the discoverable streams of the server
"""
discoverableStreams(
limit: Int! = 25
cursor: String
"""
Defaults to sorting by creation date in a descending order
"""
sort: DiscoverableStreamsSortingInput
): StreamCollection
}
type Stream {
id: String!
name: String!
description: String
"""
Whether the stream can be viewed by non-contributors
"""
isPublic: Boolean!
"""
Whether the stream (if public) can be found on public stream exploration pages
and searches
"""
isDiscoverable: Boolean!
allowPublicComments: Boolean!
"""
Your role for this stream. `null` if request is not authenticated, or the stream is not explicitly shared with you.
@@ -189,8 +212,16 @@ extend type Subscription {
input StreamCreateInput {
name: String
description: String
"""
Whether the stream can be viewed by non-contributors
"""
isPublic: Boolean
"""
Whether the stream (if public) can be found on public stream exploration pages
and searches
"""
isDiscoverable: Boolean
"""
Optionally specify user IDs of users that you want to invite to be contributors to this stream
"""
withContributors: [String!]
@@ -200,7 +231,15 @@ input StreamUpdateInput {
id: String!
name: String
description: String
"""
Whether the stream can be viewed by non-contributors
"""
isPublic: Boolean
"""
Whether the stream (if public) can be found on public stream exploration pages
and searches
"""
isDiscoverable: Boolean
allowPublicComments: Boolean
}
@@ -214,3 +253,13 @@ input StreamRevokePermissionInput {
streamId: String!
userId: String!
}
input DiscoverableStreamsSortingInput {
type: DiscoverableStreamsSortType!
direction: SortDirection!
}
enum DiscoverableStreamsSortType {
FAVORITES_COUNT
CREATED_DATE
}
@@ -1,7 +1,6 @@
extend type Query {
"""
Gets the profile of a user. If no id argument is provided, will return the current authenticated user's profile (as extracted from the authorization header).
If ID is provided, admin access is required
"""
user(id: String): User
@@ -27,7 +26,33 @@ extend type Query {
cursor: String
archived: Boolean = false
): UserSearchResultCollection
userPwdStrength(pwd: String!): JSONObject
"""
Validate password strength
"""
userPwdStrength(pwd: String!): PasswordStrengthCheckResults!
}
type PasswordStrengthCheckResults {
"""
Integer from 0-4 (useful for implementing a strength bar):
0 too guessable: risky password. (guesses < 10^3)
1 very guessable: protection from throttled online attacks. (guesses < 10^6)
2 somewhat guessable: protection from unthrottled online attacks. (guesses < 10^8)
3 safely unguessable: moderate protection from offline slow-hash scenario. (guesses < 10^10)
4 very unguessable: strong protection from offline slow-hash scenario. (guesses >= 10^10)
"""
score: Int!
"""
Verbal feedback to help choose better passwords. set when score <= 2.
"""
feedback: PasswordStrengthCheckFeedback!
}
type PasswordStrengthCheckFeedback {
warning: String
suggestions: [String!]!
}
"""
@@ -36,7 +61,6 @@ when a user is reading/writing info about himself
"""
type User {
id: String!
suuid: String
"""
E-mail can be null, if it's requested for a user other than the authenticated one
and the user isn't an admin
@@ -90,7 +90,7 @@
</head>
<body style="word-spacing: normal">
<div style="">
<div>
<!-- Header -->
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="linear-gradient(90deg, rgba(0,143,233,1) 0%, rgba(0,76,235,1) 100%)" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div
@@ -265,7 +265,7 @@
style="
font-family: Helvetica;
font-size: 16px;
line-height: 20px;
line-height: 1;
text-align: left;
color: #000000;
"
@@ -375,7 +375,7 @@
font-family: Helvetica;
font-size: 16px;
font-weight: bold;
line-height: 20px;
line-height: 120%;
margin: 0;
text-decoration: none;
text-transform: none;
@@ -462,7 +462,7 @@
style="
font-family: Helvetica;
font-size: 16px;
line-height: 20px;
line-height: 1;
text-align: left;
color: #000000;
"
@@ -0,0 +1,13 @@
extend type User {
"""
Whether the user has a pending/active email verification token
"""
hasPendingVerification: Boolean @isOwner
}
extend type Mutation {
"""
(Re-)send the account verification e-mail
"""
requestVerification: Boolean! @hasRole(role: "server:user")
}
+14
View File
@@ -2,18 +2,32 @@ overwrite: true
schema:
- 'modules/core/graph/schema/baseTypeDefs.js'
- 'assets/**/*.graphql'
- 'assets/**/*.gql'
documents: null
generates:
modules/core/graph/generated/graphql.ts:
plugins:
- 'typescript'
- 'typescript-resolvers'
config:
contextType: '@/modules/shared/helpers/typeHelper#GraphQLContext'
mappers:
Stream: '@/modules/core/helpers/graphTypes#StreamGraphQLReturn'
StreamAccessRequest: '@/modules/accessrequests/helpers/graphTypes#StreamAccessRequestGraphQLReturn'
test/graphql/generated/graphql.ts:
plugins:
- 'typescript'
- 'typescript-operations'
documents:
- 'test/graphql/*.{js,ts}'
config:
scalars:
JSONObject: Record<string, unknown>
DateTime: string
config:
scalars:
JSONObject: Record<string, unknown>
DateTime: Date
require:
- ts-node/register
- tsconfig-paths/register
@@ -0,0 +1,13 @@
import { BaseError } from '@/modules/shared/errors'
export class AccessRequestProcessingError extends BaseError {
static defaultMessage =
'An unexpected error occurred while processing an access request'
static code = 'ACCESS_REQUEST_PROCESSING_ERROR'
}
export class AccessRequestCreationError extends BaseError {
static defaultMessage =
'An unexpected error occurred while creating an access request'
static code = 'ACCESS_REQUEST_CREATION_ERROR'
}
@@ -0,0 +1,31 @@
import { ServerAccessRequestRecord } from '@/modules/accessrequests/repositories'
import { StreamRoles } from '@/modules/core/helpers/mainConstants'
import { initializeModuleEventEmitter } from '@/modules/shared/services/moduleEventEmitterSetup'
export enum AccessRequestsEvents {
Created = 'created',
Finalized = 'finalized'
}
export type AccessRequestsEventsPayloads = {
[AccessRequestsEvents.Created]: { request: ServerAccessRequestRecord }
[AccessRequestsEvents.Finalized]: {
request: ServerAccessRequestRecord
/**
* ID of the user that finalized this request
*/
finalizedBy: string
/**
* If this object is set, request was approved
*/
approved?: {
role: StreamRoles
}
}
}
const { emit, listen } = initializeModuleEventEmitter<AccessRequestsEventsPayloads>({
moduleName: 'accessrequests'
})
export const AccessRequestsEmitter = { emit, listen, events: AccessRequestsEvents }
@@ -0,0 +1,72 @@
import {
getPendingStreamRequests,
getUserStreamAccessRequest,
processPendingStreamRequest,
requestStreamAccess
} from '@/modules/accessrequests/services/stream'
import { Resolvers } from '@/modules/core/graph/generated/graphql'
import { mapStreamRoleToValue } from '@/modules/core/helpers/graphTypes'
import { LogicError } from '@/modules/shared/errors'
const resolvers: Resolvers = {
Mutation: {
async streamAccessRequestUse(_parent, args, ctx) {
const { userId } = ctx
const { requestId, accept, role } = args
if (!userId) throw new LogicError('User ID unexpectedly false')
await processPendingStreamRequest(
userId,
requestId,
accept,
mapStreamRoleToValue(role)
)
return true
},
async streamAccessRequestCreate(_parent, args, ctx) {
const { userId } = ctx
if (!userId) throw new LogicError('User ID unexpectedly false')
const { streamId } = args
return await requestStreamAccess(userId, streamId)
}
},
Query: {
async streamAccessRequest(_, args, ctx) {
const { streamId } = args
const { userId } = ctx
if (!userId) throw new LogicError('User ID unexpectedly false')
return await getUserStreamAccessRequest(userId, streamId)
}
},
Stream: {
async pendingAccessRequests(parent) {
const { id } = parent
return await getPendingStreamRequests(id)
}
},
StreamAccessRequest: {
async requester(parent, _args, ctx) {
const { requesterId } = parent
const user = await ctx.loaders.users.getUser.load(requesterId)
if (!user) {
throw new LogicError('Unable to find requester')
}
return user
},
async stream(parent, _args, ctx) {
const { streamId } = parent
const stream = await ctx.loaders.streams.getStream.load(streamId)
if (!stream) {
throw new LogicError('Unable to find request stream')
}
return stream
}
}
}
export = resolvers
@@ -0,0 +1,6 @@
import { StreamAccessRequest } from '@/modules/core/graph/generated/graphql'
export type StreamAccessRequestGraphQLReturn = Omit<
StreamAccessRequest,
'requester' | 'stream'
>
@@ -0,0 +1,20 @@
import { initializeEventListener } from '@/modules/accessrequests/services/eventListener'
import { Optional, SpeckleModule } from '@/modules/shared/helpers/typeHelper'
import { modulesDebug } from '@/modules/shared/utils/logger'
let quitListeners: Optional<() => void> = undefined
const ServerAccessRequestsModule: SpeckleModule = {
init(_, isInitial) {
modulesDebug('🔐 Init access request module')
if (isInitial) {
quitListeners = initializeEventListener()
}
},
shutdown() {
quitListeners?.()
}
}
export = ServerAccessRequestsModule
@@ -0,0 +1,35 @@
import { Knex } from 'knex'
const TABLE_NAME = 'server_access_requests'
export async function up(knex: Knex): Promise<void> {
await knex.schema.createTable(TABLE_NAME, (table) => {
table.string('id', 10)
table
.string('requesterId', 10)
.references('id')
.inTable('users')
.onDelete('cascade')
.notNullable()
table.string('resourceType').notNullable()
table.string('resourceId').nullable()
table
.timestamp('createdAt', { precision: 3, useTz: true })
.defaultTo(knex.fn.now())
.notNullable()
table
.timestamp('updatedAt', { precision: 3, useTz: true })
.defaultTo(knex.fn.now())
.notNullable()
table.primary(['id'])
table.index(['resourceType', 'resourceId'])
table.unique(['requesterId', 'resourceId', 'resourceType'])
})
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TABLE_NAME)
}
@@ -0,0 +1,131 @@
import { ServerAccessRequests, Streams } from '@/modules/core/dbSchema'
import { InvalidArgumentError } from '@/modules/shared/errors'
import { Nullable } from '@/modules/shared/helpers/typeHelper'
import cryptoRandomString from 'crypto-random-string'
export enum AccessRequestType {
Stream = 'stream'
}
export type ServerAccessRequestRecord<
T extends AccessRequestType = AccessRequestType,
I extends Nullable<string> = Nullable<string>
> = {
id: string
requesterId: string
resourceType: T
resourceId: I
createdAt: Date
updatedAt: Date
}
export type StreamAccessRequestRecord = ServerAccessRequestRecord<
AccessRequestType.Stream,
string
>
export const isStreamAccessRequest = (
req: ServerAccessRequestRecord
): req is StreamAccessRequestRecord =>
!!(req.resourceId && req.resourceType === AccessRequestType.Stream)
const baseQuery = <
T extends AccessRequestType = AccessRequestType,
I extends Nullable<string> = Nullable<string>
>(
resourceType?: T
) => {
const q = ServerAccessRequests.knex().select<ServerAccessRequestRecord<T, I>[]>(
ServerAccessRequests.cols
)
if (resourceType) {
q.where(ServerAccessRequests.col.resourceType, resourceType)
}
// validate that resourceId points to a valid resource
if (resourceType) {
switch (resourceType) {
case AccessRequestType.Stream:
q.innerJoin(Streams.name, Streams.col.id, ServerAccessRequests.col.resourceId)
break
}
}
return q
}
export const generateId = () => cryptoRandomString({ length: 10 })
export async function getPendingAccessRequests<T extends AccessRequestType>(
resourceType: T,
resourceId: string
) {
if (!resourceId || !resourceType) {
throw new InvalidArgumentError('Resource type and ID missing')
}
const q = baseQuery<T, string>(resourceType)
.andWhere(ServerAccessRequests.col.resourceId, resourceId)
.orderBy(ServerAccessRequests.col.createdAt)
return await q
}
export async function getPendingAccessRequest<
T extends AccessRequestType = AccessRequestType
>(requestId: string, resourceType?: T) {
if (!requestId) {
throw new InvalidArgumentError('Request ID missing')
}
const q = baseQuery<T, string>(resourceType)
.andWhere(ServerAccessRequests.col.id, requestId)
.first()
return await q
}
export async function deleteRequestById(requestId: string) {
if (!requestId) {
throw new InvalidArgumentError('Request ID missing')
}
const q = await ServerAccessRequests.knex()
.where(ServerAccessRequests.col.id, requestId)
.del()
return !!q
}
type AccessRecordInput<
T extends AccessRequestType = AccessRequestType,
I extends Nullable<string> = Nullable<string>
> = Omit<ServerAccessRequestRecord<T, I>, 'createdAt' | 'updatedAt'>
export async function createNewRequest<
T extends AccessRequestType = AccessRequestType,
I extends Nullable<string> = Nullable<string>
>(input: AccessRecordInput<T, I>) {
const results = await ServerAccessRequests.knex().insert<
string,
ServerAccessRequestRecord<T, I>[]
>(input, ServerAccessRequests.cols)
return results[0]
}
export async function getUsersPendingAccessRequest<
T extends AccessRequestType = AccessRequestType,
I extends Nullable<string> = Nullable<string>
>(userId: string, resourceType: T, resourceId: I) {
if (!userId || !resourceType) {
throw new InvalidArgumentError('User ID or resource type missing')
}
const q = baseQuery<T, I>(resourceType)
.andWhere(ServerAccessRequests.col.requesterId, userId)
.andWhere(ServerAccessRequests.col.resourceId, resourceId)
.first()
return await q
}
@@ -0,0 +1,66 @@
import {
AccessRequestsEmitter,
AccessRequestsEvents,
AccessRequestsEventsPayloads
} from '@/modules/accessrequests/events/emitter'
import { isStreamAccessRequest } from '@/modules/accessrequests/repositories'
import { Roles } from '@/modules/core/helpers/mainConstants'
import { getStreamCollaborators } from '@/modules/core/repositories/streams'
import { NotificationType } from '@/modules/notifications/helpers/types'
import { publishNotification } from '@/modules/notifications/services/publication'
async function onServerAccessRequestCreated(
payload: AccessRequestsEventsPayloads[AccessRequestsEvents.Created]
) {
const { request } = payload
// Send out email to all owners of the stream
if (isStreamAccessRequest(request)) {
const owners = await getStreamCollaborators(request.resourceId, Roles.Stream.Owner)
await Promise.all(
owners.map((o) =>
publishNotification(NotificationType.NewStreamAccessRequest, {
targetUserId: o.id,
data: {
requestId: request.id
}
})
)
)
}
}
async function onServerAccessRequestFinalized(
payload: AccessRequestsEventsPayloads[AccessRequestsEvents.Finalized]
) {
const { approved, request, finalizedBy } = payload
// Send out email to requester, if accepted
if (approved && isStreamAccessRequest(request)) {
await publishNotification(NotificationType.StreamAccessRequestApproved, {
targetUserId: request.requesterId,
data: {
request,
finalizedBy
}
})
}
}
/**
* Initialize event listener for tracking various Speckle events
*/
export function initializeEventListener() {
const quitCbs = [
AccessRequestsEmitter.listen(
AccessRequestsEvents.Created,
onServerAccessRequestCreated
),
AccessRequestsEmitter.listen(
AccessRequestsEvents.Finalized,
onServerAccessRequestFinalized
)
]
return () => quitCbs.forEach((quit) => quit())
}
@@ -0,0 +1,142 @@
import {
AccessRequestCreationError,
AccessRequestProcessingError
} from '@/modules/accessrequests/errors'
import { AccessRequestsEmitter } from '@/modules/accessrequests/events/emitter'
import { StreamAccessRequestGraphQLReturn } from '@/modules/accessrequests/helpers/graphTypes'
import {
AccessRequestType,
createNewRequest,
deleteRequestById,
generateId,
getPendingAccessRequest,
getPendingAccessRequests,
getUsersPendingAccessRequest,
ServerAccessRequestRecord
} from '@/modules/accessrequests/repositories'
import { StreamInvalidAccessError } from '@/modules/core/errors/stream'
import { Roles, StreamRoles } from '@/modules/core/helpers/mainConstants'
import { getStream } from '@/modules/core/repositories/streams'
import {
addOrUpdateStreamCollaborator,
validateStreamAccess
} from '@/modules/core/services/streams/streamAccessService'
import { ensureError } from '@/modules/shared/helpers/errorHelper'
import { Nullable } from '@/modules/shared/helpers/typeHelper'
function buildStreamAccessRequestGraphQLReturn(
record: ServerAccessRequestRecord<AccessRequestType.Stream, string>
): StreamAccessRequestGraphQLReturn {
return {
id: record.id,
requesterId: record.requesterId,
streamId: record.resourceId,
createdAt: record.createdAt
}
}
export async function getUserStreamAccessRequest(
userId: string,
streamId: string
): Promise<Nullable<StreamAccessRequestGraphQLReturn>> {
const req = await getUsersPendingAccessRequest(
userId,
AccessRequestType.Stream,
streamId
)
if (!req) return null
return buildStreamAccessRequestGraphQLReturn(req)
}
/**
* Create new stream access request
*/
export async function requestStreamAccess(userId: string, streamId: string) {
const [stream, existingRequest] = await Promise.all([
getStream({ userId, streamId }),
getUserStreamAccessRequest(userId, streamId)
])
if (existingRequest) {
throw new AccessRequestCreationError(
'User already has a pending access request for this stream'
)
}
if (!stream) {
throw new AccessRequestCreationError(
"Can't request access to a non-existant stream"
)
}
if (stream.role) {
throw new AccessRequestCreationError(
'User already has access to the specified stream'
)
}
const req = await createNewRequest<AccessRequestType.Stream, string>({
id: generateId(),
requesterId: userId,
resourceType: AccessRequestType.Stream,
resourceId: streamId
})
await AccessRequestsEmitter.emit(AccessRequestsEmitter.events.Created, {
request: req
})
return buildStreamAccessRequestGraphQLReturn(req)
}
/**
* Get pending stream access requests
*/
export async function getPendingStreamRequests(
streamId: string
): Promise<StreamAccessRequestGraphQLReturn[]> {
const reqs = await getPendingAccessRequests(AccessRequestType.Stream, streamId)
return reqs.map(buildStreamAccessRequestGraphQLReturn)
}
/**
* Accept or decline a pending access request
*/
export async function processPendingStreamRequest(
userId: string,
requestId: string,
accept: boolean,
role: StreamRoles = Roles.Stream.Contributor
) {
const req = await getPendingAccessRequest(requestId, AccessRequestType.Stream)
if (!req) {
throw new AccessRequestProcessingError('No request with this ID exists')
}
try {
await validateStreamAccess(userId, req.resourceId, Roles.Stream.Owner)
} catch (e: unknown) {
const err = ensureError(e, 'Stream access validation failed')
if (err instanceof StreamInvalidAccessError) {
throw new AccessRequestProcessingError(
'You must own the stream to process access requests',
{ cause: err }
)
} else {
throw err
}
}
if (accept) {
await addOrUpdateStreamCollaborator(req.resourceId, req.requesterId, role, userId)
}
await deleteRequestById(req.id)
await AccessRequestsEmitter.emit(AccessRequestsEmitter.events.Finalized, {
request: req,
approved: accept ? { role } : undefined,
finalizedBy: userId
})
}
@@ -0,0 +1,351 @@
import {
deleteRequestById,
getPendingAccessRequest
} from '@/modules/accessrequests/repositories'
import { requestStreamAccess } from '@/modules/accessrequests/services/stream'
import { ActionTypes } from '@/modules/activitystream/services'
import { ServerAccessRequests, StreamActivity } from '@/modules/core/dbSchema'
import { mapStreamRoleToValue } from '@/modules/core/helpers/graphTypes'
import { Roles } from '@/modules/core/helpers/mainConstants'
import { getStreamCollaborators } from '@/modules/core/repositories/streams'
import {
addOrUpdateStreamCollaborator,
removeStreamCollaborator
} from '@/modules/core/services/streams/streamAccessService'
import { NotificationType } from '@/modules/notifications/helpers/types'
import { BasicTestUser, createTestUsers } from '@/test/authHelper'
import {
createStreamAccessRequest,
getPendingStreamAccessRequests,
getStreamAccessRequest,
useStreamAccessRequest
} from '@/test/graphql/accessRequests'
import { StreamRole } from '@/test/graphql/generated/graphql'
import { truncateTables } from '@/test/hooks'
import { EmailSendingServiceMock } from '@/test/mocks/global'
import {
buildNotificationsStateTracker,
NotificationsStateManager
} from '@/test/notificationsHelper'
import { buildAuthenticatedApolloServer } from '@/test/serverHelper'
import { getStreamActivities } from '@/test/speckle-helpers/activityStreamHelper'
import { BasicTestStream, createTestStreams } from '@/test/speckle-helpers/streamHelper'
import { ApolloServer } from 'apollo-server-express'
import { expect } from 'chai'
import { noop } from 'lodash'
const createReqAndGetId = async (userId: string, streamId: string) => {
const createReqRes = await requestStreamAccess(userId, streamId)
return createReqRes.id
}
describe('Stream access requests', () => {
let apollo: ApolloServer
let notificationsStateManager: NotificationsStateManager
const me: BasicTestUser = {
name: 'hello itsa me',
email: '',
id: ''
}
const otherGuy: BasicTestUser = {
name: 'and im the other guy, hi!',
email: '',
id: ''
}
const anotherGuy: BasicTestUser = {
name: 'and im another guy lol',
email: '',
id: ''
}
const otherGuysPrivateStream: BasicTestStream = {
name: 'other guys test stream #1',
isPublic: false,
ownerId: '',
id: ''
}
const otherGuysPublicStream: BasicTestStream = {
name: 'other guys public test stream #2',
isPublic: true,
ownerId: '',
id: ''
}
const myPrivateStream: BasicTestStream = {
name: 'this is my private stream #1',
isPublic: false,
ownerId: '',
id: ''
}
before(async () => {
await createTestUsers([me, otherGuy, anotherGuy])
await createTestStreams([
[otherGuysPrivateStream, otherGuy],
[otherGuysPublicStream, otherGuy],
[myPrivateStream, me]
])
apollo = buildAuthenticatedApolloServer(me.id)
notificationsStateManager = buildNotificationsStateTracker()
})
after(async () => {
notificationsStateManager.destroy()
})
const createReq = (streamId: string) =>
createStreamAccessRequest(apollo, { streamId })
const getReq = (streamId: string) => getStreamAccessRequest(apollo, { streamId })
const getStreamReqs = (streamId: string) =>
getPendingStreamAccessRequests(apollo, { streamId })
const useReq = (
requestId: string,
accept: boolean,
role: StreamRole = StreamRole.StreamContributor
) => useStreamAccessRequest(apollo, { requestId, accept, role })
describe('when being created', () => {
beforeEach(async () => {
await truncateTables([ServerAccessRequests.name, StreamActivity.name])
})
afterEach(async () => {
// Ensure me doesnt have any roles on stream1
await removeStreamCollaborator(otherGuysPrivateStream.id, me.id, me.id).catch(
noop
)
})
it('operation succeeds', async () => {
const sendEmailCall = EmailSendingServiceMock.hijackFunction(
'sendEmail',
async () => true
)
const results = await createReq(otherGuysPrivateStream.id)
// req gets created
expect(results).to.not.haveGraphQLErrors()
expect(results.data?.streamAccessRequestCreate.id).to.be.ok
expect(results.data?.streamAccessRequestCreate?.createdAt).to.be.ok
expect(results.data?.streamAccessRequestCreate?.requesterId).to.be.ok
expect(results.data?.streamAccessRequestCreate?.requester.id).to.eq(
results.data?.streamAccessRequestCreate?.requesterId
)
expect(results.data?.streamAccessRequestCreate.streamId).to.be.ok
await notificationsStateManager.waitForAck(
(e) => e.result?.type === NotificationType.NewStreamAccessRequest
)
// email gets sent out
expect(sendEmailCall.args?.[0]?.[0]).to.be.ok
const emailParams = sendEmailCall.args[0][0]
expect(emailParams.subject).to.contain('A user requested access to your stream')
expect(emailParams.html).to.be.ok
expect(emailParams.text).to.be.ok
expect(emailParams.to).to.eq(otherGuy.email)
// activity stream item inserted
const streamActivity = await getStreamActivities(otherGuysPrivateStream.id, {
actionType: ActionTypes.Stream.AccessRequestSent,
userId: me.id
})
expect(streamActivity).to.have.lengthOf(1)
})
it('operation fails if request already exists', async () => {
const firstResults = await createReq(otherGuysPrivateStream.id)
expect(firstResults).to.not.haveGraphQLErrors()
expect(firstResults.data?.streamAccessRequestCreate.id).to.be.ok
const secondResults = await createReq(otherGuysPrivateStream.id)
expect(secondResults).to.haveGraphQLErrors('already has a pending access request')
expect(secondResults.data?.streamAccessRequestCreate.id).to.be.not.ok
})
it('operation fails if stream is nonexistant', async () => {
const secondResults = await createReq('abcdef123')
expect(secondResults).to.haveGraphQLErrors('non-existant stream')
expect(secondResults.data?.streamAccessRequestCreate.id).to.be.not.ok
})
it('operation fails if user already has a role on the stream', async () => {
await addOrUpdateStreamCollaborator(
otherGuysPrivateStream.id,
me.id,
Roles.Stream.Contributor,
otherGuy.id
)
const secondResults = await createReq(otherGuysPrivateStream.id)
expect(secondResults).to.haveGraphQLErrors('user already has access')
expect(secondResults.data?.streamAccessRequestCreate.id).to.be.not.ok
})
})
describe('when being read', () => {
let myRequestId: string
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let anotherGuysRequestId: string
beforeEach(async () => {
await truncateTables([ServerAccessRequests.name])
const [myNewReqId, anotherGuysNewReqId] = await Promise.all([
createReqAndGetId(me.id, otherGuysPrivateStream.id),
createReqAndGetId(anotherGuy.id, otherGuysPrivateStream.id)
])
myRequestId = myNewReqId
anotherGuysRequestId = anotherGuysNewReqId
})
it('returns the request correctly', async () => {
const results = await getReq(otherGuysPrivateStream.id)
expect(results).to.not.haveGraphQLErrors()
expect(results.data?.streamAccessRequest?.id).to.eq(myRequestId)
expect(results.data?.streamAccessRequest?.createdAt).to.be.ok
expect(results.data?.streamAccessRequest?.requesterId).to.be.ok
expect(results.data?.streamAccessRequest?.requester.id).to.eq(
results.data?.streamAccessRequest?.requesterId
)
expect(results.data?.streamAccessRequest?.streamId).to.be.ok
})
it('returns null if no req found', async () => {
await deleteRequestById(myRequestId)
const results = await getReq(otherGuysPrivateStream.id)
expect(results).to.not.haveGraphQLErrors()
expect(results.data?.streamAccessRequest).to.eq(null)
})
})
describe('when being read from a stream', () => {
before(async () => {
await truncateTables([ServerAccessRequests.name])
await addOrUpdateStreamCollaborator(
otherGuysPublicStream.id,
me.id,
Roles.Stream.Contributor,
otherGuy.id
)
await Promise.all([
createReqAndGetId(otherGuy.id, myPrivateStream.id),
createReqAndGetId(anotherGuy.id, myPrivateStream.id)
])
})
after(async () => {
await removeStreamCollaborator(otherGuysPublicStream.id, me.id, me.id).catch(noop)
})
it(`operation fails if reading from a non-owned stream`, async () => {
const results = await getStreamReqs(otherGuysPublicStream.id)
expect(results).to.haveGraphQLErrors('not authorized')
expect(results.data?.stream?.pendingAccessRequests).to.be.not.ok
expect(results.data?.stream?.id).to.be.ok
})
it('operation succeeds', async () => {
const results = await getStreamReqs(myPrivateStream.id)
expect(results).to.not.haveGraphQLErrors()
expect(results.data?.stream?.pendingAccessRequests).to.have.lengthOf(2)
for (const pendingReq of results.data!.stream!.pendingAccessRequests!) {
expect(pendingReq.id).to.be.ok
expect(pendingReq.createdAt).to.be.ok
expect(pendingReq.requesterId).to.be.ok
expect(pendingReq.streamId).to.be.ok
expect(pendingReq.stream.id).to.eq(results.data!.stream!.id)
expect(pendingReq.requester.id).to.eq(pendingReq.requesterId)
expect([otherGuy.id, anotherGuy.id].includes(pendingReq.requesterId)).to.be.true
}
})
})
describe('when being processed', () => {
let validReqId: string
beforeEach(async () => {
await truncateTables([ServerAccessRequests.name, StreamActivity.name])
await removeStreamCollaborator(
myPrivateStream.id,
otherGuy.id,
otherGuy.id
).catch(noop)
validReqId = await createReqAndGetId(otherGuy.id, myPrivateStream.id)
})
it('processing fails when pointing to nonexistant req', async () => {
const results = await useReq('abcd', true)
expect(results).to.haveGraphQLErrors('no request with this id exists')
expect(results.data?.streamAccessRequestUse).to.be.not.ok
})
it('processing fails when pointing to a req the user doesnt have access to', async () => {
const inaccessibleReqId = await createReqAndGetId(
anotherGuy.id,
otherGuysPrivateStream.id
)
const results = await useReq(inaccessibleReqId, true)
expect(results).to.haveGraphQLErrors('you must own the stream')
expect(results.data?.streamAccessRequestUse).to.be.not.ok
})
const validProcessingDataSet = [
{ display: 'declining', accept: false },
{ display: 'approving', accept: true },
{
display: 'approving with custom role',
accept: true,
role: StreamRole.StreamReviewer
}
]
validProcessingDataSet.forEach(({ display, accept, role }) => {
it(`${display} works`, async () => {
const results = await useReq(validReqId, accept, role)
expect(results).to.not.haveGraphQLErrors()
expect(results.data?.streamAccessRequestUse).to.be.ok
// req should be deleted
const req = await getPendingAccessRequest(validReqId)
expect(req).to.not.be.ok
// activity stream item should be inserted
if (accept) {
const streamActivity = await getStreamActivities(myPrivateStream.id, {
actionType: ActionTypes.Stream.PermissionsAdd,
userId: me.id
})
expect(streamActivity).to.have.lengthOf(1)
const collaborators = await getStreamCollaborators(myPrivateStream.id)
const newCollaborator = collaborators.find((c) => c.id === otherGuy.id)
expect(newCollaborator).to.be.ok
expect(newCollaborator?.role).to.eq(
role ? mapStreamRoleToValue(role) : Roles.Stream.Contributor
)
} else {
const streamActivity = await getStreamActivities(myPrivateStream.id, {
actionType: ActionTypes.Stream.AccessRequestDeclined,
userId: me.id
})
expect(streamActivity).to.have.lengthOf(1)
}
})
})
})
})
@@ -0,0 +1,12 @@
import { Nullable } from '@/modules/shared/helpers/typeHelper'
export type StreamActivityRecord = {
streamId: Nullable<string>
time: Date
resourceType: Nullable<string>
resourceId: Nullable<string>
actionType: Nullable<string>
userId: Nullable<string>
info: Nullable<Record<string, unknown>>
message: Nullable<string>
}
@@ -0,0 +1,17 @@
import { initializeEventListener } from '@/modules/activitystream/services/eventListener'
import { SpeckleModule } from '@/modules/shared/helpers/typeHelper'
import { modulesDebug } from '@/modules/shared/utils/logger'
const activityStreamModule: SpeckleModule = {
init(_, isInitial) {
modulesDebug('📅 Initializing activity-stream')
if (isInitial) {
initializeEventListener()
}
}
}
export = {
...activityStreamModule
}
@@ -0,0 +1,44 @@
import {
ActionTypes,
ResourceTypes,
saveActivity
} from '@/modules/activitystream/services'
/**
* Save a "stream access requested" activity
*/
export async function addStreamAccessRequestedActivity(params: {
streamId: string
requesterId: string
}) {
const { streamId, requesterId } = params
await saveActivity({
streamId,
resourceType: ResourceTypes.Stream,
resourceId: streamId,
userId: requesterId,
actionType: ActionTypes.Stream.AccessRequestSent,
message: `User ${requesterId} has requested access to stream ${streamId}`,
info: { requesterId }
})
}
/**
* Save a "stream acccess request declined/denied" activity
*/
export async function addStreamAccessRequestDeclinedActivity(params: {
streamId: string
requesterId: string
declinerId: string
}) {
const { streamId, requesterId, declinerId } = params
await saveActivity({
streamId,
resourceType: ResourceTypes.Stream,
resourceId: streamId,
userId: declinerId,
actionType: ActionTypes.Stream.AccessRequestDeclined,
message: `User ${declinerId} declined access to stream ${streamId} for user ${requesterId}`,
info: { requesterId, declinerId }
})
}
@@ -0,0 +1,88 @@
import {
AccessRequestsEmitter,
AccessRequestsEvents,
AccessRequestsEventsPayloads
} from '@/modules/accessrequests/events/emitter'
import { AccessRequestType } from '@/modules/accessrequests/repositories'
import { saveActivity } from '@/modules/activitystream/services'
import {
addStreamAccessRequestDeclinedActivity,
addStreamAccessRequestedActivity
} from '@/modules/activitystream/services/accessRequestActivity'
import {
UsersEmitter,
UsersEvents,
UsersEventsPayloads
} from '@/modules/core/events/usersEmitter'
async function onUserCreated(payload: UsersEventsPayloads[UsersEvents.Created]) {
const { user } = payload
await saveActivity({
streamId: null,
resourceType: 'user',
resourceId: user.id,
actionType: 'user_create',
userId: user.id,
info: { user },
message: 'User created'
})
}
async function onServerAccessRequestCreated(
payload: AccessRequestsEventsPayloads[AccessRequestsEvents.Created]
) {
const {
request: { resourceId, resourceType, requesterId }
} = payload
if (!resourceId) return
if (resourceType === AccessRequestType.Stream) {
await addStreamAccessRequestedActivity({
streamId: resourceId,
requesterId
})
}
}
async function onServerAccessRequestFinalized(
payload: AccessRequestsEventsPayloads[AccessRequestsEvents.Finalized]
) {
const {
approved,
finalizedBy,
request: { resourceId, resourceType, requesterId }
} = payload
if (!resourceId) return
if (resourceType === AccessRequestType.Stream) {
// If user was added to stream, an activity stream item was already added from 'addOrUpdateStreamCollaborator'
if (approved) return
await addStreamAccessRequestDeclinedActivity({
streamId: resourceId,
requesterId,
declinerId: finalizedBy
})
}
}
/**
* Initialize event listener for tracking various Speckle events and responding
* to them by creating activitystream entries
*/
export function initializeEventListener() {
const quitCbs = [
UsersEmitter.listen(UsersEvents.Created, onUserCreated),
AccessRequestsEmitter.listen(
AccessRequestsEvents.Created,
onServerAccessRequestCreated
),
AccessRequestsEmitter.listen(
AccessRequestsEvents.Finalized,
onServerAccessRequestFinalized
)
]
return () => quitCbs.forEach((quit) => quit())
}
@@ -10,7 +10,8 @@ const ResourceTypes = Object.freeze({
User: 'user',
Stream: 'stream',
Commit: 'commit',
Branch: 'branch'
Branch: 'branch',
Comment: 'comment'
})
const ActionTypes = Object.freeze({
@@ -22,13 +23,22 @@ const ActionTypes = Object.freeze({
Delete: 'stream_delete',
Create: 'stream_create',
InviteSent: 'stream_invite_sent',
InviteDeclined: 'stream_invite_declined'
InviteDeclined: 'stream_invite_declined',
AccessRequestSent: 'stream_access_request_sent',
AccessRequestDeclined: 'stream_access_request_declined'
},
Comment: {
Mention: 'comment_mention'
}
})
module.exports = {
ActionTypes,
ResourceTypes,
/**
* @param {Omit<import('@/modules/activitystream/helpers/types').StreamActivityRecord, "time">} param0
*/
async saveActivity({
streamId,
resourceType,
@@ -1,19 +1,21 @@
const {
import {
saveActivity,
ResourceTypes,
ActionTypes
} = require('@/modules/activitystream/services')
const { pubsub, StreamPubsubEvents } = require('@/modules/shared')
} from '@/modules/activitystream/services'
import { StreamRoles } from '@/modules/core/helpers/mainConstants'
import { pubsub, StreamPubsubEvents } from '@/modules/shared'
/**
* Save "stream permissions granted to user" activity item
*/
async function addStreamPermissionsAddedActivity({
streamId,
activityUserId,
targetUserId,
role
export async function addStreamPermissionsAddedActivity(params: {
streamId: string
activityUserId: string
targetUserId: string
role: StreamRoles
}) {
const { streamId, activityUserId, targetUserId, role } = params
await Promise.all([
saveActivity({
streamId,
@@ -37,12 +39,13 @@ async function addStreamPermissionsAddedActivity({
/**
* Save "user accepted stream invite" activity item
*/
async function addStreamInviteAcceptedActivity({
streamId,
inviteTargetId,
inviterId,
role
export async function addStreamInviteAcceptedActivity(params: {
streamId: string
inviteTargetId: string
inviterId: string
role: StreamRoles
}) {
const { streamId, inviteTargetId, inviterId, role } = params
await Promise.all([
saveActivity({
streamId,
@@ -66,11 +69,12 @@ async function addStreamInviteAcceptedActivity({
/**
* Save "stream permissions revoked for user" activity item
*/
async function addStreamPermissionsRevokedActivity({
streamId,
activityUserId,
removedUserId
export async function addStreamPermissionsRevokedActivity(params: {
streamId: string
activityUserId: string
removedUserId: string
}) {
const { streamId, activityUserId, removedUserId } = params
const isVoluntaryLeave = activityUserId === removedUserId
await Promise.all([
@@ -99,12 +103,13 @@ async function addStreamPermissionsRevokedActivity({
/**
* Save "user invited another user to stream" activity item
*/
async function addStreamInviteSentOutActivity({
streamId,
inviteTargetId,
inviterId,
inviteTargetEmail
export async function addStreamInviteSentOutActivity(params: {
streamId: string
inviteTargetId: string
inviterId: string
inviteTargetEmail: string
}) {
const { streamId, inviteTargetId, inviterId, inviteTargetEmail } = params
const targetDisplay = inviteTargetId || inviteTargetEmail
await saveActivity({
@@ -121,11 +126,12 @@ async function addStreamInviteSentOutActivity({
/**
* Save "user declined an invite" activity item
*/
async function addStreamInviteDeclinedActivity({
streamId,
inviteTargetId,
inviterId
export async function addStreamInviteDeclinedActivity(params: {
streamId: string
inviteTargetId: string
inviterId: string
}) {
const { streamId, inviteTargetId, inviterId } = params
await saveActivity({
streamId,
resourceType: ResourceTypes.Stream,
@@ -137,10 +143,29 @@ async function addStreamInviteDeclinedActivity({
})
}
module.exports = {
addStreamPermissionsAddedActivity,
addStreamPermissionsRevokedActivity,
addStreamInviteAcceptedActivity,
addStreamInviteSentOutActivity,
addStreamInviteDeclinedActivity
/**
* Save "user mentioned in stream comment" activity item
*/
export async function addStreamCommentMentionActivity(params: {
streamId: string
mentionAuthorId: string
mentionTargetId: string
commentId: string
threadId: string
}) {
const { streamId, mentionAuthorId, mentionTargetId, commentId, threadId } = params
await saveActivity({
streamId,
resourceType: ResourceTypes.Comment,
resourceId: commentId,
actionType: ActionTypes.Comment.Mention,
userId: mentionAuthorId,
message: `User ${mentionAuthorId} mentioned user ${mentionTargetId} in comment ${commentId}`,
info: {
mentionAuthorId,
mentionTargetId,
commentId,
threadId
}
})
}
@@ -0,0 +1,50 @@
import { AuthorizationCodes, RefreshTokens, knex } from '@/modules/core/dbSchema'
import { InvalidArgumentError } from '@/modules/shared/errors'
import { Nullable } from '@/modules/shared/helpers/typeHelper'
export type RefreshTokenRecord = {
id: string
tokenDigest: string
appId: string
userId: string
createdAt: string
lifespan: number
}
export type AuthorizationCodeRecord = {
id: string
appId: string
userId: string
challenge: string
createdAt: string
lifespan: number
}
export type ApiTokenRecord = {
id: string
tokenDigest: string
owner: string
name: Nullable<string>
lastChars: Nullable<string>
revoked: boolean
lifespan: number
createdAt: string
lastUsed: string
}
export async function deleteExistingAuthTokens(userId: string) {
if (!userId) throw new InvalidArgumentError('User ID must be set')
await RefreshTokens.knex().where(RefreshTokens.col.userId, userId)
await AuthorizationCodes.knex().where(AuthorizationCodes.col.userId, userId)
await knex.raw(
`
DELETE FROM api_tokens
WHERE owner = ?
AND id NOT IN (
SELECT p."tokenId" FROM personal_api_tokens p WHERE p."userId" = ?
)
`,
[userId, userId]
)
}
@@ -36,10 +36,6 @@ module.exports = async (app) => {
req.session.challenge = req.query.challenge
if (req.query.suuid) {
req.session.suuid = req.query.suuid
}
const token = req.query.token || req.query.inviteId
if (token) {
req.session.token = token
@@ -58,8 +58,6 @@ module.exports = async (app, session, sessionStorage, finalizeAuth) => {
name: req.user._json.name || req.user.displayName
}
if (req.session.suuid) user.suuid = req.session.suuid
const existingUser = await getUserByEmail({ email: user.email })
if (existingUser && !existingUser.verified) {
@@ -42,8 +42,6 @@ module.exports = async (app, session, sessionStorage, finalizeAuth) => {
const user = { email, name, bio }
if (req.session.suuid) user.suuid = req.session.suuid
const existingUser = await getUserByEmail({ email: user.email })
if (existingUser && !existingUser.verified) {
@@ -39,8 +39,6 @@ module.exports = async (app, session, sessionStorage, finalizeAuth) => {
const user = { email, name, avatar: profile._json.picture }
if (req.session.suuid) user.suuid = req.session.suuid
const existingUser = await getUserByEmail({ email: user.email })
if (existingUser && !existingUser.verified) {

Some files were not shown because too many files have changed in this diff Show More