Skip to content

Commit

Permalink
[BE] feat: Manager 권한 이상인 경우에만 그룹내 다른 멤버의 기록을 삭제할 수 있다.
Browse files Browse the repository at this point in the history
  • Loading branch information
lsh23 committed Dec 9, 2023
1 parent e2726eb commit 7229b88
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { UsersService } from '../../../users/application/users.service';
import { UsersModule } from '../../../users/users.module';
import { GroupAchievementRepository } from '../entities/group-achievement.repository';
import { GroupAchievementUpdateRequest } from '../dto/group-achievement-update-request';
import { NoSuchUserGroupException } from '../../group/exception/no-such-user-group.exception';

describe('GroupAchievementService Test', () => {
let groupAchievementService: GroupAchievementService;
Expand Down Expand Up @@ -774,7 +775,40 @@ describe('GroupAchievementService Test', () => {
});
});

test('남의 달성기록을 삭제하려하면 NoSuchGroupAchievementException를 던진다.', async () => {
test('관리자나 매니저는 다른 사람이 작성한 달성기록을 삭제할 수 있다.', async () => {
await transactionTest(dataSource, async () => {
// given
const leader = await usersFixture.getUser('ABC');
const member = await usersFixture.getUser('DEF');
const group = await groupFixture.createGroup('GROUP1', leader);
await groupFixture.addMember(group, member, UserGroupGrade.PARTICIPANT);
const groupAchievement =
await groupAchievementFixture.createGroupAchievement(
member,
group,
null,
'title',
);

// when
await groupAchievementService.delete(
leader.id,
group.id,
groupAchievement.id,
);
const findOne =
await groupAchievementRepository.findOneByIdAndUserAndGroup(
member.id,
group.id,
groupAchievement.id,
);

// then
expect(findOne).toBeUndefined();
});
});

test('관리자나 매니저가 아닌 사람이 남의 달성기록을 삭제하려하면 NoSuchGroupAchievementException를 던진다.', async () => {
await transactionTest(dataSource, async () => {
// given
const user1 = await usersFixture.getUser('ABC');
Expand All @@ -793,10 +827,10 @@ describe('GroupAchievementService Test', () => {
// then
await expect(
groupAchievementService.delete(user2.id, group.id, groupAchievement.id),
).rejects.toThrow(NoSuchGroupAchievementException);
).rejects.toThrow(UnauthorizedAchievementException);
});
});
test('다른 그룹의 달성을 삭제하려하면 NoSuchGroupAchievementException를 던진다.', async () => {
test('다른 그룹의 달성을 삭제하려하면 NoSuchUserGroupException 던진다.', async () => {
await transactionTest(dataSource, async () => {
// given
const user1 = await usersFixture.getUser('ABC');
Expand All @@ -819,7 +853,7 @@ describe('GroupAchievementService Test', () => {
group1.id,
groupAchievement.id,
),
).rejects.toThrow(NoSuchGroupAchievementException);
).rejects.toThrow(NoSuchUserGroupException);
});
});

Expand Down
37 changes: 30 additions & 7 deletions BE/src/group/achievement/application/group-achievement.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ import { NoSuchAchievementException } from '../../../achievement/exception/no-su
import { UnauthorizedAchievementException } from '../../../achievement/exception/unauthorized-achievement.exception';
import { PaginateGroupAchievementRequest } from '../dto/paginate-group-achievement-request';
import { PaginateGroupAchievementResponse } from '../dto/paginate-group-achievement-response';
import { GroupAchievementResponse } from '../dto/group-achievement-response';
import { GroupAchievementDeleteResponse } from '../dto/group-achievement-delete-response';
import { GroupAchievementUpdateRequest } from '../dto/group-achievement-update-request';
import { GroupAchievementUpdateResponse } from '../dto/group-achievement-update-response';
import { UserGroupRepository } from '../../group/entities/user-group.repository';
import { NoSuchUserGroupException } from '../../group/exception/no-such-user-group.exception';
import { UserGroupGrade } from '../../group/domain/user-group-grade';

@Injectable()
export class GroupAchievementService {
Expand All @@ -31,6 +33,7 @@ export class GroupAchievementService {
private readonly groupRepository: GroupRepository,
private readonly imageRepository: ImageRepository,
private readonly userBlockedGroupAchievementRepository: UserBlockedGroupAchievementRepository,
private readonly userGroupRepository: UserGroupRepository,
) {}

@Transactional()
Expand Down Expand Up @@ -166,12 +169,23 @@ export class GroupAchievementService {
return achievement;
}

async delete(userId: number, groupId: number, achievementId: number) {
const achievement = await this.getAchievement(
achievementId,
userId,
groupId,
);
async delete(requesterId: number, groupId: number, achievementId: number) {
const achievement =
await this.groupAchievementRepository.findOneByIdAndGroupId(
achievementId,
groupId,
);
if (!achievement) throw new NoSuchGroupAchievementException();

if (achievement.user.id != requesterId) {
const userGroup = await this.getUserGroup(requesterId, groupId);
if (
userGroup.grade !== UserGroupGrade.LEADER &&
userGroup.grade !== UserGroupGrade.MANAGER
)
throw new UnauthorizedAchievementException();
}

await this.groupAchievementRepository.repository.softDelete(achievement.id);
return GroupAchievementDeleteResponse.from(achievement);
}
Expand All @@ -190,4 +204,13 @@ export class GroupAchievementService {
if (!achievement) throw new NoSuchGroupAchievementException();
return achievement;
}

private async getUserGroup(userId: number, groupId: number) {
const userGroup = await this.userGroupRepository.findOneByUserIdAndGroupId(
userId,
groupId,
);
if (!userGroup) throw new NoSuchUserGroupException();
return userGroup;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,30 @@ describe('GroupAchievementController', () => {
});
});

it('삭제 권한이 없는 경우에는 403을 반환한다.', async () => {
// given
const { accessToken } = await authFixture.getAuthenticatedUser('ABC');

when(
mockGroupAchievementService.delete(
anyNumber(),
anyNumber(),
anyNumber(),
),
).thenThrow(new UnauthorizedAchievementException());

// when
// then
return request(app.getHttpServer())
.delete('/api/v1/groups/1/achievements/1')
.set('Authorization', `Bearer ${accessToken}`)
.expect(403)
.expect((res: request.Response) => {
expect(res.body.success).toBe(false);
expect(res.body.message).toBe('달성기록에 접근할 수 없습니다.');
});
});

it('잘못된 인증시 401을 반환한다.', async () => {
// given
const accessToken = 'abcd.abcd.efgh';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,12 @@ export class GroupAchievementRepository extends TransactionalRepository<GroupAch
}

async findOneByIdAndGroupId(achievementId: number, groupId: number) {
const findOne = await this.repository
.createQueryBuilder('ga')
.select('ga')
.where('ga.id = :achievementId', { achievementId })
.andWhere('ga.group_id = :groupId', { groupId })
.getOne();
const findOne = await this.repository.findOne({
where: { id: achievementId, group: { id: groupId } },
relations: {
user: true,
},
});
return findOne?.toModel();
}

Expand Down
2 changes: 2 additions & 0 deletions BE/src/group/achievement/group-achievement.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { UserBlockedGroupAchievementRepository } from './entities/user-blocked-g
import { GroupCategoryRepository } from '../category/entities/group-category.repository';
import { GroupRepository } from '../group/entities/group.repository';
import { ImageRepository } from '../../image/entities/image.repository';
import { UserGroupRepository } from '../group/entities/user-group.repository';

@Module({
imports: [
Expand All @@ -16,6 +17,7 @@ import { ImageRepository } from '../../image/entities/image.repository';
GroupRepository,
ImageRepository,
UserBlockedGroupAchievementRepository,
UserGroupRepository,
]),
],
controllers: [GroupAchievementController],
Expand Down

0 comments on commit 7229b88

Please sign in to comment.