Feat: configurable file limits (#835)

* Feat: configurable file limits

* ci(circleci): container build speed imporvements

* feat(frontend nginx): add file size limit configurability to frontend nginx

* feat(server blobstorage): use the new file size limit customization value

* feat(helm chart): implement the file size configuration in the helm chart

* fix(frontend docker): fix entrypoint script

* fix(server blobstorage): fix env var parsing NaN

* feat(fileimport-service): add customizable import timeout

* feat(helm chart): add fileimport service timeout value to helm chart

* feat(blobstorage): add server side blob storage size limits

* feat(docker-compose): add blob size limit env var to  docker-compose files

* refactor(frontend file uploads): refactor file uploads to use `useQuery`

* refactor(server env helper): move env helper to shared module

* refactor(blobstorage): use env helper for file size limit

* refactor(frontend file uploads): use generated query document

* fix(server blob sotrage): fix file size limit function call

Co-authored-by: Gergő Jedlicska <gergo@jedlicska.com>
This commit is contained in:
Iain Sproat 2022-07-29 11:00:29 +01:00 коммит произвёл GitHub
Родитель b55f12d6bc
Коммит 90847e422d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
25 изменённых файлов: 165 добавлений и 28 удалений

Просмотреть файл

@ -12,6 +12,8 @@ DOCKER_IMAGE_TAG=speckle/speckle-$SPECKLE_SERVER_PACKAGE
IMAGE_VERSION_TAG="${IMAGE_VERSION_TAG:-0}"
echo $IMAGE_VERSION_TAG
export DOCKER_BUILDKIT=1
docker build --build-arg SPECKLE_SERVER_VERSION=$IMAGE_VERSION_TAG -t $DOCKER_IMAGE_TAG:latest . -f $FOLDER/$SPECKLE_SERVER_PACKAGE/Dockerfile
docker tag $DOCKER_IMAGE_TAG:latest $DOCKER_IMAGE_TAG:$IMAGE_VERSION_TAG

Просмотреть файл

@ -179,6 +179,7 @@ jobs:
docker-build-and-publish: &docker-job
docker: &docker-image
- image: cimg/node:16.15
resource_class: xlarge
working_directory: *work-dir
steps:
- checkout

Просмотреть файл

@ -8,6 +8,8 @@ services:
restart: always
ports:
- '0.0.0.0:80:80'
environment:
FILE_SIZE_LIMIT_MB: 100
speckle-server:
build:
@ -37,6 +39,7 @@ services:
S3_SECRET_KEY: 'minioadmin'
S3_BUCKET: 'speckle-server'
S3_CREATE_BUCKET: 'true'
FILE_SIZE_LIMIT_MB: 100
preview-service:
build:

Просмотреть файл

@ -25,6 +25,11 @@ const TMP_RESULTS_PATH = '/tmp/import_result.json'
let shouldExit = false
let TIME_LIMIT = 10 * 60 * 1000
const providedTimeLimit = parseInt(process.env.FILE_IMPORT_TIME_LIMIT_MIN)
if (providedTimeLimit) TIME_LIMIT = providedTimeLimit * 60 * 1000
async function startTask() {
const { rows } = await knex.raw(`
UPDATE file_uploads
@ -91,7 +96,7 @@ async function doTask(task) {
{
USER_TOKEN: tempUserToken
},
20 * 60 * 1000
TIME_LIMIT
)
} else if (info.fileType === 'stl') {
await runProcessWithTimeout(
@ -107,7 +112,7 @@ async function doTask(task) {
{
USER_TOKEN: tempUserToken
},
10 * 60 * 1000
TIME_LIMIT
)
} else if (info.fileType === 'obj') {
await objDependencies.downloadDependencies({
@ -131,7 +136,7 @@ async function doTask(task) {
{
USER_TOKEN: tempUserToken
},
10 * 60 * 1000
TIME_LIMIT
)
} else {
throw new Error(`File type ${info.fileType} is not supported`)

Просмотреть файл

@ -3,6 +3,7 @@
# build stage
FROM node:16.15-bullseye-slim as build-stage
ARG SPECKLE_SERVER_VERSION=custom
WORKDIR /speckle-server
COPY .yarnrc.yml .
@ -25,9 +26,16 @@ COPY packages/frontend ./packages/frontend/
RUN yarn workspaces foreach -pt run build
# production stage
FROM openresty/openresty:1.19.9.1-bullseye as production-stage
FROM openresty/openresty:1.21.4.1-bullseye as production-stage
COPY --from=build-stage /speckle-server/packages/frontend/dist /usr/share/nginx/html
RUN rm /etc/nginx/conf.d/default.conf
COPY packages/frontend/nginx/nginx.conf /etc/nginx/conf.d
COPY packages/frontend/nginx/ /etc/nginx/
# prepare the environment
ENTRYPOINT ["/etc/nginx/docker-entrypoint.sh"]
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Просмотреть файл

@ -0,0 +1,10 @@
#!/bin/sh
set -eu pipefail
defined_envs=$(printf '${%s} ' $(env | cut -d= -f1))
echo Starting nginx environment template rendering with $defined_envs
envsubst "$defined_envs" < /etc/nginx/templates/nginx.conf.template > /etc/nginx/conf.d/nginx.conf
echo Nginx conf rendered, starting server...
exec "$@"

Просмотреть файл

@ -110,7 +110,7 @@ server {
location ~* ^/(graphql|explorer|(auth/.*)|(objects/.*)|(preview/.*)|(api/.*)) {
resolver 127.0.0.11 valid=30s;
set $upstream_speckle_server speckle-server;
client_max_body_size 100m;
client_max_body_size ${FILE_SIZE_LIMIT_MB}m;
proxy_pass http://$upstream_speckle_server:3000;
proxy_buffering off;

Просмотреть файл

@ -991,6 +991,7 @@ export type ServerInfo = {
adminContact?: Maybe<Scalars['String']>;
/** The authentication strategies available on this server. */
authStrategies?: Maybe<Array<Maybe<AuthStrategy>>>;
blobSizeLimitBytes: Scalars['Int'];
canonicalUrl?: Maybe<Scalars['String']>;
company?: Maybe<Scalars['String']>;
description?: Maybe<Scalars['String']>;
@ -1607,6 +1608,8 @@ export type StreamObjectNoDataQueryVariables = Exact<{
export type StreamObjectNoDataQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, object?: { __typename?: 'Object', totalChildrenCount?: number | null, id: string, speckleType?: string | null } | null } | null };
export type ServerInfoBlobSizeFieldsFragment = { __typename?: 'ServerInfo', blobSizeLimitBytes: number };
export type MainServerInfoFieldsFragment = { __typename?: 'ServerInfo', name: string, company?: string | null, description?: string | null, adminContact?: string | null, canonicalUrl?: string | null, termsOfService?: string | null, inviteOnly?: boolean | null, version?: string | null };
export type ServerInfoRolesFieldsFragment = { __typename?: 'ServerInfo', roles: Array<{ __typename?: 'Role', name: string, description: string, resourceTarget: string } | null> };
@ -1621,7 +1624,12 @@ export type MainServerInfoQuery = { __typename?: 'Query', serverInfo: { __typena
export type FullServerInfoQueryVariables = Exact<{ [key: string]: never; }>;
export type FullServerInfoQuery = { __typename?: 'Query', serverInfo: { __typename?: 'ServerInfo', name: string, company?: string | null, description?: string | null, adminContact?: string | null, canonicalUrl?: string | null, termsOfService?: string | null, inviteOnly?: boolean | null, version?: string | null, roles: Array<{ __typename?: 'Role', name: string, description: string, resourceTarget: string } | null>, scopes: Array<{ __typename?: 'Scope', name: string, description: string } | null> } };
export type FullServerInfoQuery = { __typename?: 'Query', serverInfo: { __typename?: 'ServerInfo', name: string, company?: string | null, description?: string | null, adminContact?: string | null, canonicalUrl?: string | null, termsOfService?: string | null, inviteOnly?: boolean | null, version?: string | null, blobSizeLimitBytes: number, roles: Array<{ __typename?: 'Role', name: string, description: string, resourceTarget: string } | null>, scopes: Array<{ __typename?: 'Scope', name: string, description: string } | null> } };
export type ServerInfoBlobSizeLimitQueryVariables = Exact<{ [key: string]: never; }>;
export type ServerInfoBlobSizeLimitQuery = { __typename?: 'Query', serverInfo: { __typename?: 'ServerInfo', blobSizeLimitBytes: number } };
export type StreamCommitsQueryVariables = Exact<{
id: Scalars['String'];
@ -1814,6 +1822,11 @@ export const UsersOwnInviteFields = gql`
}
}
${LimitedUserFields}`;
export const ServerInfoBlobSizeFields = gql`
fragment ServerInfoBlobSizeFields on ServerInfo {
blobSizeLimitBytes
}
`;
export const MainServerInfoFields = gql`
fragment MainServerInfoFields on ServerInfo {
name
@ -2031,11 +2044,20 @@ export const FullServerInfo = gql`
...MainServerInfoFields
...ServerInfoRolesFields
...ServerInfoScopesFields
...ServerInfoBlobSizeFields
}
}
${MainServerInfoFields}
${ServerInfoRolesFields}
${ServerInfoScopesFields}`;
${ServerInfoScopesFields}
${ServerInfoBlobSizeFields}`;
export const ServerInfoBlobSizeLimit = gql`
query ServerInfoBlobSizeLimit {
serverInfo {
...ServerInfoBlobSizeFields
}
}
${ServerInfoBlobSizeFields}`;
export const StreamCommits = gql`
query StreamCommits($id: String!) {
stream(id: $id) {
@ -2352,6 +2374,7 @@ export const CommentFullInfoFragmentDoc = {"kind":"Document","definitions":[{"ki
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 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 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>;
@ -2371,7 +2394,8 @@ export const BatchInviteToStreamsDocument = {"kind":"Document","definitions":[{"
export const StreamObjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"StreamObject"},"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":"object"},"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":"totalChildrenCount"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"speckleType"}},{"kind":"Field","name":{"kind":"Name","value":"data"}}]}}]}}]}}]} as unknown as DocumentNode<StreamObjectQuery, StreamObjectQueryVariables>;
export const StreamObjectNoDataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"StreamObjectNoData"},"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":"object"},"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":"totalChildrenCount"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"speckleType"}}]}}]}}]}}]} as unknown as DocumentNode<StreamObjectNoDataQuery, StreamObjectNoDataQueryVariables>;
export const MainServerInfoDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MainServerInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MainServerInfoFields"}}]}}]}},...MainServerInfoFieldsFragmentDoc.definitions]} as unknown as DocumentNode<MainServerInfoQuery, MainServerInfoQueryVariables>;
export const FullServerInfoDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FullServerInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MainServerInfoFields"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerInfoRolesFields"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerInfoScopesFields"}}]}}]}},...MainServerInfoFieldsFragmentDoc.definitions,...ServerInfoRolesFieldsFragmentDoc.definitions,...ServerInfoScopesFieldsFragmentDoc.definitions]} as unknown as DocumentNode<FullServerInfoQuery, FullServerInfoQueryVariables>;
export const FullServerInfoDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FullServerInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MainServerInfoFields"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerInfoRolesFields"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerInfoScopesFields"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerInfoBlobSizeFields"}}]}}]}},...MainServerInfoFieldsFragmentDoc.definitions,...ServerInfoRolesFieldsFragmentDoc.definitions,...ServerInfoScopesFieldsFragmentDoc.definitions,...ServerInfoBlobSizeFieldsFragmentDoc.definitions]} as unknown as DocumentNode<FullServerInfoQuery, FullServerInfoQueryVariables>;
export const ServerInfoBlobSizeLimitDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ServerInfoBlobSizeLimit"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerInfoBlobSizeFields"}}]}}]}},...ServerInfoBlobSizeFieldsFragmentDoc.definitions]} as unknown as DocumentNode<ServerInfoBlobSizeLimitQuery, ServerInfoBlobSizeLimitQueryVariables>;
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>;

Просмотреть файл

@ -1,5 +1,11 @@
import { gql } from '@apollo/client/core'
export const serverInfoBlobSizeFragment = gql`
fragment ServerInfoBlobSizeFields on ServerInfo {
blobSizeLimitBytes
}
`
export const mainServerInfoFieldsFragment = gql`
fragment MainServerInfoFields on ServerInfo {
name
@ -51,10 +57,21 @@ export const fullServerInfoQuery = gql`
...MainServerInfoFields
...ServerInfoRolesFields
...ServerInfoScopesFields
...ServerInfoBlobSizeFields
}
}
${mainServerInfoFieldsFragment}
${serverInfoRolesFieldsFragment}
${serverInfoScopesFieldsFragment}
${serverInfoBlobSizeFragment}
`
export const serverInfoBlobSizeLimitQuery = gql`
query ServerInfoBlobSizeLimit {
serverInfo {
...ServerInfoBlobSizeFields
}
}
${serverInfoBlobSizeFragment}
`

Просмотреть файл

@ -3,7 +3,7 @@
<file-upload-zone
ref="uploadZone"
v-slot="{ isFileDrag }"
:size-limit="fileSizeLimit"
:size-limit="blobSizeLimitBytes"
:count-limit="countLimit"
:accept="acceptValue"
:disabled="disabled"
@ -48,8 +48,11 @@ import {
import FileUploadProgress from '@/main/components/common/file-upload/FileUploadProgress.vue'
import { UploadFileItem } from '@/main/lib/common/file-upload/fileUploadHelper'
import { differenceBy } from 'lodash'
import { useQuery } from '@vue/apollo-composable'
import { ServerInfoBlobSizeLimitDocument } from '@/graphql/generated/graphql'
import { deleteBlob, uploadFiles } from '@/main/lib/common/file-upload/blobStorageApi'
import { JSONContent } from '@tiptap/core'
import { computed } from 'vue'
// TODO: Styling for adding attachments & rendering them
@ -84,10 +87,17 @@ export default Vue.extend({
default: true
}
},
setup() {
const { result } = useQuery(ServerInfoBlobSizeLimitDocument)
const blobSizeLimitBytes = computed(
() => result.value?.serverInfo.blobSizeLimitBytes
)
return { blobSizeLimitBytes }
},
data() {
return {
editorSchemaOptions: SMART_EDITOR_SCHEMA,
fileSizeLimit: 1024 * 1024 * 25, // 25MB
// fileSizeLimit: 1024 * 1024 * 25, // 25MB
countLimit: 5, // if it's more than 5, just zip it up
acceptValue: [
UniqueFileTypeSpecifier.AnyImage,
@ -157,8 +167,8 @@ export default Vue.extend({
},
placeholder(): string {
return this.addingComment
? 'Your comment... (press enter to send)'
: 'Reply... (press enter to send)'
? 'Your comment... (Enter sends it)'
: 'Reply... (Enter sends it)'
},
anyAttachmentsProcessing(): boolean {
return this.currentFiles.some((a) => !isUploadProcessed(a))

Просмотреть файл

@ -139,22 +139,28 @@ export function isFileTypeSpecifier(type: string): type is FileTypeSpecifier {
* Create a human readable file size string from the numeric size in bytes
*/
export function prettyFileSize(sizeInBytes: number): string {
function removeTrailingZeros(fileSize: number): string {
const fileSizeString = fileSize.toFixed(2)
const parts = fileSizeString.split('.')
if (parts[1] === '00') return parts[0]
return fileSizeString
}
if (sizeInBytes < 1024) {
return `${sizeInBytes}bytes`
}
const kbSize = sizeInBytes / 1024
if (kbSize < 1024) {
return `${kbSize.toFixed(2)}kb`
return `${removeTrailingZeros(kbSize)}kB`
}
const mbSize = kbSize / 1024
if (mbSize < 1024) {
return `${mbSize.toFixed(2)}mb`
return `${removeTrailingZeros(mbSize)}MB`
}
const gbSize = mbSize / 1024
return `${gbSize.toFixed(2)}gb`
return `${removeTrailingZeros(gbSize)}GB`
}
/**

Просмотреть файл

@ -97,7 +97,8 @@
<span class="primary--text">Drag and drop your file here!</span>
<br />
<span class="caption">
Maximum 5 files at a time. Size is restricted to 50mb each.
Maximum 5 files at a time. Size is restricted to
{{ fileSizeLimit }} mb each.
</span>
</div>
<v-alert
@ -132,6 +133,10 @@ import {
STANDARD_PORTAL_KEYS,
buildPortalStateMixin
} from '@/main/utils/portalStateManager'
import { ServerInfoBlobSizeLimitDocument } from '@/graphql/generated/graphql'
import { useQuery } from '@vue/apollo-composable'
import { computed } from 'vue'
import { prettyFileSize } from '@/main/lib/common/file-upload/fileUploadHelper'
export default {
name: 'TheUploads',
@ -183,6 +188,13 @@ export default {
}
}
},
setup() {
const { result } = useQuery(ServerInfoBlobSizeLimitDocument)
const blobSizeLimitBytes = computed(
() => result.value?.serverInfo.blobSizeLimitBytes
)
return { blobSizeLimitBytes }
},
data() {
return {
dragover: false,
@ -214,9 +226,10 @@ export default {
return
}
if (file.size > 104857600) {
this.dragError =
'Your files are too powerful (for now). Maximum upload size is 100mb!'
if (file.size > this.blobSizeLimitBytes) {
this.dragError = `Your files are too powerful (for now). Maximum upload size is ${prettyFileSize(
this.blobSizeLimitBytes
)} mb!`
return
}

Просмотреть файл

@ -22,7 +22,7 @@ const { buildContext } = require('./modules/shared')
const knex = require('./db/knex')
const { monitorActiveConnections } = require('./logging/httpServerMonitoring')
const { buildErrorFormatter } = require('@/modules/core/graph/setup')
const { isDevEnv, isTestEnv } = require('@/modules/core/helpers/envHelper')
const { isDevEnv, isTestEnv } = require('@/modules/shared/helpers/envHelper')
let graphqlServer

2
packages/server/bootstrap.js поставляемый
Просмотреть файл

@ -14,7 +14,7 @@ const {
isApolloMonitoringEnabled,
getApolloServerVersion,
getServerVersion
} = require('./modules/core/helpers/envHelper')
} = require('./modules/shared/helpers/envHelper')
if (isApolloMonitoringEnabled() && !getApolloServerVersion()) {
process.env.APOLLO_SERVER_USER_VERSION = getServerVersion()

Просмотреть файл

@ -1,12 +1,18 @@
const {
getBlobMetadata,
getBlobMetadataCollection,
blobCollectionSummary
blobCollectionSummary,
getFileSizeLimit
} = require('@/modules/blobstorage/services')
const { NotFoundError, ResourceMismatch } = require('@/modules/shared/errors')
const { UserInputError } = require('apollo-server-errors')
module.exports = {
ServerInfo: {
blobSizeLimitBytes() {
return getFileSizeLimit()
}
},
Stream: {
async blobs(parent, args) {
const streamId = parent.id

Просмотреть файл

@ -1,3 +1,7 @@
extend type ServerInfo {
blobSizeLimitBytes: Int!
}
extend type Stream {
"""
Get the metadata collection of blobs stored for this stream.

Просмотреть файл

@ -25,7 +25,8 @@ const {
markUploadOverFileSizeLimit,
deleteBlob,
getBlobMetadata,
getBlobMetadataCollection
getBlobMetadataCollection,
getFileSizeLimit
} = require('@/modules/blobstorage/services')
const {
NotFoundError,
@ -82,8 +83,7 @@ exports.init = async (app) => {
const finalizePromises = []
const busboy = Busboy({
headers: req.headers,
// this is 100 MB which matches the current frontend file size limit
limits: { fileSize: 104_857_600 }
limits: { fileSize: getFileSizeLimit() }
})
const streamId = req.params.streamId
busboy.on('file', (formKey, file, info) => {

Просмотреть файл

@ -4,6 +4,7 @@ const {
ResourceMismatch,
BadRequestError
} = require('@/modules/shared/errors')
const { getFileSizeLimitMB } = require('@/modules/shared/helpers/envHelper')
const BlobStorage = () => knex('blob_storage')
const blobLookup = ({ blobId, streamId }) =>
@ -154,6 +155,8 @@ const updateBlobMetadata = async (streamId, blobId, updateCallback) => {
return { blobId, fileName, ...updateData }
}
const getFileSizeLimit = () => getFileSizeLimitMB() * 1024 * 1024
module.exports = {
cursorFromRows,
decodeCursor,
@ -167,5 +170,6 @@ module.exports = {
getBlobMetadataCollection,
blobCollectionSummary,
getBlobs,
getBlob
getBlob,
getFileSizeLimit
}

Просмотреть файл

@ -22,11 +22,19 @@ function getApolloServerVersion() {
return process.env.APOLLO_SERVER_USER_VERSION
}
function getFileSizeLimitMB() {
let sizeLimit = 100
const suppliedSize = parseInt(process.env.FILE_SIZE_LIMIT_MB)
if (suppliedSize) sizeLimit = suppliedSize
return sizeLimit
}
module.exports = {
isTestEnv,
isDevEnv,
isProdEnv,
getServerVersion,
isApolloMonitoringEnabled,
getApolloServerVersion
getApolloServerVersion,
getFileSizeLimitMB
}

Просмотреть файл

@ -41,6 +41,8 @@ services:
restart: always
ports:
- '127.0.0.1:8000:80'
environment:
FILE_SIZE_LIMIT_MB: 100
speckle-server:
image: speckle/speckle-server:2
@ -78,6 +80,8 @@ services:
S3_BUCKET: 'speckle-server'
S3_CREATE_BUCKET: 'true'
FILE_SIZE_LIMIT_MB: 100
speckle-preview-service:
image: speckle/speckle-preview-service:2
restart: always

Просмотреть файл

@ -94,6 +94,9 @@ spec:
name: "{{ .Values.secretName }}"
key: session_secret
- name: FILE_SIZE_LIMIT_MB
value: {{ .Values.file_size_limit_mb | quote }}
# *** Redis ***
- name: REDIS_URL
valueFrom:

Просмотреть файл

@ -90,5 +90,7 @@ spec:
name: {{ .Values.secretName }}
key: s3_secret_key
- name: FILE_IMPORT_TIME_LIMIT_MIN
value: {{ .Values.fileimport_service.time_limit_min }}
{{- end }}

Просмотреть файл

@ -43,3 +43,7 @@ spec:
port: 80
initialDelaySeconds: 5
periodSeconds: 5
env:
- name: FILE_SIZE_LIMIT_MB
value: {{ .Values.file_size_limit_mb | quote }}

Просмотреть файл

@ -102,6 +102,7 @@ fileimport_service:
limits:
cpu: 1000m
memory: 2Gi
time_limit_min: 10
secretName: server-vars
@ -111,3 +112,4 @@ cert_manager_issuer: letsencrypt-staging
helm_test_enabled: true
create_namespace: false
file_size_limit_mb: 100

Просмотреть файл

@ -16,3 +16,4 @@ s3:
cert_manager_issuer: ~
enable_prometheus_monitoring: true
file_size_limit_mb: 300