diff --git a/packages/frontend-2/components/settings/workspaces/security/DefaultSeat.vue b/packages/frontend-2/components/settings/workspaces/security/DefaultSeat.vue new file mode 100644 index 000000000..da7fb8657 --- /dev/null +++ b/packages/frontend-2/components/settings/workspaces/security/DefaultSeat.vue @@ -0,0 +1,176 @@ + + + diff --git a/packages/frontend-2/lib/settings/composables/management.ts b/packages/frontend-2/lib/settings/composables/management.ts index c04a5ffaa..2de3e9658 100644 --- a/packages/frontend-2/lib/settings/composables/management.ts +++ b/packages/frontend-2/lib/settings/composables/management.ts @@ -13,8 +13,7 @@ import type { WorkspaceUpdateInput, AddDomainToWorkspaceInput } from '~~/lib/common/generated/gql/graphql' -import type { WorkspaceDomain, Workspace } from '~/lib/common/generated/gql/graphql' -import { gql } from '@apollo/client' +import type { Workspace } from '~/lib/common/generated/gql/graphql' export function useUpdateWorkspace() { const { mutate, loading } = useMutation(settingsUpdateWorkspaceMutation) @@ -49,14 +48,7 @@ export function useAddWorkspaceDomain() { const { triggerNotification } = useGlobalToast() return { - mutate: async ( - input: AddDomainToWorkspaceInput, - domains: WorkspaceDomain[], - discoverabilityEnabled: boolean, - domainBasedMembershipProtectionEnabled: boolean, - hasAccessToSSO: boolean, - hasAccessToDomainBasedSecurityPolicies: boolean - ) => { + mutate: async (input: AddDomainToWorkspaceInput) => { const result = await apollo .mutate({ mutation: settingsAddWorkspaceDomainMutation, @@ -66,37 +58,6 @@ export function useAddWorkspaceDomain() { workspaceId: input.workspaceId } }, - optimisticResponse: { - workspaceMutations: { - addDomain: { - __typename: 'Workspace', - id: input.workspaceId, - slug: ( - apollo.readFragment({ - id: getCacheId('Workspace', input.workspaceId), - fragment: gql` - fragment AddDomainWorkspace on Workspace { - slug - } - ` - }) as Workspace - ).slug, - domains: [ - ...domains, - { - __typename: 'WorkspaceDomain', - id: '', - domain: input.domain - } - ], - discoverabilityEnabled, - domainBasedMembershipProtectionEnabled, - hasAccessToSSO, - hasAccessToDomainBasedSecurityPolicies, - discoverabilityAutoJoinEnabled: false - } - } - }, update: (cache, res) => { const { data } = res if (!data?.workspaceMutations) return diff --git a/packages/frontend-2/lib/workspaces/graphql/mutations.ts b/packages/frontend-2/lib/workspaces/graphql/mutations.ts index 19539f573..c8d7604d3 100644 --- a/packages/frontend-2/lib/workspaces/graphql/mutations.ts +++ b/packages/frontend-2/lib/workspaces/graphql/mutations.ts @@ -163,3 +163,14 @@ export const workspaceUpdateAutoJoinMutation = graphql(` } } `) + +export const workspaceUpdateDefaultSeatTypeMutation = graphql(` + mutation WorkspaceUpdateDefaultSeatTypeMutation($input: WorkspaceUpdateInput!) { + workspaceMutations { + update(input: $input) { + id + defaultSeatType + } + } + } +`) diff --git a/packages/server/modules/core/graph/dataloaders/index.ts b/packages/server/modules/core/graph/dataloaders/index.ts index 640395239..90e7ec868 100644 --- a/packages/server/modules/core/graph/dataloaders/index.ts +++ b/packages/server/modules/core/graph/dataloaders/index.ts @@ -92,7 +92,7 @@ import { CommitWithStreamBranchMetadata } from '@/modules/core/domain/commits/types' import { logger } from '@/observability/logging' -import { getLastVersionByProjectIdFactory } from '@/modules/core/repositories/versions' +import { getLastVersionsByProjectIdFactory } from '@/modules/core/repositories/versions' import { StreamRoles } from '@speckle/shared' declare module '@/modules/core/loaders' { @@ -137,7 +137,7 @@ const dataLoadersDefinition = defineRequestDataloaders( const getStreamsSourceApps = getStreamsSourceAppsFactory({ db }) const getUsers = getUsersFactory({ db }) const getStreamsCollaborators = getStreamsCollaboratorsFactory({ db }) - const getLastVersionByProjectId = getLastVersionByProjectIdFactory({ db }) + const getLastestVersionsByProjectId = getLastVersionsByProjectIdFactory({ db }) const getStreamsCollaboratorCounts = getStreamsCollaboratorCountsFactory({ db }) @@ -371,11 +371,11 @@ const dataLoadersDefinition = defineRequestDataloaders( } } })(), - getLastVersion: createLoader>( + getLatestVersions: createLoader>( async (projectIds) => { const results = keyBy( - await getLastVersionByProjectId({ projectIds }), - (c) => c.projectId + await getLastestVersionsByProjectId({ projectIds }), + (c) => c[0].projectId ) return projectIds.map((projectId) => results[projectId] || null) } diff --git a/packages/server/modules/core/graph/resolvers/versions.ts b/packages/server/modules/core/graph/resolvers/versions.ts index ce3a61c6d..ef313d7b0 100644 --- a/packages/server/modules/core/graph/resolvers/versions.ts +++ b/packages/server/modules/core/graph/resolvers/versions.ts @@ -134,18 +134,23 @@ export = { project }) - let lastVersion: Version | null + let latestVersion: Version | null = null + let latestVersions: Array | null = null if (getTypeFromPath(info) === 'Model') { - lastVersion = await ctx.loaders + latestVersion = await ctx.loaders .forRegion({ db: projectDB }) .branches.getLatestCommit.load(parent.branchId) } else { - lastVersion = await ctx.loaders + latestVersions = await ctx.loaders .forRegion({ db: projectDB }) - .streams.getLastVersion.load(parent.streamId) + .streams.getLatestVersions.load(parent.streamId) } - if (lastVersion?.id === parent.id) return parent.referencedObject + if ( + latestVersion?.id === parent.id || + latestVersions?.find((lv) => lv.id === parent.id) + ) + return parent.referencedObject if (isBeyondLimit) return null return parent.referencedObject } diff --git a/packages/server/modules/core/repositories/versions.ts b/packages/server/modules/core/repositories/versions.ts index 6a2b3c158..b7408888a 100644 --- a/packages/server/modules/core/repositories/versions.ts +++ b/packages/server/modules/core/repositories/versions.ts @@ -1,31 +1,26 @@ -import { Commits, knex, StreamCommits } from '@/modules/core/dbSchema' +import { BranchCommits, knex, Branches, Commits } from '@/modules/core/dbSchema' import { Version } from '@/modules/core/domain/commits/types' import { Knex } from 'knex' +import { groupBy } from 'lodash' -const tables = { - versions: (db: Knex) => db(Commits.name) -} - -export const getLastVersionByProjectIdFactory = +export const getLastVersionsByProjectIdFactory = ({ db }: { db: Knex }) => async ({ projectIds }: { projectIds: readonly string[] - }): Promise> => { - const results = await tables - .versions(db) - .join(StreamCommits.name, StreamCommits.col.commitId, Commits.col.id) - .whereIn(StreamCommits.col.streamId, projectIds) - .distinctOn(StreamCommits.col.streamId) - .select([...Commits.cols, knex.raw(`stream_commits."streamId" as "projectId"`)]) + }): Promise>> => { + const res = await db(Branches.name) + .whereIn(Branches.col.streamId, projectIds) + .join(BranchCommits.name, BranchCommits.col.branchId, Branches.col.id) + .join(Commits.name, Commits.col.id, BranchCommits.col.commitId) + .distinctOn(Branches.col.id) + .select([...Commits.cols, knex.raw(`branches."streamId" as "projectId"`)]) .orderBy([ - { column: StreamCommits.col.streamId, order: 'desc' }, - { column: Commits.col.createdAt, order: 'desc' } + { column: Branches.col.id, order: 'desc' }, + { column: Commits.col.createdAt, order: 'desc' }, + { column: Commits.col.id, order: 'desc' } ]) - return results.reduce>( - (acc, curr) => ({ ...acc, [curr.projectId]: curr }), - {} - ) + return groupBy(res, 'projectId') } diff --git a/packages/server/modules/core/tests/helpers/creation.ts b/packages/server/modules/core/tests/helpers/creation.ts index 9118240d1..a594f0c35 100644 --- a/packages/server/modules/core/tests/helpers/creation.ts +++ b/packages/server/modules/core/tests/helpers/creation.ts @@ -2,8 +2,11 @@ import cryptoRandomString from 'crypto-random-string' import { Project } from '@/modules/core/domain/streams/types' import { ProjectRecordVisibility } from '@/modules/core/helpers/types' import { assign } from 'lodash' +import { BasicTestCommit } from '@/test/speckle-helpers/commitHelper' +import { BasicTestBranch } from '@/test/speckle-helpers/branchHelper' +import { BasicTestStream } from '@/test/speckle-helpers/streamHelper' -export const buildBasicTestProject = (overrides?: Partial): Project => +export const buildTestProject = (overrides?: Partial): Project => assign( { id: cryptoRandomString({ length: 10 }), @@ -19,3 +22,47 @@ export const buildBasicTestProject = (overrides?: Partial): Project => }, overrides ) + +export const buildBasicTestProject = ( + overrides?: Partial +): BasicTestStream => + assign( + { + name: cryptoRandomString({ length: 10 }), + isPublic: true, + ownerId: cryptoRandomString({ length: 10 }), + id: cryptoRandomString({ length: 10 }) + }, + overrides + ) + +export const buildBasicTestModel = ( + overrides?: Partial +): BasicTestBranch => + assign( + { + name: cryptoRandomString({ length: 10 }), + description: cryptoRandomString({ length: 10 }), + streamId: cryptoRandomString({ length: 10 }), + authorId: cryptoRandomString({ length: 10 }), + id: cryptoRandomString({ length: 10 }) + }, + overrides + ) + +export const buildBasicTestVersion = ( + overrides?: Partial +): BasicTestCommit => + assign( + { + id: cryptoRandomString({ length: 10 }), + objectId: cryptoRandomString({ length: 10 }), + streamId: cryptoRandomString({ length: 10 }), + authorId: cryptoRandomString({ length: 10 }), + branchId: cryptoRandomString({ length: 10 }), + branchName: cryptoRandomString({ length: 10 }), + message: cryptoRandomString({ length: 10 }), + createdAt: new Date() + }, + overrides + ) diff --git a/packages/server/modules/core/tests/integration/repositories/versions.spec.ts b/packages/server/modules/core/tests/integration/repositories/versions.spec.ts index 460e46280..ea2002749 100644 --- a/packages/server/modules/core/tests/integration/repositories/versions.spec.ts +++ b/packages/server/modules/core/tests/integration/repositories/versions.spec.ts @@ -3,7 +3,7 @@ import { createRandomEmail, createRandomString } from '@/modules/core/helpers/testHelpers' -import { getLastVersionByProjectIdFactory } from '@/modules/core/repositories/versions' +import { getLastVersionsByProjectIdFactory } from '@/modules/core/repositories/versions' import { createTestUser } from '@/test/authHelper' import { BasicTestCommit, createTestCommit } from '@/test/speckle-helpers/commitHelper' import { createTestStream } from '@/test/speckle-helpers/streamHelper' @@ -11,7 +11,7 @@ import { expect } from 'chai' describe('Versions repositories @core', () => { describe('getLastVersionByProjectIdFactory returns a function that, ', () => { - const getLastVersionByProjectId = getLastVersionByProjectIdFactory({ db }) + const getLastVersionsByProjectId = getLastVersionsByProjectIdFactory({ db }) it('should return the last version for each projectId', async () => { const user = await createTestUser({ name: createRandomString(), @@ -57,13 +57,13 @@ describe('Versions repositories @core', () => { owner: user }) - const result = await getLastVersionByProjectId({ + const result = await getLastVersionsByProjectId({ projectIds: [project1.id, project2.id] }) const lastVersionProject1 = result[project1.id] const lastVersionProject2 = result[project2.id] - expect(lastVersionProject1.projectId).to.eq(project1.id) - expect(lastVersionProject2.projectId).to.eq(project2.id) + expect(lastVersionProject1[0].projectId).to.eq(project1.id) + expect(lastVersionProject2[0].projectId).to.eq(project2.id) }) }) }) diff --git a/packages/server/modules/core/tests/integration/versions.graph.spec.ts b/packages/server/modules/core/tests/integration/versions.graph.spec.ts index e396b382f..4cd234967 100644 --- a/packages/server/modules/core/tests/integration/versions.graph.spec.ts +++ b/packages/server/modules/core/tests/integration/versions.graph.spec.ts @@ -17,6 +17,7 @@ import { CreateProjectVersionDocument, CreateWorkspaceDocument, CreateWorkspaceProjectDocument, + GetProjectVersionsDocument, GetProjectWithModelVersionsDocument, GetProjectWithVersionsDocument } from '@/test/graphql/generated/graphql' @@ -42,13 +43,26 @@ import { WorkspaceReadOnlyError } from '@/modules/gatekeeper/errors/billing' import { CreateVersionInput } from '@/modules/core/graph/generated/graphql' import { getFeatureFlags } from '@/modules/shared/helpers/envHelper' import { getEventBus } from '@/modules/shared/services/eventBus' -import { createTestUser, login } from '@/test/authHelper' +import { buildBasicTestUser, createTestUser, login } from '@/test/authHelper' import { BasicTestStream, createTestStream } from '@/test/speckle-helpers/streamHelper' -import { BasicTestCommit, createTestCommit } from '@/test/speckle-helpers/commitHelper' +import { + BasicTestCommit, + createTestCommit, + createTestObject +} from '@/test/speckle-helpers/commitHelper' import { BranchCommits, Commits, StreamCommits } from '@/modules/core/dbSchema' import { BasicTestBranch, createTestBranch } from '@/test/speckle-helpers/branchHelper' import dayjs from 'dayjs' -import { createTestWorkspace } from '@/modules/workspaces/tests/helpers/creation' +import { + buildBasicTestWorkspace, + createTestWorkspace +} from '@/modules/workspaces/tests/helpers/creation' +import { + buildBasicTestModel, + buildBasicTestProject, + buildBasicTestVersion +} from '@/modules/core/tests/helpers/creation' +import { Optional } from '@speckle/shared' const getServerInfo = getServerInfoFactory({ db }) const getUser = legacyGetUserFactory({ db }) @@ -136,6 +150,156 @@ describe('Versions graphql @core', () => { } ) }) + ;(FF_BILLING_INTEGRATION_ENABLED ? describe : describe.skip)( + 'version limit query @new-versions', + () => { + const updateCommitCreatedAtDate = async ( + id: string, + createdAt: Date // Make the project read-only + ) => await db('commits').update({ createdAt }).where({ id }) + + const user = buildBasicTestUser() + const workspace = buildBasicTestWorkspace() + const model1 = buildBasicTestModel() + const model2 = buildBasicTestModel() + const project = buildBasicTestProject() + const version1 = buildBasicTestVersion() + const version2 = buildBasicTestVersion() + const version3 = buildBasicTestVersion() + let objectId1: Optional = undefined + let objectId2: Optional = undefined + let objectId3: Optional = undefined + + before(async () => { + user.id = await createUser(user) + await createTestWorkspace(workspace, user, { + addPlan: { name: 'free', status: 'valid' } + }) + project.workspaceId = workspace.id + await createTestStream(project, user) + await createTestBranch({ + branch: model1, + stream: project, + owner: user + }) + await createTestBranch({ + branch: model2, + stream: project, + owner: user + }) + + objectId1 = await createTestObject({ + projectId: project.id, + object: { test: 'a' } + }) + objectId2 = await createTestObject({ + projectId: project.id, + object: { test: 'b' } + }) + objectId3 = await createTestObject({ + projectId: project.id, + object: { test: 'c' } + }) + + version1.objectId = objectId1 + version1.authorId = user.id + version1.branchName = model1.name + version1.branchId = model1.id + version1.streamId = project.id + + version2.objectId = objectId2 + version2.authorId = user.id + version2.branchName = model2.name + version2.branchId = model2.id + version2.streamId = project.id + + version3.objectId = objectId3 + version3.authorId = user.id + version3.branchName = model2.name // model 2 has 2 versions + version3.branchId = model2.id + version3.streamId = project.id + + await createTestCommit(version1, { owner: user }) + await createTestCommit(version2, { owner: user }) + await createTestCommit(version3, { owner: user }) + }) + + it('shows the referencedObject of all user versions', async () => { + const apollo = await testApolloServer({ authUserId: user.id }) + await updateCommitCreatedAtDate(version1.id, new Date()) + await updateCommitCreatedAtDate(version2.id, new Date()) + await updateCommitCreatedAtDate(version3.id, new Date()) + + const res = await apollo.execute(GetProjectVersionsDocument, { + projectId: project.id + }) + + const versions = res.data?.project?.versions?.items + expect(res).to.not.haveGraphQLErrors() + expect(versions) + .to.be.a('array') + .and.to.have.lengthOf(3) + .and.to.deep.contain({ + id: version1.id, + referencedObject: objectId1 + }) + .and.to.deep.contain({ + id: version2.id, + referencedObject: objectId2 + }) + .and.to.deep.contain({ + id: version3.id, + referencedObject: objectId3 + }) + }) + + it('hides those ones that are more than 7 day old', async () => { + const apollo = await testApolloServer({ authUserId: user.id }) + const tenDaysAgo = dayjs().subtract(10, 'day').toDate() + await updateCommitCreatedAtDate(version1.id, new Date()) + await updateCommitCreatedAtDate(version2.id, new Date()) + await updateCommitCreatedAtDate(version3.id, tenDaysAgo) + + const res = await apollo.execute(GetProjectVersionsDocument, { + projectId: project.id + }) + + const versions = res.data?.project?.versions?.items + expect(res).to.not.haveGraphQLErrors() + expect(versions).to.be.a('array').and.to.have.lengthOf(3).and.to.deep.contain({ + id: version3.id, + referencedObject: null + }) + }) + + it('does not hide the latest commit of a model even if its +7 days old', async () => { + const apollo = await testApolloServer({ authUserId: user.id }) + const tenDaysAgo = dayjs().subtract(10, 'day').toDate() + const elevenDaysAgo = dayjs().subtract(11, 'day').toDate() + await updateCommitCreatedAtDate(version1.id, new Date()) + await updateCommitCreatedAtDate(version2.id, tenDaysAgo) + await updateCommitCreatedAtDate(version3.id, elevenDaysAgo) + + const res = await apollo.execute(GetProjectVersionsDocument, { + projectId: project.id + }) + + const versions = res.data?.project?.versions?.items + expect(res).to.not.haveGraphQLErrors() + expect(versions) + .to.be.a('array') + .and.to.have.lengthOf(3) + .and.to.deep.contain({ + id: version2.id, + referencedObject: objectId2 + }) + .and.to.deep.contain({ + id: version3.id, + referencedObject: null + }) + }) + } + ) ;(FF_PERSONAL_PROJECTS_LIMITS_ENABLED ? describe : describe.skip)( 'Version.referencedObject', () => { diff --git a/packages/server/modules/fileuploads/tests/unit/eventListener.spec.ts b/packages/server/modules/fileuploads/tests/unit/eventListener.spec.ts index 5f5025f51..553ace256 100644 --- a/packages/server/modules/fileuploads/tests/unit/eventListener.spec.ts +++ b/packages/server/modules/fileuploads/tests/unit/eventListener.spec.ts @@ -1,4 +1,4 @@ -import { buildBasicTestProject } from '@/modules/core/tests/helpers/creation' +import { buildTestProject } from '@/modules/core/tests/helpers/creation' import { buildMixpanelFake, MixpanelFakeEventRecord @@ -11,7 +11,7 @@ import { expect } from 'chai' describe('fileuploadsTrackingFactory creates a function, that @fileuploads', () => { const workspaceId = 'some_workspace_id' - const project = buildBasicTestProject({ workspaceId }) + const project = buildTestProject({ workspaceId }) const user = buildTestUserWithOptionalRole() const getProject = async () => project const getUser = async () => user @@ -45,7 +45,7 @@ describe('fileuploadsTrackingFactory creates a function, that @fileuploads', () }) it('does not include workspace_id if project does not belong to a workspace', async () => { - const projectWithoutWorkspace = buildBasicTestProject({ workspaceId: null }) + const projectWithoutWorkspace = buildTestProject({ workspaceId: null }) const events: MixpanelFakeEventRecord = [] const workspaceTracking = fileuploadTrackingFactory({ getProject: async () => projectWithoutWorkspace, diff --git a/packages/server/modules/gatekeeperCore/utils/limits.ts b/packages/server/modules/gatekeeperCore/utils/limits.ts index da2f16889..c684f2e24 100644 --- a/packages/server/modules/gatekeeperCore/utils/limits.ts +++ b/packages/server/modules/gatekeeperCore/utils/limits.ts @@ -29,6 +29,7 @@ export const getProjectLimitDateFactory = (deps: { ctx: GraphQLContext }): GetProjectLimitDate => { const getProjectLimitDate = getProjectLimitDateFactoryBase({ + // this one getWorkspaceLimits: async ({ workspaceId }) => (await deps.ctx.loaders.gatekeeper?.getWorkspaceLimits.load(workspaceId)) || null, getPersonalProjectLimits diff --git a/packages/server/test/graphql/generated/graphql.ts b/packages/server/test/graphql/generated/graphql.ts index 24d20f0f0..675a6460f 100644 --- a/packages/server/test/graphql/generated/graphql.ts +++ b/packages/server/test/graphql/generated/graphql.ts @@ -6012,6 +6012,13 @@ export type GetProjectCollaboratorsQueryVariables = Exact<{ export type GetProjectCollaboratorsQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, team: Array<{ __typename?: 'ProjectCollaborator', id: string, role: string }> } }; +export type GetProjectVersionsQueryVariables = Exact<{ + projectId: Scalars['String']['input']; +}>; + + +export type GetProjectVersionsQuery = { __typename?: 'Query', project: { __typename?: 'Project', versions: { __typename?: 'VersionCollection', items: Array<{ __typename?: 'Version', id: string, referencedObject?: string | null }> } } }; + export type CreateServerInviteMutationVariables = Exact<{ input: ServerInviteCreateInput; }>; @@ -6546,6 +6553,7 @@ export const CreateProjectDocument = {"kind":"Document","definitions":[{"kind":" export const BatchDeleteProjectsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"BatchDeleteProjects"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"ids"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"batchDelete"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"ids"},"value":{"kind":"Variable","name":{"kind":"Name","value":"ids"}}}]}]}}]}}]} as unknown as DocumentNode; export const UpdateProjectRoleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateProjectRole"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectUpdateRoleInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateRole"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicProjectFields"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicProjectFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"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":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"allowPublicComments"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const GetProjectCollaboratorsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetProjectCollaborators"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]}}]} as unknown as DocumentNode; +export const GetProjectVersionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetProjectVersions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"versions"},"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":"referencedObject"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const CreateServerInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateServerInvite"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInviteCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInviteCreate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; export const CreateStreamInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateStreamInvite"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"StreamInviteCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamInviteCreate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; export const ResendInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ResendInvite"},"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":"inviteResend"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"inviteId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"inviteId"}}}]}]}}]} as unknown as DocumentNode; diff --git a/packages/server/test/graphql/projects.ts b/packages/server/test/graphql/projects.ts index bd45158fc..614d5d8bb 100644 --- a/packages/server/test/graphql/projects.ts +++ b/packages/server/test/graphql/projects.ts @@ -109,3 +109,16 @@ export const getProjectCollaboratorsQuery = gql` } } ` + +export const getProjectVersionsQuery = gql` + query GetProjectVersions($projectId: String!) { + project(id: $projectId) { + versions { + items { + id + referencedObject + } + } + } + } +` diff --git a/packages/server/test/speckle-helpers/commitHelper.ts b/packages/server/test/speckle-helpers/commitHelper.ts index e6551c183..15d9004e0 100644 --- a/packages/server/test/speckle-helpers/commitHelper.ts +++ b/packages/server/test/speckle-helpers/commitHelper.ts @@ -65,7 +65,10 @@ export type BasicTestCommit = { createdAt?: Date } -export async function createTestObject(params: { projectId: string }) { +export async function createTestObject(params: { + projectId: string + object?: Record +}) { const projectDb = await getProjectDbClient(params) const createObject = createObjectFactory({ storeSingleObjectIfNotFoundFactory: storeSingleObjectIfNotFoundFactory({ @@ -75,7 +78,7 @@ export async function createTestObject(params: { projectId: string }) { return await createObject({ streamId: params.projectId, - object: { foo: 'bar' } + object: params.object ?? { foo: 'bar' } }) } diff --git a/utils/helm/speckle-server/templates/file.minion.ingress.yml b/utils/helm/speckle-server/templates/file.minion.ingress.yml index aa1d598db..95c9bf2a3 100644 --- a/utils/helm/speckle-server/templates/file.minion.ingress.yml +++ b/utils/helm/speckle-server/templates/file.minion.ingress.yml @@ -25,4 +25,18 @@ spec: name: speckle-objects port: name: web + - pathType: Prefix + path: "/api/stream/" + backend: + service: + name: speckle-objects + port: + name: web + - pathType: Prefix + path: "/api/thirdparty/gendo" + backend: + service: + name: speckle-objects + port: + name: web {{- end }} diff --git a/utils/helm/speckle-server/templates/objects.minion.ingress.yml b/utils/helm/speckle-server/templates/objects.minion.ingress.yml new file mode 100644 index 000000000..4ceb95432 --- /dev/null +++ b/utils/helm/speckle-server/templates/objects.minion.ingress.yml @@ -0,0 +1,49 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: speckle-server-minion-api-objects + namespace: {{ .Values.namespace }} + labels: +{{ include "speckle.labels" . | indent 4 }} + annotations: + nginx.org/mergeable-ingress-type: "minion" + {{- if .Values.cert_manager_issuer }} + cert-manager.io/cluster-issuer: {{ .Values.cert_manager_issuer }} + {{- end }} + nginx.ingress.kubernetes.io/proxy-body-size: {{ (printf "%dm" (int .Values.objects_size_limit_mb)) | quote }} +spec: + ingressClassName: nginx + rules: + - host: {{ .Values.domain }} + http: + paths: + - pathType: Prefix + path: "/api/getobjects/" + backend: + service: + name: speckle-objects + port: + name: web + - pathType: Prefix + path: "/api/objects/" + backend: + service: + name: speckle-objects + port: + name: web + - pathType: Prefix + path: "/api/diff/" + backend: + service: + name: speckle-objects + port: + name: web + - pathType: Prefix + path: "/objects/" + backend: + service: + name: speckle-objects + port: + name: web +{{- end }} diff --git a/utils/helm/speckle-server/values.schema.json b/utils/helm/speckle-server/values.schema.json index e0808d92b..9b27da9a7 100644 --- a/utils/helm/speckle-server/values.schema.json +++ b/utils/helm/speckle-server/values.schema.json @@ -187,6 +187,11 @@ "description": "The maximum time (unit is minutes) that a file import can take before it is considered to have failed", "default": 10 }, + "objects_size_limit_mb": { + "type": "number", + "description": "This maximum size of the POST request body (unit is Megabytes) that can be sent to the Speckle Objects REST APIs.", + "default": 100 + }, "enable_prometheus_monitoring": { "type": "boolean", "description": "If enabled, Speckle deploys a Prometheus ServiceMonitor resource", diff --git a/utils/helm/speckle-server/values.yaml b/utils/helm/speckle-server/values.yaml index 0a8b10c31..3f50b240e 100644 --- a/utils/helm/speckle-server/values.yaml +++ b/utils/helm/speckle-server/values.yaml @@ -132,6 +132,10 @@ file_size_limit_mb: 100 ## file_import_time_limit_min: 10 +## @param objects_size_limit_mb This maximum size of the POST request body (unit is Megabytes) that can be sent to the Speckle Objects REST APIs. +## +objects_size_limit_mb: 100 + ## @section Monitoring ## @descriptionStart ## This enables metrics generated by Speckle to be ingested by Prometheus: https://prometheus.io/