diff --git a/backend/src/main/kotlin/no/bekk/database/ContextRepository.kt b/backend/src/main/kotlin/no/bekk/database/ContextRepository.kt
index ef5bb8fbd..590de161e 100644
--- a/backend/src/main/kotlin/no/bekk/database/ContextRepository.kt
+++ b/backend/src/main/kotlin/no/bekk/database/ContextRepository.kt
@@ -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)
diff --git a/backend/src/main/kotlin/no/bekk/routes/ContextRouting.kt b/backend/src/main/kotlin/no/bekk/routes/ContextRouting.kt
index e11bc95bd..3d0107ea5 100644
--- a/backend/src/main/kotlin/no/bekk/routes/ContextRouting.kt
+++ b/backend/src/main/kotlin/no/bekk/routes/ContextRouting.kt
@@ -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() {
@@ -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.")
+ }
}
}
}
\ No newline at end of file
diff --git a/backend/src/main/resources/db/migration/V12__replace_fk_constraints_for_contexts.sql b/backend/src/main/resources/db/migration/V12__replace_fk_constraints_for_contexts.sql
new file mode 100644
index 000000000..437a38fd2
--- /dev/null
+++ b/backend/src/main/resources/db/migration/V12__replace_fk_constraints_for_contexts.sql
@@ -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;
\ No newline at end of file
diff --git a/frontend/beCompliant/src/components/DeleteContextModal.tsx b/frontend/beCompliant/src/components/DeleteContextModal.tsx
new file mode 100644
index 000000000..e95227422
--- /dev/null
+++ b/frontend/beCompliant/src/components/DeleteContextModal.tsx
@@ -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 (
+
+
+
+ Slett skjemautfylling
+
+
+
+ Er du sikker på at du vil slette skjemautfyllingen?
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/beCompliant/src/hooks/useDeleteContext.ts b/frontend/beCompliant/src/hooks/useDeleteContext.ts
new file mode 100644
index 000000000..3832c97ca
--- /dev/null
+++ b/frontend/beCompliant/src/hooks/useDeleteContext.ts
@@ -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,
+ });
+ }
+ },
+ });
+}
diff --git a/frontend/beCompliant/src/pages/FrontPage.tsx b/frontend/beCompliant/src/pages/FrontPage.tsx
index 16b189e1d..463851432 100644
--- a/frontend/beCompliant/src/pages/FrontPage.tsx
+++ b/frontend/beCompliant/src/pages/FrontPage.tsx
@@ -7,6 +7,9 @@ import {
StackDivider,
VStack,
Text,
+ Flex,
+ useDisclosure,
+ Button,
} from '@kvib/react';
import { Link as ReactRouterLink } from 'react-router-dom';
import { Page } from '../components/layout/Page';
@@ -14,6 +17,7 @@ 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 {
@@ -88,6 +92,12 @@ function TeamContexts({ teamId }: { teamId: string }) {
isPending: tablesIsPending,
} = useFetchTables();
+ const {
+ isOpen: isDeleteOpen,
+ onOpen: onDeleteOpen,
+ onClose: onDeleteClose,
+ } = useDisclosure();
+
if (contextsIsPending || tablesIsPending) {
return ;
}
@@ -112,7 +122,25 @@ function TeamContexts({ teamId }: { teamId: string }) {
{table.name}
{contextsForTable.map((context) => (
-
+
+
+
+
+
))}