Skip to content

Commit

Permalink
add endpoint and button to delete contexts (#511)
Browse files Browse the repository at this point in the history
* add endpoint and button to delete contexts

---------

Co-authored-by: Mailn Nifeli Snieske <[email protected]>
  • Loading branch information
malinnsnieske and Mailn Nifeli Snieske authored Dec 2, 2024
1 parent 81a4639 commit c61b200
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 13 deletions.
10 changes: 10 additions & 0 deletions backend/src/main/kotlin/no/bekk/database/ContextRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ object ContextRepository {
}
}
}

fun deleteContext(id: String): Boolean {
val sqlStatementContext = "DELETE FROM contexts WHERE id = ?"
Database.getConnection().use { conn ->
conn.prepareStatement(sqlStatementContext).use { statement ->
statement.setObject(1, UUID.fromString(id))
return statement.executeUpdate() > 0
}
}
}
}

class UniqueConstraintViolationException(message: String) : RuntimeException(message)
34 changes: 22 additions & 12 deletions backend/src/main/kotlin/no/bekk/routes/ContextRouting.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import no.bekk.authentication.hasContextAccess
import no.bekk.authentication.hasTeamAccess
import no.bekk.database.AnswerRepository
import no.bekk.database.ContextRepository
import no.bekk.database.DatabaseContextRequest
import no.bekk.database.UniqueConstraintViolationException
import no.bekk.database.*
import no.bekk.util.logger

fun Route.contextRouting() {
Expand Down Expand Up @@ -78,17 +75,30 @@ fun Route.contextRouting() {

}

get("/{contextId}") {
logger.debug("Received GET /context with id: ${call.parameters["contextId"]}")
val contextId = call.parameters["contextId"] ?: throw BadRequestException("Missing contextId")
route("/{contextId}") {
get {
logger.debug("Received GET /context with id: ${call.parameters["contextId"]}")
val contextId = call.parameters["contextId"] ?: throw BadRequestException("Missing contextId")

if (!hasContextAccess(call, contextId)) {
call.respond(HttpStatusCode.Forbidden)
if (!hasContextAccess(call, contextId)) {
call.respond(HttpStatusCode.Forbidden)
return@get
}
val context = ContextRepository.getContext(contextId)
call.respond(HttpStatusCode.OK, Json.encodeToString(context))
return@get
}
val context = ContextRepository.getContext(contextId)
call.respond(HttpStatusCode.OK, Json.encodeToString(context))
return@get

delete {
logger.info("Received DELETE /context with id: ${call.parameters["contextId"]}")
val contextId = call.parameters["contextId"] ?: throw BadRequestException("Missing contextId")
if (!hasContextAccess(call, contextId)) {
call.respond(HttpStatusCode.Forbidden)
return@delete
}
ContextRepository.deleteContext(contextId)
call.respondText("Context and its answers and comments were successfully deleted.")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ALTER TABLE answers
DROP CONSTRAINT fk_answers_contexts,
ADD CONSTRAINT fk_answers_contexts
FOREIGN KEY (context_id) REFERENCES contexts(id)
ON DELETE CASCADE;

ALTER TABLE comments
DROP CONSTRAINT fk_comments_contexts,
ADD CONSTRAINT fk_comments_contexts
FOREIGN KEY (context_id) REFERENCES contexts(id)
ON DELETE CASCADE;
66 changes: 66 additions & 0 deletions frontend/beCompliant/src/components/DeleteContextModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
Button,
HStack,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Stack,
Text,
} from '@kvib/react';
import { useDeleteContext } from '../hooks/useDeleteContext';

type Props = {
onOpen: () => void;
onClose: () => void;
isOpen: boolean;
contextId: string;
teamId: string;
};
export function DeleteContextModal({
onClose,
isOpen,
contextId,
teamId,
}: Props) {
const { mutate: deleteContext, isPending: isLoading } = useDeleteContext(
contextId,
teamId,
onClose
);

return (
<Modal onClose={onClose} isOpen={isOpen} isCentered>
<ModalOverlay />
<ModalContent>
<ModalHeader>Slett skjemautfylling</ModalHeader>
<ModalBody>
<Stack>
<Text size="sm">
Er du sikker på at du vil slette skjemautfyllingen?
</Text>
</Stack>
</ModalBody>
<ModalFooter>
<HStack justifyContent="end">
<Button variant="tertiary" colorScheme="blue" onClick={onClose}>
Avbryt
</Button>
<Button
aria-label="Slett utfylling"
variant="primary"
colorScheme="red"
leftIcon="delete"
onClick={() => deleteContext()}
isLoading={isLoading}
>
Slett skjemautfylling
</Button>
</HStack>
</ModalFooter>
</ModalContent>
</Modal>
);
}
43 changes: 43 additions & 0 deletions frontend/beCompliant/src/hooks/useDeleteContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { apiConfig } from '../api/apiConfig';
import { useToast } from '@kvib/react';
import { axiosFetch } from '../api/Fetch';

export function useDeleteContext(
contextId: string,
teamId: string,
onSuccess: () => void
) {
const URL = apiConfig.contexts.byId.url(contextId);
const queryClient = useQueryClient();
const toast = useToast();

return useMutation({
mutationKey: apiConfig.contexts.byId.queryKey(contextId),
mutationFn: () => {
return axiosFetch({
url: URL,
method: 'DELETE',
});
},
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: apiConfig.contexts.forTeam.queryKey(teamId),
});
onSuccess();
},
onError: () => {
const toastId = 'delete-context-error';
if (!toast.isActive(toastId)) {
toast({
id: toastId,
title: 'Å nei!',
description: 'Det har skjedd en feil. Prøv på nytt',
status: 'error',
duration: 5000,
isClosable: true,
});
}
},
});
}
30 changes: 29 additions & 1 deletion frontend/beCompliant/src/pages/FrontPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ import {
StackDivider,
VStack,
Text,
Flex,
useDisclosure,
Button,
} from '@kvib/react';
import { Link as ReactRouterLink } from 'react-router-dom';
import { Page } from '../components/layout/Page';
import { useFetchUserinfo } from '../hooks/useFetchUserinfo';
import { useFetchTeamContexts } from '../hooks/useFetchTeamContexts';
import { useFetchContext } from '../hooks/useFetchContext';
import { useFetchTables } from '../hooks/useFetchTables';
import { DeleteContextModal } from '../components/DeleteContextModal';

const FrontPage = () => {
const {
Expand Down Expand Up @@ -88,6 +92,12 @@ function TeamContexts({ teamId }: { teamId: string }) {
isPending: tablesIsPending,
} = useFetchTables();

const {
isOpen: isDeleteOpen,
onOpen: onDeleteOpen,
onClose: onDeleteClose,
} = useDisclosure();

if (contextsIsPending || tablesIsPending) {
return <Spinner size="xl" />;
}
Expand All @@ -112,7 +122,25 @@ function TeamContexts({ teamId }: { teamId: string }) {
<Text>{table.name}</Text>
<VStack alignItems="start" pl={4}>
{contextsForTable.map((context) => (
<ContextLink key={context.id} contextId={context.id} />
<Flex alignItems="center" key={context.id}>
<ContextLink key={context.id} contextId={context.id} />
<Button
aria-label="Slett utfylling"
colorScheme="gray"
variant="tertiary"
rightIcon="delete"
onClick={() => onDeleteOpen()}
>
Slett
</Button>
<DeleteContextModal
onOpen={onDeleteOpen}
onClose={onDeleteClose}
isOpen={isDeleteOpen}
teamId={teamId}
contextId={context.id}
/>
</Flex>
))}
</VStack>
</VStack>
Expand Down

0 comments on commit c61b200

Please sign in to comment.