Skip to content
This repository has been archived by the owner on Sep 29, 2024. It is now read-only.

Commit

Permalink
Merge pull request #893 from SE-TINF22B6/search_and_user_page
Browse files Browse the repository at this point in the history
Implemented Search and user page
  • Loading branch information
maxschwinghammer authored Jun 10, 2024
2 parents 29ca65f + 2019156 commit 0113ac5
Show file tree
Hide file tree
Showing 20 changed files with 549 additions and 205 deletions.
1 change: 1 addition & 0 deletions src/main/java/de/tinf22b6/dhbwhub/mapper/UserMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public static User mapToModel(UserProposal proposal) {
public static UserInformationProposal mapToUserInformationProposal(User user, int followerAmount) {
return new UserInformationProposal(
user.getId(),
user.getAccount().getUsername(),
user.getAccount().getPicture(),
followerAmount,
user.getAge(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
public class UserInformationProposal {
private Long userId;

private String username;

private Picture picture;

private int amountFollower;
Expand Down
26 changes: 20 additions & 6 deletions src/main/java/de/tinf22b6/dhbwhub/repository/PostRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.util.Collections;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

@Repository
public class PostRepository {
Expand Down Expand Up @@ -75,12 +75,26 @@ public List<Post> findPostsByKeyword(String keyword){
}

public List<Post> findPostsByTag(String tag){
return postTagRepository.findByTag(tag).stream().map(PostTag::getPost).toList();
return postTagRepository.findByTag(tag).stream().map(PostTag::getPost)
.collect(Collectors.toMap(
Post::getId,
post -> post,
(existing, replacement) -> existing,
LinkedHashMap::new))
.values().stream().toList();
}

public List<Post> findPostsByTagKeyword(String keyword) {
return postTagRepository.findTagByKeyword(keyword).stream()
.map(PostTag::getPost)
.collect(Collectors.toMap(
Post::getId,
post -> post,
(existing, replacement) -> existing,
LinkedHashMap::new))
.values().stream().toList();
}

public List<Post> findPostsByTagKeyword(String keyword){
return postTagRepository.findTagByKeyword(keyword).stream().map(PostTag::getPost).toList();
}

public int getAmountOfComments(Long id){
Integer commentAmount = postRepository.getCommentAmount(id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,20 @@ public class WebSecurityConfig {
"/post/homepage-preview-posts/{id:\\d+}",
"/post/post-thread/{id:\\d+}",
"/post/popular-tags",
"/post/posts-by-tag/{tag}",
"/post/posts-by-tag/*",
"/post/user-posts/{id:\\d+}",
"/post/posts-by-keyword/{keyword}",
"/post/posts-by-tag-keyword/{keyword}",
"/post/posts-by-keyword/*",
"/post/posts-by-tag-keyword/*",
"/post/posts-by-tag-keyword/*",
"/event/homepage-preview-events",
"/event/calendar-events",
"/event/event-thread/{id:\\d+}",
"/event/event-comments/{id:\\d+}",

"user/{id:\\d+}",
"/user-image",
"/user/user-by-keyword/*",
"/user/user-information/*",
};

public WebSecurityConfig(UserDetailsServiceImpl userDetailsService, AuthEntryPointJwt unauthorizedHandler) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ public PostThreadViewProposal update(Long id, UpdatePostProposal proposal) {
picture = null;
}
else if (!initialImageData.equals(proposalImageData)) {
assert initialPost.getPicture() != null;
pictureRepository.delete(initialPost.getPicture().getId());
picture = pictureRepository.save(PictureMapper.mapToModelPost(proposalImageData));
} else {
Expand Down Expand Up @@ -349,7 +350,7 @@ public List<HomepagePostPreviewProposal> getPostsByKeyword(String keyword) {

@Override
public List<HomepagePostPreviewProposal> getPostTagsByKeyword(String keyword) {
List<HomepagePostPreviewProposal> posts = postTagRepository.findTagByKeyword(keyword).stream().map(t-> PostMapper.mapToHomepagePreviewProposal(t.getPost())).toList();
List<HomepagePostPreviewProposal> posts = repository.findPostsByTagKeyword(keyword).stream().map(PostMapper::mapToHomepagePreviewProposal).toList();
posts.forEach(p -> {
p.setCommentAmount(getAmountOfComments(p.getId()));
p.setTags(getPostTags(p.getId()));
Expand Down
4 changes: 2 additions & 2 deletions src/main/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { ProfilePage } from './scenes/Profile';
import { Search } from './scenes/Search';
import { Event } from './scenes/Event';
import { TagOverview } from './scenes/Tag';
import { User } from './scenes/User';
import { UserPage } from './scenes/User';
import { PageNotFound } from './scenes/PageNotFound';
import config from "./config/config";
import {GoogleOAuthProvider} from "@react-oauth/google";
Expand All @@ -40,7 +40,7 @@ class App extends Component {
<Route path="/search" element={<Search/>}/>
<Route path="/tag" element={<TagOverview/>}/>
<Route path="/terms-of-service" element={<TermsOfService/>}/>
<Route path="/user" element={<User/>}/>
<Route path="/user" element={<UserPage/>}/>
</Routes>
</Router>
</GoogleOAuthProvider>
Expand Down
26 changes: 26 additions & 0 deletions src/main/web/src/atoms/Pictures/Pictures.tsx

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/main/web/src/scenes/Home/components/post/Post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ export const Post: React.FC<PostModel> = (props: PostModel) => {
</div>

<p className="short-description" style={{ width: width}}>
{postImage ? shortenDescription(shortDescription, 150) : shortDescription}
{postImage && shortDescription ? shortenDescription(shortDescription, 150) : shortDescription}
</p>

<div className="footer">
Expand Down
5 changes: 2 additions & 3 deletions src/main/web/src/scenes/Post/PostDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,8 @@ export const PostDetail: React.FC<PostDetailModel> = (props: PostDetailModel) =>
<div className="post-detail-data">
{userImage && <img className="profile-picture" alt="Profile" src={userImage}/>}
<div>
<Link
to={`/user/?name=${username.toLowerCase().replace(' ', '-')}`}
className="author-link" aria-label="To the author">{username}
<Link to={`/user/?id=${accountId}`} className="author-link" aria-label="To the author">
{username}
</Link>
<br/>
{formattedTime}
Expand Down
33 changes: 33 additions & 0 deletions src/main/web/src/scenes/Search/components/SearchService.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.search-results {
width: 780px;
display: grid;
grid-template-columns: 1fr;
grid-gap: 10px;
position: relative;
}

@media (max-width: 412px) {
.search-results {
margin-left: 20px;
margin-top: -5px;
}
}
.user-search-results {
width: 780px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
gap: 10px;
}
@media (max-width: 412px) {
.user-search-results {
margin-left: 20px;
margin-top: -5px;
}
}

.animation {
width: 780px;
display: flex;
justify-content: center;
align-items: center;
}
151 changes: 151 additions & 0 deletions src/main/web/src/scenes/Search/components/SearchService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React, {useEffect, useMemo, useState} from "react";
import "./SearchService.css";
import Lottie from "lottie-react";
import animationData from "../../../assets/loading.json";
import {useDetectAdBlock} from "adblock-detect-react";
import {usePreventScrolling} from "../../../organisms/ad-block-overlay/preventScrolling";
import {getJWT} from "../../../services/AuthService";
import config from "../../../config/config";
import {PostModel} from "../../Home/components/post/models/PostModel";
import {User} from "./User";
import {Post} from "../../Home/components/post/Post";
import {UserModel} from "./models/UserModel";




interface SearchedPostsProps {
sortOption: string;
query: string | null;
findByOption: string;
}

export const SearchService: React.FC<SearchedPostsProps> = ({sortOption, query, findByOption}) => {
const [searchResults, setSearchResults] = useState<any[]>([]);
const jwt: string | null = getJWT();
const headersWithJwt = useMemo(() => ({
...config.headers,
'Authorization': jwt ? `Bearer ${jwt}` : ''
}), [jwt]);

const [notFound, setNotFound] = useState(false);
const [loading, setLoading] = useState(true);
const adBlockDetected = useDetectAdBlock();
usePreventScrolling(adBlockDetected);

useEffect(() => {
setSearchResults([]);
setNotFound(false);
setLoading(true);

const fetchResults = async (url: string): Promise<void> => {
try {
const response: Response = await fetch(url, {
headers: headersWithJwt
});
if (response.ok) {
const data = await response.json();
setSearchResults(data);
setNotFound(false);
} else {
setNotFound(true);
}
setLoading(false);
} catch (error) {
console.error("Error when retrieving the search data:", error);
setNotFound(true);
setLoading(false);
}
};

switch (findByOption) {
case "title":
fetchResults(config.apiUrl + `post/posts-by-keyword/${query}`);
break;
case "tags":
fetchResults(config.apiUrl + `post/posts-by-tag-keyword/${query}`);
break;
case "user":
fetchResults(config.apiUrl + `user/user-by-keyword/${query}`);
break;
default:
setNotFound(true);
setLoading(true);
break;
}
}, [findByOption, query, headersWithJwt]);

const sortedResults = (): PostModel[] => {
if (!searchResults || searchResults.length === 0) setNotFound(true);
if (findByOption !== "user") {
switch (sortOption) {
case "newest":
return searchResults.sort((a: PostModel, b: PostModel) =>
Date.parse(new Date(b.timestamp).toISOString()) - Date.parse(new Date(a.timestamp).toISOString()));
case "following":
return searchResults;
case "popular":
return searchResults.sort((a: PostModel, b: PostModel) => b.likeAmount - a.likeAmount);
default:
return searchResults;
}
}
return searchResults;
};

if (loading) {
return (
<div className="animation">
<div className="loading-animation">
<Lottie animationData={animationData}/>
</div>
</div>

);
}

if (notFound) {
return (
<div className="search-results">
<h1 className="error">No results for search term: {query}</h1>
</div>
);
}

if (findByOption === "user") {

return (
<div className="user-search-results">
{searchResults.map((user: UserModel) => (
<User
key={user.userId}
userId={user.userId}
username={user.username}
picture={user.picture}
/>))
}
</div>

);
}

return (
<div className="search-results">
{sortedResults().map(post => (
<Post
key={post.id}
id={post.id}
title={post.title}
description={post.description || ""}
tags={post.tags}
likeAmount={post.likeAmount}
commentAmount={post.commentAmount}
timestamp={post.timestamp}
postImage={post.postImage}
accountId={post.accountId}
username={post.username}
/>
))}
</div>
);
};
35 changes: 35 additions & 0 deletions src/main/web/src/scenes/Search/components/User.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
.user-container {
font-size: var(--headline-font-size);
align-items: center;
text-align: center;
border-radius: 25px;
background-color: var(--component-grey);
justify-content: center;
width: 120px;
padding: 10px;
color: white;
}

.user-image {
width: 100px;
height: 100px;
border-radius: 50px;
object-fit: cover;

}

.custom-image {
width: 100px;
height: 100px;
border-radius: 50px;
object-fit: cover;

}

.user-button {
text-decoration: none;
color: inherit;
}
.user-name{
padding-top: 10px;
}
28 changes: 28 additions & 0 deletions src/main/web/src/scenes/Search/components/User.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import './User.css';
import {Link} from "react-router-dom";
import {UserModel} from "./models/UserModel";
import {Pictures} from "../../../atoms/Pictures/Pictures";

export const User: React.FC<UserModel> = (props: UserModel) => {
const {
userId,
username,
picture,
} = props;

const truncatedUsername = username.length > 17 ? username.slice(0, 15) + "..." : username;

return (
<div className="user-container">
<Link to={`/user/?id=${userId}`} aria-label="To the user" className="user-button">
{picture && picture.imageData ? (
<img className="user-image" alt={username} src={picture.imageData} loading="lazy"/>
) : (
<Pictures defaultPicture={false} className="custom-image"/>
)}
<div className="user-name">{truncatedUsername}</div>
</Link>
</div>
);
}
Loading

0 comments on commit 0113ac5

Please sign in to comment.