Skip to content
Merged
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
44 changes: 36 additions & 8 deletions .github/workflows/gitflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
- "release/*"
- "feature/*"
- "hotfix/*"
- "bugfix/*"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand Down Expand Up @@ -44,7 +45,7 @@ jobs:
else
# Create the pull request
PR_URL=$(gh pr create \
--label "GitFlow" \
--label "gitflow" \
--base develop \
--head "$MAIN_BRANCH" \
--title "Sync $MAIN_BRANCH to develop" \
Expand All @@ -58,6 +59,7 @@ jobs:
gh pr merge "$PR_NUMBER" --merge --auto
fi


# Release to Main
- name: Create Pull Request for Release to Main
if: github.ref_name && startsWith(github.ref, 'refs/heads/release/')
Expand All @@ -76,13 +78,14 @@ jobs:
echo "PR from $RELEASE_BRANCH to develop already exists (open, closed, or merged). Skipping."
else
gh pr create \
--label "GitFlow" \
--label "gitflow" \
--base main \
--head "$RELEASE_BRANCH" \
--title "Merge $RELEASE_BRANCH into Main" \
--body "$PR_BODY"
fi


# Feature to Develop
- name: Create Pull Request for Feature to Develop
if: github.ref_name && startsWith(github.ref, 'refs/heads/feature/')
Expand All @@ -101,12 +104,37 @@ jobs:
echo "PR from $FEATURE_BRANCH to develop already exists (open, closed, or merged). Skipping."
else
gh pr create \
--label "GitFlow" \
--label "gitflow" \
--base develop \
--head "$FEATURE_BRANCH" \
--title "Feature: Merge $FEATURE_BRANCH into Develop" \
--body "$PR_BODY"
fi


# Bugfix to Develop
- name: Create Pull Request for Bugfix to Develop
if: github.ref_name && startsWith(github.ref, 'refs/heads/bugfix/')
run: |
# Extract branch name
BUGFIX_BRANCH=$(echo "${{ github.ref }}" | sed 's|refs/heads/||')

PR_DESCRIPTION="This Pull Request merges the hotfix branch '$BUGFIX_BRANCH' into 'develop'."
PR_BODY=$(cat .github/PULL_REQUEST_TEMPLATE/hotfix_bugfix.md)
PR_BODY=$(echo "$PR_BODY" | sed "s|\$PR_DESCRIPTION|$PR_DESCRIPTION|g")

# PR for bugfix to develop
if gh pr list --base develop --head "$BUGFIX_BRANCH" --json id | grep -q id; then
echo "PR from $HOTFIX_BRANCH to develop already exists. Skipping."
else
gh pr create \
--label "gitflow" \
--base develop \
--head "$BUGFIX_BRANCH" \
--title "Bugfix: Merge $$BUGFIX_BRANCH into Develop" \
--body "$PR_BODY"
fi


# Hotfix to Main and Develop
- name: Create Pull Request for Hotfix to Main and Develop
Expand All @@ -116,7 +144,7 @@ jobs:
HOTFIX_BRANCH=$(echo "${{ github.ref }}" | sed 's|refs/heads/||')

PR_DESCRIPTION="This Pull Request merges the hotfix branch '$HOTFIX_BRANCH' into 'main'. Fixes critical issues in **production**."
PR_BODY=$(cat .github/PULL_REQUEST_TEMPLATE/hotfix.md)
PR_BODY=$(cat .github/PULL_REQUEST_TEMPLATE/hotfix_bugfix.md)
PR_BODY=$(echo "$PR_BODY" | sed "s|\$PR_DESCRIPTION|$PR_DESCRIPTION|g")

# Check if PR already exists (open, closed, or merged)
Expand All @@ -126,26 +154,26 @@ jobs:
echo "PR from $HOTFIX_BRANCH to main already exists (open, closed, or merged). Skipping."
else
gh pr create \
--label "GitFlow" \
--label "gitflow" \
--base main \
--head "$HOTFIX_BRANCH" \
--title "Hotfix: Merge $HOTFIX_BRANCH into Main" \
--body "$PR_BODY"
fi

PR_DESCRIPTION="This Pull Request merges the hotfix branch '$HOTFIX_BRANCH' into 'develop'. Fixes critical issues in **production**."
PR_BODY=$(cat .github/PULL_REQUEST_TEMPLATE/hotfix.md)
PR_BODY=$(cat .github/PULL_REQUEST_TEMPLATE/hotfix_bugfix.md)
PR_BODY=$(echo "$PR_BODY" | sed "s|\$PR_DESCRIPTION|$PR_DESCRIPTION|g")

# PR for hotfix to develop
if gh pr list --base develop --head "$HOTFIX_BRANCH" --json id | grep -q id; then
echo "PR from $HOTFIX_BRANCH to develop already exists. Skipping."
else
gh pr create \
--label "GitFlow" \
--label "gitflow" \
--base develop \
--head "$HOTFIX_BRANCH" \
--title "Hotfix: Sync $HOTFIX_BRANCH with Develop" \
--title "Hotfix: Merge $HOTFIX_BRANCH into Develop" \
--body "$PR_BODY"
fi

Expand Down
6 changes: 6 additions & 0 deletions app/eslint.config.cjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const tsParser = require('@typescript-eslint/parser');
const typescriptPlugin = require('@typescript-eslint/eslint-plugin');
const unusedImports = require('eslint-plugin-unused-imports');
const prettierPlugin = require('eslint-plugin-prettier');
const importPlugin = require('eslint-plugin-import');
const boundariesPlugin = require('eslint-plugin-boundaries');
Expand All @@ -23,6 +24,7 @@ module.exports = [
plugins: {
'@typescript-eslint': typescriptPlugin,
prettier: prettierPlugin,
'unused-imports': unusedImports,
'import': importPlugin,
boundaries: boundariesPlugin
},
Expand All @@ -40,11 +42,15 @@ module.exports = [
],
},
rules: {
'complexity': ['error', { max: 10 }],
'unused-imports/no-unused-imports': 'error',
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-namespace': 'off',
'no-duplicate-case': 'error',
'no-duplicate-imports': 'error',
'prettier/prettier': [
'error',
{
Expand Down
2 changes: 1 addition & 1 deletion app/generate-index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,4 @@ const generateIndexFiles = (baseDir) => {
});
};

generateIndexFiles(directoryPath);
generateIndexFiles(directoryPath);
13 changes: 10 additions & 3 deletions app/jest.config.js → app/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { pathsToModuleNameMapper } = require('ts-jest');
const { compilerOptions } = require('./tsconfig.json');
import { pathsToModuleNameMapper } from 'ts-jest';
import { compilerOptions } from './tsconfig.json';

module.exports = {
export default {
moduleFileExtensions: ['js', 'json', 'ts'],
rootDir: './',
testRegex: '.*\\.spec\\.ts$',
Expand All @@ -14,4 +14,11 @@ module.exports = {
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
prefix: '<rootDir>/',
}),
modulePathIgnorePatterns: [
"<rootDir>/dist/"
],
testPathIgnorePatterns: [
"<rootDir>/dist/",
"<rootDir>/node_modules/"
],
};
7 changes: 4 additions & 3 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
"start:prod": "node dist/main",
"lint": "eslint \"{src,test}/**/*.ts\"",
"lint:fix": "eslint \"{src,test}/**/*.ts\" --fix",
"test": "jest --config jest.config.js",
"test:watch": "jest --watch --config jest.config.js",
"test:coverage": "jest --coverage --config jest.config.js",
"test": "jest --config jest.config.ts",
"test:watch": "jest --watch --config jest.config.ts",
"test:coverage": "jest --coverage --config jest.config.ts",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
Expand Down Expand Up @@ -66,6 +66,7 @@
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-unused-imports": "^4.1.4",
"eslint-import-resolver-typescript": "^3.7.0",
"eslint-plugin-boundaries": "^5.0.1",
"jest": "^29.7.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export class ApikeyApplicationCoreEntity {
apiKey: string;
userId: string;
description: string;
rulesPaths?: string[];
createdAt: string;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ErrorItemCoreEntity } from '@core/domain/entities/shared';

export abstract class CustomBaseException extends Error {
export abstract class AbstractBaseException extends Error {
protected constructor(
public readonly statusCode: number,
public readonly errors: ErrorItemCoreEntity[],
Expand Down
4 changes: 2 additions & 2 deletions app/src/core/domain/exceptions/custom-business.exception.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CustomBaseException } from '@core/domain/exceptions';
import { AbstractBaseException } from '@core/domain/exceptions';
import { ErrorItemCoreEntity } from '@core/domain/entities/shared';

export class CustomBusinessException extends CustomBaseException {
export class CustomBusinessException extends AbstractBaseException {
constructor(protected readonly error: ErrorItemCoreEntity) {
super(400, [error]);
}
Expand Down
4 changes: 2 additions & 2 deletions app/src/core/domain/exceptions/custom-conflict.exception.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CustomBaseException } from '@core/domain/exceptions';
import { AbstractBaseException } from '@core/domain/exceptions';
import { ErrorItemCoreEntity } from '@core/domain/entities/shared';

export class CustomConflictException extends CustomBaseException {
export class CustomConflictException extends AbstractBaseException {
constructor(protected readonly error: ErrorItemCoreEntity) {
super(409, [error]);
}
Expand Down
7 changes: 7 additions & 0 deletions app/src/core/domain/exceptions/custom-forbidden.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { AbstractBaseException } from '@core/domain/exceptions';

export class CustomForbiddenException extends AbstractBaseException {
constructor() {
super(403, [{ code: '403', message: 'Access denied' }]);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CustomBaseException } from '@core/domain/exceptions';
import { AbstractBaseException } from '@core/domain/exceptions';
import { ErrorItemCoreEntity } from '@core/domain/entities/shared';

export class CustomResourceNotFoundException extends CustomBaseException {
export class CustomResourceNotFoundException extends AbstractBaseException {
constructor(protected readonly error: ErrorItemCoreEntity) {
super(404, [error]);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CustomBaseException } from '@core/domain/exceptions';
import { AbstractBaseException } from '@core/domain/exceptions';

export class CustomUnauthorizedException extends CustomBaseException {
export class CustomUnauthorizedException extends AbstractBaseException {
constructor() {
super(401, [{ code: '401', message: 'Unauthorized' }]);
}
Expand Down
3 changes: 2 additions & 1 deletion app/src/core/domain/exceptions/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './custom-base.exception';
export * from './abstract-base.exception';
export * from './custom-business.exception';
export * from './custom-conflict.exception';
export * from './custom-forbidden.exception';
export * from './custom-resource-not-found.exception';
export * from './custom-unauthorized.exception';
2 changes: 1 addition & 1 deletion app/src/core/usecases/auth/check-api-key.usecase.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApikeyApplicationCoreEntity } from '@core/domain/entities/auth';

export interface CheckApiKeyUsecase {
execute(apiKey: string): Promise<ApikeyApplicationCoreEntity>;
execute(apiKey: string, path: string): Promise<ApikeyApplicationCoreEntity>;
}
20 changes: 18 additions & 2 deletions app/src/core/usecases/auth/impl/check-api-key.usecase.impl.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { CheckApiKeyUsecase } from '@core/usecases/auth';
import { ApiKeyApplicationRepositoryProvider } from '@core/providers/repositories';
import { ApikeyApplicationCoreEntity } from '@core/domain/entities/auth';
import { CustomUnauthorizedException } from '@core/domain/exceptions';
import { CustomForbiddenException, CustomUnauthorizedException } from '@core/domain/exceptions';

export class CheckApiKeyUsecaseImpl implements CheckApiKeyUsecase {
constructor(private readonly repositoryProvider: ApiKeyApplicationRepositoryProvider) {}

public async execute(apiKey: string): Promise<ApikeyApplicationCoreEntity> {
public async execute(apiKey: string, path: string): Promise<ApikeyApplicationCoreEntity> {
if (!apiKey || apiKey === '') {
throw new CustomUnauthorizedException();
}
Expand All @@ -20,6 +20,22 @@ export class CheckApiKeyUsecaseImpl implements CheckApiKeyUsecase {
throw new CustomUnauthorizedException();
}

if (!this.canAccessPath(path, apiKeyApplication?.rulesPaths || [])) {
throw new CustomForbiddenException();
}

return apiKeyApplication;
}

private canAccessPath(path: string, rulesPaths: string[]): boolean {
if (rulesPaths.length === 0) {
return false;
}

if (rulesPaths.length === 1 && rulesPaths.includes('*')) {
return true;
}

return path && rulesPaths.includes(path);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { applyDecorators, Type } from '@nestjs/common';
import { ApiBadRequestResponse, ApiInternalServerErrorResponse, ApiNoContentResponse, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiUnauthorizedResponse } from '@nestjs/swagger';
import {
ApiBadRequestResponse,
ApiForbiddenResponse,
ApiInternalServerErrorResponse,
ApiNoContentResponse,
ApiNotFoundResponse,
ApiOkResponse,
ApiOperation,
ApiUnauthorizedResponse,
} from '@nestjs/swagger';
import { ErrorResponse } from '@entrypoints/web/shared/response/error';

export function ApiDocGenericDelete(value: string, modelType?: Type) {
Expand All @@ -15,6 +24,7 @@ export function ApiDocGenericDelete(value: string, modelType?: Type) {
}),
ApiBadRequestResponse({ description: 'Bad request.', type: ErrorResponse }),
ApiUnauthorizedResponse({ description: 'Unauthorized.', type: ErrorResponse }),
ApiForbiddenResponse({ description: 'Forbidden.', type: ErrorResponse }),
ApiNotFoundResponse({ description: 'Resource not found.', type: ErrorResponse }),
ApiInternalServerErrorResponse({ description: 'Internal server error.', type: ErrorResponse }),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { applyDecorators, Type } from '@nestjs/common';
import { ApiBadRequestResponse, ApiInternalServerErrorResponse, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiUnauthorizedResponse } from '@nestjs/swagger';
import { ApiBadRequestResponse, ApiForbiddenResponse, ApiInternalServerErrorResponse, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiUnauthorizedResponse } from '@nestjs/swagger';
import { ErrorResponse } from '@entrypoints/web/shared/response/error';

export function ApiDocGenericGetAll(value: string, modelType: Type) {
Expand All @@ -12,6 +12,7 @@ export function ApiDocGenericGetAll(value: string, modelType: Type) {
}),
ApiBadRequestResponse({ description: 'Bad request.', type: ErrorResponse }),
ApiUnauthorizedResponse({ description: 'Unauthorized.', type: ErrorResponse }),
ApiForbiddenResponse({ description: 'Forbidden.', type: ErrorResponse }),
ApiNotFoundResponse({ description: 'Resource not found.', type: ErrorResponse }),
ApiInternalServerErrorResponse({ description: 'Internal server error', type: ErrorResponse }),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { applyDecorators, Type } from '@nestjs/common';
import { ApiBadRequestResponse, ApiInternalServerErrorResponse, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiUnauthorizedResponse } from '@nestjs/swagger';
import { ApiBadRequestResponse, ApiForbiddenResponse, ApiInternalServerErrorResponse, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiUnauthorizedResponse } from '@nestjs/swagger';
import { ErrorResponse } from '@entrypoints/web/shared/response/error';

export function ApiDocGenericGetOne(value: string, modelType: Type) {
Expand All @@ -11,6 +11,7 @@ export function ApiDocGenericGetOne(value: string, modelType: Type) {
}),
ApiBadRequestResponse({ description: 'Bad request.', type: ErrorResponse }),
ApiUnauthorizedResponse({ description: 'Unauthorized.', type: ErrorResponse }),
ApiForbiddenResponse({ description: 'Forbidden.', type: ErrorResponse }),
ApiNotFoundResponse({ description: 'Resource not found.', type: ErrorResponse }),
ApiInternalServerErrorResponse({ description: 'Internal server error.', type: ErrorResponse }),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { applyDecorators, Type } from '@nestjs/common';
import { ApiBadRequestResponse, ApiInternalServerErrorResponse, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiUnauthorizedResponse } from '@nestjs/swagger';
import { ApiBadRequestResponse, ApiForbiddenResponse, ApiInternalServerErrorResponse, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiUnauthorizedResponse } from '@nestjs/swagger';
import { ErrorResponse } from '@entrypoints/web/shared/response/error';

export function ApiDocGenericGetPagination(value: string, modelType: Type) {
Expand All @@ -12,6 +12,7 @@ export function ApiDocGenericGetPagination(value: string, modelType: Type) {
}),
ApiBadRequestResponse({ description: 'Bad request.', type: ErrorResponse }),
ApiUnauthorizedResponse({ description: 'Unauthorized.', type: ErrorResponse }),
ApiForbiddenResponse({ description: 'Forbidden.', type: ErrorResponse }),
ApiNotFoundResponse({ description: 'Resource not found.', type: ErrorResponse }),
ApiInternalServerErrorResponse({ description: 'Internal server error.', type: ErrorResponse }),
);
Expand Down
Loading
Loading