WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 96e806f

Browse files
authored
✨ feature: Cleanup features (#174)
* 🚧 wip * ✨ feat: Cleanup Features * 🧼 format & lint --------- Co-authored-by: Tade Strehk <[email protected]>
1 parent 21b2754 commit 96e806f

File tree

13 files changed

+393
-2
lines changed

13 files changed

+393
-2
lines changed

schema.graphql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,6 +1557,10 @@ type Mutation {
15571557
createOnePaymentTransaction(conferenceId: ID!, paymentFor: [ID!]!, userId: ID!): PaymentTransaction!
15581558
createOneRoleApplication(delegationId: ID!, nationId: ID, nonStateActorId: ID): RoleApplication!
15591559
createOneSingleParticipant(conferenceId: ID!, experience: String, motivation: String, roleId: ID!, school: String): SingleParticipant!
1560+
deleteDeadDelegationMembers(conferenceId: ID!): [DelegationMember!]!
1561+
deleteDeadSingleParticipants(conferenceId: ID!): [SingleParticipant!]!
1562+
deleteDeadSupervisors(conferenceId: ID!): [ConferenceSupervisor!]!
1563+
deleteEmptyDelegations(conferenceId: ID!): [Delegation!]!
15601564
deleteOneCommittee(where: CommitteeWhereUniqueInput!): Committee
15611565
deleteOneConference(where: ConferenceWhereUniqueInput!): Conference
15621566
deleteOneConferenceParticipantStatus(where: ConferenceParticipantStatusWhereUniqueInput!): ConferenceParticipantStatus

src/api/resolvers/modules/conferenceSupervisor.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,49 @@ builder.mutationFields((t) => {
185185
})
186186
};
187187
});
188+
189+
builder.mutationFields((t) => {
190+
return {
191+
// dead = not assigned to any role
192+
deleteDeadSupervisors: t.prismaField({
193+
type: ['ConferenceSupervisor'],
194+
args: {
195+
conferenceId: t.arg.id()
196+
},
197+
resolve: async (query, root, args, ctx) => {
198+
return db.$transaction(async (tx) => {
199+
const where = {
200+
conferenceId: args.conferenceId,
201+
OR: [
202+
{
203+
delegations: {
204+
none: {}
205+
}
206+
},
207+
{
208+
delegations: {
209+
every: {
210+
assignedNation: null,
211+
assignedNonStateActor: null
212+
}
213+
}
214+
}
215+
],
216+
AND: [ctx.permissions.allowDatabaseAccessTo('delete').ConferenceSupervisor]
217+
};
218+
219+
const res = await tx.conferenceSupervisor.findMany({
220+
...query,
221+
where
222+
});
223+
224+
await db.conferenceSupervisor.deleteMany({
225+
where
226+
});
227+
228+
return res;
229+
});
230+
}
231+
})
232+
};
233+
});

src/api/resolvers/modules/delegation.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,3 +378,37 @@ builder.queryFields((t) => {
378378
})
379379
};
380380
});
381+
382+
builder.mutationFields((t) => {
383+
return {
384+
// empty = no members
385+
deleteEmptyDelegations: t.prismaField({
386+
type: ['Delegation'],
387+
args: {
388+
conferenceId: t.arg.id()
389+
},
390+
resolve: async (query, root, args, ctx) => {
391+
return db.$transaction(async (tx) => {
392+
const where = {
393+
members: {
394+
none: {}
395+
},
396+
conferenceId: args.conferenceId,
397+
AND: [ctx.permissions.allowDatabaseAccessTo('delete').Delegation]
398+
};
399+
400+
const res = await tx.delegation.findMany({
401+
...query,
402+
where
403+
});
404+
405+
await db.delegation.deleteMany({
406+
where
407+
});
408+
409+
return res;
410+
});
411+
}
412+
})
413+
};
414+
});

src/api/resolvers/modules/delegationMember.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,39 @@ builder.mutationFields((t) => {
268268
})
269269
};
270270
});
271+
272+
builder.mutationFields((t) => {
273+
return {
274+
// dead = not assigned to any role
275+
deleteDeadDelegationMembers: t.prismaField({
276+
type: ['DelegationMember'],
277+
args: {
278+
conferenceId: t.arg.id()
279+
},
280+
resolve: async (query, root, args, ctx) => {
281+
return db.$transaction(async (tx) => {
282+
const where = {
283+
assignedCommittee: null,
284+
delegation: {
285+
assignedNation: null,
286+
assignedNonStateActor: null
287+
},
288+
conferenceId: args.conferenceId,
289+
AND: [ctx.permissions.allowDatabaseAccessTo('delete').DelegationMember]
290+
};
291+
292+
const res = await tx.delegationMember.findMany({
293+
...query,
294+
where
295+
});
296+
297+
await db.delegationMember.deleteMany({
298+
where
299+
});
300+
301+
return res;
302+
});
303+
}
304+
})
305+
};
306+
});

src/api/resolvers/modules/singleParticipant.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,3 +251,35 @@ builder.mutationFields((t) => {
251251
})
252252
};
253253
});
254+
255+
builder.mutationFields((t) => {
256+
return {
257+
// dead = not assigned to any role
258+
deleteDeadSingleParticipants: t.prismaField({
259+
type: ['SingleParticipant'],
260+
args: {
261+
conferenceId: t.arg.id()
262+
},
263+
resolve: async (query, root, args, ctx) => {
264+
return db.$transaction(async (tx) => {
265+
const where = {
266+
assignedRole: null,
267+
conferenceId: args.conferenceId,
268+
AND: [ctx.permissions.allowDatabaseAccessTo('delete').SingleParticipant]
269+
};
270+
271+
const res = await tx.singleParticipant.findMany({
272+
...query,
273+
where
274+
});
275+
276+
await db.singleParticipant.deleteMany({
277+
where
278+
});
279+
280+
return res;
281+
});
282+
}
283+
})
284+
};
285+
});

src/i18n/de.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,25 @@
570570
"general": "Allgemeines",
571571
"communication": "Kommunikation",
572572
"conferenceStatusDescription": "<p>Der Konferenzstatus bestimmt, was die Teilnehmenden in der App sehen und tun können. Er sollte immer nur \"vorwärts\" geändert werden. Auch wenn bei einem Rückschritt keine Daten verloren gehen, kann es doch einerseits zu Verwirrungen bei den Teilnehmenden führen und andererseits in der App zu unerwartetem Verhalten führen.</p><p>Der Konferenzstatus orientiert sich an den Phasen einer Konferenz. Die Möglichkeiten sind:</p><ul><li><strong>Vor Anmeldestart</strong>: Die Konferenz wurde angelegt,die Anmeldephase aber noch nicht gestartet. Es können alle Vorbereitungen getroffen werden, die Anmeldung ist aber noch nicht möglich und die Konferenz nach außen hin auch noch nicht sichtbar.</li><li><strong>Anmeldephase</strong>: Die Anmeldung ist möglich, bis das Datum für das Ende der Anmeldung erreicht ist. Dann wird die Anmeldephase automatisch beendet. Nun kann die Zuteilung vorgenommen und auch schon angewendet werden. Solange der Konferenzstatus noch \"Anmeldephase\"ist,können die Teilnehmenden ihre Zuweisung noch nicht sehen.</li><li><strong>Vorbereitung</strong>: Wenn die Zuteilung abgeschlossen istundmandie Ergebnisse bekannt geben möchte, kann der Konferenz status auf \"Vorbereitung\" gesetzt werden. Dieser Status sollte bis zu Beginnder Konferenz bestehen bleiben. Die Teilnehmenden bekommen Informationen zur postalischen Anmeldung und der Beitragszahlung angezeigt und können auf Materialien und Infos (weiter unten) zugreifen.</li><li><strong>Aktive Konferenz</strong>: Dieser Status sollte kurz vor Beginnder Konferenz gesetzt werden.</li><li><strong>Nach der Konferenz</strong>: Nach der Konferenz kann der Status auf \"Nach der Konferenz\" gesetzt werden. Vorher sollte eingetragen werden, wer von den Teilnehmenden anwesend war und ein Zertifikat erhalten soll. Mit diesem Status können die Teilnehmenden ihre Zertifikate herunterladen.</li></ul>",
573+
"cleanup": "Aufräumen",
574+
"cleanupDescription": "Auf dieser Seite kannst du Karteileichen aus den Datenbanken löschen, um einen besseren Überblick über die tatsächlichen Anmeldeverhältnisse zu bekommen.",
575+
"cleanupDelegationMembers": "Delegationsmitglieder aufräumen",
576+
"cleanupDelegationMembersDescription": "Mit einem Klick auf den unteren Button werden alle Delegationsmitglieder, deren Delegationen keine zugewiesenen Rollen haben, aus der Delegation entfernt.",
577+
"cleanupDelegationMembersButton": "Delegationsmitglieder aufräumen",
578+
"cleanupDelegationMembersSuccess": "Delegationsmitglieder wurden erfolgreich aufgeräumt.",
579+
"cleanupIndividualParticipants": "Einzelteilnehmende aufräumen",
580+
"cleanupIndividualParticipantsDescription": "Mit einem Klick auf den unteren Button werden alle Einzelteilnehmende, die keine zugewiesenen Rollen haben, aus der Konferenz entfernt.",
581+
"cleanupIndividualParticipantsButton": "Einzelteilnehmende aufräumen",
582+
"cleanupIndividualParticipantsSuccess": "Einzelteilnehmende wurden erfolgreich aufgeräumt.",
583+
"cleanupDelegations": "Delegationen aufräumen",
584+
"cleanupDelegationsDescription": "Mit einem Klick auf den unteren Button werden alle Delegationen, die keine Mitglieder haben, gelöscht und damit aus der Konferenz entfernt.",
585+
"cleanupDelegationsButton": "Delegationen aufräumen",
586+
"cleanupDelegationsSuccess": "Delegationen wurden erfolgreich aufgeräumt.",
587+
"cleanupSupervisors": "Betreuende aufräumen",
588+
"cleanupSupervisorsDescription": "Mit einem Klick auf den unteren Button werden alle Betreuende, die nicht mindestens eine Delegation mit einer zugewiesenen Rolle betreuen, aus der Konferenz entfernt.",
589+
"cleanupSupervisorsButton": "Betreuende aufräumen",
590+
"cleanupSupervisorsSuccess": "Betreuende wurden erfolgreich aufgeräumt.",
591+
"deleted": "Gelöscht",
573592
"downloadCommitteeData": "Daten für das Gremium \"{committee}\" herunterladen",
574593
"alpha3Code": "ISO Alpha 3 Code"
575594
}

src/i18n/en.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,25 @@
514514
"conferenceStatusParticipantRegistration": "Registration Phase",
515515
"conferenceStatusPreparation": "Preparation Phase",
516516
"conferenceStatusDescription": "<p>The conference status determines what participants can see and do in the app.It should only ever be changed \"forward\". Even though no data is lost whentaking a step back, it can lead to confusion among participants andpotentially cause unexpected behavior in the app.</p><p>The conference status is oriented around the phases of a conference. Thepossibilities are:</p><ul><li><strong>Before registration starts</strong>: The conference has been created,but the registration phase has not yet started. All preparations can bemade, but registration is not yet possible, and the conference is not yetvisible externally.</li><li><strong>Registration Phase</strong>: Registration is possible until the enddate for registration is reached. Then the registration phase isautomatically ended. Now allocation can be made and also already applied. Aslong as the conference status is still \"Registration Phase\", participantscannot yet see their assignment.</li><li><strong>Preparation Phase</strong>: When the allocation is complete and you wantto announce the results, the conference status can be set to \"Preparation Phase\".This status should remain until the beginning of the conference.Participants will be shown information about postal registration andcontribution payment and can access materials and information.</li><li><strong>Active Conference</strong>: This status should be set shortly beforethe start of the conference.</li><li><strong>After the Conference</strong>: After the conference, the status canbe set to \"After the Conference\". Before this, it should be recorded whoamong the participants was present and should receive a certificate. Withthis status, participants can download their certificates.</li></ul>",
517+
"cleanup": "Cleanup",
518+
"cleanupDescription": "On this page you can delete inactive accounts from the databases to get a better overview of the actual registration situation.",
519+
"cleanupDelegationMembers": "Clean up Delegation Members",
520+
"cleanupDelegationMembersDescription": "By clicking on the button below, all delegation members whose delegations do not have assigned roles will be removed from the delegation.",
521+
"cleanupDelegationMembersButton": "Clean up delegation members",
522+
"cleanupDelegationMembersSuccess": "Delegation members have been cleaned up successfully.",
523+
"cleanupIndividualParticipants": "Single Participants Cleanup",
524+
"cleanupIndividualParticipantsDescription": "By clicking on the button below, all single participants who do not have assigned roles will be removed from the conference.",
525+
"cleanupIndividualParticipantsButton": "Clean up single participants",
526+
"cleanupIndividualParticipantsSuccess": "Single participants have been cleaned up successfully.",
527+
"cleanupDelegations": "Clean up Delegations",
528+
"cleanupDelegationsDescription": "By clicking on the button below, all delegations that have no members will be deleted and thus removed from the conference.",
529+
"cleanupDelegationsButton": "Clean up delegations",
530+
"cleanupDelegationsSuccess": "Delegations have been cleaned up successfully.",
531+
"cleanupSupervisors": "Clean up Supervisors",
532+
"cleanupSupervisorsDescription": "By clicking on the button below, all supervisors who do not supervise at least one delegation with an assigned role will be removed from the conference.",
533+
"cleanupSupervisorsButton": "Clean up supervisors",
534+
"cleanupSupervisorsSuccess": "Supervisors have been cleaned up successfully.",
535+
"deleted": "Deleted",
517536
"downloadCommitteeData": "Download committee data for the {committee}",
518537
"alpha3Code": "ISO Alpha 3 Code"
519538
}

src/routes/(authenticated)/Breadcrumbs.svelte

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,11 @@
8787
},
8888
supervisors: {
8989
translation: m.supervisors(),
90-
icon: 'eye'
90+
icon: 'chalkboard-user'
9191
},
9292
plausibility: {
9393
translation: m.adminPlausibility(),
94-
icon: 'question'
94+
icon: 'shield-check'
9595
},
9696
seats: {
9797
translation: m.seats(),
@@ -128,6 +128,10 @@
128128
payments: {
129129
translation: m.payment(),
130130
icon: 'money-bill-transfer'
131+
},
132+
cleanup: {
133+
icon: 'broom',
134+
translation: m.cleanup()
131135
}
132136
};
133137

src/routes/(authenticated)/management/[conferenceId]/+layout.svelte

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@
7676
title={m.payment()}
7777
expanded={navbarExpanded}
7878
/>
79+
<NavMenuButton
80+
href="/management/{data.conferenceId}/cleanup"
81+
icon="fa-broom"
82+
title={m.cleanup()}
83+
expanded={navbarExpanded}
84+
/>
7985
</NavMenuDetails>
8086
</NavMenu>
8187
</SideNavigationDrawer>
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<script lang="ts">
2+
import { graphql } from '$houdini';
3+
import * as m from '$lib/paraglide/messages';
4+
import Modal from './Modal.svelte';
5+
import Section from './Section.svelte';
6+
import type { ModalData } from './types';
7+
8+
let { data } = $props();
9+
10+
let modalData = $state<ModalData>();
11+
12+
const cleanupDelegationMembers = graphql(`
13+
mutation CleanupDelegationMembers($conferenceId: ID!) {
14+
deleteDeadDelegationMembers(conferenceId: $conferenceId) {
15+
id
16+
user {
17+
given_name
18+
family_name
19+
}
20+
}
21+
}
22+
`);
23+
24+
const cleanupIndividualParticipants = graphql(`
25+
mutation CleanupIndividualParticipants($conferenceId: ID!) {
26+
deleteDeadSingleParticipants(conferenceId: $conferenceId) {
27+
id
28+
user {
29+
given_name
30+
family_name
31+
}
32+
}
33+
}
34+
`);
35+
36+
const cleanupDelegations = graphql(`
37+
mutation CleanupDelegations($conferenceId: ID!) {
38+
deleteEmptyDelegations(conferenceId: $conferenceId) {
39+
id
40+
}
41+
}
42+
`);
43+
44+
const cleanupSupervisors = graphql(`
45+
mutation CleanupSupervisors($conferenceId: ID!) {
46+
deleteDeadSupervisors(conferenceId: $conferenceId) {
47+
id
48+
user {
49+
given_name
50+
family_name
51+
}
52+
}
53+
}
54+
`);
55+
56+
function createModalData(
57+
message: string,
58+
result: { user?: { given_name: string; family_name: string }; id?: string }[] | undefined
59+
) {
60+
if (!result) return { message: 'Error - No result', count: 0, detailArray: [] };
61+
return {
62+
message,
63+
count: result.length,
64+
detailArray:
65+
result
66+
.map((item) => (item.user ? `${item.user.given_name} ${item.user.family_name}` : item.id))
67+
.filter((item) => item !== undefined) ?? []
68+
};
69+
}
70+
</script>
71+
72+
<div class="flex w-full flex-col flex-wrap gap-8 p-10">
73+
<div class="flex flex-col gap-2">
74+
<h2 class="text-2xl font-bold">{m.cleanup()}</h2>
75+
<p>{@html m.cleanupDescription()}</p>
76+
</div>
77+
<Section
78+
title={m.cleanupDelegationMembers()}
79+
description={m.cleanupDelegationMembersDescription()}
80+
btnText={m.cleanupDelegationMembersButton()}
81+
action={async () => {
82+
modalData = createModalData(
83+
m.cleanupDelegationMembersSuccess(),
84+
(await cleanupDelegationMembers.mutate({ conferenceId: data.conferenceId }))?.data
85+
?.deleteDeadDelegationMembers
86+
);
87+
}}
88+
/>
89+
<Section
90+
title={m.cleanupIndividualParticipants()}
91+
description={m.cleanupIndividualParticipantsDescription()}
92+
btnText={m.cleanupIndividualParticipantsButton()}
93+
action={async () => {
94+
modalData = createModalData(
95+
m.cleanupIndividualParticipantsSuccess(),
96+
(await cleanupIndividualParticipants.mutate({ conferenceId: data.conferenceId }))?.data
97+
?.deleteDeadSingleParticipants
98+
);
99+
}}
100+
/>
101+
<Section
102+
title={m.cleanupDelegations()}
103+
description={m.cleanupDelegationsDescription()}
104+
btnText={m.cleanupDelegationsButton()}
105+
action={async () => {
106+
modalData = createModalData(
107+
m.cleanupDelegationsSuccess(),
108+
(await cleanupDelegations.mutate({ conferenceId: data.conferenceId }))?.data
109+
?.deleteEmptyDelegations
110+
);
111+
}}
112+
/>
113+
<Section
114+
title={m.cleanupSupervisors()}
115+
description={m.cleanupSupervisorsDescription()}
116+
btnText={m.cleanupSupervisorsButton()}
117+
action={async () => {
118+
modalData = createModalData(
119+
m.cleanupSupervisorsSuccess(),
120+
(await cleanupSupervisors.mutate({ conferenceId: data.conferenceId }))?.data
121+
?.deleteDeadSupervisors
122+
);
123+
}}
124+
/>
125+
</div>
126+
127+
<Modal {modalData} open={!!modalData} closeModal={() => (modalData = undefined)} />

0 commit comments

Comments
 (0)