Files
speckle-server/packages/server/knexfile.ts
T
Gergő Jedlicska f210d9b749 gergo/web 2109 project region based db connection selector (#3434)
* feat(projects): add project regions, default to null

* feat(multiregion): add projectRegion Db client lookup logic

* feat(multiregion): add project region repositories and caching

* feat(multiRegion): db initialization and get project db client

* feat(docker-compose): add second db for regions testing

* feat(multiRegion): initialize region with pubs and subs working

* fix(multiRegion): get region client even if it was registered in another pod

* feat(workspaces): create workspace resolver split

* feat: update server region metadata

* feat(projects): rewrite project creation

* feat(multiRegion): getRegionDb

* fix(workspaces): get projects now can retur null

* feat(multiRegion): make local multi region DB-s work

* feat: set d efault workspace region

* CR changes

* tests

* feat(multiRegion): bind region properly

* fe update

* test fixes

* feat(multiRegion): automatically create aiven extras plugin

* ci(postgres): use published postgres with aiven extras

* fix(multiRegion): roll back the aiven extras migration, there is a better way

* tests fix

* fix(billing): we do not need to add a seat, if the workspace is on a plan, but has no sub

---------

Co-authored-by: Kristaps Fabians Geikins <fabis94@live.com>
2024-11-06 17:29:08 +01:00

136 lines
4.2 KiB
TypeScript

/* eslint-disable no-restricted-imports */
/* eslint-disable camelcase */
/* istanbul ignore file */
import { packageRoot } from './bootstrap'
import fs from 'fs'
import path from 'path'
import {
isTestEnv,
ignoreMissingMigrations,
postgresMaxConnections,
isDevOrTestEnv
} from '@/modules/shared/helpers/envHelper'
import { dbLogger as logger } from './logging/logging'
import { Knex } from 'knex'
function walk(dir: string) {
let results: string[] = []
const list = fs.readdirSync(dir)
list.forEach(function (file) {
const fullFile = path.join(dir, file)
const stat = fs.statSync(fullFile)
if (stat && stat.isDirectory()) {
if (file === 'migrations') results.push(fullFile)
else results = results.concat(walk(fullFile))
}
})
return results
}
// Always read migrations from /dist, otherwise we risk the same migration being applied twice
// once with the .ts extension and the 2nd time with the .js one
// The only exception is when running tests in the test DB, cause the stakes are way lower there and we always
// run them through ts-node anyway, so it doesn't make sense forcing the app to be built
const migrationModulesDir = path.resolve(
packageRoot,
isTestEnv() ? './modules' : './dist/modules'
)
const migrationDirsExist = fs.existsSync(migrationModulesDir)
if (!migrationDirsExist && !ignoreMissingMigrations()) {
throw new Error('App must be built into /dist, to enable work with migrations')
}
const migrationDirs = migrationDirsExist ? walk(migrationModulesDir) : []
// this is for readability, many users struggle to set the postgres connection uri
// in the env variables. This way its a bit easier to understand, also backward compatible.
const env = process.env
let connectionUri
if (env.POSTGRES_USER && env.POSTGRES_PASSWORD) {
connectionUri = `postgres://${encodeURIComponent(
env.POSTGRES_USER
)}:${encodeURIComponent(env.POSTGRES_PASSWORD)}@${
env.POSTGRES_URL
}/${encodeURIComponent(env.POSTGRES_DB as string)}`
} else {
connectionUri = env.POSTGRES_URL
}
// NOTE: fixes time pagination, breaks graphql DateTime parsing :/
// The pg driver (& knex?) parses dates for us and it breaks precision. This
// disables any date parsing and we guarantee values are returned as strings.
// const types = require('pg').types
// const TIMESTAMPTZ_OID = 1184
// const TIMESTAMP_OID = 1114
// types.setTypeParser(TIMESTAMPTZ_OID, (val) => val)
// types.setTypeParser(TIMESTAMP_OID, (val) => val)
// Another NOTE:
// this is why the new datetime columns are created like this
// table.specificType('createdAt', 'TIMESTAMPTZ(3)').defaultTo(knex.fn.now())
export const createKnexConfig = ({
connectionString,
caCertificate
}: {
connectionString?: string
caCertificate?: string | undefined
}): Knex.Config => {
return {
client: 'pg',
migrations: {
extension: 'ts',
loadExtensions: isTestEnv() ? ['.js', '.ts'] : ['.js'],
directory: migrationDirs
},
log: {
warn(message: unknown) {
logger.warn(message)
},
error(message: unknown) {
logger.error(message)
},
deprecate(message: unknown) {
logger.info(message)
},
debug(message: unknown) {
logger.debug(message)
}
},
connection: {
connectionString,
ssl: caCertificate ? { ca: caCertificate, rejectUnauthorized: true } : undefined,
application_name: 'speckle_server'
},
// we wish to avoid leaking sql queries in the logs: https://knexjs.org/guide/#compilesqlonerror
compileSqlOnError: isDevOrTestEnv(),
asyncStackTraces: isDevOrTestEnv(),
pool: {
min: 0,
max: postgresMaxConnections(),
acquireTimeoutMillis: 16000, //allows for 3x creation attempts plus idle time between attempts
createTimeoutMillis: 5000
}
}
}
const config: Record<string, Knex.Config> = {
test: {
...createKnexConfig({
connectionString: connectionUri || 'postgres://127.0.0.1/speckle2_test'
})
},
development: {
...createKnexConfig({
connectionString: connectionUri || 'postgres://127.0.0.1/speckle2_dev'
})
},
production: {
...createKnexConfig({
connectionString: connectionUri
})
}
}
export default config