Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add endpoint and button to delete contexts #511

Merged
merged 2 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading