From e1882e5a7fb33d0b7f7459f3dc0c38a451fa481c Mon Sep 17 00:00:00 2001 From: Kiyan Date: Fri, 19 Dec 2025 18:53:58 +0200 Subject: [PATCH] - Functioning photo update - Functioning photo search --- .../Controller/PhotoHomeController.java | 4 +- .../Controller/PhotoViewController.java | 50 ++-- .../example/PhotoGallery/Models/Photo.java | 9 +- .../com/example/PhotoGallery/Models/Tag.java | 24 -- .../example/PhotoGallery/Repos/PhotoRepo.java | 31 ++- .../example/PhotoGallery/Repos/TagRepo.java | 22 -- .../PhotoGallery/Services/PhotoService.java | 34 +-- src/main/resources/templates/home.html | 244 ++++++++++++----- src/main/resources/templates/photo.html | 259 ++++++++---------- 9 files changed, 356 insertions(+), 321 deletions(-) delete mode 100644 src/main/java/com/example/PhotoGallery/Models/Tag.java delete mode 100644 src/main/java/com/example/PhotoGallery/Repos/TagRepo.java diff --git a/src/main/java/com/example/PhotoGallery/Controller/PhotoHomeController.java b/src/main/java/com/example/PhotoGallery/Controller/PhotoHomeController.java index 61ce919..dedbdac 100644 --- a/src/main/java/com/example/PhotoGallery/Controller/PhotoHomeController.java +++ b/src/main/java/com/example/PhotoGallery/Controller/PhotoHomeController.java @@ -50,14 +50,14 @@ public class PhotoHomeController { } pageCounter++; - model.addAttribute("images", photoService.getPagedPhotos(pageCounter, 10)); + model.addAttribute("images", photoService.getPagedPhotos(pageCounter, 30)); return "home :: images"; } @PostMapping("/search") public String search(Model model, @RequestParam("search") String search) { - List searchedPhotos = photoService.findAllBySearch(search); + List searchedPhotos = photoService.searchPhotos(search); model.addAttribute("images", searchedPhotos); diff --git a/src/main/java/com/example/PhotoGallery/Controller/PhotoViewController.java b/src/main/java/com/example/PhotoGallery/Controller/PhotoViewController.java index 73fc899..a6ebef7 100644 --- a/src/main/java/com/example/PhotoGallery/Controller/PhotoViewController.java +++ b/src/main/java/com/example/PhotoGallery/Controller/PhotoViewController.java @@ -21,40 +21,35 @@ public class PhotoViewController { @Autowired private PhotoService photoService; - private ArrayList tags = new ArrayList<>(); - @GetMapping("/photo/{id}") private String photoView(Model model, @PathVariable("id") Long id) { - // reset tags on page load - tags.clear(); - final int PAGE_SIZE = 30; final int PAGE_NUM = 0; Photo photo = photoService.findById(id); - model.addAttribute("thumbnails", photoService.findAllExpect(List.of(id), PAGE_NUM, PAGE_SIZE)); model.addAttribute("photo", photo); + model.addAttribute("tags", photo.getTags()); + model.addAttribute("thumbnails", + photoService.findAllExpect(List.of(id), PAGE_NUM, PAGE_SIZE)); return "photo"; } - @PostMapping("/photo/{id}") - private String updatePhotoDetails(@PathVariable("id") Long id, - @ModelAttribute Photo photo) { +@PostMapping("/photo/{id}") +private String updatePhotoDetails(@PathVariable Long id, + @ModelAttribute Photo formPhoto) { - photoService.updatePhotoDetails(id, photo, tags); + photoService.updatePhotoDetails(id, formPhoto); + return "redirect:/photo/" + id; +} - System.out.println(photo); - - return "redirect:/photo/" + id; - } @GetMapping("/loadMoreThumbnails") - private String loadMoreThumbnails(Model model, - @RequestParam("id") Long id, - @RequestParam("pageNum") int page) { + private String loadMoreThumbnails(Model model, + @RequestParam("id") Long id, + @RequestParam("pageNum") int page) { model.addAttribute("thumbnails", photoService.findAllExpect(List.of(id), page + 1, 30)); @@ -65,17 +60,24 @@ public class PhotoViewController { } @PostMapping("/tags/add") - public String addTag(@RequestParam String tag, + public String addTag(@RequestParam Long photoId, + @RequestParam String newTag, Model model) { - String normalized = tag.trim().toUpperCase(); - if (normalized.isEmpty() || tags.contains(normalized)) { + + String normalized = newTag.trim().toUpperCase(); + if (normalized.isEmpty()) { return "photo :: empty"; } - tags.add(normalized); - model.addAttribute("tag", normalized); + Photo photo = photoService.findById(photoId); - return "photo:: badge"; + if (!photo.getTags().contains(normalized)) { + photo.getTags().add(normalized); + photoService.save(photo); + } + + model.addAttribute("tag", normalized); + return "photo :: tagBadge(tag=${tag})"; } @PostMapping("/tags/remove") @@ -86,7 +88,7 @@ public class PhotoViewController { } @PostMapping("/photo/favorite/{id}") - private String setFavourite(@PathVariable("id") Long id){ + private String setFavourite(@PathVariable("id") Long id) { photoService.setFavourite(id); diff --git a/src/main/java/com/example/PhotoGallery/Models/Photo.java b/src/main/java/com/example/PhotoGallery/Models/Photo.java index b8bfba8..55394e4 100644 --- a/src/main/java/com/example/PhotoGallery/Models/Photo.java +++ b/src/main/java/com/example/PhotoGallery/Models/Photo.java @@ -2,12 +2,14 @@ package com.example.PhotoGallery.Models; import java.util.List; +import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; import jakarta.persistence.ElementCollection; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; import jakarta.persistence.Table; import lombok.*; @@ -26,8 +28,11 @@ public class Photo { @Column(length = 255) private String description; - @Column - private List tags; + @ElementCollection + @CollectionTable(name = "photo_tags", + joinColumns = @JoinColumn(name = "photo_id")) + @Column(name = "tag") + private List tags; @Column private Integer width; diff --git a/src/main/java/com/example/PhotoGallery/Models/Tag.java b/src/main/java/com/example/PhotoGallery/Models/Tag.java deleted file mode 100644 index fa4742d..0000000 --- a/src/main/java/com/example/PhotoGallery/Models/Tag.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.PhotoGallery.Models; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import lombok.Data; - -@Entity -@Table(name = "tags") -@Data -public class Tag { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column - private String name; - - -} diff --git a/src/main/java/com/example/PhotoGallery/Repos/PhotoRepo.java b/src/main/java/com/example/PhotoGallery/Repos/PhotoRepo.java index cc8dfde..6bef206 100644 --- a/src/main/java/com/example/PhotoGallery/Repos/PhotoRepo.java +++ b/src/main/java/com/example/PhotoGallery/Repos/PhotoRepo.java @@ -26,22 +26,25 @@ public interface PhotoRepo extends JpaRepository { List getRandomPhotos(@Param("numRecords") int numRandRecords); @Query(value = """ - SELECT * - FROM photos - WHERE - CONCAT_WS(' ', - colour_space, - copy_right, - file_name, - file_size, - height, - mime_type, - title, - width) LIKE CONCAT('%', :search, '%') + SELECT DISTINCT p.* + FROM photos p + LEFT JOIN photo_tags pt ON pt.photo_id = p.id + WHERE + CONCAT_WS(' ', + p.colour_space, + p.copy_right, + p.file_name, + p.file_size, + p.height, + p.mime_type, + p.title, + p.description, + p.width + ) LIKE CONCAT('%', :searchValue, '%') + OR pt.tag LIKE CONCAT('%', :searchValue, '%') """, nativeQuery = true) - List findAllBySearch(@Param("search") String search); + List searchPhotos(@Param("searchValue") String searchValue); - // Ensure :ids is never null when calling this, or the query needs "OR :ids IS NULL" @Query(value = "SELECT * FROM photos WHERE id NOT IN (:ids)", nativeQuery = true) Page findAllExpect(@Param("ids") List ids, Pageable pageable); diff --git a/src/main/java/com/example/PhotoGallery/Repos/TagRepo.java b/src/main/java/com/example/PhotoGallery/Repos/TagRepo.java deleted file mode 100644 index 0a2eb96..0000000 --- a/src/main/java/com/example/PhotoGallery/Repos/TagRepo.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.example.PhotoGallery.Repos; - -import java.util.List; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import com.example.PhotoGallery.Models.Tag; - -@Repository -public interface TagRepo extends JpaRepository { - Boolean existsByName(String name); - - Tag findByName(String name); - - - @Query(value= "SELECT name FROM tags where id in (:tagIds)", nativeQuery= true) - List getAllTagNamesIn(@Param("tagIds") List tagIds); - -} diff --git a/src/main/java/com/example/PhotoGallery/Services/PhotoService.java b/src/main/java/com/example/PhotoGallery/Services/PhotoService.java index 64d9d3b..28f62bd 100644 --- a/src/main/java/com/example/PhotoGallery/Services/PhotoService.java +++ b/src/main/java/com/example/PhotoGallery/Services/PhotoService.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; @@ -17,9 +16,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import com.example.PhotoGallery.Models.Photo; -import com.example.PhotoGallery.Models.Tag; import com.example.PhotoGallery.Repos.PhotoRepo; -import com.example.PhotoGallery.Repos.TagRepo; @Service public class PhotoService { @@ -30,8 +27,6 @@ public class PhotoService { private ImageCompression imageCompression; @Autowired private PhotoRepo photoRepo; - @Autowired - private TagRepo tagRepo; private Path originalFilesPath; private Path thumbnailsPath; @@ -94,45 +89,32 @@ public class PhotoService { return photoRepo.getRandomPhotos(numRandRecords); } - public List findAllBySearch(String search) { + public List searchPhotos(String search) { - return photoRepo.findAllBySearch(search); + return photoRepo.searchPhotos(search); } public Photo findById(Long id) { return photoRepo.findById(id).get(); } + public void save(Photo photo){ + photoRepo.save(photo); + } + public Page findAllExpect(List ids, int pageNo, int pageSize) { Pageable pageable = PageRequest.of(pageNo, pageSize); return photoRepo.findAllExpect(ids, pageable); } - public void updatePhotoDetails(Long id, Photo newPhoto, List tagNames) { + public void updatePhotoDetails(Long id, Photo newPhoto) { Photo photo = photoRepo.findById(id) .orElseThrow(() -> new RuntimeException("No Photo with Id" + id)); photo.setTitle(newPhoto.getTitle()); photo.setDescription(newPhoto.getDescription()); - - List tagIds = new ArrayList<>(); - - for (String tagName : tagNames){ - // if the tagName is not in the Tags table - // we define a new Tag and insert it into the Tags table - if (!tagRepo.existsByName(tagName)) { - Tag tag = new Tag(); - tag.setName(tagName); - Tag newlyCreatedTag = tagRepo.save(tag); - tagIds.add(newlyCreatedTag.getId()); - } else { - Tag foundTag = tagRepo.findByName(tagName); - tagIds.add(foundTag.getId()); - } - } - - photo.setTags(tagIds); + photo.setTags(newPhoto.getTags()); photo.setCopyRight(newPhoto.getCopyRight()); photoRepo.save(photo); diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html index ab21bfc..387537f 100644 --- a/src/main/resources/templates/home.html +++ b/src/main/resources/templates/home.html @@ -1,115 +1,223 @@ - + - Photo Gallery + PhotoGallery + body { + background-color: #0b0c10; + color: #ffffff; + font-family: 'Inter', system-ui, -apple-system, sans-serif; + } + /* Glassmorphism Navbar */ + .navbar { + background: rgba(11, 12, 16, 0.8) !important; + backdrop-filter: blur(10px); + border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important; + } + + /* Carousel Refinement */ + #carousel-1 { + border-radius: 0 0 20px 20px; + overflow: hidden; + box-shadow: 0 10px 30px rgba(0,0,0,0.5); + } + + .carousel-item img { + filter: brightness(0.8); + transition: transform 10s ease; + } + + .carousel-item.active img { + transform: scale(1.1); + } + + /* THE 5-COLUMN GRID (Forces 5 columns on ALL screens) */ + .custom-photo-grid { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: var(--grid-gap); + padding: 0; + } + + .photo-card { + position: relative; + aspect-ratio: 1 / 1; + overflow: hidden; + background: #1f2833; + cursor: pointer; + } + + .photo-card img { + width: 100%; + height: 100%; + object-fit: cover; + transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); + } + + .photo-card:hover img { + transform: scale(1.1); + filter: brightness(1.1); + } + + /* Overlay effect on hover */ + .photo-overlay { + position: absolute; + inset: 0; + background: linear-gradient(to top, rgba(0,0,0,0.7) 0%, transparent 50%); + opacity: 0; + transition: opacity 0.3s; + display: flex; + align-items: flex-end; + padding: 8px; + } + + .photo-card:hover .photo-overlay { + opacity: 1; + } + + /* Search bar styling */ + .search-container { + position: relative; + max-width: 400px; + width: 100%; + } + + .search-container .form-control { + background: rgba(255,255,255,0.05); + border: 1px solid rgba(255,255,255,0.1); + color: white; + padding-left: 40px; + border-radius: 50px; + } + + .search-container .bi-search { + position: absolute; + left: 15px; + top: 50%; + transform: translateY(-50%); + color: rgba(255,255,255,0.5); + } + + /* HTMX Loading Indicator */ + .htmx-indicator { + display: none; + text-align: center; + padding: 20px; + } + .htmx-requesting .htmx-indicator { + display: block; + } + + /* Mobile specific adjustments to keep 5 columns usable */ + @media (max-width: 576px) { + :root { --grid-gap: 2px; } + .navbar-brand { font-size: 1.1rem; } + .carousel-item img { height: 250px !important; } + } + - -