From 41cfdc54306435950f5ec8b0a5f833c48262bb71 Mon Sep 17 00:00:00 2001 From: gurkvatten Date: Tue, 31 Mar 2026 09:04:00 +0200 Subject: [PATCH 01/11] added s3config --- .../example/crimearchive/config/S3Config.java | 36 +++++++++++++++++++ src/main/resources/application.properties | 4 +++ 2 files changed, 40 insertions(+) create mode 100644 src/main/java/org/example/crimearchive/config/S3Config.java diff --git a/src/main/java/org/example/crimearchive/config/S3Config.java b/src/main/java/org/example/crimearchive/config/S3Config.java new file mode 100644 index 0000000..2344eee --- /dev/null +++ b/src/main/java/org/example/crimearchive/config/S3Config.java @@ -0,0 +1,36 @@ +package org.example.crimearchive.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class S3Config { + + @Value("${minio.endpoint}") + private String endpoint; + + @Value("${minio.access-key}") + private String accessKey; + + @Value("${minio.secret-key}") + private String secretKey; + + @Bean + public AmazonS3 s3Client() { + BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); + + return AmazonS3ClientBuilder.standard() + .withEndpointConfiguration( + new AwsClientBuilder.EndpointConfiguration(endpoint, "us-east-1") + ) + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .withPathStyleAccessEnabled(true) + .build(); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index cfa9736..be908d4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,3 +3,7 @@ spring.application.name=crimeArchive spring.jpa.show-sql=true #create table automatic, understand flyway operartion and change this to validate spring.jpa.hibernate.ddl-auto=update +minio.endpoint=http://localhost:9000 +minio.access-key=minioadmin +minio.secret-key=minioadmin +minio.bucket=bevis From 5a932147186a43d2973e6a9865370fbed1b0fc1d Mon Sep 17 00:00:00 2001 From: gurkvatten Date: Tue, 31 Mar 2026 11:14:39 +0200 Subject: [PATCH 02/11] added createbucket bean --- .../example/crimearchive/config/S3Config.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/org/example/crimearchive/config/S3Config.java b/src/main/java/org/example/crimearchive/config/S3Config.java index 2344eee..e7f7688 100644 --- a/src/main/java/org/example/crimearchive/config/S3Config.java +++ b/src/main/java/org/example/crimearchive/config/S3Config.java @@ -6,6 +6,7 @@ import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.ApplicationRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -21,6 +22,9 @@ public class S3Config { @Value("${minio.secret-key}") private String secretKey; + @Value("${minio.bucket}") + private String bucket; + @Bean public AmazonS3 s3Client() { BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); @@ -33,4 +37,16 @@ public AmazonS3 s3Client() { .withPathStyleAccessEnabled(true) .build(); } + + @Bean + public ApplicationRunner createBucket(AmazonS3 s3Client) { + return args -> { + if (!s3Client.doesBucketExistV2(bucket)) { + s3Client.createBucket(bucket); + System.out.println("Bucket skapad: " + bucket); + } else { + System.out.println("Bucket finns redan: " + bucket); + } + }; + } } \ No newline at end of file From 09b2030a3bd31b6f7b25c00d797176b07374cb30 Mon Sep 17 00:00:00 2001 From: gurkvatten Date: Tue, 31 Mar 2026 11:34:08 +0200 Subject: [PATCH 03/11] s3 integration for report --- .../example/crimearchive/bevis/Report.java | 12 ++++++- .../controllers/HomeController.java | 15 ++++++--- .../crimearchive/mapper/ReportMapper.java | 6 ++-- .../crimearchive/service/ReportService.java | 31 ++++++++++++++++--- src/main/resources/templates/private.html | 11 +++++-- 5 files changed, 60 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/example/crimearchive/bevis/Report.java b/src/main/java/org/example/crimearchive/bevis/Report.java index 49a4cef..4dcb85e 100644 --- a/src/main/java/org/example/crimearchive/bevis/Report.java +++ b/src/main/java/org/example/crimearchive/bevis/Report.java @@ -13,15 +13,17 @@ public class Report { private UUID uuid; private String name; private String event; + private String s3Key; public Report() { } - public Report(UUID id, String name, String event) { + public Report(UUID id, String name, String event, String s3Key) { this.uuid = id; this.name = name; this.event = event; + this.s3Key = s3Key; } public UUID getUuid() { @@ -48,6 +50,14 @@ public void setEvent(String event) { this.event = event; } + public String getS3Key() { + return s3Key; + } + + public void setS3Key(String s3Key) { + this.s3Key = s3Key; + } + @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; diff --git a/src/main/java/org/example/crimearchive/controllers/HomeController.java b/src/main/java/org/example/crimearchive/controllers/HomeController.java index fad066c..d9b6054 100644 --- a/src/main/java/org/example/crimearchive/controllers/HomeController.java +++ b/src/main/java/org/example/crimearchive/controllers/HomeController.java @@ -8,11 +8,15 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; @Controller public class HomeController { - ReportService reportService; + private final ReportService reportService; public HomeController(ReportService reportService) { this.reportService = reportService; @@ -20,7 +24,6 @@ public HomeController(ReportService reportService) { @GetMapping("/") public String indexPage(Model model) { - //model.addAttribute("reports", reportService.getAllReports()); return "index"; } @@ -31,8 +34,10 @@ public String privatePage(Model model) { } @PostMapping("/reports/add") - public String saveReport(@ModelAttribute("newReport") @Valid CreateReport newReport) { - reportService.saveReport(newReport); + public String saveReport( + @ModelAttribute("newReport") @Valid CreateReport newReport, + @RequestParam(value = "file", required = false) MultipartFile file) throws IOException { + reportService.saveReport(newReport, file); return "redirect:/"; } -} +} \ No newline at end of file diff --git a/src/main/java/org/example/crimearchive/mapper/ReportMapper.java b/src/main/java/org/example/crimearchive/mapper/ReportMapper.java index 2576c86..c07b32c 100644 --- a/src/main/java/org/example/crimearchive/mapper/ReportMapper.java +++ b/src/main/java/org/example/crimearchive/mapper/ReportMapper.java @@ -7,11 +7,13 @@ public class ReportMapper { - public static Report toEntity(CreateReport report) { + public static Report toEntity(CreateReport report, String s3Key) { return new Report( UUID.randomUUID(), report.name(), - report.event() + report.event(), + s3Key + ); } diff --git a/src/main/java/org/example/crimearchive/service/ReportService.java b/src/main/java/org/example/crimearchive/service/ReportService.java index 1316204..06d809d 100644 --- a/src/main/java/org/example/crimearchive/service/ReportService.java +++ b/src/main/java/org/example/crimearchive/service/ReportService.java @@ -1,28 +1,51 @@ package org.example.crimearchive.service; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ObjectMetadata; import org.example.crimearchive.DTO.CreateReport; import org.example.crimearchive.bevis.Report; import org.example.crimearchive.mapper.ReportMapper; import org.example.crimearchive.repository.SimpleRepository; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.List; +import java.util.UUID; @Service public class ReportService { private final SimpleRepository simpleRepository; + private final AmazonS3 s3Client; + @Value("${minio.bucket}") + private String bucket; - public ReportService(SimpleRepository simpleRepository) { + public ReportService(SimpleRepository simpleRepository, AmazonS3 s3Client) { this.simpleRepository = simpleRepository; + this.s3Client = s3Client; } - public void saveReport(CreateReport report) { - simpleRepository.save(ReportMapper.toEntity(report)); + public void saveReport(CreateReport report, MultipartFile file) throws IOException { + String s3Key = null; + + // Ladda upp till MinIO om en fil bifogades + if (file != null && !file.isEmpty()) { + s3Key = "reports/" + UUID.randomUUID() + "_" + file.getOriginalFilename(); + + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentType(file.getContentType()); + metadata.setContentLength(file.getSize()); + + s3Client.putObject(bucket, s3Key, file.getInputStream(), metadata); + } + + simpleRepository.save(ReportMapper.toEntity(report, s3Key)); } public List getAllReports() { return simpleRepository.findAll(); } -} +} \ No newline at end of file diff --git a/src/main/resources/templates/private.html b/src/main/resources/templates/private.html index ad01f87..dcb7570 100644 --- a/src/main/resources/templates/private.html +++ b/src/main/resources/templates/private.html @@ -5,13 +5,18 @@ Title -
+
+ - +
+ + +
+
- + \ No newline at end of file From 79b2c6893f61e0f1532fe4cb08aadd03ff6bdf9d Mon Sep 17 00:00:00 2001 From: gurkvatten Date: Tue, 31 Mar 2026 13:15:43 +0200 Subject: [PATCH 04/11] added pdf and file upload to s3 --- .../example/crimearchive/bevis/Report.java | 23 +++++++--- .../crimearchive/mapper/ReportMapper.java | 5 +- .../crimearchive/service/ReportService.java | 46 +++++++++++++++---- 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/example/crimearchive/bevis/Report.java b/src/main/java/org/example/crimearchive/bevis/Report.java index 4dcb85e..d6b51b8 100644 --- a/src/main/java/org/example/crimearchive/bevis/Report.java +++ b/src/main/java/org/example/crimearchive/bevis/Report.java @@ -13,17 +13,19 @@ public class Report { private UUID uuid; private String name; private String event; - private String s3Key; + private String s3KeyPdf; + private String s3KeyFile; public Report() { } - public Report(UUID id, String name, String event, String s3Key) { + public Report(UUID id, String name, String event, String s3KeyPdf, String s3KeyFile) { this.uuid = id; this.name = name; this.event = event; - this.s3Key = s3Key; + this.s3KeyPdf = s3KeyPdf; + this.s3KeyFile = s3KeyFile; } public UUID getUuid() { @@ -50,12 +52,19 @@ public void setEvent(String event) { this.event = event; } - public String getS3Key() { - return s3Key; + public String getS3KeyPdf() { + return s3KeyPdf; } - public void setS3Key(String s3Key) { - this.s3Key = s3Key; + public void setS3KeyPdf(String s3KeyPdf) { + this.s3KeyPdf = s3KeyPdf; + } + public String getS3KeyFile() { + return s3KeyFile; + } + + public void setS3KeyFile(String s3KeyFile) { + this.s3KeyFile = s3KeyFile; } @Override diff --git a/src/main/java/org/example/crimearchive/mapper/ReportMapper.java b/src/main/java/org/example/crimearchive/mapper/ReportMapper.java index c07b32c..0577044 100644 --- a/src/main/java/org/example/crimearchive/mapper/ReportMapper.java +++ b/src/main/java/org/example/crimearchive/mapper/ReportMapper.java @@ -7,12 +7,13 @@ public class ReportMapper { - public static Report toEntity(CreateReport report, String s3Key) { + public static Report toEntity(CreateReport report, String s3KeyPdf, String s3KeyFile) { return new Report( UUID.randomUUID(), report.name(), report.event(), - s3Key + s3KeyPdf, + s3KeyFile ); } diff --git a/src/main/java/org/example/crimearchive/service/ReportService.java b/src/main/java/org/example/crimearchive/service/ReportService.java index 06d809d..d7e5a72 100644 --- a/src/main/java/org/example/crimearchive/service/ReportService.java +++ b/src/main/java/org/example/crimearchive/service/ReportService.java @@ -2,6 +2,9 @@ import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.ObjectMetadata; +import com.itextpdf.text.Document; +import com.itextpdf.text.Paragraph; +import com.itextpdf.text.pdf.PdfWriter; import org.example.crimearchive.DTO.CreateReport; import org.example.crimearchive.bevis.Report; import org.example.crimearchive.mapper.ReportMapper; @@ -10,7 +13,10 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.time.LocalDateTime; import java.util.List; import java.util.UUID; @@ -29,20 +35,44 @@ public ReportService(SimpleRepository simpleRepository, AmazonS3 s3Client) { } public void saveReport(CreateReport report, MultipartFile file) throws IOException { - String s3Key = null; + + String s3KeyPdf = null; + try { + ByteArrayOutputStream pdfStream = new ByteArrayOutputStream(); + Document document = new Document(); + PdfWriter.getInstance(document, pdfStream); + document.open(); + document.add(new Paragraph("Brottsanmälan")); + document.add(new Paragraph("Namn: " + report.name())); + document.add(new Paragraph("Brottstyp: " + report.event())); + document.add(new Paragraph("Datum: " + LocalDateTime.now())); + document.close(); - // Ladda upp till MinIO om en fil bifogades + byte[] pdfBytes = pdfStream.toByteArray(); + s3KeyPdf = "reports/pdf/" + UUID.randomUUID() + "_" + report.name() + ".pdf"; + + ObjectMetadata pdfMetadata = new ObjectMetadata(); + pdfMetadata.setContentType("application/pdf"); + pdfMetadata.setContentLength(pdfBytes.length); + + s3Client.putObject(bucket, s3KeyPdf, new ByteArrayInputStream(pdfBytes), pdfMetadata); + + } catch (Exception e) { + throw new IOException("Kunde inte generera PDF: " + e.getMessage()); + } + + String s3KeyFile = null; if (file != null && !file.isEmpty()) { - s3Key = "reports/" + UUID.randomUUID() + "_" + file.getOriginalFilename(); + s3KeyFile = "reports/files/" + UUID.randomUUID() + "_" + file.getOriginalFilename(); - ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentType(file.getContentType()); - metadata.setContentLength(file.getSize()); + ObjectMetadata fileMetadata = new ObjectMetadata(); + fileMetadata.setContentType(file.getContentType()); + fileMetadata.setContentLength(file.getSize()); - s3Client.putObject(bucket, s3Key, file.getInputStream(), metadata); + s3Client.putObject(bucket, s3KeyFile, file.getInputStream(), fileMetadata); } - simpleRepository.save(ReportMapper.toEntity(report, s3Key)); + simpleRepository.save(ReportMapper.toEntity(report, s3KeyPdf, s3KeyFile)); } public List getAllReports() { From ebd8ed6a94c39d4bb6034803c140d47b30b5a74b Mon Sep 17 00:00:00 2001 From: gurkvatten Date: Tue, 31 Mar 2026 13:24:55 +0200 Subject: [PATCH 05/11] added so user can download reports --- .../controllers/HomeController.java | 22 ++++++++-- .../crimearchive/service/ReportService.java | 40 ++++++++++++++++++- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/example/crimearchive/controllers/HomeController.java b/src/main/java/org/example/crimearchive/controllers/HomeController.java index d9b6054..c5d01d3 100644 --- a/src/main/java/org/example/crimearchive/controllers/HomeController.java +++ b/src/main/java/org/example/crimearchive/controllers/HomeController.java @@ -2,16 +2,17 @@ import jakarta.validation.Valid; import org.example.crimearchive.DTO.CreateReport; +import org.example.crimearchive.bevis.Report; import org.example.crimearchive.service.ReportService; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import java.util.List; +import java.util.UUID; @Controller public class HomeController { @@ -40,4 +41,17 @@ public String saveReport( reportService.saveReport(newReport, file); return "redirect:/"; } + @GetMapping("/reports") + public ResponseEntity> getAllReports() { + return ResponseEntity.ok(reportService.getAllReports()); + } + @GetMapping("/reports/{uuid}/download/pdf") + public ResponseEntity downloadPdf(@PathVariable UUID uuid) throws IOException { + return reportService.downloadPdf(uuid); + } + + @GetMapping("/reports/{uuid}/download/file") + public ResponseEntity downloadFile(@PathVariable UUID uuid) throws IOException { + return reportService.downloadFile(uuid); + } } \ No newline at end of file diff --git a/src/main/java/org/example/crimearchive/service/ReportService.java b/src/main/java/org/example/crimearchive/service/ReportService.java index d7e5a72..cd22baa 100644 --- a/src/main/java/org/example/crimearchive/service/ReportService.java +++ b/src/main/java/org/example/crimearchive/service/ReportService.java @@ -2,6 +2,7 @@ import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.S3Object; import com.itextpdf.text.Document; import com.itextpdf.text.Paragraph; import com.itextpdf.text.pdf.PdfWriter; @@ -10,6 +11,8 @@ import org.example.crimearchive.mapper.ReportMapper; import org.example.crimearchive.repository.SimpleRepository; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -35,7 +38,7 @@ public ReportService(SimpleRepository simpleRepository, AmazonS3 s3Client) { } public void saveReport(CreateReport report, MultipartFile file) throws IOException { - + String s3KeyPdf = null; try { ByteArrayOutputStream pdfStream = new ByteArrayOutputStream(); @@ -75,6 +78,41 @@ public void saveReport(CreateReport report, MultipartFile file) throws IOExcepti simpleRepository.save(ReportMapper.toEntity(report, s3KeyPdf, s3KeyFile)); } + public ResponseEntity downloadPdf(UUID uuid) throws IOException { + Report report = simpleRepository.findById(uuid) + .orElseThrow(() -> new RuntimeException("Rapporten hittades inte: " + uuid)); + + if (report.getS3KeyPdf() == null) { + throw new RuntimeException("Ingen PDF finns för denna rapport"); + } + + S3Object s3Object = s3Client.getObject(bucket, report.getS3KeyPdf()); + byte[] data = s3Object.getObjectContent().readAllBytes(); + + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_PDF) + .header("Content-Disposition", "attachment; filename=rapport.pdf") + .body(data); + } + + public ResponseEntity downloadFile(UUID uuid) throws IOException { + Report report = simpleRepository.findById(uuid) + .orElseThrow(() -> new RuntimeException("Rapporten hittades inte: " + uuid)); + + if (report.getS3KeyFile() == null) { + throw new RuntimeException("Ingen bifogad fil finns för denna rapport"); + } + + S3Object s3Object = s3Client.getObject(bucket, report.getS3KeyFile()); + byte[] data = s3Object.getObjectContent().readAllBytes(); + + String filename = report.getS3KeyFile().substring(report.getS3KeyFile().lastIndexOf("/") + 1); + + return ResponseEntity.ok() + .header("Content-Disposition", "attachment; filename=" + filename) + .body(data); + } + public List getAllReports() { return simpleRepository.findAll(); } From 1a52a2545884640217d3f54b52173d123df5c849 Mon Sep 17 00:00:00 2001 From: gurkvatten Date: Tue, 31 Mar 2026 13:36:57 +0200 Subject: [PATCH 06/11] renamed the bucket --- src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index be908d4..d498dd0 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,4 +6,4 @@ spring.jpa.hibernate.ddl-auto=update minio.endpoint=http://localhost:9000 minio.access-key=minioadmin minio.secret-key=minioadmin -minio.bucket=bevis +minio.bucket=crime-archive From ca466890ad3dac4682a5b89eb112271465b22197 Mon Sep 17 00:00:00 2001 From: gurkvatten Date: Wed, 1 Apr 2026 12:34:40 +0200 Subject: [PATCH 07/11] update s3 syntax to aws v2 --- .../example/crimearchive/config/S3Config.java | 50 ++++++++++------- .../crimearchive/service/ReportService.java | 54 ++++++++++++------- 2 files changed, 67 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/example/crimearchive/config/S3Config.java b/src/main/java/org/example/crimearchive/config/S3Config.java index e7f7688..c23046c 100644 --- a/src/main/java/org/example/crimearchive/config/S3Config.java +++ b/src/main/java/org/example/crimearchive/config/S3Config.java @@ -1,14 +1,18 @@ package org.example.crimearchive.config; -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.client.builder.AwsClientBuilder; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.ApplicationRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.CreateBucketRequest; +import software.amazon.awssdk.services.s3.model.HeadBucketRequest; +import software.amazon.awssdk.services.s3.model.S3Exception; + +import java.net.URI; @Configuration public class S3Config { @@ -26,26 +30,34 @@ public class S3Config { private String bucket; @Bean - public AmazonS3 s3Client() { - BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); - - return AmazonS3ClientBuilder.standard() - .withEndpointConfiguration( - new AwsClientBuilder.EndpointConfiguration(endpoint, "us-east-1") - ) - .withCredentials(new AWSStaticCredentialsProvider(credentials)) - .withPathStyleAccessEnabled(true) + public S3Client s3Client() { + return S3Client.builder() + .endpointOverride(URI.create(endpoint)) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create(accessKey, secretKey) + )) + .region(Region.US_EAST_1) + .forcePathStyle(true) .build(); } @Bean - public ApplicationRunner createBucket(AmazonS3 s3Client) { + public ApplicationRunner createBucket(S3Client s3Client) { return args -> { - if (!s3Client.doesBucketExistV2(bucket)) { - s3Client.createBucket(bucket); - System.out.println("Bucket skapad: " + bucket); - } else { + try { + s3Client.headBucket(HeadBucketRequest.builder() + .bucket(bucket) + .build()); System.out.println("Bucket finns redan: " + bucket); + } catch (S3Exception e) { + if (e.statusCode() == 404) { + s3Client.createBucket(CreateBucketRequest.builder() + .bucket(bucket) + .build()); + System.out.println("Bucket skapad: " + bucket); + } else { + System.err.println("Fel vid kontroll av bucket: " + e.getMessage()); + } } }; } diff --git a/src/main/java/org/example/crimearchive/service/ReportService.java b/src/main/java/org/example/crimearchive/service/ReportService.java index cd22baa..da54662 100644 --- a/src/main/java/org/example/crimearchive/service/ReportService.java +++ b/src/main/java/org/example/crimearchive/service/ReportService.java @@ -1,8 +1,11 @@ package org.example.crimearchive.service; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.model.ObjectMetadata; -import com.amazonaws.services.s3.model.S3Object; +import software.amazon.awssdk.core.ResponseBytes; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; import com.itextpdf.text.Document; import com.itextpdf.text.Paragraph; import com.itextpdf.text.pdf.PdfWriter; @@ -16,7 +19,6 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.time.LocalDateTime; @@ -27,12 +29,12 @@ public class ReportService { private final SimpleRepository simpleRepository; - private final AmazonS3 s3Client; + private final S3Client s3Client; // Ändrat från AmazonS3 @Value("${minio.bucket}") private String bucket; - public ReportService(SimpleRepository simpleRepository, AmazonS3 s3Client) { + public ReportService(SimpleRepository simpleRepository, S3Client s3Client) { this.simpleRepository = simpleRepository; this.s3Client = s3Client; } @@ -54,11 +56,14 @@ public void saveReport(CreateReport report, MultipartFile file) throws IOExcepti byte[] pdfBytes = pdfStream.toByteArray(); s3KeyPdf = "reports/pdf/" + UUID.randomUUID() + "_" + report.name() + ".pdf"; - ObjectMetadata pdfMetadata = new ObjectMetadata(); - pdfMetadata.setContentType("application/pdf"); - pdfMetadata.setContentLength(pdfBytes.length); + // AWS SDK v2 PutObject + PutObjectRequest putPdf = PutObjectRequest.builder() + .bucket(bucket) + .key(s3KeyPdf) + .contentType("application/pdf") + .build(); - s3Client.putObject(bucket, s3KeyPdf, new ByteArrayInputStream(pdfBytes), pdfMetadata); + s3Client.putObject(putPdf, RequestBody.fromBytes(pdfBytes)); } catch (Exception e) { throw new IOException("Kunde inte generera PDF: " + e.getMessage()); @@ -68,11 +73,14 @@ public void saveReport(CreateReport report, MultipartFile file) throws IOExcepti if (file != null && !file.isEmpty()) { s3KeyFile = "reports/files/" + UUID.randomUUID() + "_" + file.getOriginalFilename(); - ObjectMetadata fileMetadata = new ObjectMetadata(); - fileMetadata.setContentType(file.getContentType()); - fileMetadata.setContentLength(file.getSize()); + // AWS SDK v2 PutObject för MultipartFile + PutObjectRequest putFile = PutObjectRequest.builder() + .bucket(bucket) + .key(s3KeyFile) + .contentType(file.getContentType()) + .build(); - s3Client.putObject(bucket, s3KeyFile, file.getInputStream(), fileMetadata); + s3Client.putObject(putFile, RequestBody.fromInputStream(file.getInputStream(), file.getSize())); } simpleRepository.save(ReportMapper.toEntity(report, s3KeyPdf, s3KeyFile)); @@ -86,8 +94,13 @@ public ResponseEntity downloadPdf(UUID uuid) throws IOException { throw new RuntimeException("Ingen PDF finns för denna rapport"); } - S3Object s3Object = s3Client.getObject(bucket, report.getS3KeyPdf()); - byte[] data = s3Object.getObjectContent().readAllBytes(); + GetObjectRequest getObjectRequest = GetObjectRequest.builder() + .bucket(bucket) + .key(report.getS3KeyPdf()) + .build(); + + ResponseBytes objectBytes = s3Client.getObjectAsBytes(getObjectRequest); + byte[] data = objectBytes.asByteArray(); return ResponseEntity.ok() .contentType(MediaType.APPLICATION_PDF) @@ -103,8 +116,13 @@ public ResponseEntity downloadFile(UUID uuid) throws IOException { throw new RuntimeException("Ingen bifogad fil finns för denna rapport"); } - S3Object s3Object = s3Client.getObject(bucket, report.getS3KeyFile()); - byte[] data = s3Object.getObjectContent().readAllBytes(); + GetObjectRequest getObjectRequest = GetObjectRequest.builder() + .bucket(bucket) + .key(report.getS3KeyFile()) + .build(); + + ResponseBytes objectBytes = s3Client.getObjectAsBytes(getObjectRequest); + byte[] data = objectBytes.asByteArray(); String filename = report.getS3KeyFile().substring(report.getS3KeyFile().lastIndexOf("/") + 1); From 92dc9aa0ee975f3107e9fbfcdd6a8ad7efbe074d Mon Sep 17 00:00:00 2001 From: gurkvatten Date: Wed, 1 Apr 2026 14:18:51 +0200 Subject: [PATCH 08/11] fixes after coderabbit comments --- .../crimearchive/DTO/ReportResponse.java | 9 ++ .../example/crimearchive/config/S3Config.java | 2 +- .../controllers/HomeController.java | 12 +- .../crimearchive/service/ReportService.java | 125 ++++++++++-------- 4 files changed, 88 insertions(+), 60 deletions(-) create mode 100644 src/main/java/org/example/crimearchive/DTO/ReportResponse.java diff --git a/src/main/java/org/example/crimearchive/DTO/ReportResponse.java b/src/main/java/org/example/crimearchive/DTO/ReportResponse.java new file mode 100644 index 0000000..b7c24e8 --- /dev/null +++ b/src/main/java/org/example/crimearchive/DTO/ReportResponse.java @@ -0,0 +1,9 @@ +package org.example.crimearchive.DTO; + +import java.util.UUID; + +public record ReportResponse( + UUID uuid, + String name, + String event +) {} \ No newline at end of file diff --git a/src/main/java/org/example/crimearchive/config/S3Config.java b/src/main/java/org/example/crimearchive/config/S3Config.java index c23046c..b70d9a4 100644 --- a/src/main/java/org/example/crimearchive/config/S3Config.java +++ b/src/main/java/org/example/crimearchive/config/S3Config.java @@ -56,7 +56,7 @@ public ApplicationRunner createBucket(S3Client s3Client) { .build()); System.out.println("Bucket skapad: " + bucket); } else { - System.err.println("Fel vid kontroll av bucket: " + e.getMessage()); + throw new RuntimeException("Fel vid kontroll av bucket: " + e.getMessage(), e); } } }; diff --git a/src/main/java/org/example/crimearchive/controllers/HomeController.java b/src/main/java/org/example/crimearchive/controllers/HomeController.java index c5d01d3..71748b7 100644 --- a/src/main/java/org/example/crimearchive/controllers/HomeController.java +++ b/src/main/java/org/example/crimearchive/controllers/HomeController.java @@ -2,7 +2,7 @@ import jakarta.validation.Valid; import org.example.crimearchive.DTO.CreateReport; -import org.example.crimearchive.bevis.Report; +import org.example.crimearchive.DTO.ReportResponse; import org.example.crimearchive.service.ReportService; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; @@ -41,17 +41,19 @@ public String saveReport( reportService.saveReport(newReport, file); return "redirect:/"; } + @GetMapping("/reports") - public ResponseEntity> getAllReports() { - return ResponseEntity.ok(reportService.getAllReports()); + public ResponseEntity> getAllReports() { + return ResponseEntity.ok(reportService.getAllReportResponses()); } + @GetMapping("/reports/{uuid}/download/pdf") - public ResponseEntity downloadPdf(@PathVariable UUID uuid) throws IOException { + public ResponseEntity downloadPdf(@PathVariable UUID uuid) { return reportService.downloadPdf(uuid); } @GetMapping("/reports/{uuid}/download/file") - public ResponseEntity downloadFile(@PathVariable UUID uuid) throws IOException { + public ResponseEntity downloadFile(@PathVariable UUID uuid) { return reportService.downloadFile(uuid); } } \ No newline at end of file diff --git a/src/main/java/org/example/crimearchive/service/ReportService.java b/src/main/java/org/example/crimearchive/service/ReportService.java index da54662..613cb9f 100644 --- a/src/main/java/org/example/crimearchive/service/ReportService.java +++ b/src/main/java/org/example/crimearchive/service/ReportService.java @@ -1,8 +1,10 @@ package org.example.crimearchive.service; +import org.example.crimearchive.DTO.ReportResponse; import software.amazon.awssdk.core.ResponseBytes; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.GetObjectResponse; import software.amazon.awssdk.services.s3.model.PutObjectRequest; @@ -14,10 +16,13 @@ import org.example.crimearchive.mapper.ReportMapper; import org.example.crimearchive.repository.SimpleRepository; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.server.ResponseStatusException; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -29,7 +34,7 @@ public class ReportService { private final SimpleRepository simpleRepository; - private final S3Client s3Client; // Ändrat från AmazonS3 + private final S3Client s3Client; @Value("${minio.bucket}") private String bucket; @@ -39,9 +44,11 @@ public ReportService(SimpleRepository simpleRepository, S3Client s3Client) { this.s3Client = s3Client; } + @Transactional public void saveReport(CreateReport report, MultipartFile file) throws IOException { - String s3KeyPdf = null; + String s3KeyFile = null; + try { ByteArrayOutputStream pdfStream = new ByteArrayOutputStream(); Document document = new Document(); @@ -54,81 +61,91 @@ public void saveReport(CreateReport report, MultipartFile file) throws IOExcepti document.close(); byte[] pdfBytes = pdfStream.toByteArray(); - s3KeyPdf = "reports/pdf/" + UUID.randomUUID() + "_" + report.name() + ".pdf"; - - // AWS SDK v2 PutObject - PutObjectRequest putPdf = PutObjectRequest.builder() - .bucket(bucket) - .key(s3KeyPdf) - .contentType("application/pdf") - .build(); - - s3Client.putObject(putPdf, RequestBody.fromBytes(pdfBytes)); + s3KeyPdf = "reports/pdf/" + UUID.randomUUID() + ".pdf"; + + s3Client.putObject( + PutObjectRequest.builder() + .bucket(bucket) + .key(s3KeyPdf) + .contentType("application/pdf") + .build(), + RequestBody.fromBytes(pdfBytes) + ); + + if (file != null && !file.isEmpty()) { + s3KeyFile = "reports/files/" + UUID.randomUUID(); + + s3Client.putObject( + PutObjectRequest.builder() + .bucket(bucket) + .key(s3KeyFile) + .contentType(file.getContentType()) + .build(), + RequestBody.fromInputStream(file.getInputStream(), file.getSize()) + ); + } + + simpleRepository.save(ReportMapper.toEntity(report, s3KeyPdf, s3KeyFile)); } catch (Exception e) { - throw new IOException("Kunde inte generera PDF: " + e.getMessage()); + if (s3KeyPdf != null) { + s3Client.deleteObject(DeleteObjectRequest.builder() + .bucket(bucket).key(s3KeyPdf).build()); + } + if (s3KeyFile != null) { + s3Client.deleteObject(DeleteObjectRequest.builder() + .bucket(bucket).key(s3KeyFile).build()); + } + throw new IOException("Kunde inte spara rapport: " + e.getMessage()); } - - String s3KeyFile = null; - if (file != null && !file.isEmpty()) { - s3KeyFile = "reports/files/" + UUID.randomUUID() + "_" + file.getOriginalFilename(); - - // AWS SDK v2 PutObject för MultipartFile - PutObjectRequest putFile = PutObjectRequest.builder() - .bucket(bucket) - .key(s3KeyFile) - .contentType(file.getContentType()) - .build(); - - s3Client.putObject(putFile, RequestBody.fromInputStream(file.getInputStream(), file.getSize())); - } - - simpleRepository.save(ReportMapper.toEntity(report, s3KeyPdf, s3KeyFile)); } - public ResponseEntity downloadPdf(UUID uuid) throws IOException { + public ResponseEntity downloadPdf(UUID uuid) { Report report = simpleRepository.findById(uuid) - .orElseThrow(() -> new RuntimeException("Rapporten hittades inte: " + uuid)); + .orElseThrow(() -> new ResponseStatusException( + HttpStatus.NOT_FOUND, "Rapporten hittades inte: " + uuid)); if (report.getS3KeyPdf() == null) { - throw new RuntimeException("Ingen PDF finns för denna rapport"); + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ingen PDF finns för denna rapport"); } - GetObjectRequest getObjectRequest = GetObjectRequest.builder() - .bucket(bucket) - .key(report.getS3KeyPdf()) - .build(); - - ResponseBytes objectBytes = s3Client.getObjectAsBytes(getObjectRequest); - byte[] data = objectBytes.asByteArray(); + ResponseBytes objectBytes = s3Client.getObjectAsBytes( + GetObjectRequest.builder() + .bucket(bucket) + .key(report.getS3KeyPdf()) + .build() + ); return ResponseEntity.ok() .contentType(MediaType.APPLICATION_PDF) .header("Content-Disposition", "attachment; filename=rapport.pdf") - .body(data); + .body(objectBytes.asByteArray()); } - public ResponseEntity downloadFile(UUID uuid) throws IOException { + public ResponseEntity downloadFile(UUID uuid) { Report report = simpleRepository.findById(uuid) - .orElseThrow(() -> new RuntimeException("Rapporten hittades inte: " + uuid)); + .orElseThrow(() -> new ResponseStatusException( + HttpStatus.NOT_FOUND, "Rapporten hittades inte: " + uuid)); if (report.getS3KeyFile() == null) { - throw new RuntimeException("Ingen bifogad fil finns för denna rapport"); + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ingen bifogad fil finns för denna rapport"); } - GetObjectRequest getObjectRequest = GetObjectRequest.builder() - .bucket(bucket) - .key(report.getS3KeyFile()) - .build(); - - ResponseBytes objectBytes = s3Client.getObjectAsBytes(getObjectRequest); - byte[] data = objectBytes.asByteArray(); - - String filename = report.getS3KeyFile().substring(report.getS3KeyFile().lastIndexOf("/") + 1); + ResponseBytes objectBytes = s3Client.getObjectAsBytes( + GetObjectRequest.builder() + .bucket(bucket) + .key(report.getS3KeyFile()) + .build() + ); return ResponseEntity.ok() - .header("Content-Disposition", "attachment; filename=" + filename) - .body(data); + .header("Content-Disposition", "attachment; filename=fil") + .body(objectBytes.asByteArray()); + } + public List getAllReportResponses() { + return simpleRepository.findAll().stream() + .map(r -> new ReportResponse(r.getUuid(), r.getName(), r.getEvent())) + .toList(); } public List getAllReports() { From 388b85f75d7d0df3312a57afd10eab6cccc596e2 Mon Sep 17 00:00:00 2001 From: gurkvatten Date: Wed, 1 Apr 2026 16:28:55 +0200 Subject: [PATCH 09/11] fixed HomeController after coderabbit comments --- .../controllers/HomeController.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/example/crimearchive/controllers/HomeController.java b/src/main/java/org/example/crimearchive/controllers/HomeController.java index 71748b7..0994acb 100644 --- a/src/main/java/org/example/crimearchive/controllers/HomeController.java +++ b/src/main/java/org/example/crimearchive/controllers/HomeController.java @@ -34,12 +34,19 @@ public String privatePage(Model model) { return "private"; } - @PostMapping("/reports/add") + @PostMapping("/reports/add") public String saveReport( - @ModelAttribute("newReport") @Valid CreateReport newReport, - @RequestParam(value = "file", required = false) MultipartFile file) throws IOException { - reportService.saveReport(newReport, file); - return "redirect:/"; + @ModelAttribute("newReport") @Valid CreateReport newReport, + @RequestParam(value = "file", required = false) MultipartFile file, + Model model) { + try { + reportService.saveReport(newReport, file); + return "redirect:/"; + } catch (IOException e) { + model.addAttribute("error", "Kunde inte spara rapporten. Försök igen."); + model.addAttribute("newReport", newReport); + return "private"; + } } @GetMapping("/reports") From d1f52290570f0ab835d05e56fb0124f6c119ed0f Mon Sep 17 00:00:00 2001 From: gurkvatten Date: Wed, 1 Apr 2026 16:56:09 +0200 Subject: [PATCH 10/11] fixed ReportService after coderabbit comments --- .../crimearchive/service/ReportService.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/example/crimearchive/service/ReportService.java b/src/main/java/org/example/crimearchive/service/ReportService.java index 613cb9f..4d0e754 100644 --- a/src/main/java/org/example/crimearchive/service/ReportService.java +++ b/src/main/java/org/example/crimearchive/service/ReportService.java @@ -88,13 +88,21 @@ public void saveReport(CreateReport report, MultipartFile file) throws IOExcepti simpleRepository.save(ReportMapper.toEntity(report, s3KeyPdf, s3KeyFile)); } catch (Exception e) { - if (s3KeyPdf != null) { - s3Client.deleteObject(DeleteObjectRequest.builder() - .bucket(bucket).key(s3KeyPdf).build()); - } - if (s3KeyFile != null) { - s3Client.deleteObject(DeleteObjectRequest.builder() - .bucket(bucket).key(s3KeyFile).build()); + try { + if (s3KeyPdf != null) { + s3Client.deleteObject(DeleteObjectRequest.builder() + .bucket(bucket).key(s3KeyPdf).build()); + } + } catch (Exception cleanupEx) { + // Log but don't mask original exception + } + try { + if (s3KeyFile != null) { + s3Client.deleteObject(DeleteObjectRequest.builder() + .bucket(bucket).key(s3KeyFile).build()); + } + } catch (Exception cleanupEx) { + // Log but don't mask original exception } throw new IOException("Kunde inte spara rapport: " + e.getMessage()); } From 65e45cea34233a6ae92957f422b2cad517225933 Mon Sep 17 00:00:00 2001 From: gurkvatten Date: Thu, 2 Apr 2026 17:45:50 +0200 Subject: [PATCH 11/11] added functionality so that when user uploads picture,pdf or word document its attached to the pdf --- .../crimearchive/service/ReportService.java | 59 ++++++++++++++++--- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/example/crimearchive/service/ReportService.java b/src/main/java/org/example/crimearchive/service/ReportService.java index 4d0e754..3817a46 100644 --- a/src/main/java/org/example/crimearchive/service/ReportService.java +++ b/src/main/java/org/example/crimearchive/service/ReportService.java @@ -1,5 +1,6 @@ package org.example.crimearchive.service; +import com.itextpdf.text.Image; import org.example.crimearchive.DTO.ReportResponse; import software.amazon.awssdk.core.ResponseBytes; import software.amazon.awssdk.core.sync.RequestBody; @@ -54,10 +55,38 @@ public void saveReport(CreateReport report, MultipartFile file) throws IOExcepti Document document = new Document(); PdfWriter.getInstance(document, pdfStream); document.open(); + + document.add(new Paragraph("Brottsanmälan")); document.add(new Paragraph("Namn: " + report.name())); document.add(new Paragraph("Brottstyp: " + report.event())); document.add(new Paragraph("Datum: " + LocalDateTime.now())); + + + if (file != null && !file.isEmpty()) { + document.newPage(); + + if (isImage(file)) { + document.add(new Paragraph("Bifogat bevisfoto:")); + Image image = Image.getInstance(file.getBytes()); + image.scaleToFit(500, 700); + document.add(image); + + } else if (isPdf(file)) { + document.add(new Paragraph("Bifogat dokument (PDF):")); + document.add(new Paragraph(file.getOriginalFilename())); + document.add(new Paragraph("Se separat bifogad fil för fullständigt dokument.")); + + } else if (isWord(file)) { + document.add(new Paragraph("Bifogat dokument (Word):")); + document.add(new Paragraph(file.getOriginalFilename())); + document.add(new Paragraph("Se separat bifogad fil för fullständigt dokument.")); + + } else { + document.add(new Paragraph("Bifogad fil: " + file.getOriginalFilename())); + } + } + document.close(); byte[] pdfBytes = pdfStream.toByteArray(); @@ -90,24 +119,38 @@ public void saveReport(CreateReport report, MultipartFile file) throws IOExcepti } catch (Exception e) { try { if (s3KeyPdf != null) { - s3Client.deleteObject(DeleteObjectRequest.builder() + s3Client.deleteObject(DeleteObjectRequest.builder() .bucket(bucket).key(s3KeyPdf).build()); - } - } catch (Exception cleanupEx) { - // Log but don't mask original exception } + } catch (Exception cleanupEx) {} try { if (s3KeyFile != null) { s3Client.deleteObject(DeleteObjectRequest.builder() .bucket(bucket).key(s3KeyFile).build()); - } - } catch (Exception cleanupEx) { - // Log but don't mask original exception - } + } + } catch (Exception cleanupEx) {} throw new IOException("Kunde inte spara rapport: " + e.getMessage()); } } + private boolean isImage(MultipartFile file) { + String contentType = file.getContentType(); + return contentType != null && contentType.startsWith("image/"); + } + + private boolean isPdf(MultipartFile file) { + String contentType = file.getContentType(); + return contentType != null && contentType.equals("application/pdf"); + } + + private boolean isWord(MultipartFile file) { + String contentType = file.getContentType(); + return contentType != null && ( + contentType.equals("application/msword") || + contentType.equals("application/vnd.openxmlformats-officedocument.wordprocessingml.document") + ); + } + public ResponseEntity downloadPdf(UUID uuid) { Report report = simpleRepository.findById(uuid) .orElseThrow(() -> new ResponseStatusException(