Modified endpoint to properly filter Bookmarks based on Hidden flag on both the Bookmark entry itself and its associated tags

This commit is contained in:
2023-02-20 22:48:52 -06:00
parent c55b018c0d
commit 83a4fba905
7 changed files with 57 additions and 84 deletions

View File

@ -74,9 +74,9 @@ namespace YABA.API.Controllers
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<BookmarkResponse>), (int)HttpStatusCode.OK)]
public IActionResult GetAll(bool isHidden = false)
public IActionResult GetAll(bool showHidden = false)
{
var result = _bookmarkService.GetAll(isHidden);
var result = _bookmarkService.GetAll(showHidden);
return Ok(_mapper.Map<IEnumerable<BookmarkResponse>>(result));
}

View File

@ -1,13 +0,0 @@
namespace YABA.API
{
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
}

View File

@ -37,35 +37,29 @@ namespace YABA.Service
_mapper = mapper;
}
public IEnumerable<BookmarkDTO> GetAll(bool isHidden = false)
public IEnumerable<BookmarkDTO> GetAll(bool showHidden = false)
{
var currentUserId = GetCurrentUserId();
var userBookmarks = _roContext.Bookmarks.Where(x => x.UserId == currentUserId && x.IsHidden == isHidden).ToDictionary(k => k.Id, v => v);
var bookmarkTagsLookup = _roContext.BookmarkTags
.Include(x => x.Tag)
.Where(x => userBookmarks.Keys.Contains(x.BookmarkId))
.ToList()
.GroupBy(x => x.BookmarkId)
.ToDictionary(k => k.Key, v => v.ToList());
.Include(x => x.Bookmark)
.Include(x => x.Tag)
.Where(x => x.Bookmark.UserId == currentUserId && x.Tag.UserId == currentUserId)
.ToList()
.GroupBy(x => x.BookmarkId)
.ToDictionary(k => k.Key, v => new KeyValuePair<Bookmark, List<Tag>>(v.FirstOrDefault()?.Bookmark, v.Select(x => x.Tag).ToList()));
var userBookmarkDTOs = new List<BookmarkDTO>();
foreach(var bookmark in userBookmarks)
foreach (var bookmarkTag in bookmarkTagsLookup)
{
var bookmarkDTO = _mapper.Map<BookmarkDTO>(bookmark.Value);
bookmarkTagsLookup.TryGetValue(bookmark.Key, out var bookmarkTags);
if(bookmarkTags != null)
if ((showHidden && (bookmarkTag.Value.Key.IsHidden || bookmarkTag.Value.Value.Any(x => x.IsHidden))) // If showHidden = true, only add Bookmarks that are marked Hidden OR when any of its Tags are marked Hidden
|| (!showHidden && !bookmarkTag.Value.Key.IsHidden && bookmarkTag.Value.Value.All(x => !x.IsHidden))) // If showHidden = false, only add when Bookmark is not marked Hidden AND when any of its Tags are not marked as Hidden
{
foreach (var bookmarkTag in bookmarkTags)
{
var tagDTO = _mapper.Map<TagDTO>(bookmarkTag.Tag);
bookmarkDTO.Tags.Add(tagDTO);
}
var bookmarkDTO = _mapper.Map<BookmarkDTO>(bookmarkTag.Value.Key);
var bookmarkTagDTOs = _mapper.Map<IEnumerable<TagDTO>>(bookmarkTag.Value.Value);
bookmarkDTO.Tags.AddRange(bookmarkTagDTOs);
userBookmarkDTOs.Add(bookmarkDTO);
}
userBookmarkDTOs.Add(bookmarkDTO);
}
return userBookmarkDTOs;

View File

@ -2,9 +2,9 @@ import { callExternalApi } from "../apiHelper";
const apiServerUrl = `${process.env.REACT_APP_API_BASE_URL}/v1/Bookmarks`;
export const getAllBookmarks = async(accessToken, isHidden = false) => {
export const getAllBookmarks = async(accessToken, showHidden = false) => {
const config = {
url: `${apiServerUrl}?isHidden=${isHidden}`,
url: `${apiServerUrl}?showHidden=${showHidden}`,
method: "GET",
headers: {
"content-type": "application/json",

View File

@ -1,5 +1,5 @@
import { isDev } from "./isDevHelper";
import { getTagGroups } from "./tagsHelper";
import { getTagGroups, flattenTagArrays } from "./tagsHelper";
import { isSubset } from "./arrayHelper";
import { containsSubstring } from "./bookmarkHelper";
@ -7,5 +7,6 @@ export {
isDev,
getTagGroups,
isSubset,
containsSubstring
containsSubstring,
flattenTagArrays
}

View File

@ -14,4 +14,12 @@ export const getTagGroups = (allBookmarkTags) => {
return accumulator
}, []).sort((a, b) => (a.name < b.name) ? -1 : (a.name > b.name) ? 1 : 0);
}
};
export const flattenTagArrays = (allBookmarkTags) => allBookmarkTags.flat().reduce((accumulator, current) => {
if(!accumulator.find((item) => item.id === current.id)) {
accumulator.push(current);
}
return accumulator
}, []);

View File

@ -2,9 +2,9 @@ import React, { useEffect, useReducer, useState } from "react";
import { Alert, Col, Container, Row, Button, Modal } from "../components/external";
import { Bookmark } from "../components";
import { useAuth0 } from "@auth0/auth0-react";
import { deleteBookmarks, getAllBookmarks, getAllTags, hideBookmarks } from "../api";
import { deleteBookmarks, getAllBookmarks, hideBookmarks } from "../api";
import { SplashScreen, SearchForm } from "../components";
import { getTagGroups, isSubset, containsSubstring } from "../utils";
import { getTagGroups, isSubset, containsSubstring, flattenTagArrays } from "../utils";
export function BookmarksListView(props) {
const { getAccessTokenSilently } = useAuth0();
@ -65,34 +65,9 @@ export function BookmarksListView(props) {
});
}
};
const [alertMessageState, dispatchAlertMessageState] = useReducer(alertReducer, alertMessageInitialState);
const tagsInitialState = [];
const tagsReducer = (state = tagsInitialState, action) => {
switch(action.type) {
case "SET":
return action.payload.tags.map(x => ({...x, isSelected: false }));
case "ADD_SELECTED":
case "REMOVE_SELECTED":
const modifiedTags = [...state];
const selectedTagIndex = modifiedTags.findIndex((x) => x.id === action.payload.selectedTagId);
modifiedTags[selectedTagIndex].isSelected = action.type === "ADD_SELECTED";
return modifiedTags;
default:
return state;
}
}
const [tagsState, dispatchTagsState] = useReducer(tagsReducer, tagsInitialState);
const onTagSelected = (isTagSelected, tag) => dispatchTagsState({type: isTagSelected ? "ADD_SELECTED" : "REMOVE_SELECTED", payload: {selectedTagId: tag.id}})
const getSelectedTags = () => tagsState.filter((x) => x.isSelected);
const getNotSelectedTags = () => tagsState.filter((x) => !x.isSelected);
const bookmarksInitialState = [];
const bookmarksReducer = (state = bookmarksInitialState, action) => {
const bookmarksReducer = (state = [], action) => {
switch(action.type) {
case "SET":
return action.payload.bookmarks.map(x => ({...x, isSelected: false, isDisplayed: true}));
@ -122,7 +97,7 @@ export function BookmarksListView(props) {
}
};
const [bookmarksState, dispatchBookmarksState] = useReducer(bookmarksReducer, bookmarksInitialState);
const [bookmarksState, dispatchBookmarksState] = useReducer(bookmarksReducer, []);
const onBookmarkSelected = (isBookmarkSelected, bookmark) => dispatchBookmarksState({type: isBookmarkSelected ? "ADD_SELECTED" : "REMOVE_SELECTED", payload: {selectedBookmarkId: bookmark.id}});
const getSelectedBookmarksCount = () => bookmarksState.filter((x) => x.isSelected).length;
const getSelectedBookmarks = () => bookmarksState.filter((x) => x.isSelected);
@ -205,17 +180,6 @@ export function BookmarksListView(props) {
};
const fetchTags = async() => {
const accessToken = await getAccessTokenSilently();
const { data, error } = await getAllTags(accessToken);
if(error) {
dispatchAlertMessageState({type: "SHOW_ALERT", payload: {show: true, alertType: "danger", "message": `Error fetching tags: ${error.message}`}});
} else {
dispatchTagsState({type: "SET", payload: {tags: data}});
}
};
const fetchBookmarks = async() => {
const accessToken = await getAccessTokenSilently();
const { data, error } = await getAllBookmarks(accessToken, props.showHidden);
@ -224,13 +188,32 @@ export function BookmarksListView(props) {
dispatchAlertMessageState({type: "SHOW_ALERT", payload: {show: true, alertType: "danger", "message": `Error fetching bookmarks: ${error.message}`}});
} else {
dispatchBookmarksState({type: "SET", payload: {bookmarks: data}});
dispatchTagsState({type: "SET"});
}
}
const tagsReducer = (state = [], action) => {
switch(action.type) {
case "SET":
return flattenTagArrays(bookmarksState.map(x => x.tags)).map(x => Object.assign({}, x, {isSelected : false}));
case "ADD_SELECTED":
case "REMOVE_SELECTED":
const modifiedTags = [...state];
const selectedTagIndex = modifiedTags.findIndex((x) => x.id === action.payload.selectedTagId);
modifiedTags[selectedTagIndex].isSelected = action.type === "ADD_SELECTED";
return modifiedTags;
default:
return state;
}
}
useEffect(() => {
dispatchSplashScreenState({type: "SHOW_SPLASH_SCREEN", payload: {message: "Retrieving Tags..."}});
fetchTags();
const [tagsState, dispatchTagsState] = useReducer(tagsReducer, []);
const onTagSelected = (isTagSelected, tag) => dispatchTagsState({type: isTagSelected ? "ADD_SELECTED" : "REMOVE_SELECTED", payload: {selectedTagId: tag.id}})
const getSelectedTags = () => tagsState.filter((x) => x.isSelected);
const getNotSelectedTags = () => tagsState.filter((x) => !x.isSelected);
useEffect(() => {
dispatchSplashScreenState({type: "SHOW_SPLASH_SCREEN", payload: {message: "Retrieving Bookmarks..."}});
fetchBookmarks();
@ -354,7 +337,7 @@ export function BookmarksListView(props) {
<br />
{
group.tags.map((tag) => {
return <Button key={tag.id} variant="link" style={{textDecoration: "none"}} className="ms-0 me-2 p-0" onClick={() => onTagSelected(true, tag)}>
return <Button key={tag.id} variant="link" style={{textDecoration: "none"}} className={`ms-0 me-2 p-0 ${tag.isHidden ? "text-danger" : null}`} onClick={() => onTagSelected(true, tag)}>
#{tag.name}
</Button>
})