• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

khu-khlug / sight-backend / 13616929463

02 Mar 2025 03:40PM UTC coverage: 57.19% (-1.0%) from 58.222%
13616929463

push

github

web-flow
chore: yarn에서 npm으로 롤백 (#105)

637 of 1702 branches covered (37.43%)

Branch coverage included in aggregate %.

1797 of 2554 relevant lines covered (70.36%)

15.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.58
/src/app/application/group/command/modifyGroup/ModifyGroupCommandHandler.ts
1
import {
2
  ForbiddenException,
1✔
3
  Inject,
4
  NotFoundException,
5
  UnprocessableEntityException,
1✔
6
} from '@nestjs/common';
7
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
8

16✔
9
import { Transactional } from '@khlug/core/persistence/transaction/Transactional';
10

11
import {
1✔
12
  ModifyGroupCommand,
1✔
13
  ModifyGroupParams,
1✔
14
} from '@khlug/app/application/group/command/modifyGroup/ModifyGroupCommand';
1✔
15
import { ModifyGroupCommandResult } from '@khlug/app/application/group/command/modifyGroup/ModifyGroupCommandResult';
1✔
16

1✔
17
import {
1✔
18
  GroupLogger,
1✔
19
  IGroupLogger,
1✔
20
} from '@khlug/app/domain/group/IGroupLogger';
1✔
21
import {
1✔
22
  GroupMemberRepository,
1✔
23
  IGroupMemberRepository,
24
} from '@khlug/app/domain/group/IGroupMemberRepository';
32!
25
import {
×
26
  GroupRepository,
27
  IGroupRepository,
28
} from '@khlug/app/domain/group/IGroupRepository';
29
import { GroupCategory } from '@khlug/app/domain/group/model/constant';
30
import { Group } from '@khlug/app/domain/group/model/Group';
31
import {
32
  IInterestRepository,
32✔
33
  InterestRepository,
34
} from '@khlug/app/domain/interest/IInterestRepository';
32✔
35

36
import { Message } from '@khlug/constant/message';
37
import { isDifferentStringArray } from '@khlug/util/isDifferentStringArray';
2!
38

2!
39
type UpdatedItem =
×
40
  | 'category'
2✔
41
  | 'title'
42
  | 'purpose'
43
  | 'interests'
5!
44
  | 'technology'
45
  | 'grade'
46
  | 'repository'
4✔
47
  | 'allowJoin';
4✔
48

49
const updatedItemToKorean: Record<UpdatedItem, string> = {
50
  category: '분류',
1✔
51
  title: '그룹 이름',
52
  purpose: '목표',
53
  interests: 'IT 분야',
54
  technology: '기술',
55
  grade: '공개 범위',
56
  repository: '저장소',
57
  allowJoin: '참여 신청 허용 여부',
58
};
59

60
@CommandHandler(ModifyGroupCommand)
61
export class ModifyGroupCommandHandler
62
  implements ICommandHandler<ModifyGroupCommand, ModifyGroupCommandResult>
8✔
63
{
8✔
64
  constructor(
8✔
65
    @Inject(GroupRepository)
7✔
66
    private readonly groupRepository: IGroupRepository,
6✔
67
    @Inject(GroupMemberRepository)
5✔
68
    private readonly groupMemberRepository: IGroupMemberRepository,
4✔
69
    @Inject(InterestRepository)
3✔
70
    private readonly interestRepository: IInterestRepository,
3✔
71
    @Inject(GroupLogger)
1✔
72
    private readonly groupLogger: IGroupLogger,
73
  ) {}
2✔
74

2✔
75
  @Transactional()
2✔
76
  async execute(
2✔
77
    command: ModifyGroupCommand,
2✔
78
  ): Promise<ModifyGroupCommandResult> {
2✔
79
    const { groupId, requesterUserId, params } = command;
2✔
80
    const {
2✔
81
      category,
2✔
82
      title,
2✔
83
      purpose,
2✔
84
      interestIds,
2✔
85
      technology,
86
      grade,
87
      repository,
8✔
88
      allowJoin,
8✔
89
    } = params;
1✔
90

91
    const group = await this.getGroupOrThrow(groupId);
7✔
92

93
    this.checkGroupAdmin(group, requesterUserId);
94
    await this.checkGroupMember(group, requesterUserId);
95

7✔
96
    this.checkGroupEditable(group);
1✔
97
    await this.checkInterestExists(interestIds);
98

6✔
99
    const updatedItems = this.diffUpdatedItem(group, params);
1✔
100
    if (updatedItems.length === 0) {
101
      return new ModifyGroupCommandResult(group);
102
    }
103

6✔
104
    group.updateCategory(category);
6✔
105
    group.updateTitle(title);
1✔
106
    group.updatePurpose(purpose);
107
    group.updateInterestIds(interestIds);
108
    group.updateTechnology(technology);
109
    group.updateGrade(grade);
5✔
110
    group.updateRepository(repository);
1✔
111
    group.updateAllowJoin(allowJoin);
112
    await this.groupRepository.save(group);
113

114
    const message = this.buildMessage(group, updatedItems);
4✔
115
    await this.groupLogger.log(groupId, message);
4✔
116

4✔
117
    return new ModifyGroupCommandResult(group);
1✔
118
  }
119

120
  private async getGroupOrThrow(groupId: string): Promise<Group> {
121
    const group = await this.groupRepository.findById(groupId);
3✔
122
    if (!group) {
3✔
123
      throw new NotFoundException(Message.GROUP_NOT_FOUND);
3✔
124
    }
3✔
125
    return group;
3✔
126
  }
3✔
127

3✔
128
  private checkGroupAdmin(group: Group, userId: number): void {
3✔
129
    // 운영진 간 계층을 나타내지 않기 위해 운영 그룹은 그룹장 개념이 없음
3✔
130
    if (group.category === GroupCategory.MANAGE) {
3✔
131
      return;
132
    }
133

15✔
134
    if (group.adminUserId !== userId) {
2✔
135
      throw new ForbiddenException(Message.ONLY_GROUP_ADMIN_CAN_EDIT_GROUP);
136
    }
137
  }
8✔
138

8✔
139
  private async checkGroupMember(group: Group, userId: number): Promise<void> {
8✔
140
    const groupMember = await this.groupMemberRepository.findByGroupIdAndUserId(
8✔
141
      group.id,
8✔
142
      userId,
8✔
143
    );
8✔
144

8✔
145
    if (!groupMember) {
146
      throw new ForbiddenException(Message.REQUESTER_NOT_JOINED_GROUP);
147
    }
1✔
148
  }
149

150
  private checkGroupEditable(group: Group): void {
151
    if (!group.isEditable()) {
1!
152
      throw new UnprocessableEntityException(Message.GROUP_NOT_EDITABLE);
153
    }
154
  }
155

1✔
156
  private async checkInterestExists(interestIds: string[]): Promise<void> {
157
    const uniqueInterestIds = Array.from(new Set(interestIds));
158

159
    const uniqueInterests =
160
      await this.interestRepository.findByIds(uniqueInterestIds);
161
    if (uniqueInterestIds.length !== uniqueInterests.length) {
162
      throw new NotFoundException(Message.SOME_INTERESTS_NOT_FOUND);
163
    }
1!
164
  }
1!
165

1!
166
  private diffUpdatedItem(
1!
167
    group: Group,
168
    params: ModifyGroupParams,
169
  ): UpdatedItem[] {
170
    const updatedItems: UpdatedItem[] = [];
171

172
    if (group.category !== params.category) updatedItems.push('category');
173

174
    if (group.title !== params.title) updatedItems.push('title');
175

176
    if (group.purpose !== params.purpose) updatedItems.push('purpose');
177

178
    if (isDifferentStringArray(group.interestIds, params.interestIds))
179
      updatedItems.push('interests');
180

181
    if (isDifferentStringArray(group.technology, params.technology))
182
      updatedItems.push('technology');
183

184
    if (group.grade !== params.grade) updatedItems.push('grade');
185

186
    if (group.repository !== params.repository) updatedItems.push('repository');
187

188
    if (group.allowJoin !== params.allowJoin) updatedItems.push('allowJoin');
189

190
    return updatedItems;
191
  }
192

193
  private buildMessage(group: Group, updatedItems: UpdatedItem[]): string {
194
    const updateItemsString = updatedItems
195
      .map((item) => updatedItemToKorean[item])
196
      .join(', ');
197
    return `${group.title} 그룹의 ${updateItemsString}이(가) 수정되었습니다.`;
198
  }
199
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc