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:
@ -74,9 +74,9 @@ namespace YABA.API.Controllers
|
|||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType(typeof(IEnumerable<BookmarkResponse>), (int)HttpStatusCode.OK)]
|
[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));
|
return Ok(_mapper.Map<IEnumerable<BookmarkResponse>>(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -37,35 +37,29 @@ namespace YABA.Service
|
|||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<BookmarkDTO> GetAll(bool isHidden = false)
|
public IEnumerable<BookmarkDTO> GetAll(bool showHidden = false)
|
||||||
{
|
{
|
||||||
var currentUserId = GetCurrentUserId();
|
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
|
var bookmarkTagsLookup = _roContext.BookmarkTags
|
||||||
.Include(x => x.Tag)
|
.Include(x => x.Bookmark)
|
||||||
.Where(x => userBookmarks.Keys.Contains(x.BookmarkId))
|
.Include(x => x.Tag)
|
||||||
.ToList()
|
.Where(x => x.Bookmark.UserId == currentUserId && x.Tag.UserId == currentUserId)
|
||||||
.GroupBy(x => x.BookmarkId)
|
.ToList()
|
||||||
.ToDictionary(k => k.Key, v => v.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>();
|
var userBookmarkDTOs = new List<BookmarkDTO>();
|
||||||
|
|
||||||
foreach(var bookmark in userBookmarks)
|
foreach (var bookmarkTag in bookmarkTagsLookup)
|
||||||
{
|
{
|
||||||
var bookmarkDTO = _mapper.Map<BookmarkDTO>(bookmark.Value);
|
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
|
||||||
bookmarkTagsLookup.TryGetValue(bookmark.Key, out var bookmarkTags);
|
|| (!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
|
||||||
|
|
||||||
if(bookmarkTags != null)
|
|
||||||
{
|
{
|
||||||
foreach (var bookmarkTag in bookmarkTags)
|
var bookmarkDTO = _mapper.Map<BookmarkDTO>(bookmarkTag.Value.Key);
|
||||||
{
|
var bookmarkTagDTOs = _mapper.Map<IEnumerable<TagDTO>>(bookmarkTag.Value.Value);
|
||||||
var tagDTO = _mapper.Map<TagDTO>(bookmarkTag.Tag);
|
bookmarkDTO.Tags.AddRange(bookmarkTagDTOs);
|
||||||
bookmarkDTO.Tags.Add(tagDTO);
|
userBookmarkDTOs.Add(bookmarkDTO);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
userBookmarkDTOs.Add(bookmarkDTO);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return userBookmarkDTOs;
|
return userBookmarkDTOs;
|
||||||
|
|||||||
@ -2,9 +2,9 @@ import { callExternalApi } from "../apiHelper";
|
|||||||
|
|
||||||
const apiServerUrl = `${process.env.REACT_APP_API_BASE_URL}/v1/Bookmarks`;
|
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 = {
|
const config = {
|
||||||
url: `${apiServerUrl}?isHidden=${isHidden}`,
|
url: `${apiServerUrl}?showHidden=${showHidden}`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { isDev } from "./isDevHelper";
|
import { isDev } from "./isDevHelper";
|
||||||
import { getTagGroups } from "./tagsHelper";
|
import { getTagGroups, flattenTagArrays } from "./tagsHelper";
|
||||||
import { isSubset } from "./arrayHelper";
|
import { isSubset } from "./arrayHelper";
|
||||||
import { containsSubstring } from "./bookmarkHelper";
|
import { containsSubstring } from "./bookmarkHelper";
|
||||||
|
|
||||||
@ -7,5 +7,6 @@ export {
|
|||||||
isDev,
|
isDev,
|
||||||
getTagGroups,
|
getTagGroups,
|
||||||
isSubset,
|
isSubset,
|
||||||
containsSubstring
|
containsSubstring,
|
||||||
|
flattenTagArrays
|
||||||
}
|
}
|
||||||
@ -14,4 +14,12 @@ export const getTagGroups = (allBookmarkTags) => {
|
|||||||
|
|
||||||
return accumulator
|
return accumulator
|
||||||
}, []).sort((a, b) => (a.name < b.name) ? -1 : (a.name > b.name) ? 1 : 0);
|
}, []).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
|
||||||
|
}, []);
|
||||||
@ -2,9 +2,9 @@ import React, { useEffect, useReducer, useState } from "react";
|
|||||||
import { Alert, Col, Container, Row, Button, Modal } from "../components/external";
|
import { Alert, Col, Container, Row, Button, Modal } from "../components/external";
|
||||||
import { Bookmark } from "../components";
|
import { Bookmark } from "../components";
|
||||||
import { useAuth0 } from "@auth0/auth0-react";
|
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 { SplashScreen, SearchForm } from "../components";
|
||||||
import { getTagGroups, isSubset, containsSubstring } from "../utils";
|
import { getTagGroups, isSubset, containsSubstring, flattenTagArrays } from "../utils";
|
||||||
|
|
||||||
export function BookmarksListView(props) {
|
export function BookmarksListView(props) {
|
||||||
const { getAccessTokenSilently } = useAuth0();
|
const { getAccessTokenSilently } = useAuth0();
|
||||||
@ -65,34 +65,9 @@ export function BookmarksListView(props) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const [alertMessageState, dispatchAlertMessageState] = useReducer(alertReducer, alertMessageInitialState);
|
const [alertMessageState, dispatchAlertMessageState] = useReducer(alertReducer, alertMessageInitialState);
|
||||||
|
|
||||||
const tagsInitialState = [];
|
const bookmarksReducer = (state = [], action) => {
|
||||||
|
|
||||||
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) => {
|
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case "SET":
|
case "SET":
|
||||||
return action.payload.bookmarks.map(x => ({...x, isSelected: false, isDisplayed: true}));
|
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 onBookmarkSelected = (isBookmarkSelected, bookmark) => dispatchBookmarksState({type: isBookmarkSelected ? "ADD_SELECTED" : "REMOVE_SELECTED", payload: {selectedBookmarkId: bookmark.id}});
|
||||||
const getSelectedBookmarksCount = () => bookmarksState.filter((x) => x.isSelected).length;
|
const getSelectedBookmarksCount = () => bookmarksState.filter((x) => x.isSelected).length;
|
||||||
const getSelectedBookmarks = () => bookmarksState.filter((x) => x.isSelected);
|
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 fetchBookmarks = async() => {
|
||||||
const accessToken = await getAccessTokenSilently();
|
const accessToken = await getAccessTokenSilently();
|
||||||
const { data, error } = await getAllBookmarks(accessToken, props.showHidden);
|
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}`}});
|
dispatchAlertMessageState({type: "SHOW_ALERT", payload: {show: true, alertType: "danger", "message": `Error fetching bookmarks: ${error.message}`}});
|
||||||
} else {
|
} else {
|
||||||
dispatchBookmarksState({type: "SET", payload: {bookmarks: data}});
|
dispatchBookmarksState({type: "SET", payload: {bookmarks: data}});
|
||||||
|
dispatchTagsState({type: "SET"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatchSplashScreenState({type: "SHOW_SPLASH_SCREEN", payload: {message: "Retrieving Tags..."}});
|
|
||||||
fetchTags();
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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..."}});
|
dispatchSplashScreenState({type: "SHOW_SPLASH_SCREEN", payload: {message: "Retrieving Bookmarks..."}});
|
||||||
fetchBookmarks();
|
fetchBookmarks();
|
||||||
|
|
||||||
@ -354,7 +337,7 @@ export function BookmarksListView(props) {
|
|||||||
<br />
|
<br />
|
||||||
{
|
{
|
||||||
group.tags.map((tag) => {
|
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}
|
#{tag.name}
|
||||||
</Button>
|
</Button>
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user