diff --git a/client/src/ProfileCard.tsx b/client/src/ProfileCard.tsx index a8bbef6..a93f379 100644 --- a/client/src/ProfileCard.tsx +++ b/client/src/ProfileCard.tsx @@ -1,5 +1,6 @@ import "./ProfileCard.scss"; import { Form, Link, useNavigation } from "react-router-dom"; +import { useHomeContext } from "./HomeContext"; export function ProfileCard({ username, @@ -7,13 +8,17 @@ export function ProfileCard({ uuid, actions, alreadyFollowing, + isAdmin, }: { username: string; fullName: string; uuid: string; actions: boolean; alreadyFollowing: boolean; + isAdmin: boolean; }) { + const homeContext = useHomeContext(); + const navigation = useNavigation(); const busy = navigation.state === "submitting"; @@ -54,6 +59,32 @@ export function ProfileCard({ ))} + {homeContext.user.isAdmin && + (isAdmin ? ( +
+ + +
+ ) : ( +
+ + +
+ ))} ); diff --git a/client/src/UserList.tsx b/client/src/UserList.tsx index 7db546b..59d339c 100644 --- a/client/src/UserList.tsx +++ b/client/src/UserList.tsx @@ -28,6 +28,7 @@ export function UserList() { uuid={u.uuid} key={u.uuid} actions={homeContext.user.uuid != u.uuid} + isAdmin={u.isAdmin} alreadyFollowing={following.some( (f) => f.uuid == u.uuid, )} diff --git a/client/src/actions.ts b/client/src/actions.ts index c13a50d..328e7ce 100644 --- a/client/src/actions.ts +++ b/client/src/actions.ts @@ -1,6 +1,8 @@ import { + addAdmin, addFollower, deleteSelf, + removeAdmin, removeFollower, signup, updateSelf, @@ -101,6 +103,10 @@ export async function userListAction({ request }: ActionFunctionArgs) { return await addFollower(formData.get("uuid")!.toString()); } else if (intent == "unfollow") { return await removeFollower(formData.get("uuid")!.toString()); + } else if (intent == "unadmin") { + return await removeAdmin(formData.get("uuid")!.toString()); + } else if (intent == "admin") { + return await addAdmin(formData.get("uuid")!.toString()); } } diff --git a/client/src/api/Person.ts b/client/src/api/Person.ts index 9ac5354..d86b8e7 100644 --- a/client/src/api/Person.ts +++ b/client/src/api/Person.ts @@ -73,3 +73,11 @@ export async function removeFollower(uuid: string): Promise { NoContentToResp, ); } + +export async function addAdmin(uuid: string): Promise { + return fetchJSONAuth("/person/admins/" + uuid, "PUT", NoContentToResp); +} + +export async function removeAdmin(uuid: string): Promise { + return fetchJSONAuth("/person/admins/" + uuid, "DELETE", NoContentToResp); +} diff --git a/client/src/api/dto.ts b/client/src/api/dto.ts index 7488dc3..4ceec86 100644 --- a/client/src/api/dto.ts +++ b/client/src/api/dto.ts @@ -31,6 +31,7 @@ export const PersonTo = z.object({ uuid: z.string(), username: z.string(), fullName: z.string(), + isAdmin: z.boolean(), }); export type TPersonTo = z.infer; diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/controller/ChatController.java b/server/src/main/java/com/usatiuk/tjv/y/server/controller/ChatController.java index 15432f9..0e5407e 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/controller/ChatController.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/controller/ChatController.java @@ -11,10 +11,10 @@ import com.usatiuk.tjv.y.server.service.ChatService; import jakarta.persistence.EntityManager; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; -import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; @@ -36,16 +36,16 @@ public class ChatController { } @PostMapping - public ChatTo create(Principal principal, @RequestBody ChatCreateTo chatCreateTo) { + public ChatTo create(Authentication authentication, @RequestBody ChatCreateTo chatCreateTo) { var chat = new Chat(); - if (Arrays.stream(chatCreateTo.memberUuids()).noneMatch(n -> Objects.equals(n, principal.getName()))) + if (Arrays.stream(chatCreateTo.memberUuids()).noneMatch(n -> Objects.equals(n, authentication.getName()))) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Creator of chat must be its member"); if (chatCreateTo.memberUuids().length <= 1) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Chat must have members other than its creator"); - chat.setCreator(entityManager.getReference(Person.class, principal.getName())); + chat.setCreator(entityManager.getReference(Person.class, authentication.getName())); chat.setMembers(Arrays.stream(chatCreateTo.memberUuids()).map( p -> entityManager.getReference(Person.class, p) ).toList()); @@ -56,9 +56,9 @@ public class ChatController { } @GetMapping(path = "/by-id/{id}") - public ChatTo get(Principal principal, @PathVariable Long id) { + public ChatTo get(Authentication authentication, @PathVariable Long id) { var chat = chatService.readById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found")); - var userRef = entityManager.getReference(Person.class, principal.getName()); + var userRef = entityManager.getReference(Person.class, authentication.getName()); if (!chat.getMembers().contains(userRef)) throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't member of the chat"); return chatMapper.makeDto(chat); @@ -66,25 +66,25 @@ public class ChatController { @DeleteMapping(path = "/by-id/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) - public void delete(Principal principal, @PathVariable Long id) { + public void delete(Authentication authentication, @PathVariable Long id) { var chat = chatService.readById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found")); - if (!Objects.equals(chat.getCreator().getUuid(), principal.getName())) + if (!Objects.equals(chat.getCreator().getUuid(), authentication.getName())) throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't creator of the chat"); chatService.deleteById(id); } @PatchMapping(path = "/by-id/{id}") - public ChatTo update(Principal principal, @PathVariable Long id, @RequestBody ChatCreateTo chatCreateTo) { + public ChatTo update(Authentication authentication, @PathVariable Long id, @RequestBody ChatCreateTo chatCreateTo) { var chat = chatService.readById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found")); - if (!Objects.equals(chat.getCreator().getUuid(), principal.getName())) + if (!Objects.equals(chat.getCreator().getUuid(), authentication.getName())) throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't creator of the chat"); - if (Arrays.stream(chatCreateTo.memberUuids()).noneMatch(n -> Objects.equals(n, principal.getName()))) + if (Arrays.stream(chatCreateTo.memberUuids()).noneMatch(n -> Objects.equals(n, authentication.getName()))) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Creator of chat must be its member"); if (chatCreateTo.memberUuids().length <= 1) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Chat must have members other than its creator"); - + chat.setMembers(new ArrayList<>(Arrays.stream(chatCreateTo.memberUuids()).map( p -> entityManager.getReference(Person.class, p) ).toList())); @@ -96,14 +96,14 @@ public class ChatController { @GetMapping(path = "/my") - public Stream getMy(Principal principal) { - return chatService.readByMember(principal.getName()).stream().map(chatMapper::makeDto); + public Stream getMy(Authentication authentication) { + return chatService.readByMember(authentication.getName()).stream().map(chatMapper::makeDto); } @GetMapping(path = "/by-id/{id}/members") - public Stream getMembers(Principal principal, @PathVariable Long id) { + public Stream getMembers(Authentication authentication, @PathVariable Long id) { var chat = chatService.readById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found")); - var userRef = entityManager.getReference(Person.class, principal.getName()); + var userRef = entityManager.getReference(Person.class, authentication.getName()); if (!chat.getMembers().contains(userRef)) throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't member of the chat"); return chat.getMembers().stream().map(personMapper::makeDto); 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 ff9375a..4eb4b73 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 @@ -10,10 +10,10 @@ 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.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; -import java.security.Principal; import java.util.Objects; import java.util.stream.Stream; @@ -33,9 +33,9 @@ public class MessageController { } @GetMapping(path = "/by-chat/{chatTd}") - public Stream get(Principal principal, @PathVariable Long chatTd) { + public Stream get(Authentication authentication, @PathVariable Long chatTd) { var chat = chatService.readById(chatTd).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found")); - var userRef = entityManager.getReference(Person.class, principal.getName()); + var userRef = entityManager.getReference(Person.class, authentication.getName()); if (!chat.getMembers().contains(userRef)) throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't member of the chat"); @@ -43,9 +43,9 @@ public class MessageController { } @PostMapping(path = "/by-chat/{chatId}") - public MessageTo post(Principal principal, @PathVariable Long chatId, @RequestBody MessageCreateTo messageCreateTo) { + public MessageTo post(Authentication authentication, @PathVariable Long chatId, @RequestBody MessageCreateTo messageCreateTo) { var chat = chatService.readById(chatId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found")); - var userRef = entityManager.getReference(Person.class, principal.getName()); + var userRef = entityManager.getReference(Person.class, authentication.getName()); if (!chat.getMembers().contains(userRef)) throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't member of the chat"); @@ -55,9 +55,9 @@ public class MessageController { } @PatchMapping(path = "/by-id/{id}") - public MessageTo update(Principal principal, @PathVariable long id, @RequestBody MessageCreateTo messageCreateTo) { + public MessageTo update(Authentication authentication, @PathVariable long id, @RequestBody MessageCreateTo messageCreateTo) { var message = messageService.readById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - if (!Objects.equals(message.getAuthor().getUuid(), principal.getName())) + if (!Objects.equals(message.getAuthor().getUuid(), authentication.getName())) throw new ResponseStatusException(HttpStatus.FORBIDDEN); message.setContents(messageCreateTo.contents()); messageService.update(message); @@ -66,10 +66,10 @@ public class MessageController { @DeleteMapping(path = "/by-id/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) - public void delete(Principal principal, @PathVariable long id) { + public void delete(Authentication authentication, @PathVariable long id) { var read = messageService.readById(id); if (read.isEmpty()) return; - if (!Objects.equals(read.get().getAuthor().getId(), principal.getName())) { + if (!Objects.equals(read.get().getAuthor().getId(), authentication.getName())) { throw new ResponseStatusException(HttpStatus.FORBIDDEN); } messageService.deleteById(id); 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 6fb0863..8d58af3 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 @@ -11,11 +11,11 @@ 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.security.core.Authentication; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; -import java.security.Principal; import java.util.Optional; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -67,8 +67,8 @@ public class PersonController { @GetMapping(path = "/self") - public PersonTo getSelf(Principal principal) throws UserNotFoundException { - Optional found = personService.readById(principal.getName()); + public PersonTo getSelf(Authentication authentication) throws UserNotFoundException { + Optional found = personService.readById(authentication.getName()); if (found.isEmpty()) throw new UserNotFoundException(); @@ -76,8 +76,8 @@ public class PersonController { } @PatchMapping(path = "/self") - public PersonTo update(Principal principal, @RequestBody PersonSignupTo personSignupTo) { - var person = personService.readById(principal.getName()).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + public PersonTo update(Authentication authentication, @RequestBody PersonSignupTo personSignupTo) { + var person = personService.readById(authentication.getName()).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); person.setUsername(personSignupTo.username()) .setFullName(personSignupTo.fullName()); if (!personSignupTo.password().isEmpty()) person.setPassword(passwordEncoder.encode(personSignupTo.password())); @@ -87,13 +87,13 @@ public class PersonController { @DeleteMapping(path = "/self") @ResponseStatus(HttpStatus.NO_CONTENT) - public void delete(Principal principal) { - var person = personService.readById(principal.getName()).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + public void delete(Authentication authentication) { + var person = personService.readById(authentication.getName()).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); for (Chat c : person.getChats()) { c.getMembers().remove(person); chatService.update(c); } - personService.deleteById(principal.getName()); + personService.deleteById(authentication.getName()); } @GetMapping @@ -102,25 +102,43 @@ public class PersonController { } @GetMapping(path = "/followers") - public Stream getFollowers(Principal principal) throws UserNotFoundException { - return personService.getFollowers(principal.getName()).stream().map(personMapper::makeDto); + public Stream getFollowers(Authentication authentication) throws UserNotFoundException { + return personService.getFollowers(authentication.getName()).stream().map(personMapper::makeDto); } @GetMapping(path = "/following") - public Stream getFollowing(Principal principal) throws UserNotFoundException { - return personService.getFollowing(principal.getName()).stream().map(personMapper::makeDto); + public Stream getFollowing(Authentication authentication) throws UserNotFoundException { + return personService.getFollowing(authentication.getName()).stream().map(personMapper::makeDto); } + @GetMapping(path = "/admins") + public Stream getAdmins() { + return personService.getAdmins().stream().map(personMapper::makeDto); + } + + @PutMapping(path = "/admins/{uuid}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void addAdmin(Authentication authentication, @PathVariable String uuid) throws UserNotFoundException { + personService.addAdmin(authentication, uuid); + } + + @DeleteMapping(path = "/admins/{uuid}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteAdmin(Authentication authentication, @PathVariable String uuid) throws UserNotFoundException { + personService.removeAdmin(authentication, uuid); + } + + @PutMapping(path = "/following/{uuid}") @ResponseStatus(HttpStatus.NO_CONTENT) - public void addFollowing(Principal principal, @PathVariable String uuid) throws UserNotFoundException { - personService.addFollower(principal.getName(), uuid); + public void addFollowing(Authentication authentication, @PathVariable String uuid) throws UserNotFoundException { + personService.addFollower(authentication.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); + public void deleteFollowing(Authentication authentication, @PathVariable String uuid) throws UserNotFoundException { + personService.removeFollower(authentication.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 703bd19..1007c5d 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 @@ -9,10 +9,10 @@ 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.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; -import java.security.Principal; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; @@ -31,9 +31,9 @@ public class PostController { } @PostMapping - public PostTo createPost(Principal principal, @RequestBody PostCreateTo postCreateTo) { + public PostTo createPost(Authentication authentication, @RequestBody PostCreateTo postCreateTo) { Post post = new Post(); - post.setAuthor(entityManager.getReference(Person.class, principal.getName())); + post.setAuthor(entityManager.getReference(Person.class, authentication.getName())); post.setText(postCreateTo.text()); return postMapper.makeDto(postService.create(post)); } @@ -55,8 +55,8 @@ public class PostController { } @GetMapping(path = "/by-following") - public Stream readAllByFollowees(Principal principal) { - return postService.readByPersonFollowees(principal.getName()).stream().map(postMapper::makeDto); + public Stream readAllByFollowees(Authentication authentication) { + return postService.readByPersonFollowees(authentication.getName()).stream().map(postMapper::makeDto); } @GetMapping(path = "/{id}") @@ -67,9 +67,9 @@ public class PostController { } @PatchMapping(path = "/{id}") - public PostTo update(Principal principal, @PathVariable long id, @RequestBody PostCreateTo postCreateTo) { + public PostTo update(Authentication authentication, @PathVariable long id, @RequestBody PostCreateTo postCreateTo) { var post = postService.readById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - if (!Objects.equals(post.getAuthor().getUuid(), principal.getName())) + if (!Objects.equals(post.getAuthor().getUuid(), authentication.getName())) throw new ResponseStatusException(HttpStatus.FORBIDDEN); post.setText(postCreateTo.text()); postService.update(post); @@ -78,10 +78,10 @@ public class PostController { @DeleteMapping(path = "/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) - public void delete(Principal principal, @PathVariable long id) { + public void delete(Authentication authentication, @PathVariable long id) { var read = postService.readById(id); if (read.isEmpty()) return; - if (!Objects.equals(read.get().getAuthor().getId(), principal.getName())) { + if (!Objects.equals(read.get().getAuthor().getId(), authentication.getName())) { throw new ResponseStatusException(HttpStatus.FORBIDDEN); } postService.deleteById(id); diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/dto/PersonTo.java b/server/src/main/java/com/usatiuk/tjv/y/server/dto/PersonTo.java index 22f1cb1..50097a8 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/dto/PersonTo.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/dto/PersonTo.java @@ -1,4 +1,4 @@ package com.usatiuk.tjv.y.server.dto; -public record PersonTo(String uuid, String username, String fullName) { +public record PersonTo(String uuid, String username, String fullName, boolean isAdmin) { } diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/dto/converters/PersonMapper.java b/server/src/main/java/com/usatiuk/tjv/y/server/dto/converters/PersonMapper.java index 2277995..bbf4efb 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/dto/converters/PersonMapper.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/dto/converters/PersonMapper.java @@ -7,6 +7,6 @@ import org.springframework.stereotype.Component; @Component public class PersonMapper { public PersonTo makeDto(Person person) { - return new PersonTo(person.getUuid(), person.getUsername(), person.getFullName()); + return new PersonTo(person.getUuid(), person.getUsername(), person.getFullName(), person.isAdmin()); } } diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/entity/Person.java b/server/src/main/java/com/usatiuk/tjv/y/server/entity/Person.java index 81fcedd..2911a2c 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/entity/Person.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/entity/Person.java @@ -43,6 +43,8 @@ public class Person implements EntityWithId { @OneToMany(mappedBy = "author", orphanRemoval = true) private Collection messages = new ArrayList<>(); + private boolean admin; + @ManyToMany @JoinTable(name = "person_follows", joinColumns = @JoinColumn(name = "follower"), diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/repository/PersonRepository.java b/server/src/main/java/com/usatiuk/tjv/y/server/repository/PersonRepository.java index 16c3097..176210c 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/repository/PersonRepository.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/repository/PersonRepository.java @@ -3,10 +3,13 @@ package com.usatiuk.tjv.y.server.repository; import com.usatiuk.tjv.y.server.entity.Person; import org.springframework.data.repository.CrudRepository; +import java.util.Collection; import java.util.Optional; public interface PersonRepository extends CrudRepository { Optional findByUsername(String username); boolean existsByUsername(String username); + + Collection findByAdminIsTrue(); } diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/security/JwtUserDetailsService.java b/server/src/main/java/com/usatiuk/tjv/y/server/security/JwtUserDetailsService.java index 31baca5..76205b5 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/security/JwtUserDetailsService.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/security/JwtUserDetailsService.java @@ -8,7 +8,7 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -24,8 +24,9 @@ public class JwtUserDetailsService implements UserDetailsService { public UserDetails loadUserByUsername(String uuid) { Optional person = personService.readById(uuid); if (!person.isPresent()) throw new UsernameNotFoundException("User with UUID " + uuid + " not found"); - List roles = - Collections.singletonList(new SimpleGrantedAuthority(UserRoles.ROLE_USER.name())); + ArrayList roles = + new ArrayList<>(List.of(new SimpleGrantedAuthority(UserRoles.ROLE_USER.name()))); + if (person.get().isAdmin()) roles.add(new SimpleGrantedAuthority(UserRoles.ROLE_ADMIN.name())); return new JwtUser(uuid, person.get().getPassword(), roles); } 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 4892df8..fab0878 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 @@ -3,6 +3,7 @@ package com.usatiuk.tjv.y.server.service; import com.usatiuk.tjv.y.server.entity.Person; import com.usatiuk.tjv.y.server.service.exceptions.UserAlreadyExistsException; import com.usatiuk.tjv.y.server.service.exceptions.UserNotFoundException; +import org.springframework.security.core.Authentication; import java.util.Collection; import java.util.Optional; @@ -15,10 +16,13 @@ public interface PersonService extends CrudService { Optional readByUsername(String username); 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; + + Collection getAdmins(); + void addAdmin(Authentication caller, String uuid) throws UserNotFoundException; + void removeAdmin(Authentication caller, String uuid) 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 881d1e7..6cae073 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 @@ -2,12 +2,17 @@ package com.usatiuk.tjv.y.server.service; import com.usatiuk.tjv.y.server.entity.Person; import com.usatiuk.tjv.y.server.repository.PersonRepository; +import com.usatiuk.tjv.y.server.security.UserRoles; 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.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; import java.util.Collection; import java.util.Optional; @@ -36,6 +41,9 @@ public class PersonServiceImpl extends CrudServiceImpl implement throw new UserAlreadyExistsException(); person.setPassword(passwordEncoder.encode(person.getPassword())); + + if (personRepository.findByAdminIsTrue().isEmpty()) person.setAdmin(true); + return create(person); } @@ -76,4 +84,32 @@ public class PersonServiceImpl extends CrudServiceImpl implement person.getFollowing().remove(entityManager.getReference(Person.class, followee)); personRepository.save(person); } + + @Override + public void addAdmin(Authentication caller, String uuid) throws UserNotFoundException { + if (!caller.getAuthorities().contains(new SimpleGrantedAuthority(UserRoles.ROLE_ADMIN.name()))) + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); + + var person = personRepository.findById(uuid).orElseThrow(UserNotFoundException::new); + person.setAdmin(true); + personRepository.save(person); + } + + @Override + public void removeAdmin(Authentication caller, String uuid) throws UserNotFoundException { + if (!caller.getAuthorities().contains(new SimpleGrantedAuthority(UserRoles.ROLE_ADMIN.name()))) + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); + + var person = personRepository.findById(uuid).orElseThrow(UserNotFoundException::new); + // TODO + if (personRepository.findByAdminIsTrue().size() == 1) return; + + person.setAdmin(false); + personRepository.save(person); + } + + @Override + public Collection getAdmins() { + return personRepository.findByAdminIsTrue(); + } }