Skip to content

Commit e0d0f4a

Browse files
feat(all): wip
1 parent 4605263 commit e0d0f4a

File tree

15 files changed

+442
-173
lines changed

15 files changed

+442
-173
lines changed

packages/api/schema.gql

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,12 +244,14 @@ type Mutation {
244244
"""
245245
Marks User to be deleted
246246
"""
247-
markUserForDeletion(id: ID!): UserInterface
247+
adminMarkUserForDeletion(id: ID!): UserInterface
248+
trainerMarkUserForDeletion(id: ID!): UserInterface
248249

249250
"""
250251
Unmarks User from deletion
251252
"""
252-
unmarkUserForDeletion(id: ID!): UserInterface
253+
adminUnMarkUserForDeletion(id: ID!): UserInterface
254+
trainerUnMarkUserForDeletion(id: ID!): UserInterface
253255

254256
"""
255257
Updates Trainee.

packages/api/src/graphql.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ export type GqlMutation = {
164164
_devloginuser?: Maybe<GqlOAuthPayload>;
165165
/** [DEV] Sets the users type. */
166166
_devsetusertype: GqlDevSetUserPayload;
167+
/** Marks User to be deleted */
168+
adminMarkUserForDeletion?: Maybe<GqlUserInterface>;
169+
/** Unmarks User from deletion */
170+
adminUnMarkUserForDeletion?: Maybe<GqlUserInterface>;
167171
/** Claims a Trainee by the current Trainer */
168172
claimTrainee?: Maybe<GqlTrainerTraineePayload>;
169173
/** Creates Admin. */
@@ -196,16 +200,14 @@ export type GqlMutation = {
196200
linkAlexa?: Maybe<GqlUserInterface>;
197201
/** Login via microsoft */
198202
login?: Maybe<GqlOAuthPayload>;
199-
/** Marks User to be deleted */
200-
markUserForDeletion?: Maybe<GqlUserInterface>;
201203
/** Publishes all comments on a report which is identified by the id argument. */
202204
publishAllComments: GqlPublishCommentsPayload;
205+
trainerMarkUserForDeletion?: Maybe<GqlUserInterface>;
206+
trainerUnMarkUserForDeletion?: Maybe<GqlUserInterface>;
203207
/** Unclaims a Trainee by the current Trainer */
204208
unclaimTrainee?: Maybe<GqlTrainerTraineePayload>;
205209
/** Unlink Alexa account */
206210
unlinkAlexa?: Maybe<GqlUserInterface>;
207-
/** Unmarks User from deletion */
208-
unmarkUserForDeletion?: Maybe<GqlUserInterface>;
209211
/** Updates Admin. */
210212
updateAdmin?: Maybe<GqlAdmin>;
211213
/** Updates a comment on a Day which is identified by the id argument. */
@@ -241,6 +243,16 @@ export type GqlMutation_DevsetusertypeArgs = {
241243
};
242244

243245

246+
export type GqlMutationAdminMarkUserForDeletionArgs = {
247+
id: Scalars['ID']['input'];
248+
};
249+
250+
251+
export type GqlMutationAdminUnMarkUserForDeletionArgs = {
252+
id: Scalars['ID']['input'];
253+
};
254+
255+
244256
export type GqlMutationClaimTraineeArgs = {
245257
id: Scalars['ID']['input'];
246258
};
@@ -330,23 +342,23 @@ export type GqlMutationLoginArgs = {
330342
};
331343

332344

333-
export type GqlMutationMarkUserForDeletionArgs = {
345+
export type GqlMutationPublishAllCommentsArgs = {
334346
id: Scalars['ID']['input'];
347+
traineeId: Scalars['ID']['input'];
335348
};
336349

337350

338-
export type GqlMutationPublishAllCommentsArgs = {
351+
export type GqlMutationTrainerMarkUserForDeletionArgs = {
339352
id: Scalars['ID']['input'];
340-
traineeId: Scalars['ID']['input'];
341353
};
342354

343355

344-
export type GqlMutationUnclaimTraineeArgs = {
356+
export type GqlMutationTrainerUnMarkUserForDeletionArgs = {
345357
id: Scalars['ID']['input'];
346358
};
347359

348360

349-
export type GqlMutationUnmarkUserForDeletionArgs = {
361+
export type GqlMutationUnclaimTraineeArgs = {
350362
id: Scalars['ID']['input'];
351363
};
352364

@@ -909,6 +921,8 @@ export type GqlMutateEntryPayloadResolvers<ContextType = Context, ParentType ext
909921
export type GqlMutationResolvers<ContextType = Context, ParentType extends GqlResolversParentTypes['Mutation'] = GqlResolversParentTypes['Mutation']> = ResolversObject<{
910922
_devloginuser?: Resolver<Maybe<GqlResolversTypes['OAuthPayload']>, ParentType, ContextType, RequireFields<GqlMutation_DevloginuserArgs, 'id'>>;
911923
_devsetusertype?: Resolver<GqlResolversTypes['DevSetUserPayload'], ParentType, ContextType, RequireFields<GqlMutation_DevsetusertypeArgs, 'type'>>;
924+
adminMarkUserForDeletion?: Resolver<Maybe<GqlResolversTypes['UserInterface']>, ParentType, ContextType, RequireFields<GqlMutationAdminMarkUserForDeletionArgs, 'id'>>;
925+
adminUnMarkUserForDeletion?: Resolver<Maybe<GqlResolversTypes['UserInterface']>, ParentType, ContextType, RequireFields<GqlMutationAdminUnMarkUserForDeletionArgs, 'id'>>;
912926
claimTrainee?: Resolver<Maybe<GqlResolversTypes['TrainerTraineePayload']>, ParentType, ContextType, RequireFields<GqlMutationClaimTraineeArgs, 'id'>>;
913927
createAdmin?: Resolver<Maybe<GqlResolversTypes['Admin']>, ParentType, ContextType, RequireFields<GqlMutationCreateAdminArgs, 'input'>>;
914928
createCommentOnDay?: Resolver<GqlResolversTypes['CreateCommentPayload'], ParentType, ContextType, RequireFields<GqlMutationCreateCommentOnDayArgs, 'id' | 'text' | 'traineeId'>>;
@@ -925,11 +939,11 @@ export type GqlMutationResolvers<ContextType = Context, ParentType extends GqlRe
925939
getAvatarSignedUrl?: Resolver<Maybe<GqlResolversTypes['String']>, ParentType, ContextType, RequireFields<GqlMutationGetAvatarSignedUrlArgs, 'id'>>;
926940
linkAlexa?: Resolver<Maybe<GqlResolversTypes['UserInterface']>, ParentType, ContextType, RequireFields<GqlMutationLinkAlexaArgs, 'code' | 'state'>>;
927941
login?: Resolver<Maybe<GqlResolversTypes['OAuthPayload']>, ParentType, ContextType, RequireFields<GqlMutationLoginArgs, 'email'>>;
928-
markUserForDeletion?: Resolver<Maybe<GqlResolversTypes['UserInterface']>, ParentType, ContextType, RequireFields<GqlMutationMarkUserForDeletionArgs, 'id'>>;
929942
publishAllComments?: Resolver<GqlResolversTypes['PublishCommentsPayload'], ParentType, ContextType, RequireFields<GqlMutationPublishAllCommentsArgs, 'id' | 'traineeId'>>;
943+
trainerMarkUserForDeletion?: Resolver<Maybe<GqlResolversTypes['UserInterface']>, ParentType, ContextType, RequireFields<GqlMutationTrainerMarkUserForDeletionArgs, 'id'>>;
944+
trainerUnMarkUserForDeletion?: Resolver<Maybe<GqlResolversTypes['UserInterface']>, ParentType, ContextType, RequireFields<GqlMutationTrainerUnMarkUserForDeletionArgs, 'id'>>;
930945
unclaimTrainee?: Resolver<Maybe<GqlResolversTypes['TrainerTraineePayload']>, ParentType, ContextType, RequireFields<GqlMutationUnclaimTraineeArgs, 'id'>>;
931946
unlinkAlexa?: Resolver<Maybe<GqlResolversTypes['UserInterface']>, ParentType, ContextType>;
932-
unmarkUserForDeletion?: Resolver<Maybe<GqlResolversTypes['UserInterface']>, ParentType, ContextType, RequireFields<GqlMutationUnmarkUserForDeletionArgs, 'id'>>;
933947
updateAdmin?: Resolver<Maybe<GqlResolversTypes['Admin']>, ParentType, ContextType, RequireFields<GqlMutationUpdateAdminArgs, 'id' | 'input'>>;
934948
updateCommentOnDay?: Resolver<GqlResolversTypes['UpdateCommentPayload'], ParentType, ContextType, RequireFields<GqlMutationUpdateCommentOnDayArgs, 'commentId' | 'id' | 'text' | 'traineeId'>>;
935949
updateCommentOnEntry?: Resolver<GqlResolversTypes['UpdateCommentPayload'], ParentType, ContextType, RequireFields<GqlMutationUpdateCommentOnEntryArgs, 'commentId' | 'id' | 'text' | 'traineeId'>>;

packages/backend/src/permissions.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ export const permissions = shield<unknown, Context>(
8686
// Trainer mutations
8787
claimTrainee: and(authenticated, trainer),
8888
unclaimTrainee: and(authenticated, trainer),
89+
trainerMarkUserForDeletion: and(authenticated, admin),
90+
trainerUnMarkUserForDeletion: and(authenticated, admin),
8991

9092
// Trainer and Admin mutations
9193
createTrainee: and(authenticated, or(admin, trainer)),
@@ -94,8 +96,8 @@ export const permissions = shield<unknown, Context>(
9496
updateTrainee: and(authenticated, admin),
9597
createTrainer: and(authenticated, admin),
9698
updateTrainer: and(authenticated, admin),
97-
markUserForDeletion: and(authenticated, admin),
98-
unmarkUserForDeletion: and(authenticated, admin),
99+
adminMarkUserForDeletion: and(authenticated, admin),
100+
adminUnMarkUserForDeletion: and(authenticated, admin),
99101
},
100102
},
101103
{ allowExternalErrors: true }

packages/backend/src/resolvers/admin.resolver.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export const adminResolver: GqlResolvers<AdminContext> = {
6666
},
6767
},
6868
Mutation: {
69-
markUserForDeletion: async (_parent, { id }, { currentUser }) => {
69+
adminMarkUserForDeletion: async (_parent, { id }, { currentUser }) => {
7070
const user = await userById(id)
7171

7272
if (!user) {
@@ -85,7 +85,7 @@ export const adminResolver: GqlResolvers<AdminContext> = {
8585

8686
return user
8787
},
88-
unmarkUserForDeletion: async (_parent, { id }, { currentUser }) => {
88+
adminUnMarkUserForDeletion: async (_parent, { id }, { currentUser }) => {
8989
const user = await userById(id)
9090

9191
if (!user) {

packages/backend/src/resolvers/trainer.resolver.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ import { GqlResolvers, TrainerContext } from '@lara/api'
44

55
import { reportByYearAndWeek } from '../repositories/report.repo'
66
import { allTrainees, traineeById, traineesByTrainerId } from '../repositories/trainee.repo'
7-
import { updateUser } from '../repositories/user.repo'
7+
import { updateUser, userById } from '../repositories/user.repo'
88
import { alexaSkillLinked } from '../services/alexa.service'
99
import { createT } from '../i18n'
10+
import { addMonths } from 'date-fns'
11+
import { isTrainee } from 'src/permissions'
12+
import { sendDeletionMail } from 'src/services/email.service'
1013

1114
export const trainerResolver: GqlResolvers<TrainerContext> = {
1215
Trainer: {
@@ -77,5 +80,55 @@ export const trainerResolver: GqlResolvers<TrainerContext> = {
7780
trainer: currentUser,
7881
}
7982
},
83+
84+
trainerMarkUserForDeletion: async (_parent, { id }, { currentUser }) => {
85+
const user = await userById(id)
86+
const t = createT(currentUser.language)
87+
88+
if (!user) {
89+
throw new GraphQLError(t('errors.missingUser'))
90+
}
91+
92+
if (user.id === currentUser.id) {
93+
throw new GraphQLError(t('errors.cantDeleteYourself'))
94+
}
95+
96+
if (!isTrainee(user)) {
97+
throw new GraphQLError(t('errors.insufficientPermissions'))
98+
}
99+
100+
// // Prüfe ob der Trainer der Trainer des Trainees ist
101+
// if (user.trainerId !== currentUser.id) {
102+
// throw new GraphQLError(t('errors.cantDeleteOtherTrainersTrainee'))
103+
// }
104+
105+
user.deleteAt = addMonths(new Date(), 3).toISOString()
106+
107+
await updateUser(user, { updateKeys: ['deleteAt'] })
108+
109+
await sendDeletionMail(user)
110+
111+
return user
112+
},
113+
114+
trainerUnMarkUserForDeletion: async (_parent, { id }, { currentUser }) => {
115+
const user = await userById(id)
116+
const t = createT(currentUser.language)
117+
118+
if (!user) {
119+
throw new GraphQLError(t('errors.missingUser'))
120+
}
121+
122+
if (!isTrainee(user)) {
123+
throw new GraphQLError(t('errors.insufficientPermissions'))
124+
}
125+
126+
// // Prüfe ob der Trainer der Trainer des Trainees ist
127+
// if (user.trainerId !== currentUser.id) {
128+
// throw new GraphQLError(t('errors.cantUnmarkOtherTrainersTrainee'))
129+
// }
130+
131+
return updateUser(user, { removeKeys: ['deleteAt'] })
132+
},
80133
},
81134
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import React from 'react'
2+
import { H1, Paragraph, Spacings, Flex, Box } from '@lara/components'
3+
import { PrimaryButton, SecondaryButton } from './button'
4+
import Modal from './modal'
5+
import { useToastContext } from '../hooks/use-toast-context'
6+
import { GraphQLError } from 'graphql'
7+
import strings from '../locales/localization'
8+
9+
interface DeletionModalProps {
10+
show: boolean
11+
onClose: () => void
12+
onConfirm: () => Promise<unknown>
13+
userName: string
14+
loading?: boolean
15+
}
16+
17+
export const DeletionModal: React.FC<DeletionModalProps> = ({
18+
show,
19+
onClose,
20+
onConfirm,
21+
userName,
22+
loading = false,
23+
}) => {
24+
const { addToast } = useToastContext()
25+
26+
const handleConfirm = () => {
27+
onConfirm()
28+
.then(() => {
29+
onClose()
30+
addToast({
31+
icon: 'PersonAttention',
32+
title: strings.userDelete.title,
33+
text: strings.userDelete.description,
34+
type: 'error',
35+
})
36+
})
37+
.catch((exception: GraphQLError) => {
38+
addToast({
39+
title: strings.errors.error,
40+
text: exception.message,
41+
type: 'error',
42+
})
43+
})
44+
}
45+
46+
return (
47+
<Modal show={show} customClose handleClose={onClose}>
48+
<H1 noMargin>{strings.formatString(strings.deleteTrainer.title, userName)}</H1>
49+
<Paragraph margin={`${Spacings.l}`} color="darkFont">
50+
{strings.deleteTrainer.description}
51+
</Paragraph>
52+
<Flex justifyContent="flex-end">
53+
<Box pr={'1'}>
54+
<SecondaryButton ghost onClick={onClose} disabled={loading}>
55+
{strings.cancel}
56+
</SecondaryButton>
57+
</Box>
58+
<Box pl={'1'}>
59+
<PrimaryButton danger onClick={handleConfirm}>
60+
{strings.deactivate}
61+
</PrimaryButton>
62+
</Box>
63+
</Flex>
64+
</Modal>
65+
)
66+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { SecondaryButton } from './button'
2+
import strings from '../locales/localization'
3+
import React from 'react'
4+
import {
5+
useAdminMarkUserForDeleteMutation,
6+
useAdminUnmarkUserForDeleteMutation,
7+
useTrainerMarkUserForDeleteMutation,
8+
useTrainerUnmarkUserForDeleteMutation,
9+
} from '../graphql'
10+
interface UseDeleteActionsProps {
11+
//id = routesId
12+
id: string | undefined
13+
currentUserId: string
14+
}
15+
16+
interface MutationVariables {
17+
variables: {
18+
id: string
19+
}
20+
}
21+
22+
export const useDeleteActions = ({ currentUserId, id }: UseDeleteActionsProps) => {
23+
const vars = { variables: { id: id ?? '' } }
24+
25+
const [markForDeleteAdmin, { loading: deleteLoadingAdmin }] = useTrainerMarkUserForDeleteMutation()
26+
const [unmarkDeleteAdmin, { loading: undeleteLoadingAdmin }] = useTrainerUnmarkUserForDeleteMutation()
27+
const [markForDeleteTrainer, { loading: deleteLoadingTrainer }] = useAdminMarkUserForDeleteMutation()
28+
const [unmarkDeleteTrainer, { loading: undeleteLoadingTrainer }] = useAdminUnmarkUserForDeleteMutation()
29+
30+
const isTrainer = currentUserId === '456'
31+
const isAdmin = currentUserId === '789'
32+
33+
const deleteActionLoading = isTrainer
34+
? deleteLoadingTrainer || undeleteLoadingTrainer
35+
: deleteLoadingAdmin || undeleteLoadingAdmin
36+
37+
const [showDeletionModal, setShowDeletionModal] = React.useState(false)
38+
39+
const toggleDeletionModal = () => {
40+
setShowDeletionModal(!showDeletionModal)
41+
}
42+
43+
const selectQueryForType = (vars: MutationVariables) => {
44+
//Trainer
45+
if (isTrainer) {
46+
unmarkDeleteTrainer(vars)
47+
} else if (isAdmin) {
48+
unmarkDeleteAdmin(vars)
49+
}
50+
}
51+
52+
const renderDeleteAction = (deleteAt?: string) => {
53+
if (currentUserId === id) return <></>
54+
if (deleteAt) {
55+
return (
56+
<SecondaryButton disabled={deleteActionLoading} onClick={() => selectQueryForType(vars)}>
57+
{strings.unmarkDelete}
58+
</SecondaryButton>
59+
)
60+
}
61+
62+
return (
63+
<SecondaryButton danger disabled={deleteActionLoading} onClick={() => toggleDeletionModal()}>
64+
{strings.markDelete}
65+
</SecondaryButton>
66+
)
67+
}
68+
69+
return {
70+
renderDeleteAction,
71+
toggleDeletionModal,
72+
showDeletionModal,
73+
markForDeleteTrainer,
74+
markForDeleteAdmin,
75+
}
76+
}

0 commit comments

Comments
 (0)