Skip to content

Commit

Permalink
Merge branch 'develop' into feature/#5507-enable-add-manual-time
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelmbabhazi committed Oct 24, 2024
2 parents c5b3967 + df3a4fd commit 506c4e4
Show file tree
Hide file tree
Showing 16 changed files with 335 additions and 101 deletions.
2 changes: 2 additions & 0 deletions .scripts/configure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ if (!isDocker) {
API_BASE_URL: API_BASE_URL,
CLIENT_BASE_URL: CLIENT_BASE_URL,
COOKIE_DOMAIN: '${env.COOKIE_DOMAIN}',
PLATFORM_WEBSITE_URL: '${env.PLATFORM_WEBSITE_URL}',
PLATFORM_WEBSITE_DOWNLOAD_URL: '${env.PLATFORM_WEBSITE_DOWNLOAD_URL}',
Expand Down Expand Up @@ -221,6 +222,7 @@ if (!isDocker) {
API_BASE_URL: API_BASE_URL,
CLIENT_BASE_URL: CLIENT_BASE_URL,
COOKIE_DOMAIN: 'DOCKER_COOKIE_DOMAIN',
PLATFORM_WEBSITE_URL: 'DOCKER_PLATFORM_WEBSITE_URL',
PLATFORM_WEBSITE_DOWNLOAD_URL: 'DOCKER_PLATFORM_WEBSITE_DOWNLOAD_URL',
Expand Down
4 changes: 4 additions & 0 deletions .scripts/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export type Env = Readonly<{
// Set to true if build / runs in Docker
IS_DOCKER: boolean;

COOKIE_DOMAIN: string;

// Base URL of Gauzy UI website
CLIENT_BASE_URL: string;

Expand Down Expand Up @@ -140,6 +142,8 @@ export const env: Env = cleanEnv(

IS_DOCKER: bool({ default: false }),

COOKIE_DOMAIN: str({ default: '.gauzy.co' }),

CLIENT_BASE_URL: str({ default: 'http://localhost:4200' }),

API_BASE_URL: str({ default: 'http://localhost:3000' }),
Expand Down
4 changes: 2 additions & 2 deletions apps/gauzy/src/app/app.module.guard.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot } from '@angular/router';
import { Router, ActivatedRouteSnapshot } from '@angular/router';
import { environment } from '@gauzy/ui-config';
import { Store } from '@gauzy/ui-core/core';

@Injectable()
export class AppModuleGuard implements CanActivate {
export class AppModuleGuard {
constructor(private readonly router: Router, private readonly store: Store) {}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,8 @@ export class InvoiceEditComponent extends PaginationFilterBaseComponent implemen
component: InvoiceProductsSelectorComponent
},
valuePrepareFunction: (product: IProduct) => {
return product?.name
? `${this.translatableService.getTranslatedProperty(product, 'name')}`
: '';
const translatedName = this.translatableService.getTranslatedProperty(product, 'name');
return translatedName || '';
}
};
break;
Expand Down
136 changes: 70 additions & 66 deletions apps/gauzy/src/app/pages/pages.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Data, Router } from '@angular/router';
import { NbMenuItem } from '@nebular/theme';
import { TranslateService } from '@ngx-translate/core';
import { merge, pairwise } from 'rxjs';
import { filter, map, take, tap } from 'rxjs/operators';
import { filter, map, merge, pairwise, take, tap } from 'rxjs';
import { NgxPermissionsService } from 'ngx-permissions';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { chain } from 'underscore';
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
import { FeatureEnum, IOrganization, IRolePermission, IUser, IntegrationEnum, PermissionsEnum } from '@gauzy/contracts';
import {
AuthStrategy,
IJobMatchingEntity,
Expand All @@ -19,8 +17,8 @@ import {
Store,
UsersService
} from '@gauzy/ui-core/core';
import { FeatureEnum, IOrganization, IRolePermission, IUser, IntegrationEnum, PermissionsEnum } from '@gauzy/contracts';
import { distinctUntilChange, isNotEmpty } from '@gauzy/ui-core/common';
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
import { ReportService } from './reports/all-report/report.service';

@UntilDestroy({ checkProperties: true })
Expand Down Expand Up @@ -80,15 +78,9 @@ export class PagesComponent extends TranslationBaseComponent implements AfterVie
filter((organization: IOrganization) => !!organization),
distinctUntilChange(),
pairwise(), // Pair each emitted value with the previous one
tap(([organization]: [IOrganization, IOrganization]) => {
const { id: organizationId, tenantId } = organization;

tap(([previousOrganization]: [IOrganization, IOrganization]) => {
// Remove the specified menu items for previous selected organization
this._navMenuBuilderService.removeNavMenuItems(
// Define the base item IDs
this.getReportMenuBaseItemIds().map((itemId) => `${itemId}-${organizationId}-${tenantId}`),
'reports'
);
this.removeOrganizationReportsMenuItems(previousOrganization);
}),
untilDestroyed(this)
)
Expand Down Expand Up @@ -118,25 +110,21 @@ export class PagesComponent extends TranslationBaseComponent implements AfterVie
.subscribe();

this.reportService.menuItems$.pipe(distinctUntilChange(), untilDestroyed(this)).subscribe((menuItems) => {
if (menuItems) {
this.reportMenuItems = chain(menuItems)
.values()
.map((item) => {
return {
id: item.slug + `-${this.organization?.id}`,
title: item.name,
link: `/pages/reports/${item.slug}`,
icon: item.iconClass,
data: {
translationKey: `${item.name}`
}
};
})
.value();
} else {
this.reportMenuItems = [];
}
this.addOrganizationReportsMenuItems();
// Convert the menuItems object to an array
const reportItems = menuItems ? Object.values(menuItems) : [];

this.reportMenuItems = reportItems.map((item) => ({
id: item.slug,
title: item.name,
link: `/pages/reports/${item.slug}`,
icon: item.iconClass,
data: {
translationKey: item.name
}
}));

// Add the report menu items to the navigation menu
this.addOrRemoveOrganizationReportsMenuItems();
});
}

Expand Down Expand Up @@ -176,49 +164,62 @@ export class PagesComponent extends TranslationBaseComponent implements AfterVie
}

/**
* Adds report menu items to the organization's navigation menu.
* Removes the report menu items associated with the current organization.
*
* This function checks if the organization is defined. If not, it logs a warning and exits early.
* If the organization is defined, it constructs item IDs based on the organization and tenant ID
* and removes these items from the navigation menu.
*
* @returns {void} This function does not return a value.
*/
private addOrganizationReportsMenuItems() {
if (!this.organization) {
// Handle the case where this.organization is not defined
console.warn('Organization not defined. Unable to add/remove menu items.');
private removeOrganizationReportsMenuItems(organization: IOrganization): void {
// Return early if the organization is not defined, logging a warning
if (!organization) {
console.warn(`Organization not defined. Unable to remove menu items.`);
return;
}
const { id: organizationId, tenantId } = this.organization;

// Remove the specified menu items for current selected organization
// Note: We need to remove old menus before constructing new menus for the organization.
this._navMenuBuilderService.removeNavMenuItems(
// Define the base item IDs
this.getReportMenuBaseItemIds().map((itemId) => `${itemId}-${organizationId}-${tenantId}`),
'reports'
// Destructure organization properties
const { id: organizationId, tenantId } = organization;

// Generate the item IDs to remove and call the service method
const itemIdsToRemove = this.getReportMenuBaseItemIds().map(
(itemId) => `${itemId}-${organizationId}-${tenantId}`
);

// Validate if reportMenuItems is an array and has elements
if (!Array.isArray(this.reportMenuItems) || this.reportMenuItems.length === 0) {
this._navMenuBuilderService.removeNavMenuItems(itemIdsToRemove, 'reports');
}

/**
* Adds report menu items to the organization's navigation menu.
*/
private addOrRemoveOrganizationReportsMenuItems() {
if (!this.organization) {
console.warn('Organization not defined. Unable to add/remove menu items.');
return;
}

const { id: organizationId, tenantId } = this.organization;

// Remove old menu items before constructing new ones for the organization
this.removeOrganizationReportsMenuItems(this.organization);

// Iterate over each report and add it to the navigation menu
try {
this.reportMenuItems.forEach((report: NavMenuSectionItem) => {
// Validate the structure of each report item
if (report && report.id && report.title) {
this._navMenuBuilderService.addNavMenuItem(
{
id: report.id, // Unique identifier for the menu item
title: report.title, // The title of the menu item
icon: report.icon, // The icon class for the menu item, using FontAwesome in this case
link: report.link, // The link where the menu item directs
data: report.data
},
'reports'
); // The id of the section where this item should be added
}
});
} catch (error) {
console.error('Error adding report menu items', error);
}
this.reportMenuItems.forEach((report: NavMenuSectionItem) => {
// Validate the structure of each report item
if (report?.id && report?.title) {
this._navMenuBuilderService.addNavMenuItem(
{
id: `${report.id}-${organizationId}-${tenantId}`, // Unique identifier for the menu item
title: report.title, // The title of the menu item
icon: report.icon, // The icon class for the menu item
link: report.link, // The link where the menu item directs
data: report.data // The data associated with the menu item
},
'reports' // The id of the section where this item should be added
);
}
});
}

/**
Expand Down Expand Up @@ -402,5 +403,8 @@ export class PagesComponent extends TranslationBaseComponent implements AfterVie
this.store.featureTenant = tenant.featureOrganizations.filter((item) => !item.organizationId);
}

ngOnDestroy() {}
ngOnDestroy() {
// Remove the report menu items associated with the current organization before destroying the component
this.removeOrganizationReportsMenuItems(this.organization);
}
}
1 change: 1 addition & 0 deletions packages/ui-config/src/lib/environments/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export interface Environment {

API_BASE_URL: string;
CLIENT_BASE_URL: string;
COOKIE_DOMAIN?: string;

PLATFORM_WEBSITE_URL?: string;
PLATFORM_WEBSITE_DOWNLOAD_URL?: string;
Expand Down
1 change: 1 addition & 0 deletions packages/ui-core/common/src/lib/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './app.constants';
export * from './layout.constants';
export * from './route.constant';
export * from './timesheet.constants';
4 changes: 4 additions & 0 deletions packages/ui-core/common/src/lib/constants/route.constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// In a constants file or configuration service
export const ROUTES = {
DASHBOARD: '/pages/dashboard'
} as const;
1 change: 1 addition & 0 deletions packages/ui-core/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from './lib/auth';
export * from './lib/common/component-registry.types';
export * from './lib/components';
export * from './lib/core.module';
export * from './lib/extension';
export * from './lib/guards';
export * from './lib/interceptors';
export * from './lib/module-import-guard';
Expand Down
59 changes: 40 additions & 19 deletions packages/ui-core/core/src/lib/auth/auth.guard.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { firstValueFrom } from 'rxjs';
import { AuthService, AuthStrategy, ElectronService, Store } from '../services';
import { getCookie } from './cookie-helper';

@Injectable()
export class AuthGuard implements CanActivate {
export class AuthGuard {
constructor(
private readonly router: Router,
private readonly authService: AuthService,
private readonly authStrategy: AuthStrategy,
private readonly store: Store,
private readonly electronService: ElectronService
private readonly _router: Router,
private readonly _authService: AuthService,
private readonly _authStrategy: AuthStrategy,
private readonly _store: Store,
private readonly _electronService: ElectronService
) {}

/**
Expand All @@ -21,20 +22,30 @@ export class AuthGuard implements CanActivate {
* @return {Promise<boolean>} A promise that resolves to true if the user is authenticated, false otherwise.
*/
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
const token = route.queryParamMap.get('token');
const userId = route.queryParamMap.get('userId');
const token = route.queryParamMap.get('token') || getCookie('token');
const userId = route.queryParamMap.get('userId') || getCookie('userId');
const refreshToken = route.queryParamMap.get('refresh_token') || getCookie('refresh_token');

// If token and userId exist, store them
if (token && userId) {
this.store.token = token;
this.store.userId = userId;
this._store.token = token;
this._store.userId = userId;
this._store.refresh_token = refreshToken;
}

if (await this.authService.isAuthenticated()) {
// Logged in, so allow navigation
return true;
// Validate the token before proceeding
if (token && !this.validateToken(token)) {
console.error('Invalid token, redirecting to login page...');
await this.handleLogout(state.url); // Handle invalid token
return false; // Prevent navigation
}

// Not logged in, handle the logout process
// Check if the user is authenticated
if (await this._authService.isAuthenticated()) {
return true; // Allow navigation
}

// Not authenticated, handle logout
await this.handleLogout(state.url);
return false;
}
Expand All @@ -45,15 +56,25 @@ export class AuthGuard implements CanActivate {
* @param {string} returnUrl - The URL to return to after logging in.
*/
private async handleLogout(returnUrl: string): Promise<void> {
if (this.electronService.isElectron) {
if (this._electronService.isElectron) {
try {
this.electronService.ipcRenderer.send('logout');
this._electronService.ipcRenderer.send('logout');
} catch (error) {
console.error('Error sending logout message to Electron:', error);
}
}

await firstValueFrom(this.authStrategy.logout());
await this.router.navigate(['/auth/login'], { queryParams: { returnUrl } });
await firstValueFrom(this._authStrategy.logout());
await this._router.navigate(['/auth/login'], { queryParams: { returnUrl } });
}

/**
* Validates the format of a JWT token.
*
* @param {string} token - The JWT token to validate.
* @returns {boolean} - Returns true if the token is valid, otherwise false.
*/
private validateToken(token: string): boolean {
return typeof token === 'string' && token.trim().length > 0 && token.split('.').length === 3;
}
}
3 changes: 3 additions & 0 deletions packages/ui-core/core/src/lib/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { AuthGuard } from './auth.guard';
import { NoAuthGuard } from './no-auth.guard';
import { AuthService, AuthStrategy, ElectronService, Store } from '../services';

/**
* Social links for auth
*/
const socialLinks = [
{
url: environment.GOOGLE_AUTH_LINK,
Expand Down
Loading

0 comments on commit 506c4e4

Please sign in to comment.