Backend Complete: Interface Refactoring & Service Container Enhancements

Repository Interface Optimization:
- Created IBaseRepository.ts and IPaginatedRepository.ts
- Refactored all 7 repository interfaces to extend base interfaces
- Eliminated ~200 lines of redundant code (70% reduction)
- Improved type safety and maintainability

 Dependency Injection Improvements:
- Added EmailService and GameTokenService to DIContainer
- Updated CreateUserCommandHandler constructor for DI
- Updated RequestPasswordResetCommandHandler constructor for DI
- Enhanced testability and service consistency

 Environment Configuration:
- Created comprehensive .env.example with 40+ variables
- Organized into 12 logical sections (Database, Security, Email, etc.)
- Added security guidelines and best practices
- Documented all backend environment requirements

 Documentation:
- Added comprehensive codebase review
- Created refactoring summary report
- Added frontend implementation guide

Impact: Improved code quality, reduced maintenance overhead, enhanced developer experience
This commit is contained in:
2025-09-21 03:27:57 +02:00
parent 5b7c3ba4b2
commit 86211923db
306 changed files with 52956 additions and 0 deletions
@@ -0,0 +1,137 @@
import { ContactMapper } from '../../../../src/Application/DTOs/Mappers/ContactMapper';
import { ContactType, ContactState } from '../../../../src/Domain/Contact/ContactAggregate';
describe('ContactMapper', () => {
const createMockContact = (overrides: any = {}) => ({
id: 'contact-123',
name: 'John Doe',
email: 'john.doe@example.com',
userid: 'user-456',
type: ContactType.QUESTION,
txt: 'This is a test contact message.',
state: ContactState.ACTIVE,
createDate: new Date('2024-01-01'),
updateDate: new Date('2024-01-02'),
adminResponse: null,
responseDate: null,
respondedBy: null,
...overrides
});
describe('toShortDto', () => {
it('should map ContactAggregate to ShortContactDto correctly', () => {
// Arrange
const contact = createMockContact();
// Act
const result = ContactMapper.toShortDto(contact);
// Assert
expect(result).toEqual({
id: 'contact-123',
name: 'John Doe',
email: 'john.doe@example.com',
type: ContactType.QUESTION,
createDate: new Date('2024-01-01'),
state: ContactState.ACTIVE,
});
});
it('should handle different contact types', () => {
// Arrange
const bugContact = createMockContact({
id: 'bug-contact',
type: ContactType.BUG,
name: 'Bug Reporter'
});
// Act
const result = ContactMapper.toShortDto(bugContact);
// Assert
expect(result.type).toBe(ContactType.BUG);
expect(result.name).toBe('Bug Reporter');
});
});
describe('toDetailDto', () => {
it('should map ContactAggregate to DetailContactDto correctly', () => {
// Arrange
const contact = createMockContact();
// Act
const result = ContactMapper.toDetailDto(contact);
// Assert
expect(result).toEqual({
id: 'contact-123',
name: 'John Doe',
email: 'john.doe@example.com',
userid: 'user-456',
type: ContactType.QUESTION,
txt: 'This is a test contact message.',
state: ContactState.ACTIVE,
createDate: new Date('2024-01-01'),
updateDate: new Date('2024-01-02'),
adminResponse: null,
responseDate: null,
respondedBy: null,
});
});
it('should handle contact with admin response', () => {
// Arrange
const respondedContact = createMockContact({
adminResponse: 'Thank you for your question. Here is the answer...',
responseDate: new Date('2024-01-03'),
respondedBy: 'admin-789'
});
// Act
const result = ContactMapper.toDetailDto(respondedContact);
// Assert
expect(result.adminResponse).toBe('Thank you for your question. Here is the answer...');
expect(result.responseDate).toEqual(new Date('2024-01-03'));
expect(result.respondedBy).toBe('admin-789');
});
});
describe('toShortDtoList', () => {
it('should map array of ContactAggregate to array of ShortContactDto', () => {
// Arrange
const contacts = [
createMockContact({ id: 'contact-1', name: 'First Contact' }),
createMockContact({ id: 'contact-2', name: 'Second Contact', type: ContactType.BUG }),
createMockContact({ id: 'contact-3', name: 'Third Contact', type: ContactType.SALES })
];
// Act
const result = ContactMapper.toShortDtoList(contacts);
// Assert
expect(result).toHaveLength(3);
expect(result[0]).toEqual({
id: 'contact-1',
name: 'First Contact',
email: 'john.doe@example.com',
type: ContactType.QUESTION,
createDate: new Date('2024-01-01'),
state: ContactState.ACTIVE,
});
expect(result[1].type).toBe(ContactType.BUG);
expect(result[2].type).toBe(ContactType.SALES);
});
it('should handle empty array', () => {
// Arrange
const contacts: any[] = [];
// Act
const result = ContactMapper.toShortDtoList(contacts);
// Assert
expect(result).toEqual([]);
});
});
});
@@ -0,0 +1,187 @@
import { DeckMapper } from '../../../../src/Application/DTOs/Mappers/DeckMapper';
import { Type, CType, State } from '../../../../src/Domain/Deck/DeckAggregate';
describe('DeckMapper', () => {
const createMockDeck = (overrides: any = {}) => ({
id: 'deck-123',
name: 'Test Deck',
type: Type.LUCK,
userid: 'user-123',
creationdate: new Date('2024-01-01'),
cards: [
{ text: 'Test card 1', answer: 'Answer 1' },
{ text: 'Test card 2' }
],
playedNumber: 5,
ctype: CType.PUBLIC,
updatedate: new Date('2024-01-02'),
state: State.ACTIVE,
organization: null,
...overrides
});
describe('toShortDto', () => {
it('should map DeckAggregate to ShortDeckDto correctly', () => {
// Arrange
const deck = createMockDeck();
// Act
const result = DeckMapper.toShortDto(deck);
// Assert
expect(result).toEqual({
id: 'deck-123',
name: 'Test Deck',
type: Type.LUCK,
playedNumber: 5,
ctype: CType.PUBLIC
});
});
it('should handle different deck types', () => {
// Arrange
const jokeDeck = createMockDeck({
id: 'joker-deck',
name: 'Joker Deck',
type: Type.JOKER,
playedNumber: 10
});
// Act
const result = DeckMapper.toShortDto(jokeDeck);
// Assert
expect(result.type).toBe(Type.JOKER);
expect(result.playedNumber).toBe(10);
});
it('should handle private decks', () => {
// Arrange
const privateDeck = createMockDeck({
ctype: CType.PRIVATE,
playedNumber: 0
});
// Act
const result = DeckMapper.toShortDto(privateDeck);
// Assert
expect(result.ctype).toBe(CType.PRIVATE);
expect(result.playedNumber).toBe(0);
});
});
describe('toDetailDto', () => {
it('should map DeckAggregate to DetailDeckDto correctly', () => {
// Arrange
const deck = createMockDeck();
// Act
const result = DeckMapper.toDetailDto(deck);
// Assert
expect(result).toEqual({
id: 'deck-123',
name: 'Test Deck',
type: Type.LUCK,
userid: 'user-123',
creationdate: new Date('2024-01-01'),
cards: [
{ text: 'Test card 1', answer: 'Answer 1' },
{ text: 'Test card 2' }
],
playedNumber: 5,
ctype: CType.PUBLIC
});
});
it('should handle empty cards array', () => {
// Arrange
const deckWithNoCards = createMockDeck({
cards: []
});
// Act
const result = DeckMapper.toDetailDto(deckWithNoCards);
// Assert
expect(result.cards).toEqual([]);
});
it('should handle question type deck', () => {
// Arrange
const questionDeck = createMockDeck({
type: Type.QUESTION,
cards: [
{ text: 'Question 1?', answer: 'Answer 1' },
{ text: 'Question 2?', answer: null }
]
});
// Act
const result = DeckMapper.toDetailDto(questionDeck);
// Assert
expect(result.type).toBe(Type.QUESTION);
expect(result.cards).toHaveLength(2);
expect(result.cards[1].answer).toBeNull();
});
});
describe('toShortDtoList', () => {
it('should map array of DeckAggregate to array of ShortDeckDto', () => {
// Arrange
const decks = [
createMockDeck({ id: 'deck-1', name: 'First Deck' }),
createMockDeck({ id: 'deck-2', name: 'Second Deck', type: Type.JOKER }),
createMockDeck({ id: 'deck-3', name: 'Third Deck', ctype: CType.PRIVATE })
];
// Act
const result = DeckMapper.toShortDtoList(decks);
// Assert
expect(result).toHaveLength(3);
expect(result[0]).toEqual({
id: 'deck-1',
name: 'First Deck',
type: Type.LUCK,
playedNumber: 5,
ctype: CType.PUBLIC
});
expect(result[1].type).toBe(Type.JOKER);
expect(result[2].ctype).toBe(CType.PRIVATE);
});
it('should handle empty array', () => {
// Arrange
const decks: any[] = [];
// Act
const result = DeckMapper.toShortDtoList(decks);
// Assert
expect(result).toEqual([]);
expect(result).toHaveLength(0);
});
it('should handle large arrays', () => {
// Arrange
const decks = Array.from({ length: 50 }, (_, i) =>
createMockDeck({
id: `deck-${i + 1}`,
name: `Deck ${i + 1}`,
playedNumber: i
})
);
// Act
const result = DeckMapper.toShortDtoList(decks);
// Assert
expect(result).toHaveLength(50);
expect(result[0].playedNumber).toBe(0);
expect(result[49].playedNumber).toBe(49);
});
});
});
@@ -0,0 +1,206 @@
import { OrganizationMapper } from '../../../../src/Application/DTOs/Mappers/OrganizationMapper';
import { OrganizationState, OrganizationStateType } from '../../../../src/Domain/Organization/OrganizationAggregate';
describe('OrganizationMapper', () => {
const createMockOrganization = (overrides: any = {}) => ({
id: 'org-123',
name: 'Test Organization',
contactfname: 'John',
contactlname: 'Doe',
contactphone: '+1234567890',
contactemail: 'john@test.org',
state: OrganizationState.ACTIVE as OrganizationStateType,
regdate: new Date('2024-01-01'),
updatedate: new Date('2024-01-02'),
url: 'https://test.org',
userinorg: 5,
users: [
{ id: 'user-1', name: 'User One' },
{ id: 'user-2', name: 'User Two' }
],
...overrides
});
describe('toShortDto', () => {
it('should map OrganizationAggregate to ShortOrganizationDto correctly', () => {
// Arrange
const org = createMockOrganization();
// Act
const result = OrganizationMapper.toShortDto(org);
// Assert
expect(result).toEqual({
id: 'org-123',
name: 'Test Organization',
state: OrganizationState.ACTIVE,
userinorg: 5
});
});
it('should handle different organization states', () => {
// Arrange
const registeredOrg = createMockOrganization({
state: OrganizationState.REGISTERED,
userinorg: 0
});
// Act
const result = OrganizationMapper.toShortDto(registeredOrg);
// Assert
expect(result.state).toBe(OrganizationState.REGISTERED);
expect(result.userinorg).toBe(0);
});
it('should handle organization with many users', () => {
// Arrange
const orgWithManyUsers = createMockOrganization({
userinorg: 100
});
// Act
const result = OrganizationMapper.toShortDto(orgWithManyUsers);
// Assert
expect(result.userinorg).toBe(100);
});
});
describe('toDetailDto', () => {
it('should map OrganizationAggregate to DetailOrganizationDto correctly', () => {
// Arrange
const org = createMockOrganization();
// Act
const result = OrganizationMapper.toDetailDto(org);
// Assert
expect(result).toEqual({
id: 'org-123',
name: 'Test Organization',
contactfname: 'John',
contactlname: 'Doe',
contactphone: '+1234567890',
contactemail: 'john@test.org',
state: OrganizationState.ACTIVE,
regdate: new Date('2024-01-01'),
updatedate: new Date('2024-01-02'),
url: 'https://test.org',
userinorg: 5,
users: ['user-1', 'user-2']
});
});
it('should handle organization without URL', () => {
// Arrange
const orgWithoutUrl = createMockOrganization({
url: null
});
// Act
const result = OrganizationMapper.toDetailDto(orgWithoutUrl);
// Assert
expect(result.url).toBeNull();
});
it('should handle organization without users', () => {
// Arrange
const orgWithoutUsers = createMockOrganization({
users: null,
userinorg: 0
});
// Act
const result = OrganizationMapper.toDetailDto(orgWithoutUsers);
// Assert
expect(result.users).toEqual([]);
expect(result.userinorg).toBe(0);
});
it('should handle empty users array', () => {
// Arrange
const orgWithEmptyUsers = createMockOrganization({
users: [],
userinorg: 0
});
// Act
const result = OrganizationMapper.toDetailDto(orgWithEmptyUsers);
// Assert
expect(result.users).toEqual([]);
});
it('should handle soft deleted organization', () => {
// Arrange
const softDeletedOrg = createMockOrganization({
state: OrganizationState.SOFT_DELETE
});
// Act
const result = OrganizationMapper.toDetailDto(softDeletedOrg);
// Assert
expect(result.state).toBe(OrganizationState.SOFT_DELETE);
});
});
describe('toShortDtoList', () => {
it('should map array of OrganizationAggregate to array of ShortOrganizationDto', () => {
// Arrange
const orgs = [
createMockOrganization({ id: 'org-1', name: 'First Org', userinorg: 10 }),
createMockOrganization({ id: 'org-2', name: 'Second Org', state: OrganizationState.REGISTERED }),
createMockOrganization({ id: 'org-3', name: 'Third Org', userinorg: 0 })
];
// Act
const result = OrganizationMapper.toShortDtoList(orgs);
// Assert
expect(result).toHaveLength(3);
expect(result[0]).toEqual({
id: 'org-1',
name: 'First Org',
state: OrganizationState.ACTIVE,
userinorg: 10
});
expect(result[1].state).toBe(OrganizationState.REGISTERED);
expect(result[2].userinorg).toBe(0);
});
it('should handle empty array', () => {
// Arrange
const orgs: any[] = [];
// Act
const result = OrganizationMapper.toShortDtoList(orgs);
// Assert
expect(result).toEqual([]);
expect(result).toHaveLength(0);
});
it('should handle large arrays', () => {
// Arrange
const orgs = Array.from({ length: 25 }, (_, i) =>
createMockOrganization({
id: `org-${i + 1}`,
name: `Organization ${i + 1}`,
userinorg: i * 2
})
);
// Act
const result = OrganizationMapper.toShortDtoList(orgs);
// Assert
expect(result).toHaveLength(25);
expect(result[0].userinorg).toBe(0);
expect(result[24].userinorg).toBe(48);
});
});
});
@@ -0,0 +1,164 @@
import { UserMapper } from '../../../../src/Application/DTOs/Mappers/UserMapper';
import { UserAggregate, UserState } from '../../../../src/Domain/User/UserAggregate';
import { createMockUser } from '../../../testUtils';
describe('UserMapper', () => {
describe('toShortDto', () => {
it('should map UserAggregate to ShortUserDto correctly', () => {
// Arrange
const user = createMockUser({
id: 'user-123',
username: 'testuser',
email: 'test@example.com',
fname: 'John',
lname: 'Doe',
state: UserState.VERIFIED_REGULAR
});
// Act
const result = UserMapper.toShortDto(user);
// Assert
expect(result).toEqual({
id: 'user-123',
username: 'testuser',
state: UserState.VERIFIED_REGULAR,
authLevel: 0
});
// Should not contain sensitive information
expect(result).not.toHaveProperty('email');
expect(result).not.toHaveProperty('password');
expect(result).not.toHaveProperty('token');
});
it('should map admin user with authLevel 1', () => {
// Arrange
const adminUser = createMockUser({
id: 'admin-123',
username: 'admin',
email: 'admin@example.com',
fname: 'Admin',
lname: 'User',
state: UserState.ADMIN
});
// Act
const result = UserMapper.toShortDto(adminUser);
// Assert
expect(result).toEqual({
id: 'admin-123',
username: 'admin',
state: UserState.ADMIN,
authLevel: 1
});
});
});
describe('toDetailDto', () => {
it('should map UserAggregate to DetailUserDto correctly', () => {
// Arrange
const user = createMockUser({
id: 'user-123',
orgid: 'org-456',
username: 'testuser',
email: 'test@example.com',
fname: 'John',
lname: 'Doe',
token: 'verification-token',
type: 'admin',
phone: '+1234567890',
state: UserState.ADMIN
});
// Act
const result = UserMapper.toDetailDto(user);
// Assert
expect(result).toEqual({
id: 'user-123',
orgid: 'org-456',
username: 'testuser',
email: 'test@example.com',
fname: 'John',
lname: 'Doe',
code: 'verification-token',
type: 'admin',
phone: '+1234567890',
state: UserState.ADMIN
});
// Should not contain password
expect(result).not.toHaveProperty('password');
});
it('should handle null values correctly', () => {
// Arrange
const user = createMockUser({
id: 'user-123',
orgid: null,
username: 'testuser',
email: 'test@example.com',
fname: 'John',
lname: 'Doe',
token: null,
type: 'regular',
phone: null,
state: UserState.VERIFIED_REGULAR
});
// Act
const result = UserMapper.toDetailDto(user);
// Assert
expect(result.orgid).toBeNull();
expect(result.code).toBeNull();
expect(result.phone).toBeNull();
});
});
describe('toShortDtoList', () => {
it('should map array of UserAggregate to ShortUserDto array', () => {
// Arrange
const users = [
createMockUser({ id: 'user-1', username: 'user1', state: UserState.VERIFIED_REGULAR }),
createMockUser({ id: 'user-2', username: 'user2', state: UserState.REGISTERED_NOT_VERIFIED }),
createMockUser({ id: 'user-3', username: 'user3', state: UserState.DEACTIVATED })
];
// Act
const result = UserMapper.toShortDtoList(users);
// Assert
expect(result).toHaveLength(3);
expect(result[0]).toEqual({
id: 'user-1',
username: 'user1',
state: UserState.VERIFIED_REGULAR,
authLevel: 0
});
expect(result[1]).toEqual({
id: 'user-2',
username: 'user2',
state: UserState.REGISTERED_NOT_VERIFIED,
authLevel: 0
});
expect(result[2]).toEqual({
id: 'user-3',
username: 'user3',
state: UserState.DEACTIVATED,
authLevel: 0
});
});
it('should handle empty array', () => {
// Arrange
const users: UserAggregate[] = [];
// Act
const result = UserMapper.toShortDtoList(users);
// Assert
expect(result).toEqual([]);
});
});
});