Skip to content

Commit

Permalink
[Feat] Mikro Orm Query Support (#7507)
Browse files Browse the repository at this point in the history
* feat: create mikro-orm and typeorm combined decorator

* fix: type issue

* feat: update relations in entities to support mikro-orm

* feat: create mikro-orm and typeorm combined decorator

* fix: type issue

* feat: update relations in entities to support mikro-orm

* fix: relations for mikro-orm

* fix: one-to-one-relation for mikro-orm

* fix: one-to-one and many-to-one relation in mikro-orm

* fix: relation ref column and join column

* feat: update column decorator

* fix: many-to-one relations

* feat: update joinColumn

* fix:Map Mikro ORM Entity To Json

* feat: create mikro-orm and typeorm combined decorator

* fix: type issue

* feat: update relations in entities to support mikro-orm

* feat: create mikro-orm and typeorm combined decorator

* fix: type issue

* feat: update relations in entities to support mikro-orm

* fix: relations for mikro-orm

* fix: one-to-one-relation for mikro-orm

* fix: one-to-one and many-to-one relation in mikro-orm

* fix: relation ref column and join column

* feat: update column decorator

* fix: many-to-one relations

* feat: update joinColumn

* fix:Map Mikro ORM Entity To Json

* fix: circular user and employee entity (create employee)

* fix(deepscan): removed unnecessary import

* fix: gauzy develop start error

* feat: parse where condition in mikro-orm

* chore(deps): updated yarn.lock

* refactor: many to one decorator

* fix: added new decortors for relations and columns

* fix(deepscan): removed unused import

* fix: updated column decorator

* fix: refactor column decorator types

* fix: one to one entity relations

* fix: updated employee create handler

* fix: refactor one to one decortor

* feat: added pipeline core subscriber

* fix: pipeline entity and shared utils

* fix: updated pipeline filters

* refactor: updated crud service

* fix: warehouse many to many relations

* fix: refactor relations decorators

* Revert "fix: refactor relations decorators"

This reverts commit 1f2d94d.

* feat: added shared types

* refactor: one to one and many to one decorators

* refactor: one to many and many to many decorators

* fix: missing await / async for find one method

* fix: updated transform interceptor

* fix: updated relations decorators

* fix: role service used custom role repository

* fix: missing where in count by crud method

* wip: column/property indexing

* feat: added mikro orm DB logging

* fix: serialize and wrap mikro orm entity

* fix: replace user module for permission guard

* fix: role permission custom repository for typeorm

* Update utils.ts

* feat: create type-orm to mikro-orm query builder mapping

* remove console log

* feat: fix query builder class mapping for mikro-orm

* chore(deps): bump @mikro-orm/core from 6.0.5 to 6.1.7

* fix: strict partial loading for specific fields

* fix: added missing subscribers for mikro-orm

* wip: event subscriber for mikro and type orm

* fix: rename event subscriber name

* fix: created generic entity event subscriber for MikroOrm and TypeOrm

* fix: rename base entity event subscriber

* fix: revert back defaults for local env

* chore: logging was not working well

* fix: tracer importing

* fix: before insert and create combine subscriber method

* fix: common mikro and typeorm before update event hook

---------

Co-authored-by: RAHUL RATHORE <[email protected]>
Co-authored-by: Ruslan K <[email protected]>
Co-authored-by: Rahul R <[email protected]>
  • Loading branch information
4 people authored Mar 8, 2024
1 parent 3bc7ad0 commit 1289c36
Show file tree
Hide file tree
Showing 232 changed files with 4,480 additions and 2,669 deletions.
2 changes: 1 addition & 1 deletion apps/gauzy/src/app/@core/sentry-error.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { GAUZY_ENV } from './constants';

@Injectable()
export class SentryErrorHandler implements ErrorHandler {
constructor(@Inject(GAUZY_ENV) private readonly environment: Environment) {}
constructor(@Inject(GAUZY_ENV) private readonly environment: Environment) { }

handleError(error) {
if (this.environment.SENTRY_DSN) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import { UntypedFormGroup } from '@angular/forms';
import { isNotNullOrUndefined } from '@gauzy/common-angular';

/**
* Validates that at least one field in the group has a valid, non-null, and non-undefined value.
*
* @param group - The form group to validate.
* @returns A validation error object if no valid value is found in the group, otherwise null.
*/
export function AtLeastOneFieldValidator(group: UntypedFormGroup): { [key: string]: any } {
let isAtLeastOne = false;

if (group && group.controls) {
for (const control in group.controls) {
if (group.controls.hasOwnProperty(control) && group.controls[control].valid && group.controls[control].value) {
if (
group.controls.hasOwnProperty(control) &&
group.controls[control].valid &&
isNotNullOrUndefined(group.controls[control].value)
) {
isAtLeastOne = true;
break;
}
}
}

return isAtLeastOne ? null : { requiredAtLeastOne: true };
}
19 changes: 5 additions & 14 deletions apps/gauzy/src/app/pages/pipelines/pipelines.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -102,24 +102,15 @@ <h4>
<div class="w-100">
<nb-select
fullWidth
[placeholder]="
'PIPELINES_PAGE.ALL_STATUS'
| translate
"
[placeholder]="'PIPELINES_PAGE.ALL_STATUS' | translate"
id="status"
formControlName="status"
>
<nb-option [value]="'active'">
{{
'PIPELINES_PAGE.ACTIVE'
| translate
}}
<nb-option [value]="true">
{{ 'PIPELINES_PAGE.ACTIVE' | translate }}
</nb-option>
<nb-option [value]="'inactive'">
{{
'PIPELINES_PAGE.INACTIVE'
| translate
}}
<nb-option [value]="false">
{{ 'PIPELINES_PAGE.INACTIVE' | translate }}
</nb-option>
</nb-select>
</div>
Expand Down
8 changes: 4 additions & 4 deletions apps/gauzy/src/app/pages/pipelines/pipelines.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { TranslateService } from '@ngx-translate/core';
import { NbDialogService, NbTabComponent } from '@nebular/theme';
import { Subject, firstValueFrom, BehaviorSubject } from 'rxjs';
import { debounceTime, filter, tap } from 'rxjs/operators';
import { distinctUntilChange, isNotEmpty } from '@gauzy/common-angular';
import { distinctUntilChange, isNotEmpty, isNotNullOrUndefined } from '@gauzy/common-angular';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { PipelineFormComponent } from './pipeline-form/pipeline-form.component';
import { DeleteConfirmationComponent } from '../../@shared/user/forms';
Expand Down Expand Up @@ -616,13 +616,13 @@ export class PipelinesComponent extends PaginationFilterBaseComponent implements
const { status, name, stages } = this.searchForm.getRawValue();

// Set filters based on the extracted values
if (status) {
if (isNotNullOrUndefined(status)) {
this.setFilter({ field: 'isActive', search: status }, false);
}
if (name) {
if (isNotNullOrUndefined(name)) {
this.setFilter({ field: 'name', search: name }, false);
}
if (stages) {
if (isNotNullOrUndefined(stages)) {
this.setFilter({ field: 'stages', search: stages }, false);
}

Expand Down
59 changes: 38 additions & 21 deletions packages/auth/src/social-auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@ import { Injectable } from '@nestjs/common';
import { ConfigService, IEnvironment } from '@gauzy/config';
import * as bcrypt from 'bcrypt';

/**
* Base class for social authentication.
*/
export abstract class BaseSocialAuth {
/**
* Validate OAuth login email.
*
* @param args - Arguments for validating OAuth login email.
* @returns The result of the validation.
*/
public abstract validateOAuthLoginEmail(args: []): any;
}

Expand All @@ -15,34 +24,42 @@ export class SocialAuthService extends BaseSocialAuth {
constructor() {
super();
this.configService = new ConfigService();
this.saltRounds = this.configService.get(
'USER_PASSWORD_BCRYPT_SALT_ROUNDS'
) as number;
this.clientBaseUrl = this.configService.get(
'clientBaseUrl'
) as keyof IEnvironment;
this.saltRounds = this.configService.get('USER_PASSWORD_BCRYPT_SALT_ROUNDS') as number;
this.clientBaseUrl = this.configService.get('clientBaseUrl') as keyof IEnvironment;
}

public validateOAuthLoginEmail(args: []): any { }

/**
* Generate a hash for the provided password.
*
* @param password - The password to hash.
* @returns A promise that resolves to the hashed password.
*/
public async getPasswordHash(password: string): Promise<string> {
return bcrypt.hash(password, this.saltRounds);
try {
return await bcrypt.hash(password, this.saltRounds);
} catch (error) {
// Handle the error appropriately, e.g., log it or throw a custom error
console.error('Error in getPasswordHash:', error);
throw new Error('Failed to hash the password');
}
}

// Redirect frontend
public async routeRedirect(
success: boolean,
auth: {
jwt: string;
userId: string;
},
res: any
) {
/**
* Redirect the user based on the success status.
*
* @param success - Indicates whether the operation was successful.
* @param auth - Object containing JWT and userId.
* @param res - Express response object.
* @returns The redirect response.
*/
async routeRedirect(success: boolean, auth: { jwt: string; userId: string }, res: any) {
const { userId, jwt } = auth;
if (success) {
return res.redirect(`${this.clientBaseUrl}/#/sign-in/success?jwt=${jwt}&userId=${userId}`);
} else {
return res.redirect(`${this.clientBaseUrl}/#/auth/register`);
}

const redirectPath = success ? `#/sign-in/success?jwt=${jwt}&userId=${userId}` : `#/auth/register`;
const redirectUrl = `${this.clientBaseUrl}/${redirectPath}`;

return res.redirect(redirectUrl);
}
}
20 changes: 20 additions & 0 deletions packages/common-angular/src/utils/shared-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ import * as timezone from 'moment-timezone';
import { distinctUntilChanged } from 'rxjs/operators';
import slugify from 'slugify';

/**
* Check string is null or undefined
* From https://github.com/typeorm/typeorm/issues/873#issuecomment-502294597
*
* @param obj
* @returns
*/
export function isNullOrUndefined<T>(value: T | null | undefined): value is null | undefined {
return value === undefined || value === null;
}

/**
* Checks if a value is not null or undefined.
* @param value The value to be checked.
* @returns true if the value is not null or undefined, false otherwise.
*/
export function isNotNullOrUndefined<T>(value: T | undefined | null): value is T {
return value !== undefined && value !== null;
}

// It will use for pass nested object or array in query params in get method.
export function toParams(query) {
let params: HttpParams = new HttpParams();
Expand Down
22 changes: 11 additions & 11 deletions packages/common/src/utils/shared-utils.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
import slugify from 'slugify';

/**
* Checks if a value is not null or undefined.
* @param value The value to be checked.
* @returns true if the value is not null or undefined, false otherwise.
*/
export function isNotNullOrUndefined<T>(value: T | undefined | null): boolean {
return value !== undefined && value !== null;
}

/**
* Check is function .
* @param item
Expand Down Expand Up @@ -134,8 +125,17 @@ export function removeDuplicates(data: string[]) {
* @param obj
* @returns
*/
export function isNullOrUndefined<T>(string: T | null | undefined): string is null | undefined {
return typeof string === 'undefined' || string === null;
export function isNullOrUndefined<T>(value: T | null | undefined): value is null | undefined {
return value === undefined || value === null;
}

/**
* Checks if a value is not null or undefined.
* @param value The value to be checked.
* @returns true if the value is not null or undefined, false otherwise.
*/
export function isNotNullOrUndefined<T>(value: T | undefined | null): value is T {
return value !== undefined && value !== null;
}

/**
Expand Down
24 changes: 22 additions & 2 deletions packages/config/src/database-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { TlsOptions } from "tls";
import { TlsOptions } from 'tls';

export type MikroLoggerNamespace = 'query' | 'query-params' | 'schema' | 'discovery' | 'info';

export enum DatabaseTypeEnum {
mongodb = 'mongodb',
Expand Down Expand Up @@ -57,7 +59,6 @@ export const getTlsOptions = (dbSslMode: boolean): TlsOptions | undefined => {
}
};


/**
* Get logging options based on the provided dbLogging value.
* @param {string} dbLogging - The value of process.env.DB_LOGGING
Expand All @@ -80,3 +81,22 @@ export const getLoggingOptions = (dbLogging: string): false | 'all' | ['query',
}
return loggingOptions;
};

/**
* Gets MikroORM logging options based on the specified logging type.
*
* @param dbLogging - The logging type.
* @returns False if logging is disabled, or an array of LoggerNamespace for the specified logging type.
*/
export const getLoggingMikroOptions = (dbLogging: string): false | MikroLoggerNamespace[] => {
const loggingOptionsMap: Record<string, MikroLoggerNamespace[]> = {
query: ['query'],
'query-params': ['query-params'],
schema: ['schema'],
discovery: ['discovery'],
info: ['info'],
all: ['query', 'query-params', 'schema', 'discovery', 'info']
};

return loggingOptionsMap[dbLogging] || false;
};
23 changes: 13 additions & 10 deletions packages/config/src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { DataSourceOptions } from 'typeorm';
import { KnexModuleOptions } from 'nest-knexjs';
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';
import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions';
import { DatabaseTypeEnum, getLoggingOptions, getTlsOptions } from './database-helpers';
import { DatabaseTypeEnum, getLoggingMikroOptions, getLoggingOptions, getTlsOptions } from './database-helpers';

/**
* Type representing the ORM types.
Expand All @@ -37,11 +37,11 @@ function getORMType(defaultValue: MultiORM = MultiORMEnum.TypeORM): MultiORM {
}

console.log(chalk.magenta(`NodeJs Version %s`), process.version);
console.log('Is DEMO: ', process.env.DEMO);
console.log('NODE_ENV: ', process.env.NODE_ENV);
console.log('Is DEMO: %s', process.env.DEMO);
console.log('NODE_ENV: %s', process.env.NODE_ENV);

const dbORM: MultiORM = getORMType();
console.log('DB ORM: ' + dbORM);
console.log('DB ORM: %s', dbORM);

const dbType = process.env.DB_TYPE || DatabaseTypeEnum.betterSqlite3;

Expand Down Expand Up @@ -100,7 +100,8 @@ switch (dbType) {
min: 0,
max: dbPoolSize
},
namingStrategy: EntityCaseNamingStrategy
namingStrategy: EntityCaseNamingStrategy,
debug: getLoggingMikroOptions(process.env.DB_LOGGING), // by default set to false only
};
mikroOrmConnectionConfig = mikroOrmMySqlOptions;

Expand Down Expand Up @@ -193,7 +194,8 @@ switch (dbType) {
// connection timeout - number of milliseconds to wait before timing out when connecting a new client
acquireTimeoutMillis: dbConnectionTimeout
},
namingStrategy: EntityCaseNamingStrategy
namingStrategy: EntityCaseNamingStrategy,
debug: getLoggingMikroOptions(process.env.DB_LOGGING), // by default set to false only
};
mikroOrmConnectionConfig = mikroOrmPostgresOptions;

Expand Down Expand Up @@ -275,7 +277,8 @@ switch (dbType) {
const mikroOrmSqliteConfig: MikroOrmSqliteOptions = {
driver: SqliteDriver,
dbName: dbPath,
namingStrategy: EntityCaseNamingStrategy
namingStrategy: EntityCaseNamingStrategy,
debug: getLoggingMikroOptions(process.env.DB_LOGGING), // by default set to false only
};
mikroOrmConnectionConfig = mikroOrmSqliteConfig;

Expand Down Expand Up @@ -304,15 +307,15 @@ switch (dbType) {
break;

case DatabaseTypeEnum.betterSqlite3:
const betterSqlitePath =
process.env.DB_PATH || path.join(process.cwd(), ...['apps', 'api', 'data'], 'gauzy.sqlite3');
const betterSqlitePath = process.env.DB_PATH || path.join(process.cwd(), ...['apps', 'api', 'data'], 'gauzy.sqlite3');
console.log('Better Sqlite DB Path: ' + betterSqlitePath);

// MikroORM DB Config (Better-SQLite3)
const mikroOrmBetterSqliteConfig: MikroOrmBetterSqliteOptions = {
driver: BetterSqliteDriver,
dbName: betterSqlitePath,
namingStrategy: EntityCaseNamingStrategy
namingStrategy: EntityCaseNamingStrategy,
debug: getLoggingMikroOptions(process.env.DB_LOGGING), // by default set to false only
};
mikroOrmConnectionConfig = mikroOrmBetterSqliteConfig;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { Column, Index } from 'typeorm';
import { Index } from 'typeorm';
import { AccountingTemplateTypeEnum, IAccountingTemplate } from '@gauzy/contracts';
import { isMySQL } from '@gauzy/config';
import { TenantOrganizationBaseEntity } from '../core/entities/internal';
import { MultiORMEntity } from './../core/decorators/entity';
import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity';
import { MikroOrmAccountingTemplateRepository } from './repository/mikro-orm-accounting-template.repository';

@MultiORMEntity('accounting_template', { mikroOrmRepository: () => MikroOrmAccountingTemplateRepository })
Expand All @@ -12,23 +12,23 @@ export class AccountingTemplate extends TenantOrganizationBaseEntity

@ApiProperty({ type: () => String })
@Index()
@Column()
@MultiORMColumn()
name?: string;

@ApiProperty({ type: () => String })
@Index()
@Column()
@MultiORMColumn()
languageCode?: string;

@ApiProperty({ type: () => String })
@Column({ type: 'text', nullable: true })
@MultiORMColumn({ type: 'text', nullable: true })
mjml?: string;

@ApiProperty({ type: () => String })
@Column({ ...(isMySQL() ? { type: "longtext" } : {}) })
@MultiORMColumn({ ...(isMySQL() ? { type: "longtext" } : {}) })
hbs?: string;

@ApiProperty({ type: () => String, enum: AccountingTemplateTypeEnum })
@Column()
@MultiORMColumn()
templateType?: string;
}
Loading

0 comments on commit 1289c36

Please sign in to comment.