diff --git a/docker-compose.yml b/docker-compose.yml index 5f378e6..33b3b86 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,6 @@ services: postgresql: image: postgres:17-alpine container_name: projekt-arendehantering-postgres - profiles: ["postgres"] environment: POSTGRES_DB: arende POSTGRES_USER: arende diff --git a/src/main/java/org/example/projektarendehantering/infrastructure/persistence/CaseEntity.java b/src/main/java/org/example/projektarendehantering/infrastructure/persistence/CaseEntity.java index 2d57c78..c058a00 100644 --- a/src/main/java/org/example/projektarendehantering/infrastructure/persistence/CaseEntity.java +++ b/src/main/java/org/example/projektarendehantering/infrastructure/persistence/CaseEntity.java @@ -1,8 +1,7 @@ package org.example.projektarendehantering.infrastructure.persistence; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; +import jakarta.persistence.*; + import java.time.Instant; import java.util.UUID; @@ -11,6 +10,7 @@ public class CaseEntity { @Id + @GeneratedValue(strategy = GenerationType.UUID) private UUID id; private String status; private UUID ownerId; diff --git a/src/main/java/org/example/projektarendehantering/presentation/dto/CreateCaseForm.java b/src/main/java/org/example/projektarendehantering/presentation/dto/CreateCaseForm.java new file mode 100644 index 0000000..2ed88e2 --- /dev/null +++ b/src/main/java/org/example/projektarendehantering/presentation/dto/CreateCaseForm.java @@ -0,0 +1,23 @@ +package org.example.projektarendehantering.presentation.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public class CreateCaseForm { + + @NotBlank(message = "Title is required") + @Size(max = 200, message = "Title must be under 200 characters") + private String title; + + @NotBlank(message = "Description is required") + @Size(max = 4000, message = "Description must be under 4000 characters") + private String description; + + public CreateCaseForm() {} + + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } +} diff --git a/src/main/java/org/example/projektarendehantering/presentation/web/UiController.java b/src/main/java/org/example/projektarendehantering/presentation/web/UiController.java index 66c79c2..6ef3f47 100644 --- a/src/main/java/org/example/projektarendehantering/presentation/web/UiController.java +++ b/src/main/java/org/example/projektarendehantering/presentation/web/UiController.java @@ -1,31 +1,62 @@ package org.example.projektarendehantering.presentation.web; +import org.example.projektarendehantering.application.service.CaseService; +import org.example.projektarendehantering.presentation.dto.CaseDTO; +import org.example.projektarendehantering.presentation.dto.CreateCaseForm; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; + +import jakarta.validation.Valid; +import java.util.UUID; @Controller public class UiController { + private final CaseService caseService; + + public UiController(CaseService caseService) { + this.caseService = caseService; + } + @GetMapping("/") public String index() { return "index"; } @GetMapping("/ui/cases") - public String listCases() { + public String listCases(Model model) { + model.addAttribute("cases", caseService.getAllCases()); return "cases/list"; } @GetMapping("/ui/cases/new") - public String newCase() { + public String newCase(Model model) { + model.addAttribute("createCaseForm", new CreateCaseForm()); return "cases/new"; } + @PostMapping("/ui/cases/new") + public String createCase(@Valid @ModelAttribute("createCaseForm") CreateCaseForm form, BindingResult result) { + if (result.hasErrors()) { + return "cases/new"; + } + + CaseDTO caseDTO = new CaseDTO(); + caseDTO.setTitle(form.getTitle()); + caseDTO.setDescription(form.getDescription()); + + caseService.createCase(caseDTO); + return "redirect:/ui/cases"; + } + @GetMapping("/ui/cases/{caseId}") - public String caseDetail(@PathVariable String caseId, Model model) { - model.addAttribute("caseId", caseId); + public String caseDetail(@PathVariable UUID caseId, Model model) { + caseService.getCase(caseId).ifPresent(c -> model.addAttribute("case", c)); return "cases/detail"; } } diff --git a/src/main/resources/static/app.js b/src/main/resources/static/app.js deleted file mode 100644 index 72f0529..0000000 --- a/src/main/resources/static/app.js +++ /dev/null @@ -1,123 +0,0 @@ -(() => { - const STORAGE_KEY = "case-ui-auth"; - - function loadAuth() { - try { - const raw = localStorage.getItem(STORAGE_KEY); - if (!raw) return null; - return JSON.parse(raw); - } catch { - return null; - } - } - - function saveAuth(auth) { - localStorage.setItem(STORAGE_KEY, JSON.stringify(auth)); - } - - function ensureDefaultAuth() { - const existing = loadAuth(); - if (existing?.userId && existing?.role) return existing; - const defaultAuth = { - userId: crypto.randomUUID(), - role: "ADMIN", - }; - saveAuth(defaultAuth); - return defaultAuth; - } - - function setAuthInputs(auth) { - const userId = document.getElementById("authUserId"); - const role = document.getElementById("authRole"); - if (userId) userId.value = auth.userId ?? ""; - if (role) role.value = auth.role ?? "ADMIN"; - } - - function bindAuthBar() { - const btn = document.getElementById("authSave"); - if (!btn) return; - - btn.addEventListener("click", () => { - const userId = document.getElementById("authUserId")?.value?.trim(); - const role = document.getElementById("authRole")?.value?.trim(); - - if (!userId) { - alert("X-User-Id is required (UUID)."); - return; - } - if (!role) { - alert("X-Role is required."); - return; - } - - saveAuth({ userId, role }); - alert("Saved headers for API requests."); - }); - } - - async function apiFetch(path, init = {}) { - const auth = ensureDefaultAuth(); - - const headers = new Headers(init.headers || {}); - headers.set("X-User-Id", auth.userId); - headers.set("X-Role", auth.role); - - return fetch(path, { ...init, headers }); - } - - function bindCreateCaseForm() { - const form = document.getElementById("createCaseForm"); - if (!form) return; - - const result = document.getElementById("createCaseResult"); - const createdCaseId = document.getElementById("createdCaseId"); - const openCaseLink = document.getElementById("openCaseLink"); - - const errorPanel = document.getElementById("createCaseError"); - const errorText = document.getElementById("createCaseErrorText"); - - form.addEventListener("submit", async (e) => { - e.preventDefault(); - if (result) result.hidden = true; - if (errorPanel) errorPanel.hidden = true; - - const fd = new FormData(form); - const payload = { - title: String(fd.get("title") || ""), - description: String(fd.get("description") || ""), - }; - - try { - const res = await apiFetch("/cases", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }); - - if (!res.ok) { - const body = await res.text(); - throw new Error(`HTTP ${res.status}\n${body}`); - } - - const json = await res.json(); - const caseId = json.caseId; - - if (createdCaseId) createdCaseId.textContent = caseId; - if (openCaseLink) openCaseLink.href = `/ui/cases/${encodeURIComponent(caseId)}`; - if (result) result.hidden = false; - } catch (err) { - if (errorText) errorText.textContent = String(err?.message || err); - if (errorPanel) errorPanel.hidden = false; - } - }); - } - - document.addEventListener("DOMContentLoaded", () => { - const auth = ensureDefaultAuth(); - setAuthInputs(auth); - bindAuthBar(); - bindCreateCaseForm(); - window.caseUi = { apiFetch }; - }); -})(); - diff --git a/src/main/resources/templates/cases/detail.html b/src/main/resources/templates/cases/detail.html index ec222fb..1179be7 100644 --- a/src/main/resources/templates/cases/detail.html +++ b/src/main/resources/templates/cases/detail.html @@ -1,11 +1,6 @@ -
- - -case-id
-
- This is a placeholder until GET /cases/{id} exists. Once it does, we can populate this view with real data.
-
Case not found.
- This page is ready to be wired up once you add read endpoints (e.g. GET /cases, GET /cases/{id}).
-
/ui/cases/<caseId>
- | Title | +Created | +Action | +
|---|---|---|
| Title | +Date | ++ View + | +
No cases found.