diff --git a/client/src/App.tsx b/client/src/App.tsx index ca6dd51..6655bfc 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -37,6 +37,7 @@ import { Chats } from "./Chats"; import { ChatCreate } from "./ChatCreate"; import { Chat } from "./Chat"; import { ChatEdit } from "./ChatEdit"; +import { Haters } from "./Haters"; const router = createBrowserRouter([ { @@ -98,6 +99,10 @@ const router = createBrowserRouter([ // action: profileSelfAction, element: , }, + { + path: "haters", + element: , + }, ], }, { diff --git a/client/src/Haters.scss b/client/src/Haters.scss new file mode 100644 index 0000000..14c124e --- /dev/null +++ b/client/src/Haters.scss @@ -0,0 +1,21 @@ +@import "./common"; + +.hatersManagement { + min-width: 100%; + display: flex; + flex-direction: column; + padding: 2.5rem 2rem 0; + + .tools { + display: flex; + flex-direction: column; + max-width: 10rem; + } + + .found { + margin-top: 1rem; + } + + .posts { + } +} \ No newline at end of file diff --git a/client/src/Haters.tsx b/client/src/Haters.tsx new file mode 100644 index 0000000..1eb59c6 --- /dev/null +++ b/client/src/Haters.tsx @@ -0,0 +1,178 @@ +import { useEffect, useState } from "react"; +import { deleteUser, getAllPerson } from "./api/Person"; +import { getAllMessage } from "./api/Message"; +import { getAllPost } from "./api/Post"; +import { isError, TMessageTo, TPersonTo, TPostTo } from "./api/dto"; + +import "./Haters.scss"; +import "./Post.scss"; +import { getTokenUserUuid } from "./api/utils"; + +export function Haters() { + const [data, setData] = useState< + | null + | { + error: false; + persons: TPersonTo[]; + messages: TMessageTo[]; + posts: TPostTo[]; + } + | { error: true; what: string } + >(null); + const [search, setSearch] = useState(""); + const [deleted, setDeleted] = useState([]); + + useEffect(() => { + let ignore = false; + const fetchall = async () => { + return { + error: false, + persons: await getAllPerson(), + messages: await getAllMessage(), + posts: await getAllPost(), + }; + }; + fetchall() + .then((result) => { + if (isError(result.persons)) + setData({ + error: true, + what: result.persons.errors.join(" "), + }); + else if (isError(result.messages)) + setData({ + error: true, + what: result.messages.errors.join(" "), + }); + else if (isError(result.posts)) + setData({ + error: true, + what: result.posts.errors.join(" "), + }); + else if (!ignore) { + setData( + result as { + error: false; + persons: TPersonTo[]; + messages: TMessageTo[]; + posts: TPostTo[]; + }, + ); + } + }) + .catch((e) => setData(e)); + return () => { + ignore = true; + }; + }, []); + + if (!data || data.error) { + return ( + + {data ? data.what : "error"} + + ); + } + + const foundM = data.messages.filter( + (m) => + m.contents.includes(search) && m.authorUuid != getTokenUserUuid(), + ); + const foundP = data.posts.filter( + (p) => p.text.includes(search) && p.authorUuid != getTokenUserUuid(), + ); + + const foundU = new Set(); + + const allfound: { + id: number; + type: string; + contents: string; + author: string; + }[] = []; + + foundM.forEach((m) => { + foundU.add(m.authorUuid); + allfound.push({ + id: m.id, + type: "message", + contents: m.contents, + author: m.authorUsername, + }); + }); + foundP.forEach((p) => { + foundU.add(p.authorUuid); + allfound.push({ + id: p.id, + type: "post", + contents: p.text, + author: p.authorUsername, + }); + }); + + console.log(foundU); + + const foundUArr = Array.from(foundU); + + return ( + + + Search for: + setSearch(e.target.value)} + /> + { + e.preventDefault(); + foundUArr.forEach((u) => + deleteUser(u) + .then((e) => { + if (isError(e)) { + setDeleted((old) => [ + ...old, + `error deleting user with uuid ${u}: ` + + e.errors.join(" "), + ]); + } else { + setDeleted((old) => [ + ...old, + "deleted user with id: " + u, + ]); + } + }) + .catch((e) => { + setDeleted((old) => [ + ...old, + `error deleting user with uuid ${u}`, + ]); + }), + ); + }} + > + delete all users + + + {deleted.map((d) => ( + {d} + ))} + + + + + {allfound.map((m) => ( + + {m.contents} + + + {m.type} by {m.author} + + + + ))} + + + ); +} diff --git a/client/src/Home.tsx b/client/src/Home.tsx index 127ca3c..7761539 100644 --- a/client/src/Home.tsx +++ b/client/src/Home.tsx @@ -53,6 +53,14 @@ export function Home() { Profile + {loaderData.isAdmin && ( + + Haters + + )} diff --git a/client/src/Profile.tsx b/client/src/Profile.tsx index 3c4da1e..d6f6b3f 100644 --- a/client/src/Profile.tsx +++ b/client/src/Profile.tsx @@ -76,17 +76,25 @@ export function Profile({ self }: IProfileProps) { <> {user.fullName} {user.username} - { setEditing(true)}>edit} - - - delete account - - + {self && ( + <> + { + setEditing(true)}> + edit + + } + + + delete account + + + > + )} > )} diff --git a/client/src/api/Message.ts b/client/src/api/Message.ts index b01c998..bbd12c7 100644 --- a/client/src/api/Message.ts +++ b/client/src/api/Message.ts @@ -46,3 +46,7 @@ export async function deleteMessage( NoContentToResp, ); } + +export async function getAllMessage(): Promise { + return fetchJSONAuth("/message", "GET", MessagesToResp); +} diff --git a/client/src/api/Person.ts b/client/src/api/Person.ts index d86b8e7..dd956f2 100644 --- a/client/src/api/Person.ts +++ b/client/src/api/Person.ts @@ -36,6 +36,10 @@ export async function deleteSelf(): Promise { return fetchJSONAuth("/person/self", "DELETE", NoContentToResp); } +export async function deleteUser(uuid: string): Promise { + return fetchJSONAuth("/person/by-uuid/" + uuid, "DELETE", NoContentToResp); +} + export async function getPersonByUuid(uuid: string): Promise { return fetchJSONAuth("/person/by-uuid/" + uuid, "GET", PersonToResp); } diff --git a/client/src/api/Post.ts b/client/src/api/Post.ts index 89dd9a9..0fd5d99 100644 --- a/client/src/api/Post.ts +++ b/client/src/api/Post.ts @@ -50,3 +50,7 @@ export async function getPostsByAuthorUsername( PostToArrResp, ); } + +export async function getAllPost(): Promise { + return fetchJSONAuth("/post", "GET", PostToArrResp); +} diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/controller/MessageController.java b/server/src/main/java/com/usatiuk/tjv/y/server/controller/MessageController.java index 4eb4b73..c538e08 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/controller/MessageController.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/controller/MessageController.java @@ -5,17 +5,20 @@ import com.usatiuk.tjv.y.server.dto.MessageTo; import com.usatiuk.tjv.y.server.dto.converters.MessageMapper; import com.usatiuk.tjv.y.server.entity.Message; import com.usatiuk.tjv.y.server.entity.Person; +import com.usatiuk.tjv.y.server.security.UserRoles; import com.usatiuk.tjv.y.server.service.ChatService; import com.usatiuk.tjv.y.server.service.MessageService; import jakarta.persistence.EntityManager; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; import java.util.Objects; import java.util.stream.Stream; +import java.util.stream.StreamSupport; @RestController @RequestMapping(value = "/message", produces = MediaType.APPLICATION_JSON_VALUE) @@ -75,4 +78,13 @@ public class MessageController { messageService.deleteById(id); } + @GetMapping + public Stream getAll(Authentication authentication) { + if (!authentication.getAuthorities().contains(new SimpleGrantedAuthority(UserRoles.ROLE_ADMIN.name()))) + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); + + return StreamSupport.stream(messageService.readAll().spliterator(), false).map(messageMapper::makeDto); + } + + } 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 8d58af3..29cb838 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 @@ -5,6 +5,7 @@ import com.usatiuk.tjv.y.server.dto.PersonTo; import com.usatiuk.tjv.y.server.dto.converters.PersonMapper; import com.usatiuk.tjv.y.server.entity.Chat; import com.usatiuk.tjv.y.server.entity.Person; +import com.usatiuk.tjv.y.server.security.UserRoles; import com.usatiuk.tjv.y.server.service.ChatService; import com.usatiuk.tjv.y.server.service.PersonService; import com.usatiuk.tjv.y.server.service.exceptions.UserAlreadyExistsException; @@ -12,6 +13,7 @@ import com.usatiuk.tjv.y.server.service.exceptions.UserNotFoundException; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; @@ -96,6 +98,26 @@ public class PersonController { personService.deleteById(authentication.getName()); } + @DeleteMapping(path = "/by-uuid/{uuid}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteByUuid(Authentication authentication, @PathVariable String uuid) throws UserNotFoundException { + if (!authentication.getAuthorities().contains(new SimpleGrantedAuthority(UserRoles.ROLE_ADMIN.name()))) + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); + + var person = personService.readById(uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + for (Chat c : person.getChats()) { + c.getMembers().remove(person); + chatService.update(c); + } + for (Person p : person.getFollowers()) { + p.getFollowing().remove(person); + personService.update(p); + } + + personService.deleteById(person.getUuid()); + } + + @GetMapping public Stream getAll() throws UserNotFoundException { return StreamSupport.stream(personService.readAll().spliterator(), false).map(personMapper::makeDto); 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 1007c5d..33ed898 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 @@ -5,17 +5,20 @@ import com.usatiuk.tjv.y.server.dto.PostTo; import com.usatiuk.tjv.y.server.dto.converters.PostMapper; import com.usatiuk.tjv.y.server.entity.Person; import com.usatiuk.tjv.y.server.entity.Post; +import com.usatiuk.tjv.y.server.security.UserRoles; import com.usatiuk.tjv.y.server.service.PostService; import jakarta.persistence.EntityManager; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; +import java.util.stream.StreamSupport; @RestController @RequestMapping(value = "/post", produces = MediaType.APPLICATION_JSON_VALUE) @@ -87,4 +90,12 @@ public class PostController { postService.deleteById(id); } + @GetMapping + public Stream getAll(Authentication authentication) { + if (!authentication.getAuthorities().contains(new SimpleGrantedAuthority(UserRoles.ROLE_ADMIN.name()))) + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); + + return StreamSupport.stream(postService.readAll().spliterator(), false).map(postMapper::makeDto); + } + }