diff --git a/data/demo.mv.db b/data/demo.mv.db index 7ce692294bc8c4f8f1c7c478ed4243370dcb0e91..f84059d8cf5bc953366dd45d5128b5673bb7d10a 100644 Binary files a/data/demo.mv.db and b/data/demo.mv.db differ diff --git a/src/main/java/com/dre0059/articleprocessor/config/DataInitializer.java b/src/main/java/com/dre0059/articleprocessor/config/DataInitializer.java index 5cbbded85869d00b4b55838cd8ec81247269bbc2..d7f91572bb985aca4bdecf05ffb49bda63eebbf5 100644 --- a/src/main/java/com/dre0059/articleprocessor/config/DataInitializer.java +++ b/src/main/java/com/dre0059/articleprocessor/config/DataInitializer.java @@ -8,6 +8,7 @@ import org.springframework.context.annotation.Configuration; import java.util.List; +// nestavenie defaultnĂ©ho zoznamu kategĂłriĂ @Configuration public class DataInitializer { diff --git a/src/main/java/com/dre0059/articleprocessor/config/GrobidProperties.java b/src/main/java/com/dre0059/articleprocessor/config/GrobidProperties.java index 0b4aab563c933cf3f7ca53118844905d9d05cff6..ab83461344f1a913f99131ad98e0f66e84799f4e 100644 --- a/src/main/java/com/dre0059/articleprocessor/config/GrobidProperties.java +++ b/src/main/java/com/dre0059/articleprocessor/config/GrobidProperties.java @@ -3,10 +3,15 @@ package com.dre0059.articleprocessor.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; +/** + KonfiguraÄŤná trieda pre naÄŤĂtanie vlastnostĂ GROBID zo sĂşboru application.properties alebo application.yml. + **/ @Configuration @ConfigurationProperties(prefix = "grobid") public class GrobidProperties { + + // URL pre GROBID server private String host; public void setHost(String host) { diff --git a/src/main/java/com/dre0059/articleprocessor/controller/DocumentController.java b/src/main/java/com/dre0059/articleprocessor/controller/DocumentController.java index 379bc5b700ad5623b114b9ce55a9eb212e9b7494..8b8871bfe3186c736d87ab2c2af51a389ab70df2 100644 --- a/src/main/java/com/dre0059/articleprocessor/controller/DocumentController.java +++ b/src/main/java/com/dre0059/articleprocessor/controller/DocumentController.java @@ -19,6 +19,10 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; +/** + KontrolĂ©r - backend pre prácu s dokumentmi a ich referenciami. + Obsahuje API aj webovĂ© endpointy na zobrazenie, Ăşpravu a mazanie dokumentov. + */ @Controller @RequestMapping public class DocumentController { @@ -35,11 +39,23 @@ public class DocumentController { this.documentService = documentService; } + /** + // NaÄŤĂta dokument podÄľa ID a vráti DTO. + * + * @param id ID dokumentu + * @return DTO dokumentu + */ @GetMapping("/api/documents/{id}") public ResponseEntity<DocumentDto> getDocumentById(@PathVariable Long id) { return ResponseEntity.ok(documentService.getDocumentById(id)); } + /** + * NaÄŤĂta dokumentu - ako PDF - podÄľa ID. + * + * @param id ID ĹľiadanĂ©ho dokumentu + * @return obsah PDF sĂşboru ako pole bytov + */ @GetMapping( value = "/api/documents/{id}/content", produces = MediaType.APPLICATION_PDF_VALUE @@ -48,11 +64,25 @@ public class DocumentController { return documentService.getDocumentContentById(id).getContent(); } + + /** + * // naÄŤĂta zoznam citovanĂ˝ch dokumentov v danom dokumente s danĂ˝m ID + * + * @param id ID dokumentu + * @return zoznam citovanĂ˝ch dokumentov + */ @GetMapping("/api/documents/{id}/references") public ResponseEntity<List<SimpleDocumentDto>> getReferencesFromDocument(@PathVariable Long id) { return ResponseEntity.ok(documentService.getReferencedDocumentsById(id)); } + /** + ZOBRAZENIE DOKUMENTU S INFO AJ REFERENCIAMI (view/{id}) + * + * @param model model pre šablĂłnu + * @param id ID dokumentu ktorĂ˝ chcem zobraziĹĄ + * @return html stránka "view-pdf" + **/ @GetMapping("/view/{id}") public String viewDocument(Model model, @PathVariable("id") Long id) { var references = documentService.getReferencedDocumentsById(id); @@ -63,8 +93,11 @@ public class DocumentController { throw new IllegalArgumentException("Document with ID " + id + " not found."); } + if(!document.getStatus().equals("Referenced")) - model.addAttribute("category", document.getCategory().getName()); + model.addAttribute("category", document.getCategory().getName()); + + // naÄŤĂta info o dokumente a pošle na frontend model.addAttribute("author", document.authorsToString()); model.addAttribute("year", document.getYear()); model.addAttribute("doi", document.getDoi()); @@ -88,24 +121,43 @@ public class DocumentController { model.addAttribute("categories", categoryRepository.findAll()); - return "view-pdf"; + return "view-pdf"; // view-pdf.html } + /** + * // Zobrazenie všetkĂ˝ch dokumentov. + * + * @param model model pre šablĂłnu + * @return názov šablĂłny "view-all" + */ @GetMapping("/view") public String viewAllDocuments(Model model) { var documents = documentService.getAllDocuments(); model.addAttribute("documents", documents); - return "view-all"; + return "view-all"; // view-all.html } + /** + * vymaĹľe dokument podÄľa ID -> predpripravená metĂłda na implementovanie delete + * + * @param id ID dokumentu + * @return názov šablĂłny "view-all" + **/ @PostMapping("/delete/{id}") public ResponseEntity<String> deleteDocument(@PathVariable Long id) { documentService.deleteDocument(id); return ResponseEntity.ok("References deleted successfully."); } + /** + * pridanie vlastnĂ˝ch poznámok k dokumentu + * + * @param documentId + * @param payload - obsah poznámky + * @return odpoveÄŹ o Ăşspechu / neĂşspechu + */ @PostMapping("/api/documents/{documentId}/setNotes") @ResponseBody public ResponseEntity<Map<String, Object>> setNotes(@PathVariable Long documentId, @RequestBody Map<String, String> payload) { @@ -123,6 +175,12 @@ public class DocumentController { return ResponseEntity.ok(response); } + /** nastavenie kategĂłrie (nová kategĂłria, alebo prepĂsanie starej) + * + * @param documentId + * @param payload - obsah kategĂłrie + * @return odpoveÄŹ o Ăşspechu / nneĂşspechu + */ @PostMapping("/api/documents/{documentId}/setCategory") @ResponseBody public ResponseEntity<Map<String, Object>> setCategory(@PathVariable Long documentId, @RequestBody Map<String, String> payload) { @@ -131,10 +189,9 @@ public class DocumentController { String categoryName = payload.get("categoryName"); - // Skontrolujeme, ÄŤi kategĂłria existuje + // check, ÄŤi kategĂłria existuje (podÄľa mena - to je UNIQUE) Category category = categoryRepository.findByName(categoryName); if (category == null) { - // Ak kategĂłria neexistuje, mĂ´Ĺľeme buÄŹ priradiĹĄ default hodnotu, alebo poslaĹĄ error return ResponseEntity.badRequest().body(Map.of("error", "Category with name '" + categoryName + "' not found")); } @@ -146,9 +203,14 @@ public class DocumentController { return ResponseEntity.ok(response); } + + /** statická stránka o projekte + * + * @return názov šablĂłny pre html = "about-project" + */ @GetMapping("/about") public String aboutProject() { - return "about-project"; // názov HTML sĂşboru bez prĂpony + return "about-project"; // about-project.html } diff --git a/src/main/java/com/dre0059/articleprocessor/controller/FileUploadController.java b/src/main/java/com/dre0059/articleprocessor/controller/FileUploadController.java index 9bc0176c9188bc9a1ca3e1358126f82c8b3d0f3a..c45c02e24187567af53cdfe513a8d21d0966a6d7 100644 --- a/src/main/java/com/dre0059/articleprocessor/controller/FileUploadController.java +++ b/src/main/java/com/dre0059/articleprocessor/controller/FileUploadController.java @@ -25,6 +25,12 @@ import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.bind.annotation.ExceptionHandler; +/** + KontrolĂ©r na prácu s nahratĂ˝m PDF dokumentom : + - spracovanie UPLOAD-u PDF SĂšBOROV + - komunikáciu s GROBID serverom + - uloĹľenie nahratĂ©ho PDF dokumentu do databázy + **/ @Controller @RequestMapping public class FileUploadController { @@ -40,6 +46,12 @@ public class FileUploadController { this.categoryService = categoryService; } + /** + * // formulár pre nahratie PDF sĂşboru + * + * @param model model pre šablĂłnu + * @return názov vrátenej html stránky = "upload" + */ @GetMapping(value = {"/upload","/"}) public String showUploadForm(Model model) { var categories = categoryService.getAll(); @@ -47,6 +59,16 @@ public class FileUploadController { return "upload"; // vracia upload.html } + /** + * spracovanie nahratĂ©ho PDF sĂşboru, + * analĂ˝za PDF cez GROBID + * uloĹľenie vĂ˝sledkov + * + * @param file nahranĂ˝ PDF sĂşbor + * @param categoryId ID kategĂłrie + * @param tags zoznam tagov + * @return status spracovania (error / success) + **/ @PostMapping("/api/upload") @ResponseBody public ResponseEntity<?> handleFileUpload( @@ -58,7 +80,7 @@ public class FileUploadController { return ResponseEntity.badRequest().body(Map.of("error", "No file uploaded!")); } - // Pridáme kontrolu veÄľkosti ešte pred spracovanĂm + // kontrola veÄľkosti ešte pred spracovanĂm - aby nám to nepadlo final long maxFileSizeBytes = 5 * 1024 * 1024; // 5 MB if (file.getSize() > maxFileSizeBytes) { return ResponseEntity.badRequest().body(Map.of("error", "Uploaded PDF is too large. Maximum allowed size is 5MB.")); @@ -68,7 +90,7 @@ public class FileUploadController { System.out.println("Processing file " + file.getOriginalFilename()); File tmpFile = File.createTempFile("article-", ".pdf"); - // save data from file to tmpFile + // uložà dáta z PDF do doÄŤasnĂ©ho sĂşboru try(FileOutputStream stream = new FileOutputStream(tmpFile)) { stream.write(file.getBytes()); } catch (IOException e) { @@ -77,6 +99,7 @@ public class FileUploadController { String header; try { + // spracovanie hlaviÄŤky dokumentu CEZ GROBID SERVER (pomocou grobidClient triedy) header = grobidClient.processHeader(tmpFile); } catch (RuntimeException e) { if (e.getMessage().contains("Failed to connect to GROBID server")) { @@ -86,20 +109,22 @@ public class FileUploadController { return ResponseEntity.internalServerError().body(Map.of("error", "GROBID processing error")); } + // spracovanie referenciĂ cez GROBID SERVER String references = grobidClient.processReferences(tmpFile); + /** volaná metĂłda na spracovanie informáciĂ z hlaviÄŤky o dokumente a uloĹľenie dokumentu do DBS aj so základnĂ˝mi info + - ak bol dokument doteraz ako Referenced, zmenĂ sa na PDF a doplnia sa metadáta + **/ Dokument savedDocument = headerService.processHeader(header, categoryId, tags, tmpFile); - if(savedDocument == null) { - if (tmpFile.exists()) tmpFile.delete(); - return ResponseEntity.status(HttpStatus.CONFLICT) - .body(Map.of("error", "Document already exists in the database")); - } - + /** + * volaná metĂłda na spracovanie referenciĂ + */ referenceService.extractReferences(references); if (tmpFile.exists()) tmpFile.delete(); + // vráti success ako odpoveÄŹ Map<String, Object> response = new HashMap<>(); response.put("id", savedDocument.getId()); response.put("message", "Upload successful"); @@ -111,7 +136,13 @@ public class FileUploadController { } } - // Globálny handler pre veÄľkĂ© sĂşbory + + /** + // Hhandler pre veÄľkĂ© sĂşbory (nad 5MB - nastaviĹĄ sa dá veÄľkosĹĄ v application.properties) + * + * @param exc vĂ˝nimka pre prekroÄŤenĂş veÄľkosĹĄ + * @return chybovĂ© hlásenie + */ @ExceptionHandler(MaxUploadSizeExceededException.class) @ResponseBody public ResponseEntity<?> handleMaxSizeException(MaxUploadSizeExceededException exc) { diff --git a/src/main/java/com/dre0059/articleprocessor/controller/StatisticsController.java b/src/main/java/com/dre0059/articleprocessor/controller/StatisticsController.java index 380f0c447cc7d50a8fa8a98c5723097351c4f3ae..22a46763d8a6ab829f21a7b1f6b129af607d2b13 100644 --- a/src/main/java/com/dre0059/articleprocessor/controller/StatisticsController.java +++ b/src/main/java/com/dre0059/articleprocessor/controller/StatisticsController.java @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RequestParam; import java.util.*; import java.util.stream.Collectors; +// kontrolĂ©r pre prácu s grafmi a štatistikami (/statistics, /statistics/citation-timeline?documentId={id}, /statistics/more-citations) @Controller public class StatisticsController { @@ -23,6 +24,16 @@ public class StatisticsController { this.referenceRepository = referenceRepository; } + + /** + * Zobrazenie základnĂ˝ch štatistĂk + * Vracia stránku so štatistickĂ˝mi grafmi o všetkĂ˝ch dátach vo vĂ˝slednej aplikácii + * + * @param category (voliteÄľnĂ˝) filter na kategĂłriu + * @param model model pre šablĂłnu + * @return názov hmtl stránky = "statistics" + */ + // všeibecnĂ© štatistiky @GetMapping("/statistics") public String statistics( @RequestParam(value = "category", required = false, defaultValue = "") String category, @@ -35,7 +46,7 @@ public class StatisticsController { Map<String, Long> statusCount = documents.stream() .collect(Collectors.groupingBy(Dokument::getStatus, Collectors.counting())); - // 3) Category count (len tie, ktorĂ© majĂş kategĂłriu) + // 3) len a kategĂłriou Map<String, Long> categoryCount = documents.stream() .filter(d -> d.getCategory() != null) .collect(Collectors.groupingBy( @@ -43,17 +54,13 @@ public class StatisticsController { Collectors.counting() )); - // 4) Zoznam všetkĂ˝ch kategĂłriĂ (poradie podÄľa vstupu do mapy) List<String> categories = new ArrayList<>(categoryCount.keySet()); - // 5) Vyberieme "selectedCategory": - // - ak prišlo z parametra, pouĹľijeme ho - // - inak vezmeme prvĂş kategĂłriu zo zoznamu (ak existuje) String selectedCategory = category.trim().isEmpty() ? (categories.isEmpty() ? "" : categories.get(0)) : category.trim(); - // 6) SpoÄŤĂtame referencie podÄľa rokov pre vybranĂş kategĂłriu + // 6) referencie podÄľa rokov pre vybranĂş kategĂłriu Map<Integer, Long> referenceCounts = new LinkedHashMap<>(); if (!selectedCategory.isEmpty()) { List<Object[]> raw = referenceRepository @@ -66,7 +73,7 @@ public class StatisticsController { }); } - // 7) Pridáme všetko do modelu + // 7) Pridáme do modelu model.addAttribute("statusCount", statusCount); model.addAttribute("categoryCount", categoryCount); model.addAttribute("referenceCounts", referenceCounts); @@ -75,7 +82,13 @@ public class StatisticsController { return "statistics"; } - // sofistikovanĂ˝ graf + /** + * Graf : chronologickĂ© zoradenie referenciĂ pre danĂ© kategĂłrie a vybratĂ˝ dokument + * * + * @param documentId ID hlavnĂ©ho dokumentu + * @param model model pre šablĂłnu + * @return názov hmtl stránky = "citation-timeline" + */ @GetMapping("/statistics/citation-timeline") public String citationTimeline(@RequestParam("documentId") Long documentId, Model model) { Optional<Dokument> mainDocOpt = documentRepository.findById(documentId); @@ -117,5 +130,66 @@ public class StatisticsController { return "citation-timeline"; } + /** + * graf s viacerĂ˝mi hlavnĂ˝mi dokumentmi a ich citáciami. + * + * @param model model pre šablĂłnu + * @return názov html stránky = "more-citations" + */ + // sofistikovanĂ˝ graf (more citations) + @GetMapping("/statistics/more-citations") + public String moreCitations(Model model) { + // 1: Zoznam ID dokumentov, ktorĂ© chceme zobraziĹĄ + List<Long> documentIds = List.of(1L, 2L, 3L, 4L, 40L, 212L); + + List<Dokument> mainDocuments = documentRepository.findAllById(documentIds); + + // 2: PripravĂme zoznam hlavnĂ˝ch ÄŤlánkov pre JS + List<Map<String, Object>> mainArticles = mainDocuments.stream() + .map(doc -> { + Map<String, Object> map = new HashMap<>(); + map.put("id", doc.getId()); + map.put("title", doc.getTitle()); + map.put("year", doc.getPublicationYear()); + map.put("category", doc.getCategory() != null ? doc.getCategory().getName() : "Unknown"); + return map; + }) + .filter(map -> map.get("year") != null) + .collect(Collectors.toList()); + + // 4: PripravĂme všetky referencie + List<Map<String, Object>> allReferences = mainDocuments.stream() + .flatMap(doc -> doc.getReferences().stream() + .filter(ref -> ref.getToDocument() != null && + ref.getToDocument().getPublicationYear() != null && + ref.getToDocument().getCategory() != null) + .map(ref -> { + Dokument cited = ref.getToDocument(); + Map<String, Object> refMap = new HashMap<>(); + refMap.put("mainArticleId", doc.getId()); + refMap.put("title", cited.getTitle()); + refMap.put("year", cited.getPublicationYear()); + refMap.put("category", cited.getCategory().getName()); + return refMap; + }) + ) + .collect(Collectors.toList()); + + // 5: všetky kategĂłrie + List<String> categories = documentRepository.findAll().stream() + .map(Dokument::getCategory) + .filter(Objects::nonNull) + .map(c -> c.getName()) + .distinct() + .sorted() + .collect(Collectors.toList()); + + // 6: do modelu + model.addAttribute("mainArticles", mainArticles); + model.addAttribute("allReferences", allReferences); + model.addAttribute("categories", categories); + + return "more-citations"; + } } diff --git a/src/main/java/com/dre0059/articleprocessor/controller/TagController.java b/src/main/java/com/dre0059/articleprocessor/controller/TagController.java index b7893b9bee8409ab704951db1ca7be6d5a73ad2d..f940fd881752d7c3b7bc378ec1f1ff889c96f9c6 100644 --- a/src/main/java/com/dre0059/articleprocessor/controller/TagController.java +++ b/src/main/java/com/dre0059/articleprocessor/controller/TagController.java @@ -11,6 +11,9 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +/** + * KontrolĂ©r pre prácu s TAGMI ("kľúčovĂ© slová" priradenĂ© dokumentu uĹľĂvateÄľom) + */ @RestController @RequestMapping("/api") public class TagController { @@ -21,6 +24,13 @@ public class TagController { this.tagRepository = tagRepository; } + /** + * VyhÄľadáva tagy obsahujĂşce danĂ˝ reĹĄazec (case-insensitive). + * VĂ˝stup je vo formáte vhodnom pre kniĹľnicu Select2 (pozri stránku /upload a okno na nahrávanie tagov) + * + * @param term hÄľadanĂ˝ reĹĄazec + * @return zoznam tagov vo formáte {id, text} + */ @GetMapping("/tags") public List<Map<String, String>> getTags(@RequestParam("term") String term) { return tagRepository.findByTitleContainingIgnoreCase(term).stream() diff --git a/src/main/java/com/dre0059/articleprocessor/dto/CategoryDto.java b/src/main/java/com/dre0059/articleprocessor/dto/CategoryDto.java index c7f6d89e7526c2e2b948847737e9d596122381dd..60e1a71f65eecc776732fc39829106965b980f63 100644 --- a/src/main/java/com/dre0059/articleprocessor/dto/CategoryDto.java +++ b/src/main/java/com/dre0059/articleprocessor/dto/CategoryDto.java @@ -1,23 +1,17 @@ package com.dre0059.articleprocessor.dto; +import lombok.Getter; +import lombok.Setter; + +/** + * Data Transfer Object reprezentujĂşci kategĂłriu dokumentov + * Obsahuje ID a názov kategĂłrie (nazov - name - je UNIQUE a definuje kategĂłriu) + */ +@Getter +@Setter public class CategoryDto { private String id; private String name; - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } -} + } diff --git a/src/main/java/com/dre0059/articleprocessor/dto/DocumentContentDto.java b/src/main/java/com/dre0059/articleprocessor/dto/DocumentContentDto.java index d5546983af33e442e89504b59c641a35c5b3cdef..ed980b4d9583837773f6344bd58c7b0ab1ad5746 100644 --- a/src/main/java/com/dre0059/articleprocessor/dto/DocumentContentDto.java +++ b/src/main/java/com/dre0059/articleprocessor/dto/DocumentContentDto.java @@ -1,22 +1,16 @@ package com.dre0059.articleprocessor.dto; +import lombok.Getter; +import lombok.Setter; + +/** + * DTO (data-transfer-object) obsahujĂşci binárny obsah dokumentu + * PouĹľĂvanĂ˝ ako PDF preview + */ +@Getter +@Setter public class DocumentContentDto { private Long id; private byte[] content; - public byte[] getContent() { - return content; - } - - public void setContent(byte[] content) { - this.content = content; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } } diff --git a/src/main/java/com/dre0059/articleprocessor/dto/DocumentDto.java b/src/main/java/com/dre0059/articleprocessor/dto/DocumentDto.java index eba2f7da9b754970afb954b2c71f8d1da6011032..539d8c3a8751fcebf98cb985e819f0c567b17063 100644 --- a/src/main/java/com/dre0059/articleprocessor/dto/DocumentDto.java +++ b/src/main/java/com/dre0059/articleprocessor/dto/DocumentDto.java @@ -6,6 +6,10 @@ import lombok.Setter; import java.util.ArrayList; import java.util.List; +/** + * DetailnĂ© DTO pre dokument (popisuje kompletnĂş entitu dokument) + * Obsahuje metadáta, detaily dokumentu a aj priradenĂ© tagy + */ @Getter @Setter public class DocumentDto { @@ -16,7 +20,7 @@ public class DocumentDto { private String abstractText; private String status; private String publisher; - private String target; + private String target; // http link ÄŤlánku (ak existuje) private List<TagDto> tags; } diff --git a/src/main/java/com/dre0059/articleprocessor/dto/SimpleDocumentDto.java b/src/main/java/com/dre0059/articleprocessor/dto/SimpleDocumentDto.java index ea44d6b9b3e9a14e887084308020c13fea0ce3f7..f6cd696cd67e032286013ab06759585bceb4ee1a 100644 --- a/src/main/java/com/dre0059/articleprocessor/dto/SimpleDocumentDto.java +++ b/src/main/java/com/dre0059/articleprocessor/dto/SimpleDocumentDto.java @@ -3,6 +3,11 @@ package com.dre0059.articleprocessor.dto; import lombok.Getter; import lombok.Setter; +/** + * ZjednodušenĂ˝ DTO pre dokumenty + * + * Obsahuje základnĂ© informácie + */ @Getter @Setter public class SimpleDocumentDto { @@ -10,6 +15,11 @@ public class SimpleDocumentDto { private String title; private String status; + /** + * Pomocná metĂłda pre detaily dokumentu + * + * @return URL adresu k dokumentu (de-facto odkaz sám na seba) + */ public String getLink() { return "/api/document/"+getId(); } diff --git a/src/main/java/com/dre0059/articleprocessor/dto/TagDto.java b/src/main/java/com/dre0059/articleprocessor/dto/TagDto.java index 06bd1b07b7ef84311abb1aaa9c4d2178685b62c4..879d372b9e42fdcc112067941fa6e0da7c2ee714 100644 --- a/src/main/java/com/dre0059/articleprocessor/dto/TagDto.java +++ b/src/main/java/com/dre0059/articleprocessor/dto/TagDto.java @@ -3,6 +3,10 @@ package com.dre0059.articleprocessor.dto; import lombok.Getter; import lombok.Setter; +/** + * DTO reprezentujĂşce tagy (kľúčovĂ© slová) priradenĂ© k dokumentu + * + */ @Getter @Setter public class TagDto { diff --git a/src/main/java/com/dre0059/articleprocessor/mapper/CategoryMapper.java b/src/main/java/com/dre0059/articleprocessor/mapper/CategoryMapper.java index c85cbc84cbeb82bee921bc2e127d2e657591bfc1..ed93917e5381599c1a0d1cb943353bb4d1bf1c2d 100644 --- a/src/main/java/com/dre0059/articleprocessor/mapper/CategoryMapper.java +++ b/src/main/java/com/dre0059/articleprocessor/mapper/CategoryMapper.java @@ -5,13 +5,19 @@ import com.dre0059.articleprocessor.model.Category; import java.util.List; import org.mapstruct.Mapper; +/** + * MapStruct mapper na prevod medzi entitou {@link Category} a DTO objektom {@link CategoryDto}. + * + * Obsahuje metĂłdy na mapovanie + */ @Mapper(componentModel = "spring") public interface CategoryMapper { - + /*** Premapuje entitu {@link Category} na {@link CategoryDto}.*/ CategoryDto toCategoryDto(Category entity); Category toCategory(CategoryDto categoryDto); + /*** Premapuje zoznam entĂt {@link Category} na zoznam DTO {@link CategoryDto}.*/ List<CategoryDto> toCategoryDtoList(List<Category> entities); } diff --git a/src/main/java/com/dre0059/articleprocessor/mapper/DocumentMapper.java b/src/main/java/com/dre0059/articleprocessor/mapper/DocumentMapper.java index 2823a862b169014073c98e0c804516baf8e4d7f1..e26d71e1d77f2a9677571f439514c62e20cefdc8 100644 --- a/src/main/java/com/dre0059/articleprocessor/mapper/DocumentMapper.java +++ b/src/main/java/com/dre0059/articleprocessor/mapper/DocumentMapper.java @@ -6,20 +6,46 @@ import com.dre0059.articleprocessor.dto.SimpleDocumentDto; import com.dre0059.articleprocessor.model.Dokument; import java.util.List; import org.mapstruct.Mapper; -import org.mapstruct.Mapping; + +/** + * MapStruct mapper na prevod medzi entitou {@link Dokument} a (Simple)DocumentDTO, DocumentContentDTO. + */ @Mapper(componentModel = "spring", uses = {TagMapper.class}) public interface DocumentMapper { - //@Mapping(target = "publication_year", source = "year") + /** + * Mapuje entitu {@link Dokument} na DTO {@link DocumentDto} (kompletnĂ© info o dokumente) + * + * @param entity entita dokumentu + * @return DTO dokumentu + */ DocumentDto toDocumentDto(Dokument entity); + /** + * + * Premapuje entitu {@link Dokument} na DTO {@link DocumentContentDto}, + * pouĹľĂvanĂ© najmä na prenos binárneho obsahu dokumentu (PDF fformát) + */ DocumentContentDto toDocumentContentDto(Dokument entity); + /** + * Mapuje entitu {@link Dokument} na zjednodušenĂ© DTO {@link SimpleDocumentDto}, + * vhodnĂ© na vĂ˝pisy zoznamov. + */ SimpleDocumentDto toSimpleDocumentDto(Dokument entity); + /** + * Premapuje zoznam entĂt {@link Dokument} na zoznam zjednodušenĂ˝ch DTO {@link SimpleDocumentDto}. + */ List<SimpleDocumentDto> toSimpleDocumentList(List<Dokument> entities); + /** + * Pomocná metĂłda na konverziu poÄľa bytov na {@link String}. + * + * @param bytes pole bytov + * @return textová reprezentácia poÄľa bytov (prĂp. {@code null}) + */ default String toString(byte[] bytes) { if (bytes == null) { return null; diff --git a/src/main/java/com/dre0059/articleprocessor/mapper/TagMapper.java b/src/main/java/com/dre0059/articleprocessor/mapper/TagMapper.java index 824318d9536753eb8af47a19ece4657ab4d46487..b8ee6cd41cf58ae56c475912ee53bc08ebddff22 100644 --- a/src/main/java/com/dre0059/articleprocessor/mapper/TagMapper.java +++ b/src/main/java/com/dre0059/articleprocessor/mapper/TagMapper.java @@ -6,13 +6,39 @@ import org.mapstruct.Mapper; import java.util.List; +/** + * MapStruct mapper na prevod entity {@link Tag} a DTO objektom {@link TagDto}. + * + * Obsahuje metĂłdy na mapovanie v oboch smeroch. + */ @Mapper(componentModel = "spring") public interface TagMapper { + /** + * mapuje entitu {@link Tag} na {@link TagDto} + * @param tag (entita) + * @return DTO tagu + */ TagDto toTagDto(Tag tag); + /** + * mapuje {@link TagDto} na entitu {@link Tag} + * @param tagDto + * @return tag (entita) + */ Tag toTag(TagDto tagDto); + /** + * mapuje zoznam entĂt {@link Tag} na zoznam DTO {@link TagDto} + * @param tags + * @return tagDTOS + */ List<TagDto> toTagDtoList(List<Tag> tags); + + /** + * mapuje zoznam DTO {@link TagDto} na zoznam entĂt {@link Tag} + * @param tagDtos + * @return tags + */ List<Tag> toTagList(List<TagDto> tagDtos); } diff --git a/src/main/java/com/dre0059/articleprocessor/model/Author.java b/src/main/java/com/dre0059/articleprocessor/model/Author.java index 6d86454462ba7b9bbaf472530cd0ecba3db7e950..93bb2bb90b3ee6c7ed47c98d846da5ac3e2a2631 100644 --- a/src/main/java/com/dre0059/articleprocessor/model/Author.java +++ b/src/main/java/com/dre0059/articleprocessor/model/Author.java @@ -1,12 +1,16 @@ package com.dre0059.articleprocessor.model; import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; import java.util.ArrayList; import java.util.List; @Entity @Table(name = "authors") +@Getter +@Setter public class Author { @Id @@ -25,12 +29,10 @@ public class Author { this.lastName = lastName; } - public Long getId() { return id; } + // mala som tu TYPO vo velkom a malom pĂsmene, všimla som si to nakonci, preto som nevyuĹľila GETTER a SETTER, aby som nemusela navyše prepisovaĹĄ ÄŤasti kĂłdu public String getFirstname() { return firstName; } public String getLastname() { return lastName; } - public List<Dokument> getDocuments() { return documents; } public void setFirstname(String name) { this.firstName = name; } public void setLastname(String surname) { this.lastName = surname; } - public void setDocuments(List<Dokument> documents) { this.documents = documents; } } diff --git a/src/main/resources/templates/citation-timeline.html b/src/main/resources/templates/citation-timeline.html index c980ab2b0f273135e8b1a1245b1e93d12f913554..6cdff9b8001415aafddd787b3afaeacc6eb257f7 100644 --- a/src/main/resources/templates/citation-timeline.html +++ b/src/main/resources/templates/citation-timeline.html @@ -1,6 +1,7 @@ <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> + <!-- zobrazuje graf ÄŤlánku na /statistics/citation-timeline?documentId={id} --> <meta charset="UTF-8"> <title>Document Statistics</title> <script src="https://cdn.plot.ly/plotly-1.58.5.min.js"></script> @@ -110,7 +111,7 @@ </div> </header> - <!-- sofistikovanĂ˝ graf --> + <!-- graf --> <div class="container mt-5"> <div class="chart-container"> <div id="referenceTimelineChart" class="chart-box"></div> @@ -119,7 +120,7 @@ <script th:inline="javascript"> - // Predpokladám, Ĺľe tieto Ăşdaje sĂş dostupnĂ© z backendu cez Thymeleaf. + // Ăşdaje sĂş dostupnĂ© z backendu cez Thymeleaf. const mainArticle = { title: [[${mainArticleTitle}]], year: [[${mainArticleYear}]], @@ -127,21 +128,21 @@ }; const references = [[${references}]]; - const categories = [[${categories}]]; // KategĂłrie, ktorĂ© dostaneme z databázy + const categories = [[${categories}]]; // KategĂłrie z databázy console.log("Categories received:", categories); - // Predstavujeme kategĂłrie ako mapu (indexovanie podÄľa názvu kategĂłrie) + // indexovanie podÄľa názvu kategĂłrie const categoryIndex = categories.reduce((acc, category, index) => { acc[category] = index + 1; // ZaÄŤneme od 1, aby sme vyhli hodnotám 0 return acc; }, {}); - // Funkcia pre zĂskanie Y-ovej hodnoty podÄľa kategĂłrie + // zĂskanie Y-ovej hodnoty podÄľa kategĂłrie function getCategoryY(categoryName) { return categoryIndex[categoryName] || 0; // Ak kategĂłria nie je v zozname, pouĹľijeme hodnotu 0 } - // PripravĂme hlavnĂ˝ dokument + // hlavnĂ˝ dokument (1,3,212) const mainTrace = { x: [mainArticle.year], y: [getCategoryY(mainArticle.category)], @@ -153,11 +154,10 @@ size: 14, color: 'gold' }, - // Po kliknutĂ na marker sa zobrazĂ informácia customdata: [{ title: mainArticle.title, year: mainArticle.year, category: mainArticle.category }] }; - // PripravĂme referencie + // referencie dokumentu const referenceTrace = { x: references.map(r => r.year), y: references.map(r => getCategoryY(r.category)), @@ -173,7 +173,7 @@ customdata: references.map(r => ({ title: r.title, year: r.year, category: r.category })) }; - // Spojovacie ÄŤiary medzi referenciami a hlavnĂ˝m dokumentom + // Spojovacie ÄŤiary const connectionLines = references.map(ref => ({ type: 'line', x0: ref.year, @@ -194,8 +194,8 @@ plot_bgcolor: '#6780a3', font: { color: '#ffffff' }, margin: { - l: 180, // zväčšà ľavĂ˝ okraj pre ÄŤitateÄľnosĹĄ názvov kategĂłriĂ - t: 150 // zväčšà hornĂ˝ okraj pre pridanie odsadenia pod nadpis + l: 180, // zväčšà ľavĂ˝ okraj + t: 150 // zväčšà hornĂ˝ okraj }, xaxis: { title: 'Publication Year', @@ -217,14 +217,13 @@ shapes: connectionLines, legend: { orientation: 'h', // horizontálne usporiadanie legendy - yanchor: 'bottom', // pozĂcia legendy - y: 1.1, // trošku nad grafom + yanchor: 'bottom', + y: 1.1, xanchor: 'center', x: 0.5 } }; - // Vytvorenie grafu bez pridania event listenera na kliknutie Plotly.newPlot('referenceTimelineChart', [mainTrace, referenceTrace], layout, { responsive: true }); </script> diff --git a/src/main/resources/templates/more-citations.html b/src/main/resources/templates/more-citations.html new file mode 100644 index 0000000000000000000000000000000000000000..b59b664c021fb4ca37c4ed573367b59b821023f4 --- /dev/null +++ b/src/main/resources/templates/more-citations.html @@ -0,0 +1,259 @@ +<!DOCTYPE html> +<html xmlns:th="http://www.thymeleaf.org"> +<head> + <!-- zobrazuje sa na stránke /statistics/more-citations --> + <meta charset="UTF-8"> + <title>Document Statistics</title> + <script src="https://cdn.plot.ly/plotly-1.58.5.min.js"></script> + + <link rel="icon" type="image/x-icon" href="/assets/favicon.ico" /> + <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css" rel="stylesheet" /> + <link href="https://fonts.googleapis.com/css?family=Lato:300,400,700,300italic,400italic,700italic" rel="stylesheet" /> + + <link rel="stylesheet" th:href="@{/styles.css}" /> + + <style> + .navbar-nav .nav-link { + font-size: 1.2rem; + margin-right: 20px; + color: rgb(51, 102, 255); + } + + .navbar-nav .nav-link:hover { + color: #00134d; + } + + .navbar-nav .nav-link.active { + color: rgb(51, 102, 255) !important; + } + + .navbar-nav .nav-link.active:hover { + color: #00134d !important; + } + + h2 { + color: #ffffff; + text-align: center; + margin-top: 30px; + } + + .chart-container { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + gap: 20px; + margin-top: 30px; + } + + .chart-box { + flex: 1; + min-width: 400px; + height: 500px; + background-color: #6780a3; + border-radius: 12px; + padding: 15px; + overflow: hidden; + } + + .chart-box:hover { + transform: scale(1.01); + transition: transform 0.2s ease-in-out; + } + + .chart-box .js-plotly-plot { + position: absolute; + top: 0; + left: 0; + width: 100% !important; /* natiahne graf presne na šĂrku boxu */ + height: 100% !important; /* natiahne graf presne na výšku boxu */ + } + </style> +</head> + +<body> +<!-- Navbar --> +<nav class="navbar navbar-expand-lg navbar-light bg-light static-top"> + <div class="container"> + <a class="navbar-brand" href="/upload">Article Processor</a> + <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" + aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> + <span class="navbar-toggler-icon"></span> + </button> + <div class="collapse navbar-collapse justify-content-end" id="navbarNav"> + <ul class="navbar-nav"> + <li class="nav-item"> + <a class="nav-link" href="/upload">Upload</a> + </li> + <li class="nav-item"> + <a class="nav-link" href="/view">View all</a> + </li> + <li class="nav-item"> + <a class="nav-link" href="/statistics">Statistics</a> + </li> + <li class="nav-item"> + <a class="nav-link" href="/about">About project</a> + </li> + </ul> + </div> + </div> +</nav> + +<!-- Page Content --> +<header class="masthead"> + <div class="container position-relative"> + <div class="row justify-content-center"> + <div class="col-xl-8"> + <div class="text-center text-white"> + <h1 class="mb-4">📊 Graph of references</h1> + </div> + </div> + </div> + </div> +</header> + +<!-- sofistikovanĂ˝ graf --> +<div class="container mt-5"> + <div class="chart-container"> + <div id="referenceTimelineChart" class="chart-box"></div> + </div> +</div> + +<script th:inline="javascript"> + const mainArticles = [[${mainArticles}]]; // [{id, title, year, category}] + const references = [[${allReferences}]]; // [{mainArticleId, title, year, category}] + const categories = [[${categories}]]; + + const categoryIndex = categories.reduce((acc, category, index) => { + acc[category] = index + 1; + return acc; + }, {}); + + function getCategoryY(categoryName) { + return categoryIndex[categoryName] || 0; + } + + // farby ÄŤlánkov + const colors = ['gold', 'orange', 'limegreen', 'magenta', 'cyan', 'coral', 'plum']; + + const traces = []; + const connectionLines = []; + const mainArticleIds = new Set(mainArticles.map(a => a.id)); + + mainArticles.forEach((article, i) => { + const color = colors[i % colors.length]; + const articleY = getCategoryY(article.category); + + // HlavnĂ˝ ÄŤlánok + traces.push({ + x: [article.year], + y: [articleY], + mode: 'markers', + type: 'scatter', + name: `Main: ${article.title}`, + hoverinfo: 'text', + marker: { + size: 16, + color: color, + }, + customdata: [article] + }); + + // Referencie danĂ©ho ÄŤlánku + const articleRefs = references.filter(ref => ref.mainArticleId === article.id); + traces.push({ + x: articleRefs.map(r => r.year), + y: articleRefs.map(r => getCategoryY(r.category)), + mode: 'markers', + type: 'scatter', + name: `References to ${article.title}`, + text: articleRefs.map(r => r.title), + hoverinfo: 'text', + marker: { + size: 10, + color: color, + opacity: 0.6 + }, + customdata: articleRefs + }); + + // Spojovacie ÄŤiary + articleRefs.forEach(ref => { + const refY = getCategoryY(ref.category); + if (refY > 0) { + connectionLines.push({ + type: 'line', + x0: ref.year, + y0: refY, + x1: article.year, + y1: articleY, + line: { + color: color, + width: 1.5, + dash: 'dot' + } + }); + } + }); + + // referencie medzi hlavnĂ˝mi ÄŤlánkami (pridá pevnĂş ÄŤiaru) + articleRefs.forEach(ref => { + const matchingMain = mainArticles.find(a => a.title === ref.title && a.year === ref.year); + if (matchingMain) { + const refY = getCategoryY(matchingMain.category); + connectionLines.push({ + type: 'line', + x0: article.year, + y0: articleY, + x1: matchingMain.year, + y1: refY, + line: { + color: color, + width: 2, + dash: 'solid' + } + }); + } + }); + }); + + const layout = { + paper_bgcolor: '#6780a3', + plot_bgcolor: '#6780a3', + font: { color: '#ffffff' }, + margin: { l: 180, t: 150 }, + xaxis: { + title: 'Publication Year', + showgrid: true, + zeroline: false, + range: [1950, 2030] + }, + yaxis: { + title: 'Category', + tickmode: 'array', + tickvals: categories.map((_, index) => index + 1), + ticktext: categories, + showgrid: true, + automargin: true, + tickfont: { size: 14 } + }, + shapes: connectionLines, + legend: { + orientation: 'h', + yanchor: 'bottom', + y: 1.1, + xanchor: 'center', + x: 0.5 + } + }; + + Plotly.newPlot('referenceTimelineChart', traces, layout, { responsive: true }); +</script> + + +<footer class="footer bg-light mt-5"> + <div class="container text-center"> + <p class="text-muted small">© Eliška Kozáčiková 2025. All Rights Reserved.</p> + </div> +</footer> +</body> +</html>