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();
+ }
}