chore(server): migrating fully to ESM (#5042)
* wip * some extra fixes * stuff kinda works? * need to figure out mocks * need to figure out mocks * fix db listener * gqlgen fix * minor gqlgen watch adjustment * lint fixes * delete old codegen file * converting migrations to ESM * getModuleDIrectory * vitest sort of works * added back ts-vitest * resolve gql double load * fixing test timeout configs * TSC lint fix * fix automate tests * moar debugging * debugging * more debugging * codegen update * server works * yargs migrated * chore(server): getting rid of global mocks for Server ESM (#5046) * got rid of email mock * got rid of comment mocks * got rid of multi region mocks * got rid of stripe mock * admin override mock updated * removed final mock * fixing import.meta.resolve calls * another import.meta.resolve fix * added requested test * nyc ESM fix * removed unneeded deps + linting * yarn lock forgot to commit * tryna fix flakyness * email capture util fix * sendEmail fix * fix TSX check * sender transporter fix + CR comments * merge main fix * test fixx * circleci fix * gqlgen bigint fix * error formatter fix * more error formatting improvements * esmloader added to Dockerfile * more dockerfile fixes * bg jobs fix
This commit is contained in:
committed by
GitHub
parent
520e931211
commit
bde148f286
+1
-17
@@ -527,10 +527,6 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
command: cp .env.test-example .env.test
|
command: cp .env.test-example .env.test
|
||||||
working_directory: 'packages/server'
|
working_directory: 'packages/server'
|
||||||
- run:
|
|
||||||
name: 'Lint'
|
|
||||||
command: yarn lint:ci
|
|
||||||
working_directory: 'packages/server'
|
|
||||||
- run:
|
- run:
|
||||||
name: 'Run tests'
|
name: 'Run tests'
|
||||||
# Extra formatting to get timestamps on each line in CI (for profiling purposes)
|
# Extra formatting to get timestamps on each line in CI (for profiling purposes)
|
||||||
@@ -546,18 +542,6 @@ jobs:
|
|||||||
no_output_timeout: 30m
|
no_output_timeout: 30m
|
||||||
- codecov/upload:
|
- codecov/upload:
|
||||||
files: packages/server/coverage/lcov.info
|
files: packages/server/coverage/lcov.info
|
||||||
- run:
|
|
||||||
name: Introspect GQL schema for subsequent checks
|
|
||||||
command: 'IGNORE_MISSING_MIGRATIONS=true yarn cli graphql introspect'
|
|
||||||
working_directory: 'packages/server'
|
|
||||||
- run:
|
|
||||||
name: Checking for GQL schema breakages against app.speckle.systems
|
|
||||||
command: 'yarn rover graph check Speckle-Server@app-speckle-systems --schema ./introspected-schema.graphql'
|
|
||||||
working_directory: 'packages/server'
|
|
||||||
- run:
|
|
||||||
name: Checking for GQL schema breakages against latest.speckle.systems
|
|
||||||
command: 'yarn rover graph check Speckle-Server@latest-speckle-systems --schema ./introspected-schema.graphql'
|
|
||||||
working_directory: 'packages/server'
|
|
||||||
- store_test_results:
|
- store_test_results:
|
||||||
path: packages/server/reports
|
path: packages/server/reports
|
||||||
|
|
||||||
@@ -591,7 +575,7 @@ jobs:
|
|||||||
test-server-multiregion:
|
test-server-multiregion:
|
||||||
<<: *test-server-job
|
<<: *test-server-job
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.19.0
|
- image: cimg/node:22.6.0
|
||||||
- image: cimg/redis:7.2.4
|
- image: cimg/redis:7.2.4
|
||||||
- image: 'speckle/speckle-postgres'
|
- image: 'speckle/speckle-postgres'
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -78,9 +78,11 @@ bin/
|
|||||||
!packages/monitor-deployment/bin
|
!packages/monitor-deployment/bin
|
||||||
!packages/preview-service/bin
|
!packages/preview-service/bin
|
||||||
!packages/server/bin
|
!packages/server/bin
|
||||||
|
!packages/server/modules/cli/bin
|
||||||
!packages/viewer/src/modules/loaders/OBJ
|
!packages/viewer/src/modules/loaders/OBJ
|
||||||
|
|
||||||
# Server
|
# Server
|
||||||
multiregion.json
|
multiregion.json
|
||||||
multiregion.test.json
|
multiregion.test.json
|
||||||
packages/*/.tshy/
|
packages/*/.tshy/
|
||||||
|
.vite-node
|
||||||
+2
-1
@@ -95,7 +95,8 @@
|
|||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"typescript-eslint": "^8.20.0",
|
"typescript-eslint": "^8.20.0",
|
||||||
"wait-on": ">=7.2.0",
|
"wait-on": ">=7.2.0",
|
||||||
"vitest": "^3.0.7"
|
"vitest": "^3.0.7",
|
||||||
|
"@types/node": "22.16.2"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"commitizen": {
|
"commitizen": {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
import { CameraController, ViewMode, VisualDiffMode } from '@speckle/viewer'
|
import { CameraController, ViewMode, VisualDiffMode } from '@speckle/viewer'
|
||||||
import type { NumericPropertyInfo } from '@speckle/viewer'
|
import type { NumericPropertyInfo } from '@speckle/viewer'
|
||||||
import type { PartialDeep } from 'type-fest'
|
import type { PartialDeep } from 'type-fest'
|
||||||
import type { SectionBoxData } from '@speckle/shared/dist/esm/viewer/helpers/state.js'
|
import type { SectionBoxData } from '@speckle/shared/viewer/state'
|
||||||
|
|
||||||
type SerializedViewerState = SpeckleViewer.ViewerState.SerializedViewerState
|
type SerializedViewerState = SpeckleViewer.ViewerState.SerializedViewerState
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ import {
|
|||||||
import { useSynchronizedCookie } from '~~/lib/common/composables/reactiveCookie'
|
import { useSynchronizedCookie } from '~~/lib/common/composables/reactiveCookie'
|
||||||
import { buildManualPromise } from '@speckle/ui-components'
|
import { buildManualPromise } from '@speckle/ui-components'
|
||||||
import { PassReader } from '../extensions/PassReader'
|
import { PassReader } from '../extensions/PassReader'
|
||||||
import type { SectionBoxData } from '@speckle/shared/dist/esm/viewer/helpers/state.js'
|
import type { SectionBoxData } from '@speckle/shared/viewer/state'
|
||||||
|
|
||||||
export type LoadedModel = NonNullable<
|
export type LoadedModel = NonNullable<
|
||||||
Get<ViewerLoadedResourcesQuery, 'project.models.items[0]'>
|
Get<ViewerLoadedResourcesQuery, 'project.models.items[0]'>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ import {
|
|||||||
import { setupDebugMode } from '~~/lib/viewer/composables/setup/dev'
|
import { setupDebugMode } from '~~/lib/viewer/composables/setup/dev'
|
||||||
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
|
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
|
||||||
import { useMixpanel } from '~~/lib/core/composables/mp'
|
import { useMixpanel } from '~~/lib/core/composables/mp'
|
||||||
import type { SectionBoxData } from '@speckle/shared/dist/esm/viewer/helpers/state.js'
|
import type { SectionBoxData } from '@speckle/shared/viewer/state'
|
||||||
|
|
||||||
function useViewerIsBusyEventHandler() {
|
function useViewerIsBusyEventHandler() {
|
||||||
const state = useInjectedViewerState()
|
const state = useInjectedViewerState()
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ COPY --link --from=dependency-stage /speckle-server/node_modules ./node_modules
|
|||||||
|
|
||||||
WORKDIR /speckle-server/packages/server
|
WORKDIR /speckle-server/packages/server
|
||||||
COPY --link --from=build-stage /speckle-server/packages/server/package.json ./package.json
|
COPY --link --from=build-stage /speckle-server/packages/server/package.json ./package.json
|
||||||
|
COPY --link --from=build-stage /speckle-server/packages/server/esmLoader.js ./esmLoader.js
|
||||||
|
COPY --link --from=build-stage /speckle-server/packages/server/root.js ./root.js
|
||||||
COPY --link --from=build-stage /speckle-server/packages/server/dist ./dist
|
COPY --link --from=build-stage /speckle-server/packages/server/dist ./dist
|
||||||
COPY --link --from=build-stage /speckle-server/packages/server/assets ./assets
|
COPY --link --from=build-stage /speckle-server/packages/server/assets ./assets
|
||||||
COPY --link --from=build-stage /speckle-server/packages/server/bin ./bin
|
COPY --link --from=build-stage /speckle-server/packages/server/bin ./bin
|
||||||
@@ -86,4 +88,4 @@ ARG NODE_ENV
|
|||||||
ENV NODE_ENV=${NODE_ENV} \
|
ENV NODE_ENV=${NODE_ENV} \
|
||||||
SPECKLE_SERVER_VERSION=${SPECKLE_SERVER_VERSION}
|
SPECKLE_SERVER_VERSION=${SPECKLE_SERVER_VERSION}
|
||||||
|
|
||||||
ENTRYPOINT [ "tini", "--", "/nodejs/bin/node", "./bin/www" ]
|
ENTRYPOINT [ "tini", "--", "/nodejs/bin/node", "--import=./esmLoader.js", "./bin/www" ]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import './bootstrap'
|
import './bootstrap.js'
|
||||||
import http from 'http'
|
import http from 'http'
|
||||||
import express, { Express } from 'express'
|
import express, { Express } from 'express'
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ import {
|
|||||||
import * as ModulesSetup from '@/modules/index'
|
import * as ModulesSetup from '@/modules/index'
|
||||||
import { GraphQLContext, Optional } from '@/modules/shared/helpers/typeHelper'
|
import { GraphQLContext, Optional } from '@/modules/shared/helpers/typeHelper'
|
||||||
|
|
||||||
import { get, has, isString } from 'lodash'
|
import { get, has, isString } from 'lodash-es'
|
||||||
import { corsMiddlewareFactory } from '@/modules/core/configs/cors'
|
import { corsMiddlewareFactory } from '@/modules/core/configs/cors'
|
||||||
import {
|
import {
|
||||||
authContextMiddleware,
|
authContextMiddleware,
|
||||||
@@ -103,17 +103,17 @@ const isWsServer = (server: http.Server | MockWsServer): server is MockWsServer
|
|||||||
* is that graphql-ws uses an entirely different protocol, so the client-side has to change as well, and so old clients
|
* is that graphql-ws uses an entirely different protocol, so the client-side has to change as well, and so old clients
|
||||||
* will be unable to use any WebSocket/subscriptions functionality with the updated server
|
* will be unable to use any WebSocket/subscriptions functionality with the updated server
|
||||||
*/
|
*/
|
||||||
export function buildApolloSubscriptionServer(params: {
|
export async function buildApolloSubscriptionServer(params: {
|
||||||
server: http.Server | MockWsServer
|
server: http.Server | MockWsServer
|
||||||
registers?: Registry[]
|
registers?: Registry[]
|
||||||
}): SubscriptionServer {
|
}): Promise<SubscriptionServer> {
|
||||||
const { server, registers } = params
|
const { server, registers } = params
|
||||||
const httpServer = isWsServer(server) ? undefined : server
|
const httpServer = isWsServer(server) ? undefined : server
|
||||||
const mockServer = isWsServer(server) ? server : undefined
|
const mockServer = isWsServer(server) ? server : undefined
|
||||||
|
|
||||||
// we have to break the type here, cause its a mock
|
// we have to break the type here, cause its a mock
|
||||||
const wsServer = mockServer ? (mockServer as unknown as ws.Server) : undefined
|
const wsServer = mockServer ? (mockServer as unknown as ws.Server) : undefined
|
||||||
const schema = ModulesSetup.graphSchema()
|
const schema = await ModulesSetup.graphSchema()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
metricConnectCounter,
|
metricConnectCounter,
|
||||||
@@ -250,7 +250,7 @@ export async function buildApolloServer(options?: {
|
|||||||
}): Promise<ApolloServer<GraphQLContext>> {
|
}): Promise<ApolloServer<GraphQLContext>> {
|
||||||
const includeStacktraceInErrorResponses = isDevEnv() || isTestEnv()
|
const includeStacktraceInErrorResponses = isDevEnv() || isTestEnv()
|
||||||
const subscriptionServer = options?.subscriptionServer
|
const subscriptionServer = options?.subscriptionServer
|
||||||
const schema = ModulesSetup.graphSchema(await buildMocksConfig())
|
const schema = await ModulesSetup.graphSchema(await buildMocksConfig())
|
||||||
|
|
||||||
const server = new ApolloServer({
|
const server = new ApolloServer({
|
||||||
schema,
|
schema,
|
||||||
@@ -356,7 +356,7 @@ export async function init() {
|
|||||||
|
|
||||||
// Init HTTP server & subscription server
|
// Init HTTP server & subscription server
|
||||||
const server = http.createServer(app)
|
const server = http.createServer(app)
|
||||||
const subscriptionServer = buildApolloSubscriptionServer({
|
const subscriptionServer = await buildApolloSubscriptionServer({
|
||||||
server,
|
server,
|
||||||
registers: [promRegister]
|
registers: [promRegister]
|
||||||
})
|
})
|
||||||
@@ -398,8 +398,7 @@ const shouldUseFrontendProxy = () => isDevEnv()
|
|||||||
async function createFrontendProxy() {
|
async function createFrontendProxy() {
|
||||||
const frontendHost = process.env.FRONTEND_HOST || '127.0.0.1'
|
const frontendHost = process.env.FRONTEND_HOST || '127.0.0.1'
|
||||||
const frontendPort = process.env.FRONTEND_PORT || 8081
|
const frontendPort = process.env.FRONTEND_PORT || 8081
|
||||||
const { createProxyMiddleware } =
|
const { createProxyMiddleware } = await import('http-proxy-middleware')
|
||||||
require('http-proxy-middleware') as typeof import('http-proxy-middleware')
|
|
||||||
|
|
||||||
// even tho it has default values, it fixes http-proxy setting `Connection: close` on each request
|
// even tho it has default values, it fixes http-proxy setting `Connection: close` on each request
|
||||||
// slowing everything down
|
// slowing everything down
|
||||||
|
|||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find gqlgen and run it (we don't want to hardcode a specific node_modules path).
|
||||||
|
* We use this so that we can pass in specific flags to node before it even begins to run
|
||||||
|
*/
|
||||||
|
const relativeBinPath = './bin.js'
|
||||||
|
|
||||||
|
const mochaPath = import.meta.resolve('@graphql-codegen/cli')
|
||||||
|
const mochaPathDir = path.dirname(mochaPath)
|
||||||
|
const mochaBinPath = path.join(mochaPathDir, relativeBinPath)
|
||||||
|
await import(mochaBinPath)
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
'use strict'
|
import path from 'path'
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find mocha and run it (we don't want to hardcode a specific node_modules path).
|
* Find mocha and run it (we don't want to hardcode a specific node_modules path).
|
||||||
@@ -8,7 +7,7 @@ const path = require('path')
|
|||||||
*/
|
*/
|
||||||
const relativeBinPath = './bin/mocha.js'
|
const relativeBinPath = './bin/mocha.js'
|
||||||
|
|
||||||
const mochaPath = require.resolve('mocha')
|
const mochaPath = import.meta.resolve('mocha')
|
||||||
const mochaPathDir = path.dirname(mochaPath)
|
const mochaPathDir = path.dirname(mochaPath)
|
||||||
const mochaBinPath = path.join(mochaPathDir, relativeBinPath)
|
const mochaBinPath = path.join(mochaPathDir, relativeBinPath)
|
||||||
require(mochaBinPath)
|
await import(mochaBinPath)
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as 'www', but runs the app from source code directly through ts-node, so no need to build the app into /dist first.
|
|
||||||
* Although ts-node with swc is pretty fast, in production environments you should use `www` and a built app.
|
|
||||||
*/
|
|
||||||
|
|
||||||
require('ts-node/register')
|
|
||||||
const { logger } = require('../observability/logging')
|
|
||||||
const { init, startHttp } = require('../app')
|
|
||||||
|
|
||||||
init()
|
|
||||||
.then(({ app, graphqlServer, registers, server, readinessCheck }) =>
|
|
||||||
startHttp({ app, graphqlServer, registers, server, readinessCheck })
|
|
||||||
)
|
|
||||||
.catch((err) => {
|
|
||||||
logger.error(err, 'Failed to start server. Exiting with non-zero exit code...')
|
|
||||||
|
|
||||||
// kill it with fire 🔥
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 💥
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const { logger } = require('../dist/observability/logging')
|
import { logger } from '../dist/observability/logging.js'
|
||||||
const { init, startHttp } = require('../dist/app')
|
import { init, startHttp } from '../dist/app.js'
|
||||||
|
|
||||||
init()
|
init()
|
||||||
.then(({ app, graphqlServer, registers, server, readinessCheck }) =>
|
.then(({ app, graphqlServer, registers, server, readinessCheck }) =>
|
||||||
|
|||||||
Vendored
+16
-33
@@ -1,38 +1,28 @@
|
|||||||
/* istanbul ignore file */
|
import dotenv from 'dotenv'
|
||||||
|
import {
|
||||||
|
isTestEnv,
|
||||||
|
isDevEnv,
|
||||||
|
isApolloMonitoringEnabled,
|
||||||
|
getApolloServerVersion,
|
||||||
|
getServerVersion
|
||||||
|
} from '@/modules/shared/helpers/envHelper'
|
||||||
|
import { logger } from '@/observability/logging'
|
||||||
|
import { initOpenTelemetry } from '@/observability/otel'
|
||||||
|
import { patchKnex } from '@/modules/core/patches/knex'
|
||||||
|
import { appRoot, packageRoot } from '#/root.js'
|
||||||
|
import inspector from 'node:inspector'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bootstrap module that should be imported at the very top of each entry point module
|
* Bootstrap module that should be imported at the very top of each entry point module
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Conditionally change appRoot and packageRoot according to whether we're running from /dist/ or not (ts-node)
|
|
||||||
const path = require('path')
|
|
||||||
const isTsNode = !!process[Symbol.for('ts-node.register.instance')]
|
|
||||||
const appRoot = __dirname
|
|
||||||
const packageRoot = isTsNode ? appRoot : path.resolve(__dirname, '../')
|
|
||||||
|
|
||||||
// Initializing module aliases for absolute import paths
|
|
||||||
const moduleAlias = require('module-alias')
|
|
||||||
moduleAlias.addAliases({
|
|
||||||
'@': appRoot,
|
|
||||||
'#': packageRoot
|
|
||||||
})
|
|
||||||
|
|
||||||
// Initializing env vars
|
// Initializing env vars
|
||||||
const dotenv = require('dotenv')
|
|
||||||
const {
|
|
||||||
isTestEnv,
|
|
||||||
isApolloMonitoringEnabled,
|
|
||||||
getApolloServerVersion,
|
|
||||||
getServerVersion,
|
|
||||||
isDevEnv
|
|
||||||
} = require('@/modules/shared/helpers/envHelper')
|
|
||||||
const { logger } = require('@/observability/logging')
|
|
||||||
|
|
||||||
if (isApolloMonitoringEnabled() && !getApolloServerVersion()) {
|
if (isApolloMonitoringEnabled() && !getApolloServerVersion()) {
|
||||||
process.env.APOLLO_SERVER_USER_VERSION = getServerVersion()
|
process.env.APOLLO_SERVER_USER_VERSION = getServerVersion()
|
||||||
}
|
}
|
||||||
|
|
||||||
// If running in test env, load .env.test first
|
// If running in test env, load .env.test first
|
||||||
// (appRoot necessary, cause env files aren't loaded through require() calls)
|
// (appRoot necessary, cause env files aren't loaded through require()/import() calls)
|
||||||
if (isTestEnv()) {
|
if (isTestEnv()) {
|
||||||
const { error } = dotenv.config({ path: `${packageRoot}/.env.test` })
|
const { error } = dotenv.config({ path: `${packageRoot}/.env.test` })
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -48,7 +38,6 @@ if (isTestEnv()) {
|
|||||||
// (e.g. due to various child processes capturing the --inspect flag)
|
// (e.g. due to various child processes capturing the --inspect flag)
|
||||||
const startDebugger = process.env.START_DEBUGGER
|
const startDebugger = process.env.START_DEBUGGER
|
||||||
if ((isTestEnv() || isDevEnv()) && startDebugger) {
|
if ((isTestEnv() || isDevEnv()) && startDebugger) {
|
||||||
const inspector = require('node:inspector')
|
|
||||||
if (!inspector.url()) {
|
if (!inspector.url()) {
|
||||||
console.log('Debugger starting on process ' + process.pid)
|
console.log('Debugger starting on process ' + process.pid)
|
||||||
inspector.open(0, undefined, true)
|
inspector.open(0, undefined, true)
|
||||||
@@ -58,13 +47,7 @@ if ((isTestEnv() || isDevEnv()) && startDebugger) {
|
|||||||
dotenv.config({ path: `${packageRoot}/.env` })
|
dotenv.config({ path: `${packageRoot}/.env` })
|
||||||
|
|
||||||
// knex is a singleton controlled by module so can't wait til app init
|
// knex is a singleton controlled by module so can't wait til app init
|
||||||
const { initOpenTelemetry } = require('@/observability/otel')
|
|
||||||
initOpenTelemetry()
|
initOpenTelemetry()
|
||||||
|
|
||||||
const { patchKnex } = require('@/modules/core/patches/knex')
|
|
||||||
patchKnex()
|
patchKnex()
|
||||||
|
|
||||||
module.exports = {
|
export { appRoot, packageRoot }
|
||||||
appRoot,
|
|
||||||
packageRoot
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
import type { CodegenConfig } from '@graphql-codegen/cli'
|
||||||
|
|
||||||
|
const config: CodegenConfig = {
|
||||||
|
schema: ['modules/core/graph/schema.ts'],
|
||||||
|
overwrite: true,
|
||||||
|
documents: undefined,
|
||||||
|
generates: {
|
||||||
|
'modules/core/graph/generated/graphql.ts': {
|
||||||
|
plugins: ['typescript', 'typescript-resolvers'],
|
||||||
|
config: {
|
||||||
|
enumsAsConst: true,
|
||||||
|
contextType: '@/modules/shared/helpers/typeHelper#GraphQLContext',
|
||||||
|
mappers: {
|
||||||
|
Stream: '@/modules/core/helpers/graphTypes#StreamGraphQLReturn',
|
||||||
|
Commit: '@/modules/core/helpers/graphTypes#CommitGraphQLReturn',
|
||||||
|
Project: '@/modules/core/helpers/graphTypes#ProjectGraphQLReturn',
|
||||||
|
Object: '@/modules/core/helpers/graphTypes#ObjectGraphQLReturn',
|
||||||
|
Version: '@/modules/core/helpers/graphTypes#VersionGraphQLReturn',
|
||||||
|
ServerInvite:
|
||||||
|
'@/modules/core/helpers/graphTypes#ServerInviteGraphQLReturnType',
|
||||||
|
Model: '@/modules/core/helpers/graphTypes#ModelGraphQLReturn',
|
||||||
|
ModelsTreeItem:
|
||||||
|
'@/modules/core/helpers/graphTypes#ModelsTreeItemGraphQLReturn',
|
||||||
|
StreamAccessRequest:
|
||||||
|
'@/modules/accessrequests/helpers/graphTypes#StreamAccessRequestGraphQLReturn',
|
||||||
|
ProjectAccessRequest:
|
||||||
|
'@/modules/accessrequests/helpers/graphTypes#ProjectAccessRequestGraphQLReturn',
|
||||||
|
ProjectAccessRequestMutations:
|
||||||
|
'@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn',
|
||||||
|
LimitedUser: '@/modules/core/helpers/graphTypes#LimitedUserGraphQLReturn',
|
||||||
|
User: '@/modules/core/helpers/graphTypes#UserGraphQLReturn',
|
||||||
|
EmbedToken: '@/modules/core/helpers/graphTypes#EmbedTokenGraphQLReturn',
|
||||||
|
ActiveUserMutations:
|
||||||
|
'@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn',
|
||||||
|
UserMetaMutations:
|
||||||
|
'@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn',
|
||||||
|
UserEmailMutations:
|
||||||
|
'@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn',
|
||||||
|
ProjectMutations:
|
||||||
|
'@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn',
|
||||||
|
ProjectInviteMutations:
|
||||||
|
'@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn',
|
||||||
|
ModelMutations:
|
||||||
|
'@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn',
|
||||||
|
VersionMutations:
|
||||||
|
'@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn',
|
||||||
|
FileUploadMutations:
|
||||||
|
'@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn',
|
||||||
|
CommentMutations:
|
||||||
|
'@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn',
|
||||||
|
AutomateMutations:
|
||||||
|
'@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn',
|
||||||
|
AdminMutations:
|
||||||
|
'@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn',
|
||||||
|
AdminQueries: '@/modules/core/helpers/graphTypes#GraphQLEmptyReturn',
|
||||||
|
ServerStatistics: '@/modules/core/helpers/graphTypes#GraphQLEmptyReturn',
|
||||||
|
ServerStats: '@/modules/core/helpers/graphTypes#GraphQLEmptyReturn',
|
||||||
|
CommentReplyAuthorCollection:
|
||||||
|
'@/modules/comments/helpers/graphTypes#CommentReplyAuthorCollectionGraphQLReturn',
|
||||||
|
Comment: '@/modules/comments/helpers/graphTypes#CommentGraphQLReturn',
|
||||||
|
CommentPermissionChecks:
|
||||||
|
'@/modules/comments/helpers/graphTypes#CommentPermissionChecksGraphQLReturn',
|
||||||
|
PendingStreamCollaborator:
|
||||||
|
'@/modules/serverinvites/helpers/graphTypes#PendingStreamCollaboratorGraphQLReturn',
|
||||||
|
StreamCollaborator:
|
||||||
|
'@/modules/core/helpers/graphTypes#StreamCollaboratorGraphQLReturn',
|
||||||
|
ProjectCollaborator:
|
||||||
|
'@/modules/core/helpers/graphTypes#ProjectCollaboratorGraphQLReturn',
|
||||||
|
FileUpload: '@/modules/fileuploads/helpers/types#FileUploadGraphQLReturn',
|
||||||
|
AutomateFunction:
|
||||||
|
'@/modules/automate/helpers/graphTypes#AutomateFunctionGraphQLReturn',
|
||||||
|
AutomateFunctionRelease:
|
||||||
|
'@/modules/automate/helpers/graphTypes#AutomateFunctionReleaseGraphQLReturn',
|
||||||
|
Automation: '@/modules/automate/helpers/graphTypes#AutomationGraphQLReturn',
|
||||||
|
AutomationPermissionChecks:
|
||||||
|
'@/modules/automate/helpers/graphTypes#AutomationPermissionChecksGraphQLReturn',
|
||||||
|
AutomationRevision:
|
||||||
|
'@/modules/automate/helpers/graphTypes#AutomationRevisionGraphQLReturn',
|
||||||
|
AutomationRevisionFunction:
|
||||||
|
'@/modules/automate/helpers/graphTypes#AutomationRevisionFunctionGraphQLReturn',
|
||||||
|
AutomateRun: '@/modules/automate/helpers/graphTypes#AutomateRunGraphQLReturn',
|
||||||
|
AutomationRunTrigger:
|
||||||
|
'@/modules/automate/helpers/graphTypes#AutomationRunTriggerGraphQLReturn',
|
||||||
|
VersionCreatedTrigger:
|
||||||
|
'@/modules/automate/helpers/graphTypes#AutomationRunTriggerGraphQLReturn',
|
||||||
|
AutomationRevisionTriggerDefinition:
|
||||||
|
'@/modules/automate/helpers/graphTypes#AutomationRevisionTriggerDefinitionGraphQLReturn',
|
||||||
|
VersionCreatedTriggerDefinition:
|
||||||
|
'@/modules/automate/helpers/graphTypes#AutomationRevisionTriggerDefinitionGraphQLReturn',
|
||||||
|
AutomateFunctionRun:
|
||||||
|
'@/modules/automate/helpers/graphTypes#AutomateFunctionRunGraphQLReturn',
|
||||||
|
TriggeredAutomationsStatus:
|
||||||
|
'@/modules/automate/helpers/graphTypes#TriggeredAutomationsStatusGraphQLReturn',
|
||||||
|
ProjectAutomationMutations:
|
||||||
|
'@/modules/automate/helpers/graphTypes#ProjectAutomationMutationsGraphQLReturn',
|
||||||
|
ProjectTriggeredAutomationsStatusUpdatedMessage:
|
||||||
|
'@/modules/automate/helpers/graphTypes#ProjectTriggeredAutomationsStatusUpdatedMessageGraphQLReturn',
|
||||||
|
ProjectAutomationsUpdatedMessage:
|
||||||
|
'@/modules/automate/helpers/graphTypes#ProjectAutomationsUpdatedMessageGraphQLReturn',
|
||||||
|
UserAutomateInfo:
|
||||||
|
'@/modules/automate/helpers/graphTypes#UserAutomateInfoGraphQLReturn',
|
||||||
|
Workspace:
|
||||||
|
'@/modules/workspacesCore/helpers/graphTypes#WorkspaceGraphQLReturn',
|
||||||
|
WorkspaceSso:
|
||||||
|
'@/modules/workspacesCore/helpers/graphTypes#WorkspaceSsoGraphQLReturn',
|
||||||
|
WorkspaceMutations:
|
||||||
|
'@/modules/workspacesCore/helpers/graphTypes#WorkspaceMutationsGraphQLReturn',
|
||||||
|
WorkspaceJoinRequestMutations:
|
||||||
|
'@/modules/workspacesCore/helpers/graphTypes#WorkspaceJoinRequestMutationsGraphQLReturn',
|
||||||
|
WorkspaceInviteMutations:
|
||||||
|
'@/modules/workspacesCore/helpers/graphTypes#WorkspaceInviteMutationsGraphQLReturn',
|
||||||
|
WorkspacePlan:
|
||||||
|
'@/modules/gatekeeperCore/helpers/graphTypes#WorkspacePlanGraphQLReturn',
|
||||||
|
WorkspacePlanUsage:
|
||||||
|
'@/modules/gatekeeperCore/helpers/graphTypes#WorkspacePlanUsageGraphQLReturn',
|
||||||
|
WorkspaceProjectMutations:
|
||||||
|
'@/modules/workspacesCore/helpers/graphTypes#WorkspaceProjectMutationsGraphQLReturn',
|
||||||
|
WorkspaceBillingMutations:
|
||||||
|
'@/modules/gatekeeper/helpers/graphTypes#WorkspaceBillingMutationsGraphQLReturn',
|
||||||
|
PendingWorkspaceCollaborator:
|
||||||
|
'@/modules/workspacesCore/helpers/graphTypes#PendingWorkspaceCollaboratorGraphQLReturn',
|
||||||
|
WorkspaceCollaborator:
|
||||||
|
'@/modules/workspacesCore/helpers/graphTypes#WorkspaceCollaboratorGraphQLReturn',
|
||||||
|
LimitedWorkspace:
|
||||||
|
'@/modules/workspacesCore/helpers/graphTypes#LimitedWorkspaceGraphQLReturn',
|
||||||
|
LimitedWorkspaceCollaborator:
|
||||||
|
'@/modules/workspacesCore/helpers/graphTypes#LimitedWorkspaceCollaboratorGraphQLReturn',
|
||||||
|
WorkspaceSubscriptionSeats:
|
||||||
|
'@/modules/gatekeeper/helpers/graphTypes#WorkspaceSubscriptionSeatsGraphQLReturn',
|
||||||
|
WorkspaceJoinRequest:
|
||||||
|
'@/modules/workspacesCore/helpers/graphTypes#WorkspaceJoinRequestGraphQLReturn',
|
||||||
|
LimitedWorkspaceJoinRequest:
|
||||||
|
'@/modules/workspacesCore/helpers/graphTypes#LimitedWorkspaceJoinRequestGraphQLReturn',
|
||||||
|
ProjectMoveToWorkspaceDryRun:
|
||||||
|
'@/modules/workspacesCore/helpers/graphTypes#ProjectMoveToWorkspaceDryRunGraphQLReturn',
|
||||||
|
Webhook: '@/modules/webhooks/helpers/graphTypes#WebhookGraphQLReturn',
|
||||||
|
SmartTextEditorValue:
|
||||||
|
'@/modules/core/services/richTextEditorService#SmartTextEditorValueGraphQLReturn',
|
||||||
|
BlobMetadata: '@/modules/blobstorage/domain/types#BlobStorageItem',
|
||||||
|
ServerWorkspacesInfo: '@/modules/core/helpers/graphTypes#GraphQLEmptyReturn',
|
||||||
|
ActivityCollection:
|
||||||
|
'@/modules/activitystream/helpers/graphTypes#ActivityCollectionGraphQLReturn',
|
||||||
|
ProjectRole:
|
||||||
|
'@/modules/workspacesCore/helpers/graphTypes#ProjectRoleGraphQLReturn',
|
||||||
|
ServerApp: '@/modules/auth/helpers/graphTypes#ServerAppGraphQLReturn',
|
||||||
|
ServerAppListItem:
|
||||||
|
'@/modules/auth/helpers/graphTypes#ServerAppListItemGraphQLReturn',
|
||||||
|
ServerInfo: '@/modules/core/helpers/graphTypes#ServerInfoGraphQLReturn',
|
||||||
|
Branch: '@/modules/core/helpers/graphTypes#BranchGraphQLReturn',
|
||||||
|
GendoAIRender:
|
||||||
|
'@/modules/gendo/helpers/types/graphTypes#GendoAIRenderGraphQLReturn',
|
||||||
|
ServerMultiRegionConfiguration:
|
||||||
|
'@/modules/core/helpers/graphTypes#GraphQLEmptyReturn',
|
||||||
|
ServerInfoMutations:
|
||||||
|
'@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn',
|
||||||
|
ServerRegionMutations:
|
||||||
|
'@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn',
|
||||||
|
ServerRegionItem:
|
||||||
|
'@/modules/multiregion/helpers/graphTypes#ServerRegionItemGraphQLReturn',
|
||||||
|
Price: '@/modules/gatekeeperCore/helpers/graphTypes#PriceGraphQLReturn',
|
||||||
|
WorkspaceSubscription:
|
||||||
|
'@/modules/gatekeeper/helpers/graphTypes#WorkspaceSubscriptionGraphQLReturn',
|
||||||
|
UserMeta: '@/modules/core/helpers/graphTypes#UserMetaGraphQLReturn',
|
||||||
|
ProjectPermissionChecks:
|
||||||
|
'@/modules/core/helpers/graphTypes#ProjectPermissionChecksGraphQLReturn',
|
||||||
|
ModelPermissionChecks:
|
||||||
|
'@/modules/core/helpers/graphTypes#ModelPermissionChecksGraphQLReturn',
|
||||||
|
VersionPermissionChecks:
|
||||||
|
'@/modules/core/helpers/graphTypes#VersionPermissionChecksGraphQLReturn',
|
||||||
|
RootPermissionChecks:
|
||||||
|
'@/modules/core/helpers/graphTypes#RootPermissionChecksGraphQLReturn',
|
||||||
|
WorkspacePermissionChecks:
|
||||||
|
'@/modules/workspacesCore/helpers/graphTypes#WorkspacePermissionChecksGraphQLReturn'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'modules/cross-server-sync/graph/generated/graphql.ts': {
|
||||||
|
plugins: ['typescript', 'typescript-operations'],
|
||||||
|
documents: ['modules/cross-server-sync/**/*.{js,ts}'],
|
||||||
|
config: {
|
||||||
|
enumsAsConst: true,
|
||||||
|
scalars: {
|
||||||
|
JSONObject: 'Record<string, unknown>',
|
||||||
|
DateTime: 'string'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'test/graphql/generated/graphql.ts': {
|
||||||
|
plugins: ['typescript', 'typescript-operations', 'typed-document-node'],
|
||||||
|
documents: [
|
||||||
|
'test/graphql/*.{js,ts}',
|
||||||
|
'modules/**/tests/helpers/graphql.ts',
|
||||||
|
'modules/**/tests/helpers/*Graphql.ts',
|
||||||
|
'modules/**/tests/helpers/graphql/*.ts'
|
||||||
|
],
|
||||||
|
config: {
|
||||||
|
enumsAsConst: true,
|
||||||
|
scalars: {
|
||||||
|
JSONObject: 'Record<string, unknown>',
|
||||||
|
DateTime: 'string'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
enumsAsConst: true,
|
||||||
|
scalars: {
|
||||||
|
JSONObject: 'Record<string, unknown>',
|
||||||
|
DateTime: 'Date'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
overwrite: true
|
|
||||||
schema:
|
|
||||||
- 'modules/schema.ts'
|
|
||||||
documents: null
|
|
||||||
generates:
|
|
||||||
modules/core/graph/generated/graphql.ts:
|
|
||||||
plugins:
|
|
||||||
- 'typescript'
|
|
||||||
- 'typescript-resolvers'
|
|
||||||
config:
|
|
||||||
enumsAsConst: true
|
|
||||||
contextType: '@/modules/shared/helpers/typeHelper#GraphQLContext'
|
|
||||||
mappers:
|
|
||||||
Stream: '@/modules/core/helpers/graphTypes#StreamGraphQLReturn'
|
|
||||||
Commit: '@/modules/core/helpers/graphTypes#CommitGraphQLReturn'
|
|
||||||
Project: '@/modules/core/helpers/graphTypes#ProjectGraphQLReturn'
|
|
||||||
Object: '@/modules/core/helpers/graphTypes#ObjectGraphQLReturn'
|
|
||||||
Version: '@/modules/core/helpers/graphTypes#VersionGraphQLReturn'
|
|
||||||
ServerInvite: '@/modules/core/helpers/graphTypes#ServerInviteGraphQLReturnType'
|
|
||||||
Model: '@/modules/core/helpers/graphTypes#ModelGraphQLReturn'
|
|
||||||
ModelsTreeItem: '@/modules/core/helpers/graphTypes#ModelsTreeItemGraphQLReturn'
|
|
||||||
StreamAccessRequest: '@/modules/accessrequests/helpers/graphTypes#StreamAccessRequestGraphQLReturn'
|
|
||||||
ProjectAccessRequest: '@/modules/accessrequests/helpers/graphTypes#ProjectAccessRequestGraphQLReturn'
|
|
||||||
ProjectAccessRequestMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'
|
|
||||||
LimitedUser: '@/modules/core/helpers/graphTypes#LimitedUserGraphQLReturn'
|
|
||||||
User: '@/modules/core/helpers/graphTypes#UserGraphQLReturn'
|
|
||||||
EmbedToken: '@/modules/core/helpers/graphTypes#EmbedTokenGraphQLReturn'
|
|
||||||
ActiveUserMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'
|
|
||||||
UserMetaMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'
|
|
||||||
UserEmailMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'
|
|
||||||
ProjectMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'
|
|
||||||
ProjectInviteMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'
|
|
||||||
ModelMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'
|
|
||||||
VersionMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'
|
|
||||||
FileUploadMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'
|
|
||||||
CommentMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'
|
|
||||||
AutomateMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'
|
|
||||||
AdminMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'
|
|
||||||
AdminQueries: '@/modules/core/helpers/graphTypes#GraphQLEmptyReturn'
|
|
||||||
ServerStatistics: '@/modules/core/helpers/graphTypes#GraphQLEmptyReturn'
|
|
||||||
ServerStats: '@/modules/core/helpers/graphTypes#GraphQLEmptyReturn'
|
|
||||||
CommentReplyAuthorCollection: '@/modules/comments/helpers/graphTypes#CommentReplyAuthorCollectionGraphQLReturn'
|
|
||||||
Comment: '@/modules/comments/helpers/graphTypes#CommentGraphQLReturn'
|
|
||||||
CommentPermissionChecks: '@/modules/comments/helpers/graphTypes#CommentPermissionChecksGraphQLReturn'
|
|
||||||
PendingStreamCollaborator: '@/modules/serverinvites/helpers/graphTypes#PendingStreamCollaboratorGraphQLReturn'
|
|
||||||
StreamCollaborator: '@/modules/core/helpers/graphTypes#StreamCollaboratorGraphQLReturn'
|
|
||||||
ProjectCollaborator: '@/modules/core/helpers/graphTypes#ProjectCollaboratorGraphQLReturn'
|
|
||||||
FileUpload: '@/modules/fileuploads/helpers/types#FileUploadGraphQLReturn'
|
|
||||||
AutomateFunction: '@/modules/automate/helpers/graphTypes#AutomateFunctionGraphQLReturn'
|
|
||||||
AutomateFunctionRelease: '@/modules/automate/helpers/graphTypes#AutomateFunctionReleaseGraphQLReturn'
|
|
||||||
Automation: '@/modules/automate/helpers/graphTypes#AutomationGraphQLReturn'
|
|
||||||
AutomationPermissionChecks: '@/modules/automate/helpers/graphTypes#AutomationPermissionChecksGraphQLReturn'
|
|
||||||
AutomationRevision: '@/modules/automate/helpers/graphTypes#AutomationRevisionGraphQLReturn'
|
|
||||||
AutomationRevisionFunction: '@/modules/automate/helpers/graphTypes#AutomationRevisionFunctionGraphQLReturn'
|
|
||||||
AutomateRun: '@/modules/automate/helpers/graphTypes#AutomateRunGraphQLReturn'
|
|
||||||
AutomationRunTrigger: '@/modules/automate/helpers/graphTypes#AutomationRunTriggerGraphQLReturn'
|
|
||||||
VersionCreatedTrigger: '@/modules/automate/helpers/graphTypes#AutomationRunTriggerGraphQLReturn'
|
|
||||||
AutomationRevisionTriggerDefinition: '@/modules/automate/helpers/graphTypes#AutomationRevisionTriggerDefinitionGraphQLReturn'
|
|
||||||
VersionCreatedTriggerDefinition: '@/modules/automate/helpers/graphTypes#AutomationRevisionTriggerDefinitionGraphQLReturn'
|
|
||||||
AutomateFunctionRun: '@/modules/automate/helpers/graphTypes#AutomateFunctionRunGraphQLReturn'
|
|
||||||
TriggeredAutomationsStatus: '@/modules/automate/helpers/graphTypes#TriggeredAutomationsStatusGraphQLReturn'
|
|
||||||
ProjectAutomationMutations: '@/modules/automate/helpers/graphTypes#ProjectAutomationMutationsGraphQLReturn'
|
|
||||||
ProjectTriggeredAutomationsStatusUpdatedMessage: '@/modules/automate/helpers/graphTypes#ProjectTriggeredAutomationsStatusUpdatedMessageGraphQLReturn'
|
|
||||||
ProjectAutomationsUpdatedMessage: '@/modules/automate/helpers/graphTypes#ProjectAutomationsUpdatedMessageGraphQLReturn'
|
|
||||||
UserAutomateInfo: '@/modules/automate/helpers/graphTypes#UserAutomateInfoGraphQLReturn'
|
|
||||||
Workspace: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceGraphQLReturn'
|
|
||||||
WorkspaceSso: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceSsoGraphQLReturn'
|
|
||||||
WorkspaceMutations: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceMutationsGraphQLReturn'
|
|
||||||
WorkspaceJoinRequestMutations: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceJoinRequestMutationsGraphQLReturn'
|
|
||||||
WorkspaceInviteMutations: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceInviteMutationsGraphQLReturn'
|
|
||||||
WorkspacePlan: '@/modules/gatekeeperCore/helpers/graphTypes#WorkspacePlanGraphQLReturn'
|
|
||||||
WorkspacePlanUsage: '@/modules/gatekeeperCore/helpers/graphTypes#WorkspacePlanUsageGraphQLReturn'
|
|
||||||
WorkspaceProjectMutations: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceProjectMutationsGraphQLReturn'
|
|
||||||
WorkspaceBillingMutations: '@/modules/gatekeeper/helpers/graphTypes#WorkspaceBillingMutationsGraphQLReturn'
|
|
||||||
PendingWorkspaceCollaborator: '@/modules/workspacesCore/helpers/graphTypes#PendingWorkspaceCollaboratorGraphQLReturn'
|
|
||||||
WorkspaceCollaborator: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceCollaboratorGraphQLReturn'
|
|
||||||
LimitedWorkspace: '@/modules/workspacesCore/helpers/graphTypes#LimitedWorkspaceGraphQLReturn'
|
|
||||||
LimitedWorkspaceCollaborator: '@/modules/workspacesCore/helpers/graphTypes#LimitedWorkspaceCollaboratorGraphQLReturn'
|
|
||||||
WorkspaceSubscriptionSeats: '@/modules/gatekeeper/helpers/graphTypes#WorkspaceSubscriptionSeatsGraphQLReturn'
|
|
||||||
WorkspaceJoinRequest: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceJoinRequestGraphQLReturn'
|
|
||||||
LimitedWorkspaceJoinRequest: '@/modules/workspacesCore/helpers/graphTypes#LimitedWorkspaceJoinRequestGraphQLReturn'
|
|
||||||
ProjectMoveToWorkspaceDryRun: '@/modules/workspacesCore/helpers/graphTypes#ProjectMoveToWorkspaceDryRunGraphQLReturn'
|
|
||||||
Webhook: '@/modules/webhooks/helpers/graphTypes#WebhookGraphQLReturn'
|
|
||||||
SmartTextEditorValue: '@/modules/core/services/richTextEditorService#SmartTextEditorValueGraphQLReturn'
|
|
||||||
BlobMetadata: '@/modules/blobstorage/domain/types#BlobStorageItem'
|
|
||||||
ServerWorkspacesInfo: '@/modules/core/helpers/graphTypes#GraphQLEmptyReturn'
|
|
||||||
ActivityCollection: '@/modules/activitystream/helpers/graphTypes#ActivityCollectionGraphQLReturn'
|
|
||||||
ProjectRole: '@/modules/workspacesCore/helpers/graphTypes#ProjectRoleGraphQLReturn'
|
|
||||||
ServerApp: '@/modules/auth/helpers/graphTypes#ServerAppGraphQLReturn'
|
|
||||||
ServerAppListItem: '@/modules/auth/helpers/graphTypes#ServerAppListItemGraphQLReturn'
|
|
||||||
ServerInfo: '@/modules/core/helpers/graphTypes#ServerInfoGraphQLReturn'
|
|
||||||
Branch: '@/modules/core/helpers/graphTypes#BranchGraphQLReturn'
|
|
||||||
GendoAIRender: '@/modules/gendo/helpers/types/graphTypes#GendoAIRenderGraphQLReturn'
|
|
||||||
ServerMultiRegionConfiguration: '@/modules/core/helpers/graphTypes#GraphQLEmptyReturn'
|
|
||||||
ServerInfoMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'
|
|
||||||
ServerRegionMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'
|
|
||||||
ServerRegionItem: '@/modules/multiregion/helpers/graphTypes#ServerRegionItemGraphQLReturn'
|
|
||||||
Price: '@/modules/gatekeeperCore/helpers/graphTypes#PriceGraphQLReturn'
|
|
||||||
WorkspaceSubscription: '@/modules/gatekeeper/helpers/graphTypes#WorkspaceSubscriptionGraphQLReturn'
|
|
||||||
UserMeta: '@/modules/core/helpers/graphTypes#UserMetaGraphQLReturn'
|
|
||||||
ProjectPermissionChecks: '@/modules/core/helpers/graphTypes#ProjectPermissionChecksGraphQLReturn'
|
|
||||||
ModelPermissionChecks: '@/modules/core/helpers/graphTypes#ModelPermissionChecksGraphQLReturn'
|
|
||||||
VersionPermissionChecks: '@/modules/core/helpers/graphTypes#VersionPermissionChecksGraphQLReturn'
|
|
||||||
RootPermissionChecks: '@/modules/core/helpers/graphTypes#RootPermissionChecksGraphQLReturn'
|
|
||||||
WorkspacePermissionChecks: '@/modules/workspacesCore/helpers/graphTypes#WorkspacePermissionChecksGraphQLReturn'
|
|
||||||
modules/cross-server-sync/graph/generated/graphql.ts:
|
|
||||||
plugins:
|
|
||||||
- 'typescript'
|
|
||||||
- 'typescript-operations'
|
|
||||||
documents:
|
|
||||||
- 'modules/cross-server-sync/**/*.{js,ts}'
|
|
||||||
config:
|
|
||||||
enumsAsConst: true
|
|
||||||
scalars:
|
|
||||||
JSONObject: Record<string, unknown>
|
|
||||||
DateTime: string
|
|
||||||
test/graphql/generated/graphql.ts:
|
|
||||||
plugins:
|
|
||||||
- 'typescript'
|
|
||||||
- 'typescript-operations'
|
|
||||||
- 'typed-document-node'
|
|
||||||
documents:
|
|
||||||
- 'test/graphql/*.{js,ts}'
|
|
||||||
- 'modules/**/tests/helpers/graphql.ts'
|
|
||||||
- 'modules/**/tests/helpers/*Graphql.ts'
|
|
||||||
- 'modules/**/tests/helpers/graphql/*.ts'
|
|
||||||
config:
|
|
||||||
enumsAsConst: true
|
|
||||||
scalars:
|
|
||||||
JSONObject: Record<string, unknown>
|
|
||||||
DateTime: string
|
|
||||||
config:
|
|
||||||
enumsAsConst: true
|
|
||||||
scalars:
|
|
||||||
JSONObject: Record<string, unknown>
|
|
||||||
DateTime: Date
|
|
||||||
require:
|
|
||||||
- ts-node/register
|
|
||||||
- tsconfig-paths/register
|
|
||||||
@@ -13,24 +13,24 @@ const configs = [
|
|||||||
...baseConfigs,
|
...baseConfigs,
|
||||||
{
|
{
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
sourceType: 'commonjs',
|
sourceType: 'module',
|
||||||
globals: {
|
globals: {
|
||||||
...globals.node
|
...globals.node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.mjs'],
|
files: ['**/*.cjs', '**/*.cts'],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
sourceType: 'module'
|
sourceType: 'commonjs'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...tseslint.configs.recommendedTypeChecked.map((c) => ({
|
...tseslint.configs.recommendedTypeChecked.map((c) => ({
|
||||||
...c,
|
...c,
|
||||||
files: [...(c.files || []), '**/*.ts', '**/*.d.ts']
|
files: [...(c.files || []), '**/*.ts', '**/*.d.ts', '**/*.cts']
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
files: ['**/*.ts', '**/*.d.ts'],
|
files: ['**/*.ts', '**/*.d.ts', '**/*.cts'],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
tsconfigRootDir: getESMDirname(import.meta.url),
|
tsconfigRootDir: getESMDirname(import.meta.url),
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
import { pathToFileURL } from 'node:url'
|
||||||
|
import { register } from 'node:module'
|
||||||
|
import { appRoot, packageRoot } from './root.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must be invoked through --import when running the node app to set up the following:
|
||||||
|
* - Custom path aliases for imports
|
||||||
|
* - Extensionless imports
|
||||||
|
* - Directory imports like in CJS
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PATH ALIAS DEFINITIONS
|
||||||
|
*/
|
||||||
|
const aliases = {
|
||||||
|
'@/': appRoot + '/',
|
||||||
|
'#/': packageRoot + '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EXTENSIONS TO EVALUATE FOR EXTENSIONLESS IMPORTS
|
||||||
|
*/
|
||||||
|
const extensions = ['.js', '.mjs', '.cjs', '.json']
|
||||||
|
|
||||||
|
// Register the module hooks
|
||||||
|
register('./esmLoader.js', {
|
||||||
|
parentURL: import.meta.url
|
||||||
|
})
|
||||||
|
|
||||||
|
// Custom path resolver
|
||||||
|
function resolveAlias(specifier) {
|
||||||
|
for (const [alias, target] of Object.entries(aliases)) {
|
||||||
|
if (specifier.startsWith(alias)) {
|
||||||
|
const relativePath = specifier.replace(alias, target)
|
||||||
|
return pathToFileURL(path.resolve(relativePath)).href
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null // No alias found, fall back to default resolution
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjust global ESM resolution logic to allow for path/package aliases, dir imports and extensionless imports
|
||||||
|
*/
|
||||||
|
export async function resolve(specifier, _context, nextResolve) {
|
||||||
|
// Resolve alias
|
||||||
|
const aliasResolved = resolveAlias(specifier)
|
||||||
|
specifier = aliasResolved || specifier
|
||||||
|
|
||||||
|
// Try to resolve as is
|
||||||
|
let throwableError = undefined
|
||||||
|
try {
|
||||||
|
return await nextResolve(specifier)
|
||||||
|
} catch (e) {
|
||||||
|
throwableError = e
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDirImport = throwableError.code === 'ERR_UNSUPPORTED_DIR_IMPORT'
|
||||||
|
|
||||||
|
// Didn't work, try with extensions
|
||||||
|
for (const ext of extensions) {
|
||||||
|
try {
|
||||||
|
return await nextResolve(specifier + ext)
|
||||||
|
} catch (e) {
|
||||||
|
if (!throwableError) {
|
||||||
|
throwableError = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it was a dir import also, try that with extensions
|
||||||
|
specifier = isDirImport ? path.join(specifier, 'index') : specifier
|
||||||
|
for (const ext of extensions) {
|
||||||
|
try {
|
||||||
|
return await nextResolve(specifier + ext)
|
||||||
|
} catch (e) {
|
||||||
|
if (!throwableError) {
|
||||||
|
throwableError = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw throwableError
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ensureErrorOrWrapAsCause } from '@/modules/shared/errors/ensureError'
|
import { ensureErrorOrWrapAsCause } from '@/modules/shared/errors/ensureError'
|
||||||
import { join, merge } from 'lodash'
|
import { join, merge } from 'lodash-es'
|
||||||
import { MultiError } from 'verror'
|
import VError from 'verror'
|
||||||
import {
|
import {
|
||||||
FreeConnectionsCalculators,
|
FreeConnectionsCalculators,
|
||||||
MultiDBCheck,
|
MultiDBCheck,
|
||||||
@@ -30,7 +30,7 @@ export const handleLivenessFactory =
|
|||||||
', '
|
', '
|
||||||
)} is not available.`,
|
)} is not available.`,
|
||||||
{
|
{
|
||||||
cause: new MultiError(
|
cause: new VError.MultiError(
|
||||||
Object.entries(allPostgresResults).map((kv) =>
|
Object.entries(allPostgresResults).map((kv) =>
|
||||||
ensureErrorOrWrapAsCause(
|
ensureErrorOrWrapAsCause(
|
||||||
//HACK: kv[1] is not typed correctly as the filter does not narrow the type
|
//HACK: kv[1] is not typed correctly as the filter does not narrow the type
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable no-restricted-imports */
|
/* eslint-disable no-restricted-imports */
|
||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
import { packageRoot } from './bootstrap'
|
import { packageRoot } from './bootstrap.js'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -268,4 +268,4 @@ const resolvers: Resolvers = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = resolvers
|
export default resolvers
|
||||||
|
|||||||
@@ -26,4 +26,4 @@ const ServerAccessRequestsModule: SpeckleModule = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = ServerAccessRequestsModule
|
export default ServerAccessRequestsModule
|
||||||
|
|||||||
@@ -51,15 +51,15 @@ import {
|
|||||||
} from '@/test/graphql/generated/graphql'
|
} from '@/test/graphql/generated/graphql'
|
||||||
import { testApolloServer, TestApolloServer } from '@/test/graphqlHelper'
|
import { testApolloServer, TestApolloServer } from '@/test/graphqlHelper'
|
||||||
import { truncateTables } from '@/test/hooks'
|
import { truncateTables } from '@/test/hooks'
|
||||||
import { EmailSendingServiceMock } from '@/test/mocks/global'
|
|
||||||
import {
|
import {
|
||||||
buildNotificationsStateTracker,
|
buildNotificationsStateTracker,
|
||||||
NotificationsStateManager
|
NotificationsStateManager
|
||||||
} from '@/test/notificationsHelper'
|
} from '@/test/notificationsHelper'
|
||||||
import { getStreamActivities } from '@/test/speckle-helpers/activityStreamHelper'
|
import { getStreamActivities } from '@/test/speckle-helpers/activityStreamHelper'
|
||||||
|
import { createEmailListener, TestEmailListener } from '@/test/speckle-helpers/email'
|
||||||
import { BasicTestStream, createTestStreams } from '@/test/speckle-helpers/streamHelper'
|
import { BasicTestStream, createTestStreams } from '@/test/speckle-helpers/streamHelper'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { noop } from 'lodash'
|
import { noop } from 'lodash-es'
|
||||||
|
|
||||||
const getUser = getUserFactory({ db })
|
const getUser = getUserFactory({ db })
|
||||||
const getStream = getStreamFactory({ db })
|
const getStream = getStreamFactory({ db })
|
||||||
@@ -150,6 +150,8 @@ describe('Project access requests', () => {
|
|||||||
id: ''
|
id: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let emailListener: TestEmailListener
|
||||||
|
|
||||||
let quitters: (() => void)[] = []
|
let quitters: (() => void)[] = []
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
@@ -164,15 +166,18 @@ describe('Project access requests', () => {
|
|||||||
authUserId: me.id
|
authUserId: me.id
|
||||||
})
|
})
|
||||||
notificationsStateManager = buildNotificationsStateTracker()
|
notificationsStateManager = buildNotificationsStateTracker()
|
||||||
|
emailListener = await createEmailListener()
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
emailListener.reset()
|
||||||
quitters.forEach((q) => q())
|
quitters.forEach((q) => q())
|
||||||
quitters = []
|
quitters = []
|
||||||
})
|
})
|
||||||
|
|
||||||
after(async () => {
|
after(async () => {
|
||||||
notificationsStateManager.destroy()
|
notificationsStateManager.destroy()
|
||||||
|
await emailListener.destroy()
|
||||||
})
|
})
|
||||||
|
|
||||||
const createReq = async (projectId: string) =>
|
const createReq = async (projectId: string) =>
|
||||||
@@ -228,10 +233,8 @@ describe('Project access requests', () => {
|
|||||||
eventFired = true
|
eventFired = true
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
const sendEmailCall = EmailSendingServiceMock.hijackFunction(
|
|
||||||
'sendEmail',
|
const { getSends } = emailListener.listen({ times: 1 })
|
||||||
async () => true
|
|
||||||
)
|
|
||||||
|
|
||||||
const waitForAck = notificationsStateManager.waitForAck(
|
const waitForAck = notificationsStateManager.waitForAck(
|
||||||
(e) => e.result?.type === NotificationType.NewStreamAccessRequest
|
(e) => e.result?.type === NotificationType.NewStreamAccessRequest
|
||||||
@@ -255,8 +258,9 @@ describe('Project access requests', () => {
|
|||||||
await waitForAck
|
await waitForAck
|
||||||
|
|
||||||
// email gets sent out
|
// email gets sent out
|
||||||
expect(sendEmailCall.args?.[0]?.[0]).to.be.ok
|
const sentEmails = getSends()
|
||||||
const emailParams = sendEmailCall.args[0][0]
|
expect(sentEmails.length).to.eq(1)
|
||||||
|
const emailParams = sentEmails[0]
|
||||||
|
|
||||||
expect(emailParams.subject).to.contain('A user requested access to your project')
|
expect(emailParams.subject).to.contain('A user requested access to your project')
|
||||||
expect(emailParams.html).to.be.ok
|
expect(emailParams.html).to.be.ok
|
||||||
|
|||||||
@@ -52,15 +52,15 @@ import {
|
|||||||
import { StreamRole } from '@/test/graphql/generated/graphql'
|
import { StreamRole } from '@/test/graphql/generated/graphql'
|
||||||
import { createAuthedTestContext, ServerAndContext } from '@/test/graphqlHelper'
|
import { createAuthedTestContext, ServerAndContext } from '@/test/graphqlHelper'
|
||||||
import { truncateTables } from '@/test/hooks'
|
import { truncateTables } from '@/test/hooks'
|
||||||
import { EmailSendingServiceMock } from '@/test/mocks/global'
|
|
||||||
import {
|
import {
|
||||||
buildNotificationsStateTracker,
|
buildNotificationsStateTracker,
|
||||||
NotificationsStateManager
|
NotificationsStateManager
|
||||||
} from '@/test/notificationsHelper'
|
} from '@/test/notificationsHelper'
|
||||||
import { getStreamActivities } from '@/test/speckle-helpers/activityStreamHelper'
|
import { getStreamActivities } from '@/test/speckle-helpers/activityStreamHelper'
|
||||||
|
import { createEmailListener, TestEmailListener } from '@/test/speckle-helpers/email'
|
||||||
import { BasicTestStream, createTestStreams } from '@/test/speckle-helpers/streamHelper'
|
import { BasicTestStream, createTestStreams } from '@/test/speckle-helpers/streamHelper'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { noop } from 'lodash'
|
import { noop } from 'lodash-es'
|
||||||
|
|
||||||
const getUser = getUserFactory({ db })
|
const getUser = getUserFactory({ db })
|
||||||
const getStreamCollaborators = getStreamCollaboratorsFactory({ db })
|
const getStreamCollaborators = getStreamCollaboratorsFactory({ db })
|
||||||
@@ -154,6 +154,8 @@ describe('Stream access requests', () => {
|
|||||||
id: ''
|
id: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let emailListener: TestEmailListener
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await cleanup()
|
await cleanup()
|
||||||
await createTestUsers([me, otherGuy, anotherGuy])
|
await createTestUsers([me, otherGuy, anotherGuy])
|
||||||
@@ -167,10 +169,16 @@ describe('Stream access requests', () => {
|
|||||||
context: await createAuthedTestContext(me.id)
|
context: await createAuthedTestContext(me.id)
|
||||||
}
|
}
|
||||||
notificationsStateManager = buildNotificationsStateTracker()
|
notificationsStateManager = buildNotificationsStateTracker()
|
||||||
|
emailListener = await createEmailListener()
|
||||||
})
|
})
|
||||||
|
|
||||||
after(async () => {
|
after(async () => {
|
||||||
notificationsStateManager.destroy()
|
notificationsStateManager.destroy()
|
||||||
|
await emailListener.destroy()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
emailListener.reset()
|
||||||
})
|
})
|
||||||
|
|
||||||
const createReq = (streamId: string) =>
|
const createReq = (streamId: string) =>
|
||||||
@@ -202,10 +210,7 @@ describe('Stream access requests', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('operation succeeds', async () => {
|
it('operation succeeds', async () => {
|
||||||
const sendEmailCall = EmailSendingServiceMock.hijackFunction(
|
const { getSends } = emailListener.listen({ times: 1 })
|
||||||
'sendEmail',
|
|
||||||
async () => true
|
|
||||||
)
|
|
||||||
|
|
||||||
const waitForAck = notificationsStateManager.waitForAck(
|
const waitForAck = notificationsStateManager.waitForAck(
|
||||||
(e) => e.result?.type === NotificationType.NewStreamAccessRequest
|
(e) => e.result?.type === NotificationType.NewStreamAccessRequest
|
||||||
@@ -226,8 +231,9 @@ describe('Stream access requests', () => {
|
|||||||
await waitForAck
|
await waitForAck
|
||||||
|
|
||||||
// email gets sent out
|
// email gets sent out
|
||||||
expect(sendEmailCall.args?.[0]?.[0]).to.be.ok
|
const sentEmails = getSends()
|
||||||
const emailParams = sendEmailCall.args[0][0]
|
expect(sentEmails.length).to.eq(1)
|
||||||
|
const emailParams = sentEmails[0]
|
||||||
|
|
||||||
expect(emailParams.subject).to.contain('A user requested access to your project')
|
expect(emailParams.subject).to.contain('A user requested access to your project')
|
||||||
expect(emailParams.html).to.be.ok
|
expect(emailParams.html).to.be.ok
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
import { CommentEvents, CommentEventsPayloads } from '@/modules/comments/domain/events'
|
import { CommentEvents, CommentEventsPayloads } from '@/modules/comments/domain/events'
|
||||||
import { ReplyCreateInput } from '@/modules/core/graph/generated/graphql'
|
import { ReplyCreateInput } from '@/modules/core/graph/generated/graphql'
|
||||||
import { EventBusListen } from '@/modules/shared/services/eventBus'
|
import { EventBusListen } from '@/modules/shared/services/eventBus'
|
||||||
import { has } from 'lodash'
|
import { has } from 'lodash-es'
|
||||||
import { OverrideProperties } from 'type-fest'
|
import { OverrideProperties } from 'type-fest'
|
||||||
|
|
||||||
const addThreadCreatedActivityFactory =
|
const addThreadCreatedActivityFactory =
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ const userTimelineQueryCore = async (
|
|||||||
return { items, cursor, totalCount }
|
return { items, cursor, totalCount }
|
||||||
}
|
}
|
||||||
|
|
||||||
export = {
|
export default {
|
||||||
LimitedUser: {
|
LimitedUser: {
|
||||||
async activity(parent, args) {
|
async activity(parent, args) {
|
||||||
return await userActivityQueryCore(parent, args)
|
return await userActivityQueryCore(parent, args)
|
||||||
|
|||||||
@@ -154,6 +154,6 @@ const activityModule: SpeckleModule = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = {
|
export default {
|
||||||
...activityModule
|
...activityModule
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// /* istanbul ignore file */
|
// /* istanbul ignore file */
|
||||||
exports.up = async (knex) => {
|
const up = async (knex) => {
|
||||||
await knex.schema.createTable('stream_activity', (table) => {
|
await knex.schema.createTable('stream_activity', (table) => {
|
||||||
// No foreign keys because the referenced objects may be deleted, but we want to keep their ids here in this table for future analysis
|
// No foreign keys because the referenced objects may be deleted, but we want to keep their ids here in this table for future analysis
|
||||||
table.string('streamId', 10)
|
table.string('streamId', 10)
|
||||||
@@ -18,6 +18,8 @@ exports.up = async (knex) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.down = async (knex) => {
|
const down = async (knex) => {
|
||||||
await knex.schema.dropTableIfExists('stream_activity')
|
await knex.schema.dropTableIfExists('stream_activity')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { up, down }
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import { SpeckleModule } from '@/modules/shared/helpers/typeHelper'
|
|||||||
import { moduleLogger } from '@/observability/logging'
|
import { moduleLogger } from '@/observability/logging'
|
||||||
import { readFile } from 'fs/promises'
|
import { readFile } from 'fs/promises'
|
||||||
import { getFrontendOrigin } from '@/modules/shared/helpers/envHelper'
|
import { getFrontendOrigin } from '@/modules/shared/helpers/envHelper'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
async function getExplorerHtml() {
|
async function getExplorerHtml() {
|
||||||
const fileBaseContents = await readFile(
|
const fileBaseContents = await readFile(
|
||||||
require.resolve('#/assets/apiexplorer/templates/explorer.html'),
|
fileURLToPath(import.meta.resolve('#/assets/apiexplorer/templates/explorer.html')),
|
||||||
{ encoding: 'utf-8' }
|
{ encoding: 'utf-8' }
|
||||||
)
|
)
|
||||||
return fileBaseContents.replace(
|
return fileBaseContents.replace(
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const revokeExistingAppCredentialsForUser = revokeExistingAppCredentialsForUserF
|
|||||||
db
|
db
|
||||||
})
|
})
|
||||||
|
|
||||||
export = {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
async app(_parent, args) {
|
async app(_parent, args) {
|
||||||
const app = await getApp({ id: args.id })
|
const app = await getApp({ id: args.id })
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { getAuthStrategies } from '@/modules/auth'
|
import { getAuthStrategies } from '@/modules/auth'
|
||||||
import { Resolvers } from '@/modules/core/graph/generated/graphql'
|
import { Resolvers } from '@/modules/core/graph/generated/graphql'
|
||||||
|
|
||||||
export = {
|
export default {
|
||||||
ServerInfo: {
|
ServerInfo: {
|
||||||
authStrategies() {
|
authStrategies() {
|
||||||
return getAuthStrategies()
|
return getAuthStrategies()
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { describe, it } from 'mocha'
|
|
||||||
import { getNameFromUserInfo } from '@/modules/auth/helpers/oidc'
|
import { getNameFromUserInfo } from '@/modules/auth/helpers/oidc'
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
getFrontendOrigin,
|
getFrontendOrigin,
|
||||||
getSessionSecret
|
getSessionSecret
|
||||||
} from '@/modules/shared/helpers/envHelper'
|
} from '@/modules/shared/helpers/envHelper'
|
||||||
import { isString, noop } from 'lodash'
|
import { isString, noop } from 'lodash-es'
|
||||||
import { CreateAuthorizationCode } from '@/modules/auth/domain/operations'
|
import { CreateAuthorizationCode } from '@/modules/auth/domain/operations'
|
||||||
import { ensureError, TIME_MS } from '@speckle/shared'
|
import { ensureError, TIME_MS } from '@speckle/shared'
|
||||||
import { LegacyGetUser } from '@/modules/core/domain/users/operations'
|
import { LegacyGetUser } from '@/modules/core/domain/users/operations'
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
// Knex table migrations
|
// Knex table migrations
|
||||||
exports.up = async (knex) => {
|
const up = async (knex) => {
|
||||||
// Applications that integrate with this server.
|
// Applications that integrate with this server.
|
||||||
await knex.schema.createTable('server_apps', (table) => {
|
await knex.schema.createTable('server_apps', (table) => {
|
||||||
table.string('id', 10).primary()
|
table.string('id', 10).primary()
|
||||||
@@ -93,7 +93,7 @@ exports.up = async (knex) => {
|
|||||||
// await knex( 'scopes' ).insert( appTokenScopes )
|
// await knex( 'scopes' ).insert( appTokenScopes )
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.down = async (knex) => {
|
const down = async (knex) => {
|
||||||
await knex.schema.dropTableIfExists('server_apps_scopes')
|
await knex.schema.dropTableIfExists('server_apps_scopes')
|
||||||
await knex.schema.dropTableIfExists('authorization_codes')
|
await knex.schema.dropTableIfExists('authorization_codes')
|
||||||
await knex.schema.dropTableIfExists('refresh_tokens')
|
await knex.schema.dropTableIfExists('refresh_tokens')
|
||||||
@@ -101,3 +101,5 @@ exports.down = async (knex) => {
|
|||||||
|
|
||||||
await knex.schema.dropTableIfExists('server_apps')
|
await knex.schema.dropTableIfExists('server_apps')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { up, down }
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ import {
|
|||||||
import { ServerAppRecord, UserRecord } from '@/modules/core/helpers/types'
|
import { ServerAppRecord, UserRecord } from '@/modules/core/helpers/types'
|
||||||
import cryptoRandomString from 'crypto-random-string'
|
import cryptoRandomString from 'crypto-random-string'
|
||||||
import { Knex } from 'knex'
|
import { Knex } from 'knex'
|
||||||
import { difference, omit } from 'lodash'
|
import { difference, omit } from 'lodash-es'
|
||||||
import { AppCreateError } from '@/modules/auth/errors'
|
import { AppCreateError } from '@/modules/auth/errors'
|
||||||
import { UserInputError } from '@/modules/core/errors/userinput'
|
import { UserInputError } from '@/modules/core/errors/userinput'
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
import { InvalidArgumentError } from '@/modules/shared/errors'
|
import { InvalidArgumentError } from '@/modules/shared/errors'
|
||||||
import { Nullable } from '@/modules/shared/helpers/typeHelper'
|
import { Nullable } from '@/modules/shared/helpers/typeHelper'
|
||||||
import { ServerAppsScopesRecord } from '@/modules/auth/helpers/types'
|
import { ServerAppsScopesRecord } from '@/modules/auth/helpers/types'
|
||||||
import { groupBy, mapValues } from 'lodash'
|
import { groupBy, mapValues } from 'lodash-es'
|
||||||
import { TokenScopeData } from '@/modules/shared/domain/rolesAndScopes/types'
|
import { TokenScopeData } from '@/modules/shared/domain/rolesAndScopes/types'
|
||||||
import { Knex } from 'knex'
|
import { Knex } from 'knex'
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Strategy, AuthenticateOptions } from 'passport'
|
|||||||
import passport from 'passport'
|
import passport from 'passport'
|
||||||
import type { Request, Response, NextFunction, RequestHandler } from 'express'
|
import type { Request, Response, NextFunction, RequestHandler } from 'express'
|
||||||
import { ensureError, type Optional, throwUncoveredError } from '@speckle/shared'
|
import { ensureError, type Optional, throwUncoveredError } from '@speckle/shared'
|
||||||
import { get, isArray, isObjectLike, isString } from 'lodash'
|
import { get, isArray, isObjectLike, isString } from 'lodash-es'
|
||||||
import type { PassportAuthenticateHandlerBuilder } from '@/modules/auth/domain/operations'
|
import type { PassportAuthenticateHandlerBuilder } from '@/modules/auth/domain/operations'
|
||||||
import { ExpectedAuthFailure } from '@/modules/auth/domain/const'
|
import { ExpectedAuthFailure } from '@/modules/auth/domain/const'
|
||||||
import type { ResolveAuthRedirectPath } from '@/modules/serverinvites/services/operations'
|
import type { ResolveAuthRedirectPath } from '@/modules/serverinvites/services/operations'
|
||||||
|
|||||||
@@ -80,4 +80,4 @@ const setupStrategiesFactory =
|
|||||||
return authStrategies
|
return authStrategies
|
||||||
}
|
}
|
||||||
|
|
||||||
export = setupStrategiesFactory
|
export default setupStrategiesFactory
|
||||||
|
|||||||
@@ -223,4 +223,4 @@ const azureAdStrategyBuilderFactory =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = azureAdStrategyBuilderFactory
|
export default azureAdStrategyBuilderFactory
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
getServerOrigin
|
getServerOrigin
|
||||||
} from '@/modules/shared/helpers/envHelper'
|
} from '@/modules/shared/helpers/envHelper'
|
||||||
import type { Request } from 'express'
|
import type { Request } from 'express'
|
||||||
import { get } from 'lodash'
|
import { get } from 'lodash-es'
|
||||||
import { ensureError, Optional } from '@speckle/shared'
|
import { ensureError, Optional } from '@speckle/shared'
|
||||||
import { ServerInviteRecord } from '@/modules/serverinvites/domain/types'
|
import { ServerInviteRecord } from '@/modules/serverinvites/domain/types'
|
||||||
import {
|
import {
|
||||||
@@ -200,4 +200,4 @@ const githubStrategyBuilderFactory =
|
|||||||
return strategy
|
return strategy
|
||||||
}
|
}
|
||||||
|
|
||||||
export = githubStrategyBuilderFactory
|
export default githubStrategyBuilderFactory
|
||||||
|
|||||||
@@ -212,4 +212,4 @@ const googleStrategyBuilderFactory =
|
|||||||
return strategy
|
return strategy
|
||||||
}
|
}
|
||||||
|
|
||||||
export = googleStrategyBuilderFactory
|
export default googleStrategyBuilderFactory
|
||||||
|
|||||||
@@ -162,4 +162,4 @@ const localStrategyBuilderFactory =
|
|||||||
return strategy
|
return strategy
|
||||||
}
|
}
|
||||||
|
|
||||||
export = localStrategyBuilderFactory
|
export default localStrategyBuilderFactory
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { getNameFromUserInfo } from '@/modules/auth/helpers/oidc'
|
|||||||
import { ServerInviteResourceType } from '@/modules/serverinvites/domain/constants'
|
import { ServerInviteResourceType } from '@/modules/serverinvites/domain/constants'
|
||||||
import { getResourceTypeRole } from '@/modules/serverinvites/helpers/core'
|
import { getResourceTypeRole } from '@/modules/serverinvites/helpers/core'
|
||||||
import { AuthStrategyBuilder } from '@/modules/auth/helpers/types'
|
import { AuthStrategyBuilder } from '@/modules/auth/helpers/types'
|
||||||
import { get } from 'lodash'
|
import { get } from 'lodash-es'
|
||||||
import { ensureError, Optional } from '@speckle/shared'
|
import { ensureError, Optional } from '@speckle/shared'
|
||||||
import { ServerInviteRecord } from '@/modules/serverinvites/domain/types'
|
import { ServerInviteRecord } from '@/modules/serverinvites/domain/types'
|
||||||
import {
|
import {
|
||||||
@@ -203,4 +203,4 @@ const oidcStrategyBuilderFactory =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = oidcStrategyBuilderFactory
|
export default oidcStrategyBuilderFactory
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ import { RateLimiterMemory } from 'rate-limiter-flexible'
|
|||||||
import { TIME } from '@speckle/shared'
|
import { TIME } from '@speckle/shared'
|
||||||
import type { Application } from 'express'
|
import type { Application } from 'express'
|
||||||
import { passportAuthenticationCallbackFactory } from '@/modules/auth/services/passportService'
|
import { passportAuthenticationCallbackFactory } from '@/modules/auth/services/passportService'
|
||||||
import { testLogger as logger } from '@/observability/logging'
|
import { extendLoggerComponent, logger as baseLogger } from '@/observability/logging'
|
||||||
import {
|
import {
|
||||||
processFinalizedProjectInviteFactory,
|
processFinalizedProjectInviteFactory,
|
||||||
validateProjectInviteBeforeFinalizationFactory
|
validateProjectInviteBeforeFinalizationFactory
|
||||||
@@ -188,6 +188,7 @@ const createUser = createUserFactory({
|
|||||||
})
|
})
|
||||||
const getUserByEmail = legacyGetUserByEmailFactory({ db })
|
const getUserByEmail = legacyGetUserByEmailFactory({ db })
|
||||||
const updateServerInfo = updateServerInfoFactory({ db })
|
const updateServerInfo = updateServerInfoFactory({ db })
|
||||||
|
const logger = extendLoggerComponent(baseLogger, 'auth-tests')
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'
|
|||||||
import { RelativeURL } from '@speckle/shared'
|
import { RelativeURL } from '@speckle/shared'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import type { Express } from 'express'
|
import type { Express } from 'express'
|
||||||
import { has, isString } from 'lodash'
|
import { has, isString } from 'lodash-es'
|
||||||
import request from 'supertest'
|
import request from 'supertest'
|
||||||
|
|
||||||
export const appId = 'spklwebapp' // same values as on FE
|
export const appId = 'spklwebapp' // same values as on FE
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import {
|
|||||||
TestApolloServer
|
TestApolloServer
|
||||||
} from '@/test/graphqlHelper'
|
} from '@/test/graphqlHelper'
|
||||||
import { beforeEachContext } from '@/test/hooks'
|
import { beforeEachContext } from '@/test/hooks'
|
||||||
import { EmailSendingServiceMock } from '@/test/mocks/global'
|
|
||||||
import { captureCreatedInvite } from '@/test/speckle-helpers/inviteHelper'
|
import { captureCreatedInvite } from '@/test/speckle-helpers/inviteHelper'
|
||||||
import {
|
import {
|
||||||
BasicTestStream,
|
BasicTestStream,
|
||||||
@@ -89,10 +88,6 @@ describe('Server registration', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
EmailSendingServiceMock.resetMockedFunctions()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('with local strategy (email/pw)', () => {
|
describe('with local strategy (email/pw)', () => {
|
||||||
it('works', async () => {
|
it('works', async () => {
|
||||||
const challenge = 'asd123'
|
const challenge = 'asd123'
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
} from '@speckle/shared'
|
} from '@speckle/shared'
|
||||||
import { randomUUID } from 'crypto'
|
import { randomUUID } from 'crypto'
|
||||||
import { automateLogger, type Logger } from '@/observability/logging'
|
import { automateLogger, type Logger } from '@/observability/logging'
|
||||||
import { has, isObjectLike, isEmpty } from 'lodash'
|
import { has, isObjectLike, isEmpty } from 'lodash-es'
|
||||||
import { getRequestLogger } from '@/observability/utils/requestContext'
|
import { getRequestLogger } from '@/observability/utils/requestContext'
|
||||||
|
|
||||||
export type AuthCodePayloadWithOrigin = AuthCodePayload & { origin: string }
|
export type AuthCodePayloadWithOrigin = AuthCodePayload & { origin: string }
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
|||||||
import { faker } from '@faker-js/faker'
|
import { faker } from '@faker-js/faker'
|
||||||
import { Automate, isNullOrUndefined, SourceAppNames } from '@speckle/shared'
|
import { Automate, isNullOrUndefined, SourceAppNames } from '@speckle/shared'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { times } from 'lodash'
|
import { times } from 'lodash-es'
|
||||||
|
|
||||||
const { FF_AUTOMATE_MODULE_ENABLED } = getFeatureFlags()
|
const { FF_AUTOMATE_MODULE_ENABLED } = getFeatureFlags()
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ import {
|
|||||||
getFunctionInputsForFrontendFactory
|
getFunctionInputsForFrontendFactory
|
||||||
} from '@/modules/automate/services/encryption'
|
} from '@/modules/automate/services/encryption'
|
||||||
import { buildDecryptor } from '@/modules/shared/utils/libsodium'
|
import { buildDecryptor } from '@/modules/shared/utils/libsodium'
|
||||||
import { keyBy } from 'lodash'
|
import * as _ from 'lodash-es'
|
||||||
import { redactWriteOnlyInputData } from '@/modules/automate/utils/jsonSchemaRedactor'
|
import { redactWriteOnlyInputData } from '@/modules/automate/utils/jsonSchemaRedactor'
|
||||||
import {
|
import {
|
||||||
ProjectSubscriptions,
|
ProjectSubscriptions,
|
||||||
@@ -142,7 +142,7 @@ const createAppToken = createAppTokenFactory({
|
|||||||
storeUserServerAppToken: storeUserServerAppTokenFactory({ db })
|
storeUserServerAppToken: storeUserServerAppTokenFactory({ db })
|
||||||
})
|
})
|
||||||
|
|
||||||
export = (FF_AUTOMATE_MODULE_ENABLED
|
export default (FF_AUTOMATE_MODULE_ENABLED
|
||||||
? {
|
? {
|
||||||
/**
|
/**
|
||||||
* If automate module is enabled
|
* If automate module is enabled
|
||||||
@@ -443,7 +443,7 @@ export = (FF_AUTOMATE_MODULE_ENABLED
|
|||||||
const fns = await ctx.loaders
|
const fns = await ctx.loaders
|
||||||
.forRegion({ db: projectDb })
|
.forRegion({ db: projectDb })
|
||||||
.automations.getRevisionFunctions.load(parent.id)
|
.automations.getRevisionFunctions.load(parent.id)
|
||||||
const fnsReleases = keyBy(
|
const fnsReleases = _.keyBy(
|
||||||
(
|
(
|
||||||
await ctx.loaders
|
await ctx.loaders
|
||||||
.forRegion({ db: projectDb })
|
.forRegion({ db: projectDb })
|
||||||
|
|||||||
@@ -394,4 +394,4 @@ const automateModule: SpeckleModule = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = automateModule
|
export default automateModule
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ import {
|
|||||||
import { Nullable, StreamRoles, isNullOrUndefined } from '@speckle/shared'
|
import { Nullable, StreamRoles, isNullOrUndefined } from '@speckle/shared'
|
||||||
import cryptoRandomString from 'crypto-random-string'
|
import cryptoRandomString from 'crypto-random-string'
|
||||||
import { Knex } from 'knex'
|
import { Knex } from 'knex'
|
||||||
import _, { clamp, groupBy, keyBy, pick } from 'lodash'
|
import { clamp, groupBy, keyBy, pick } from 'lodash-es'
|
||||||
import { SetOptional, SetRequired } from 'type-fest'
|
import { SetOptional, SetRequired } from 'type-fest'
|
||||||
|
|
||||||
const tables = {
|
const tables = {
|
||||||
@@ -163,7 +163,7 @@ export const upsertAutomationFunctionRunFactory =
|
|||||||
await tables
|
await tables
|
||||||
.automationFunctionRuns(deps.db)
|
.automationFunctionRuns(deps.db)
|
||||||
.insert(
|
.insert(
|
||||||
_.pick(automationFunctionRun, AutomationFunctionRuns.withoutTablePrefix.cols)
|
pick(automationFunctionRun, AutomationFunctionRuns.withoutTablePrefix.cols)
|
||||||
)
|
)
|
||||||
.onConflict(AutomationFunctionRuns.withoutTablePrefix.col.id)
|
.onConflict(AutomationFunctionRuns.withoutTablePrefix.col.id)
|
||||||
.merge([
|
.merge([
|
||||||
@@ -185,7 +185,7 @@ export const upsertAutomationRunFactory =
|
|||||||
async (automationRun: InsertableAutomationRun) => {
|
async (automationRun: InsertableAutomationRun) => {
|
||||||
await tables
|
await tables
|
||||||
.automationRuns(deps.db)
|
.automationRuns(deps.db)
|
||||||
.insert(_.pick(automationRun, AutomationRuns.withoutTablePrefix.cols))
|
.insert(pick(automationRun, AutomationRuns.withoutTablePrefix.cols))
|
||||||
.onConflict(AutomationRuns.withoutTablePrefix.col.id)
|
.onConflict(AutomationRuns.withoutTablePrefix.col.id)
|
||||||
.merge([
|
.merge([
|
||||||
AutomationRuns.withoutTablePrefix.col.status,
|
AutomationRuns.withoutTablePrefix.col.status,
|
||||||
@@ -198,7 +198,7 @@ export const upsertAutomationRunFactory =
|
|||||||
.insert(
|
.insert(
|
||||||
automationRun.triggers.map((t) => ({
|
automationRun.triggers.map((t) => ({
|
||||||
automationRunId: automationRun.id,
|
automationRunId: automationRun.id,
|
||||||
..._.pick(t, AutomationRunTriggers.withoutTablePrefix.cols)
|
...pick(t, AutomationRunTriggers.withoutTablePrefix.cols)
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
.onConflict()
|
.onConflict()
|
||||||
@@ -207,7 +207,7 @@ export const upsertAutomationRunFactory =
|
|||||||
.automationFunctionRuns(deps.db)
|
.automationFunctionRuns(deps.db)
|
||||||
.insert(
|
.insert(
|
||||||
automationRun.functionRuns.map((f) => ({
|
automationRun.functionRuns.map((f) => ({
|
||||||
..._.pick(f, AutomationFunctionRuns.withoutTablePrefix.cols),
|
...pick(f, AutomationFunctionRuns.withoutTablePrefix.cols),
|
||||||
runId: automationRun.id
|
runId: automationRun.id
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
@@ -372,7 +372,7 @@ export const storeAutomationRevisionFactory =
|
|||||||
(deps: { db: Knex }): StoreAutomationRevision =>
|
(deps: { db: Knex }): StoreAutomationRevision =>
|
||||||
async (revision: InsertableAutomationRevision) => {
|
async (revision: InsertableAutomationRevision) => {
|
||||||
const id = revision.id || generateRevisionId()
|
const id = revision.id || generateRevisionId()
|
||||||
const rev = _.pick(revision, AutomationRevisions.withoutTablePrefix.cols)
|
const rev = pick(revision, AutomationRevisions.withoutTablePrefix.cols)
|
||||||
const [newRev] = await tables
|
const [newRev] = await tables
|
||||||
.automationRevisions(deps.db)
|
.automationRevisions(deps.db)
|
||||||
.insert({
|
.insert({
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { AutomateAuthCodeHandshakeError } from '@/modules/automate/errors/manage
|
|||||||
import { EventBus } from '@/modules/shared/services/eventBus'
|
import { EventBus } from '@/modules/shared/services/eventBus'
|
||||||
import cryptoRandomString from 'crypto-random-string'
|
import cryptoRandomString from 'crypto-random-string'
|
||||||
import Redis from 'ioredis'
|
import Redis from 'ioredis'
|
||||||
import { get, has, isObjectLike } from 'lodash'
|
import { get, has, isObjectLike } from 'lodash-es'
|
||||||
import { Logger } from 'pino'
|
import { Logger } from 'pino'
|
||||||
import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events'
|
import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events'
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import {
|
|||||||
AutomationRunStatuses,
|
AutomationRunStatuses,
|
||||||
VersionCreationTriggerType
|
VersionCreationTriggerType
|
||||||
} from '@/modules/automate/helpers/types'
|
} from '@/modules/automate/helpers/types'
|
||||||
import { keyBy, uniq } from 'lodash'
|
import { keyBy, uniq } from 'lodash-es'
|
||||||
import { resolveStatusFromFunctionRunStatuses } from '@/modules/automate/services/runsManagement'
|
import { resolveStatusFromFunctionRunStatuses } from '@/modules/automate/services/runsManagement'
|
||||||
import { TriggeredAutomationsStatusGraphQLReturn } from '@/modules/automate/helpers/graphTypes'
|
import { TriggeredAutomationsStatusGraphQLReturn } from '@/modules/automate/helpers/graphTypes'
|
||||||
import { FunctionInputDecryptor } from '@/modules/automate/services/encryption'
|
import { FunctionInputDecryptor } from '@/modules/automate/services/encryption'
|
||||||
@@ -57,7 +57,7 @@ import { GetBranchesByIds } from '@/modules/core/domain/branches/operations'
|
|||||||
import { ValidateStreamAccess } from '@/modules/core/domain/streams/operations'
|
import { ValidateStreamAccess } from '@/modules/core/domain/streams/operations'
|
||||||
import { EventBusEmit } from '@/modules/shared/services/eventBus'
|
import { EventBusEmit } from '@/modules/shared/services/eventBus'
|
||||||
import { AutomationEvents } from '@/modules/automate/domain/events'
|
import { AutomationEvents } from '@/modules/automate/domain/events'
|
||||||
import { UnformattableTriggerDefinitionSchemaError } from '@speckle/shared/dist/commonjs/automate/index.js'
|
import { UnformattableTriggerDefinitionSchemaError } from '@speckle/shared/automate'
|
||||||
|
|
||||||
export type CreateAutomationDeps = {
|
export type CreateAutomationDeps = {
|
||||||
createAuthCode: CreateStoredAuthCode
|
createAuthCode: CreateStoredAuthCode
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { getEncryptionKeysPath } from '@/modules/shared/helpers/envHelper'
|
|||||||
import { packageRoot } from '@/bootstrap'
|
import { packageRoot } from '@/bootstrap'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import fs from 'node:fs/promises'
|
import fs from 'node:fs/promises'
|
||||||
import { has, isArray, isObjectLike } from 'lodash'
|
import { has, isArray, isObjectLike } from 'lodash-es'
|
||||||
import { Nullable, Optional } from '@speckle/shared'
|
import { Nullable, Optional } from '@speckle/shared'
|
||||||
import { MisconfiguredEnvironmentError } from '@/modules/shared/errors'
|
import { MisconfiguredEnvironmentError } from '@/modules/shared/errors'
|
||||||
import { AutomationFunctionInputEncryptionError } from '@/modules/automate/errors/management'
|
import { AutomationFunctionInputEncryptionError } from '@/modules/automate/errors/management'
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ import { getFunctionsMarketplaceUrl } from '@/modules/core/helpers/routeHelper'
|
|||||||
import type { Logger } from '@/observability/logging'
|
import type { Logger } from '@/observability/logging'
|
||||||
import { CreateStoredAuthCode } from '@/modules/automate/domain/operations'
|
import { CreateStoredAuthCode } from '@/modules/automate/domain/operations'
|
||||||
import { GetUser } from '@/modules/core/domain/users/operations'
|
import { GetUser } from '@/modules/core/domain/users/operations'
|
||||||
import { noop } from 'lodash'
|
import { noop } from 'lodash-es'
|
||||||
import { UnknownFunctionTemplateError } from '@/modules/automate/errors/functions'
|
import { UnknownFunctionTemplateError } from '@/modules/automate/errors/functions'
|
||||||
import { UserInputError } from '@/modules/core/errors/userinput'
|
import { UserInputError } from '@/modules/core/errors/userinput'
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import {
|
|||||||
import { BasicTestStream, createTestStreams } from '@/test/speckle-helpers/streamHelper'
|
import { BasicTestStream, createTestStreams } from '@/test/speckle-helpers/streamHelper'
|
||||||
import { Automate, Roles } from '@speckle/shared'
|
import { Automate, Roles } from '@speckle/shared'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { times } from 'lodash'
|
import { times } from 'lodash-es'
|
||||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||||
import { db } from '@/db/knex'
|
import { db } from '@/db/knex'
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ const backgroundJobsModule: SpeckleModule = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = backgroundJobsModule
|
export default backgroundJobsModule
|
||||||
|
|||||||
@@ -19,22 +19,26 @@ import {
|
|||||||
GetSignedUrl
|
GetSignedUrl
|
||||||
} from '@/modules/blobstorage/domain/operations'
|
} from '@/modules/blobstorage/domain/operations'
|
||||||
|
|
||||||
export type ObjectStorage = {
|
|
||||||
client: S3Client
|
|
||||||
bucket: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GetProjectObjectStorage = (args: {
|
export type GetProjectObjectStorage = (args: {
|
||||||
projectId: string
|
projectId: string
|
||||||
}) => Promise<ObjectStorage>
|
}) => Promise<ObjectStorage>
|
||||||
|
|
||||||
export type GetObjectStorageParams = {
|
export type GetObjectStorageParams = {
|
||||||
credentials: S3ClientConfig['credentials']
|
credentials: {
|
||||||
endpoint: S3ClientConfig['endpoint']
|
accessKeyId: string
|
||||||
region: S3ClientConfig['region']
|
secretAccessKey: string
|
||||||
|
}
|
||||||
|
endpoint: string
|
||||||
|
region: string
|
||||||
bucket: string
|
bucket: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ObjectStorage = {
|
||||||
|
client: S3Client
|
||||||
|
bucket: string
|
||||||
|
params: GetObjectStorageParams
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get object storage client
|
* Get object storage client
|
||||||
*/
|
*/
|
||||||
@@ -48,7 +52,7 @@ export const getObjectStorage = (params: GetObjectStorageParams): ObjectStorage
|
|||||||
forcePathStyle: true
|
forcePathStyle: true
|
||||||
}
|
}
|
||||||
const client = new S3Client(config)
|
const client = new S3Client(config)
|
||||||
return { client, bucket }
|
return { client, bucket, params }
|
||||||
}
|
}
|
||||||
|
|
||||||
let mainObjectStorage: Optional<ObjectStorage> = undefined
|
let mainObjectStorage: Optional<ObjectStorage> = undefined
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ const streamBlobResolvers = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = {
|
export default {
|
||||||
ServerInfo: {
|
ServerInfo: {
|
||||||
//deprecated
|
//deprecated
|
||||||
blobSizeLimitBytes() {
|
blobSizeLimitBytes() {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const TABLE_NAME = 'blob_storage'
|
|||||||
* @param { import("knex").Knex } knex
|
* @param { import("knex").Knex } knex
|
||||||
* @returns { Promise<void> }
|
* @returns { Promise<void> }
|
||||||
*/
|
*/
|
||||||
exports.up = async (knex) => {
|
const up = async (knex) => {
|
||||||
await knex.schema.createTable(TABLE_NAME, (table) => {
|
await knex.schema.createTable(TABLE_NAME, (table) => {
|
||||||
table.string('id', 10)
|
table.string('id', 10)
|
||||||
// dont cascade on delete, cause it doesn't clean the object storage for the objs
|
// dont cascade on delete, cause it doesn't clean the object storage for the objs
|
||||||
@@ -26,6 +26,8 @@ exports.up = async (knex) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.down = async (knex) => {
|
const down = async (knex) => {
|
||||||
await knex.schema.dropTableIfExists(TABLE_NAME)
|
await knex.schema.dropTableIfExists(TABLE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { up, down }
|
||||||
|
|||||||
+4
-2
@@ -7,14 +7,16 @@ const HASH_COLUMN_NAME = 'fileHash'
|
|||||||
* @param { import("knex").Knex } knex
|
* @param { import("knex").Knex } knex
|
||||||
* @returns { Promise<void> }
|
* @returns { Promise<void> }
|
||||||
*/
|
*/
|
||||||
exports.up = async (knex) => {
|
const up = async (knex) => {
|
||||||
await knex.schema.table(TABLE_NAME, (table) => {
|
await knex.schema.table(TABLE_NAME, (table) => {
|
||||||
table.string(HASH_COLUMN_NAME)
|
table.string(HASH_COLUMN_NAME)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.down = async (knex) => {
|
const down = async (knex) => {
|
||||||
await knex.schema.alterTable(TABLE_NAME, (table) => {
|
await knex.schema.alterTable(TABLE_NAME, (table) => {
|
||||||
table.dropColumn(HASH_COLUMN_NAME)
|
table.dropColumn(HASH_COLUMN_NAME)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { up, down }
|
||||||
|
|||||||
+4
-2
@@ -2,7 +2,7 @@
|
|||||||
* @param { import("knex").Knex } knex
|
* @param { import("knex").Knex } knex
|
||||||
* @returns { Promise<void> }
|
* @returns { Promise<void> }
|
||||||
*/
|
*/
|
||||||
exports.up = async (knex) => {
|
const up = async (knex) => {
|
||||||
await knex.raw(
|
await knex.raw(
|
||||||
'ALTER TABLE "blob_storage" ALTER COLUMN "id" SET DATA TYPE varchar(255);'
|
'ALTER TABLE "blob_storage" ALTER COLUMN "id" SET DATA TYPE varchar(255);'
|
||||||
)
|
)
|
||||||
@@ -12,8 +12,10 @@ exports.up = async (knex) => {
|
|||||||
* @param { import("knex").Knex } knex
|
* @param { import("knex").Knex } knex
|
||||||
* @returns { Promise<void> }
|
* @returns { Promise<void> }
|
||||||
*/
|
*/
|
||||||
exports.down = async (knex) => {
|
const down = async (knex) => {
|
||||||
await knex.raw(
|
await knex.raw(
|
||||||
'ALTER TABLE "blob_storage" ALTER COLUMN "id" SET DATA TYPE varchar(10);'
|
'ALTER TABLE "blob_storage" ALTER COLUMN "id" SET DATA TYPE varchar(10);'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { up, down }
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
import { Upload } from '@aws-sdk/lib-storage'
|
import { Upload } from '@aws-sdk/lib-storage'
|
||||||
import type { Command } from '@aws-sdk/smithy-client'
|
import type { Command } from '@aws-sdk/smithy-client'
|
||||||
import { ensureError } from '@speckle/shared'
|
import { ensureError } from '@speckle/shared'
|
||||||
import { get } from 'lodash'
|
import { get } from 'lodash-es'
|
||||||
import type stream from 'stream'
|
import type stream from 'stream'
|
||||||
|
|
||||||
const sendCommand = async <CommandOutput extends ServiceOutputTypes>(
|
const sendCommand = async <CommandOutput extends ServiceOutputTypes>(
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
streamReadPermissionsPipelineFactory
|
streamReadPermissionsPipelineFactory
|
||||||
} from '@/modules/shared/authz'
|
} from '@/modules/shared/authz'
|
||||||
import { authMiddlewareCreator } from '@/modules/shared/middleware'
|
import { authMiddlewareCreator } from '@/modules/shared/middleware'
|
||||||
import { isArray } from 'lodash'
|
import { isArray } from 'lodash-es'
|
||||||
import { UnauthorizedError } from '@/modules/shared/errors'
|
import { UnauthorizedError } from '@/modules/shared/errors'
|
||||||
import {
|
import {
|
||||||
getAllStreamBlobIdsFactory,
|
getAllStreamBlobIdsFactory,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
AlreadyRegisteredBlobError,
|
AlreadyRegisteredBlobError,
|
||||||
StoredBlobAccessError
|
StoredBlobAccessError
|
||||||
} from '@/modules/blobstorage/errors'
|
} from '@/modules/blobstorage/errors'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash-es'
|
||||||
import { MisconfiguredEnvironmentError } from '@/modules/shared/errors'
|
import { MisconfiguredEnvironmentError } from '@/modules/shared/errors'
|
||||||
// import { acceptedFileExtensions } from '@speckle/shared'
|
// import { acceptedFileExtensions } from '@speckle/shared'
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { getProjectObjectStorage } from '@/modules/multiregion/utils/blobStorage
|
|||||||
import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector'
|
import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector'
|
||||||
import type { Logger } from '@/observability/logging'
|
import type { Logger } from '@/observability/logging'
|
||||||
import type { Readable, Writable } from 'stream'
|
import type { Readable, Writable } from 'stream'
|
||||||
import { get } from 'lodash'
|
import { get } from 'lodash-es'
|
||||||
import type { UploadResult, ProcessingResult } from '@/modules/blobstorage/domain/types'
|
import type { UploadResult, ProcessingResult } from '@/modules/blobstorage/domain/types'
|
||||||
import type { Busboy } from 'busboy'
|
import type { Busboy } from 'busboy'
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import { BasicTestUser } from '@/test/authHelper'
|
|||||||
import cryptoRandomString from 'crypto-random-string'
|
import cryptoRandomString from 'crypto-random-string'
|
||||||
import type { BlobStorageItem } from '@/modules/blobstorage/domain/types'
|
import type { BlobStorageItem } from '@/modules/blobstorage/domain/types'
|
||||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
const getServerInfo = getServerInfoFactory({ db })
|
const getServerInfo = getServerInfoFactory({ db })
|
||||||
|
|
||||||
@@ -134,8 +135,8 @@ describe('Blobs integration @blobstorage', () => {
|
|||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.post(`/api/stream/${streamId}/blob`)
|
.post(`/api/stream/${streamId}/blob`)
|
||||||
.set('Authorization', `Bearer ${token}`)
|
.set('Authorization', `Bearer ${token}`)
|
||||||
.attach('blob1', require.resolve('@/readme.md'))
|
.attach('blob1', fileURLToPath(import.meta.resolve('@/readme.md')))
|
||||||
.attach('blob2', require.resolve('@/package.json'))
|
.attach('blob2', fileURLToPath(import.meta.resolve('@/package.json')))
|
||||||
expect(response.status).to.equal(201)
|
expect(response.status).to.equal(201)
|
||||||
expect(response.body.uploadResults).to.exist
|
expect(response.body.uploadResults).to.exist
|
||||||
const uploadResults = response.body.uploadResults
|
const uploadResults = response.body.uploadResults
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
import crs from 'crypto-random-string'
|
import crs from 'crypto-random-string'
|
||||||
import { range } from 'lodash'
|
import { range } from 'lodash-es'
|
||||||
import { knex } from '@/db/knex'
|
import { knex } from '@/db/knex'
|
||||||
|
|
||||||
const BlobStorage = () => knex('blob_storage')
|
const BlobStorage = () => knex('blob_storage')
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { beforeEachContext } from '@/test/hooks'
|
import { beforeEachContext } from '@/test/hooks'
|
||||||
import { NotFoundError, BadRequestError } from '@/modules/shared/errors'
|
import { NotFoundError, BadRequestError } from '@/modules/shared/errors'
|
||||||
import { range } from 'lodash'
|
import { range } from 'lodash-es'
|
||||||
import { fakeIdGenerator, createBlobs } from '@/modules/blobstorage/tests/helpers'
|
import { fakeIdGenerator, createBlobs } from '@/modules/blobstorage/tests/helpers'
|
||||||
import {
|
import {
|
||||||
uploadFileStreamFactory,
|
uploadFileStreamFactory,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { Knex } from 'knex'
|
|||||||
import cryptoRandomString from 'crypto-random-string'
|
import cryptoRandomString from 'crypto-random-string'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { testLogger } from '@/observability/logging'
|
import { testLogger } from '@/observability/logging'
|
||||||
import { put } from 'axios'
|
import axios from 'axios'
|
||||||
import { expectToThrow } from '@/test/assertionHelper'
|
import { expectToThrow } from '@/test/assertionHelper'
|
||||||
import {
|
import {
|
||||||
AlreadyRegisteredBlobError,
|
AlreadyRegisteredBlobError,
|
||||||
@@ -148,7 +148,7 @@ const { FF_LARGE_FILE_IMPORTS_ENABLED } = getFeatureFlags()
|
|||||||
|
|
||||||
const fileSize = 100
|
const fileSize = 100
|
||||||
|
|
||||||
const response = await put(url, cryptoRandomString({ length: fileSize }))
|
const response = await axios.put(url, cryptoRandomString({ length: fileSize }))
|
||||||
expect(
|
expect(
|
||||||
response.status,
|
response.status,
|
||||||
JSON.stringify({ statusText: response.statusText, body: response.data })
|
JSON.stringify({ statusText: response.statusText, body: response.data })
|
||||||
@@ -205,7 +205,7 @@ const { FF_LARGE_FILE_IMPORTS_ENABLED } = getFeatureFlags()
|
|||||||
urlExpiryDurationSeconds: expiryDuration
|
urlExpiryDurationSeconds: expiryDuration
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await put(url, 'test content') // more than 1 byte long
|
const response = await axios.put(url, 'test content') // more than 1 byte long
|
||||||
expect(
|
expect(
|
||||||
response.status,
|
response.status,
|
||||||
JSON.stringify({ statusText: response.statusText, body: response.data })
|
JSON.stringify({ statusText: response.statusText, body: response.data })
|
||||||
@@ -245,7 +245,7 @@ const { FF_LARGE_FILE_IMPORTS_ENABLED } = getFeatureFlags()
|
|||||||
urlExpiryDurationSeconds: expiryDuration
|
urlExpiryDurationSeconds: expiryDuration
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await put(url, 'test content')
|
const response = await axios.put(url, 'test content')
|
||||||
expect(
|
expect(
|
||||||
response.status,
|
response.status,
|
||||||
JSON.stringify({ statusText: response.statusText, body: response.data })
|
JSON.stringify({ statusText: response.statusText, body: response.data })
|
||||||
@@ -291,7 +291,7 @@ const { FF_LARGE_FILE_IMPORTS_ENABLED } = getFeatureFlags()
|
|||||||
urlExpiryDurationSeconds: expiryDuration
|
urlExpiryDurationSeconds: expiryDuration
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await put(url, cryptoRandomString({ length: 100 })) // more than 1 byte long
|
const response = await axios.put(url, cryptoRandomString({ length: 100 })) // more than 1 byte long
|
||||||
expect(
|
expect(
|
||||||
response.status,
|
response.status,
|
||||||
JSON.stringify({ statusText: response.statusText, body: response.data })
|
JSON.stringify({ statusText: response.statusText, body: response.data })
|
||||||
@@ -337,7 +337,7 @@ const { FF_LARGE_FILE_IMPORTS_ENABLED } = getFeatureFlags()
|
|||||||
|
|
||||||
const fileSize = 100
|
const fileSize = 100
|
||||||
|
|
||||||
const response = await put(url, cryptoRandomString({ length: fileSize }))
|
const response = await axios.put(url, cryptoRandomString({ length: fileSize }))
|
||||||
expect(
|
expect(
|
||||||
response.status,
|
response.status,
|
||||||
JSON.stringify({ statusText: response.statusText, body: response.data })
|
JSON.stringify({ statusText: response.statusText, body: response.data })
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { noop } from 'lodash'
|
import { noop } from 'lodash-es'
|
||||||
import { CommandModule } from 'yargs'
|
import { CommandModule } from 'yargs'
|
||||||
|
|
||||||
const command: CommandModule = {
|
const command: CommandModule = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { noop } from 'lodash'
|
import { noop } from 'lodash-es'
|
||||||
import { CommandModule } from 'yargs'
|
import { CommandModule } from 'yargs'
|
||||||
|
|
||||||
const command: CommandModule = {
|
const command: CommandModule = {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
NOTIFICATIONS_QUEUE,
|
NOTIFICATIONS_QUEUE,
|
||||||
buildNotificationsQueue
|
buildNotificationsQueue
|
||||||
} from '@/modules/notifications/services/queue'
|
} from '@/modules/notifications/services/queue'
|
||||||
import { noop } from 'lodash'
|
import { noop } from 'lodash-es'
|
||||||
import { cliLogger } from '@/observability/logging'
|
import { cliLogger } from '@/observability/logging'
|
||||||
|
|
||||||
const PORT = 3032
|
const PORT = 3032
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { cliLogger } from '@/observability/logging'
|
|||||||
import { NotificationType } from '@/modules/notifications/helpers/types'
|
import { NotificationType } from '@/modules/notifications/helpers/types'
|
||||||
import { initializeConsumption } from '@/modules/notifications/index'
|
import { initializeConsumption } from '@/modules/notifications/index'
|
||||||
import { EnvironmentResourceError } from '@/modules/shared/errors'
|
import { EnvironmentResourceError } from '@/modules/shared/errors'
|
||||||
import { get, noop } from 'lodash'
|
import { get, noop } from 'lodash-es'
|
||||||
import { CommandModule } from 'yargs'
|
import { CommandModule } from 'yargs'
|
||||||
|
|
||||||
const command: CommandModule = {
|
const command: CommandModule = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { noop } from 'lodash'
|
import { noop } from 'lodash-es'
|
||||||
import { CommandModule } from 'yargs'
|
import { CommandModule } from 'yargs'
|
||||||
|
|
||||||
const command: CommandModule = {
|
const command: CommandModule = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { noop } from 'lodash'
|
import { noop } from 'lodash-es'
|
||||||
import { CommandModule } from 'yargs'
|
import { CommandModule } from 'yargs'
|
||||||
|
|
||||||
const command: CommandModule = {
|
const command: CommandModule = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { noop } from 'lodash'
|
import { noop } from 'lodash-es'
|
||||||
import { CommandModule } from 'yargs'
|
import { CommandModule } from 'yargs'
|
||||||
|
|
||||||
const command: CommandModule = {
|
const command: CommandModule = {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { getUserFactory } from '@/modules/core/repositories/users'
|
|||||||
import { ForbiddenError } from '@/modules/shared/errors'
|
import { ForbiddenError } from '@/modules/shared/errors'
|
||||||
import { BasicTestCommit, createTestCommits } from '@/test/speckle-helpers/commitHelper'
|
import { BasicTestCommit, createTestCommits } from '@/test/speckle-helpers/commitHelper'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { times } from 'lodash'
|
import { times } from 'lodash-es'
|
||||||
import { CommandModule } from 'yargs'
|
import { CommandModule } from 'yargs'
|
||||||
import { ProjectRecordVisibility } from '@/modules/core/helpers/types'
|
import { ProjectRecordVisibility } from '@/modules/core/helpers/types'
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { cliLogger as logger } from '@/observability/logging'
|
|||||||
import { Users, ServerAcl } from '@/modules/core/dbSchema'
|
import { Users, ServerAcl } from '@/modules/core/dbSchema'
|
||||||
import { Roles } from '@/modules/core/helpers/mainConstants'
|
import { Roles } from '@/modules/core/helpers/mainConstants'
|
||||||
import { faker } from '@faker-js/faker'
|
import { faker } from '@faker-js/faker'
|
||||||
import { range } from 'lodash'
|
import { range } from 'lodash-es'
|
||||||
import { UniqueEnforcer } from 'enforce-unique'
|
import { UniqueEnforcer } from 'enforce-unique'
|
||||||
import { CommandModule } from 'yargs'
|
import { CommandModule } from 'yargs'
|
||||||
import { UserRecord } from '@/modules/core/helpers/types'
|
import { UserRecord } from '@/modules/core/helpers/types'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { noop } from 'lodash'
|
import { noop } from 'lodash-es'
|
||||||
import { CommandModule } from 'yargs'
|
import { CommandModule } from 'yargs'
|
||||||
|
|
||||||
const command: CommandModule = {
|
const command: CommandModule = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { noop } from 'lodash'
|
import { noop } from 'lodash-es'
|
||||||
import { CommandModule } from 'yargs'
|
import { CommandModule } from 'yargs'
|
||||||
|
|
||||||
const command: CommandModule = {
|
const command: CommandModule = {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const command: CommandModule<unknown, { file: string }> = {
|
|||||||
},
|
},
|
||||||
handler: async ({ file }) => {
|
handler: async ({ file }) => {
|
||||||
logger.info('Loading GQL schema...')
|
logger.info('Loading GQL schema...')
|
||||||
const schema = ModulesSetup.graphSchema()
|
const schema = await ModulesSetup.graphSchema()
|
||||||
const schemaString = printSchema(schema)
|
const schemaString = printSchema(schema)
|
||||||
|
|
||||||
logger.info(`Saving to "${file}"...`)
|
logger.info(`Saving to "${file}"...`)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { noop } from 'lodash'
|
import { noop } from 'lodash-es'
|
||||||
import { CommandModule } from 'yargs'
|
import { CommandModule } from 'yargs'
|
||||||
|
|
||||||
const command: CommandModule = {
|
const command: CommandModule = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { noop } from 'lodash'
|
import { noop } from 'lodash-es'
|
||||||
import { CommandModule } from 'yargs'
|
import { CommandModule } from 'yargs'
|
||||||
|
|
||||||
const command: CommandModule = {
|
const command: CommandModule = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { noop } from 'lodash'
|
import { noop } from 'lodash-es'
|
||||||
import { CommandModule } from 'yargs'
|
import { CommandModule } from 'yargs'
|
||||||
|
|
||||||
const command: CommandModule = {
|
const command: CommandModule = {
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
/* eslint-disable no-restricted-imports */
|
/* eslint-disable no-restricted-imports */
|
||||||
|
import '../../bootstrap.js'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import yargs from 'yargs'
|
import yargs from 'yargs'
|
||||||
import '../../bootstrap'
|
import { hideBin } from 'yargs/helpers'
|
||||||
import { cliLogger as logger } from '@/observability/logging'
|
import { cliLogger as logger } from '@/observability/logging'
|
||||||
import { isTestEnv } from '@/modules/shared/helpers/envHelper'
|
import { isTestEnv } from '@/modules/shared/helpers/envHelper'
|
||||||
import { mochaHooks } from '@/test/hooks'
|
import { beforeEntireTestRun } from '@/test/hooks'
|
||||||
|
import { getModuleDirectory } from '@speckle/shared/environment/node'
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
const execution = yargs
|
await yargs(hideBin(process.argv))
|
||||||
.scriptName('yarn cli')
|
.scriptName('yarn cli')
|
||||||
.usage('$0 <cmd> [args]')
|
.usage('$0 <cmd> [args]')
|
||||||
.commandDir(path.resolve(__dirname, './commands'), { extensions: ['js', 'ts'] })
|
.commandDir(path.resolve(getModuleDirectory(import.meta), './commands'), {
|
||||||
|
extensions: ['js', 'ts']
|
||||||
|
})
|
||||||
.option('beforeAll', {
|
.option('beforeAll', {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
@@ -24,7 +28,7 @@ const main = async () => {
|
|||||||
// In test env, run beforeAll hooks to properly initialize everything first
|
// In test env, run beforeAll hooks to properly initialize everything first
|
||||||
if (isBeforeAllSet && isTestEnv()) {
|
if (isBeforeAllSet && isTestEnv()) {
|
||||||
logger.info('Running test beforeAll hooks...')
|
logger.info('Running test beforeAll hooks...')
|
||||||
await (mochaHooks.beforeAll as () => Promise<void>)()
|
await beforeEntireTestRun()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.fail((msg, err, yargs) => {
|
.fail((msg, err, yargs) => {
|
||||||
@@ -40,12 +44,10 @@ const main = async () => {
|
|||||||
|
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
})
|
})
|
||||||
.help().argv
|
.help()
|
||||||
|
.parseAsync()
|
||||||
|
|
||||||
return execution
|
process.exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
void main().then(() => {
|
await main()
|
||||||
// weird TS typing issue
|
|
||||||
yargs.exit(0, undefined as unknown as Error)
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { defineRequestDataloaders } from '@/modules/shared/helpers/graphqlHelper'
|
import { defineRequestDataloaders } from '@/modules/shared/helpers/graphqlHelper'
|
||||||
import { keyBy } from 'lodash'
|
import { keyBy } from 'lodash-es'
|
||||||
import { Nullable } from '@/modules/shared/helpers/typeHelper'
|
import { Nullable } from '@/modules/shared/helpers/typeHelper'
|
||||||
import { ResourceIdentifier } from '@/modules/core/graph/generated/graphql'
|
import { ResourceIdentifier } from '@/modules/core/graph/generated/graphql'
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import {
|
|||||||
ensureCommentSchema,
|
ensureCommentSchema,
|
||||||
validateInputAttachmentsFactory
|
validateInputAttachmentsFactory
|
||||||
} from '@/modules/comments/services/commentTextService'
|
} from '@/modules/comments/services/commentTextService'
|
||||||
import { has } from 'lodash'
|
import { has } from 'lodash-es'
|
||||||
import {
|
import {
|
||||||
documentToBasicString,
|
documentToBasicString,
|
||||||
SmartTextEditorValueSchema
|
SmartTextEditorValueSchema
|
||||||
@@ -143,7 +143,7 @@ const getAuthorizedStreamCommentFactory =
|
|||||||
return comment
|
return comment
|
||||||
}
|
}
|
||||||
|
|
||||||
export = {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
async comment(_parent, args, context) {
|
async comment(_parent, args, context) {
|
||||||
const projectId = args.streamId
|
const projectId = args.streamId
|
||||||
|
|||||||
@@ -60,4 +60,4 @@ const commentsModule: SpeckleModule = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = commentsModule
|
export default commentsModule
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// /* istanbul ignore file */
|
// /* istanbul ignore file */
|
||||||
exports.up = async (knex) => {
|
const up = async (knex) => {
|
||||||
await knex.schema.createTable('comments', (table) => {
|
await knex.schema.createTable('comments', (table) => {
|
||||||
table.string('id', 10).primary()
|
table.string('id', 10).primary()
|
||||||
table
|
table
|
||||||
@@ -53,8 +53,10 @@ exports.up = async (knex) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.down = async (knex) => {
|
const down = async (knex) => {
|
||||||
await knex.schema.dropTableIfExists('comment_views')
|
await knex.schema.dropTableIfExists('comment_views')
|
||||||
await knex.schema.dropTableIfExists('comment_links')
|
await knex.schema.dropTableIfExists('comment_links')
|
||||||
await knex.schema.dropTableIfExists('comments')
|
await knex.schema.dropTableIfExists('comments')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { up, down }
|
||||||
|
|||||||
+5
-3
@@ -1,4 +1,4 @@
|
|||||||
const { Users } = require('@/modules/core/dbSchema')
|
import { Users } from '@/modules/core/dbSchema'
|
||||||
|
|
||||||
const COMMENTS_TABLE = 'comments'
|
const COMMENTS_TABLE = 'comments'
|
||||||
const COMMENT_VIEWS_TABLE = 'comment_views'
|
const COMMENT_VIEWS_TABLE = 'comment_views'
|
||||||
@@ -7,7 +7,7 @@ const COMMENT_VIEWS_TABLE = 'comment_views'
|
|||||||
* @param { import("knex").Knex } knex
|
* @param { import("knex").Knex } knex
|
||||||
* @returns { Promise<void> }
|
* @returns { Promise<void> }
|
||||||
*/
|
*/
|
||||||
exports.up = async function (knex) {
|
async function up(knex) {
|
||||||
// Delete all orphaned comments, which can be there even though there was a FK there before for some reason
|
// Delete all orphaned comments, which can be there even though there was a FK there before for some reason
|
||||||
await knex
|
await knex
|
||||||
.table(COMMENTS_TABLE)
|
.table(COMMENTS_TABLE)
|
||||||
@@ -41,7 +41,7 @@ exports.up = async function (knex) {
|
|||||||
* @param { import("knex").Knex } knex
|
* @param { import("knex").Knex } knex
|
||||||
* @returns { Promise<void> }
|
* @returns { Promise<void> }
|
||||||
*/
|
*/
|
||||||
exports.down = async function (knex) {
|
async function down(knex) {
|
||||||
await knex.schema.alterTable(COMMENTS_TABLE, (table) => {
|
await knex.schema.alterTable(COMMENTS_TABLE, (table) => {
|
||||||
table.dropForeign('authorId')
|
table.dropForeign('authorId')
|
||||||
table.foreign('authorId').references(Users.col.id).onDelete('NO ACTION')
|
table.foreign('authorId').references(Users.col.id).onDelete('NO ACTION')
|
||||||
@@ -58,3 +58,5 @@ exports.down = async function (knex) {
|
|||||||
table.foreign('userId').references(Users.col.id).onDelete('NO ACTION')
|
table.foreign('userId').references(Users.col.id).onDelete('NO ACTION')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { up, down }
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
ResourceType
|
ResourceType
|
||||||
} from '@/modules/core/graph/generated/graphql'
|
} from '@/modules/core/graph/generated/graphql'
|
||||||
import { Optional } from '@/modules/shared/helpers/typeHelper'
|
import { Optional } from '@/modules/shared/helpers/typeHelper'
|
||||||
import { clamp, keyBy, reduce } from 'lodash'
|
import { clamp, keyBy, reduce } from 'lodash-es'
|
||||||
import crs from 'crypto-random-string'
|
import crs from 'crypto-random-string'
|
||||||
import { executeBatchedSelect } from '@/modules/shared/helpers/dbHelper'
|
import { executeBatchedSelect } from '@/modules/shared/helpers/dbHelper'
|
||||||
import { Knex } from 'knex'
|
import { Knex } from 'knex'
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
isDocEmpty,
|
isDocEmpty,
|
||||||
documentToBasicString
|
documentToBasicString
|
||||||
} from '@/modules/core/services/richTextEditorService'
|
} from '@/modules/core/services/richTextEditorService'
|
||||||
import { isString, uniq } from 'lodash'
|
import { isString, uniq } from 'lodash-es'
|
||||||
import { InvalidAttachmentsError } from '@/modules/comments/errors'
|
import { InvalidAttachmentsError } from '@/modules/comments/errors'
|
||||||
import { JSONContent } from '@tiptap/core'
|
import { JSONContent } from '@tiptap/core'
|
||||||
import { ValidateInputAttachments } from '@/modules/comments/domain/operations'
|
import { ValidateInputAttachments } from '@/modules/comments/domain/operations'
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
import { LegacyCommentViewerData } from '@/modules/core/graph/generated/graphql'
|
import { LegacyCommentViewerData } from '@/modules/core/graph/generated/graphql'
|
||||||
import { viewerResourcesToString } from '@/modules/core/services/commit/viewerResources'
|
import { viewerResourcesToString } from '@/modules/core/services/commit/viewerResources'
|
||||||
import { Nullable, SpeckleViewer } from '@speckle/shared'
|
import { Nullable, SpeckleViewer } from '@speckle/shared'
|
||||||
import { has, get, intersection, isObjectLike } from 'lodash'
|
import { has, get, intersection, isObjectLike } from 'lodash-es'
|
||||||
|
|
||||||
type SerializedViewerState = SpeckleViewer.ViewerState.SerializedViewerState
|
type SerializedViewerState = SpeckleViewer.ViewerState.SerializedViewerState
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,15 @@ export const createCommentFactory =
|
|||||||
emitEvent: EventBusEmit
|
emitEvent: EventBusEmit
|
||||||
getViewerResourcesFromLegacyIdentifiers: GetViewerResourcesFromLegacyIdentifiers
|
getViewerResourcesFromLegacyIdentifiers: GetViewerResourcesFromLegacyIdentifiers
|
||||||
}) =>
|
}) =>
|
||||||
async ({ userId, input }: { userId: string; input: CommentCreateInput }) => {
|
async (
|
||||||
|
{ userId, input }: { userId: string; input: CommentCreateInput },
|
||||||
|
options?: Partial<{
|
||||||
|
/**
|
||||||
|
* Used in tests to skip text validation & formatting - text is saved in DB as is
|
||||||
|
*/
|
||||||
|
skipTextValidation: boolean
|
||||||
|
}>
|
||||||
|
) => {
|
||||||
if (input.resources.length < 1)
|
if (input.resources.length < 1)
|
||||||
throw new UserInputError(
|
throw new UserInputError(
|
||||||
'Must specify at least one resource as the comment target'
|
'Must specify at least one resource as the comment target'
|
||||||
@@ -91,10 +99,12 @@ export const createCommentFactory =
|
|||||||
}
|
}
|
||||||
|
|
||||||
await deps.validateInputAttachments(input.streamId, input.blobIds)
|
await deps.validateInputAttachments(input.streamId, input.blobIds)
|
||||||
comment.text = buildCommentTextFromInput({
|
comment.text = options?.skipTextValidation
|
||||||
doc: input.text,
|
? (input.text as SmartTextEditorValueSchema)
|
||||||
blobIds: input.blobIds
|
: buildCommentTextFromInput({
|
||||||
})
|
doc: input.text,
|
||||||
|
blobIds: input.blobIds
|
||||||
|
})
|
||||||
|
|
||||||
const id = crs({ length: 10 })
|
const id = crs({ length: 10 })
|
||||||
const [newComment] = await deps.insertComments([
|
const [newComment] = await deps.insertComments([
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { CommentRecord } from '@/modules/comments/helpers/types'
|
|||||||
import { ensureCommentSchema } from '@/modules/comments/services/commentTextService'
|
import { ensureCommentSchema } from '@/modules/comments/services/commentTextService'
|
||||||
import type { JSONContent } from '@tiptap/core'
|
import type { JSONContent } from '@tiptap/core'
|
||||||
import { iterateContentNodes } from '@/modules/core/services/richTextEditorService'
|
import { iterateContentNodes } from '@/modules/core/services/richTextEditorService'
|
||||||
import { difference, flatten } from 'lodash'
|
import { difference, flatten } from 'lodash-es'
|
||||||
import {
|
import {
|
||||||
NotificationPublisher,
|
NotificationPublisher,
|
||||||
NotificationType
|
NotificationType
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Optional } from '@speckle/shared'
|
import { Optional } from '@speckle/shared'
|
||||||
import { isUndefined } from 'lodash'
|
import { isUndefined } from 'lodash-es'
|
||||||
import {
|
import {
|
||||||
GetPaginatedBranchCommentsFactory,
|
GetPaginatedBranchCommentsFactory,
|
||||||
GetPaginatedBranchCommentsPage,
|
GetPaginatedBranchCommentsPage,
|
||||||
|
|||||||
@@ -11,13 +11,16 @@ import {
|
|||||||
editCommentFactory,
|
editCommentFactory,
|
||||||
archiveCommentFactory
|
archiveCommentFactory
|
||||||
} from '@/modules/comments/services/index'
|
} from '@/modules/comments/services/index'
|
||||||
import { convertBasicStringToDocument } from '@/modules/core/services/richTextEditorService'
|
import {
|
||||||
|
convertBasicStringToDocument,
|
||||||
|
SmartTextEditorValueSchema
|
||||||
|
} from '@/modules/core/services/richTextEditorService'
|
||||||
import {
|
import {
|
||||||
ensureCommentSchema,
|
ensureCommentSchema,
|
||||||
buildCommentTextFromInput,
|
buildCommentTextFromInput,
|
||||||
validateInputAttachmentsFactory
|
validateInputAttachmentsFactory
|
||||||
} from '@/modules/comments/services/commentTextService'
|
} from '@/modules/comments/services/commentTextService'
|
||||||
import { get, range } from 'lodash'
|
import { get, range } from 'lodash-es'
|
||||||
import { buildApolloServer } from '@/app'
|
import { buildApolloServer } from '@/app'
|
||||||
import { AllScopes } from '@/modules/core/helpers/mainConstants'
|
import { AllScopes } from '@/modules/core/helpers/mainConstants'
|
||||||
import { createAuthTokenForUser } from '@/test/authHelper'
|
import { createAuthTokenForUser } from '@/test/authHelper'
|
||||||
@@ -30,11 +33,6 @@ import {
|
|||||||
purgeNotifications
|
purgeNotifications
|
||||||
} from '@/test/notificationsHelper'
|
} from '@/test/notificationsHelper'
|
||||||
import { NotificationType } from '@/modules/notifications/helpers/types'
|
import { NotificationType } from '@/modules/notifications/helpers/types'
|
||||||
import {
|
|
||||||
EmailSendingServiceMock,
|
|
||||||
CommentsRepositoryMock,
|
|
||||||
StreamsRepositoryMock
|
|
||||||
} from '@/test/mocks/global'
|
|
||||||
import { createAuthedTestContext, ServerAndContext } from '@/test/graphqlHelper'
|
import { createAuthedTestContext, ServerAndContext } from '@/test/graphqlHelper'
|
||||||
import {
|
import {
|
||||||
checkStreamResourceAccessFactory,
|
checkStreamResourceAccessFactory,
|
||||||
@@ -128,7 +126,6 @@ import {
|
|||||||
LegacyCommentViewerData,
|
LegacyCommentViewerData,
|
||||||
ReplyCreateInput
|
ReplyCreateInput
|
||||||
} from '@/modules/core/graph/generated/graphql'
|
} from '@/modules/core/graph/generated/graphql'
|
||||||
import { CommentRecord } from '@/modules/comments/helpers/types'
|
|
||||||
import { MaybeNullOrUndefined, TIME_MS } from '@speckle/shared'
|
import { MaybeNullOrUndefined, TIME_MS } from '@speckle/shared'
|
||||||
import { CommentEvents } from '@/modules/comments/domain/events'
|
import { CommentEvents } from '@/modules/comments/domain/events'
|
||||||
import {
|
import {
|
||||||
@@ -136,7 +133,6 @@ import {
|
|||||||
getViewerResourcesForCommentsFactory,
|
getViewerResourcesForCommentsFactory,
|
||||||
getViewerResourcesFromLegacyIdentifiersFactory
|
getViewerResourcesFromLegacyIdentifiersFactory
|
||||||
} from '@/modules/core/services/commit/viewerResources'
|
} from '@/modules/core/services/commit/viewerResources'
|
||||||
import { StreamRecord } from '@/modules/core/helpers/types'
|
|
||||||
import {
|
import {
|
||||||
processFinalizedProjectInviteFactory,
|
processFinalizedProjectInviteFactory,
|
||||||
validateProjectInviteBeforeFinalizationFactory
|
validateProjectInviteBeforeFinalizationFactory
|
||||||
@@ -146,11 +142,9 @@ import {
|
|||||||
validateStreamAccessFactory
|
validateStreamAccessFactory
|
||||||
} from '@/modules/core/services/streams/access'
|
} from '@/modules/core/services/streams/access'
|
||||||
import { authorizeResolver } from '@/modules/shared'
|
import { authorizeResolver } from '@/modules/shared'
|
||||||
|
import { createEmailListener, TestEmailListener } from '@/test/speckle-helpers/email'
|
||||||
type LegacyCommentRecord = CommentRecord & {
|
import { buildTestProject } from '@/modules/core/tests/helpers/creation'
|
||||||
total_count: string
|
import { GetCommentsQueryVariables } from '@/test/graphql/generated/graphql'
|
||||||
resources: Array<{ resourceId: string; resourceType: string }>
|
|
||||||
}
|
|
||||||
|
|
||||||
const getServerInfo = getServerInfoFactory({ db })
|
const getServerInfo = getServerInfoFactory({ db })
|
||||||
const getUser = getUserFactory({ db })
|
const getUser = getUserFactory({ db })
|
||||||
@@ -285,33 +279,34 @@ const buildFinalizeProjectInvite = () =>
|
|||||||
getServerInfo
|
getServerInfo
|
||||||
})
|
})
|
||||||
|
|
||||||
const createStream = legacyCreateStreamFactory({
|
const createStreamReturnRecord = createStreamReturnRecordFactory({
|
||||||
createStreamReturnRecord: createStreamReturnRecordFactory({
|
inviteUsersToProject: inviteUsersToProjectFactory({
|
||||||
inviteUsersToProject: inviteUsersToProjectFactory({
|
createAndSendInvite: createAndSendInviteFactory({
|
||||||
createAndSendInvite: createAndSendInviteFactory({
|
findUserByTarget: findUserByTargetFactory({ db }),
|
||||||
findUserByTarget: findUserByTargetFactory({ db }),
|
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
|
||||||
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
|
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
|
||||||
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
|
getStream
|
||||||
getStream
|
|
||||||
}),
|
|
||||||
buildInviteEmailContents: buildCoreInviteEmailContentsFactory({
|
|
||||||
getStream
|
|
||||||
}),
|
|
||||||
emitEvent: ({ eventName, payload }) =>
|
|
||||||
getEventBus().emit({
|
|
||||||
eventName,
|
|
||||||
payload
|
|
||||||
}),
|
|
||||||
getUser,
|
|
||||||
getServerInfo,
|
|
||||||
finalizeInvite: buildFinalizeProjectInvite()
|
|
||||||
}),
|
}),
|
||||||
getUsers
|
buildInviteEmailContents: buildCoreInviteEmailContentsFactory({
|
||||||
|
getStream
|
||||||
|
}),
|
||||||
|
emitEvent: ({ eventName, payload }) =>
|
||||||
|
getEventBus().emit({
|
||||||
|
eventName,
|
||||||
|
payload
|
||||||
|
}),
|
||||||
|
getUser,
|
||||||
|
getServerInfo,
|
||||||
|
finalizeInvite: buildFinalizeProjectInvite()
|
||||||
}),
|
}),
|
||||||
createStream: createStreamFactory({ db }),
|
getUsers
|
||||||
createBranch: createBranchFactory({ db }),
|
}),
|
||||||
emitEvent: getEventBus().emit
|
createStream: createStreamFactory({ db }),
|
||||||
})
|
createBranch: createBranchFactory({ db }),
|
||||||
|
emitEvent: getEventBus().emit
|
||||||
|
})
|
||||||
|
const createStream = legacyCreateStreamFactory({
|
||||||
|
createStreamReturnRecord
|
||||||
})
|
})
|
||||||
|
|
||||||
const findEmail = findEmailFactory({ db })
|
const findEmail = findEmailFactory({ db })
|
||||||
@@ -353,9 +348,8 @@ function generateRandomCommentText() {
|
|||||||
return buildCommentInputFromString(crs({ length: 10 }))
|
return buildCommentInputFromString(crs({ length: 10 }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const mailerMock = EmailSendingServiceMock
|
const buildTestStream = () =>
|
||||||
const commentRepoMock = CommentsRepositoryMock
|
buildTestProject({ workspaceId: undefined, regionKey: undefined })
|
||||||
const streamsRepoMock = StreamsRepositoryMock
|
|
||||||
|
|
||||||
describe('Comments @comments', () => {
|
describe('Comments @comments', () => {
|
||||||
let app: express.Express
|
let app: express.Express
|
||||||
@@ -434,13 +428,6 @@ describe('Comments @comments', () => {
|
|||||||
|
|
||||||
after(() => {
|
after(() => {
|
||||||
notificationsState.destroy()
|
notificationsState.destroy()
|
||||||
commentRepoMock.destroy()
|
|
||||||
streamsRepoMock.destroy()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
commentRepoMock.disable()
|
|
||||||
commentRepoMock.resetMockedFunctions()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should be able to create a comment and a reply', async () => {
|
it('Should be able to create a comment and a reply', async () => {
|
||||||
@@ -1498,7 +1485,7 @@ describe('Comments @comments', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const createComment = (input = {}) =>
|
const createCommentGql = (input = {}) =>
|
||||||
CommentsGraphQLClient.createComment(apollo, {
|
CommentsGraphQLClient.createComment(apollo, {
|
||||||
input: {
|
input: {
|
||||||
streamId: stream.id,
|
streamId: stream.id,
|
||||||
@@ -1509,7 +1496,7 @@ describe('Comments @comments', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const createReply = (input?: ReplyCreateInput) =>
|
const createReplyGql = (input?: ReplyCreateInput) =>
|
||||||
CommentsGraphQLClient.createReply(apollo, {
|
CommentsGraphQLClient.createReply(apollo, {
|
||||||
input: {
|
input: {
|
||||||
streamId: stream.id,
|
streamId: stream.id,
|
||||||
@@ -1528,7 +1515,7 @@ describe('Comments @comments', () => {
|
|||||||
await truncateTables([Comments.name])
|
await truncateTables([Comments.name])
|
||||||
|
|
||||||
// Create a single comment with a blob
|
// Create a single comment with a blob
|
||||||
const createCommentResult = await createComment({
|
const createCommentResult = await createCommentGql({
|
||||||
text: generateRandomCommentText(),
|
text: generateRandomCommentText(),
|
||||||
blobIds: [blob1.blobId]
|
blobIds: [blob1.blobId]
|
||||||
})
|
})
|
||||||
@@ -1536,7 +1523,7 @@ describe('Comments @comments', () => {
|
|||||||
if (!parentCommentId) throw new Error('Comment creation failed!')
|
if (!parentCommentId) throw new Error('Comment creation failed!')
|
||||||
|
|
||||||
// Create a reply with a blob
|
// Create a reply with a blob
|
||||||
await createReply({
|
await createReplyGql({
|
||||||
text: generateRandomCommentText(),
|
text: generateRandomCommentText(),
|
||||||
blobIds: [blob1.blobId],
|
blobIds: [blob1.blobId],
|
||||||
parentComment: parentCommentId,
|
parentComment: parentCommentId,
|
||||||
@@ -1544,7 +1531,7 @@ describe('Comments @comments', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Create a reply with a blob, but no text
|
// Create a reply with a blob, but no text
|
||||||
const emptyCommentResult = await createReply({
|
const emptyCommentResult = await createReplyGql({
|
||||||
blobIds: [blob1.blobId],
|
blobIds: [blob1.blobId],
|
||||||
parentComment: parentCommentId,
|
parentComment: parentCommentId,
|
||||||
streamId: stream.id
|
streamId: stream.id
|
||||||
@@ -1559,7 +1546,7 @@ describe('Comments @comments', () => {
|
|||||||
...(input || { id: '' })
|
...(input || { id: '' })
|
||||||
})
|
})
|
||||||
|
|
||||||
const readComments = (input = {}) =>
|
const readComments = (input: Partial<GetCommentsQueryVariables> = {}) =>
|
||||||
CommentsGraphQLClient.getComments(apollo, {
|
CommentsGraphQLClient.getComments(apollo, {
|
||||||
cursor: null,
|
cursor: null,
|
||||||
streamId: stream.id,
|
streamId: stream.id,
|
||||||
@@ -1567,61 +1554,99 @@ describe('Comments @comments', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('both legacy (string) comments and new (ProseMirror) documents are formatted as SmartTextEditorValue values', async () => {
|
it('both legacy (string) comments and new (ProseMirror) documents are formatted as SmartTextEditorValue values', async () => {
|
||||||
commentRepoMock.enable()
|
const streamId = await createStream({ ...buildTestStream(), ownerId: user.id })
|
||||||
commentRepoMock.mockFunction('getCommentsLegacyFactory', () => {
|
|
||||||
return async () => ({
|
await Promise.all([
|
||||||
items: [
|
// Legacy
|
||||||
// Legacy
|
createComment(
|
||||||
{
|
{
|
||||||
id: 'a',
|
userId: user.id,
|
||||||
text: 'hey dude! welcome to my legacy-type comment!',
|
input: {
|
||||||
streamId: stream.id
|
streamId,
|
||||||
},
|
resources: [
|
||||||
// New
|
{ resourceId: streamId, resourceType: ResourceType.Stream }
|
||||||
{
|
],
|
||||||
id: 'b',
|
text: 'hey dude! welcome to my legacy-type comment!' as unknown as SmartTextEditorValueSchema,
|
||||||
|
data: {},
|
||||||
|
blobIds: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ skipTextValidation: true }
|
||||||
|
),
|
||||||
|
// New
|
||||||
|
createComment(
|
||||||
|
{
|
||||||
|
userId: user.id,
|
||||||
|
input: {
|
||||||
|
streamId,
|
||||||
|
resources: [
|
||||||
|
{ resourceId: streamId, resourceType: ResourceType.Stream }
|
||||||
|
],
|
||||||
text: JSON.stringify(
|
text: JSON.stringify(
|
||||||
buildCommentTextFromInput({
|
buildCommentTextFromInput({
|
||||||
doc: buildCommentInputFromString('new comment schema here')
|
doc: buildCommentInputFromString('new comment schema here')
|
||||||
})
|
})
|
||||||
),
|
) as unknown as SmartTextEditorValueSchema,
|
||||||
streamId: stream.id
|
data: {},
|
||||||
},
|
blobIds: []
|
||||||
// New, but for some reason the text object is already deserialized
|
}
|
||||||
{
|
},
|
||||||
id: 'c',
|
{ skipTextValidation: true }
|
||||||
|
),
|
||||||
|
// New, but for some reason the text object is already deserialized
|
||||||
|
createComment(
|
||||||
|
{
|
||||||
|
userId: user.id,
|
||||||
|
input: {
|
||||||
|
streamId,
|
||||||
|
resources: [
|
||||||
|
{ resourceId: streamId, resourceType: ResourceType.Stream }
|
||||||
|
],
|
||||||
text: buildCommentTextFromInput({
|
text: buildCommentTextFromInput({
|
||||||
doc: buildCommentInputFromString('another new comment schema here')
|
doc: buildCommentInputFromString('another new comment schema here')
|
||||||
}),
|
}),
|
||||||
streamId: stream.id
|
data: {},
|
||||||
|
blobIds: []
|
||||||
}
|
}
|
||||||
] as unknown as Array<LegacyCommentRecord>,
|
},
|
||||||
cursor: new Date().toISOString(),
|
{ skipTextValidation: true }
|
||||||
totalCount: 3
|
)
|
||||||
})
|
])
|
||||||
})
|
|
||||||
|
|
||||||
const { data, errors } = await readComments()
|
const { data, errors } = await readComments({
|
||||||
|
streamId
|
||||||
|
})
|
||||||
|
|
||||||
expect(errors?.length || 0).to.eq(0)
|
expect(errors?.length || 0).to.eq(0)
|
||||||
expect(data?.comments?.items?.length || 0).to.eq(3)
|
expect(data?.comments?.items?.length || 0).to.eq(3)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('legacy comment with a single link is formatted correctly', async () => {
|
it('legacy comment with a single link is formatted correctly', async () => {
|
||||||
|
const streamId = await createStream({ ...buildTestStream(), ownerId: user.id })
|
||||||
|
|
||||||
|
// Low-level insert cause all we need are just the main DB entries
|
||||||
const item = {
|
const item = {
|
||||||
id: '1',
|
text: 'https://aaa.com:3000/h3ll0-world/_?a=1&b=2#aaa' as unknown as SmartTextEditorValueSchema,
|
||||||
text: 'https://aaa.com:3000/h3ll0-world/_?a=1&b=2#aaa',
|
streamId,
|
||||||
streamId: stream.id
|
authorId: user.id
|
||||||
} as unknown as LegacyCommentRecord
|
}
|
||||||
|
await createComment(
|
||||||
|
{
|
||||||
|
userId: user.id,
|
||||||
|
input: {
|
||||||
|
streamId,
|
||||||
|
resources: [{ resourceId: streamId, resourceType: ResourceType.Stream }],
|
||||||
|
text: item.text,
|
||||||
|
data: {},
|
||||||
|
blobIds: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ skipTextValidation: true }
|
||||||
|
)
|
||||||
|
|
||||||
commentRepoMock.enable()
|
const { data, errors } = await readComments({
|
||||||
commentRepoMock.mockFunction('getCommentsLegacyFactory', () => async () => ({
|
streamId
|
||||||
items: [item],
|
})
|
||||||
cursor: new Date().toISOString(),
|
|
||||||
totalCount: 1
|
|
||||||
}))
|
|
||||||
|
|
||||||
const { data, errors } = await readComments()
|
|
||||||
|
|
||||||
expect(data?.comments?.items?.length || 0).to.eq(1)
|
expect(data?.comments?.items?.length || 0).to.eq(1)
|
||||||
expect(errors?.length || 0).to.eq(0)
|
expect(errors?.length || 0).to.eq(0)
|
||||||
@@ -1637,6 +1662,8 @@ describe('Comments @comments', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('legacy comment with multiple links formats them correctly', async () => {
|
it('legacy comment with multiple links formats them correctly', async () => {
|
||||||
|
const streamId = await createStream({ ...buildTestStream(), ownerId: user.id })
|
||||||
|
|
||||||
const textParts = [
|
const textParts = [
|
||||||
"Here's one ",
|
"Here's one ",
|
||||||
// The period and comma def shouldn't belong to the following URL, but we have a pretty basic
|
// The period and comma def shouldn't belong to the following URL, but we have a pretty basic
|
||||||
@@ -1648,20 +1675,26 @@ describe('Comments @comments', () => {
|
|||||||
'http://agag.com:3000'
|
'http://agag.com:3000'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// Low-level insert cause all we need are just the main DB entries
|
||||||
const item = {
|
const item = {
|
||||||
id: '1',
|
text: textParts.join('') as unknown as SmartTextEditorValueSchema,
|
||||||
text: textParts.join(''),
|
streamId,
|
||||||
streamId: stream.id
|
authorId: user.id
|
||||||
} as unknown as LegacyCommentRecord
|
}
|
||||||
|
await createComment(
|
||||||
commentRepoMock.enable()
|
{
|
||||||
commentRepoMock.mockFunction('getCommentsLegacyFactory', () => async () => ({
|
userId: user.id,
|
||||||
items: [item],
|
input: {
|
||||||
cursor: new Date().toISOString(),
|
streamId,
|
||||||
totalCount: 1
|
resources: [{ resourceId: streamId, resourceType: ResourceType.Stream }],
|
||||||
}))
|
text: item.text,
|
||||||
|
data: {},
|
||||||
const { data, errors } = await readComments()
|
blobIds: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ skipTextValidation: true }
|
||||||
|
)
|
||||||
|
const { data, errors } = await readComments({ streamId })
|
||||||
|
|
||||||
const runExpectationsOnTextNode = (idx: number, shouldBeLink: boolean) => {
|
const runExpectationsOnTextNode = (idx: number, shouldBeLink: boolean) => {
|
||||||
expect(textNodes[idx].text).to.eq(textParts[idx])
|
expect(textNodes[idx].text).to.eq(textParts[idx])
|
||||||
@@ -1724,7 +1757,7 @@ describe('Comments @comments', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('returns raw text correctly', async () => {
|
it('returns raw text correctly', async () => {
|
||||||
const { data } = await createReply({
|
const { data } = await createReplyGql({
|
||||||
text: {
|
text: {
|
||||||
type: 'doc',
|
type: 'doc',
|
||||||
content: [
|
content: [
|
||||||
@@ -1761,43 +1794,6 @@ describe('Comments @comments', () => {
|
|||||||
expect(data?.comment?.text?.doc).to.be.null
|
expect(data?.comment?.text?.doc).to.be.null
|
||||||
expect(data?.comment?.text?.attachments?.length).to.be.greaterThan(0)
|
expect(data?.comment?.text?.attachments?.length).to.be.greaterThan(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
const unexpectedValDataset = [
|
|
||||||
{ display: 'number', value: 3 },
|
|
||||||
{ display: 'random object', value: { a: 1, b: 2 } }
|
|
||||||
]
|
|
||||||
unexpectedValDataset.forEach(({ display, value }) => {
|
|
||||||
it(`unexpected text value (${display}) in DB throw sanitized errors`, async () => {
|
|
||||||
streamsRepoMock.enable()
|
|
||||||
streamsRepoMock.mockFunction('getStreamsFactory', () => async () => [
|
|
||||||
{
|
|
||||||
id: stream.id,
|
|
||||||
workspaceId: ''
|
|
||||||
} as unknown as StreamRecord
|
|
||||||
])
|
|
||||||
const item = {
|
|
||||||
id: '1',
|
|
||||||
text: value,
|
|
||||||
streamId: stream.id,
|
|
||||||
createdAt: new Date()
|
|
||||||
} as unknown as LegacyCommentRecord
|
|
||||||
|
|
||||||
commentRepoMock.enable()
|
|
||||||
commentRepoMock.mockFunction('getCommentsLegacyFactory', () => async () => ({
|
|
||||||
items: [item],
|
|
||||||
cursor: new Date().toISOString(),
|
|
||||||
totalCount: 1
|
|
||||||
}))
|
|
||||||
|
|
||||||
const { errors } = await readComments()
|
|
||||||
|
|
||||||
expect((errors || []).map((e) => e.message).join(';')).to.contain(
|
|
||||||
'Unexpected comment schema format'
|
|
||||||
)
|
|
||||||
streamsRepoMock.disable()
|
|
||||||
streamsRepoMock.resetMockedFunctions()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const creatingOrReplyingDataSet = [
|
const creatingOrReplyingDataSet = [
|
||||||
@@ -1809,8 +1805,8 @@ describe('Comments @comments', () => {
|
|||||||
|
|
||||||
const createOrReplyComment = (input = {}) =>
|
const createOrReplyComment = (input = {}) =>
|
||||||
creating
|
creating
|
||||||
? createComment(input)
|
? createCommentGql(input)
|
||||||
: createReply({
|
: createReplyGql({
|
||||||
parentComment: parentCommentId,
|
parentComment: parentCommentId,
|
||||||
blobIds: [],
|
blobIds: [],
|
||||||
streamId: stream.id,
|
streamId: stream.id,
|
||||||
@@ -1825,7 +1821,7 @@ describe('Comments @comments', () => {
|
|||||||
before(async () => {
|
before(async () => {
|
||||||
if (replying) {
|
if (replying) {
|
||||||
// Create comment for attaching replies to
|
// Create comment for attaching replies to
|
||||||
const { data } = await createComment({
|
const { data } = await createCommentGql({
|
||||||
text: generateRandomCommentText()
|
text: generateRandomCommentText()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1920,6 +1916,20 @@ describe('Comments @comments', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('and mentioning a user', () => {
|
describe('and mentioning a user', () => {
|
||||||
|
let emailListener: TestEmailListener
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
emailListener = await createEmailListener()
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await emailListener.destroy()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
emailListener.reset()
|
||||||
|
})
|
||||||
|
|
||||||
const createOrReplyCommentWithMention = (targetUserId: string, input = {}) =>
|
const createOrReplyCommentWithMention = (targetUserId: string, input = {}) =>
|
||||||
createOrReplyComment({
|
createOrReplyComment({
|
||||||
text: {
|
text: {
|
||||||
@@ -1942,10 +1952,7 @@ describe('Comments @comments', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('a valid mention triggers a notification', async () => {
|
it('a valid mention triggers a notification', async () => {
|
||||||
const sendEmailInvocations = mailerMock.hijackFunction(
|
const { getSends } = emailListener.listen({ times: 2 })
|
||||||
'sendEmail',
|
|
||||||
async () => false
|
|
||||||
)
|
|
||||||
|
|
||||||
const waitForAck = notificationsState.waitForAck(
|
const waitForAck = notificationsState.waitForAck(
|
||||||
(e) => e.result?.type === NotificationType.MentionedInComment
|
(e) => e.result?.type === NotificationType.MentionedInComment
|
||||||
@@ -1960,7 +1967,8 @@ describe('Comments @comments', () => {
|
|||||||
// Wait for
|
// Wait for
|
||||||
await waitForAck
|
await waitForAck
|
||||||
|
|
||||||
const emailParams = sendEmailInvocations.args[0][0]
|
const emailSends = getSends()
|
||||||
|
const emailParams = emailSends[0]
|
||||||
expect(emailParams).to.be.ok
|
expect(emailParams).to.be.ok
|
||||||
expect(emailParams.subject).to.contain('mentioned in a Speckle comment')
|
expect(emailParams.subject).to.contain('mentioned in a Speckle comment')
|
||||||
expect(emailParams.to).to.eq(otherUser.email)
|
expect(emailParams.to).to.eq(otherUser.email)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Optional } from '@speckle/shared'
|
|||||||
import knex from '@/db/knex'
|
import knex from '@/db/knex'
|
||||||
import { BaseMetaRecord } from '@/modules/core/helpers/meta'
|
import { BaseMetaRecord } from '@/modules/core/helpers/meta'
|
||||||
import { Knex } from 'knex'
|
import { Knex } from 'knex'
|
||||||
import { reduce } from 'lodash'
|
import { reduce } from 'lodash-es'
|
||||||
|
|
||||||
type BaseInnerSchemaConfig<T extends string, C extends string> = {
|
type BaseInnerSchemaConfig<T extends string, C extends string> = {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
UserSubscriptions,
|
UserSubscriptions,
|
||||||
WorkspaceSubscriptions
|
WorkspaceSubscriptions
|
||||||
} from '@/modules/shared/utils/subscriptions'
|
} from '@/modules/shared/utils/subscriptions'
|
||||||
import { chunk, flatten } from 'lodash'
|
import { chunk, flatten } from 'lodash-es'
|
||||||
|
|
||||||
const reportModelCreatedFactory =
|
const reportModelCreatedFactory =
|
||||||
(deps: { publish: PublishSubscription }) =>
|
(deps: { publish: PublishSubscription }) =>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user