Skip to content

Commit

Permalink
Ability to search by tags
Browse files Browse the repository at this point in the history
  • Loading branch information
R-Sandor committed Nov 29, 2024
1 parent 2b54ba2 commit 2e480ea
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import java.util.List;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

import dev.findfirst.core.dto.BookmarkDTO;
import dev.findfirst.core.dto.BookmarkOnly;
import dev.findfirst.core.model.SearchBkmkByTagReq;
Expand All @@ -12,6 +12,7 @@
import dev.findfirst.core.service.SearchService;
import dev.findfirst.core.utilies.Response;

import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -26,17 +27,16 @@ public class SearchController {


@GetMapping("/api/search/tags")
public ResponseEntity<List<BookmarkDTO>> bookmarkSearchByTag(
public ResponseEntity<List<BookmarkOnly>> bookmarkSearchByTag(
@Valid @ModelAttribute SearchBkmkByTagReq searchBkmkByTagReq) {
// GH ISSUE #279
return ResponseEntity
.ok(List.of(new BookmarkDTO(0, null, null, null, false, null, null, null)));
return ResponseEntity.ok(search.bookmarksByTags(searchBkmkByTagReq.tags()));
}

@GetMapping("/api/search/title")
public ResponseEntity<List<BookmarkOnly>> bookMarkSearchByTitleKeywords(
@Valid @ModelAttribute SearchBkmkByTitleReq searchBkmkByTitleReq) {
return new Response<>(search.titleKeywordSearch(searchBkmkByTitleReq.keywords()), HttpStatus.OK).get();
return new Response<>(search.titleKeyword(searchBkmkByTitleReq.keywords()), HttpStatus.OK)
.get();
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package dev.findfirst.core.model;

import jakarta.validation.constraints.NotBlank;
import java.util.Arrays;

import jakarta.validation.constraints.NotEmpty;

public record SearchBkmkByTagReq(@NotEmpty String[]tags){@Override public boolean equals(Object obj){if(obj instanceof SearchBkmkByTagReq tagSearch){return Arrays.equals(this.tags(),tagSearch.tags());}return false;}

@Override public int hashCode(){return Arrays.hashCode(this.tags());}

@Override public String toString(){return Arrays.toString(this.tags());}

public record SearchBkmkByTagReq(@NotBlank String tag) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,10 @@

import jakarta.validation.constraints.NotEmpty;

public record SearchBkmkByTitleReq(@NotEmpty String [] keywords) {
public record SearchBkmkByTitleReq(@NotEmpty String[]keywords){

@Override
public boolean equals(Object obj) {
if (obj instanceof SearchBkmkByTitleReq titleSearch) {
return Arrays.equals(this.keywords(), titleSearch.keywords());
}
return false;
}
@Override public boolean equals(Object obj){if(obj instanceof SearchBkmkByTitleReq titleSearch){return Arrays.equals(this.keywords(),titleSearch.keywords());}return false;}

@Override
public int hashCode() {
return Arrays.hashCode(this.keywords());
}
@Override public int hashCode(){return Arrays.hashCode(this.keywords());}

@Override
public String toString() {
return Arrays.toString(this.keywords());
}
}
@Override public String toString(){return Arrays.toString(this.keywords());}}
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,17 @@ public interface BookmarkJDBCRepository extends CrudRepository<BookmarkJDBC, Lon
@Query("select * from bookmark where to_tsvector(title) @@ to_tsquery(:keywords) AND bookmark.user_id = :userID")
List<BookmarkJDBC> titleKeywordSearch(String keywords, int userID);

@Query("""
SELECT *
FROM bookmark
WHERE user_id = :userID AND id IN (
SELECT bookmark_id
FROM bookmark_tag
WHERE bookmark_tag.tag_id IN (
SELECT id FROM Tag t WHERE t.user_id = :userID AND t.tag_title IN (:tagTitles)
)
)
""")
List<BookmarkJDBC> findBookmarkByTagTitle(@Param("tagTitles") List<String> tagTitles,
@Param("userID") int userID);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public interface TagJDBCRepository extends CrudRepository<TagJDBC, Long> {
@Query("SELECT COUNT(*) FROM tag WHERE user_id = :userID")
Integer countUserTags(@Param("userID") Integer userID);

@Query("SELECT * FROM Tag t WHERE t.user_id = :userID AND t.tag_title IN :tagTitles ")
@Query("SELECT * FROM Tag t WHERE t.user_id = :userID AND t.tag_title IN :tagTitles")
List<TagJDBC> findByTagTitles(@Param("tagTitles") List<String> tagTitles,
@Param("userID") int userID);
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,9 @@ public List<BookmarkDTO> convertBookmarkJDBCToDTO(List<BookmarkJDBC> bookmarkEnt
public List<BookmarkOnly> convertBookmarkJDBCToBookmarkOnly(List<BookmarkJDBC> bookmarkEntities) {
// Get the bookmarks that are associated to the Tag.
return bookmarkEntities.stream()
.map(ent -> new BookmarkOnly(ent.getId(), ent.getTitle(), ent.getUrl(), ent.getScreenshotUrl(),
ent.getScrapable(), ent.getCreatedDate(), ent.getLastModifiedDate()))
.map(ent -> new BookmarkOnly(ent.getId(), ent.getTitle(), ent.getUrl(),
ent.getScreenshotUrl(), ent.getScrapable(), ent.getCreatedDate(),
ent.getLastModifiedDate()))
.toList();
}

Expand Down Expand Up @@ -154,8 +155,9 @@ public BookmarkDTO addBookmark(AddBkmkReq reqBkmk)

var savedTags = new HashSet<BookmarkTag>();

var newBkmkJdbc = new BookmarkJDBC(null, user.getUserId(), new Date(), user.getUsername(), user.getUsername(),
new Date(), title, reqBkmk.url(), screenshotUrlOpt.orElse(""), true, savedTags);
var newBkmkJdbc =
new BookmarkJDBC(null, user.getUserId(), new Date(), user.getUsername(), user.getUsername(),
new Date(), title, reqBkmk.url(), screenshotUrlOpt.orElse(""), true, savedTags);

var saved = bookmarkJDBCRepository.save(newBkmkJdbc);
for (var tag : tags) {
Expand Down Expand Up @@ -186,10 +188,8 @@ public BookmarkDTO addTagById(BookmarkJDBC bk, long tagId) {
}

/**
* Exports bookmarks by their tag groups. The largest tag groups are exported
* first. Any bookmark
* already accounted for in that group will be excluded from any other group
* that it was also
* Exports bookmarks by their tag groups. The largest tag groups are exported first. Any bookmark
* already accounted for in that group will be excluded from any other group that it was also
* tagged.
*
* @return String representing HTLM file.
Expand All @@ -213,17 +213,14 @@ public String export() {
}

/**
* Checks if a bookmark has already been found in previous tag group. If it has
* not it is added to
* uniques, and the id added to map for fast lookups. Finally record that
* contains the title of
* the tag `cooking` `docs` for example is created with it associated bookmarks.
* The record is
* Checks if a bookmark has already been found in previous tag group. If it has not it is added to
* uniques, and the id added to map for fast lookups. Finally record that contains the title of
* the tag `cooking` `docs` for example is created with it associated bookmarks. The record is
* added to uniqueBkmkWithTags.
*
* @param t Tag
* @param uniques List<Bookmark> of uniques
* @param alreadyFound Map<Long, Long> for fast lookup
* @param t Tag
* @param uniques List<Bookmark> of uniques
* @param alreadyFound Map<Long, Long> for fast lookup
* @param uniqueBkmksWithTag Record of Tag Title with Bookmark.
*/
private void addUniqueBookmarks(TagDTO t, List<BookmarkOnly> uniques,
Expand Down
24 changes: 10 additions & 14 deletions server/src/main/java/dev/findfirst/core/service/SearchService.java
Original file line number Diff line number Diff line change
@@ -1,44 +1,40 @@
package dev.findfirst.core.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringJoiner;

import dev.findfirst.core.dto.BookmarkOnly;
import dev.findfirst.core.dto.TagDTO;
import dev.findfirst.core.model.jdbc.TagJDBC;
import dev.findfirst.core.repository.jdbc.BookmarkJDBCRepository;
import dev.findfirst.core.repository.jdbc.TagJDBCRepository;
import dev.findfirst.security.userauth.context.UserContext;
import lombok.RequiredArgsConstructor;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Slf4j
public class SearchService {

private final TagJDBCRepository tagRepository;

private final BookmarkJDBCRepository bookmarkRepo;

private final BookmarkService bookmarkService;

private final UserContext userContext;

public List<BookmarkOnly> titleKeywordSearch(String[] keywords) {
public List<BookmarkOnly> titleKeyword(String[] keywords) {
StringJoiner joiner = new StringJoiner(" | ");
for (String kw : keywords) {
joiner.add(kw);
}
return bookmarkService
.convertBookmarkJDBCToBookmarkOnly(bookmarkRepo.titleKeywordSearch(joiner.toString(), userContext.getUserId()));
return bookmarkService.convertBookmarkJDBCToBookmarkOnly(
bookmarkRepo.titleKeywordSearch(joiner.toString(), userContext.getUserId()));
}

public List<BookmarkOnly> bookmarkSearchByTagTitles(List<String> titles) {
List<TagJDBC> foundTags = tagRepository.findByTagTitles(titles, userContext.getUserId());
List<BookmarkOnly> foundBookmarks = new ArrayList<>();
return foundBookmarks;
public List<BookmarkOnly> bookmarksByTags(String[] tags) {
return bookmarkService.convertBookmarkJDBCToBookmarkOnly(
bookmarkRepo.findBookmarkByTagTitle(Arrays.asList(tags), userContext.getUserId()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import static dev.findfirst.utilities.HttpUtility.getHttpEntity;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.ArrayList;

import dev.findfirst.core.annotations.IntegrationTest;
import dev.findfirst.core.dto.BookmarkDTO;
import dev.findfirst.core.model.SearchBkmkByTagReq;
import dev.findfirst.core.dto.BookmarkOnly;
import dev.findfirst.core.model.SearchBkmkByTextReq;
import dev.findfirst.core.model.SearchBkmkByTitleReq;

Expand Down Expand Up @@ -36,24 +39,32 @@ class SearchControllerTest {

@Test
void searchByTags() {
var resp = restTemplate.exchange("/api/search/tags?tag={}", HttpMethod.GET,
getHttpEntity(restTemplate), BookmarkDTO[].class, new SearchBkmkByTagReq("Test"));
var resp = restTemplate.exchange("/api/search/tags?tags={tag1},{tag2}", HttpMethod.GET,
getHttpEntity(restTemplate), BookmarkOnly[].class, "Cooking", "web_dev");
assertEquals(HttpStatus.OK, resp.getStatusCode());
var bkmks = resp.getBody();
assertEquals(3, bkmks.length);
var titles = new ArrayList<String>();
for (int i = 0; i < 3; i++) {
titles.add(bkmks[i].title());
}
assertTrue(titles.contains("Best Cheesecake Recipe"));
}

@Test
void searchByTitle() {
var resp = restTemplate.exchange("/api/search/title?keywords={}", HttpMethod.GET,
getHttpEntity(restTemplate), BookmarkDTO[].class, new SearchBkmkByTitleReq(new String[]{"Test", "Title"}));
getHttpEntity(restTemplate), BookmarkDTO[].class,
new SearchBkmkByTitleReq(new String[] {"Test", "Title"}));

assertEquals(HttpStatus.OK, resp.getStatusCode());
}

@Test
void searchByText() {
var resp =
restTemplate.exchange("/api/search/text?text={}", HttpMethod.GET, getHttpEntity(restTemplate),
BookmarkDTO[].class, new SearchBkmkByTextReq("Text in bookmark"));
var resp = restTemplate.exchange("/api/search/text?text={}", HttpMethod.GET,
getHttpEntity(restTemplate), BookmarkDTO[].class,
new SearchBkmkByTextReq("Text in bookmark"));

assertEquals(HttpStatus.OK, resp.getStatusCode());
}
Expand Down

0 comments on commit 2e480ea

Please sign in to comment.