Skip to content

Commit

Permalink
Merge pull request #38 from fga-eps-mds/qas
Browse files Browse the repository at this point in the history
[FEAT] Adiciona área de conhecimento (fga-eps-mds/2024.2-ARANDU-DOC#71)
  • Loading branch information
gabrielm2q authored Feb 2, 2025
2 parents 4d4c463 + 14e425e commit 2766bf1
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 12 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
.PHONY: build
build:
docker-compose build --no-cache
docker-compose build

.PHONY: start
start:
docker-compose up

.PHONY: run
run:
docker-compose build --no-cache && docker-compose up
docker-compose build && docker-compose up

.PHONY: stop
stop:
Expand Down
2 changes: 2 additions & 0 deletions src/users/interface/user.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ export interface User extends Document {
verificationToken?: string;
isVerified?: boolean;
role?: UserRole;
knowledges?: mongoose.Types.ObjectId[];
subjects?: mongoose.Types.ObjectId[];
journeys?: mongoose.Types.ObjectId[];
subscribedJourneys?: mongoose.Types.ObjectId[];
subscribedSubjects?: mongoose.Types.ObjectId[];
completedTrails?: mongoose.Types.ObjectId[];
}
4 changes: 4 additions & 0 deletions src/users/interface/user.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ export const UserSchema = new mongoose.Schema(
default: UserRole.ALUNO,
},
subjects: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Subject' }],
knowledges: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Knowledge' }],
subscribedJourneys: [
{ type: mongoose.Schema.Types.ObjectId, ref: 'Journey' },
],
subscribedSubjects: [
{ type: mongoose.Schema.Types.ObjectId, ref: 'Subject' },
],
completedTrails: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Trail' }],
},
{ timestamps: true, collection: 'users' },
Expand Down
40 changes: 38 additions & 2 deletions src/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
constructor(private readonly usersService: UsersService) { }

@Post()
@UsePipes(ValidationPipe)
Expand Down Expand Up @@ -54,7 +54,7 @@ export class UsersController {
return {
message: `User ${user.username} updated successfully!`
}
} catch ( error ) {
} catch (error) {
if (error instanceof NotFoundException) {
throw new NotFoundException(`User with ID ${req.userId} not found`);
}
Expand All @@ -80,6 +80,11 @@ export class UsersController {
return await this.usersService.getSubscribedJourneys(userId);
}

@Get(':userId/subscribedSubjects')
async getSubscribedSubjects(@Param('userId') userId: string): Promise<Types.ObjectId[]> {
return await this.usersService.getSubscribedSubjects(userId);
}

@Get()
async getUsers() {
return await this.usersService.getUsers();
Expand All @@ -97,6 +102,37 @@ export class UsersController {
}
}

@Put(':id/knowledges/:knowledgeId/add')
async addKnowledgeToUser(
@Param('id') id: string,
@Param('knowledgeId') knowledgeId: string,
) {
try {
return await this.usersService.addKnowledgeToUser(id, knowledgeId);
} catch (error) {
throw error;
}
}

@UseGuards(JwtAuthGuard)
@Post(':userId/subjects/subscribe/:subjectId')
async subscribeSubject(
@Param('userId') userId: string,
@Param('subjectId') subjectId: string,
) {
return await this.usersService.subscribeSubject(userId, subjectId);
}

@UseGuards(JwtAuthGuard)
@Delete(':userId/subjects/unsubscribe/:subjectId')
async unsubscribeSubject(
@Param('userId') userId: string,
@Param('subjectId') subjectId: string,
) {
return this.usersService.unsubscribeSubject(userId, subjectId);
}


@UseGuards(JwtAuthGuard)
@Post(':userId/subscribe/:journeyId')
async subscribeJourney(
Expand Down
82 changes: 74 additions & 8 deletions src/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class UsersService {
constructor(
@InjectModel('User') private readonly userModel: Model<User>,
private readonly emailService: EmailService,
) {}
) { }

async createUser(createUserDto: CreateUserDto): Promise<User> {
const { name, email, username, password } = createUserDto;
Expand All @@ -45,13 +45,13 @@ export class UsersService {
}
}

async updateUser ( userId: string, updateUserDto: UpdateUserDto ): Promise<User> {
async updateUser(userId: string, updateUserDto: UpdateUserDto): Promise<User> {
await this.getUserById(userId);

if ( updateUserDto.email ) updateUserDto.isVerified = false;
if (updateUserDto.email) updateUserDto.isVerified = false;

var updateAttr = Object.fromEntries(
Object.entries(updateUserDto).filter(([value]) => value !== null)
Object.entries(updateUserDto).filter(([value]) => value !== null)
)

try {
Expand All @@ -64,12 +64,12 @@ export class UsersService {
}
)

if ( updateAttr['isVerified'] === false ) await this.emailService.sendVerificationEmail(updateAttr['email']);
if (updateAttr['isVerified'] === false) await this.emailService.sendVerificationEmail(updateAttr['email']);

return updatedUser;

} catch ( error ) {
if ( error instanceof MongoError ) {
} catch (error) {
if (error instanceof MongoError) {
throw new NotFoundException(`User with ID ${userId} not found!`)
}
throw error
Expand Down Expand Up @@ -112,14 +112,28 @@ export class UsersService {
return user;
}

async addKnowledgeToUser(userId: string, knowledgeId: string): Promise<User> {
const user = await this.userModel.findById(userId).exec();

if (!user) throw new NotFoundException(`User with ID ${userId} not found`);

const objectId = new Types.ObjectId(knowledgeId);

user.knowledges = (user.knowledges ? user.knowledges : [])

if (!user.knowledges.includes(objectId)) user.knowledges.push(objectId);

return user.save();
}

async addSubjectToUser(userId: string, subjectId: string): Promise<User> {
const user = await this.userModel.findById(userId).exec();

if (!user) throw new NotFoundException(`User with ID ${userId} not found`);

const objectId = new Types.ObjectId(subjectId);

user.subjects = ( user.subjects ? user.subjects : [] )
user.subjects = (user.subjects ? user.subjects : [])

if (!user.subjects.includes(objectId)) user.subjects.push(objectId);

Expand Down Expand Up @@ -208,6 +222,27 @@ export class UsersService {
return user.save();
}

async subscribeSubject(userId: string, subjectId: string): Promise<Partial<User>> {
const user = await this.userModel.findById(userId).exec();

if (!user) throw new NotFoundException(`Couldn't find user with ID ${userId}`);

if (!user.subscribedSubjects) user.subscribedSubjects = [new Types.ObjectId(subjectId)];
else if (!user.subscribedSubjects.includes(new Types.ObjectId(subjectId)))
user.subscribedSubjects.push(new Types.ObjectId(subjectId));
else throw new ConflictException(`User already subscribed at subject with ID ${subjectId}!`);

const savedUser = await user.save();

return {
id: savedUser.id,
email: savedUser.email,
name: savedUser.name,
username: savedUser.username,
subscribedSubjects: savedUser.subscribedSubjects
}
}

async unsubscribeJourney(userId: string, journeyId: string): Promise<User> {
const user = await this.userModel.findById(userId).exec();
if (!user) {
Expand All @@ -225,6 +260,29 @@ export class UsersService {
return user.save();
}

async unsubscribeSubject(userId: string, subjectId: string): Promise<Partial<User>> {
const user = await this.userModel.findById(userId).exec();

if (!user) throw new NotFoundException(`Couldn't find user with ID ${userId}`);

const objectId = new Types.ObjectId(subjectId);

if (!user.subscribedSubjects || !user.subscribedSubjects.includes(objectId))
throw new NotFoundException(`User not subscribed at subject with ID ${subjectId}`)

user.subscribedSubjects = user.subscribedSubjects.filter((id) => !id.equals(objectId))

const savedUser = await user.save();

return {
id: savedUser.id,
name: savedUser.name,
email: savedUser.email,
username: savedUser.username,
subscribedSubjects: savedUser.subscribedSubjects
}
}

async getSubscribedJourneys(userId: string): Promise<Types.ObjectId[]> {
const user = await this.userModel.findById(userId).exec();
if (!user) {
Expand All @@ -234,6 +292,14 @@ export class UsersService {
return user.subscribedJourneys;
}

async getSubscribedSubjects(userId: string): Promise<Types.ObjectId[]> {
const user = await this.userModel.findById(userId).exec();

if (!user) throw new NotFoundException(`Couldn't find user with ID ${userId}`);

return user.subscribedSubjects;
}

async findByEmail(email: string): Promise<User | null> {
return this.userModel.findOne({ email }).exec();
}
Expand Down
64 changes: 64 additions & 0 deletions test/user.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,34 @@ describe('UsersController', () => {
role: UserRole.ALUNO,
}

const mockSubscribedSubject = {
_id: 'mocked-id',
email: 'mocked-email',
name: 'mocked-name',
username: 'mocked-username',
subscribedSubjects: ['mocked-subject']
}

const mockUnsubscribedSubject = {
id: 'mocked-id',
email: 'mocked-email',
name: 'mocked-name',
username: 'mocked-username',
subscribedSubjects: []
}

const mockUserService = {
createUser: jest.fn().mockResolvedValue(mockUser),
verifyUser: jest.fn().mockResolvedValue(mockUser),
updateUser: jest.fn().mockResolvedValue(mockUpdatedUser),
getSubscribedJourneys: jest.fn().mockResolvedValue([]),
getSubscribedSubjects: jest.fn().mockResolvedValue([]),
getUsers: jest.fn().mockResolvedValue([mockUser]),
addSubjectToUser: jest.fn().mockResolvedValue(mockUser),
subscribeJourney: jest.fn().mockResolvedValue(mockUser),
unsubscribeJourney: jest.fn().mockResolvedValue(mockUser),
subscribeSubject: jest.fn().mockResolvedValue(mockSubscribedSubject),
unsubscribeSubject: jest.fn().mockResolvedValue(mockUnsubscribedSubject),
getUserById: jest.fn().mockResolvedValue(mockUser),
deleteUserById: jest.fn().mockResolvedValue(undefined),
updateUserRole: jest.fn().mockResolvedValue(mockUser),
Expand Down Expand Up @@ -89,6 +108,38 @@ describe('UsersController', () => {
})
})

it('should subscribe to subject', async () => {
const userId = 'mocked-id';
const subjectId = 'mocked-subject';

expect(await controller.subscribeSubject(userId, subjectId)).toBe(mockSubscribedSubject);
})

it('should return error when trying to subscribe to subject', async () => {
const userId = 'false-id';
const subjectId = 'mocked-subject';

mockUserService.subscribeSubject.mockRejectedValueOnce(new NotFoundException(`Couldn't find user with ID ${userId}`));

await expect(controller.subscribeSubject(userId, subjectId)).rejects.toBeInstanceOf(NotFoundException);
})

it('should unsubscribe to subject', async () => {
const userId = 'mocked-id';
const subjectId = 'mocked-subject';

expect(await controller.unsubscribeSubject(userId, subjectId)).toBe(mockUnsubscribedSubject);
})

it('should return error when trying to unsubscribe to subject', async () => {
const userId = 'false-id';
const subjectId = 'mocked-subject';

mockUserService.unsubscribeSubject.mockRejectedValueOnce(new NotFoundException(`Couldn't find user with ID ${userId}`));

await expect(controller.unsubscribeSubject(userId, subjectId)).rejects.toBeInstanceOf(NotFoundException);
})

it('should return an error while trying to update user', async () => {
const updateUserDto: UpdateUserDto = {
name: 'Mock User',
Expand All @@ -115,6 +166,19 @@ describe('UsersController', () => {
await expect(controller.getSubscribedJourneys(userId)).resolves.toEqual([]);
});

it('should return error when trying to return subscribed subjects', async () => {
const userId = 'false-id';

mockUserService.getSubscribedSubjects.mockRejectedValueOnce(new NotFoundException(`User with ID ${userId} not found`));

await expect(controller.getSubscribedSubjects(userId)).rejects.toBeInstanceOf(NotFoundException);
});

it('should get subscribed subjects', async () => {
const userId = 'mockUserId';
await expect(controller.getSubscribedSubjects(userId)).resolves.toEqual([]);
});

it('should get all users', async () => {
await expect(controller.getUsers()).resolves.toEqual([mockUser]);
});
Expand Down
Loading

0 comments on commit 2766bf1

Please sign in to comment.