Skip to content

Commit 9822eb4

Browse files
authored
Merge pull request #77 from raouf-b-dev/develop
Develop
2 parents b6aa99d + 488bea8 commit 9822eb4

File tree

135 files changed

+1330
-2392
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

135 files changed

+1330
-2392
lines changed

.github/workflows/tag-on-merge.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Tag on Merge
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
- develop
8+
9+
jobs:
10+
tag:
11+
runs-on: ubuntu-latest
12+
permissions:
13+
contents: write
14+
steps:
15+
- uses: actions/checkout@v4
16+
with:
17+
fetch-depth: 0 # Fetch all history for all tags and branches
18+
19+
- name: Get version from package.json
20+
id: package_version
21+
run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV
22+
23+
- name: Create and Push Tag
24+
run: |
25+
TAG_NAME="v${{ env.VERSION }}"
26+
# Only tag if the tag doesn't exist yet
27+
if ! git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
28+
echo "Creating new tag: $TAG_NAME"
29+
git config user.name "github-actions[bot]"
30+
git config user.email "github-actions[bot]@users.noreply.github.com"
31+
git tag -a "$TAG_NAME" -m "Automated tag for version ${{ env.VERSION }}"
32+
git push origin "$TAG_NAME"
33+
else
34+
echo "Tag $TAG_NAME already exists. Skipping."
35+
fi

DDD-HEXAGONAL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ src/modules/[module]/
102102
| **Shared Kernel** | A subset of the domain model shared between multiple contexts. Must be pure domain — no infrastructure | `src/shared-kernel/domain/` — contains only `Result`, `AppError`, `UseCase`, `Money`, `Quantity`, `IdempotencyStore` |
103103
| **Context Map** | Documents the relationships between Bounded Contexts | Orders imports from Customers (ACL via CustomerGateway), Carts (ACL via CartGateway) |
104104
| **Upstream/Downstream** | One context provides, another consumes | Orders (downstream) consumes Customers, Carts, Inventory, Payments (upstream) |
105-
| **Anti-Corruption Layer** | Translates between two contexts' models | Mappers in `secondary-adapters/persistence/mappers/` |
105+
| **Anti-Corruption Layer** | Translates between two contexts' models | Gateway adapters in `secondary-adapters/adapters/` (e.g., `CustomerGatewayAdapter`, `CartGatewayAdapter`) |
106106

107107
### 2.2 Tactical Design Patterns
108108

src/modules/auth/auth.module.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
33
import { JwtModule } from '@nestjs/jwt';
44
import { PassportModule } from '@nestjs/passport';
55
import { PostgresUserRepository } from './secondary-adapters/repositories/postgres-user-repository/postgres-user.repository';
6-
import { RedisUserRepository } from './secondary-adapters/repositories/redis-user-repository/redis-user.repository';
6+
import { CachedUserRepository } from './secondary-adapters/repositories/cached-user-repository/cached-user.repository';
77
import { BcryptService } from './secondary-adapters/services/bcrypt.service';
88
import { JwtStrategy } from './secondary-adapters/strategies/jwt.strategy';
99
import { UserEntity } from './secondary-adapters/orm/user.schema';
@@ -14,7 +14,7 @@ import { LoginUserUseCase } from './core/application/usecases/login-user/login-u
1414
import { RegisterUserUseCase } from './core/application/usecases/register-user/register-user.usecase';
1515
import {
1616
POSTGRES_USER_REPOSITORY,
17-
REDIS_USER_REPOSITORY,
17+
CACHED_USER_REPOSITORY,
1818
CUSTOMER_GATEWAY,
1919
} from './auth.tokens';
2020
import { ModuleCustomerGateway } from './secondary-adapters/adapters/module-customer.gateway';
@@ -45,18 +45,18 @@ import { EnvConfigService } from '../../config/env-config.service';
4545
useClass: PostgresUserRepository,
4646
},
4747
{
48-
provide: REDIS_USER_REPOSITORY,
48+
provide: CACHED_USER_REPOSITORY,
4949
useFactory: (
5050
cacheService: CacheService,
5151
postgresRepo: PostgresUserRepository,
5252
) => {
53-
return new RedisUserRepository(cacheService, postgresRepo);
53+
return new CachedUserRepository(cacheService, postgresRepo);
5454
},
5555
inject: [CacheService, POSTGRES_USER_REPOSITORY],
5656
},
5757
{
5858
provide: UserRepository,
59-
useExisting: REDIS_USER_REPOSITORY,
59+
useExisting: CACHED_USER_REPOSITORY,
6060
},
6161

6262
// Gateways

src/modules/auth/auth.tokens.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export const POSTGRES_USER_REPOSITORY = Symbol('POSTGRES_USER_REPOSITORY');
2-
export const REDIS_USER_REPOSITORY = Symbol('REDIS_USER_REPOSITORY');
2+
export const CACHED_USER_REPOSITORY = Symbol('CACHED_USER_REPOSITORY');
33
export const CUSTOMER_GATEWAY = Symbol('CUSTOMER_GATEWAY');

src/modules/auth/core/application/usecases/login-user/login-user.usecase.spec.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,4 @@ describe('LoginUserUseCase', () => {
6969
UseCaseError,
7070
);
7171
});
72-
73-
it('should return failure if unexpected error occurs', async () => {
74-
const error = new Error('Unexpected error');
75-
userRepository.findByEmail.mockRejectedValue(error);
76-
const result = await usecase.execute({
77-
email: 'test@example.com',
78-
password: 'password',
79-
});
80-
ResultAssertionHelper.assertResultFailure(
81-
result,
82-
'Unexpected error',
83-
UseCaseError,
84-
);
85-
});
8672
});

src/modules/auth/core/application/usecases/login-user/login-user.usecase.ts

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,35 +25,31 @@ export class LoginUserUseCase extends UseCase<
2525
async execute(
2626
dto: LoginDto,
2727
): Promise<Result<{ accessToken: string }, UseCaseError>> {
28-
try {
29-
// 1. Find User
30-
const userResult = await this.userRepository.findByEmail(dto.email);
31-
if (userResult.isFailure || !userResult.value) {
32-
return ErrorFactory.UseCaseError('Invalid credentials');
33-
}
34-
const user = userResult.value;
28+
// 1. Find User
29+
const userResult = await this.userRepository.findByEmail(dto.email);
30+
if (userResult.isFailure || !userResult.value) {
31+
return ErrorFactory.UseCaseError('Invalid credentials');
32+
}
33+
const user = userResult.value;
3534

36-
// 2. Verify Password
37-
const isMatch = await this.bcryptService.compare(
38-
dto.password,
39-
user.passwordHash,
40-
);
41-
if (!isMatch) {
42-
return ErrorFactory.UseCaseError('Invalid credentials');
43-
}
35+
// 2. Verify Password
36+
const isMatch = await this.bcryptService.compare(
37+
dto.password,
38+
user.passwordHash,
39+
);
40+
if (!isMatch) {
41+
return ErrorFactory.UseCaseError('Invalid credentials');
42+
}
4443

45-
// 3. Generate Token
46-
const payload = {
47-
sub: user.id,
48-
email: user.email,
49-
role: user.role,
50-
customerId: user.customerId,
51-
};
52-
const accessToken = this.jwtService.sign(payload);
44+
// 3. Generate Token
45+
const payload = {
46+
sub: user.id,
47+
email: user.email,
48+
role: user.role,
49+
customerId: user.customerId,
50+
};
51+
const accessToken = this.jwtService.sign(payload);
5352

54-
return Result.success({ accessToken });
55-
} catch (error) {
56-
return ErrorFactory.UseCaseError('Unexpected error during login', error);
57-
}
53+
return Result.success({ accessToken });
5854
}
5955
}

src/modules/auth/core/application/usecases/register-user/register-user.usecase.spec.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -80,26 +80,4 @@ describe('RegisterUserUseCase', () => {
8080
RepositoryError,
8181
);
8282
});
83-
84-
it('should return failure if unexpected error occurs', async () => {
85-
userRepository.findByEmail.mockResolvedValue(Result.success(null));
86-
mockCustomerGateway.createCustomer.mockResolvedValue(
87-
Result.success(mockCustomerRecord),
88-
);
89-
userRepository.save.mockRejectedValue(new Error('Unexpected error'));
90-
91-
const result = await usecase.execute({
92-
email: 'test@example.com',
93-
password: 'password',
94-
firstName: 'John',
95-
lastName: 'Doe',
96-
phone: '1234567890',
97-
});
98-
99-
ResultAssertionHelper.assertResultFailure(
100-
result,
101-
'Unexpected error',
102-
UseCaseError,
103-
);
104-
});
10583
});

src/modules/auth/core/application/usecases/register-user/register-user.usecase.ts

Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -33,49 +33,42 @@ export class RegisterUserUseCase extends UseCase<
3333
async execute(
3434
dto: RegisterDto,
3535
): Promise<Result<{ user: IUser; customerId: number }, UseCaseError>> {
36-
try {
37-
// 1. Check if user exists
38-
const existingUser = await this.userRepository.findByEmail(dto.email);
39-
if (existingUser.isSuccess && existingUser.value) {
40-
return ErrorFactory.UseCaseError('User with this email already exists');
41-
}
36+
// 1. Check if user exists
37+
const existingUser = await this.userRepository.findByEmail(dto.email);
38+
if (existingUser.isSuccess && existingUser.value) {
39+
return ErrorFactory.UseCaseError('User with this email already exists');
40+
}
4241

43-
// 2. Create Customer
44-
const customerResult = await this.customerGateway.createCustomer({
45-
firstName: dto.firstName,
46-
lastName: dto.lastName,
47-
email: dto.email,
48-
phone: dto.phone,
49-
});
42+
// 2. Create Customer
43+
const customerResult = await this.customerGateway.createCustomer({
44+
firstName: dto.firstName,
45+
lastName: dto.lastName,
46+
email: dto.email,
47+
phone: dto.phone,
48+
});
5049

51-
if (isFailure(customerResult)) return customerResult;
50+
if (isFailure(customerResult)) return customerResult;
5251

53-
const customer = customerResult.value;
52+
const customer = customerResult.value;
5453

55-
// 3. Hash Password
56-
const passwordHash = await this.bcryptService.hash(dto.password);
54+
// 3. Hash Password
55+
const passwordHash = await this.bcryptService.hash(dto.password);
5756

58-
// 4. Create User
59-
const user = User.create(
60-
null,
61-
dto.email,
62-
passwordHash,
63-
UserRoleType.CUSTOMER,
64-
customer.id!,
65-
);
57+
// 4. Create User
58+
const user = User.create(
59+
null,
60+
dto.email,
61+
passwordHash,
62+
UserRoleType.CUSTOMER,
63+
customer.id!,
64+
);
6665

67-
const saveResult = await this.userRepository.save(user);
68-
if (isFailure(saveResult)) return saveResult;
66+
const saveResult = await this.userRepository.save(user);
67+
if (isFailure(saveResult)) return saveResult;
6968

70-
return Result.success({
71-
user: saveResult.value.toPrimitives(),
72-
customerId: customer.id!,
73-
});
74-
} catch (error) {
75-
return ErrorFactory.UseCaseError(
76-
'Unexpected error during registration',
77-
error,
78-
);
79-
}
69+
return Result.success({
70+
user: saveResult.value.toPrimitives(),
71+
customerId: customer.id!,
72+
});
8073
}
8174
}

src/modules/auth/secondary-adapters/repositories/redis-user-repository/redis-user.repository.spec.ts renamed to src/modules/auth/secondary-adapters/repositories/cached-user-repository/cached-user.repository.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Test, TestingModule } from '@nestjs/testing';
2-
import { RedisUserRepository } from './redis-user.repository';
2+
import { CachedUserRepository } from './cached-user.repository';
33
import { CacheService } from '../../../../../infrastructure/redis/cache/cache.service';
44
import { UserRepository } from '../../../core/domain/repositories/user.repository';
55
import { MockUserRepository } from '../../../testing/mocks/user-repository.mock';
@@ -11,8 +11,8 @@ import { UserCacheMapper } from '../../persistence/mappers/user.mapper';
1111
import { USER_REDIS } from '../../../../../infrastructure/redis/constants/redis.constants';
1212
import { Result } from '../../../../../shared-kernel/domain/result';
1313

14-
describe('RedisUserRepository', () => {
15-
let repository: RedisUserRepository;
14+
describe('CachedUserRepository', () => {
15+
let repository: CachedUserRepository;
1616
let cacheService: jest.Mocked<CacheService>;
1717
let postgresRepo: MockUserRepository;
1818

@@ -22,7 +22,7 @@ describe('RedisUserRepository', () => {
2222
beforeEach(async () => {
2323
const module: TestingModule = await Test.createTestingModule({
2424
providers: [
25-
RedisUserRepository,
25+
CachedUserRepository,
2626
{
2727
provide: CacheService,
2828
useValue: {
@@ -39,7 +39,7 @@ describe('RedisUserRepository', () => {
3939
],
4040
}).compile();
4141

42-
repository = module.get<RedisUserRepository>(RedisUserRepository);
42+
repository = module.get<CachedUserRepository>(CachedUserRepository);
4343
cacheService = module.get(CacheService);
4444
postgresRepo = module.get(UserRepository);
4545
});

src/modules/auth/secondary-adapters/repositories/redis-user-repository/redis-user.repository.ts renamed to src/modules/auth/secondary-adapters/repositories/cached-user-repository/cached-user.repository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
} from '../../persistence/mappers/user.mapper';
1313

1414
@Injectable()
15-
export class RedisUserRepository implements UserRepository {
15+
export class CachedUserRepository implements UserRepository {
1616
constructor(
1717
private readonly cacheService: CacheService,
1818
private readonly postgresRepo: UserRepository,

0 commit comments

Comments
 (0)