Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manager 권한 이상인 경우 그룹내 다른 멤버의 기록을 삭제할 수 있다. #547

Merged
merged 1 commit into from
Dec 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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