Skip to content

Commit

Permalink
Merge branch 'dev' into nacho-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
na-cho-dev authored Mar 1, 2025
2 parents 0d24033 + fa685bc commit c54e20e
Show file tree
Hide file tree
Showing 22 changed files with 376 additions and 364 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ fabric.properties
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore


# DB
data/db/

docker-compose.yml
# User-specific files
*.suo
*.user
Expand Down
2 changes: 0 additions & 2 deletions src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,10 @@ export default class AuthenticationService {
identifierType: 'email',
});

console.log('userExists', userExists);
if (userExists) {
throw new CustomHttpException(SYS_MSG.USER_ACCOUNT_EXIST, HttpStatus.BAD_REQUEST);
}

console.log('createUserDto', createUserDto);
const user = await this.userService.createUser(createUserDto, manager);

if (!user) {
Expand Down
File renamed without changes.
18 changes: 0 additions & 18 deletions src/modules/comments/entities/comment.entity.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/modules/comments/entities/comments.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ export class Comment extends AbstractBaseEntity {
@ManyToOne(() => User, user => user.comments, { cascade: true })
user: User;

@Column({ nullable: false })
@Column({ nullable: true })
model_id: string;

@Column()
@Column({ nullable: true })
model_type: string;
}
4 changes: 2 additions & 2 deletions src/modules/email/email.consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export default class EmailQueueConsumer {
}
}

@Process('in-app-notification')
@Process('login-otp')
async sendLoginOtpEmailJob(job: Job<MailInterface>) {
try {
const {
Expand All @@ -113,7 +113,7 @@ export default class EmailQueueConsumer {
}
}

@Process('login-otp')
@Process('in-app-notification')
async sendNotificationMail(job: Job<MailInterface>) {
try {
const {
Expand Down
71 changes: 61 additions & 10 deletions src/modules/email/hng-templates/login-otp.hbs
Original file line number Diff line number Diff line change
@@ -1,20 +1,71 @@
<html lang='en'><head><meta charset='UTF-8' /><meta
name='viewport'
content='width=device-width, initial-scale=1.0'
/><title>Email Confirmation</title><style>body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin:
0; padding: 0; } .container { width: 100%; max-width: 600px; margin: 0 auto; background-color: #ffffff; padding:
20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); } .header { text-align: center; padding-bottom:
20px; } .header img { width: 120px; } .content { text-align: center; padding: 20px; } .content h1 { color: #333;
font-size: 24px; margin-bottom: 15px; } .content p { color: #666; font-size: 16px; line-height: 1.5;
margin-bottom: 20px; } .btn { display: inline-block; padding: 12px 25px; color: #ffffff; background-color:
#28a745; text-decoration: none; border-radius: 5px; font-weight: bold; font-size: 16px; margin-top: 20px; }
.footer { text-align: center; padding-top: 20px; font-size: 12px; color: #888888; } .footer a { color: #28a745;
text-decoration: none; }</style></head><body><div class='container'><div class='header'><img
/><title>Email Confirmation</title><style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
}
.container {
width: 100%;
max-width: 600px;
margin: 0 auto;
background-color: #ffffff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.header {
text-align: center;
padding-bottom: 20px;
}
.header img {
width: 120px;
}
.content {
text-align: center;
padding: 20px;
}
.content h1 {
color: #333;
font-size: 24px;
margin-bottom: 15px;
}
.content p {
color: #666;
font-size: 16px;
line-height: 1.5;
margin-bottom: 20px;
}
.btn {
display: inline-block;
padding: 12px 25px;
color: #ffffff;
background-color: #28a745;
text-decoration: none;
border-radius: 5px;
font-weight: bold;
font-size: 16px;
margin-top: 20px;
}
.footer {
text-align: center;
padding-top: 20px;
font-size: 12px;
color: #888888;
}
.footer a {
color: #28a745;
text-decoration: none;
}
</style></head><body><div class='container'><div class='header'><img
src='https://www.shutterstock.com/image-vector/circle-line-simple-design-logo-600nw-2174926871.jpg'
alt='Company Logo'
/></div><div class='content'><h1>Confirm Your Email Address</h1><p>Hello {{email}}</p><p>Thank you for
registering. Please confirm your email address by entering the otp:
<strong>{{otp}}</strong></p><p>If you didn't create an account with us, you can safely ignore this email.</p></div><div
<strong>{{token}}</strong></p><p>If you didn't create an account with us, you can safely ignore this email.</p></div><div
class='footer'
><p>&copy; 2024 Company Name. All rights reserved.</p><p><a
href='https://example.com/unsubscribe'
Expand Down
57 changes: 56 additions & 1 deletion src/modules/languages/languages.controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
import { Controller, Post, Body, Get, Patch, Param, Res, Request, Response } from '@nestjs/common';
import {
Controller,
Post,
Body,
Get,
Patch,
Param,
Res,
Request,
Response,
UseGuards,
BadRequestException,
Delete,
HttpCode,
HttpStatus,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBody, ApiBearerAuth } from '@nestjs/swagger';
import { CreateLanguageDto, UpdateLanguageDto } from './dto/create-language.dto';
import { LanguagesService } from './languages.service';
import { AuthGuard } from '@guards/auth.guard';
import { NotFoundException, UnauthorizedException } from '@nestjs/common';

@ApiTags('Languages')
@Controller('languages')
Expand Down Expand Up @@ -52,4 +69,42 @@ export class LanguagesController {
async updateLanguage(@Param('id') id: string, @Body() updateLanguageDto: UpdateLanguageDto) {
return this.languagesService.updateLanguage(id, updateLanguageDto);
}

@ApiBearerAuth()
@UseGuards(AuthGuard)
@ApiOperation({ summary: 'Get all languages by User ID' })
@ApiResponse({ status: 200, description: 'Returns languages associated with user.' })
@ApiResponse({ status: 404, description: 'User not found or no languages available.' })
@ApiResponse({ status: 401, description: 'Unauthorized access.' })
@Get(':id/languages')
async getUserLanguages(@Param('id') userId: string) {
if (!userId) {
throw new UnauthorizedException('Invalid user ID.');
}

const languages = await this.languagesService.getUserLanguages(userId);

if (!languages.length) {
throw new NotFoundException('No languages found for this user.');
}

return { status: 200, data: languages };
}

@Delete(':id')
@ApiBearerAuth()
@UseGuards(AuthGuard)
@ApiOperation({ summary: 'Delete a specific user language by ID' })
@ApiResponse({ status: 200, description: 'Language successfully deleted for the user.' })
@ApiResponse({ status: 400, description: 'Cannot delete language due to dependencies.' })
@ApiResponse({ status: 404, description: 'Language not found for the user.' })
@ApiResponse({ status: 401, description: 'Unauthorized access.' })
@ApiResponse({ status: 403, description: 'User not authorized to delete this language.' })
@HttpCode(HttpStatus.OK)
async deleteUserLanguage(@Param('id') languageId: string, @Request() req) {
if (!languageId) {
throw new BadRequestException('Invalid language ID provided.');
}
return await this.languagesService.deleteUserLanguage(languageId, req.user.id);
}
}
29 changes: 28 additions & 1 deletion src/modules/languages/languages.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import { isUUID } from 'class-validator';
export class LanguagesService {
constructor(
@InjectRepository(Language)
private readonly languageRepository: Repository<Language>
private readonly languageRepository: Repository<Language>,
@InjectRepository(User)
private readonly userRepository: Repository<User>
) {}

async createLanguage(createLanguageDto: CreateLanguageDto): Promise<any> {
Expand Down Expand Up @@ -158,4 +160,29 @@ export class LanguagesService {
});
}
}

async getUserLanguages(userId: string): Promise<Language[]> {
const user = await this.userRepository.findOne({ where: { id: userId }, relations: ['languages'] });
if (!user) throw new NotFoundException('User not found.');
return user.languages || [];
}

async deleteUserLanguage(languageId: string, userId: string): Promise<{ message: string }> {
const user = await this.userRepository.findOne({ where: { id: userId }, relations: ['languages'] });
if (!user) {
throw new NotFoundException('User not found.');
}

const language = user.languages.find(lang => lang.id === languageId);
if (!language) {
throw new NotFoundException('Language not found for this user.');
}

try {
await this.languageRepository.remove(language);
return { message: 'Language successfully deleted for the user.' };
} catch (error) {
throw new BadRequestException('Cannot delete language due to dependencies.');
}
}
}
10 changes: 10 additions & 0 deletions src/modules/languages/tests/languages.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Language } from '../entities/language.entity';
import { CreateLanguageDto } from '../dto/create-language.dto';
import { BadRequestException, ForbiddenException, HttpException, HttpStatus, NotFoundException } from '@nestjs/common';
import { User } from '@modules/user/entities/user.entity';
import { JwtService } from '@nestjs/jwt';

const mockLanguageRepository = {
findOne: jest.fn(),
Expand All @@ -21,6 +22,11 @@ const mockLanguagesService = {
getSupportedLanguages: jest.fn(),
};

const mockJwtService = {
sign: jest.fn(),
verify: jest.fn(),
};

describe('LanguagesController', () => {
let controller: LanguagesController;
let service: LanguagesService;
Expand All @@ -37,6 +43,10 @@ describe('LanguagesController', () => {
provide: getRepositoryToken(Language),
useValue: mockLanguageRepository,
},
{
provide: JwtService,
useValue: mockJwtService,
},
],
}).compile();

Expand Down
Loading

0 comments on commit c54e20e

Please sign in to comment.