diff --git a/client/src/ProfileCard.tsx b/client/src/ProfileCard.tsx
index 614813a..b8a4b66 100644
--- a/client/src/ProfileCard.tsx
+++ b/client/src/ProfileCard.tsx
@@ -1,12 +1,18 @@
import "./ProfileCard.scss";
-import { Link } from "react-router-dom";
+import { Form, Link } from "react-router-dom";
export function ProfileCard({
username,
fullName,
+ uuid,
+ actions,
+ alreadyFollowing,
}: {
username: string;
fullName: string;
+ uuid: string;
+ actions: boolean;
+ alreadyFollowing: boolean;
}) {
return (
@@ -16,6 +22,30 @@ export function ProfileCard({
{username}
+ {actions &&
+ (alreadyFollowing ? (
+
+ ) : (
+
+ ))}
);
}
diff --git a/client/src/UserList.tsx b/client/src/UserList.tsx
index 6cf445d..eb02ed9 100644
--- a/client/src/UserList.tsx
+++ b/client/src/UserList.tsx
@@ -2,22 +2,33 @@ import { useLoaderData } from "react-router-dom";
import { LoaderToType, userListLoader } from "./loaders";
import { isError } from "./api/dto";
import { ProfileCard } from "./ProfileCard";
+import { useHomeContext } from "./HomeContext";
export function UserList() {
const loaderData = useLoaderData() as LoaderToType
;
+ const homeContext = useHomeContext();
- if (!loaderData || isError(loaderData)) {
+ if (!loaderData) {
return Error
;
}
+ const { people, following } = loaderData;
+ if (isError(following) || isError(people)) {
+ return Error
;
+ }
return (
- {loaderData.map((u) => {
+ {people.map((u) => {
return (
f.uuid == u.uuid,
+ )}
/>
);
})}
diff --git a/client/src/actions.ts b/client/src/actions.ts
index 75ff543..71dcd68 100644
--- a/client/src/actions.ts
+++ b/client/src/actions.ts
@@ -1,4 +1,4 @@
-import { signup } from "./api/Person";
+import { addFollower, removeFollower, signup } from "./api/Person";
import { ActionFunctionArgs, redirect } from "react-router-dom";
import { login } from "./api/Token";
import { isError } from "./api/dto";
@@ -61,3 +61,14 @@ export async function profileSelfAction({ request }: ActionFunctionArgs) {
);
}
}
+
+export async function userListAction({ request }: ActionFunctionArgs) {
+ const formData = await request.formData();
+ const intent = formData.get("intent")!.toString();
+ console.log(intent);
+ if (intent == "follow") {
+ return await addFollower(formData.get("uuid")!.toString());
+ } else if (intent == "unfollow") {
+ return await removeFollower(formData.get("uuid")!.toString());
+ }
+}
diff --git a/client/src/api/Person.ts b/client/src/api/Person.ts
index 9b19ba9..6140f08 100644
--- a/client/src/api/Person.ts
+++ b/client/src/api/Person.ts
@@ -1,7 +1,9 @@
import { fetchJSON, fetchJSONAuth } from "./utils";
import {
+ NoContentToResp,
PersonToArrResp,
PersonToResp,
+ TNoContentToResp,
TPersonToArrResp,
TPersonToResp,
} from "./dto";
@@ -30,6 +32,10 @@ export async function getAllPerson(): Promise {
return fetchJSONAuth("/person", "GET", PersonToArrResp);
}
+export async function getFollowing(): Promise {
+ return fetchJSONAuth("/person/following", "GET", PersonToArrResp);
+}
+
export async function getPersonByUsername(
username: string,
): Promise {
@@ -39,3 +45,15 @@ export async function getPersonByUsername(
PersonToResp,
);
}
+
+export async function addFollower(uuid: string): Promise {
+ return fetchJSONAuth("/person/following/" + uuid, "PUT", NoContentToResp);
+}
+
+export async function removeFollower(uuid: string): Promise {
+ return fetchJSONAuth(
+ "/person/following/" + uuid,
+ "DELETE",
+ NoContentToResp,
+ );
+}
diff --git a/client/src/api/Post.ts b/client/src/api/Post.ts
index 65d6218..e285e8a 100644
--- a/client/src/api/Post.ts
+++ b/client/src/api/Post.ts
@@ -17,6 +17,22 @@ export async function deletePost(id: number): Promise {
return fetchJSONAuth(`/post/${id.toString()}`, "DELETE", NoContentToResp);
}
-export async function getPosts(author: string): Promise {
- return fetchJSONAuth(`/post?author=${author}`, "GET", PostToArrResp);
+export async function getPostsByAuthorUuid(
+ author: string,
+): Promise {
+ return fetchJSONAuth(
+ `/post/by-author-uuid?author=${author}`,
+ "GET",
+ PostToArrResp,
+ );
+}
+
+export async function getPostsByAuthorUsername(
+ author: string,
+): Promise {
+ return fetchJSONAuth(
+ `/post/by-author-username?author=${author}`,
+ "GET",
+ PostToArrResp,
+ );
}
diff --git a/client/src/api/utils.ts b/client/src/api/utils.ts
index bf309ef..8591627 100644
--- a/client/src/api/utils.ts
+++ b/client/src/api/utils.ts
@@ -1,5 +1,8 @@
// import { apiRoot } from "~src/env";
+import { jwtDecode } from "jwt-decode";
+import { isError, TErrorTo } from "./dto";
+
const apiRoot: string = "http://localhost:8080";
let token: string | null;
@@ -16,6 +19,12 @@ export function getToken(): string | null {
return token;
}
+export function getTokenUserUuid(): string | null {
+ const token = getToken();
+ if (!token) return null;
+ return jwtDecode(token).sub ?? null;
+}
+
export function deleteToken(): void {
token = null;
localStorage.removeItem("jwt_token");
diff --git a/client/src/loaders.ts b/client/src/loaders.ts
index 2f5f14a..0ff881b 100644
--- a/client/src/loaders.ts
+++ b/client/src/loaders.ts
@@ -1,8 +1,13 @@
-import { getAllPerson, getPersonByUsername, getSelf } from "./api/Person";
-import { deleteToken, getToken } from "./api/utils";
+import {
+ getAllPerson,
+ getFollowing,
+ getPersonByUsername,
+ getSelf,
+} from "./api/Person";
+import { deleteToken, getToken, getTokenUserUuid } from "./api/utils";
import { redirect } from "react-router-dom";
import { isError } from "./api/dto";
-import { getPosts } from "./api/Post";
+import { getPostsByAuthorUsername, getPostsByAuthorUuid } from "./api/Post";
export type LoaderToType any> =
| Exclude>, Response>
@@ -25,7 +30,7 @@ export async function homeLoader() {
}
export async function userListLoader() {
- return await getAllPerson();
+ return { people: await getAllPerson(), following: await getFollowing() };
}
export async function profileLoader({
@@ -33,27 +38,31 @@ export async function profileLoader({
}: {
params: { username?: string };
}) {
- const self = await getCheckUserSelf();
- if (!self || self instanceof Response || isError(self)) {
- return self;
- }
-
- if (self.username == params.username) {
+ const selfUuid = getTokenUserUuid();
+ if (!selfUuid) return redirect("/");
+ if (selfUuid == params.username) {
return redirect("/home/profile");
}
- const user = params.username
- ? await getPersonByUsername(params.username)
- : self;
- if (!user || user instanceof Response || isError(user)) {
- return user;
- }
+ const posts = params.username
+ ? await getPostsByAuthorUsername(params.username)
+ : await getPostsByAuthorUuid(selfUuid);
- const posts = await getPosts(user.uuid);
+ const retUser = params.username
+ ? await getPersonByUsername(params.username)
+ : null;
+
+ if (
+ (params.username && !retUser) ||
+ retUser instanceof Response ||
+ isError(retUser)
+ ) {
+ return retUser;
+ }
if (isError(posts)) {
- return { user, posts: null };
+ return { user: retUser, posts: null };
}
- return { user, posts };
+ return { user: retUser, posts };
}
diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/controller/PersonController.java b/server/src/main/java/com/usatiuk/tjv/y/server/controller/PersonController.java
index c4410b8..7d016f4 100644
--- a/server/src/main/java/com/usatiuk/tjv/y/server/controller/PersonController.java
+++ b/server/src/main/java/com/usatiuk/tjv/y/server/controller/PersonController.java
@@ -7,6 +7,7 @@ import com.usatiuk.tjv.y.server.entity.Person;
import com.usatiuk.tjv.y.server.service.PersonService;
import com.usatiuk.tjv.y.server.service.exceptions.UserAlreadyExistsException;
import com.usatiuk.tjv.y.server.service.exceptions.UserNotFoundException;
+import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
@@ -79,4 +80,16 @@ public class PersonController {
return personService.getFollowing(principal.getName()).stream().map(PersonMapper::makeDto);
}
+ @PutMapping(path = "/following/{uuid}")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void addFollowing(Principal principal, @PathVariable String uuid) throws UserNotFoundException {
+ personService.addFollower(principal.getName(), uuid);
+ }
+
+ @DeleteMapping(path = "/following/{uuid}")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void deleteFollowing(Principal principal, @PathVariable String uuid) throws UserNotFoundException {
+ personService.removeFollower(principal.getName(), uuid);
+ }
+
}
diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/controller/PostController.java b/server/src/main/java/com/usatiuk/tjv/y/server/controller/PostController.java
index a878fb8..12e9920 100644
--- a/server/src/main/java/com/usatiuk/tjv/y/server/controller/PostController.java
+++ b/server/src/main/java/com/usatiuk/tjv/y/server/controller/PostController.java
@@ -36,8 +36,16 @@ public class PostController {
return PostMapper.makeDto(postService.create(post));
}
- @GetMapping
- public Stream readAllByAuthor(@RequestParam Optional author) {
+ @GetMapping(path = "/by-author-uuid")
+ public Stream readAllByAuthorUuid(@RequestParam Optional author) {
+ if (author.isPresent())
+ return postService.readByAuthorId(author.get()).stream().map(PostMapper::makeDto);
+ else
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
+ }
+
+ @GetMapping(path = "/by-author-username")
+ public Stream readAllByAuthorUsername(@RequestParam Optional author) {
if (author.isPresent())
return postService.readByAuthorId(author.get()).stream().map(PostMapper::makeDto);
else
diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/repository/PostRepository.java b/server/src/main/java/com/usatiuk/tjv/y/server/repository/PostRepository.java
index a6fa220..3bdb319 100644
--- a/server/src/main/java/com/usatiuk/tjv/y/server/repository/PostRepository.java
+++ b/server/src/main/java/com/usatiuk/tjv/y/server/repository/PostRepository.java
@@ -12,6 +12,8 @@ import java.util.Collection;
public interface PostRepository extends PagingAndSortingRepository, CrudRepository {
Collection findByAuthorUuid(String authorUuid);
+ Collection findByAuthorUsername(String authorUsername);
+
@Query(value = "SELECT p FROM Post p " +
"WHERE EXISTS " +
"(SELECT u FROM Person u LEFT JOIN u.following f where u.uuid = :personUuid and f.uuid = p.author.uuid)")
diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/service/PersonService.java b/server/src/main/java/com/usatiuk/tjv/y/server/service/PersonService.java
index 78a0abe..4892df8 100644
--- a/server/src/main/java/com/usatiuk/tjv/y/server/service/PersonService.java
+++ b/server/src/main/java/com/usatiuk/tjv/y/server/service/PersonService.java
@@ -17,4 +17,8 @@ public interface PersonService extends CrudService {
Collection getFollowers(String uuid) throws UserNotFoundException;
Collection getFollowing(String uuid) throws UserNotFoundException;
+
+ void addFollower(String follower, String followee) throws UserNotFoundException;
+
+ void removeFollower(String follower, String followee) throws UserNotFoundException;
}
diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/service/PersonServiceImpl.java b/server/src/main/java/com/usatiuk/tjv/y/server/service/PersonServiceImpl.java
index 297b693..881d1e7 100644
--- a/server/src/main/java/com/usatiuk/tjv/y/server/service/PersonServiceImpl.java
+++ b/server/src/main/java/com/usatiuk/tjv/y/server/service/PersonServiceImpl.java
@@ -4,6 +4,7 @@ import com.usatiuk.tjv.y.server.entity.Person;
import com.usatiuk.tjv.y.server.repository.PersonRepository;
import com.usatiuk.tjv.y.server.service.exceptions.UserAlreadyExistsException;
import com.usatiuk.tjv.y.server.service.exceptions.UserNotFoundException;
+import jakarta.persistence.EntityManager;
import org.springframework.data.repository.CrudRepository;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@@ -15,11 +16,13 @@ import java.util.Optional;
public class PersonServiceImpl extends CrudServiceImpl implements PersonService {
private final PersonRepository personRepository;
private final PasswordEncoder passwordEncoder;
+ private final EntityManager entityManager;
public PersonServiceImpl(PersonRepository personRepository,
- PasswordEncoder passwordEncoder) {
+ PasswordEncoder passwordEncoder, EntityManager entityManager) {
this.personRepository = personRepository;
this.passwordEncoder = passwordEncoder;
+ this.entityManager = entityManager;
}
@Override
@@ -59,4 +62,18 @@ public class PersonServiceImpl extends CrudServiceImpl implement
public Collection getFollowing(String uuid) throws UserNotFoundException {
return personRepository.findById(uuid).orElseThrow(UserNotFoundException::new).getFollowing();
}
+
+ @Override
+ public void addFollower(String follower, String followee) throws UserNotFoundException {
+ var person = personRepository.findById(follower).orElseThrow(UserNotFoundException::new);
+ person.getFollowing().add(entityManager.getReference(Person.class, followee));
+ personRepository.save(person);
+ }
+
+ @Override
+ public void removeFollower(String follower, String followee) throws UserNotFoundException {
+ var person = personRepository.findById(follower).orElseThrow(UserNotFoundException::new);
+ person.getFollowing().remove(entityManager.getReference(Person.class, followee));
+ personRepository.save(person);
+ }
}
diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/service/PostService.java b/server/src/main/java/com/usatiuk/tjv/y/server/service/PostService.java
index 93d76db..0074b90 100644
--- a/server/src/main/java/com/usatiuk/tjv/y/server/service/PostService.java
+++ b/server/src/main/java/com/usatiuk/tjv/y/server/service/PostService.java
@@ -7,5 +7,7 @@ import java.util.Collection;
public interface PostService extends CrudService {
Collection readByAuthorId(String authorUuid);
+ Collection readByAuthorUsername(String authorUsername);
+
Collection readByPersonFollowees(String personUuid);
}
diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/service/PostServiceImpl.java b/server/src/main/java/com/usatiuk/tjv/y/server/service/PostServiceImpl.java
index cc89371..7062eab 100644
--- a/server/src/main/java/com/usatiuk/tjv/y/server/service/PostServiceImpl.java
+++ b/server/src/main/java/com/usatiuk/tjv/y/server/service/PostServiceImpl.java
@@ -25,6 +25,11 @@ public class PostServiceImpl extends CrudServiceImpl implements Post
return postRepository.findByAuthorUuid(authorId);
}
+ @Override
+ public Collection readByAuthorUsername(String authorUsername) {
+ return postRepository.findByAuthorUsername(authorUsername);
+ }
+
@Override
public Collection readByPersonFollowees(String personUuid) {
return postRepository.findByPersonFollowees(personUuid);
diff --git a/server/src/test/java/com/usatiuk/tjv/y/server/controller/PersonControllerTest.java b/server/src/test/java/com/usatiuk/tjv/y/server/controller/PersonControllerTest.java
index 2dd6595..87afe55 100644
--- a/server/src/test/java/com/usatiuk/tjv/y/server/controller/PersonControllerTest.java
+++ b/server/src/test/java/com/usatiuk/tjv/y/server/controller/PersonControllerTest.java
@@ -119,4 +119,25 @@ public class PersonControllerTest extends DemoDataDbTest {
Assertions.assertIterableEquals(Arrays.asList(personToResponse), List.of(PersonMapper.makeDto(person2), PersonMapper.makeDto(person1)));
}
+ @Test
+ void shouldAddFollowing() {
+ var response = restTemplate.exchange(addr + "/person/following/" + person3.getUuid(),
+ HttpMethod.PUT, new HttpEntity<>(createAuthHeaders(person1Auth)), Object.class);
+
+ Assertions.assertNotNull(response);
+ Assertions.assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode());
+
+ var response2 = restTemplate.exchange(addr + "/person/following",
+ HttpMethod.GET, new HttpEntity<>(createAuthHeaders(person1Auth)), PersonTo[].class);
+
+ Assertions.assertNotNull(response2);
+ Assertions.assertEquals(HttpStatus.OK, response2.getStatusCode());
+
+ var personToResponse = response2.getBody();
+ Assertions.assertNotNull(personToResponse);
+
+ Assertions.assertEquals(1, personToResponse.length);
+ Assertions.assertIterableEquals(Arrays.asList(personToResponse), List.of(PersonMapper.makeDto(person3)));
+ }
+
}
diff --git a/server/src/test/java/com/usatiuk/tjv/y/server/controller/PostControllerTest.java b/server/src/test/java/com/usatiuk/tjv/y/server/controller/PostControllerTest.java
index 488824d..80a9edc 100644
--- a/server/src/test/java/com/usatiuk/tjv/y/server/controller/PostControllerTest.java
+++ b/server/src/test/java/com/usatiuk/tjv/y/server/controller/PostControllerTest.java
@@ -60,7 +60,7 @@ public class PostControllerTest extends DemoDataDbTest {
@Test
void shouldGetByAuthor() {
- var response = restTemplate.exchange(addr + "/post?author=" + person1.getUuid(), HttpMethod.GET,
+ var response = restTemplate.exchange(addr + "/post/by-author-uuid?author=" + person1.getUuid(), HttpMethod.GET,
HttpEntity.EMPTY, PostTo[].class);
Assertions.assertEquals(HttpStatus.OK, response.getStatusCode());