From 650ef9adc53729cc1823252b87166fb869937beb Mon Sep 17 00:00:00 2001 From: Artem Niehrieiev Date: Fri, 12 Sep 2025 11:41:33 +0000 Subject: [PATCH 1/5] feat: integrate PGLite support and update database configurations --- Dockerfile | 2 +- backend/.development.env | 5 ++++- backend/package.json | 2 ++ .../connection-properties.entity.ts | 6 ++--- .../entities/connection/connection.entity.ts | 12 +++++----- backend/src/entities/group/group.entity.ts | 2 +- .../action-event.entity.ts | 3 +-- .../table-field-info.entity.ts | 2 +- .../table-settings/table-settings.entity.ts | 10 ++++----- .../user-actions/user-action.entity.ts | 2 +- .../user-custom-repository-extension.ts | 2 +- backend/src/entities/user/user.entity.ts | 10 ++++----- backend/src/shared/config/config.service.ts | 22 +++++++++++++++++-- backend/src/types/pglite-contrib.d.ts | 3 +++ yarn.lock | 18 +++++++++++++++ 15 files changed, 72 insertions(+), 29 deletions(-) create mode 100644 backend/src/types/pglite-contrib.d.ts diff --git a/Dockerfile b/Dockerfile index 184ec9e44..f8c24fe31 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,5 +43,5 @@ RUN chown -R appuser:appuser /run USER appuser WORKDIR /app/backend -CMD [ "/app/backend/runner.sh" ] ENTRYPOINT ["/app/backend/entrypoint.sh"] +CMD [ "/app/backend/runner.sh" ] diff --git a/backend/.development.env b/backend/.development.env index d6236ece3..02a55b57d 100755 --- a/backend/.development.env +++ b/backend/.development.env @@ -2,7 +2,10 @@ ROCKETADMIN_COOKIE_DOMAIN= # connection parameters to your main database -DATABASE_URL=postgres://postgres:abc123@postgres:5432/postgres +# DATABASE_URL=postgres://postgres:abc123@postgres:5432/postgres + +# pglite file path, if you want to use it instead of main database +PGLITE_FOLDER_PATH=./data/ # optional parameter. you need it if you want to use sentry service. SENTRY_DSN= diff --git a/backend/package.json b/backend/package.json index 21cf8caa3..f4d736c63 100644 --- a/backend/package.json +++ b/backend/package.json @@ -34,6 +34,7 @@ "dependencies": { "@amplitude/node": "1.10.2", "@aws-sdk/lib-dynamodb": "^3.873.0", + "@electric-sql/pglite": "^0.3.8", "@faker-js/faker": "^10.0.0", "@nestjs/common": "11.1.6", "@nestjs/config": "4.0.2", @@ -94,6 +95,7 @@ "safe-regex2": "^5.0.0", "secure-json-parse": "4.0.0", "typeorm": "0.3.26", + "typeorm-pglite": "^0.3.2", "uuid": "^11.1.0", "validator": "^13.15.15", "winston": "3.17.0" diff --git a/backend/src/entities/connection-properties/connection-properties.entity.ts b/backend/src/entities/connection-properties/connection-properties.entity.ts index 085714472..152681302 100644 --- a/backend/src/entities/connection-properties/connection-properties.entity.ts +++ b/backend/src/entities/connection-properties/connection-properties.entity.ts @@ -24,13 +24,13 @@ export class ConnectionPropertiesEntity { @Column({ default: null }) company_name: string; - @Column({ default: true }) + @Column({ default: true, type: 'boolean' }) tables_audit: boolean; - @Column({ default: true }) + @Column({ default: true, type: 'boolean' }) human_readable_table_names: boolean; - @Column({ default: true }) + @Column({ default: true, type: 'boolean' }) allow_ai_requests: boolean; @Column({ default: null }) diff --git a/backend/src/entities/connection/connection.entity.ts b/backend/src/entities/connection/connection.entity.ts index 5f289f4b5..aab4d6de6 100644 --- a/backend/src/entities/connection/connection.entity.ts +++ b/backend/src/entities/connection/connection.entity.ts @@ -36,7 +36,7 @@ export class ConnectionEntity { @Column({ default: '' }) title?: string; - @Column({ default: false }) + @Column({ default: false, type: 'boolean' }) masterEncryption: boolean; @Column({ default: null }) @@ -69,7 +69,7 @@ export class ConnectionEntity { @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) updatedAt: Date; - @Column({ default: false }) + @Column({ default: false, type: 'boolean' }) ssh?: boolean; @Column({ default: null }) @@ -84,19 +84,19 @@ export class ConnectionEntity { @Column({ default: null }) sshUsername?: string | null; - @Column({ default: false }) + @Column({ default: false, type: 'boolean' }) ssl?: boolean | null; @Column({ default: null }) cert?: string | null; - @Column({ default: false }) + @Column({ default: false, type: 'boolean' }) isTestConnection: boolean; - @Column({ default: false }) + @Column({ default: false, type: 'boolean' }) azure_encryption: boolean; - @Column({ default: false }) + @Column({ default: false, type: 'boolean' }) is_frozen: boolean; @Column({ default: 0 }) diff --git a/backend/src/entities/group/group.entity.ts b/backend/src/entities/group/group.entity.ts index ab0bd911b..800e80c10 100644 --- a/backend/src/entities/group/group.entity.ts +++ b/backend/src/entities/group/group.entity.ts @@ -12,7 +12,7 @@ export class GroupEntity { @Column() title: string; - @Column({ default: false }) + @Column({ default: false, type: 'boolean' }) isMain: boolean; @ManyToMany((_) => PermissionEntity, (permission) => permission.groups, { diff --git a/backend/src/entities/table-actions/table-action-events-module/action-event.entity.ts b/backend/src/entities/table-actions/table-action-events-module/action-event.entity.ts index 51eb28d03..426e93d97 100644 --- a/backend/src/entities/table-actions/table-action-events-module/action-event.entity.ts +++ b/backend/src/entities/table-actions/table-action-events-module/action-event.entity.ts @@ -16,7 +16,6 @@ export class ActionEventsEntity { }) event!: TableActionEventEnum; - @Column('enum', { nullable: false, enum: TableActionTypeEnum, @@ -33,7 +32,7 @@ export class ActionEventsEntity { @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) created_at: Date; - @Column({ default: false }) + @Column({ default: false, type: 'boolean' }) require_confirmation: boolean; @ManyToOne((type) => ActionRulesEntity, (rules) => rules.table_actions, { onDelete: 'CASCADE' }) diff --git a/backend/src/entities/table-field-info/table-field-info.entity.ts b/backend/src/entities/table-field-info/table-field-info.entity.ts index a0155f9a5..6bba82ace 100644 --- a/backend/src/entities/table-field-info/table-field-info.entity.ts +++ b/backend/src/entities/table-field-info/table-field-info.entity.ts @@ -6,7 +6,7 @@ export class TableFieldInfoEntity { @PrimaryGeneratedColumn('uuid') id: string; - @Column({ default: null }) + @Column({ default: null, type: 'boolean' }) allow_null: boolean; @Column({ default: null }) diff --git a/backend/src/entities/table-settings/table-settings.entity.ts b/backend/src/entities/table-settings/table-settings.entity.ts index ec2b2d0c6..d0147a3ca 100644 --- a/backend/src/entities/table-settings/table-settings.entity.ts +++ b/backend/src/entities/table-settings/table-settings.entity.ts @@ -58,19 +58,19 @@ export class TableSettingsEntity { @Column('varchar', { array: true, default: null }) columns_view: string[]; - @Column({ default: true }) + @Column({ default: true, type: 'boolean' }) can_delete: boolean; - @Column({ default: true }) + @Column({ default: true, type: 'boolean' }) can_update: boolean; - @Column({ default: true }) + @Column({ default: true, type: 'boolean' }) can_add: boolean; - @Column({ default: true }) + @Column({ default: true, type: 'boolean' }) allow_csv_export: boolean; - @Column({ default: true }) + @Column({ default: true, type: 'boolean' }) allow_csv_import: boolean; @Column('varchar', { array: true, default: null }) diff --git a/backend/src/entities/user-actions/user-action.entity.ts b/backend/src/entities/user-actions/user-action.entity.ts index 37783a757..b7c1f401c 100644 --- a/backend/src/entities/user-actions/user-action.entity.ts +++ b/backend/src/entities/user-actions/user-action.entity.ts @@ -12,7 +12,7 @@ export class UserActionEntity { @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) createdAt: Date; - @Column({ default: false }) + @Column({ default: false, type: 'boolean' }) mail_sent: boolean; @OneToOne(() => UserEntity, (user) => user.user_action, { onDelete: 'CASCADE' }) diff --git a/backend/src/entities/user/repository/user-custom-repository-extension.ts b/backend/src/entities/user/repository/user-custom-repository-extension.ts index d36a37308..0432f48af 100644 --- a/backend/src/entities/user/repository/user-custom-repository-extension.ts +++ b/backend/src/entities/user/repository/user-custom-repository-extension.ts @@ -11,7 +11,7 @@ export const userCustomRepositoryExtension: IUserRepository = { const newUser: UserEntity = new UserEntity(); newUser.id = userData.id; newUser.isActive = true; - newUser.gclid = userData.gclidValue; + newUser.gclid = userData.gclidValue || null; return await this.save(newUser); }, diff --git a/backend/src/entities/user/user.entity.ts b/backend/src/entities/user/user.entity.ts index d19dc427e..29d426e39 100644 --- a/backend/src/entities/user/user.entity.ts +++ b/backend/src/entities/user/user.entity.ts @@ -42,10 +42,10 @@ export class UserEntity { @Column({ default: null }) name: string; - @Column({ default: false }) + @Column({ default: false, type: 'boolean' }) suspended: boolean; - @Column({ default: false }) + @Column({ default: false, type: 'boolean' }) isDemoAccount: boolean; @BeforeInsert() @@ -78,7 +78,7 @@ export class UserEntity { @Column({ default: null }) gclid: string; - @Column({ default: false }) + @Column({ default: false, type: 'boolean' }) isOTPEnabled: boolean; @Column({ default: null }) @@ -123,7 +123,7 @@ export class UserEntity { @OneToMany((_) => AiResponsesToUserEntity, (response) => response.user) ai_responses: Relation[]; - @Column({ default: false }) + @Column({ default: false, type: 'boolean' }) isActive: boolean; @Column('enum', { @@ -143,7 +143,7 @@ export class UserEntity { @Column({ default: null }) samlNameId: string; - @Column({ default: true }) + @Column({ default: true, type: 'boolean' }) showTestConnections: boolean; private emailToLowerCase() { diff --git a/backend/src/shared/config/config.service.ts b/backend/src/shared/config/config.service.ts index 13cfbc407..99b23330e 100644 --- a/backend/src/shared/config/config.service.ts +++ b/backend/src/shared/config/config.service.ts @@ -6,6 +6,8 @@ import parse from 'pg-connection-string'; import dotenv from 'dotenv'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); +import { uuid_ossp } from '@electric-sql/pglite/contrib/uuid_ossp'; +import { PGliteDriver } from 'typeorm-pglite'; dotenv.config(); @@ -40,7 +42,21 @@ class ConfigService { } public getTypeOrmConfig(): DataSourceOptions { - const connectionParams = this.parseTypeORMUrl(this.getValue('DATABASE_URL')); + + const pgLiteFolderPath = process.env.PGLITE_FOLDER_PATH; + + let pgLiteDriver = null; + let connectionParams = {}; + + if (pgLiteFolderPath && pgLiteFolderPath.length > 0) { + pgLiteDriver = new PGliteDriver({ + extensions: { uuid_ossp }, + dataDir: path.resolve(pgLiteFolderPath), + }).driver; + } else { + connectionParams = this.parseTypeORMUrl(this.getValue('DATABASE_URL')); + } + const newTypeOrmProdConfig: DataSourceOptions = { type: 'postgres', ...connectionParams, @@ -52,6 +68,7 @@ class ConfigService { max: 20, idle_in_transaction_session_timeout: 20 * 1000, }, + driver: pgLiteDriver ? pgLiteDriver : undefined, }; const newTypeOrmTestConfig: DataSourceOptions = { @@ -66,6 +83,7 @@ class ConfigService { max: 2, }, logger: 'advanced-console', + driver: pgLiteDriver ? pgLiteDriver : undefined, }; return this.isTestEnvironment() ? newTypeOrmTestConfig : newTypeOrmProdConfig; @@ -93,6 +111,6 @@ class ConfigService { } } -const configService = new ConfigService(process.env).ensureValues(['DATABASE_URL']); +const configService = new ConfigService(process.env).ensureValues([]); export { configService }; diff --git a/backend/src/types/pglite-contrib.d.ts b/backend/src/types/pglite-contrib.d.ts new file mode 100644 index 000000000..d7d3c9154 --- /dev/null +++ b/backend/src/types/pglite-contrib.d.ts @@ -0,0 +1,3 @@ +declare module '@electric-sql/pglite/contrib/uuid_ossp' { + export const uuid_ossp: any; +} diff --git a/yarn.lock b/yarn.lock index 7c46cda37..9b28ff89d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1362,6 +1362,13 @@ __metadata: languageName: node linkType: hard +"@electric-sql/pglite@npm:^0.3.8": + version: 0.3.8 + resolution: "@electric-sql/pglite@npm:0.3.8" + checksum: 0329f79f2b0fd0dcab48853a9db2d314f88e5e52569ec87c7ce8d30ab60c2b334aca8e90ac863ad3f54e92f7f12726f4bf79d80a02550448f83aeac2d1b5e590 + languageName: node + linkType: hard + "@emnapi/core@npm:^1.4.3": version: 1.4.3 resolution: "@emnapi/core@npm:1.4.3" @@ -5631,6 +5638,7 @@ __metadata: "@amplitude/node": 1.10.2 "@ava/typescript": 6.0.0 "@aws-sdk/lib-dynamodb": ^3.873.0 + "@electric-sql/pglite": ^0.3.8 "@faker-js/faker": ^10.0.0 "@nestjs/cli": ^11.0.10 "@nestjs/common": 11.1.6 @@ -5719,6 +5727,7 @@ __metadata: ts-node: ^10.9.2 tsconfig-paths: ^4.2.0 typeorm: 0.3.26 + typeorm-pglite: ^0.3.2 typescript: 5.9.2 uuid: ^11.1.0 validator: ^13.15.15 @@ -14022,6 +14031,15 @@ __metadata: languageName: node linkType: hard +"typeorm-pglite@npm:^0.3.2": + version: 0.3.2 + resolution: "typeorm-pglite@npm:0.3.2" + peerDependencies: + "@electric-sql/pglite": ">= 0.2.12" + checksum: 0b3a21f5ca166bc2af675e9d282340c0e2e259ad4715dbecdab8365d8b961df9b3b4f6af7604cf98f413707f9c79271f116b524a573117405c0903e543c9a512 + languageName: node + linkType: hard + "typeorm@npm:0.3.26": version: 0.3.26 resolution: "typeorm@npm:0.3.26" From 05d0a240cb856eb33c66c484b1eb68256ee7bae7 Mon Sep 17 00:00:00 2001 From: Artem Niehrieiev Date: Thu, 18 Sep 2025 11:30:20 +0000 Subject: [PATCH 2/5] Enhance Dockerfile and ConfigService for PGLite directory management - Added creation and permission setting for /app/backend/data in Dockerfile. - Implemented checks for PGLite directory existence and writability in ConfigService. --- Dockerfile | 2 ++ backend/src/shared/config/config.service.ts | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index c9c8915dd..b6877f89b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -50,6 +50,8 @@ RUN chown -R appuser:appuser /app RUN chown -R appuser:appuser /var/lib/nginx RUN chown -R appuser:appuser /var/log/nginx RUN chown -R appuser:appuser /run +RUN mkdir -p /app/backend/data && chown -R appuser:appuser /app/backend/data +RUN chmod 755 /app/backend/data USER appuser diff --git a/backend/src/shared/config/config.service.ts b/backend/src/shared/config/config.service.ts index 99b23330e..721adaa02 100644 --- a/backend/src/shared/config/config.service.ts +++ b/backend/src/shared/config/config.service.ts @@ -8,6 +8,7 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); import { uuid_ossp } from '@electric-sql/pglite/contrib/uuid_ossp'; import { PGliteDriver } from 'typeorm-pglite'; +import fs from 'fs'; dotenv.config(); @@ -42,16 +43,29 @@ class ConfigService { } public getTypeOrmConfig(): DataSourceOptions { - const pgLiteFolderPath = process.env.PGLITE_FOLDER_PATH; let pgLiteDriver = null; let connectionParams = {}; if (pgLiteFolderPath && pgLiteFolderPath.length > 0) { + const resolvedPath = path.resolve(pgLiteFolderPath); + try { + fs.accessSync(resolvedPath, fs.constants.F_OK); + console.log('PGLite directory exists'); + try { + fs.accessSync(resolvedPath, fs.constants.W_OK); + console.log('PGLite directory is writable'); + } catch (writeError) { + console.warn('PGLite directory exists but may not be writable:', writeError.message); + } + } catch (error) { + console.log('PGLite directory does not exist, will be created by PGLite', error); + } + pgLiteDriver = new PGliteDriver({ extensions: { uuid_ossp }, - dataDir: path.resolve(pgLiteFolderPath), + dataDir: path.resolve(resolvedPath), }).driver; } else { connectionParams = this.parseTypeORMUrl(this.getValue('DATABASE_URL')); From 78e97ddd9e3f663e985f40c61b867002cecba34b Mon Sep 17 00:00:00 2001 From: Artem Niehrieiev Date: Fri, 19 Sep 2025 12:58:36 +0000 Subject: [PATCH 3/5] fix: update PGLITE_FOLDER_PATH to absolute path and improve path resolution in ConfigService --- backend/.development.env | 2 +- backend/src/shared/config/config.service.ts | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/backend/.development.env b/backend/.development.env index 02a55b57d..7af5f2284 100755 --- a/backend/.development.env +++ b/backend/.development.env @@ -5,7 +5,7 @@ ROCKETADMIN_COOKIE_DOMAIN= # DATABASE_URL=postgres://postgres:abc123@postgres:5432/postgres # pglite file path, if you want to use it instead of main database -PGLITE_FOLDER_PATH=./data/ +PGLITE_FOLDER_PATH=/data/ # optional parameter. you need it if you want to use sentry service. SENTRY_DSN= diff --git a/backend/src/shared/config/config.service.ts b/backend/src/shared/config/config.service.ts index 721adaa02..fb7fafb80 100644 --- a/backend/src/shared/config/config.service.ts +++ b/backend/src/shared/config/config.service.ts @@ -9,6 +9,7 @@ const __dirname = path.dirname(__filename); import { uuid_ossp } from '@electric-sql/pglite/contrib/uuid_ossp'; import { PGliteDriver } from 'typeorm-pglite'; import fs from 'fs'; +import { isTest } from '../../helpers/app/is-test.js'; dotenv.config(); @@ -49,7 +50,11 @@ class ConfigService { let connectionParams = {}; if (pgLiteFolderPath && pgLiteFolderPath.length > 0) { - const resolvedPath = path.resolve(pgLiteFolderPath); + const fullPath = isTest() + ? path.join(process.cwd(), ...pgLiteFolderPath.split('/')) + : path.join(__dirname, '..', '..', '..', pgLiteFolderPath); + + const resolvedPath = path.resolve(fullPath); try { fs.accessSync(resolvedPath, fs.constants.F_OK); console.log('PGLite directory exists'); From 7f745a2acd3b2f1482439dbf0cf7c2153784954b Mon Sep 17 00:00:00 2001 From: Artem Niehrieiev Date: Fri, 19 Sep 2025 16:12:14 +0000 Subject: [PATCH 4/5] fix: reset PGLITE_FOLDER_PATH to empty for default configuration --- backend/.development.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/.development.env b/backend/.development.env index 7af5f2284..4426b8a24 100755 --- a/backend/.development.env +++ b/backend/.development.env @@ -5,7 +5,7 @@ ROCKETADMIN_COOKIE_DOMAIN= # DATABASE_URL=postgres://postgres:abc123@postgres:5432/postgres # pglite file path, if you want to use it instead of main database -PGLITE_FOLDER_PATH=/data/ +PGLITE_FOLDER_PATH= # optional parameter. you need it if you want to use sentry service. SENTRY_DSN= From b1d05999131517208634230ecb1bcb59925fb3a8 Mon Sep 17 00:00:00 2001 From: Artem Niehrieiev Date: Fri, 19 Sep 2025 18:18:45 +0000 Subject: [PATCH 5/5] fix: remove unnecessary creation and permission setting for /app/backend/data --- Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index b6877f89b..c9c8915dd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -50,8 +50,6 @@ RUN chown -R appuser:appuser /app RUN chown -R appuser:appuser /var/lib/nginx RUN chown -R appuser:appuser /var/log/nginx RUN chown -R appuser:appuser /run -RUN mkdir -p /app/backend/data && chown -R appuser:appuser /app/backend/data -RUN chmod 755 /app/backend/data USER appuser