Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
321447d
Added case to dashboard-layout.html
MartinStenhagen Apr 20, 2026
8a8d04d
Added case-management.html
MartinStenhagen Apr 20, 2026
23a079e
Added CaseManagementViewController
MartinStenhagen Apr 20, 2026
a6fb2d8
Added case-management.css
MartinStenhagen Apr 20, 2026
3352440
Added registry-list.html
MartinStenhagen Apr 20, 2026
c196d92
Added CaseManagementViewController and new help-methods etc
MartinStenhagen Apr 20, 2026
3c0b83f
Fixed error that prevented case-management from loading properly
MartinStenhagen Apr 20, 2026
6f05362
Added Registry creation
MartinStenhagen Apr 20, 2026
7eb9300
Added Case record creation etc
MartinStenhagen Apr 20, 2026
a4f77b6
UI fixes etc
MartinStenhagen Apr 20, 2026
eb6978f
Fixed some issues
MartinStenhagen Apr 20, 2026
d0313fd
Added support to update caserecords
MartinStenhagen Apr 20, 2026
5d41077
Moved code from CaseManagementViewController to new controllers and a…
MartinStenhagen Apr 21, 2026
d994441
chaged init-localstack.sh to autocreate bucket etc
MartinStenhagen Apr 21, 2026
f7adf67
Added better exception handling for upload and delete files
MartinStenhagen Apr 21, 2026
00deb88
Added sorting to uploaded files
MartinStenhagen Apr 21, 2026
f88b66d
Added sorting to uploaded files and casefile numbering
MartinStenhagen Apr 21, 2026
6e9a18a
Updated tests to match changes in CaseFileService and Repository
MartinStenhagen Apr 21, 2026
8ee59db
Updated and added tests
MartinStenhagen Apr 21, 2026
2e3212f
Added CaseFileViewControllerTest
MartinStenhagen Apr 21, 2026
220874a
Added confidentiality to CaseFile, enum and CaseFileAcess-entity
MartinStenhagen Apr 21, 2026
55aeab9
Added CaseFileAccess + repository and flyway migration
MartinStenhagen Apr 21, 2026
839dc52
Added CaseFileAccessAdminService, CaseFileListItemDto and updated Cas…
MartinStenhagen Apr 21, 2026
50ef8a5
Changes to implement confidentiality to files
MartinStenhagen Apr 21, 2026
a719a19
Added confidentiality to case-file-list.html
MartinStenhagen Apr 21, 2026
0817d76
improvements to UserRole
MartinStenhagen Apr 21, 2026
379e2f9
Added CaseFileAccessServiceTest and changed user role from String to …
MartinStenhagen Apr 21, 2026
d09a658
Added new tests for confidentiality to CaseFileServiceTest
MartinStenhagen Apr 21, 2026
78a3914
Updated old tests in CaseFileControllerTest
MartinStenhagen Apr 21, 2026
9d426e3
Changes after merge with main
MartinStenhagen Apr 21, 2026
913eea3
Merge branch 'main' into feature/frontend-registry-and-caserecord
MartinStenhagen Apr 21, 2026
a2e57e0
More fixes after merge
MartinStenhagen Apr 21, 2026
6a977fd
Fix document number allocation race condition by adding pessimistic l…
MartinStenhagen Apr 21, 2026
37cf746
Reworked casefile-controllers and service to fix coderabbit feedback …
MartinStenhagen Apr 21, 2026
217e963
Reworked upLoadCaseFile and deleteCaseFile and added reloadFileListFr…
MartinStenhagen Apr 21, 2026
1961827
Fixed coderabbit feedback: Don’t re-query the missing entity after ca…
MartinStenhagen Apr 21, 2026
6b6d334
fixed coderabbitfeedback: Keep setters aligned with entity constraint…
MartinStenhagen Apr 21, 2026
26ec276
Flyway migration and changed to CONTENT_TOO_LARGE in exceptionhandler…
MartinStenhagen Apr 22, 2026
474af00
Reworked exception handling after coderabbit feedback
MartinStenhagen Apr 22, 2026
e7ceb5b
Fixed Avoid flooring the displayed upload limit.
MartinStenhagen Apr 22, 2026
6cc03e5
Ui fixes after coderabbit feedback
MartinStenhagen Apr 22, 2026
b3f3925
changed to <form class="registry-form"
MartinStenhagen Apr 22, 2026
e847dbe
Reworked tests after changes to the code and coderabbit feedback
MartinStenhagen Apr 22, 2026
06d0f0d
Removed redundant lines after coderabbit feedback
MartinStenhagen Apr 22, 2026
7b7dcf2
Add CSRF token to the HTMX DELETE request.
MartinStenhagen Apr 22, 2026
b6e4ceb
Fixed Compare the enum by name when selecting the current status.
MartinStenhagen Apr 22, 2026
be43072
removed a second, different AccessDeniedException response contract.
MartinStenhagen Apr 22, 2026
66913d8
Reworked exception handling to address this issue: Catch‑all Exceptio…
MartinStenhagen Apr 22, 2026
7f08b5e
Added missing test annotation
MartinStenhagen Apr 22, 2026
edc0faf
Merge branch 'main' into feature/frontend-registry-and-caserecord
MartinStenhagen Apr 22, 2026
a573c9a
Fixed and issue with missing csrf-token for creating registries and c…
MartinStenhagen Apr 22, 2026
82325d6
Fixed coderabbit-feedback: Associate the owner label with its read-on…
MartinStenhagen Apr 22, 2026
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
8 changes: 7 additions & 1 deletion init-localstack.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
#!/bin/bash
set -euo pipefail

bucket="${AWS_BUCKET_NAME:-team4you-files}"
awslocal s3api head-bucket --bucket "$bucket" 2>/dev/null || awslocal s3 mb "s3://$bucket"
echo "checking/creating s3 bucket: $bucket"

awslocal s3api head-bucket --bucket "$bucket" 2>/dev/null || awslocal s3 mb "s3://$bucket"

echo "available buckets:"
awslocal s3 ls
13 changes: 7 additions & 6 deletions src/main/java/backendlab/team4you/Team4youApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import backendlab.team4you.user.UserRepository;
import backendlab.team4you.user.UserEntity;
import backendlab.team4you.user.UserRole;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
Expand All @@ -25,25 +26,25 @@ ApplicationRunner init(UserRepository repository, BCryptPasswordEncoder encoder)

UserEntity devAdmin = new UserEntity(
Bytes.fromBase64("YWRtaW4="),
"dev", // name (username)
"admin" // displayName
"dev",
"admin"
);

devAdmin.setPasswordHash(encoder.encode("123456"));
devAdmin.setRole("ROLE_ADMIN");
devAdmin.setRole(UserRole.ADMIN);
devAdmin.setEmail("devadmin@gmail.com");

repository.save(devAdmin);
System.out.println("✅ Admin created");

UserEntity devUser = new UserEntity(
Bytes.fromBase64("dXNlcg=="),
"user", // name (username)
"user" // displayName
"user",
"user"
);

devUser.setPasswordHash(encoder.encode("1234"));
devUser.setRole("ROLE_USER");
devUser.setRole(UserRole.USER);
devUser.setEmail("devuser@gmail.com");

repository.save(devUser);
Expand Down
45 changes: 44 additions & 1 deletion src/main/java/backendlab/team4you/casefile/CaseFile.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package backendlab.team4you.casefile;

import backendlab.team4you.caserecord.CaseRecord;
import backendlab.team4you.common.ConfidentialityLevel;
import jakarta.persistence.*;

import java.time.LocalDateTime;
Expand All @@ -9,7 +10,11 @@
@Table(
name = "case_file",
uniqueConstraints = {
@UniqueConstraint(name = "uk_case_file_s3_key", columnNames = "s3_key")
@UniqueConstraint(name = "uk_case_file_s3_key", columnNames = "s3_key"),
@UniqueConstraint(name = "uk_case_file_case_record_document_number",
columnNames = {"case_record_id", "document_number"}),
@UniqueConstraint(name = "uk_case_file_document_reference",
columnNames = "document_reference")
}
)
public class CaseFile {
Expand Down Expand Up @@ -37,6 +42,16 @@ public class CaseFile {
@Column(name = "uploaded_at", nullable = false)
private LocalDateTime uploadedAt;

@Column(name = "document_number", nullable = false)
private int documentNumber;

@Column(name = "document_reference", nullable = false)
private String documentReference;

@Enumerated(EnumType.STRING)
@Column(name = "confidentiality_level", nullable = false, length = 50)
private ConfidentialityLevel confidentialityLevel;

public String getS3Key() {
return s3Key;
}
Expand Down Expand Up @@ -92,4 +107,32 @@ public CaseRecord getCaseRecord() {
public String getOriginalFilename() {
return originalFileName;
}

public int getDocumentNumber() {
return documentNumber;
}

public void setDocumentNumber(int documentNumber) {
this.documentNumber = documentNumber;
}

public String getDocumentReference() {
return documentReference;
}

public void setDocumentReference(String documentReference) {
this.documentReference = documentReference;
}

public ConfidentialityLevel getConfidentialityLevel() {
return confidentialityLevel;
}

public void setConfidentialityLevel(ConfidentialityLevel confidentialityLevel) {
this.confidentialityLevel = confidentialityLevel;
}

public boolean isConfidential() {
return confidentialityLevel == ConfidentialityLevel.CONFIDENTIAL;
}
}
45 changes: 32 additions & 13 deletions src/main/java/backendlab/team4you/casefile/CaseFileController.java
Original file line number Diff line number Diff line change
@@ -1,62 +1,79 @@
package backendlab.team4you.casefile;

import backendlab.team4you.common.ConfidentialityLevel;
import backendlab.team4you.user.UserEntity;
import backendlab.team4you.user.UserService;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import org.springframework.http.ContentDisposition;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.List;

@RestController
@RequestMapping("/api/cases/{caseRecordId}/files")
public class CaseFileController {

private final CaseFileService caseFileService;
private final UserService userService;

public CaseFileController(CaseFileService caseFileService) {
public CaseFileController(CaseFileService caseFileService, UserService userService) {
this.caseFileService = caseFileService;
this.userService = userService;
}

@PostMapping
public ResponseEntity<CaseFileResponseDto> uploadFile(
@PathVariable Long caseRecordId,
@RequestParam("file") MultipartFile file
@RequestParam("file") MultipartFile file,
@RequestParam("confidentialityLevel") ConfidentialityLevel confidentialityLevel,
Principal principal
) throws IOException {
CaseFile savedFile = caseFileService.uploadFile(caseRecordId, file);
UserEntity currentUser = userService.getCurrentUser(principal);
CaseFile savedFile = caseFileService.uploadFile(caseRecordId, file, confidentialityLevel, currentUser);
return ResponseEntity.ok(CaseFileResponseDto.from(savedFile));
}

@GetMapping
public ResponseEntity <List<CaseFileResponseDto>> listFiles(@PathVariable Long caseRecordId) {
List<CaseFileResponseDto> files = caseFileService.listFiles(caseRecordId).stream()
.map(CaseFileResponseDto::from)
.toList();
public ResponseEntity<List<CaseFileListItemDto>> listFiles(
@PathVariable Long caseRecordId,
Principal principal
) {
UserEntity currentUser = userService.getCurrentUser(principal);

List<CaseFileListItemDto> files =
caseFileService.listFileItemsForViewer(caseRecordId, currentUser);

return ResponseEntity.ok(files);
}

@GetMapping("/{fileId}")
public ResponseEntity<StreamingResponseBody> downloadFile(
@PathVariable Long caseRecordId,
@PathVariable Long fileId
@PathVariable Long fileId,
Principal principal
) {
CaseFile caseFile = caseFileService.getCaseFile(caseRecordId, fileId);
UserEntity currentUser = userService.getCurrentUser(principal);
CaseFile caseFile = caseFileService.getCaseFileForViewer(caseRecordId, fileId, currentUser);

MediaType mediaType = MediaType.APPLICATION_OCTET_STREAM;
if (caseFile.getContentType() != null && !caseFile.getContentType().isBlank()) {
mediaType = MediaType.parseMediaType(caseFile.getContentType());
}

StreamingResponseBody body = outputStream -> {
try (InputStream stream = caseFileService.downloadFile(caseRecordId, fileId)) {
try (InputStream stream = caseFileService.downloadFile(caseRecordId, fileId, currentUser)) {
stream.transferTo(outputStream);
}
};

return ResponseEntity.ok()
.header(
HttpHeaders.CONTENT_DISPOSITION,
Expand All @@ -72,9 +89,11 @@ public ResponseEntity<StreamingResponseBody> downloadFile(
@DeleteMapping("/{fileId}")
public ResponseEntity<Void> deleteFile(
@PathVariable Long caseRecordId,
@PathVariable Long fileId
@PathVariable Long fileId,
Principal principal
) {
caseFileService.deleteFile(caseRecordId, fileId);
UserEntity currentUser = userService.getCurrentUser(principal);
caseFileService.deleteFile(caseRecordId, fileId, currentUser);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package backendlab.team4you.casefile;

public record CaseFileListItemDto(
Long id,
String documentReference,
String displayName,
boolean confidential,
boolean canDownload
) {
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package backendlab.team4you.casefile;

import jakarta.persistence.LockModeType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;

import java.util.List;
import java.util.Optional;

public interface CaseFileRepository extends JpaRepository<CaseFile, Long> {
List<CaseFile> findByCaseRecordId(Long caseRecordId);
List<CaseFile> findByCaseRecordIdOrderByDocumentNumberAsc(Long caseRecordId);
Optional<CaseFile> findByIdAndCaseRecordId(Long id, Long caseRecordId);
@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<CaseFile> findTopByCaseRecordIdOrderByDocumentNumberDesc(Long caseRecordId);
Comment thread
MartinStenhagen marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
package backendlab.team4you.casefile;

import backendlab.team4you.common.ConfidentialityLevel;

import java.time.LocalDateTime;

public record CaseFileResponseDto(
Long id,
String originalFilename,
String contentType,
long size,
LocalDateTime uploadedAt
LocalDateTime uploadedAt,
int documentNumber,
String documentReference,
ConfidentialityLevel confidentialityLevel
) {
public static CaseFileResponseDto from(CaseFile caseFile) {
return new CaseFileResponseDto(
caseFile.getId(),
caseFile.getOriginalFilename(),
caseFile.getContentType(),
caseFile.getSize(),
caseFile.getUploadedAt()
caseFile.getUploadedAt(),
caseFile.getDocumentNumber(),
caseFile.getDocumentReference(),
caseFile.getConfidentialityLevel()
);
}
}
Loading