Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
# [Optional] Uncomment if you want to install more global node modules
# RUN su node -c "npm install -g <your-package-list-here>"

RUN npm install -g nx
RUN npm install -g turbo
RUN apt-get update && apt-get install -y git

RUN apt-get update \
Expand Down
3 changes: 2 additions & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@
"intercom-client": "6.2.0",
"intl-messageformat": "10.5.14",
"ioredis": "5.4.1",
"is-base64": "1.1.0",
"isolated-vm": "5.0.1",
"js-yaml": "4.1.1",
"jsdom": "23.0.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/server/api/.env.tests
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ AP_REDIS_TYPE=MEMORY
AP_EXECUTION_MODE=UNSANDBOXED
AP_JWT_SECRET=secret
AP_STRIPE_SECRET_KEY=invalid-key
AP_FIREBASE_HASH_PARAMETERS={\"memCost\":14,\"rounds\":8,\"signerKey\":\"YE0dO4bwD4JnJafh6lZZfkp1MtKzuKAXQcDCJNJNyeCHairWHKENOkbh3dzwaCdizzOspwr/FITUVlnOAwPKyw==\",\"saltSeparator\":\"Bw==\"}
AP_FIREBASE_HASH_PARAMETERS='{"memCost":14,"rounds":8,"signerKey":"YE0dO4bwD4JnJafh6lZZfkp1MtKzuKAXQcDCJNJNyeCHairWHKENOkbh3dzwaCdizzOspwr/FITUVlnOAwPKyw==","saltSeparator":"Bw=="}'
AP_API_KEY="api-key"
AP_CLOUD_PLATFORM_ID="cloud-id"
AP_APPSUMO_TOKEN="app-sumo-token"
Expand Down
1 change: 0 additions & 1 deletion packages/server/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@
"@types/content-disposition": "0.5.9",
"@types/decompress": "4.2.4",
"@types/deep-equal": "1.0.1",
"@types/is-base64": "1.1.1",
"@types/jsonwebtoken": "9.0.10",
"@types/mime-types": "2.1.1",
"@types/mustache": "4.2.4",
Expand Down
15 changes: 8 additions & 7 deletions packages/server/api/src/app/core/db/repo-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type RepoGetter<T extends ObjectLiteral = ObjectLiteral> = (
entityManager?: EntityManager
) => Repository<T>

const instances = new Map<EntitySchema, RepoGetter>()
const instances = new Map<string, RepoGetter>()

/**
* Creates a {@link RepoGetter} for the given entity.
Expand All @@ -24,17 +24,18 @@ const instances = new Map<EntitySchema, RepoGetter>()
export const repoFactory = <T extends ObjectLiteral>(
entity: EntitySchema<T>,
): RepoGetter<T> => {
if (instances.has(entity)) {
return instances.get(entity) as RepoGetter<T>
const entityName = entity.options.name
if (instances.has(entityName)) {
return instances.get(entityName) as RepoGetter<T>
}

const newInstance: RepoGetter<T> = (entityManager?: EntityManager) => {
return (
entityManager?.getRepository(entity) ??
databaseConnection().getRepository(entity)
)
entityManager?.getRepository(entityName) ??
databaseConnection().getRepository(entityName)
) as Repository<T>
}

instances.set(entity, newInstance as RepoGetter)
instances.set(entityName, newInstance as RepoGetter)
return newInstance
}
23 changes: 19 additions & 4 deletions packages/server/api/src/app/database/database-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,15 @@ export const commonProperties = {
entities: getEntities(),
}

let _databaseConnection: DataSource | null = null
const DB_GLOBAL_KEY = '__AP_DB_CONNECTION__'

function getPersistedConnection(): DataSource | null {
return ((globalThis as Record<string, unknown>)[DB_GLOBAL_KEY] as DataSource) ?? null
}

function setPersistedConnection(ds: DataSource | null): void {
(globalThis as Record<string, unknown>)[DB_GLOBAL_KEY] = ds
}

const createDataSource = (): DataSource => {
switch (databaseType) {
Expand All @@ -130,8 +138,15 @@ const createDataSource = (): DataSource => {
}

export const databaseConnection = (): DataSource => {
if (isNil(_databaseConnection)) {
_databaseConnection = createDataSource()
const existing = getPersistedConnection()
if (!isNil(existing)) {
return existing
}
return _databaseConnection
const ds = createDataSource()
setPersistedConnection(ds)
return ds
}

export function resetDatabaseConnection(): void {
setPersistedConnection(null)
}
6 changes: 3 additions & 3 deletions packages/server/api/src/app/database/pglite-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ export const createPGliteDataSource = (): DataSource => {
},
},
}).driver,
migrationsRun: true,
migrationsRun: env !== ApEnvironment.TESTING,
migrationsTransactionMode: 'each',
migrations: getMigrations(),
synchronize: false,
migrations: env !== ApEnvironment.TESTING ? getMigrations() : [],
synchronize: env === ApEnvironment.TESTING,
...commonProperties,
})
}
Expand Down
6 changes: 4 additions & 2 deletions packages/server/api/src/app/helper/system-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ export const validateEnvPropsOnStartup = async (log: FastifyBaseLogger): Promise
}
}

await packageManager(log).validate()
await registryPieceManager(log).validate()
if (environment !== ApEnvironment.TESTING) {
await packageManager(log).validate()
await registryPieceManager(log).validate()
}
}
20 changes: 20 additions & 0 deletions packages/server/api/test/helpers/db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { databaseConnection } from '../../src/app/database/database-connection'

export const db = {
save<T extends Record<string, unknown>>(entity: string, data: T | T[]): Promise<T> {
const items = Array.isArray(data) ? data : [data]
return databaseConnection().getRepository(entity).save(items) as Promise<T>
},

update(entity: string, id: string, data: Record<string, unknown>): Promise<unknown> {
return databaseConnection().getRepository(entity).update(id, data)
},

findOneByOrFail<T>(entity: string, where: Record<string, unknown>): Promise<T> {
return databaseConnection().getRepository(entity).findOneByOrFail(where) as Promise<T>
},

findOneBy<T>(entity: string, where: Record<string, unknown>): Promise<T | null> {
return databaseConnection().getRepository(entity).findOneBy(where) as Promise<T | null>
},
}
20 changes: 20 additions & 0 deletions packages/server/api/test/helpers/describe-with-auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { FastifyInstance } from 'fastify'
import { createServiceContext, createTestContext, TestContext, TestContextParams } from './test-context'

export function describeWithAuth(
name: string,
getApp: () => FastifyInstance,
fn: (setup: () => Promise<TestContext>) => void,
params?: TestContextParams,
): void {
describe.each(['USER', 'SERVICE'] as const)(`${name} [%s]`, (authType) => {
const setup = async (): Promise<TestContext> => {
const userCtx = await createTestContext(getApp(), params)
if (authType === 'SERVICE') {
return createServiceContext(getApp(), userCtx)
}
return userCtx
}
fn(setup)
})
}
41 changes: 41 additions & 0 deletions packages/server/api/test/helpers/mocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {

CustomDomain,
CustomDomainStatus,
EventDestinationScope,
Field,
FieldType,
File,
Expand All @@ -33,6 +34,7 @@ import {
FlowTriggerType,
FlowVersion,
FlowVersionState,
Folder,
GitBranchType,
GitRepo,
InvitationStatus,
Expand Down Expand Up @@ -800,6 +802,45 @@ export const mockPieceMetadata = async (mockLog: FastifyBaseLogger): Promise<Pie
return mockPieceMetadata
}

export const createMockFolder = (folder?: Partial<Folder>): Folder => {
return {
id: folder?.id ?? apId(),
created: folder?.created ?? faker.date.recent().toISOString(),
updated: folder?.updated ?? faker.date.recent().toISOString(),
projectId: folder?.projectId ?? apId(),
displayName: folder?.displayName ?? faker.lorem.word(),
displayOrder: folder?.displayOrder ?? faker.number.int({ min: 0, max: 100 }),
}
}

export const createMockEventDestination = (eventDestination?: Partial<{
id: string
created: string
updated: string
platformId: string
events: ApplicationEventName[]
url: string
scope: EventDestinationScope
}>): {
id: string
created: string
updated: string
platformId: string
events: ApplicationEventName[]
url: string
scope: EventDestinationScope
} => {
return {
id: eventDestination?.id ?? apId(),
created: eventDestination?.created ?? faker.date.recent().toISOString(),
updated: eventDestination?.updated ?? faker.date.recent().toISOString(),
platformId: eventDestination?.platformId ?? apId(),
events: eventDestination?.events ?? [faker.helpers.enumValue(ApplicationEventName)],
url: eventDestination?.url ?? faker.internet.url(),
scope: eventDestination?.scope ?? EventDestinationScope.PLATFORM,
}
}

type CreateMockPlatformWithOwnerParams = {
platform?: Partial<Omit<Platform, 'ownerId'>>
owner?: Partial<Omit<User, 'platformId'>>
Expand Down
51 changes: 51 additions & 0 deletions packages/server/api/test/helpers/permission-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { DefaultProjectRole } from '@activepieces/shared'
import { FastifyInstance } from 'fastify'
import { StatusCodes } from 'http-status-codes'
import { createMemberContext, createTestContext, TestContext } from './test-context'

export function describeRolePermissions(config: RolePermissionConfig): void {
const {
app,
request,
allowedRoles,
forbiddenRoles,
beforeEach: beforeEachFn,
} = config

if (allowedRoles.length > 0) {
it.each(allowedRoles)('Succeeds if user role is %s', async (role) => {
const ctx = await createTestContext(app())
const memberCtx = await createMemberContext(app(), ctx, { projectRole: role })
if (beforeEachFn) {
await beforeEachFn(ctx)
}
const response = await request(memberCtx, ctx)
expect(response.statusCode).not.toBe(StatusCodes.FORBIDDEN)
})
}

if (forbiddenRoles.length > 0) {
it.each(forbiddenRoles)('Fails if user role is %s', async (role) => {
const ctx = await createTestContext(app())
const memberCtx = await createMemberContext(app(), ctx, { projectRole: role })
if (beforeEachFn) {
await beforeEachFn(ctx)
}
const response = await request(memberCtx, ctx)
expect(response.statusCode).toBe(StatusCodes.FORBIDDEN)

const responseBody = response.json()
expect(responseBody?.code).toBe('PERMISSION_DENIED')
expect(responseBody?.params?.userId).toBe(memberCtx.user.id)
expect(responseBody?.params?.projectId).toBe(ctx.project.id)
})
}
}

type RolePermissionConfig = {
app: () => FastifyInstance
request: (memberCtx: TestContext, ownerCtx: TestContext) => ReturnType<FastifyInstance['inject']>
allowedRoles: DefaultProjectRole[]
forbiddenRoles: DefaultProjectRole[]
beforeEach?: (ctx: TestContext) => Promise<void>
}
Loading
Loading