Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/sample/src/listener/listener.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { EVENTS } from '@rumsan/user';
import { EVENTS } from '@rumsan/extensions/constants';
import { DevService } from '../utils/develop.service';
import { EmailService } from '../utils/email.service';

Expand Down
10 changes: 3 additions & 7 deletions apps/sample/src/user/user.controller.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { Controller, Get, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import {
ACTIONS,
AbilitiesGuard,
CheckAbilities,
JwtGuard,
SUBJECTS,
} from '@rumsan/user';
import { ACTIONS, SUBJECTS } from '@rumsan/extensions/constants';
import { JwtGuard } from '@rumsan/extensions/guards';
import { AbilitiesGuard, CheckAbilities } from '@rumsan/user';
import { APP } from '../constants';
import { AppUsersService } from './user.service';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { RSError } from '@rumsan/extensions/exceptions';
import { RSError } from '../exceptions';

export function RSE(
message: string,
name: string = 'UNKNOWN',
httpCode: number = 500,
name = 'UNKNOWN',
httpCode = 500,
meta?: any,
) {
return new RSError({ message, name, httpCode, srcModule: 'RS_USER', meta });
}

export const ERRORS = {
export const RSERRORS = {
ROLE_NAME_INVALID: RSE(
'Invalid characters in role name.',
'ROLE_NAME_INVALID',
Expand Down
30 changes: 30 additions & 0 deletions libs/extensions/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,33 @@ export const ConstantControllers: { [key: string]: ControllerFunction } = {
};

export const PROTECTED_SETTINGS = 'PROTECTED';

export { RSE, RSERRORS } from './errors';
export * from './events';

//For Ability Guard
export const ACTIONS = {
MANAGE: 'manage',
CREATE: 'create',
UPDATE: 'update',
DELETE: 'delete',
READ: 'read',
};

// For Ability Guard
export const SUBJECTS = {
ALL: 'all',
PUBLIC: 'public',
USER: 'user',
ROLE: 'role',
};

export const APP = {
JWT_BEARER: 'JWT',
};

export const CLIENT_TOKEN_LIFETIME = 600;

export const IS_PUBLIC_KEY = 'isPublic';

export const NOT_AVAILABLE = 'N/A';
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { CurrentUserInterface } from '../interfaces/current-user.interface';
import { CurrentUserInterface } from '@rumsan/sdk/interfaces';

export const CurrentUser = createParamDecorator(
(data: undefined, ctx: ExecutionContext): CurrentUserInterface => {
Expand Down
2 changes: 2 additions & 0 deletions libs/extensions/src/decorators/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './currentUser.decorator';
export * from './param.uuid.decorator';
export * from './public.decorator';
export * from './request.decorator';
4 changes: 4 additions & 0 deletions libs/extensions/src/decorators/public.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { SetMetadata } from '@nestjs/common';
import { IS_PUBLIC_KEY } from '../constants';

export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
13 changes: 13 additions & 0 deletions libs/extensions/src/dtos/settingDto/createSetting.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ export class CreateSettingDto {
@IsOptional()
@IsBoolean()
isPrivate?: boolean;

@IsString()
@IsOptional()
createdBy?: string;

@IsString()
@IsOptional()
updatedBy?: string;

@IsString()
@IsNotEmpty()
@IsOptional()
sessionId: string;
}

export class UpdateSettingDto extends PickType(CreateSettingDto, ['value']) {}
Expand Down
8 changes: 8 additions & 0 deletions libs/extensions/src/dtos/settingDto/updateSetting.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,12 @@ export class UpdateSettngsDto {
})
@IsBoolean()
isReadOnly!: false;

@IsOptional()
@IsNotEmpty()
sessionId?: string;

@IsOptional()
@IsNotEmpty()
updatedBy?: string;
}
File renamed without changes.
23 changes: 23 additions & 0 deletions libs/extensions/src/guards/jwt.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { Observable } from 'rxjs';
import { IS_PUBLIC_KEY } from '../constants';

@Injectable()
export class JwtGuard extends AuthGuard('jwt') implements CanActivate {
constructor(private reflector: Reflector) {
super();
}
override canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const isPublic = this.reflector.get<boolean>(
IS_PUBLIC_KEY,
context.getHandler(),
);

if (isPublic) return true;
return super.canActivate(context);
}
}
26 changes: 23 additions & 3 deletions libs/extensions/src/settings/settings.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,53 @@ import {
Patch,
Post,
Query,
UseGuards,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { CUI } from '@rumsan/sdk/interfaces';
import { APP } from '../constants';
import { Public } from '../decorators';
import { CurrentUser } from '../decorators/currentUser.decorator';
import { CreateSettingDto, ListSettingDto, UpdateSettngsDto } from '../dtos';
import { JwtGuard } from '../guards';
import { SettingsService } from './settings.service';

@Controller('settings')
@ApiTags('Settings')
@ApiBearerAuth(APP.JWT_BEARER)
@UseGuards(JwtGuard)
export class SettingsController {
constructor(private readonly settingsService: SettingsService) {}

@Get('')
@Public()
list(@Query() query: ListSettingDto) {
return this.settingsService.list(query);
}

@Post('')
create(@Body() createSettingDto: CreateSettingDto) {
create(@Body() createSettingDto: CreateSettingDto, @CurrentUser() cu: CUI) {
if (cu.name) {
createSettingDto.createdBy = cu.name;
}
createSettingDto.sessionId = cu.sessionId;
return this.settingsService.create(createSettingDto);
}

@Get(':name')
@Public()
get(@Param('name') name: string) {
return this.settingsService.getByName(name);
}

@Patch(':name')
udpdate(@Param('name') name: string, @Body() @Body() dto: UpdateSettngsDto) {
update(
@Param('name') name: string,
@Body() dto: UpdateSettngsDto,
@CurrentUser() cu: CUI,
) {
dto.sessionId = cu.sessionId;
if (cu.name) dto.updatedBy = cu?.name;
return this.settingsService.update(name, dto);
}
}
132 changes: 85 additions & 47 deletions libs/extensions/src/settings/settings.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,101 @@
import { SettingsService } from './settings.service';

describe('SettingsService', () => {
let service: SettingsService;
let prismaMock: any;

beforeEach(() => {
prismaMock = {
setting: {
findMany: jest.fn(),
findUnique: jest.fn(),
},
};
service = new SettingsService(prismaMock);
});
let service: SettingsService;
let prismaMock: any;

it('should return public settings', async () => {
const mockSettings = [
{ name: 'SMTP', value: { "HOST": "smtp.gmail.com", "PORT": 465, "SECURE": true, "PASSWORD": "test", "USERNAME": "test" }, isPrivate: false },
{ name: 'SMTP1', value: { "HOST": "smtp.gmail.com", "PORT": 465, "SECURE": true, "PASSWORD": "test", "USERNAME": "test" }, isPrivate: false },
];
beforeEach(() => {
prismaMock = {
setting: {
findMany: jest.fn(),
findUnique: jest.fn(),
},
};
service = new SettingsService(prismaMock);
});

prismaMock.setting.findMany.mockResolvedValue(mockSettings);
it('should return public settings', async () => {
const mockSettings = [
{
name: 'SMTP',
value: {
HOST: 'smtp.gmail.com',
PORT: 465,
SECURE: true,
PASSWORD: 'test',
USERNAME: 'test',
},
isPrivate: false,
},
{
name: 'SMTP1',
value: {
HOST: 'smtp.gmail.com',
PORT: 465,
SECURE: true,
PASSWORD: 'test',
USERNAME: 'test',
},
isPrivate: false,
},
];

const result = await service.listPublic();
console.log(result)
prismaMock.setting.findMany.mockResolvedValue(mockSettings);

expect(result).toEqual({
SMTP: { "HOST": "smtp.gmail.com", "PORT": 465, "SECURE": true, "PASSWORD": "test", "USERNAME": "test" },
SMTP1: { "HOST": "smtp.gmail.com", "PORT": 465, "SECURE": true, "PASSWORD": "test", "USERNAME": "test" },
});
expect(prismaMock.setting.findMany).toHaveBeenCalledWith({
where: { isPrivate: false },
});
});
const result = await service.listPublic();

expect(result).toEqual({
SMTP: {
HOST: 'smtp.gmail.com',
PORT: 465,
SECURE: true,
PASSWORD: 'test',
USERNAME: 'test',
},
SMTP1: {
HOST: 'smtp.gmail.com',
PORT: 465,
SECURE: true,
PASSWORD: 'test',
USERNAME: 'test',
},
});
expect(prismaMock.setting.findMany).toHaveBeenCalledWith({
where: { isPrivate: false },
});
});

it('should return a public setting by name', async () => {
const mockSetting = { name: 'SMTP', value: { "HOST": "smtp.gmail.com", "PORT": 465, "SECURE": true, "PASSWORD": "test", "USERNAME": "test" }, isPrivate: false };
it('should return a public setting by name', async () => {
const mockSetting = {
name: 'SMTP',
value: {
HOST: 'smtp.gmail.com',
PORT: 465,
SECURE: true,
PASSWORD: 'test',
USERNAME: 'test',
},
isPrivate: false,
};

prismaMock.setting.findUnique.mockResolvedValue(mockSetting);
prismaMock.setting.findUnique.mockResolvedValue(mockSetting);

const result = await service.getPublic('SMTP');
const result = await service.getPublic('SMTP');

expect(result).toEqual(mockSetting);
expect(prismaMock.setting.findUnique).toHaveBeenCalledWith({
where: { name: 'SMTP', isPrivate: false },
});
expect(result).toEqual(mockSetting);
expect(prismaMock.setting.findUnique).toHaveBeenCalledWith({
where: { name: 'SMTP', isPrivate: false },
});
});

it('should throw an error if a public setting is not found', async () => {
prismaMock.setting.findUnique.mockResolvedValue(null);


it('should throw an error if a public setting is not found', async () => {
prismaMock.setting.findUnique.mockResolvedValue(null);

await expect(service.getPublic('NONEXISTENT')).rejects.toThrow(
"Public setting 'NONEXISTENT' not found"
);
expect(prismaMock.setting.findUnique).toHaveBeenCalledWith({
where: { name: 'NONEXISTENT', isPrivate: false },
});
await expect(service.getPublic('NONEXISTENT')).rejects.toThrow(
"Public setting 'NONEXISTENT' not found",
);
expect(prismaMock.setting.findUnique).toHaveBeenCalledWith({
where: { name: 'NONEXISTENT', isPrivate: false },
});
});
});
});
7 changes: 6 additions & 1 deletion libs/extensions/src/settings/settings.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ export class SettingsService {
}

async list(query: ListSettingDto) {
// console.log(query);
const AND_CONDITIONS = [];
let conditions = {};

Expand Down Expand Up @@ -189,6 +188,8 @@ export class SettingsService {
requiredFields: dto.requiredFields,
isPrivate: dto.isPrivate,
isReadOnly: dto.isReadOnly,
sessionId: dto.sessionId,
updatedBy: dto.updatedBy,
},
});
this.load();
Expand Down Expand Up @@ -218,6 +219,8 @@ export class SettingsService {
requiredFields,
isReadOnly,
isPrivate,
sessionId,
createdBy,
} = createSettingDto;
let value: any = dtoValue;

Expand Down Expand Up @@ -280,6 +283,8 @@ export class SettingsService {
requiredFields: requiredFieldsArray,
isReadOnly,
isPrivate,
sessionId,
createdBy,
},
});

Expand Down
File renamed without changes.
Loading