From 0e3465ba9466d431b7447bc5dc8d3b5795d8c9f0 Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:54:06 +0000 Subject: [PATCH] feat(multi-region): define the default region to create projects in --- .../modules/core/graph/resolvers/projects.ts | 12 +++++++--- .../core/services/streams/management.ts | 16 +++++++++++++ .../modules/multiregion/regionConfig.ts | 1 + .../multiregion/utils/regionSelector.ts | 18 ++++++++++++++ .../src/environment/multiRegionConfig.ts | 24 +++++++++++++++---- 5 files changed, 64 insertions(+), 7 deletions(-) diff --git a/packages/server/modules/core/graph/resolvers/projects.ts b/packages/server/modules/core/graph/resolvers/projects.ts index fc6bc83ae4..1b0232aebd 100644 --- a/packages/server/modules/core/graph/resolvers/projects.ts +++ b/packages/server/modules/core/graph/resolvers/projects.ts @@ -62,6 +62,7 @@ import { } from '@/modules/core/services/streams/access' import { cloneStreamFactory } from '@/modules/core/services/streams/clone' import { + createStreamInDefaultRegionFactory, createStreamReturnRecordFactory, deleteStreamAndNotifyFactory, updateStreamAndNotifyFactory, @@ -117,7 +118,10 @@ const createStreamReturnRecord = createStreamReturnRecordFactory({ }), getUsers }), - createStream: createStreamFactory({ db }), + createStream: await createStreamInDefaultRegionFactory({ + //FIXME this is a promise + createStreamFactory + }), createBranch: createBranchFactory({ db }), projectsEventsEmitter: ProjectsEmitter.emit }) @@ -158,7 +162,10 @@ const cloneStream = cloneStreamFactory({ getUser, newProjectDb: db, sourceProjectDb: db, - createStream: createStreamFactory({ db }), + createStream: await createStreamInDefaultRegionFactory({ + //FIXME this is a promise + createStreamFactory + }), insertCommits: insertCommitsFactory({ db }), getBatchedStreamCommits: getBatchedStreamCommitsFactory({ db }), insertStreamCommits: insertStreamCommitsFactory({ db }), @@ -277,7 +284,6 @@ export = { }) return await updateStreamAndNotify(update, userId!, resourceAccessRules) }, - // This one is only used outside of a workspace, so the project is always created in the main db async create(_parent, args, context) { const rateLimitResult = await getRateLimitResult('STREAM_CREATE', context.userId!) if (isRateLimitBreached(rateLimitResult)) { diff --git a/packages/server/modules/core/services/streams/management.ts b/packages/server/modules/core/services/streams/management.ts index 93ca7376a5..2ff55a58cc 100644 --- a/packages/server/modules/core/services/streams/management.ts +++ b/packages/server/modules/core/services/streams/management.ts @@ -50,6 +50,22 @@ import { AddStreamUpdatedActivity } from '@/modules/activitystream/domain/operations' import { LogicError } from '@/modules/shared/errors' +import { Knex } from 'knex' +import { getDb } from '@/modules/multiregion/utils/dbSelector' +import { isMultiRegionEnabled } from '@/modules/multiregion/helpers' +import { getDefaultProjectRegionKey } from '@/modules/multiregion/utils/regionSelector' + +export const createStreamInDefaultRegionFactory = async (deps: { + createStreamFactory: (deps: { db: Knex }) => StoreStream +}): Promise => { + let regionKey = null + if (isMultiRegionEnabled()) { + //FIXME is adding this check here the correct location? + regionKey = await getDefaultProjectRegionKey() + } + const db = await getDb({ regionKey }) //FIXME does this work if multi-region disabled? + return deps.createStreamFactory({ db }) +} export const createStreamReturnRecordFactory = (deps: { diff --git a/packages/server/modules/multiregion/regionConfig.ts b/packages/server/modules/multiregion/regionConfig.ts index f162c26e33..7fc2e3ca1a 100644 --- a/packages/server/modules/multiregion/regionConfig.ts +++ b/packages/server/modules/multiregion/regionConfig.ts @@ -20,6 +20,7 @@ const getMultiRegionConfig = async (): Promise => { // Only for non region enabled dev envs const emptyReturn = (): MultiRegionConfig => ({ main: { + isDefaultProjectStore: true, postgres: { connectionUri: '' }, blobStorage: { accessKey: '', diff --git a/packages/server/modules/multiregion/utils/regionSelector.ts b/packages/server/modules/multiregion/utils/regionSelector.ts index e12248c96d..031bdabfd2 100644 --- a/packages/server/modules/multiregion/utils/regionSelector.ts +++ b/packages/server/modules/multiregion/utils/regionSelector.ts @@ -72,3 +72,21 @@ export const getProjectRegionKey: GetProjectRegionKey = async ({ projectId }) => return await cachedProjectRegionKeyResolver({ projectId }) } + +export const getDefaultProjectRegionKey = async () => { + const registeredRegionConfigs = await getRegisteredRegionConfigs() + if (!registeredRegionConfigs) { + return null + } + const defaultProjectStoreRegion = Object.entries(registeredRegionConfigs) + .filter( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ([_, config]) => config.isDefaultProjectStore + ) + .pop() + if (!defaultProjectStoreRegion) { + return null + } + + return defaultProjectStoreRegion[0] +} diff --git a/packages/shared/src/environment/multiRegionConfig.ts b/packages/shared/src/environment/multiRegionConfig.ts index ccbf32d090..a6e1192d99 100644 --- a/packages/shared/src/environment/multiRegionConfig.ts +++ b/packages/shared/src/environment/multiRegionConfig.ts @@ -4,6 +4,13 @@ import { Knex, knex } from 'knex' import { Logger } from 'pino' const regionConfigSchema = z.object({ + isDefaultProjectStore: z + .boolean() + .describe( + 'Indicates that this is the default region in which projects should be created.' + ) + .optional() + .default(false), postgres: z.object({ connectionUri: z .string() @@ -39,10 +46,19 @@ const regionConfigSchema = z.object({ }) }) -const multiRegionConfigSchema = z.object({ - main: regionConfigSchema, - regions: z.record(z.string(), regionConfigSchema) -}) +const multiRegionConfigSchema = z + .object({ + main: regionConfigSchema, + regions: z.record(z.string(), regionConfigSchema) + }) + .refine( + (input) => + Object.values(input.regions).filter((region) => region.isDefaultProjectStore) + .length + + (input.main.isDefaultProjectStore ? 1 : 0) === + 1, + 'Only one region can be the default project store' + ) export type MultiRegionConfig = z.infer export type MainRegionConfig = MultiRegionConfig['main']