Skip to content

Commit

Permalink
Merge pull request #7669 from ever-co/develop
Browse files Browse the repository at this point in the history
* [Feat] Mikro Orm Query Support (#7507)

* 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]>

* Update README.md

---------

Co-authored-by: Chetan Khandla <[email protected]>
Co-authored-by: RAHUL RATHORE <[email protected]>
Co-authored-by: Rahul R <[email protected]>
  • Loading branch information
4 people authored Mar 8, 2024
2 parents 142a141 + 2259cb5 commit 5b21066
Show file tree
Hide file tree
Showing 233 changed files with 4,486 additions and 2,677 deletions.
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,11 @@ More information about our Server & Desktop Apps:

## 🧱 Technology Stack and Requirements

- [TypeScript](https://www.typescriptlang.org) language
- [TypeScript](https://www.typescriptlang.org)
- [NodeJs](https://nodejs.org) / [NestJs](https://github.com/nestjs/nest)
- [Nx](https://nx.dev)
- [Angular](https://angular.io)
- [RxJS](http://reactivex.io/rxjs)
- [TypeORM](https://github.com/typeorm/typeorm)
- [Ngx-admin](https://github.com/akveo/ngx-admin)
- [Nx](https://nx.dev) / [Lerna](https://github.com/lerna/lerna)
- [Angular](https://angular.io) / [RxJS](http://reactivex.io/rxjs) / [Ngx-admin](https://github.com/akveo/ngx-admin)
- [TypeORM](https://github.com/typeorm/typeorm) / [MikroORM](https://github.com/mikro-orm/mikro-orm) / [Knex](https://github.com/knex/knex)

For Production, we recommend:

Expand Down Expand Up @@ -232,7 +230,7 @@ Notes:

#### Optional / Recommended for Production

- Optionally (recommended for production) install and run [PostgreSQL](https://www.postgresql.org) version 14 or later. Note: other DB can be configured manually in TypeORM. The default DB is set to SQLite (recommended for testing/demo purposes only).
- Optionally (recommended for production) install and run [PostgreSQL](https://www.postgresql.org) version 14 or later (16.x recommended for production). Note: other DB can be configured manually in TypeORM / MikroORM / Knex. The default DB is set to SQLite (recommended for testing/demo purposes only).
- Optionally (recommended for production) install and run [Redis](https://github.com/redis/redis). Notes: the platform will work without Redis using an in-memory caching strategy instead of a distributed one (recommended for testing/demo purposes only). Please note however that Redis is required for Jitsu.
- Optionally (recommended for production) install and run [ElasticSearch](https://github.com/elastic/elasticsearch). Note: the platform will work without ElasticSearch using DB build-in search capabilities (recommended for testing/demo purposes only).
- Optionally install and run [MinIO](https://github.com/minio/minio) or [LocalStack](https://github.com/localstack/localstack). Note: the platform will work without MinIO / LocalStack or other S3-compatible storage using local filesystem-based storage (recommended for testing/demo purposes only). For production, we recommend using Wasabi or AWS S3 storage or another S3-compatible cloud storage.
Expand All @@ -244,7 +242,7 @@ Notes:
- See [Setup Gauzy for Client Server](https://github.com/ever-co/ever-gauzy/wiki/Setup-Gauzy-for-Client-Server) for more information about production setup on your servers.
- We recommend deploying to Kubernetes (k8s), either manually (see below) or with our [Ever Helm Charts](https://github.com/ever-co/ever-charts).
- For the most simple deployment scenarios (e.g. for yourself or your small organization), check our [DigitalOcean App Platform configurations](https://github.com/ever-co/ever-gauzy/tree/develop/.do) and corresponding [GitHub Action](https://github.com/ever-co/ever-gauzy/blob/develop/.github/workflows/deploy-do-app-platform-stage.yml).
- Another variant to deploy Gauzy is to use DigitalOcean Droplets or any other virtual instance (with Ubuntu OS) and deploy using SCP/SSH, see for example following [GitHub Action](https://github.com/ever-co/ever-gauzy/blob/develop/.github/workflows/deploy-do-droplet-demo.yml)
- Another variant to deploy Gauzy is to use DigitalOcean Droplets or any other virtual instance (with Ubuntu OS) and deploy using SCP/SSH, for example following [GitHub Action](https://github.com/ever-co/ever-gauzy/blob/develop/.github/workflows/deploy-do-droplet-demo.yml)
- For more complex deployment scenarios, please see [Kubernetes configurations](https://github.com/ever-co/ever-gauzy/tree/develop/.deploy/k8s), which we are using to deploy Gauzy into [DigitalOcean k8s cluster](https://www.digitalocean.com/products/kubernetes).
- In addition, check [Gauzy Pulumi](https://github.com/ever-co/ever-gauzy-pulumi) project (WIP), it makes complex Clouds deployments possible with a single command (`pulumi up`). Note: it currently supports AWS EKS (Kubernetes) for development and production with Application Load Balancers and AWS RDS Serverless PostgreSQL DB deployments. We also implemented deployments to ECS EC2 and Fargate Clusters in the same Pulumi project.

Expand Down
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
Loading

0 comments on commit 5b21066

Please sign in to comment.