-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathnoteSettings.ts
More file actions
226 lines (196 loc) · 7.45 KB
/
noteSettings.ts
File metadata and controls
226 lines (196 loc) · 7.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
import type { NoteInternalId } from '@domain/entities/note.js';
import type { InvitationHash } from '@domain/entities/noteSettings.js';
import type NoteSettings from '@domain/entities/noteSettings.js';
import type NoteSettingsRepository from '@repository/noteSettings.repository.js';
import type TeamRepository from '@repository/team.repository.js';
import type { Team, TeamMember, TeamMemberCreationAttributes } from '@domain/entities/team.js';
import { MemberRole } from '@domain/entities/team.js';
import type User from '@domain/entities/user.js';
import { createInvitationHash } from '@infrastructure/utils/invitationHash.js';
import { DomainError } from '@domain/entities/DomainError.js';
import type { SharedDomainMethods } from './shared/index.js';
import EventBus from '@domain/event-bus/index.js';
import { NOTE_ADDED_EVENT_NAME } from '@domain/event-bus/events/noteAddedEvent.js';
/**
* Service responsible for Note Settings
*/
export default class NoteSettingsService {
/**
* Note Settings repository
*/
public noteSettingsRepository: NoteSettingsRepository;
private readonly teamRepository: TeamRepository;
/**
* Note Settings service constructor
*
* @param noteSettingsRepository - note settings repository
* @param teamRepository - team repository
* @param shared - shared domain methods
*/
constructor(noteSettingsRepository: NoteSettingsRepository, teamRepository: TeamRepository, private readonly shared: SharedDomainMethods) {
this.noteSettingsRepository = noteSettingsRepository;
this.teamRepository = teamRepository;
/**
* Listen to the note related events
*/
EventBus.getInstance().addEventListener(NOTE_ADDED_EVENT_NAME, async (event) => {
const { noteId, userId } = (event as CustomEvent<{ noteId: number; userId: number }>).detail;
try {
await this.addNoteSettings(noteId);
await this.createTeamMember({
noteId: noteId,
userId: userId,
role: MemberRole.Write,
});
} catch (error) {
throw error;
}
});
}
/**
* Add user to the team by invitation hash
*
* @param invitationHash - hash for joining to the team
* @param userId - user to add
*/
public async addUserToTeamByInvitationHash(invitationHash: InvitationHash, userId: User['id']): Promise<TeamMember | null> {
const defaultUserRole = MemberRole.Read;
const noteSettings = await this.noteSettingsRepository.getNoteSettingsByInvitationHash(invitationHash);
/**
* Check if invitation hash is valid
*/
if (noteSettings === null) {
throw new DomainError(`Wrong invitation`);
}
/**
* Check if user not already in team
*/
const isUserTeamMember = await this.teamRepository.isUserInTeam(userId, noteSettings.noteId);
if (isUserTeamMember) {
throw new DomainError(`User already in team`);
}
return await this.teamRepository.createTeamMembership({
noteId: noteSettings.noteId,
userId,
role: defaultUserRole,
});
}
/**
* Returns settings for a note with all team members
*
* @param id - note internal id
*/
public async getNoteSettingsByNoteId(id: NoteInternalId): Promise<NoteSettings> {
const noteSettings = await this.noteSettingsRepository.getNoteSettingsByNoteId(id);
if (noteSettings === null) {
throw new DomainError(`Note settings not found`);
}
noteSettings.team = await this.teamRepository.getTeamMembersByNoteId(id);
return noteSettings;
}
/**
* Adds note settings
*
* @param noteId - note id
* @param isPublic - is note public
* @returns added note settings
*/
public async addNoteSettings(noteId: NoteInternalId, isPublic: boolean = true): Promise<NoteSettings> {
return await this.noteSettingsRepository.addNoteSettings({
noteId: noteId,
isPublic: isPublic,
invitationHash: createInvitationHash(),
});
}
/**
* Partially updates note settings
*
* @param noteId - note internal id
* @param data - note settings data with new values
* @returns updated note settings
*/
public async patchNoteSettingsByNoteId(noteId: NoteInternalId, data: Partial<NoteSettings>): Promise<NoteSettings | null> {
const noteSettings = await this.noteSettingsRepository.getNoteSettingsByNoteId(noteId);
if (noteSettings === null) {
throw new DomainError(`Note settings not found`);
}
return await this.noteSettingsRepository.patchNoteSettingsById(noteSettings.id, data);
}
/**
* Get user role in team by user id and note id
* If user is not a member of note, return null
*
* @param userId - user id to check his role
* @param noteId - note id where user should have role
*/
public async getUserRoleByUserIdAndNoteId(userId: User['id'], noteId: NoteInternalId): Promise<MemberRole | undefined> {
const team = await this.getInheritedTeamByNoteId(noteId);
return team.find(teamMember => teamMember.userId === userId)?.role;
}
/**
* Get all team members by note id
*
* @param noteId - note id to get all team members
* @returns team members
*/
public async getInheritedTeamByNoteId(noteId: NoteInternalId): Promise<Team> {
let team = await this.teamRepository.getInheritedTeamByNoteId(noteId);
let parentId = await this.shared.note.getParentNoteIdByNoteId(noteId);
/**
* team.length === 1 means that team contains only creator or owner, it means that team is not specified by user
* parentId === null means that note has no parent to inherit its team
*/
while (team.length === 1 && parentId !== null) {
team = await this.teamRepository.getInheritedTeamByNoteId(parentId);
parentId = await this.shared.note.getParentNoteIdByNoteId(parentId);
}
return team;
}
/**
* Remove team member by userId and noteId
*
* @param userId - id of team member
* @param noteId - note internal id
* @returns returns userId if team member was deleted and undefined overwise
*/
public async removeTeamMemberByUserIdAndNoteId(userId: TeamMember['id'], noteId: NoteInternalId): Promise<User['id'] | undefined> {
return await this.teamRepository.removeTeamMemberByUserIdAndNoteId(userId, noteId);
}
/**
* Creates team member
*
* @param team - data for team member creation
* @returns created team member
*/
public async createTeamMember(team: TeamMemberCreationAttributes): Promise<TeamMember> {
return await this.teamRepository.createTeamMembership(team);
}
/**
* Updates invitation hash in note settings
*
* @param noteId - note internal id
* @returns updated note settings
*/
public async regenerateInvitationHash(noteId: NoteInternalId): Promise<NoteSettings> {
/**
* Generates a new invitation hash
*/
const data = { invitationHash: createInvitationHash() };
const updatedNoteSettings = await this.patchNoteSettingsByNoteId(noteId, data);
if (updatedNoteSettings === null) {
throw new DomainError(`Note settings was not updated`);
}
return updatedNoteSettings;
}
/**
* Patch team member role by user and note id
*
* @param id - userId of team member
* @param noteId - note internal id
* @param role - new team member role
* @returns returns 1 if the role has been changed and 0 otherwise
*/
public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role: MemberRole): Promise<MemberRole | undefined> {
return await this.teamRepository.patchMemberRoleByUserId(id, noteId, role);
}
}